From 87492c2abea6211011a522bd123552edb5b7d65a Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Tue, 26 Mar 2024 00:04:01 +0000 Subject: [PATCH] Big neovim overhaul (#38) --- flake.lock | 214 +++++++++++++-- flake.nix | 11 +- home-manager/home.nix | 133 +++++++--- home-manager/init.vim | 357 -------------------------- home-manager/nvim/chadtree.vim | 4 + home-manager/nvim/init.lua | 256 ++++++++++++++++++ home-manager/nvim/ionide-vim.lua | 263 +++++++++++++++++++ home-manager/nvim/lspconfig.lua | 170 ++++++++++++ home-manager/nvim/nvim-dap-python.lua | 1 + home-manager/nvim/nvim-dap.lua | 99 +++++++ home-manager/nvim/roslyn-nvim.lua | 4 + home-manager/nvim/tokyonight.lua | 5 + home-manager/nvim/treesitter.lua | 9 + home-manager/nvim/venv-selector.lua | 199 ++++++++++++++ home-manager/nvim/which-key.lua | 88 +++++++ 15 files changed, 1395 insertions(+), 418 deletions(-) delete mode 100644 home-manager/init.vim create mode 100644 home-manager/nvim/chadtree.vim create mode 100644 home-manager/nvim/init.lua create mode 100644 home-manager/nvim/ionide-vim.lua create mode 100644 home-manager/nvim/lspconfig.lua create mode 100644 home-manager/nvim/nvim-dap-python.lua create mode 100644 home-manager/nvim/nvim-dap.lua create mode 100644 home-manager/nvim/roslyn-nvim.lua create mode 100644 home-manager/nvim/tokyonight.lua create mode 100644 home-manager/nvim/treesitter.lua create mode 100644 home-manager/nvim/venv-selector.lua create mode 100644 home-manager/nvim/which-key.lua diff --git a/flake.lock b/flake.lock index 82efca9..3db20e4 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1705557527, - "narHash": "sha256-DuxxHTQ/W5KToFLWG4FUF8hLldNo9eXlbt7JgvhrMnY=", + "lastModified": 1710209440, + "narHash": "sha256-1JwFo3u2aVrvpz12OotjCK51EQ0hEDI7xSG7CEvTSk8=", "owner": "tpwrules", "repo": "nixos-apple-silicon", - "rev": "6e324ab06cb27a19409ebc1dc2664bf1e585490a", + "rev": "bdc68b494d6a26c9457f4841ab1a6109b12a33e6", "type": "github" }, "original": { @@ -27,11 +27,11 @@ ] }, "locked": { - "lastModified": 1705915768, - "narHash": "sha256-+Jlz8OAqkOwJlioac9wtpsCnjgGYUhvLpgJR/5tP9po=", + "lastModified": 1710717205, + "narHash": "sha256-Wf3gHh5uV6W1TV/A8X8QJf99a5ypDSugY4sNtdJDe0A=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "1e706ef323de76236eb183d7784f3bd57255ec0b", + "rev": "bcc8afd06e237df060c85bad6af7128e05fd61a3", "type": "github" }, "original": { @@ -50,11 +50,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1706170797, - "narHash": "sha256-oGuFylWYU9OY5DaEJEK+Z7EL81Ln27xz01LN9+8U0P0=", + "lastModified": 1711271005, + "narHash": "sha256-JrhnnutZvHowEJFIrA/rQAFgGAc83WOx+BVy97teqKM=", "owner": "nix-community", "repo": "emacs-overlay", - "rev": "dd5d758f69dd1ae6d0399763aa73ca34974ce9e3", + "rev": "d6bbd32eb3e0f167f312e1031c1beee452dc9174", "type": "github" }, "original": { @@ -78,16 +78,74 @@ "type": "github" } }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "neovim-nightly", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": [ + "neovim-nightly", + "hercules-ci-effects", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709336216, + "narHash": "sha256-Dt/wOWeW6Sqm11Yh+2+t0dfEWxoMxGBvv3JpIocFl9E=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7b3c975cf067e56e7cda6cb098ebe3fb4d74ca2", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, "flake-utils": { "inputs": { "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": { @@ -114,6 +172,46 @@ "type": "github" } }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "hercules-ci-effects": { + "inputs": { + "flake-parts": "flake-parts_2", + "nixpkgs": [ + "neovim-nightly", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1710478346, + "narHash": "sha256-Xjf8BdnQG0tLhPMlqQdwCIjOp7Teox0DP3N/jjyiGM4=", + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "rev": "64e7763d72c1e4c1e5e6472640615b6ae2d40fbf", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "hercules-ci-effects", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -121,11 +219,11 @@ ] }, "locked": { - "lastModified": 1706134977, - "narHash": "sha256-KwNb1Li3K6vuVwZ77tFjZ89AWBo7AiCs9t0Cens4BsM=", + "lastModified": 1711133180, + "narHash": "sha256-WJOahf+6115+GMl3wUfURu8fszuNeJLv9qAWFQl3Vmo=", "owner": "nix-community", "repo": "home-manager", - "rev": "6359d40f6ec0b72a38e02b333f343c3d4929ec10", + "rev": "1c2c5e4cabba4c43504ef0f8cc3f3dfa284e2dbb", "type": "github" }, "original": { @@ -146,29 +244,77 @@ "url": "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin?download=true" } }, + "neovim-flake": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "neovim-nightly", + "nixpkgs" + ] + }, + "locked": { + "dir": "contrib", + "lastModified": 1711323947, + "narHash": "sha256-Vc478rxwJkMuOcgBXm+brraWk9lbFqrGEdXVuST2l/A=", + "owner": "neovim", + "repo": "neovim", + "rev": "02d00cf3eed6681c6dde40585551c8243d7c003f", + "type": "github" + }, + "original": { + "dir": "contrib", + "owner": "neovim", + "repo": "neovim", + "type": "github" + } + }, + "neovim-nightly": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-parts": "flake-parts", + "hercules-ci-effects": "hercules-ci-effects", + "neovim-flake": "neovim-flake", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1711325009, + "narHash": "sha256-c5OJdyuXYzTP+k+PN73X+0pvgXR1yYMYok+72x4SLVg=", + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "rev": "119bbc295f56b531cb87502f5d2fff13dcc35a35", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "neovim-nightly-overlay", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1705316053, - "narHash": "sha256-J2Ey5mPFT8gdfL2XC0JTZvKaBw/b2pnyudEXFvl+dQM=", + "lastModified": 1709961763, + "narHash": "sha256-6H95HGJHhEZtyYA3rIQpvamMKAGoa8Yh2rFV29QnuGw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c3e128f3c0ecc1fb04aef9f72b3dcc2f6cecf370", + "rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34", "type": "github" }, "original": { "owner": "nixos", "repo": "nixpkgs", - "rev": "c3e128f3c0ecc1fb04aef9f72b3dcc2f6cecf370", + "rev": "3030f185ba6a4bf4f18b87f345f104e6a6961f34", "type": "github" } }, "nixpkgs-stable": { "locked": { - "lastModified": 1705916986, - "narHash": "sha256-iBpfltu6QvN4xMpen6jGGEb6jOqmmVQKUrXdOJ32u8w=", + "lastModified": 1711124224, + "narHash": "sha256-l0zlN/3CiodvWDtfBOVxeTwYSRz93muVbXWSpaMjXxM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d7f206b723e42edb09d9d753020a84b3061a79d8", + "rev": "56528ee42526794d413d6f244648aaee4a7b56c0", "type": "github" }, "original": { @@ -180,11 +326,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1706006310, - "narHash": "sha256-nDPz0fj0IFcDhSTlXBU2aixcnGs2Jm4Zcuoj0QtmiXQ=", + "lastModified": 1711231723, + "narHash": "sha256-dARJQ8AJOv6U+sdRePkbcVyVbXJTi1tReCrkkOeusiA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b43bb235efeab5324c5e486882ef46749188eee2", + "rev": "e1d501922fd7351da4200e1275dfcf5faaad1220", "type": "github" }, "original": { @@ -215,6 +361,7 @@ "darwin": "darwin", "emacs": "emacs", "home-manager": "home-manager", + "neovim-nightly": "neovim-nightly", "nixpkgs": "nixpkgs_2", "whisper": "whisper" } @@ -265,9 +412,24 @@ "type": "github" } }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "whisper": { "inputs": { - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "model": "model", "nixpkgs": "nixpkgs_3" }, diff --git a/flake.nix b/flake.nix index 85379b0..ba6ba03 100644 --- a/flake.nix +++ b/flake.nix @@ -18,19 +18,20 @@ 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"; }; whisper = { url = "github:Smaug123/whisper.cpp/nix"; }; + neovim-nightly = { + url = "github:nix-community/neovim-nightly-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { + neovim-nightly, darwin, emacs, nixpkgs, @@ -45,7 +46,7 @@ }; systems = ["aarch64-darwin" "aarch64-linux" "x86_64-linux"]; in let - overlays = [emacs.overlay] ++ import ./overlays.nix; + overlays = [emacs.overlay neovim-nightly.overlay] ++ import ./overlays.nix; recursiveMerge = attrList: let f = attrPath: builtins.zipAttrsWith (n: values: diff --git a/home-manager/home.nix b/home-manager/home.nix index 4732cca..b089597 100644 --- a/home-manager/home.nix +++ b/home-manager/home.nix @@ -38,7 +38,7 @@ programs.zsh = { enable = true; autocd = true; - enableAutosuggestions = true; + autosuggestion.enable = true; enableCompletion = true; history = { expireDuplicatesFirst = true; @@ -47,7 +47,7 @@ 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"; + RUSTFLAGS = "-L ${nixpkgs.libiconv}/lib -L ${nixpkgs.libcxx}/lib"; RUST_BACKTRACE = "full"; }; shellAliases = { @@ -139,65 +139,132 @@ }; programs.neovim = let + pynvimpp = nixpkgs.python3.pkgs.buildPythonPackage { + pname = "pynvim-pp"; + version = "unstable-2024-03-24"; + pyproject = true; + + src = nixpkgs.fetchFromGitHub { + owner = "ms-jpq"; + repo = "pynvim_pp"; + rev = "34e3a027c595981886d7efd1c91071f3eaa4715d"; + hash = "sha256-2+jDRJXlg9q4MN9vOhmeq4cWVJ0wp5r5xAh3G8lqgOg="; + }; + + nativeBuildInputs = [nixpkgs.python3.pkgs.setuptools]; + + propagatedBuildInputs = [nixpkgs.python3.pkgs.pynvim]; + }; + in let pythonEnv = nixpkgs.python3.withPackages (ps: [ ps.pynvim - ps.pynvim-pp + pynvimpp ps.pyyaml ps.std2 ]); + debugPyEnv = nixpkgs.python3.withPackages (ps: [ps.debugpy]); in { enable = true; plugins = [ - nixpkgs.vimPlugins.molokai + { + 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; - config = '' - 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 t :call fsharp#showTooltip() - augroup END - endif - ''; + type = "lua"; + config = builtins.readFile ./nvim/ionide-vim.lua; } { plugin = nixpkgs.vimPlugins.chadtree; - config = "let g:chadtree_settings = {'xdg': v:true}"; + config = builtins.readFile ./nvim/chadtree.vim; } { plugin = nixpkgs.vimPlugins.coq_nvim; - config = ''let g:coq_settings = { 'auto_start': v:true, 'xdg': v:true }''; + config = ''let g:coq_settings = { 'auto_start': 'shut-up', 'xdg': v:true }''; } { - plugin = nixpkgs.vimPlugins.rust-vim; - config = "let g:rustfmt_autosave = 1"; + plugin = nixpkgs.vimPlugins.rustaceanvim; } { plugin = nixpkgs.vimPlugins.LanguageClient-neovim; - config = "let g:LanguageClient_serverCommands = { 'nix': ['rnix-lsp'] }"; } { - plugin = nixpkgs.vimPlugins.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''; + 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"; } - - nixpkgs.vimPlugins.tagbar ]; viAlias = true; vimAlias = true; vimdiffAlias = true; withPython3 = true; - extraLuaConfig = ''vim.g.python3_host_prog="${pythonEnv}/bin/python"''; - extraConfig = builtins.readFile ./init.vim; + extraLuaConfig = builtins.replaceStrings ["%PYTHONENV%"] ["${pythonEnv}"] (builtins.readFile ./nvim/init.lua); + + package = nixpkgs.neovim-nightly; }; programs.direnv = { @@ -218,6 +285,10 @@ }; home.packages = [ + nixpkgs.csharp-ls + nixpkgs.netcoredbg + nixpkgs.nil + nixpkgs.fsautocomplete nixpkgs.keepassxc nixpkgs.rust-analyzer nixpkgs.tmux @@ -244,7 +315,6 @@ nixpkgs.git-lfs nixpkgs.imagemagick nixpkgs.nixpkgs-fmt - nixpkgs.rnix-lsp nixpkgs.grpc-tools nixpkgs.element-desktop nixpkgs.ihp-new @@ -252,6 +322,7 @@ nixpkgs.lnav nixpkgs.age nixpkgs.nodejs + nixpkgs.nodePackages.pyright nixpkgs.sqlitebrowser nixpkgs.typst nixpkgs.poetry @@ -262,6 +333,8 @@ nixpkgs.ffmpeg nixpkgs.bat nixpkgs.pandoc + nixpkgs.fd + nixpkgs.sumneko-lua-language-server (nixpkgs.nerdfonts.override {fonts = ["FiraCode" "DroidSansMono"];}) ]; diff --git a/home-manager/init.vim b/home-manager/init.vim deleted file mode 100644 index e1f19bc..0000000 --- a/home-manager/init.vim +++ /dev/null @@ -1,357 +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 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 to / (search) and Ctrl- to ? (backwards search) -map / -map ? - -" Disable highlight when is pressed -map :noh - -" Smart way to move between windows -map j -map k -map h -map l - -" Close the current buffer -map bd :Bclose:tabclosegT - -" Close all the buffers -map ba :bufdo bd - -map l :bnext -map h :bprevious - -" Useful mappings for managing tabs -map tn :tabnew -map to :tabonly -map tc :tabclose -map tm :tabmove -map t :tabnext - -" Let 'tl' toggle between this and the last accessed tab -let g:lasttab = 1 -nmap tl :exe "tabn ".g:lasttab -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 te :tabedit =expand("%:p:h")/ - -" Switch CWD to the directory of the open buffer -map cd :cd %:p:h:pwd - -" 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 ss :setlocal spell! - -" Shortcuts using -map sn ]s -map sp [s -map sa zg -map s? z= - - -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" => Misc -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" Remove the Windows ^M - when the encodings gets messed up -noremap m mmHmt:%s///ge'tzt'm - -" Quickly open a buffer for scribble -map q :e ~/buffer - -" Quickly open a markdown buffer for scribble -map x :e ~/buffer.md - -" Toggle paste mode on and off -map pp :setlocal paste! - - -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" => 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 BufcloseCloseIt() -function! 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 c :!cargo clippy -nnoremap 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 diff --git a/home-manager/nvim/chadtree.vim b/home-manager/nvim/chadtree.vim new file mode 100644 index 0000000..f86b780 --- /dev/null +++ b/home-manager/nvim/chadtree.vim @@ -0,0 +1,4 @@ +let g:chadtree_settings = {'xdg': v:true} + +autocmd VimEnter * CHADopen --nofocus +autocmd bufenter * if (winnr("$") == 1 && &filetype == 'CHADtree') | q | endif diff --git a/home-manager/nvim/init.lua b/home-manager/nvim/init.lua new file mode 100644 index 0000000..bc4f1a7 --- /dev/null +++ b/home-manager/nvim/init.lua @@ -0,0 +1,256 @@ +vim.g.python3_host_prog = "%PYTHONENV%/bin/python" +vim.opt.mouse = "" +vim.opt.history = 500 +vim.opt.background = "dark" + +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 CWD: %r%{getcwd()}%h Line: %l Column: %c" + +-------------------------------------------------------------- + +vim.api.nvim_set_keymap("n", ";", "", { noremap = true }) +vim.api.nvim_set_var("maplocalleader", ";") + +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("") + 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 status, whichkey = pcall(require, "which-key") +if status then + 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 + -- TODO: If a command is a prefix of an existing command, prepend its description to those commands' descriptions, and append a '...' to the parent's description. + 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", "", 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({ + -- TODO: this isn't working for the FSI ones - maybe we've moved to a different buffer by the time we ask for the keymap? + [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", + }, + ["cd"] = { + ChangeToCurrentDirectory, + "Switch CWD to the directory of the open buffer", + }, + ["ss"] = { + ToggleSpell, + "Toggle spell-checker on or off", + }, + }, { prefix = vim.api.nvim_get_var("maplocalleader") }) +else + vim.api.nvim_set_keymap("n", "mp", ":lua MarkdownPreview()", { noremap = true, silent = true }) + -- Remove the Windows ^M - when the encodings gets messed up + vim.api.nvim_set_keymap("n", "md", ":lua RemoveCarriageReturn()", { noremap = true }) + vim.api.nvim_set_keymap("n", "j", ":lua FormatJson()", { noremap = true }) + vim.api.nvim_set_keymap("n", "cd", ":lua ChangeToCurrentDirectory()", { noremap = true }) +end diff --git a/home-manager/nvim/ionide-vim.lua b/home-manager/nvim/ionide-vim.lua new file mode 100644 index 0000000..43fef16 --- /dev/null +++ b/home-manager/nvim/ionide-vim.lua @@ -0,0 +1,263 @@ +vim.g["fsharp#fsautocomplete_command"] = { "fsautocomplete" } +vim.g["fsharp#show_signature_on_cursor_move"] = 1 +vim.g["fsharp#fsi_keymap"] = "none" + +-- MASSIVE HACK - raised https://github.com/ionide/Ionide-vim/pull/78 +local function captureLoadedProjects() + vim.fn.execute("redir => g:massive_hack_patrick_capture") + vim.fn.execute("call fsharp#showLoadedProjects()") + vim.fn.execute("redir END") + local output = vim.fn.eval("g:massive_hack_patrick_capture") + + local projects = {} + + for line in output:gmatch("[^\r\n]+") do + local project = line:gsub("^%s*-%s*", "") + table.insert(projects, project) + end + + return projects +end + +-- Supply nil to get all loaded F# projects and build them. +local function BuildFSharpProjects(projects) + local function on_output(context, prefix, err, data) + 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.buf) + vim.api.nvim_buf_set_lines(context.buf, 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.buf) + vim.api.nvim_buf_set_lines( + context.buf, + 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.buf then + local new_line_count = vim.api.nvim_buf_line_count(context.buf) + vim.api.nvim_win_set_cursor(context.window, { new_line_count, 0 }) + end + end + -- 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) + end + end + + local function spawn_next(context) + if context.completed == context.expected then + if context.errs == 0 and context.warn == 0 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.buf then + vim.api.nvim_win_close(context.window, 1) + end + print("All builds successful") + end + else + local handle + local stdout = vim.loop.new_pipe(false) + local stderr = vim.loop.new_pipe(false) + + handle, _ = vim.loop.spawn( + "dotnet", + { + args = { "build", context.projects[context.completed + 1] }, + stdio = { nil, stdout, stderr }, + }, + vim.schedule_wrap(function(code, signal) + stdout:read_stop() + stderr:read_stop() + stdout:close() + stderr:close() + handle:close() + 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 + .. ")" + ) + + spawn_next(context) + end) + ) + + if not handle then + print("Failed to start build process.") + return + end + + vim.loop.read_start(stdout, function(err, data) + on_output(context, "OUT", err, data) + end) + vim.loop.read_start(stderr, function(err, data) + on_output(context, "ERR", err, data) + end) + end + end + + if not projects then + projects = captureLoadedProjects() + end + if projects then + local total_projects = 0 + for _, _ in ipairs(projects) do + total_projects = total_projects + 1 + end + + -- 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("columns") + local height = vim.api.nvim_get_option("lines") + local win_height = math.min(10, math.floor(height * 0.2)) -- 20% of total height or 10 lines + local original_win = vim.api.nvim_get_current_win() + 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, true, win_opts) + -- Switch back to the original window + vim.api.nvim_set_current_win(original_win) + + local build_context = { + warn = 0, + errs = 0, + completed = 0, + expected = total_projects, + window = win, + projects = projects, + buf = buf, + } + + spawn_next(build_context) + end +end + +-- local function fsprojAndDirCompletion(ArgLead, _, _) +-- local results = {} +-- local loc = ArgLead +-- if not loc then +-- loc = "." +-- end +-- local command = string.format( +-- "find " +-- .. vim.fn.shellescape(loc) +-- .. " -maxdepth 2 \\( -type f -name '*.fsproj' -o -type d \\) -print0 2> /dev/null" +-- ) +-- local handle = io.popen(command) +-- if handle then +-- local stdout = handle:read("*all") +-- handle:close() +-- +-- local allResults = {} +-- for match in string.gmatch(stdout, "([^%z]+)") do +-- table.insert(allResults, match) +-- end +-- table.sort(allResults, function(a, b) +-- local aEndsWithProj = a:match("proj$") +-- local bEndsWithProj = b:match("proj$") +-- if aEndsWithProj and not bEndsWithProj then +-- return true +-- elseif not aEndsWithProj and bEndsWithProj then +-- return false +-- else +-- return a < b -- If both or neither end with 'proj', sort alphabetically +-- end +-- end) +-- +-- for _, line in ipairs(allResults) do +-- table.insert(results, line) +-- end +-- end +-- return results +-- 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 = "Actions", + finder = finders.new_table({ + results = captureLoadedProjects(), + }), + 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" }) + +vim.api.nvim_create_autocmd("FileType", { + pattern = "fsharp", + callback = function() + local status, whichkey = pcall(require, "which-key") + if status then + whichkey.register({ + f = { + t = { ":call fsharp#showTooltip()", "Show F# Tooltip" }, + ["si"] = { ":call fsharp#toggleFsi()", "Toggle FSI (F# Interactive)" }, + ["sl"] = { ":call fsharp#sendLineToFsi()", "Send line to FSI (F# Interactive)" }, + }, + b = { + p = { + 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", "ft", ":call fsharp#showTooltip()", { noremap = true }) + vim.api.nvim_set_keymap("n", "fsi", ":call fsharp#toggleFsi()", { noremap = true }) + vim.api.nvim_set_keymap("n", "fsl", ":call fsharp#sendLineToFsi()", { noremap = true }) + vim.api.nvim_set_keymap("n", "bpa", BuildFSharpProjects, { noremap = true }) + vim.api.nvim_set_keymap("n", "bps", ":BuildFSharpProject", { noremap = true }) + end + end, +}) diff --git a/home-manager/nvim/lspconfig.lua b/home-manager/nvim/lspconfig.lua new file mode 100644 index 0000000..e8f1275 --- /dev/null +++ b/home-manager/nvim/lspconfig.lua @@ -0,0 +1,170 @@ +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({}) + +require("lspconfig")["lua_ls"].setup({ + on_init = function(client) + local path = client.workspace_folders[1].name + if vim.loop.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("maplocalleader") }) + else + vim.keymap.set("n", "lp", vim.diagnostic.goto_prev) + vim.keymap.set("n", "ln", vim.diagnostic.goto_next) + vim.keymap.set("n", "ll", ToggleLocList) + vim.keymap.set("n", "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_status, whichkey = pcall(require, "which-key") + -- Enable completion triggered by + 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 + local opts = { buffer = ev.buf } + if whichkey_status then + 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({ + [""] = { 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 = "" }) + else + vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts) + vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts) + vim.keymap.set("n", "K", vim.lsp.buf.hover, opts) + vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts) + vim.keymap.set("n", "", vim.lsp.buf.signature_help, opts) + vim.keymap.set("n", "wa", vim.lsp.buf.add_workspace_folder, opts) + vim.keymap.set("n", "wr", vim.lsp.buf.remove_workspace_folder, opts) + vim.keymap.set("n", "wl", function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, opts) + vim.keymap.set("n", "D", vim.lsp.buf.type_definition, opts) + vim.keymap.set("n", "rn", vim.lsp.buf.rename, opts) + vim.keymap.set({ "n", "v" }, "ca", vim.lsp.buf.code_action, opts) + vim.keymap.set("n", "gr", function() + require("telescope.builtin").lsp_references() + end, opts) + vim.keymap.set("n", "f", function() + vim.lsp.buf.format({ async = true }) + end, opts) + end + end, +}) diff --git a/home-manager/nvim/nvim-dap-python.lua b/home-manager/nvim/nvim-dap-python.lua new file mode 100644 index 0000000..cd88bac --- /dev/null +++ b/home-manager/nvim/nvim-dap-python.lua @@ -0,0 +1 @@ +require("dap-python").setup("%PYTHONENV%/bin/python") diff --git a/home-manager/nvim/nvim-dap.lua b/home-manager/nvim/nvim-dap.lua new file mode 100644 index 0000000..e4ab02e --- /dev/null +++ b/home-manager/nvim/nvim-dap.lua @@ -0,0 +1,99 @@ +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 status, whichkey = pcall(require, "which-key") + if status then + 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") }) + else + vim.api.nvim_set_keymap("n", "do", ":lua require('dap').step_over()", { noremap = true }) + vim.api.nvim_set_keymap("n", "di", ":lua require('dap').step_into()", { noremap = true }) + vim.api.nvim_set_keymap("n", "dc", ":lua require('dap').continue()", { noremap = true }) + vim.api.nvim_set_keymap("n", "dC", ":lua require('dap').run_last()", { noremap = true }) + vim.api.nvim_set_keymap( + "n", + "db", + ":lua require('dap').toggle_breakpoint()", + { noremap = true } + ) + vim.api.nvim_set_keymap("n", "dr", ":lua require('dap').repl.open()", { noremap = true }) + vim.api.nvim_set_keymap( + "n", + "dvv", + ":lua require('dap.ui.widgets').hover()", + { noremap = true } + ) + vim.api.nvim_set_keymap( + "n", + "dvs", + ":lua require('dap.ui.widgets').sidebar(require('dap.ui.widgets').scopes).open()", + { noremap = true } + ) + vim.api.nvim_set_keymap( + "n", + "dvf", + ":lua require('dap.ui.widgets').sidebar(require('dap.ui.widgets').frames).open()", + { noremap = true } + ) + vim.api.nvim_set_keymap("n", "dt", ":lua require('dap').terminate()", { noremap = true }) + end +end diff --git a/home-manager/nvim/roslyn-nvim.lua b/home-manager/nvim/roslyn-nvim.lua new file mode 100644 index 0000000..7a78a9f --- /dev/null +++ b/home-manager/nvim/roslyn-nvim.lua @@ -0,0 +1,4 @@ +require("roslyn").setup({ + on_attach = function(_, _) end, + capabilities = vim.lsp.protocol.make_client_capabilities(), +}) diff --git a/home-manager/nvim/tokyonight.lua b/home-manager/nvim/tokyonight.lua new file mode 100644 index 0000000..229ff32 --- /dev/null +++ b/home-manager/nvim/tokyonight.lua @@ -0,0 +1,5 @@ +require("tokyonight").setup({ + style = "night", +}) + +vim.cmd([[colorscheme tokyonight]]) diff --git a/home-manager/nvim/treesitter.lua b/home-manager/nvim/treesitter.lua new file mode 100644 index 0000000..c33e5af --- /dev/null +++ b/home-manager/nvim/treesitter.lua @@ -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, + }, +}) diff --git a/home-manager/nvim/venv-selector.lua b/home-manager/nvim/venv-selector.lua new file mode 100644 index 0000000..84f6cd7 --- /dev/null +++ b/home-manager/nvim/venv-selector.lua @@ -0,0 +1,199 @@ +local venv_selector = require("venv-selector") + +venv_selector.setup({ + changed_venv_hooks = { venv_selector.hooks.pyright }, + name = { "venv", ".venv" }, +}) + +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 handle + local stdout = vim.loop.new_pipe(false) + local stderr = vim.loop.new_pipe(false) + + local function on_output(context, prefix, err, data) + 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.buf) + vim.api.nvim_buf_set_lines( + context.buf, + 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.buf) + vim.api.nvim_buf_set_lines( + context.buf, + 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.buf then + local new_line_count = vim.api.nvim_buf_line_count(context.buf) + vim.api.nvim_win_set_cursor(context.window, { new_line_count, 0 }) + end + end + end) + end + end + + -- TODO: commonise wth what's in ionide-vim + + -- 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("columns") + local height = vim.api.nvim_get_option("lines") + local win_height = math.min(10, math.floor(height * 0.2)) -- 20% of total height or 10 lines + local original_win = vim.api.nvim_get_current_win() + 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, true, win_opts) + -- Switch back to the original window + vim.api.nvim_set_current_win(original_win) + + local context = { + window = win, + buf = buf, + } + + handle, _ = vim.loop.spawn( + -- TODO: do we need to escape this? Don't know whether spawn goes via a shell + venv_dir .. "/bin/python", + { + -- TODO: and do we need to escape this? + args = { "-m", "pip", "install", "-r", requirements_path }, + stdio = { nil, stdout, stderr }, + }, + vim.schedule_wrap(function(code, signal) + stdout:read_stop() + stderr:read_stop() + stdout:close() + stderr:close() + handle:close() + print("Venv creation completed, exit code " .. code .. " and signal " .. signal) + load_venv(venv_dir) + end) + ) + + if not handle then + print("Failed to start venv install process.") + return + end + + vim.loop.read_start(stdout, function(err, data) + on_output(context, "OUT", err, data) + end) + vim.loop.read_start(stderr, function(err, data) + on_output(context, "ERR", err, data) + end) + else + load_venv(venv_dir) + end +end + +do + local status, whichkey = pcall(require, "which-key") + if status then + whichkey.register({ + p = { + name = "Python-related commands", + v = { + name = "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"), buffer = vim.api.nvim_get_current_buf() }) + else + vim.api.nvim_set_keymap("n", "pvc", ":lua CreateVenv()", { noremap = true }) + vim.api.nvim_set_keymap("n", "pvl", ":lua SelectVenv()", { noremap = true }) + vim.api.nvim_set_keymap("n", "pvo", ":VenvSelect", { noremap = true }) + end +end diff --git a/home-manager/nvim/which-key.lua b/home-manager/nvim/which-key.lua new file mode 100644 index 0000000..c94e855 --- /dev/null +++ b/home-manager/nvim/which-key.lua @@ -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 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 + 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: + -- [""] = "SPC", + -- [""] = "RET", + -- [""] = "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 = "", -- binding to scroll down inside the popup + scroll_up = "", -- 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 = { "", "", "", "", "^:", "^ ", "^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 = {""} -- or specifiy a list manually + -- list of triggers, where WhichKey should not wait for timeoutlen and show immediately + triggers_nowait = { + -- marks + "`", + "'", + "g`", + "g'", + -- registers + '"', + "", + -- 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 = {}, + }, +})