# NeoVim IDE setup from scratch This article describes how to get basic functions work in [NeoVim](https://github.com/neovim/neovim) `nvim` for software development, with Rust and Python code example. ## Prerequisite - Ubuntu `16.04` + - NeoVim `v0.7.0` + - Rust (`rustc`, [`rust-anaylzer`](https://github.com/rust-lang/rust-analyzer)) `v1.75.0` + - ensure the path to these executables is added to environmental variable `$PATH` - Python `3.9.6` + - for some plug-ins like [`puremourning/vimspector`](https://github.com/puremourning/vimspector) --- ## Steps for Common Function ### Directory Structure Be sure to have following files in `$HOME/.config/nvim`, so the changes to all the files make sense : ```shell ├── init.lua ├── lua │ ├── keys.lua │ ├── opts.lua │ ├── plug.lua │ └── vars.lua └── plugin └── packer_compiled.lua ``` Add the following script to `$HOME/.config/nvim/init.lua`, to load other sub-modules next time when you open `nvim` ```lua= require('vars') -- Variables require('opts') -- Options require('keys') -- Keymaps require('plug') -- Plugins ``` ### Plug-in Management I choose a NeoVim plug-in [Packer](https://github.com/wbthomason/packer.nvim) for plug-in management. It is essential to manually install `Packer` by cloning the codebase to following default local workspace : ```shell git clone --depth 1 https://github.com/wbthomason/packer.nvim \ $HOME/.local/share/nvim/site/pack/packer/start ``` Add the following script in `$HOME/.config/nvim/lua/plug.lua` : ```lua= return require('packer').startup( function() -- Packer can manage itself, this plug-in -- should be always kept. use 'wbthomason/packer.nvim' -- add more plug-ins by specifying names of -- their Github codebase. end ) ``` #### How to Install New Plug-ins Whenever the script `lua/plug.lua` is saved : - reload the same script by the `nvim` command `:luafile %` - install new plug-ins or uninstall absent plug-ins, using extra `nvim` commands like `:PackerInstall` or `:PackerUpdate`. - all plug-ins managed by `Packer` will be installed under the path `$HOME/.local/share/nvim/site/pack/packer/start`. ### Language Server Protocol (LSP) It is a [framework](https://microsoft.github.io/language-server-protocol/) that provides language-specific information and facilitates other features in development tools. It is typically designed as client-server architecture in which NeoVim `nvim` acts as client side and [`rust-analyzer`](https://github.com/rust-lang/rust-analyzer/tree/master/lib/lsp-server) or [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server) act as server side. #### LSP server setup Depend on the language to use. |Language|tool name|setup steps| |---|---|---| |Rust|[`rust-analyzer`](https://github.com/rust-lang/rust-analyzer/tree/master/lib/lsp-server)|provided as you [install basic Rust tool](https://doc.rust-lang.org/book/ch01-01-installation.html) (since v1.64.0) | |Python|[`python-lsp-server`](https://github.com/python-lsp/python-lsp-server)|It is suggested to install it within a virtual environment. (tips: gather all 3rd-party packages required by `nvim` to the same `venv`)| |Python| [`pyright`](https://github.com/microsoft/pyright) |TODO| |||| #### LSP client setup in NeoVim [Install](#How-to-Install-New-Plug-ins) the following plug-ins : - [`neovim/nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig/) : base configuration. - [`simrat39/rust-tools.nvim`](https://github.com/simrat39/rust-tools.nvim) wraps `lspconfig`, provides more configuration parameters and interacts with Rust language server (in this case, `rust-analyzer`). Add configuration object for `rust-tools` in `init.lua` : - the example below enables [features](https://github.com/simrat39/rust-tools.nvim#usage) like **hover actions** or **expand macros**. - enable more features accroding to your requirement. ```lua= local rt = require("rust-tools") rt.setup({ server = { on_attach = function(_, bufnr) -- Hover actions, move text cursor to any symbol. -- Press your custom key combo to show the inner popup -- which describes the brief definition. -- Switch text cursor to the inner popup by pressing -- the same key combo again vim.keymap.set("n", "<C-space>", rt.hover_actions.hover_actions, { buffer = bufnr }) -- expand macro, show the definition in another -- split window of the same tab vim.keymap.set("n", "mac", rt.expand_macro.expand_macro, { buffer = bufnr }) end, -- `on_attach` callback is invoked when attaching -- a buffer to lanugage server. settings = { ['rust-analyzer'] = { -- cargo.sysroot defaults to `discover`, which means the plug-in client -- or rust-analyzer will try to find it cargo = { sysroot = '/path/to/installed', -- sysrootSrc = '/path/to/installed/lib/rustlib/src/rust/library' }, procMacro = { enable = true, -- note `rust-analyzer-proc-macro-srv` should come with rust toolchain server = '/path/to/installed/libexec/rust-analyzer-proc-macro-srv', }, } } }, }) -- end of rust-tools setup ``` Enable Python language server (in this case I use `python-lsp-server`) by adding the script below : ```lua= local default_lsp = require("lspconfig") default_lsp.pylsp.setup({ settings = { pylsp = { plugins = { maxLineLength = 250, jedi_completion = { include_class_objects = true, include_function_objects = true }, jedi = { environment = os.getenv("VENV_PATH_PYLSP") } -- note each python application may require different -- virtual environment, users need to add -- path to specific venv when typing command -- `nvim /path/to/file.py` }}} }) ``` Refresh the configuration by restarting `nvim` |hover actions|expand macros| |-|-| |![](https://hackmd.io/_uploads/rJZe3aYI2.png)|![](https://hackmd.io/_uploads/rybghpYLh.png)| ## Auto Completion Edit the script below to `lua/plug.lua`, then [Install the plug-in](#How-to-Install-New-Plug-ins) [`nvim-cmp`](https://github.com/hrsh7th/nvim-cmp) and its extensions. ```lua= return require('packer').startup( function() -- other plug-ins -- use 'hrsh7th/nvim-cmp' -- Completion framework use 'hrsh7th/cmp-nvim-lsp' -- LSP completion source: -- Useful completion sources: use 'hrsh7th/cmp-nvim-lua' use 'hrsh7th/cmp-nvim-lsp-signature-help' use 'hrsh7th/cmp-vsnip' use 'hrsh7th/cmp-path' use 'hrsh7th/cmp-buffer' use 'hrsh7th/vim-vsnip' -- other plug-ins -- end ) ``` Add configuration object for `cmp` plug-in in `init.lua`, see [reference](#Reference) for detail : ```lua= -- Completion Plugin Setup local cmpobj = require'cmp' cmpobj.setup({ snippet = { ... }, -- Enable LSP snippets mapping = { ... }, -- keyboard shortcuts sources = { ... }, -- installed source window = { ... }, -- menu layout, symbol for categories formatting = { ... } }) -- end of `cmp` setup ``` Configure [insert-mode completion](https://neovim.io/doc/user/options.html#'completeopt') in `lua/opts.lua` : ```lua= vim.opt.completeopt = {'menuone', 'noselect', 'noinsert'} ``` Restart the `nvim` to enable auto-completion. When you are typing NeoVim prompts a popup menu listing all possible text options, some of the options have documentation, see Rust example below. ![](https://hackmd.io/_uploads/Hyefr7q82.png) ### Key Mappings to Functions You can navigate the popup menu by adding keyboard shortcuts which perform some `cmp` functions in the `mapping` field, where : - `<C-Up>` means `ctrl` key plus `up` arrow key - `<S-Up>` means `shift` key plus `up` arrow key |key combo|command| |---------|-------| |`<C-Up>`|`cmpobj.mapping.select_prev_item()`| |`<C-Down>`|`cmpobj.mapping.select_next_item()`| |`<S-Up>`|`cmpobj.mapping.scroll_docs(-3)`| |`<S-Down>`|`cmpobj.mapping.scroll_docs(3)`| |`<C-cmp>`|`cmp.mapping.complete()`| |`<C-e>`|`cmp.mapping.close()`| ||| ### Sources for Syntax Lookup The sources may come from : - code files of current application project (e.g. `cargo` in Rust) - installed software libraries. Each source in `sources` field contains : - name of the source - minimum number of characters in your keyword to launch syntax lookup The table below shows a valid setting |name|num-chars-keyword| |---------|-------| |`path`|N/A| |`nvim_lsp`|`1`| |`nvim_lua`|`2`| > **Side Note** : > NeoVim is unable to provide more precise information for each text option of a completion popup without language servers and adapters like `rust-tools`, the plug-in `cmp` itself still works. --- ## Go-to Definition ### Prerequisites - [Install plug-ins](#How-to-Install-New-Plug-ins) `nvim-lspconfig` - ensure the language server you use also supports go-to definition. ### Configuration Add new key mapping in the Lua script : ```lua= vim.api.nvim_buf_set_keymap(0, 'n', '<C-]>', '<cmd>lua vim.lsp.buf.definition()<CR>', { noremap = true, silent = true }) ``` Note the module `vim.lsp` in NeoVim built-in Lua provides a generic function [`vim.lsp.buf.definition()`](https://neovim.io/doc/user/lsp.html#lsp-buf), which requests a go-to definition to language server. A typical use case looks like this : - when you move the text cursor to a symbol in NeoVim, a keyboard shortcut `ctrl + ]` will invoke `vim.lsp.buf.definition()`, which sends request to a language server (in this article, `rust-analyzer`) - whenever the language server responds with useful information, NeoVim jumps to another code file which contains the symbol definition. - To get back from the definition, you simply press the key `ctrl + o` or `ctrl + t` (default setting) ### Restriction for Rust Tools Use go-to definition ONLY under the scope of current `cargo` project, if you go beyond it (e.g. to other 3rd-party crates or standard library) , NeoVim will launch new language server (another `rust-analyzer` process) for each different crate , **allocate more memory** and **seem to never release them**. --- ## Debugger This section demonstrates how to set up debugger with NeoVim, [C/C++ extension for VScode](https://github.com/microsoft/vscode-cpptools) and [GDB](https://en.wikipedia.org/wiki/GNU_Debugger). ### Prerequisites - [Install the plug-in](#How-to-Install-New-Plug-ins) [`puremourning/vimspector`](https://github.com/puremourning/vimspector) ```lua= return require('packer').startup( function() -- other plug-ins -- use 'puremourning/vimspector' -- other plug-ins -- end ) ``` - `vimspector` is implemented in **Python** - [create a virtual environment](https://docs.python.org/3/tutorial/venv.html#creating-virtual-environments) using `pip3 -m venv /PATH/TO/PY-WORK-FOR-NVIM` and activate it. - install the python package [`pynvim`](https://github.com/neovim/pynvim) in the virtual environment. - the Python virtual environment should have been activated as long as your NeoVim editor is open for debugging, otherwise NeoVim will report the following error : ``` Vimspector unavailable: Requires Vim compiled with +python3 ``` ### Configuration for Debugger #### Keyboard Shortcuts Add key mappings for debug session control, breakpoint, watchpoint, or line-by-line step control ...etc, to `lua/keys.lua` ```lua= vim.cmd([[ nmap <F5> <cmd>call vimspector#Launch()<cr> nmap <F8> <cmd>call vimspector#Reset()<cr> nmap <F9> <cmd>call vimspector#Continue()<cr> nmap <F10> <cmd>call vimspector#StepOver()<cr> ]]) vim.api.nvim_set_keymap('n', "bp", ":call vimspector#ToggleBreakpoint()<cr>") ``` |key|function|meaning| |-|-|-| |`<F5>`|`vimspector#Launch()`|start a new debug session in a new tab, note you can't do this if current session hasn't terminated yet.| |`<F8>`|`vimspector#Reset()`|terminate current debug session and close the tab.| |`<F9>`|`vimspector#Continue()`|continue the program execution from current breakpoint| |`<F10>`|`vimspector#StepOver()`|run single line of code| |`b + p`|`vimspector#ToggleBreakpoint()`|create / delete a breakpoint on specific line of code file.| Restart your `nvim` after the configuration above is saved, now you should be able to toggle breakpoints on some lines of your code, check the list of breakpoints by a dedicated `nvim` command `:VimspectorBreakpoints`. ![](https://hackmd.io/_uploads/rJyEKQo8h.png) #### Window Layout Change [window size](https://github.com/puremourning/vimspector#changing-the-default-window-sizes) by adding the script below to `lua/opts.lua` ```lua= vim.cmd([[ let g:vimspector_sidebar_width = 64 let g:vimspector_bottombar_height = 15 let g:vimspector_terminal_maxwidth = 60 let g:vimspector_terminal_minwidth = 35 ]]) ``` #### [Debug Profile Configuration](https://puremourning.github.io/vimspector/configuration.html#debug-profile-configuration) It is a JSON object describing debugger behaviour. By default `vimspector` automatically extracts debug configurations from the file `.vimspector.json`. - the value of the `adapter` field in a debug configuration has to be the name of [a gadget properly installed](#Vimspector-Gadget-Installation) in `vimspector` In Rust you can simply place the file on top of `cargo` project. See [the example configuration](https://github.com/metalalive/EnvToolSetupJunkBox/blob/master/code_snippet/lua/nvim/custom_ide/example.vimspector.json). ### Vimspector Gadget Installation It is necessary to [install some gadgets](https://github.com/puremourning/vimspector#install-some-gadgets) the first time you launch debugger with `vimspector`. Restart `nvim` with the environment variable [`SSL_CERT_DIR`](https://www.openssl.org/docs/man3.1/man3/SSL_CTX_set_default_verify_dir.html) , which will be referenced in [`ssl.get_default_verify_paths()`](https://docs.python.org/3/library/ssl.html#ssl.get_default_verify_paths) when `vimspector` is downloading gadgets from remote sites. ```! SSL_CERT_DIR="/path/to/ssl/certs/" nvim /path/to/your/code-file ``` `vimspector` provides extra `nvim` commands `VimspectorInstall` for gadget installation, in this section I'll use [C/C++ extension for VScode](https://github.com/microsoft/vscode-cpptools) : ```! :VimspectorInstall vscode-cpptools ``` Here's the result for successful installation : ``` Vimspector gadget installation complete! ``` > **Note** > > Without `SSL_CERT_DIR`, `vimspector` will encouter download failure because Python does not know the path to appropriate CA certificates by default. > ```! > Installing vscode-cpptools@1.14.5... > - FAILED installing vscode-cpptools: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer ce rtificate (_ssl.c:1129)> > Installing CodeLLDB@v1.9.0... > - FAILED installing CodeLLDB: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certifica te (_ssl.c:1129)> > Failed to install adapters: > * vscode-cpptools > * CodeLLDB > Re-run with --verbose for more info on failures > ``` ### Check Debugger Layout After you've done [the configuration](#Configuration-for-Debugger) above, you may : - launch a debug session by pressing key `<F5>` - control program execution by : - adding / deleting breakpoints with key `b + p` - continue to next breakpoint, or run line by line - monitor current program state, by checking the sub-windows `vimspector.Variables` or `vimspector.StackTrace`. ![](https://hackmd.io/_uploads/Hkck4_jU2.png) > In this section, a debug session will trigger a `gdb` process and `OpenDebugAD7` process for the gadget `vscode-cpptools`, they should be present in active process list of your operating system. --- ## TODO - explore other plug-ins that provide similar functions. - [explore project structure](https://github.com/nvim-tree/nvim-tree.lua), requires `nvim >= 0.8` - figure out root cause of [this issue](#Restriction-for-Rust-Tools) about duplicate Rust LSP servers in go-to definition - `simrat39/rust-tools.nvim` has been archived since Jan. 2024 and does not seem to maintain in foreseeable future, I would switch to another plug-in when I have time for the update. - find a way to prevent `rust-analyzer` from using a lot of memory space when there are more 3rd-party crates to apply to a `cargo` project, more crates mean more syntax trees to load (from these 3rd-party crates). ## Reference #### NeoVim Lua Configuration Check out the detail script at [here](https://github.com/metalalive/EnvToolSetupJunkBox/blob/master/code_snippet/lua/nvim/custom_ide). #### Relevant articles - [Rust and Neovim - A Thorough Guide and Walkthrough](https://rsdlt.github.io/posts/rust-nvim-ide-guide-walkthrough-development-debug) - [Rust analyzer manual](https://rust-analyzer.github.io/manual.html) - [Debugging in Vim with Vimspector and Node.js example - DEV community](https://dev.to/iggredible/debugging-in-vim-with-vimspector-4n0m) - [Neovim from scratch - Github Repo](https://github.com/LunarVim/Neovim-from-scratch) ###### tags: `Dev-tool` `Rust` `Python`