Merge main

This commit is contained in:
Patrick Stevens
2024-06-14 21:58:47 +01:00
47 changed files with 2556 additions and 618 deletions

View File

@@ -13,7 +13,7 @@ jobs:
- name: "Checkout"
uses: "actions/checkout@v4"
- name: "Install Nix"
uses: "cachix/install-nix-action@v25"
uses: "cachix/install-nix-action@V27"
with: { "extra_nix_config": "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" }
- name: "Check flake"
run: "nix flake check --all-systems"

View File

@@ -1,9 +1,6 @@
This repository currently has no licence applied to it, except for the NeoVim configuration.
That configuration is in large part derived from https://github.com/amix/vimrc and is therefore provided under the following licence.
The MIT License (MIT)
Copyright (c) 2016 Amir Salihefendic
Copyright (c) 2024 Patrick Stevens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,5 +1,5 @@
{pkgs, ...}: let
python = import ./python.nix {inherit pkgs;};
mbsync = import ./mbsync.nix {inherit pkgs;};
in {
nix.useDaemon = true;
@@ -11,7 +11,7 @@ in {
pkgs.rustup
pkgs.libiconv
pkgs.clang
python
pkgs.python3
];
users.users.patrick = {
@@ -21,16 +21,58 @@ in {
# This line is required; otherwise, on shell startup, you won't have Nix stuff in the PATH.
programs.zsh.enable = true;
programs.gnupg.agent.enable = true;
# Use a custom configuration.nix location.
# $ darwin-rebuild switch -I darwin-config=$HOME/.config/nixpkgs/darwin/configuration.nix
environment.darwinConfig = "$HOME/.nixpkgs/darwin-configuration.nix";
launchd.agents = {
mbsync-btinternet = {
command = "${mbsync}/bin/mbsync BTInternet > /tmp/mbsync.btinternet.log 2>/tmp/mbsync.btinternet.2.log";
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 60;
RunAtLoad = true;
};
};
mbsync-proton = {
command = "${mbsync}/bin/mbsync Proton > /tmp/mbsync.proton.1.log 2>/tmp/mbsync.proton.2.log";
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 60;
RunAtLoad = true;
};
};
mbsync-gmail = {
command = "${mbsync}/bin/mbsync Gmail > /tmp/mbsync.gmail.1.log 2>/tmp/mbsync.gmail.2.log";
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 60;
RunAtLoad = true;
};
};
backup-calendar = {
command = ''${pkgs.bash}/bin/bash -c "mkdir -p /Users/patrick/Backups && if [ ! -d /Users/patrick/Backups/radicale ] ; then ${pkgs.git}/bin/git clone root@patrickstevens.co.uk:/preserve/radicale/data/.git /Users/patrick/Backups/radicale >/tmp/radicale.out.log 2>/tmp/radicale.err.log; fi && ${pkgs.git}/bin/git --git-dir /Users/patrick/Backups/radicale/.git pull 2>>/tmp/radicale.err.log"'';
serviceConfig = {
KeepAlive = false;
UserName = "patrick";
StartInterval = 3600;
RunAtLoad = true;
};
};
};
# Auto upgrade nix package and the daemon service.
services.nix-daemon.enable = true;
nix.package = pkgs.nixVersions.stable;
nix.gc.automatic = true;
nix.nixPath = ["darwin=/nix/store/zq4v3pi2wsfsrjkpk71kcn8srhbwjabf-nix-darwin"];
# Sandbox causes failure: https://github.com/NixOS/nix/issues/4119
nix.settings.sandbox = false;

88
flake.lock generated
View File

@@ -7,11 +7,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
"lastModified": 1705557527,
"narHash": "sha256-DuxxHTQ/W5KToFLWG4FUF8hLldNo9eXlbt7JgvhrMnY=",
"lastModified": 1716699938,
"narHash": "sha256-AzTYm22tTDJy0tSqMDf95rmCxxoGTPClu0paGPeh5a0=",
"owner": "tpwrules",
"repo": "nixos-apple-silicon",
"rev": "6e324ab06cb27a19409ebc1dc2664bf1e585490a",
"rev": "842306255856d7f5677c113e699101eb253e2e3f",
"type": "github"
},
"original": {
@@ -27,11 +27,11 @@
]
},
"locked": {
"lastModified": 1705915768,
"narHash": "sha256-+Jlz8OAqkOwJlioac9wtpsCnjgGYUhvLpgJR/5tP9po=",
"lastModified": 1716511055,
"narHash": "sha256-5Fe/DGgvMhPEMl9VdVxv3zvwRcwNDmW5eRJ0gk72w7U=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "1e706ef323de76236eb183d7784f3bd57255ec0b",
"rev": "0bea8222f6e83247dd13b055d83e64bce02ee532",
"type": "github"
},
"original": {
@@ -50,11 +50,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1706170797,
"narHash": "sha256-oGuFylWYU9OY5DaEJEK+Z7EL81Ln27xz01LN9+8U0P0=",
"lastModified": 1716800073,
"narHash": "sha256-ZznQFA/Mjomt1phpfwVFtp3F2b6yvlyslmjJ6bgBB5w=",
"owner": "nix-community",
"repo": "emacs-overlay",
"rev": "dd5d758f69dd1ae6d0399763aa73ca34974ce9e3",
"rev": "165e4bc50493e402cf296c413edb479b04aeb339",
"type": "github"
},
"original": {
@@ -83,11 +83,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@@ -121,11 +121,11 @@
]
},
"locked": {
"lastModified": 1706134977,
"narHash": "sha256-KwNb1Li3K6vuVwZ77tFjZ89AWBo7AiCs9t0Cens4BsM=",
"lastModified": 1716736760,
"narHash": "sha256-h3RmnNknKYtVA+EvUSra6QAwfZjC2q1G8YA7W0gat8Y=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "6359d40f6ec0b72a38e02b333f343c3d4929ec10",
"rev": "5d151429e1e79107acf6d06dcc5ace4e642ec239",
"type": "github"
},
"original": {
@@ -148,27 +148,27 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1705316053,
"narHash": "sha256-J2Ey5mPFT8gdfL2XC0JTZvKaBw/b2pnyudEXFvl+dQM=",
"lastModified": 1716293225,
"narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c3e128f3c0ecc1fb04aef9f72b3dcc2f6cecf370",
"rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c3e128f3c0ecc1fb04aef9f72b3dcc2f6cecf370",
"rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1705916986,
"narHash": "sha256-iBpfltu6QvN4xMpen6jGGEb6jOqmmVQKUrXdOJ32u8w=",
"lastModified": 1716633019,
"narHash": "sha256-xim1b5/HZYbWaZKyI7cn9TJCM6ewNVZnesRr00mXeS4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d7f206b723e42edb09d9d753020a84b3061a79d8",
"rev": "9d29cd266cebf80234c98dd0b87256b6be0af44e",
"type": "github"
},
"original": {
@@ -178,29 +178,13 @@
"type": "github"
}
},
"nixpkgs-stable_2": {
"locked": {
"lastModified": 1705033721,
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1706006310,
"narHash": "sha256-nDPz0fj0IFcDhSTlXBU2aixcnGs2Jm4Zcuoj0QtmiXQ=",
"lastModified": 1716715802,
"narHash": "sha256-usk0vE7VlxPX8jOavrtpOqphdfqEQpf9lgedlY/r66c=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b43bb235efeab5324c5e486882ef46749188eee2",
"rev": "e2dd4e18cc1c7314e24154331bae07df76eb582f",
"type": "github"
},
"original": {
@@ -232,7 +216,6 @@
"emacs": "emacs",
"home-manager": "home-manager",
"nixpkgs": "nixpkgs_2",
"sops-nix": "sops-nix",
"whisper": "whisper"
}
},
@@ -252,27 +235,6 @@
"type": "github"
}
},
"sops-nix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable_2"
},
"locked": {
"lastModified": 1706130372,
"narHash": "sha256-fHZxKH1DhsXPP36a2vJ91Zy6S+q6+QRIFlpLr9fZHU8=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "4606d9b1595e42ffd9b75b9e69667708c70b1d68",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,

View File

@@ -18,10 +18,6 @@
url = "github:nix-community/emacs-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
apple-silicon = {
url = "github:tpwrules/nixos-apple-silicon";
};
@@ -31,23 +27,21 @@
};
outputs = {
self,
darwin,
emacs,
nixpkgs,
home-manager,
sops-nix,
apple-silicon,
whisper,
...
} @ inputs: let
}: let
config = {
# contentAddressedByDefault = true;
allowUnfree = true;
};
systems = ["aarch64-darwin" "aarch64-linux" "x86_64-linux"];
in let
overlays = [emacs.overlay] ++ import ./overlays.nix;
overlays = [emacs.overlay];
recursiveMerge = attrList: let
f = attrPath:
builtins.zipAttrsWith (n: values:
@@ -62,6 +56,31 @@
f [] attrList;
in {
nixosConfigurations = {
capybara = let
system = "x86_64-linux";
in let
pkgs = import nixpkgs {inherit system config overlays;};
in
nixpkgs.lib.nixosSystem {
inherit system;
modules = let
args = {
nixpkgs = pkgs;
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
mbsync = import ./mbsync.nix {inherit pkgs;};
secretsPath = "/home/patrick/.secrets/";
};
in [
./home-manager/capybara-config.nix
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.patrick = recursiveMerge [(import ./home-manager/linux.nix args) (import ./home-manager/home.nix args)];
}
];
};
earthworm = let
system = "aarch64-linux";
in let
@@ -74,6 +93,8 @@
nixpkgs = pkgs;
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
mbsync = import ./mbsync.nix {inherit pkgs;};
secretsPath = "/home/patrick/.secrets/";
};
in [
./home-manager/earthworm-config.nix
@@ -82,7 +103,7 @@
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.patrick = recursiveMerge [(import ./home-manager/earthworm.nix args) (import ./home-manager/home.nix args)];
home-manager.users.patrick = recursiveMerge [(import ./home-manager/linux.nix args) (import ./home-manager/home.nix args)];
}
];
};
@@ -101,10 +122,11 @@
username = "patrick";
dotnet = pkgs.dotnet-sdk_8;
whisper = whisper.packages.${system};
mbsync = import ./mbsync.nix {inherit pkgs;};
secretsPath = "/Users/patrick/.secrets/";
};
in [
./darwin-configuration.nix
sops-nix.nixosModules.sops
home-manager.darwinModules.home-manager
{
home-manager.useGlobalPkgs = true;
@@ -121,11 +143,12 @@
pkgs.stdenvNoCC.mkDerivation {
name = "fmt-check";
src = ./.;
nativeBuildInputs = [pkgs.alejandra pkgs.shellcheck pkgs.shfmt];
nativeBuildInputs = [pkgs.alejandra pkgs.shellcheck pkgs.shfmt pkgs.stylua];
checkPhase = ''
find . -type f -name '*.sh' | xargs shfmt -d -s -i 2 -ci
alejandra -c .
find . -type f -name '*.sh' -exec shellcheck -x {} \;
find . -type f -name '*.lua' -exec stylua --check {} \;
'';
installPhase = "mkdir $out";
dontBuild = true;
@@ -143,7 +166,7 @@
pkgs = import nixpkgs {inherit config system;};
in {
default = pkgs.mkShell {
buildInputs = [pkgs.alejandra pkgs.shellcheck];
buildInputs = [pkgs.alejandra pkgs.shellcheck pkgs.stylua];
};
}
);

34
hardware/capybara.nix Normal file
View File

@@ -0,0 +1,34 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usb_storage" "usbhid" "sd_mod"];
boot.initrd.kernelModules = [];
boot.kernelModules = ["kvm-intel"];
boot.extraModulePackages = [];
fileSystems."/" = {
device = "/dev/disk/by-uuid/63c5394d-55ce-48a9-8d7c-2b68f3b5f834";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/nvme0n1p2";
fsType = "vfat";
};
swapDevices = [];
powerManagement.cpuFreqGovernor = lib.mkDefault "powersave";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View File

@@ -0,0 +1,70 @@
{
pkgs,
config,
...
}: {
nixpkgs.config.allowUnfree = true;
imports = [
../hardware/capybara.nix
];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.loader.grub.useOSProber = true;
boot.extraModulePackages = [config.boot.kernelPackages.rtl8821au];
networking = {
hostName = "capybara";
networkmanager.enable = true;
};
time.timeZone = "Europe/London";
programs.sway.enable = true;
programs.zsh.enable = true;
# TODO: work out secrets management for password, then set mutableUsers to false
users.mutableUsers = true;
users.users.patrick = {
isNormalUser = true;
extraGroups = ["wheel" "networkManager"];
};
services.syncthing = {
enable = true;
user = "patrick";
dataDir = "/home/patrick/syncthing";
};
environment.systemPackages = [
pkgs.vim
pkgs.wget
pkgs.tmux
pkgs.home-manager
pkgs.firefox
pkgs.steam-run
];
environment.loginShellInit = ''
[[ "$(tty)" == /dev/tty1 ]] && sway
'';
services.openssh.enable = true;
system.stateVersion = "23.11";
nix.settings.experimental-features = ["nix-command" "flakes" "ca-derivations"];
nix.gc.automatic = true;
nix.extraOptions = ''
auto-optimise-store = true
max-jobs = auto
keep-outputs = true
keep-derivations = true
'';
programs.steam = {
enable = true;
remotePlay.openFirewall = true;
};
}

View File

@@ -1,8 +1,4 @@
{
config,
pkgs,
...
}: {
{pkgs, ...}: {
imports = [
../hardware/earthworm.nix
];

View File

@@ -1,7 +1,9 @@
{
nixpkgs,
username,
mbsync,
dotnet,
secretsPath,
...
}: {
# Let Home Manager install and manage itself.
@@ -21,59 +23,42 @@
# changes in each release.
home.stateVersion = "22.05";
programs.tmux = {
shell = "\${nixpkgs.zsh}/bin/zsh";
escapeTime = 50;
mouse = false;
prefix = "C-b";
};
fonts.fontconfig.enable = true;
programs.zsh = {
enable = true;
autocd = true;
enableAutosuggestions = true;
enableCompletion = true;
history = {
expireDuplicatesFirst = true;
};
oh-my-zsh = {
enable = true;
plugins = ["git" "macos" "dircycle" "timer"];
theme = "robbyrussell";
};
sessionVariables = {
EDITOR = "vim";
LC_ALL = "en_US.UTF-8";
LC_CTYPE = "en_US.UTF-8";
RUSTFLAGS = "-L ${nixpkgs.libiconv}/lib -L ${nixpkgs.libcxxabi}/lib -L ${nixpkgs.libcxx}/lib";
RUST_BACKTRACE = "full";
};
shellAliases = {
vim = "nvim";
view = "vim -R";
grep = "${nixpkgs.ripgrep}/bin/rg";
};
sessionVariables = {
RIPGREP_CONFIG_PATH = "/Users/${username}/.config/ripgrep/config";
};
};
imports = [
# ./modules/agda.nix
# ./modules/emacs.nix
./modules/direnv.nix
./modules/tmux.nix
./modules/zsh.nix
./modules/ripgrep.nix
./modules/alacritty.nix
./modules/rust.nix
(import ./modules/mail.nix
{
inherit mbsync secretsPath;
pkgs = nixpkgs;
})
];
programs.fzf = {
enable = true;
enableZshIntegration = true;
};
programs.git = {
package = nixpkgs.gitAndTools.gitFull;
enable = true;
userName = "Smaug123";
userEmail = "patrick+github@patrickstevens.co.uk";
userEmail = "3138005+Smaug123@users.noreply.github.com";
aliases = {
co = "checkout";
st = "status";
};
delta = {enable = true;};
difftastic.enable = true;
extraConfig = {
commit.gpgsign = true;
gpg.program = "${nixpkgs.gnupg}/bin/gpg";
user.signingkey = "7C97D679CF3BC4F9";
core = {
autocrlf = "input";
};
@@ -135,49 +120,146 @@
};
};
programs.neovim.enable = true;
programs.neovim.plugins = with nixpkgs.vimPlugins; [
molokai
tagbar
fzf-vim
Ionide-vim
{
plugin = rust-vim;
config = "let g:rustfmt_autosave = 1";
}
{
plugin = LanguageClient-neovim;
config = "let g:LanguageClient_serverCommands = { 'nix': ['rnix-lsp'] }";
}
{
plugin = syntastic;
config = '' let g:syntastic_rust_checkers = ['cargo']
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0'';
}
# YouCompleteMe
tagbar
];
programs.neovim.viAlias = true;
programs.neovim.vimAlias = true;
programs.neovim.vimdiffAlias = true;
programs.neovim.withPython3 = true;
programs.neovim.extraConfig = builtins.readFile ./init.vim;
programs.direnv = {
services.syncthing = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
programs.neovim = let
debugPyEnv = nixpkgs.python3.withPackages (ps: [ps.debugpy]);
in {
enable = true;
plugins = [
{
plugin = nixpkgs.vimPlugins.nvim-lightbulb;
type = "lua";
config = builtins.readFile ./nvim/nvim-lightbulb.lua;
}
{
plugin = nixpkgs.vimPlugins.lean-nvim;
type = "lua";
config = builtins.readFile ./nvim/lean.lua;
}
{
plugin = nixpkgs.vimPlugins.which-key-nvim;
type = "lua";
config = builtins.readFile ./nvim/which-key.lua;
}
{
plugin = nixpkgs.vimPlugins.tokyonight-nvim;
config = builtins.readFile ./nvim/tokyonight.lua;
type = "lua";
}
{
plugin = nixpkgs.vimPlugins.nvim-treesitter.withAllGrammars;
config = builtins.readFile ./nvim/treesitter.lua;
type = "lua";
}
{
plugin = nixpkgs.vimPlugins.nvim-lspconfig;
config = builtins.readFile ./nvim/lspconfig.lua;
type = "lua";
}
nixpkgs.vimPlugins.telescope-nvim
nixpkgs.vimPlugins.tagbar
nixpkgs.vimPlugins.fzf-vim
{
plugin = nixpkgs.vimPlugins.roslyn-nvim;
config = builtins.readFile ./nvim/roslyn-nvim.lua;
type = "lua";
}
{
plugin = let
name = "coq.artifacts";
rev = "9c5067a471322c6bb866545e88e5b28c82511865";
in
nixpkgs.vimUtils.buildVimPlugin {
name = name;
src = nixpkgs.fetchFromGitHub {
owner = "ms-jpq";
repo = name;
rev = rev;
hash = "sha256-BHm7U3pINtYamY7m26I4lQee7ccJ6AcHmYx7j1MRFDA=";
};
};
}
{
plugin = let
name = "venv-selector.nvim";
rev = "2ad34f36d498ff5193ea10f79c87688bd5284172";
in
nixpkgs.vimUtils.buildVimPlugin {
name = name;
src = nixpkgs.fetchFromGitHub {
owner = "linux-cultist";
repo = name;
rev = rev;
hash = "sha256-aOga7kJ1y3T2vDyYFl/XHOwk35ZqeUcfPUk+Pr1mIeo=";
};
};
config = builtins.readFile ./nvim/venv-selector.lua;
type = "lua";
}
{
plugin = nixpkgs.vimPlugins.Ionide-vim;
type = "lua";
config = builtins.readFile ./nvim/ionide-vim.lua;
}
{
plugin = nixpkgs.vimPlugins.chadtree;
config = builtins.readFile ./nvim/chadtree.lua;
type = "lua";
}
{
plugin = nixpkgs.vimPlugins.coq_nvim;
config = ''let g:coq_settings = { 'auto_start': 'shut-up', 'xdg': v:true }'';
}
{
plugin = nixpkgs.vimPlugins.rustaceanvim;
}
{
plugin = nixpkgs.vimPlugins.LanguageClient-neovim;
}
{
plugin = nixpkgs.vimPlugins.nvim-dap;
config = builtins.readFile ./nvim/nvim-dap.lua;
type = "lua";
}
{
plugin = nixpkgs.vimPlugins.nvim-dap-python;
config = builtins.replaceStrings ["%PYTHONENV%"] ["${debugPyEnv}"] (builtins.readFile ./nvim/nvim-dap-python.lua);
type = "lua";
}
];
viAlias = true;
vimAlias = true;
vimdiffAlias = true;
withPython3 = true;
extraPython3Packages = ps: [
ps.pynvim
ps.pynvim-pp
ps.pyyaml
ps.std2
];
withRuby = true;
extraLuaConfig = builtins.readFile ./nvim/build-utils.lua + "\n" + builtins.readFile ./nvim/dotnet.lua + "\n" + builtins.readFile ./nvim/init.lua + "\n" + builtins.readFile ./nvim/python.lua;
};
home.packages = [
nixpkgs.difftastic
nixpkgs.syncthing
nixpkgs.nodePackages_latest.dockerfile-language-server-nodejs
nixpkgs.nodePackages_latest.bash-language-server
nixpkgs.nodePackages_latest.vscode-json-languageserver
nixpkgs.nodePackages_latest.vscode-langservers-extracted
nixpkgs.hadolint
nixpkgs.ltex-ls
nixpkgs.yaml-language-server
nixpkgs.csharp-ls
nixpkgs.netcoredbg
nixpkgs.nil
nixpkgs.fsautocomplete
nixpkgs.keepassxc
nixpkgs.rust-analyzer
nixpkgs.tmux
nixpkgs.wget
nixpkgs.yt-dlp
nixpkgs.cmake
@@ -188,54 +270,33 @@
nixpkgs.hledger-web
dotnet
nixpkgs.jitsi-meet
nixpkgs.ripgrep
nixpkgs.elan
nixpkgs.coreutils-prefixed
nixpkgs.shellcheck
nixpkgs.html-tidy
nixpkgs.hugo
nixpkgs.agda
nixpkgs.pijul
nixpkgs.universal-ctags
nixpkgs.asciinema
nixpkgs.git-lfs
nixpkgs.imagemagick
nixpkgs.nixpkgs-fmt
nixpkgs.rnix-lsp
nixpkgs.grpc-tools
nixpkgs.element-desktop
nixpkgs.ihp-new
nixpkgs.direnv
nixpkgs.lnav
nixpkgs.age
nixpkgs.nodejs
nixpkgs.sqlitebrowser
nixpkgs.typst
nixpkgs.poetry
nixpkgs.nodePackages.pyright
nixpkgs.woodpecker-agent
nixpkgs.alacritty
nixpkgs.lynx
nixpkgs.alejandra
nixpkgs.ffmpeg
nixpkgs.bat
nixpkgs.pandoc
nixpkgs.fd
nixpkgs.sumneko-lua-language-server
nixpkgs.gnupg
];
home.file.".mailcap".source = ./mailcap;
home.file.".ideavimrc".source = ./ideavimrc;
home.file.".config/yt-dlp/config".source = ./youtube-dl.conf;
home.file.".config/ripgrep/config".source = ./ripgrep.conf;
programs.emacs = {
enable = true;
package = nixpkgs.emacs;
extraPackages = epkgs: [];
extraConfig = ''
(load-file (let ((coding-system-for-read 'utf-8))
(shell-command-to-string "agda-mode locate")))
'';
};
programs.firefox = {
enable = true;
profiles = {
@@ -316,5 +377,15 @@
};
};
programs.emacs = {
enable = true;
package = nixpkgs.emacs;
extraPackages = epkgs: [];
extraConfig = ''
(load-file (let ((coding-system-for-read 'utf-8))
(shell-command-to-string "agda-mode locate")))
'';
};
home.file.".cargo/config.toml".source = ./cargo-config.toml;
}

View File

@@ -1,371 +0,0 @@
set nu
colorscheme molokai
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Maintainer:
" Amir Salihefendic — @amix3k
"
" Awesome_version:
" Get this config, nice color schemes and lots of plugins!
"
" Install the awesome version from:
"
" https://github.com/amix/vimrc
"
" Sections:
" -> General
" -> VIM user interface
" -> Colors and Fonts
" -> Files and backups
" -> Text, tab and indent related
" -> Visual mode related
" -> Moving around, tabs and buffers
" -> Status line
" -> Editing mappings
" -> vimgrep searching and cope displaying
" -> Spell checking
" -> Misc
" -> Helper functions
"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => General
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Sets how many lines of history VIM has to remember
set history=500
" Enable filetype plugins
filetype plugin on
filetype indent on
" Set to auto read when a file is changed from the outside
set autoread
" With a map leader it's possible to do extra key combinations
" like <leader>w saves the current file
let mapleader = "`"
" :W sudo saves the file
" (useful for handling the permission-denied error)
command W w !sudo tee % > /dev/null
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => VIM user interface
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Set 7 lines to the cursor - when moving vertically using j/k
set so=7
" Avoid garbled characters in Chinese language windows OS
let $LANG='en'
set langmenu=en
source $VIMRUNTIME/delmenu.vim
source $VIMRUNTIME/menu.vim
" Turn on the Wild menu
set wildmenu
" Ignore compiled files
set wildignore=*.o,*~,*.pyc
if has("win16") || has("win32")
set wildignore+=.git\*,.hg\*,.svn\*
else
set wildignore+=*/.git/*,*/.hg/*,*/.svn/*,*/.DS_Store
endif
"Always show current position
set ruler
" Height of the command bar
set cmdheight=2
" A buffer becomes hidden when it is abandoned
set hid
" Ignore case when searching
set ignorecase
" When searching try to be smart about cases
set smartcase
" Highlight search results
set hlsearch
" Makes search act like search in modern browsers
set incsearch
" Don't redraw while executing macros (good performance config)
set lazyredraw
" For regular expressions turn magic on
set magic
" Show matching brackets when text indicator is over them
set showmatch
" How many tenths of a second to blink when matching brackets
set mat=2
" No annoying sound on errors
set noerrorbells
set novisualbell
set t_vb=
set tm=500
" Properly disable sound on errors on MacVim
if has("gui_macvim")
autocmd GUIEnter * set vb t_vb=
endif
" Add a bit extra margin to the left
set foldcolumn=1
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Colors and Fonts
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Enable syntax highlighting
syntax enable
" Enable 256 colors palette in Gnome Terminal
if $COLORTERM == 'gnome-terminal'
set t_Co=256
endif
set background=dark
" Set extra options when running in GUI mode
if has("gui_running")
set guioptions-=T
set guioptions-=e
set t_Co=256
set guitablabel=%M\ %t
endif
" Set utf8 as standard encoding and en_US as the standard language
set encoding=utf8
" Use Unix as the standard file type
set ffs=unix,mac
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Files, backups and undo
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Turn backup off, since most stuff is in SVN, git et.c anyway...
set nobackup
set nowb
set noswapfile
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Text, tab and indent related
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Use spaces instead of tabs
set expandtab
" Be smart when using tabs ;)
set smarttab
" 1 tab == 4 spaces
set shiftwidth=4
set tabstop=4
" Linebreak on 500 characters
set lbr
set tw=500
set ai "Auto indent
set si "Smart indent
set wrap "Wrap lines
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Moving around, tabs, windows and buffers
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Map <Space> to / (search) and Ctrl-<Space> to ? (backwards search)
map <space> /
map <c-space> ?
" Disable highlight when <leader><cr> is pressed
map <silent> <leader><cr> :noh<cr>
" Smart way to move between windows
map <C-j> <C-W>j
map <C-k> <C-W>k
map <C-h> <C-W>h
map <C-l> <C-W>l
" Close the current buffer
map <leader>bd :Bclose<cr>:tabclose<cr>gT
" Close all the buffers
map <leader>ba :bufdo bd<cr>
map <leader>l :bnext<cr>
map <leader>h :bprevious<cr>
" Useful mappings for managing tabs
map <leader>tn :tabnew<cr>
map <leader>to :tabonly<cr>
map <leader>tc :tabclose<cr>
map <leader>tm :tabmove
map <leader>t<leader> :tabnext
" Let 'tl' toggle between this and the last accessed tab
let g:lasttab = 1
nmap <Leader>tl :exe "tabn ".g:lasttab<CR>
au TabLeave * let g:lasttab = tabpagenr()
" Opens a new tab with the current buffer's path
" Super useful when editing files in the same directory
map <leader>te :tabedit <c-r>=expand("%:p:h")<cr>/
" Switch CWD to the directory of the open buffer
map <leader>cd :cd %:p:h<cr>:pwd<cr>
" Specify the behavior when switching between buffers
try
set switchbuf=useopen,usetab,newtab
set stal=2
catch
endtry
" Return to last edit position when opening files (You want this!)
au BufReadPost * if line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"" | endif
""""""""""""""""""""""""""""""
" => Status line
""""""""""""""""""""""""""""""
" Always show the status line
set laststatus=2
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Editing mappings
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Delete trailing white space on save, useful for some filetypes ;)
fun! CleanExtraSpaces()
let save_cursor = getpos(".")
let old_query = getreg('/')
silent! %s/\s\+$//e
call setpos('.', save_cursor)
call setreg('/', old_query)
endfun
if has("autocmd")
autocmd BufWritePre *.fs,*.fsi,*.txt,*.js,*.py,*.wiki,*.sh,*.coffee :call CleanExtraSpaces()
endif
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Spell checking
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Pressing ,ss will toggle and untoggle spell checking
map <leader>ss :setlocal spell!<cr>
" Shortcuts using <leader>
map <leader>sn ]s
map <leader>sp [s
map <leader>sa zg
map <leader>s? z=
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Misc
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Remove the Windows ^M - when the encodings gets messed up
noremap <Leader>m mmHmt:%s/<C-V><cr>//ge<cr>'tzt'm
" Quickly open a buffer for scribble
map <leader>q :e ~/buffer<cr>
" Quickly open a markdown buffer for scribble
map <leader>x :e ~/buffer.md<cr>
" Toggle paste mode on and off
map <leader>pp :setlocal paste!<cr>
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Helper functions
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Returns true if paste mode is enabled
function! HasPaste()
if &paste
return 'PASTE MODE '
endif
return ''
endfunction
" Don't close window, when deleting a buffer
command! Bclose call <SID>BufcloseCloseIt()
function! <SID>BufcloseCloseIt()
let l:currentBufNum = bufnr("%")
let l:alternateBufNum = bufnr("#")
if buflisted(l:alternateBufNum)
buffer #
else
bnext
endif
if bufnr("%") == l:currentBufNum
new
endif
if buflisted(l:currentBufNum)
execute("bdelete! ".l:currentBufNum)
endif
endfunction
function! CmdLine(str)
call feedkeys(":" . a:str)
endfunction
function! VisualSelection(direction, extra_filter) range
let l:saved_reg = @"
execute "normal! vgvy"
let l:pattern = escape(@", "\\/.*'$^~[]")
let l:pattern = substitute(l:pattern, "\n$", "", "")
if a:direction == 'gv'
call CmdLine("Ack '" . l:pattern . "' " )
elseif a:direction == 'replace'
call CmdLine("%s" . '/'. l:pattern . '/')
endif
let @/ = l:pattern
let @" = l:saved_reg
endfunction
nnoremap <leader>c :!cargo clippy
nnoremap <leader>j :%!python -m json.tool
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
" Format the status line
set statusline=\ %{HasPaste()}%F%m%r%h\ %w\ \ CWD:\ %r%{getcwd()}%h\ \ \ Line:\ %l\ \ Column:\ %c
set fileformat=unix
let g:fsharp#fsautocomplete_command =
\ [ 'dotnet',
\ 'fsautocomplete',
\ '--background-service-enabled'
\ ]
let g:fsharp#show_signature_on_cursor_move = 1
if has('nvim') && exists('*nvim_open_win')
augroup FSharpGroup
autocmd!
autocmd FileType fsharp nnoremap <leader>t :call fsharp#showTooltip()<CR>
augroup END
endif

View File

@@ -0,0 +1,7 @@
{pkgs, ...}: {
imports = [./emacs.nix];
home.packages = [
pkgs.agda
];
}

View File

@@ -0,0 +1,17 @@
{pkgs, ...}: {
programs.alacritty = {
enable = true;
settings = {
font = {
normal = {
family = "FiraCode Nerd Font Mono";
};
};
};
};
home.packages = [
pkgs.alacritty
(pkgs.nerdfonts.override {fonts = ["FiraCode" "DroidSansMono"];})
];
}

View File

@@ -0,0 +1,10 @@
{pkgs, ...}: {
home.packages = [
pkgs.direnv
];
programs.direnv = {
enable = true;
enableZshIntegration = true;
nix-direnv.enable = true;
};
}

View File

@@ -0,0 +1,14 @@
{pkgs, ...}: {
programs.emacs = {
enable = true;
package = pkgs.emacs;
extraPackages = epkgs: [epkgs.evil];
extraConfig = ''
(load-file (let ((coding-system-for-read 'utf-8))
(shell-command-to-string "agda-mode locate")))
(require 'evil)
(evil-mode 1)
(evil-set-undo-system 'undo-redo)
'';
};
}

View File

@@ -0,0 +1,179 @@
{
pkgs,
mbsync,
secretsPath,
...
}: let
deobfuscate = str: let
lib = pkgs.lib;
base64Table =
builtins.listToAttrs
(lib.imap0 (i: c: lib.nameValuePair c i)
(lib.stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"));
# Generated using python3:
# print(''.join([ chr(n) for n in range(1, 256) ]), file=open('ascii', 'w'))
ascii = builtins.readFile ./mail/ascii;
# List of base-64 numbers
numbers64 = map (c: base64Table.${c}) (lib.lists.reverseList (lib.stringToCharacters str));
# List of base-256 numbers
numbers256 = lib.concatLists (lib.genList (
i: let
v =
lib.foldl'
(acc: el: acc * 64 + el)
0
(lib.sublist (i * 4) 4 numbers64);
in [
(lib.mod (v / 256 / 256) 256)
(lib.mod (v / 256) 256)
(lib.mod v 256)
]
) (lib.length numbers64 / 4));
in
# Converts base-256 numbers to ascii
lib.concatMapStrings (
n:
# Can't represent the null byte in Nix..
let
result = lib.substring (n - 1) 1 ascii;
in
if result == " "
then ""
else result
)
numbers256;
in {
accounts.email.accounts."Gmail" = let
address = (deobfuscate "AFTN0cWdh12c") + "gmail.com";
in {
notmuch.enable = true;
neomutt = {
enable = true;
};
address = address;
flavor = "gmail.com";
mbsync = {
enable = true;
create = "maildir";
extraConfig.account = {
AuthMechs = "XOAUTH2";
};
};
userName = address;
# This is accompanied by a developer application at Google:
# https://console.cloud.google.com/apis/credentials
# Create an OAuth 2.0 Client ID with type `Desktop`.
# The Google application needs the https://mail.google.com scope; mine has
# an authorized domain `google.com` but I don't know if that's required.
# Enter the client ID and client secret into a two-line text file
# named gmail-client-app.txt immediately next to the intended destination
# secret file (the arg to mutt-oauth2.py in the invocation):
# so here it would be /path/to/gmail-client-app.txt .
# Run `./mail/mutt-oauth2.py /path/to/secret --authorize --verbose` once manually,
# and that will populate /path/to/secret.
# I've left it unencrypted here; the original uses GPG to store it encrypted at rest.
passwordCommand = ''${pkgs.python3}/bin/python ${./mail/mutt-oauth2.py} ${secretsPath}/gmail.txt'';
realName = "Patrick Stevens";
};
accounts.email.accounts."BTInternet" = let
address = (deobfuscate "z5WZ2VGdz5yajlmc0FGc") + "@btinternet.com";
in {
notmuch.enable = true;
neomutt = {
enable = true;
};
address = address;
imap = {
host = "mail.btinternet.com";
port = 993;
tls = {
enable = true;
useStartTls = false;
};
};
mbsync = {
enable = true;
create = "maildir";
};
realName = "Patrick Stevens";
passwordCommand = "${pkgs.coreutils}/bin/cat ${secretsPath}/btinternet.txt";
smtp = {
host = "mail.btinternet.com";
port = 465;
tls = {
enable = true;
useStartTls = false;
};
};
userName = address;
primary = true;
};
accounts.email.accounts."Proton" = let
address = deobfuscate "gAya15ybj5ycuVmdlR3crNWayRXYwB0ajlmc0FGc";
in {
notmuch.enable = true;
neomutt = {
enable = true;
};
address = address;
# I use the ProtonMail bridge, which sits at localhost.
imap = {
host = "127.0.0.1";
port = 1143; # 8125; if using hydroxide
tls = {
enable = false;
useStartTls = true;
};
};
mbsync = {
enable = true;
create = "maildir";
extraConfig.account = {
# Because ProtonMail Bridge is localhost, we don't
# care that we can only auth to it in plain text.
AuthMechs = "LOGIN";
};
};
realName = "Patrick Stevens";
passwordCommand =
# I store the ProtonMail Bridge password here.
# Extracting it from a keychain would be better.
"${pkgs.coreutils}/bin/cat ${secretsPath}/proton.txt";
smtp = {
host = "127.0.0.1";
port = 1025; # 8126; if using hydroxide
tls = {enable = false;};
};
userName = address;
};
programs.mbsync = {
enable = true;
extraConfig = ''
CopyArrivalDate yes
'';
package = mbsync;
};
programs.neomutt = {
enable = true;
extraConfig = ''
set use_threads=threads sort=last-date sort_aux=date
'';
sidebar.enable = true;
vimKeys = true;
};
programs.notmuch.enable = true;
home.file.".mailcap".source = ./mail/mailcap;
home.packages = [
pkgs.notmuch
pkgs.lynx
];
}

View File

@@ -0,0 +1,2 @@


View File

@@ -0,0 +1,402 @@
#!/usr/bin/env python3
#
# Mutt OAuth2 token management script, version 2020-08-07
# Written against python 3.7.3, not tried with earlier python versions.
#
# Copyright (C) 2020 Alexander Perlis
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
# Subsequently adapted by Patrick Stevens, who hacked it up to read gmail
# client app configuration from a file called gmail-client-app.txt that
# lives next to the secret file.
'''Mutt OAuth2 token management'''
import sys
import json
import argparse
import urllib.error
import urllib.parse
import urllib.request
import imaplib
import poplib
import smtplib
import base64
import secrets
import hashlib
import time
from datetime import timedelta, datetime
from pathlib import Path
import socket
import http.server
import subprocess
ap = argparse.ArgumentParser(epilog='''
This script obtains and prints a valid OAuth2 access token. State is maintained in an
encrypted TOKENFILE. Run with "--verbose --authorize" to get started or whenever all
tokens have expired, optionally with "--authflow" to override the default authorization
flow. To truly start over from scratch, first delete TOKENFILE. Use "--verbose --test"
to test the IMAP/POP/SMTP endpoints.
''')
ap.add_argument('-v', '--verbose', action='store_true', help='increase verbosity')
ap.add_argument('-d', '--debug', action='store_true', help='enable debug output')
ap.add_argument('tokenfile', help='persistent token storage')
ap.add_argument('-a', '--authorize', action='store_true', help='manually authorize new tokens')
ap.add_argument('--authflow', help='authcode | localhostauthcode | devicecode')
ap.add_argument('-t', '--test', action='store_true', help='test IMAP/POP/SMTP endpoints')
args = ap.parse_args()
token = {}
path = Path(args.tokenfile)
if path.exists():
if 0o777 & path.stat().st_mode != 0o600:
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
try:
token = json.loads(path.read_bytes())
except subprocess.CalledProcessError:
sys.exit('Difficulty decrypting token file. Is your decryption agent primed for '
'non-interactive usage, or an appropriate environment variable such as '
'GPG_TTY set to allow interactive agent usage from inside a pipe?')
client_id, client_secret = (path.parent / "gmail-client-app.txt").read_text().strip().split('\n')
registrations = {
'google': {
'authorize_endpoint': 'https://accounts.google.com/o/oauth2/auth',
'devicecode_endpoint': 'https://oauth2.googleapis.com/device/code',
'token_endpoint': 'https://accounts.google.com/o/oauth2/token',
'redirect_uri': 'urn:ietf:wg:oauth:2.0:oob',
'imap_endpoint': 'imap.gmail.com',
'pop_endpoint': 'pop.gmail.com',
'smtp_endpoint': 'smtp.gmail.com',
'sasl_method': 'OAUTHBEARER',
'scope': 'https://mail.google.com/',
'client_id': client_id,
'client_secret': client_secret,
},
}
def writetokenfile():
'''Writes global token dictionary into token file.'''
if not path.exists():
path.touch(mode=0o600)
if 0o777 & path.stat().st_mode != 0o600:
sys.exit('Token file has unsafe mode. Suggest deleting and starting over.')
path.write_bytes(json.dumps(token).encode('utf-8'))
if args.debug:
print('Obtained from token file:', json.dumps(token))
if not token:
if not args.authorize:
sys.exit('You must run script with "--authorize" at least once.')
print('Available app and endpoint registrations:', *registrations)
token['registration'] = input('OAuth2 registration: ')
token['authflow'] = input('Preferred OAuth2 flow ("authcode" or "localhostauthcode" '
'or "devicecode"): ')
token['email'] = input('Account e-mail address: ')
token['access_token'] = ''
token['access_token_expiration'] = ''
token['refresh_token'] = ''
writetokenfile()
if token['registration'] not in registrations:
sys.exit(f'ERROR: Unknown registration "{token["registration"]}". Delete token file '
f'and start over.')
registration = registrations[token['registration']]
authflow = token['authflow']
if args.authflow:
authflow = args.authflow
baseparams = {'client_id': registration['client_id']}
# Microsoft uses 'tenant' but Google does not
if 'tenant' in registration:
baseparams['tenant'] = registration['tenant']
def access_token_valid():
'''Returns True when stored access token exists and is still valid at this time.'''
token_exp = token['access_token_expiration']
return token_exp and datetime.now() < datetime.fromisoformat(token_exp)
def update_tokens(r):
'''Takes a response dictionary, extracts tokens out of it, and updates token file.'''
token['access_token'] = r['access_token']
token['access_token_expiration'] = (datetime.now() +
timedelta(seconds=int(r['expires_in']))).isoformat()
if 'refresh_token' in r:
token['refresh_token'] = r['refresh_token']
writetokenfile()
if args.verbose:
print(f'NOTICE: Obtained new access token, expires {token["access_token_expiration"]}.')
if args.authorize:
p = baseparams.copy()
p['scope'] = registration['scope']
if authflow in ('authcode', 'localhostauthcode'):
verifier = secrets.token_urlsafe(90)
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest())[:-1]
redirect_uri = registration['redirect_uri']
listen_port = 0
if authflow == 'localhostauthcode':
# Find an available port to listen on
s = socket.socket()
s.bind(('127.0.0.1', 0))
listen_port = s.getsockname()[1]
s.close()
redirect_uri = 'http://localhost:'+str(listen_port)+'/'
# Probably should edit the port number into the actual redirect URL.
p.update({'login_hint': token['email'],
'response_type': 'code',
'redirect_uri': redirect_uri,
'code_challenge': challenge,
'code_challenge_method': 'S256'})
print(registration["authorize_endpoint"] + '?' +
urllib.parse.urlencode(p, quote_via=urllib.parse.quote))
authcode = ''
if authflow == 'authcode':
authcode = input('Visit displayed URL to retrieve authorization code. Enter '
'code from server (might be in browser address bar): ')
else:
print('Visit displayed URL to authorize this application. Waiting...',
end='', flush=True)
class MyHandler(http.server.BaseHTTPRequestHandler):
'''Handles the browser query resulting from redirect to redirect_uri.'''
# pylint: disable=C0103
def do_HEAD(self):
'''Response to a HEAD requests.'''
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
'''For GET request, extract code parameter from URL.'''
# pylint: disable=W0603
global authcode
querystring = urllib.parse.urlparse(self.path).query
querydict = urllib.parse.parse_qs(querystring)
if 'code' in querydict:
authcode = querydict['code'][0]
self.do_HEAD()
self.wfile.write(b'<html><head><title>Authorizaton result</title></head>')
self.wfile.write(b'<body><p>Authorization redirect completed. You may '
b'close this window.</p></body></html>')
with http.server.HTTPServer(('127.0.0.1', listen_port), MyHandler) as httpd:
try:
httpd.handle_request()
except KeyboardInterrupt:
pass
if not authcode:
sys.exit('Did not obtain an authcode.')
for k in 'response_type', 'login_hint', 'code_challenge', 'code_challenge_method':
del p[k]
p.update({'grant_type': 'authorization_code',
'code': authcode,
'client_secret': registration['client_secret'],
'code_verifier': verifier})
print('Exchanging the authorization code for an access token')
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
elif authflow == 'devicecode':
try:
response = urllib.request.urlopen(registration['devicecode_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
print(response['message'])
del p['scope']
p.update({'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
'client_secret': registration['client_secret'],
'device_code': response['device_code']})
interval = int(response['interval'])
print('Polling...', end='', flush=True)
while True:
time.sleep(interval)
print('.', end='', flush=True)
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
# Not actually always an error, might just mean "keep trying..."
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' not in response:
break
if response['error'] == 'authorization_declined':
print(' user declined authorization.')
sys.exit(1)
if response['error'] == 'expired_token':
print(' too much time has elapsed.')
sys.exit(1)
if response['error'] != 'authorization_pending':
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
sys.exit(1)
print()
else:
sys.exit(f'ERROR: Unknown OAuth2 flow "{token["authflow"]}. Delete token file and '
f'start over.')
update_tokens(response)
if not access_token_valid():
if args.verbose:
print('NOTICE: Invalid or expired access token; using refresh token '
'to obtain new access token.')
if not token['refresh_token']:
sys.exit('ERROR: No refresh token. Run script with "--authorize".')
p = baseparams.copy()
p.update({'client_secret': registration['client_secret'],
'refresh_token': token['refresh_token'],
'grant_type': 'refresh_token'})
try:
response = urllib.request.urlopen(registration['token_endpoint'],
urllib.parse.urlencode(p).encode())
except urllib.error.HTTPError as err:
print(err.code, err.reason)
response = err
response = response.read()
if args.debug:
print(response)
response = json.loads(response)
if 'error' in response:
print(response['error'])
if 'error_description' in response:
print(response['error_description'])
print('Perhaps refresh token invalid. Try running once with "--authorize"')
sys.exit(1)
update_tokens(response)
if not access_token_valid():
sys.exit('ERROR: No valid access token. This should not be able to happen.')
if args.verbose:
print('Access Token: ', end='')
print(token['access_token'])
def build_sasl_string(user, host, port, bearer_token):
'''Build appropriate SASL string, which depends on cloud server's supported SASL method.'''
if registration['sasl_method'] == 'OAUTHBEARER':
return f'n,a={user},\1host={host}\1port={port}\1auth=Bearer {bearer_token}\1\1'
if registration['sasl_method'] == 'XOAUTH2':
return f'user={user}\1auth=Bearer {bearer_token}\1\1'
sys.exit(f'Unknown SASL method {registration["sasl_method"]}.')
if args.test:
errors = False
imap_conn = imaplib.IMAP4_SSL(registration['imap_endpoint'])
sasl_string = build_sasl_string(token['email'], registration['imap_endpoint'], 993,
token['access_token'])
if args.debug:
imap_conn.debug = 4
try:
imap_conn.authenticate(registration['sasl_method'], lambda _: sasl_string.encode())
# Microsoft has a bug wherein a mismatch between username and token can still report a
# successful login... (Try a consumer login with the token from a work/school account.)
# Fortunately subsequent commands fail with an error. Thus we follow AUTH with another
# IMAP command before reporting success.
imap_conn.list()
if args.verbose:
print('IMAP authentication succeeded')
except imaplib.IMAP4.error as e:
print('IMAP authentication FAILED (does your account allow IMAP?):', e)
errors = True
pop_conn = poplib.POP3_SSL(registration['pop_endpoint'])
sasl_string = build_sasl_string(token['email'], registration['pop_endpoint'], 995,
token['access_token'])
if args.debug:
pop_conn.set_debuglevel(2)
try:
# poplib doesn't have an auth command taking an authenticator object
# Microsoft requires a two-line SASL for POP
# pylint: disable=W0212
pop_conn._shortcmd('AUTH ' + registration['sasl_method'])
pop_conn._shortcmd(base64.standard_b64encode(sasl_string.encode()).decode())
if args.verbose:
print('POP authentication succeeded')
except poplib.error_proto as e:
print('POP authentication FAILED (does your account allow POP?):', e.args[0].decode())
errors = True
# SMTP_SSL would be simpler but Microsoft does not answer on port 465.
smtp_conn = smtplib.SMTP(registration['smtp_endpoint'], 587)
sasl_string = build_sasl_string(token['email'], registration['smtp_endpoint'], 587,
token['access_token'])
smtp_conn.ehlo('test')
smtp_conn.starttls()
smtp_conn.ehlo('test')
if args.debug:
smtp_conn.set_debuglevel(2)
try:
smtp_conn.auth(registration['sasl_method'], lambda _=None: sasl_string)
if args.verbose:
print('SMTP authentication succeeded')
except smtplib.SMTPAuthenticationError as e:
print('SMTP authentication FAILED:', e)
errors = True
if errors:
sys.exit(1)

View File

@@ -0,0 +1,7 @@
{pkgs, ...}: {
home.packages = [
pkgs.ripgrep
];
home.file.".config/ripgrep/config".source = ./ripgrep/ripgrep.conf;
}

View File

@@ -0,0 +1,10 @@
{pkgs, ...}: {
programs.zsh.sessionVariables = {
RUSTFLAGS = "-L ${pkgs.libiconv}/lib -L ${pkgs.libcxx}/lib";
RUST_BACKTRACE = "full";
};
home.file.".cargo/config.toml".source = ./rust/cargo-config.toml;
home.packages = [
pkgs.rust-analyzer
];
}

View File

@@ -0,0 +1,18 @@
{pkgs, ...}: {
imports = [./zsh.nix];
home.packages = [
pkgs.tmux
];
programs.tmux = {
shell = "${pkgs.zsh}/bin/zsh";
escapeTime = 50;
mouse = false;
prefix = "C-b";
enable = true;
terminal = "screen-256color";
extraConfig = ''
set-option -sa terminal-features ',xterm-256color:RGB'
'';
};
}

View File

@@ -0,0 +1,23 @@
{pkgs, ...}: {
programs.zsh = {
enable = true;
autocd = true;
autosuggestion.enable = true;
enableCompletion = true;
history = {
expireDuplicatesFirst = true;
};
sessionVariables = {
EDITOR = "vim";
LC_ALL = "en_US.UTF-8";
LC_CTYPE = "en_US.UTF-8";
};
shellAliases = {
vim = "nvim";
view = "vim -R";
};
initExtra = builtins.readFile ./zsh/zshrc;
};
programs.fzf.enableZshIntegration = true;
}

View File

@@ -0,0 +1,32 @@
prompt_custom() {
local cyan='%F{cyan}'
local red='%F{red}'
local blue='%F{blue}'
local reset_color='%f'
local git_info=$(git symbolic-ref --short HEAD 2> /dev/null || git describe --tags --exact-match 2> /dev/null || git rev-parse --short HEAD 2> /dev/null)
if [[ -n "$git_info" ]]; then
# escape the percent character, which is the only zsh prompt metacharacter
git_info=$git_info:s/%/%%/
git_info=" ${blue}git:${reset_color}${red}(${git_info})${reset_color}"
else
git_info=""
fi
# %1 is the name of cwd
PROMPT="${cyan}%1~${reset_color}${git_info} > "
}
# Full path to cwd, with `~` for any initial home component, in light green,
RPROMPT='%F{155}%~%f'
precmd_functions+=(prompt_custom)
export WORDCHARS=''
autoload edit-command-line
zle -N edit-command-line
bindkey '^X^E' edit-command-line
bindkey -e
PATH="$PATH:$HOME/.cargo/bin"

View File

@@ -0,0 +1,110 @@
BuildUtils = {}
-- Create a new buffer and a new floating window to hold that buffer.
local function create_floating_window()
-- Create a new buffer for build output
local buf = vim.api.nvim_create_buf(false, true) -- No listed, scratch buffer
-- Calculate window size and position here (example: full width, 10 lines high at the bottom)
local width = vim.api.nvim_get_option_value("columns", {})
local height = vim.api.nvim_get_option_value("lines", {})
local win_height = math.min(10, math.floor(height * 0.2)) -- 20% of total height or 10 lines
local win_opts = {
relative = "editor",
width = width,
height = win_height,
col = 0,
row = height - win_height,
style = "minimal",
border = "single",
}
local win = vim.api.nvim_open_win(buf, false, win_opts)
return { window = win, buffer = buf }
end
local function _on_output(context, is_stdout, err, data, on_line)
local prefix
if is_stdout then
prefix = "OUT"
else
prefix = "ERR"
end
if err or data then
vim.schedule(function()
if err then
-- Append the error message to the buffer
local count = vim.api.nvim_buf_line_count(context.buffer)
vim.api.nvim_buf_set_lines(context.buffer, count, count, false, { "error " .. prefix .. ": " .. err })
end
if data then
-- Append the data to the buffer
local count = vim.api.nvim_buf_line_count(context.buffer)
vim.api.nvim_buf_set_lines(
context.buffer,
count,
count,
false,
vim.tbl_map(function(line)
return prefix .. ": " .. line
end, vim.split(data, "\n"))
)
end
if vim.api.nvim_win_is_valid(context.window) then
local cur_win = vim.api.nvim_get_current_win()
local cur_buf = vim.api.nvim_win_get_buf(cur_win)
if cur_buf ~= context.buffer then
local new_line_count = vim.api.nvim_buf_line_count(context.buffer)
vim.api.nvim_win_set_cursor(context.window, { new_line_count, 0 })
end
end
on_line(data, is_stdout, context)
end)
end
end
-- Arguments:
-- * exe, a string (no need to escape this)
-- * args, a table like { "-m", "venv", vim.fn.shellescape(some_path) }
-- * description of this process, visible to the user, e.g. "venv creation"
-- * context, the result of `create_floating_window`
-- * on_line, a function which takes "the string written", (true if stdout else false), and the context table; should return nothing. We'll call that on every line of stdout and stderr.
-- * on_complete, takes `context`, `code` (exit code) and `signal` ("documented" with neovim's uv.spawn, hah)
local function run_external(exe, args, description, context, on_line, on_complete)
local handle
local stdout = vim.uv.new_pipe(false)
local stderr = vim.uv.new_pipe(false)
handle, _ = vim.uv.spawn(
exe,
{
args = args,
stdio = { nil, stdout, stderr },
},
vim.schedule_wrap(function(code, signal)
stdout:read_stop()
stderr:read_stop()
stdout:close()
stderr:close()
handle:close()
print("External process " .. description .. " completed, exit code " .. code .. " and signal " .. signal)
on_complete(context, code, signal)
end)
)
if not handle then
print("Failed to start " .. description .. " process.")
return
end
vim.uv.read_start(stdout, function(err, data)
_on_output(context, true, err, data, on_line)
end)
vim.uv.read_start(stderr, function(err, data)
_on_output(context, false, err, data, on_line)
end)
end
BuildUtils.create_window = create_floating_window
BuildUtils.run = run_external

View File

@@ -0,0 +1,57 @@
vim.g.chadtree_settings = { xdg = true }
vim.api.nvim_create_autocmd("VimEnter", {
pattern = "*",
command = "CHADopen --nofocus",
})
vim.api.nvim_create_autocmd("BufEnter", {
pattern = "*",
callback = function()
if vim.fn.winnr("$") == 1 and vim.bo.filetype == "CHADTree" then
vim.cmd("quit")
end
end,
})
-- Variable to store the CHADtree window ID
local chadtree_winid_and_buf = nil
-- Function to check if a window is displaying CHADtree
local function is_chadtree_window(winid)
local bufnr = vim.api.nvim_win_get_buf(winid)
local filetype = vim.api.nvim_get_option_value("filetype", { buf = bufnr })
return filetype == "CHADTree"
end
-- Function to find and store the CHADtree window ID
local function find_chadtree_window()
for _, winid in ipairs(vim.api.nvim_list_wins()) do
if is_chadtree_window(winid) then
chadtree_winid_and_buf = { winid, vim.api.nvim_win_get_buf(winid) }
break
end
end
end
-- Function to switch to CHADtree buffer in the CHADtree window
local function switch_to_chadtree()
if chadtree_winid_and_buf and vim.api.nvim_win_is_valid(chadtree_winid_and_buf[1]) then
local current_winid = vim.api.nvim_get_current_win()
if current_winid == chadtree_winid_and_buf[1] and not is_chadtree_window(current_winid) then
print("CHADtree window may only point to CHADtree")
vim.api.nvim_win_set_buf(chadtree_winid_and_buf[1], chadtree_winid_and_buf[2])
end
end
end
-- Autocommand to find the CHADtree window after startup
vim.api.nvim_create_autocmd("VimEnter", {
callback = function()
vim.defer_fn(find_chadtree_window, 500)
end,
})
vim.api.nvim_create_autocmd("BufEnter", {
callback = switch_to_chadtree,
})

View File

@@ -0,0 +1,160 @@
local dotnet_has_set_status_line
function DetachSolution()
vim.g.current_sln_path = nil
-- TODO: unregister key bindings again
end
local function on_line(data, _, context)
-- Keep the window alive if there were warnings
if string.match(data, "%s[1-9]%d* Warning%(s%)") then
context.warn = context.warn + 1
end
end
local function on_complete(context, code, _)
if code ~= 0 then
print("Exit code " .. code)
context.errs = context.errs + 1
end
if context.errs == 0 and context.warn == 0 then
-- Close the temporary floating window (but keep it alive if the
-- cursor is in it)
local cur_win = vim.api.nvim_get_current_win()
local cur_buf = vim.api.nvim_win_get_buf(cur_win)
if cur_buf ~= context.buffer then
vim.api.nvim_win_close(context.window, true)
end
print("All builds successful")
end
end
function GetCurrentSln()
if vim.g.current_sln_path then
return vim.g.current_sln_path
else
return nil
end
end
function BuildDotNetSolution()
if vim.g.current_sln_path then
local context = BuildUtils.create_window()
context.errs = 0
context.warn = 0
BuildUtils.run("dotnet", { "build", vim.g.current_sln_path }, "dotnet build", context, on_line, on_complete)
end
end
function TestDotNetSolution()
if vim.g.current_sln_path then
local context = BuildUtils.create_window()
context.warn = 0
context.errs = 0
BuildUtils.run("dotnet", { "test", vim.g.current_sln_path }, "dotnet test", context, on_line, on_complete)
end
end
function CurrentSlnOrEmpty()
local sln = GetCurrentSln()
if sln then
return sln
else
return ""
end
end
function RegisterSolution(sln_path)
vim.g.current_sln_path = sln_path
if not dotnet_has_set_status_line then
dotnet_has_set_status_line = true
vim.o.statusline = vim.o.statusline .. " %{v:lua.CurrentSlnOrEmpty()}"
end
local whichkey = require("which-key")
whichkey.register({
s = {
name = ".NET solution",
b = { BuildDotNetSolution, "Build .NET solution" },
t = { TestDotNetSolution, "Test .NET solution" },
},
}, { prefix = vim.api.nvim_get_var("maplocalleader"), buffer = vim.api.nvim_get_current_buf() })
end
local function find_nearest_slns()
local path = vim.fn.expand("%:p:h") -- Get the full path of the current buffer's directory
while path and path ~= "/" do
local sln_paths = vim.fn.glob(path .. "/*.sln", nil, true)
if #sln_paths > 0 then
return sln_paths
end
path = vim.fn.fnamemodify(path, ":h") -- Move up one directory
end
return {}
end
local function FindAndRegisterSolution(should_override)
if not should_override and GetCurrentSln() ~= nil then
RegisterSolution(GetCurrentSln())
end
local solutions = find_nearest_slns()
if not solutions or #solutions == 0 then
print("No .sln file found in any parent directory.")
return
elseif #solutions == 1 then
-- Exactly one solution found; register it directly
RegisterSolution(solutions[1])
elseif #solutions > 1 then
-- Multiple solutions found; use Telescope to pick one
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local actions = require("telescope.actions")
local action_state = require("telescope.actions.state")
local conf = require("telescope.config").values
pickers
.new({}, {
prompt_title = "Select a Solution File",
finder = finders.new_table({
results = solutions,
entry_maker = function(entry)
return {
value = entry,
display = entry,
ordinal = entry,
}
end,
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_bufnr, _)
actions.select_default:replace(function()
local selection = action_state.get_selected_entry()
actions.close(prompt_bufnr)
RegisterSolution(selection.value)
end)
return true
end,
})
:find()
end
end
vim.api.nvim_create_autocmd({ "BufReadPost", "BufNewFile" }, {
pattern = "*.sln",
callback = function()
if GetCurrentSln() == nil then
RegisterSolution(vim.fn.expand("%:p"))
end
end,
})
vim.api.nvim_create_autocmd("FileType", {
pattern = { "fsharp", "cs" },
callback = function()
FindAndRegisterSolution(false)
end,
})

296
home-manager/nvim/init.lua Normal file
View File

@@ -0,0 +1,296 @@
vim.opt.mouse = ""
vim.opt.history = 500
vim.opt.background = "dark"
vim.opt.signcolumn = "yes"
vim.opt.wildmenu = true
vim.opt.wildignore = vim.opt.wildignore + { "*/.git/*", "*/.hg/*", "*/.svn/*", "*/.DS_Store" }
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.incsearch = true
vim.opt.magic = true
vim.opt.hlsearch = true
vim.opt.autoindent = true
vim.opt.smartindent = true
vim.opt.wrap = true
vim.opt.linebreak = true
vim.opt.textwidth = 500
vim.opt.switchbuf = "useopen"
vim.opt.laststatus = 2
-- I don't use tabs, but one day I might!
vim.opt.showtabline = 2
vim.opt.langmenu = "en"
vim.opt.ffs = "unix"
vim.opt.encoding = "utf8"
-- Always show current position
vim.opt.ruler = true
vim.opt.number = true
-- A bit of extra margin to the left
vim.opt.foldcolumn = "1"
vim.opt.autoread = true
vim.opt.backup = false
vim.opt.writebackup = true
vim.opt.swapfile = false
vim.opt.cmdheight = 2
-- Use spaces instead of tabs
vim.opt.expandtab = true
vim.opt.smarttab = true
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
vim.opt.lazyredraw = true
-- Show matching brackets when text indicator is on one of them
vim.opt.showmatch = true
vim.opt.mat = 2
-- Turn off sound
vim.opt.errorbells = false
vim.opt.visualbell = false
vim.opt.timeoutlen = 500
vim.opt.scrolloff = 2
-- Return to last edit position when opening files
vim.api.nvim_create_autocmd("BufReadPost", {
pattern = "*",
callback = function()
local line = vim.fn.line
local last_pos = line("'\"")
if last_pos > 1 and last_pos <= line("$") then
vim.cmd("normal! g'\"")
end
end,
})
-- Trim trailing whitespace on save
function CleanExtraSpaces()
local save_cursor = vim.api.nvim_win_get_cursor(0)
local old_query = vim.fn.getreg("/")
vim.cmd("%s/\\s\\+$//e")
vim.api.nvim_win_set_cursor(0, save_cursor)
vim.fn.setreg("/", old_query)
end
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = { "*.fs", "*.fsi", "*.txt", "*.js", "*.py", "*.wiki", "*.sh", "*.coffee" },
callback = CleanExtraSpaces,
})
-- Status line
-- Returns true if paste mode is enabled
function HasPaste()
if vim.opt.paste:get() then
return "PASTE MODE "
end
return ""
end
vim.o.statusline = vim.o.statusline .. "%{v:lua.HasPaste()}%F%m%r%h %w Line: %l Column: %c"
--------------------------------------------------------------
vim.api.nvim_set_keymap("n", ";", "<Nop>", { noremap = true })
vim.api.nvim_set_var("maplocalleader", ";")
vim.api.nvim_set_var("mapleader", " ")
function MarkdownPreview()
local temp_file = vim.fn.tempname() .. ".md"
local file_name = vim.fn.substitute(vim.fn.tolower(vim.fn.expand("%:t")), "\\W", "_", "g")
local temp_html = "/tmp/" .. file_name .. "_tmp.html"
-- Write the current buffer to the temp file
vim.cmd("write! " .. temp_file)
local pandoc_cmd = "pandoc " .. temp_file .. " -o " .. temp_html
-- Execute the pandoc command
vim.fn.system(pandoc_cmd)
-- Use tmux and lynx to preview the HTML file
local lynx_cmd = "tmux split-window -h lynx " .. temp_html
vim.fn.jobstart(vim.split(lynx_cmd, " "), { silent = true })
-- Delete the temp markdown file
vim.fn.delete(temp_file, "rf")
end
function RemoveCarriageReturn()
vim.cmd("mark m")
vim.cmd("normal! Hmt")
vim.cmd("%s/\r//ge")
vim.cmd("normal! 'tzt'm")
end
function FormatJson()
vim.cmd("%!python -m json.tool")
end
function ChangeToCurrentDirectory()
vim.cmd(":cd %:p:h")
vim.cmd(":pwd")
end
local function close_loclist_if_orphaned()
local win = vim.fn.expand("<afile>")
vim.fn.win_execute(win, "lclose")
end
-- Set up an autocmd using the nvim_create_autocmd API
vim.api.nvim_create_autocmd("WinClosed", {
pattern = "*",
callback = close_loclist_if_orphaned,
})
local whichkey = require("which-key")
local pickers = require("telescope.pickers")
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
function DisplayAllMappingsWithTelescope()
local mappings = {}
local commands = {} -- Store commands keyed by the display string
local function accumulate(tree)
tree:walk(function(node)
-- Note: we could (if desired) view all groups, because the `node.mapping` table looks like this:
-- { prefix = "g", group = true, keys = {...}}
if node.mapping then
local mapping = node.mapping
if not mapping.group then
local description = mapping.desc or mapping.label or mapping.cmd
-- Some actions are just there for which-key to hook into to display prefixes; they don't have a description.
if description then
local displayString = description .. " | " .. mapping.prefix
commands[displayString] = mapping.prefix
mappings[#mappings + 1] = displayString
else
for k, v in pairs(mapping) do
print("Nothing: " .. k .. " : " .. tostring(v) .. " (type: " .. type(v) .. ")")
end
print("-----")
end
end
end
end)
end
local cur_buf = vim.api.nvim_win_get_buf(0)
accumulate(require("which-key.keys").get_tree("n").tree)
accumulate(require("which-key.keys").get_tree("n", cur_buf).tree)
pickers
.new({}, {
prompt_title = "Actions",
finder = finders.new_table({
results = mappings,
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(_, map)
map("i", "<CR>", function(bufnr)
local selection = action_state.get_selected_entry()
actions.close(bufnr)
local cmd = commands[selection.value]
if cmd then
vim.api.nvim_command(":normal " .. vim.api.nvim_replace_termcodes(cmd, true, true, true))
else
print("no command found")
end
end)
return true
end,
})
:find()
end
function ToggleSpell()
vim.cmd("setlocal spell!")
end
whichkey.register({
[vim.api.nvim_get_var("maplocalleader")] = {
DisplayAllMappingsWithTelescope,
"View all mappings",
},
m = {
p = { MarkdownPreview, "Preview Markdown in Lynx" },
d = { RemoveCarriageReturn, "Delete carriage returns from file" },
},
["j"] = {
FormatJson,
"Auto-format JSON",
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
whichkey.register({
g = {
function()
require("telescope.builtin").grep_string()
end,
"Find instances of text under cursor",
},
h = {
name = "Find historical...",
f = {
function()
require("telescope.builtin").oldfiles()
end,
"List previously open files",
},
c = {
function()
require("telescope.builtin").command_history()
end,
"List previously run commands",
},
s = {
function()
require("telescope.builtin").search_history()
end,
"List previously run searches",
},
},
m = {
function()
require("telescope.builtin").marks()
end,
"List marks",
},
["cd"] = {
ChangeToCurrentDirectory,
"Switch CWD to the directory of the open buffer",
},
["ss"] = {
ToggleSpell,
"Toggle spell-checker on or off",
},
[vim.api.nvim_get_var("mapleader")] = {
function()
require("telescope.builtin").find_files()
end,
"Find files by name",
},
}, { prefix = vim.api.nvim_get_var("mapleader") })
vim.api.nvim_create_autocmd({"BufRead","BufNewFile"}, {
pattern = {"Directory.Build.props", "*.fsproj", "*.csproj"},
callback = function()
vim.bo.filetype = "xml"
end,
})

View File

@@ -0,0 +1,239 @@
vim.g["fsharp#fsautocomplete_command"] = { "fsautocomplete" }
vim.g["fsharp#show_signature_on_cursor_move"] = 1
vim.g["fsharp#fsi_keymap"] = "none"
-- Supply nil to get all loaded F# projects and build them.
local function BuildFSharpProjects(projects)
local function on_line(data, _, context)
-- Keep the window alive if there were warnings
if string.match(data, "%s[1-9]%d* Warning%(s%)") then
context.warn = context.warn + 1
end
end
local on_complete
local function spawn_next(context)
BuildUtils.run(
"dotnet",
{ "build", context.projects[context.completed + 1] },
"dotnet build",
context,
on_line,
on_complete
)
end
on_complete = function(context, code, signal)
print("Build process exited with code " .. code .. " and signal " .. signal)
if code ~= 0 then
context.errs = context.errs + 1
end
context.completed = context.completed + 1
print(
"Completed: "
.. context.completed
.. " out of "
.. context.expected
.. " (errors: "
.. context.errs
.. ", warnings: "
.. context.warn
.. ")"
)
if context.completed == context.expected then
if context.errs == 0 and context.warn == 0 then
-- Close the temporary floating window (but keep it alive if the
-- cursor is in it)
local cur_win = vim.api.nvim_get_current_win()
local cur_buf = vim.api.nvim_win_get_buf(cur_win)
if cur_buf ~= context.buffer then
vim.api.nvim_win_close(context.window, true)
end
print("All builds successful")
end
else
spawn_next(context)
end
end
if not projects then
projects = vim.fn['fsharp#getLoadedProjects']()
end
if projects then
local total_projects = 0
for _, _ in ipairs(projects) do
total_projects = total_projects + 1
end
local context = BuildUtils.create_window()
context.warn = 0
context.errs = 0
context.completed = 0
context.expected = total_projects
context.projects = projects
spawn_next(context)
end
end
vim.api.nvim_create_user_command("BuildFSharpProject", function(opts)
if opts.fargs and opts.fargs[1] then
BuildFSharpProjects(opts.fargs)
else
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
pickers
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = vim.fn['fsharp#getLoadedProjects'](),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
actions.select_default:replace(function()
actions.close(prompt_buf)
local selection = action_state.get_selected_entry()
BuildFSharpProjects({ selection.value })
end)
return true
end,
})
:find()
end
end, { nargs = "?", complete = "file" })
local function TableConcat(tables)
local result = {}
for _, tab in ipairs(tables) do
for _, v in ipairs(tab) do
table.insert(result, v)
end
end
return result
end
-- args is a table that will be splatted into the command line and will be immediately
-- followed by the project.
local function RunDotnet(command, args, project, configuration)
local function on_line(data, _, context) end
local function on_complete(context, code, signal) end
local context = BuildUtils.create_window()
BuildUtils.run(
"dotnet",
TableConcat({ { command }, args, { project, "--configuration", configuration } }),
"dotnet",
context,
on_line,
on_complete
)
end
-- Call this as:
-- RunFSharpProject path/to/fsproj
-- RunFSharpProject Debug path/to/fsproj
vim.api.nvim_create_user_command("RunFSharpProject", function(opts)
local configuration = "Release"
if opts.fargs and opts.fargs[1] and opts.fargs[1]:match("sproj$") then
RunDotnet("run", { "--project" }, opts.fargs[1], configuration)
elseif opts.fargs and opts.fargs[1] and opts.fargs[2] then
configuration = opts.fargs[1]
RunDotnet("run", { "--project" }, opts.fargs[2], configuration)
else
configuration = opts.fargs[1]
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
pickers
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = vim.fn['fsharp#getLoadedProjects'](),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
actions.select_default:replace(function()
actions.close(prompt_buf)
local selection = action_state.get_selected_entry()
RunDotnet("run", { "--project" }, selection.value, configuration)
end)
return true
end,
})
:find()
end
end, { nargs = "*", complete = "file" })
vim.api.nvim_create_user_command("PublishFSharpProject", function(opts)
if opts.fargs and opts.fargs[1] then
RunDotnet("publish", {}, opts.fargs[1], "Release")
else
local pickers = require("telescope.pickers")
local finders = require("telescope.finders")
local conf = require("telescope.config").values
local action_state = require("telescope.actions.state")
local actions = require("telescope.actions")
pickers
.new({}, {
prompt_title = "Projects",
finder = finders.new_table({
results = vim.fn['fsharp#getLoadedProjects'](),
}),
sorter = conf.generic_sorter({}),
attach_mappings = function(prompt_buf, _)
actions.select_default:replace(function()
actions.close(prompt_buf)
local selection = action_state.get_selected_entry()
RunDotnet("publish", {}, selection.value, "Release")
end)
return true
end,
})
:find()
end
end, { nargs = "*", complete = "file" })
vim.api.nvim_create_autocmd("FileType", {
pattern = "fsharp",
callback = function()
local status, whichkey = pcall(require, "which-key")
if status then
whichkey.register({
f = {
name = "F#",
t = { ":call fsharp#showTooltip()<CR>", "Show F# Tooltip" },
["si"] = { ":call fsharp#toggleFsi()<CR>", "Toggle FSI (F# Interactive)" },
["sl"] = { ":call fsharp#sendLineToFsi()<cr>", "Send line to FSI (F# Interactive)" },
r = {
name = "Run F# project",
d = { ":RunFSharpProject Debug", "Run F# project in debug configuration" },
r = { ":RunFSharpProject Release", "Run F# project in release configuration" },
},
p = {
":PublishFSharpProject",
"Publish F# project",
},
b = {
"Build F# project",
a = { BuildFSharpProjects, "Build all projects" },
s = { ":BuildFSharpProject", "Build specified project" },
},
},
}, { prefix = vim.api.nvim_get_var("maplocalleader"), buffer = vim.api.nvim_get_current_buf() })
else
vim.api.nvim_set_keymap("n", "<localleader>ft", ":call fsharp#showTooltip()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>fsi", ":call fsharp#toggleFsi()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>fsl", ":call fsharp#sendLineToFsi()<CR>", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>bpa", ":lua BuildFSharpProjects()", { noremap = true })
vim.api.nvim_set_keymap("n", "<localleader>bps", ":BuildFSharpProject", { noremap = true })
end
end,
})

View File

@@ -0,0 +1,17 @@
require("lspconfig")["leanls"].setup({})
require("lean").setup({})
require("which-key").register({
l = {
i = { "<Cmd>LeanInfoviewToggle<CR>", "Toggle Lean info view" },
p = { "<Cmd>LeanInfoviewPinTogglePause<CR>", "Pause Lean info view" },
s = { "<Cmd>LeanSorryFill<CR>", "Fill open goals with sorry" },
w = { "<Cmd>LeanInfoviewEnableWidgets<CR>", "Enable Lean widgets" },
W = { "<Cmd>LeanInfoviewDisableWidgets<CR>", "Disable Lean widgets" },
["?"] = {
"<Cmd>LeanAbbreviationsReverseLookup<CR>",
"Show what Lean abbreviation produces the symbol under the cursor",
},
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })

View File

@@ -0,0 +1,191 @@
local coq = require("coq")
-- Using rustaceanvim means we shouldn't set up the LSP for Rust manually.
-- Similarly csharp_ls is unnecessary given roslyn.nvim
-- require("lspconfig")["csharp_ls"].setup({})
local schemas = {
["https://raw.githubusercontent.com/docker/compose/master/compose/config/compose_spec.json"] = "docker-compose*.{yml,yaml}",
["https://json.schemastore.org/github-workflow.json"] = ".github/**/*.{yml,yaml}",
["https://json.schemastore.org/package.json"] = "package.json",
["https://json.schemastore.org/global.json"] = "global.json",
["https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json"] = "version.json",
["https://json-schema.org/draft/2020-12/schema"] = "*.schema.json",
["https://json.schemastore.org/dotnet-tools.json"] = "dotnet-tools.json",
}
require("lspconfig")["yamlls"].setup({
settings = {
yaml = {
validate = true,
-- disable the schema store
schemaStore = {
enable = false,
url = "",
},
-- manually select schemas
schemas = schemas,
},
},
filetypes = { "yaml", "json", "jsonc" },
})
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.completion.completionItem.snippetSupport = true
require("lspconfig")["jsonls"].setup({
capabilities = capabilities,
cmd = { "vscode-json-languageserver", "--stdio" },
settings = {
json = {
validate = { enable = true },
},
},
})
require("lspconfig")["bashls"].setup({})
require("lspconfig")["dockerls"].setup({})
require("lspconfig")["html"].setup({
capabilities = capabilities,
})
require("lspconfig")["ltex"].setup({})
require("lspconfig")["lua_ls"].setup({
on_init = function(client)
local path = client.workspace_folders[1].name
if vim.uv.fs_stat(path .. "/.luarc.json") or vim.loop.fs_stat(path .. "/.luarc.jsonc") then
return
end
client.config.settings.Lua = vim.tbl_deep_extend("force", client.config.settings.Lua, {
runtime = {
-- Tell the language server which version of Lua you're using
-- (most likely LuaJIT in the case of Neovim)
version = "LuaJIT",
},
-- Make the server aware of Neovim runtime files
workspace = {
checkThirdParty = false,
library = {
vim.env.VIMRUNTIME,
-- Depending on the usage, you might want to add additional paths here.
-- "${3rd}/luv/library"
-- "${3rd}/busted/library",
},
-- or pull in all of 'runtimepath'. NOTE: this is a lot slower
-- library = vim.api.nvim_get_runtime_file("", true)
},
})
end,
settings = {
Lua = {},
},
})
require("lspconfig").pyright.setup(coq.lsp_ensure_capabilities({
handlers = {
["textDocument/publishDiagnostics"] = function(...)
vim.lsp.diagnostic.on_publish_diagnostics(...)
local window = vim.api.nvim_get_current_win()
vim.diagnostic.setloclist({ open_loclist = true })
vim.api.nvim_set_current_win(window)
end,
},
}))
require("lspconfig").nil_ls.setup(coq.lsp_ensure_capabilities({
settings = {
nix = {
flake = {
autoArchive = true,
},
},
},
}))
function ToggleLocList()
local winid = vim.fn.getloclist(0, { winid = 0 }).winid
if winid == 0 then
local window = vim.api.nvim_get_current_win()
vim.cmd.lopen()
vim.api.nvim_set_current_win(window)
else
vim.cmd.lclose()
end
end
do
local whichkey_status, whichkey = pcall(require, "which-key")
if whichkey_status then
whichkey.register({
l = {
name = "loclist-related commands",
p = { vim.diagnostic.goto_prev, "Go to previous entry in loclist" },
n = { vim.diagnostic.goto_next, "Go to next entry in loclist" },
l = { ToggleLocList, "Toggle loclist" },
f = { vim.diagnostic.open_float, "Open current loclist entry in floating window" },
},
}, { prefix = vim.api.nvim_get_var("mapleader") })
else
vim.keymap.set("n", "<leader>lp", vim.diagnostic.goto_prev)
vim.keymap.set("n", "<leader>ln", vim.diagnostic.goto_next)
vim.keymap.set("n", "<leader>ll", ToggleLocList)
vim.keymap.set("n", "<leader>lf", vim.diagnostic.open_float)
end
end
-- Use LspAttach autocommand to only map the following keys
-- after the language server attaches to the current buffer
vim.api.nvim_create_autocmd("LspAttach", {
group = vim.api.nvim_create_augroup("UserLspConfig", {}),
callback = function(ev)
local whichkey = require("which-key")
-- Enable completion triggered by <c-x><c-o>
vim.bo[ev.buf].omnifunc = "v:lua.vim.lsp.omnifunc"
-- Buffer local mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
whichkey.register({
g = {
name = "Go-to related commands",
D = { vim.lsp.buf.declaration, "Go to declaration" },
d = { vim.lsp.buf.definition, "Go to definition" },
i = { vim.lsp.buf.implementation, "Go to implementation" },
r = {
function()
require("telescope.builtin").lsp_references()
end,
"Find references",
},
},
K = { vim.lsp.buf.hover, "Display information about symbol under cursor" },
})
whichkey.register({
["<C-k>"] = { vim.lsp.buf.signature_help, "Display signature information about symbol under cursor" },
})
whichkey.register({
w = {
a = { vim.lsp.buf.add_workspace_folder, "Add a path to the workspace folders list" },
r = { vim.lsp.buf.add_workspace_folder, "Remove a path from the workspace folders list" },
l = {
function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end,
"Show the workspace folders list",
},
},
f = {
function()
vim.lsp.buf.format({ async = true })
end,
"Autoformat",
},
c = {
a = { vim.lsp.buf.code_action, "Select a code action" },
},
r = {
n = { vim.lsp.buf.rename, "Rename variable" },
},
D = { vim.lsp.buf.type_definition, "Go to type definition" },
}, { prefix = vim.api.nvim_get_var("mapleader") })
end,
})

View File

@@ -0,0 +1,15 @@
require("dap-python").setup("%PYTHONENV%/bin/python")
do
local whichkey = require("which-key")
whichkey.register({
['pd'] = {
"Debugger-related commands",
t = {
"Tests",
f = { require("dap-python").test_class, "Run Python tests in the current file" },
c = { require("dap-python").test_method, "Run the Python test under the cursor" },
},
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
end

View File

@@ -0,0 +1,66 @@
local dap = require("dap")
local dap_ui = require("dap.ui.widgets")
dap.adapters.coreclr = {
type = "executable",
command = "netcoredbg",
args = { "--interpreter=vscode" },
}
dap.configurations.fsharp = {
{
type = "coreclr",
name = "launch - netcoredbg",
request = "launch",
program = function()
return vim.fn.input("Path to dll: ", vim.fn.getcwd() .. "/bin/Debug/", "file")
end,
},
}
dap.configurations.cs = {
{
type = "coreclr",
name = "launch - netcoredbg",
request = "launch",
program = function()
return vim.fn.input("Path to dll: ", vim.fn.getcwd() .. "/bin/Debug/", "file")
end,
},
}
do
local whichkey = require("which-key")
whichkey.register({
d = {
name = "Debugger-related commands",
o = { dap.step_over, "Step over" },
i = { dap.step_into, "Step into" },
c = { dap.continue, "Continue" },
C = { dap.run_last, "Run with last debug configuration" },
b = { dap.toggle_breakpoint, "Toggle breakpoint" },
r = { dap.repl.open, "Open debug repl" },
v = {
name = "Commands to view debugger state",
v = {
function()
dap_ui.hover()
end,
"View value of expression under cursor",
},
s = {
function()
dap_ui.sidebar(dap_ui.scopes).open()
end,
"View values of all variables in all scopes",
},
f = {
function()
dap_ui.sidebar(dap_ui.frames).open()
end,
"View stack frames",
},
},
t = { dap.terminate, "Terminate/stop/end debug session" },
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
end

View File

@@ -0,0 +1,9 @@
require("nvim-lightbulb").setup({
autocmd = { enabled = true },
ignore = {
clients = {
-- This one is really noisy
"lua_ls",
},
},
})

View File

@@ -0,0 +1,66 @@
local function pytest_on_line(_, _, _) end
local function pytest_on_complete(_, code, _)
if code ~= 0 then
print("Exit code " .. code)
end
end
function RunPythonTestAtCursor()
local api = vim.api
-- Get the current buffer and cursor position
local bufnr = api.nvim_get_current_buf()
local line_nr = api.nvim_win_get_cursor(0)[1]
local filename = api.nvim_buf_get_name(bufnr)
-- Read the file content
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
-- Find the test function
local test_name = nil
for i = line_nr, 1, -1 do
local line = lines[i]
if line:match("^def test_") then
test_name = line:match("^def (%S+)%(")
break
end
end
if test_name then
-- Run pytest for the found test function
local context = BuildUtils.create_window()
BuildUtils.run(
"pytest",
{ filename .. "::" .. test_name },
"Run PyTest (" .. test_name .. ")",
context,
pytest_on_line,
pytest_on_complete
)
else
print("No test function found at or above line " .. line_nr)
end
end
function RunPythonTestsInFile()
local file_path = vim.fn.expand("%:p")
local context = BuildUtils.create_window()
BuildUtils.run("pytest", { file_path }, "Run PyTest", context, pytest_on_line, pytest_on_complete)
end
function RunAllPythonTests()
local context = BuildUtils.create_window()
BuildUtils.run("pytest", {}, "Run PyTest", context, pytest_on_line, pytest_on_complete)
end
do
local whichkey = require("which-key")
whichkey.register({
['pt'] = {
"Run Python tests",
f = { RunPythonTestsInFile, "Run Python tests in the current file" },
a = { RunAllPythonTests, "Run all Python tests" },
c = { RunPythonTestAtCursor, "Run the Python test under the cursor" },
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
end

View File

@@ -0,0 +1,4 @@
require("roslyn").setup({
on_attach = function(_, _) end,
capabilities = vim.lsp.protocol.make_client_capabilities(),
})

View File

@@ -0,0 +1,5 @@
require("tokyonight").setup({
style = "night",
})
vim.cmd([[colorscheme tokyonight]])

View File

@@ -0,0 +1,9 @@
require("nvim-treesitter.configs").setup({
-- Automatically install missing parsers when entering buffer
-- Recommendation: set to false if you don't have `tree-sitter` CLI installed locally
auto_install = false,
highlight = {
enable = true,
},
})

View File

@@ -0,0 +1,100 @@
local venv_selector = require("venv-selector")
venv_selector.setup({
changed_venv_hooks = { venv_selector.hooks.pyright },
name = { "venv", ".venv" },
search_venv_managers = true,
})
vim.api.nvim_create_autocmd("VimEnter", {
desc = "Auto select virtualenv Nvim open",
pattern = "*",
callback = function()
-- Mystery: this seems to be being called twice whenever we open nvim
local venv = vim.fn.findfile("pyproject.toml", vim.fn.getcwd() .. ";")
if venv ~= "" then
require("venv-selector").retrieve_from_cache()
end
end,
once = true,
})
function SelectVenv()
local old_path = vim.fn.getenv("PATH")
vim.cmd("VenvSelectCached")
local new_path = vim.fn.getenv("PATH")
if old_path == new_path then
-- Failed to source venv. Get the user to choose one.
vim.cmd("VenvSelect")
end
end
local function find_requirements_txt(start_path)
local path = vim.fn.fnamemodify(start_path, ":p")
while path and #path > 1 do
local req_path = path .. "requirements.txt"
if vim.fn.filereadable(req_path) ~= 0 then
return req_path
end
path = vim.fn.fnamemodify(path, ":h")
end
return nil
end
-- TODO: make this one work
local function load_venv(venv_dir)
require("venv-selector.venv").load()
require("venv-selector.venv").set_venv_and_system_paths(venv_dir)
require("venv-selector.venv").cache_venv(venv_dir)
end
function CreateVenv()
local requirements_path = find_requirements_txt(vim.fn.getcwd())
local venv_dir
if not requirements_path then
print("requirements.txt not found; creating fresh venv in current working directory.")
venv_dir = vim.fn.getcwd() .. "/.venv"
else
venv_dir = vim.fn.fnamemodify(requirements_path, ":h") .. "/.venv"
end
print("Creating virtual environment in " .. venv_dir)
-- Create virtual environment
vim.fn.system("python -m venv " .. vim.fn.shellescape(venv_dir))
-- Install requirements
if requirements_path then
print("Installing requirements from " .. requirements_path)
local context = BuildUtils.create_window()
BuildUtils.run(
venv_dir .. "/bin/python",
{ "-m", "pip", "install", "-r", requirements_path },
"venv creation",
context,
function(_, _, _) end,
function(_, _, _)
load_venv(venv_dir)
end
)
else
load_venv(venv_dir)
end
end
do
local whichkey = require("which-key")
whichkey.register({
['pv'] = {
name = "Python virtual environment-related commands",
c = { CreateVenv, "Create virtual environment" },
l = { SelectVenv, "Load virtual environment" },
o = {
function()
vim.cmd("VenvSelect")
end,
"Choose (override) new virtual environment",
},
},
}, { prefix = vim.api.nvim_get_var("maplocalleader") })
end

View File

@@ -0,0 +1,88 @@
require("which-key").setup({
plugins = {
marks = true, -- shows a list of your marks on ' and `
registers = true, -- shows your registers on " in NORMAL or <C-r> in INSERT mode
-- the presets plugin, adds help for a bunch of default keybindings in Neovim
-- No actual key bindings are created
spelling = {
enabled = true, -- enabling this will show WhichKey when pressing z= to select spelling suggestions
suggestions = 20, -- how many suggestions should be shown in the list?
},
presets = {
operators = true, -- adds help for operators like d, y, ...
motions = true, -- adds help for motions
text_objects = true, -- help for text objects triggered after entering an operator
windows = true, -- default bindings on <c-w>
nav = true, -- misc bindings to work with windows
z = true, -- bindings for folds, spelling and others prefixed with z
g = true, -- bindings for prefixed with g
},
},
-- add operators that will trigger motion and text object completion
-- to enable all native operators, set the preset / operators plugin above
operators = { gc = "Comments" },
key_labels = {
-- override the label used to display some keys. It doesn't effect WK in any other way.
-- For example:
-- ["<space>"] = "SPC",
-- ["<cr>"] = "RET",
-- ["<tab>"] = "TAB",
},
motions = {
count = true,
},
icons = {
breadcrumb = "»", -- symbol used in the command line area that shows your active key combo
separator = "", -- symbol used between a key and it's label
group = "+", -- symbol prepended to a group
},
popup_mappings = {
scroll_down = "<c-d>", -- binding to scroll down inside the popup
scroll_up = "<c-u>", -- binding to scroll up inside the popup
},
window = {
border = "none", -- none, single, double, shadow
position = "bottom", -- bottom, top
margin = { 1, 0, 1, 0 }, -- extra window margin [top, right, bottom, left]. When between 0 and 1, will be treated as a percentage of the screen size.
padding = { 1, 2, 1, 2 }, -- extra window padding [top, right, bottom, left]
winblend = 0, -- value between 0-100 0 for fully opaque and 100 for fully transparent
zindex = 1000, -- positive value to position WhichKey above other floating windows.
},
layout = {
height = { min = 4, max = 25 }, -- min and max height of the columns
width = { min = 20, max = 50 }, -- min and max width of the columns
spacing = 3, -- spacing between columns
align = "left", -- align columns left, center or right
},
ignore_missing = false, -- enable this to hide mappings for which you didn't specify a label
hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "^:", "^ ", "^call ", "^lua " }, -- hide mapping boilerplate
show_help = true, -- show a help message in the command line for using WhichKey
show_keys = true, -- show the currently pressed key and its label as a message in the command line
triggers = "auto", -- automatically setup triggers
-- triggers = {"<leader>"} -- or specifiy a list manually
-- list of triggers, where WhichKey should not wait for timeoutlen and show immediately
triggers_nowait = {
-- marks
"`",
"'",
"g`",
"g'",
-- registers
'"',
"<c-r>",
-- spelling
"z=",
},
triggers_blacklist = {
-- list of mode / prefixes that should never be hooked by WhichKey
-- this is mostly relevant for keymaps that start with a native binding
i = { "j", "k" },
v = { "j", "k" },
},
-- disable the WhichKey popup for certain buf types and file types.
-- Disabled by default for Telescope
disable = {
buftypes = {},
filetypes = {},
},
})

View File

@@ -14,6 +14,8 @@ with pkgs.vscode-extensions;
shardulm94.trailing-spaces
nvarner.typst-lsp
arrterian.nix-env-selector
# Doesn't build on arm64
# vadimcn.vscode-lldb
]
++ pkgs.vscode-utils.extensionsFromVscodeMarketplace [
{
@@ -41,22 +43,16 @@ with pkgs.vscode-extensions;
sha256 = "yk7buEyQIw6aiUizAm+sgalWxUibIuP9crhyBaOjC2E=";
}
{
name = "Ionide-Paket";
publisher = "Ionide";
version = "2.0.0";
sha256 = "1455zx5p0d30b1agdi1zw22hj0d3zqqglw98ga8lj1l1d757gv6v";
}
{
name = "lean";
publisher = "jroesch";
version = "0.16.58";
sha256 = "sha256-e5+C6dAcpet4xOiifmTJ1vm2pNrcPhx/mjl70il5NG0=";
name = "ionide-fsharp";
publisher = "ionide";
version = "7.18.1";
sha256 = "sha256-6NPMQncoZhZYtx5c+qzarjuSzUXMb5HdKCzcHPCFUhU=";
}
{
name = "lean4";
publisher = "leanprover";
version = "0.0.101";
sha256 = "sha256-tHxP6X6qp3qVkkCn5TjhHrYHHvGGWJ4kYE7la6bPT6w=";
version = "0.0.128";
sha256 = "sha256-odRDOrlDFahweLzoQtpufY8UUwAutPFunqg7atTxnPo=";
}
{
name = "vscode-clang";
@@ -89,23 +85,3 @@ with pkgs.vscode-extensions;
sha256 = "sha256-lLLa8SN+Sf9Tbi7HeWYWa2KhPQFJyQWrf9l3EUljwYo=";
}
]
++ [
(let
vsix = builtins.fetchurl {
name = "vadimcn-vscode-lldb.zip";
url = "https://github.com/vadimcn/codelldb/releases/download/v1.9.0/codelldb-aarch64-darwin.vsix";
sha256 = "sha256:1kxrxxlzasa9jl73lqh3n36fzpdgh2hbxpzp8fk6xyzcc5vm9zfb";
};
in
pkgs.vscode-utils.buildVscodeExtension
{
vsix = vsix;
src = vsix;
vscodeExtPublisher = "vadimcn";
vscodeExtName = "vscode-lldb";
vscodeExtUniqueId = "vadimcn-vscode-lldb";
publisher = "vadimcn";
version = "1.9.0";
name = "vadimcn-vscode-lldb-1.9.0";
})
]

11
mbsync.nix Normal file
View File

@@ -0,0 +1,11 @@
{pkgs}:
pkgs.buildEnv {
name = "isync-oauth2";
paths = [pkgs.isync];
pathsToLink = ["/bin"];
nativeBuildInputs = [pkgs.makeWrapper];
postBuild = ''
wrapProgram "$out/bin/mbsync" \
--prefix SASL_PATH : "${pkgs.cyrus_sasl}/lib/sasl2:${pkgs.cyrus-sasl-xoauth2}/lib/sasl2"
'';
}

View File

@@ -1,10 +0,0 @@
[
(self: super: {
# https://github.com/NixOS/nixpkgs/issues/153304
alacritty = super.alacritty.overrideAttrs (
o: rec {
doCheck = false;
}
);
})
]

View File

@@ -1,14 +0,0 @@
{pkgs}: let
my-python-packages = python-packages:
with python-packages; [
pip
mathlibtools
];
in let
packageOverrides = self: super: {
# Test failures on darwin ("windows-1252"); just skip pytest
# (required for elan)
beautifulsoup4 = super.beautifulsoup4.overridePythonAttrs (old: {pytestCheckPhase = "true";});
};
in
(pkgs.python3.override {inherit packageOverrides;}).withPackages my-python-packages

View File

@@ -1,2 +0,0 @@
{nixpkgs, ...}: {
}