Neovim LSP 将取代 VSCode

2025-06-07

Neovim LSP 将取代 VSCode

如果您正在寻找一种更简单的解决方案来用终端编辑器替换 VSCode,我建议您阅读我的极简 vim帖子。

TLDR;

所有代码均可从nvim-code获取

设置

本文假设 neovim 0.6+ 已安装并准备就绪。接下来,我们来规划一下接下来要处理的目录结构。

Neovim 默认使用${HOME}/.config/nvim作为配置目录。我将以此为基础构建配置文件。

nvim
├── after
│   └── ftplugin
│       └── python.lua
├── init.lua
├── lua
│   ├── _lsp.lua
│   ├── _options.lua
│   ├── _plugins.lua
│   ├── _statusline.lua
│   ├── _telescope.lua
│   ├── _treesitter.lua
│   └── _whichkey.lua
Enter fullscreen mode Exit fullscreen mode

初始化lua

require("_options")
require("_plugins")
require("_lsp")
require("_treesitter")
require("_telescope")
require("_whichkey")
require("_statusline")

vim.cmd("colorscheme walh-gruvbox")
Enter fullscreen mode Exit fullscreen mode

_options.lua

我不会介绍所有可用的选项,但这里列出了一些可以使用或省略的常用选项。这很大程度上受到了lunarvim的启发。

vim.g.mapleader = " "

vim.g.border_style = "rounded"
vim.g.markdown_fenced_languages = {
    "bash=sh",
}

vim.opt.backup = false -- creates a backup file
vim.opt.clipboard = "" -- don't use clipboard
vim.opt.cmdheight = 1 -- more space in the neovim command line for displaying messages
vim.opt.colorcolumn = "99999" -- fixes indentline for now
vim.opt.completeopt = { "menuone", "noselect" }
vim.opt.conceallevel = 0 -- so that `` is visible in markdown files
vim.opt.cursorline = true -- highlight the current line
vim.opt.expandtab = true -- convert tabs to spaces
vim.opt.fileencoding = "utf-8" -- the encoding written to a file
vim.opt.foldexpr = "" -- set to "nvim_treesitter#foldexpr()" for treesitter based folding
vim.opt.foldmethod = "manual" -- folding set to "expr" for treesitter based folding
vim.opt.hidden = true -- required to keep multiple buffers and open multiple buffers
vim.opt.hlsearch = true -- highlight all matches on previous search pattern
vim.opt.ignorecase = true -- ignore case in search patterns
vim.opt.list = true
vim.opt.listchars = "tab:│ ,trail:·,nbsp:+"
vim.opt.number = true -- set numbered lines
vim.opt.numberwidth = 1 -- set number column width to 2 {default 4}
vim.opt.pumheight = 10 -- pop up menu height
vim.opt.relativenumber = false -- set relative numbered lines
vim.opt.scrolloff = 4 -- is one of my fav
vim.opt.shiftwidth = 2 -- the number of spaces inserted for each indentation
vim.opt.showmode = false -- we don't need to see things like -- INSERT -- anymore
vim.opt.sidescrolloff = 4
vim.opt.signcolumn = "yes" -- always show the sign column otherwise it would shift the text each time
vim.opt.smartcase = true -- smart case
vim.opt.smartindent = true -- make indenting smarter again
vim.opt.spell = false -- disable spell checking
vim.opt.spelllang = "en" -- language for spell checking
vim.opt.splitbelow = true -- force all horizontal splits to go below current window
vim.opt.splitright = true -- force all vertical splits to go to the right of current window
vim.opt.swapfile = false -- creates a swapfile
vim.opt.tabstop = 2 -- insert 2 spaces for a tab
vim.opt.termguicolors = false -- set term gui colors (most terminals support this)
vim.opt.timeoutlen = 500 -- timeout length
vim.opt.title = true -- set the title of window to the value of the titlestring
vim.opt.titlestring = "%<%F - nvim" -- what the title of the window will be set to
vim.opt.undodir = vim.fn.stdpath("cache") .. "/undo"
vim.opt.undofile = true -- enable persistent undo
vim.opt.updatetime = 300 -- faster completion
vim.opt.wrap = true -- display lines as one long line
vim.opt.writebackup = false -- if a file is being edited by another program (or was written to file while editing with another program) it is not allowed to be edited

vim.opt.showtabline = 2 -- always show tabs
vim.opt.laststatus = 2 -- hide statusline
Enter fullscreen mode Exit fullscreen mode

_plugins.lua

我不得不承认使用 neovims 内置 lsp 很好,但它需要安装许多插件才能获得与 VSCode 类似的体验。

local fn = vim.fn
local install_path = fn.stdpath("data") .. "/site/pack/packer/start/packer.nvim"
if fn.empty(fn.glob(install_path)) > 0 then
    packer_bootstrap = fn.system({
        "git",
        "clone",
        "--depth",
        "1",
        "https://github.com/wbthomason/packer.nvim",
        install_path,
    })
end

return require("packer").startup(function()
    use({
        "L3MON4D3/LuaSnip",
        "casonadams/walh",
        "folke/trouble.nvim",
        "hrsh7th/cmp-buffer",
        "hrsh7th/cmp-cmdline",
        "hrsh7th/cmp-nvim-lsp",
        "hrsh7th/cmp-path",
        "hrsh7th/nvim-cmp",
        "jose-elias-alvarez/null-ls.nvim",
        "neovim/nvim-lspconfig",
        "nvim-lua/lsp-status.nvim",
        "nvim-treesitter/nvim-treesitter",
        "saadparwaiz1/cmp_luasnip",
        "tamago324/nlsp-settings.nvim",
        "wbthomason/packer.nvim",
        "williamboman/nvim-lsp-installer",
    })
    use({
        "rafamadriz/friendly-snippets",
    })
    use({
        "nvim-telescope/telescope.nvim",
        requires = { "nvim-lua/plenary.nvim" },
    })
    use({
        "nvim-lualine/lualine.nvim",
        requires = { "kyazdani42/nvim-web-devicons", opt = true },
    })
    use({
        "folke/which-key.nvim",
        config = function()
            require("which-key").setup({})
        end,
    })
    use({
        "terrortylor/nvim-comment",
        config = function()
            require("nvim_comment").setup({})
        end,
    })
    use({
        "lewis6991/gitsigns.nvim",
        config = function()
            require("gitsigns").setup({ yadm = { enable = true } })
        end,
    })
    use({
        "ethanholz/nvim-lastplace",
        event = "BufRead",
        config = function()
            require("nvim-lastplace").setup({
                lastplace_ignore_buftype = { "quickfix", "nofile", "help" },
                lastplace_ignore_filetype = { "gitcommit", "gitrebase", "svn", "hgcommit" },
                lastplace_open_folds = true,
            })
        end,
    })
    -- Automatically set up your configuration after cloning packer.nvim
    -- Put this at the end after all plugins
    if packer_bootstrap then
        require("packer").sync()
    end
end)
Enter fullscreen mode Exit fullscreen mode

_lsp.lua

local cmp = require("cmp")
local lsp_status = require("lsp-status")

local win = require("lspconfig.ui.windows")
local _default_opts = win.default_opts

win.default_opts = function(options)
    local opts = _default_opts(options)
    opts.border = "rounded"
    return opts
end

-- statusline progress setup
lsp_status.config({
    current_function = false,
    show_filename = false,
    diagnostics = false,
    status_symbol = "",
    select_symbol = nil,
    update_interval = 200,
})

-- completion setup
cmp.setup({
    snippet = {
        expand = function(args)
            -- vim.fn["vsnip#anonymous"](args.body)
            require("luasnip").lsp_expand(args.body) -- For `luasnip` users.
            -- vim.fn["UltiSnips#Anon"](args.body)
        end,
    },
    mapping = {
        ["<C-d>"] = cmp.mapping.scroll_docs(-4),
        ["<C-f>"] = cmp.mapping.scroll_docs(4),
        ["<C-Space>"] = cmp.mapping.complete(),
        ["<C-e>"] = cmp.mapping.close(),
        ["<CR>"] = cmp.mapping.confirm({ select = false }),
        ["<Tab>"] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "s" }),
        ["<S-Tab>"] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "s" }),
    },
    sources = {
        { name = "nvim_lsp" },
        { name = "luasnip" },
        -- { name = "ultisnips" },
        -- { name = "vsnip" },
        { name = "buffer" },
        { name = "path" },
    },
})

-- helper function for mappings
local m = function(mode, key, result)
    vim.api.nvim_buf_set_keymap(0, mode, key, "<cmd> " .. result .. "<cr>", {
        noremap = true,
        silent = true,
    })
end

-- function to attach completion when setting up lsp
local on_attach = function(client)
    lsp_status.register_progress()
    lsp_status.on_attach(client)

    -- Mappings.
    m("n", "ga", "lua vim.lsp.buf.code_action()")
    m("n", "gD", "lua vim.lsp.buf.declaration()")
    m("n", "gd", "lua vim.lsp.buf.definition()")
    m("n", "ge", "lua vim.lsp.diagnostic.goto_next()")
    m("n", "gE", "lua vim.lsp.diagnostic.goto_prev()")
    m("n", "gi", "lua vim.lsp.buf.implementation()")
    m("n", "gr", "lua vim.lsp.buf.references()")
    m("n", "K", "lua vim.lsp.buf.hover()")
    -- m("n", "<space>rn", "lua vim.lsp.buf.rename()")
    m("n", "gl", "lua vim.lsp.diagnostic.show_line_diagnostics()")
    -- m("n", "<space>f", "lua vim.lsp.buf.formatting()")
end

-- setup lsp installer
local lsp_installer = require("nvim-lsp-installer")
-- Provide settings first!
lsp_installer.settings({
    ui = {
        icons = {
            server_installed = "✓",
            server_pending = "➜",
            server_uninstalled = "✗",
        },
    },
})
lsp_installer.on_server_ready(function(server)
    local opts = {
        on_attach = on_attach,
        capabilities = require("cmp_nvim_lsp").update_capabilities(vim.lsp.protocol.make_client_capabilities()),
        flags = {
            debounce_text_changes = 150,
        },
    }
    server:setup(opts)
end)

-- lsp settings
require("nlspsettings").setup()

-- diagnostics
vim.diagnostic.config({
    virtual_text = false,
    underline = true,
    float = {
        source = "always",
    },
    severity_sort = true,
    --[[ virtual_text = {
      prefix = "»",
      spacing = 4,
    }, ]]
    signs = true,
    update_in_insert = false,
})
Enter fullscreen mode Exit fullscreen mode

_treesitter.lua

require'nvim-treesitter.configs'.setup {
  ensure_installed = "maintained",
  sync_install = false,
  highlight = {
    enable = true,
    additional_vim_regex_highlighting = false,
  },
}
Enter fullscreen mode Exit fullscreen mode

_telescope.lua

require("telescope").setup({
    defaults = {
        border = true,
        layout_strategy = "bottom_pane",
        layout_config = {
            height = 0.30,
            width = 1.00,
        },
        -- path_display = { "shorten" },
        sorting_strategy = "ascending",
    },
})

require("trouble").setup({
  icons=false
})
Enter fullscreen mode Exit fullscreen mode

_whichkey.lua

local which_key = {
    setup = {
        plugins = {
            marks = true,
            registers = true,
            presets = {
                operators = false,
                motions = false,
                text_objects = false,
                windows = true,
                nav = true,
                z = true,
                g = true,
            },
            spelling = { enabled = true, suggestions = 20 },
        },
        icons = {
            breadcrumb = "»",
            separator = "➜",
            group = "+",
        },
        window = {
            border = "none", -- none, single, double, shadow
            position = "bottom", -- bottom, top
            margin = { 1, 0, 1, 0 },
            padding = { 2, 2, 2, 2 },
        },
        layout = {
            height = { min = 4, max = 25 },
            width = { min = 20, max = 50 },
            spacing = 3,
        },
        hidden = { "<silent>", "<cmd>", "<Cmd>", "<CR>", "call", "lua", "^:", "^ " },
        show_help = true,
    },

    opts = {
        mode = "n",
        prefix = "<leader>",
        buffer = nil,
        silent = true,
        noremap = true,
        nowait = true,
    },
    vopts = {
        mode = "v",
        prefix = "<leader>",
        buffer = nil,
        silent = true,
        noremap = true,
        nowait = true,
    },
    -- NOTE: Prefer using : over <cmd> as the latter avoids going back in normal-mode.
    -- see https://neovim.io/doc/user/map.html#:map-cmd
    vmappings = {},
    mappings = {
        ["c"] = { ":BufferClose!<CR>", "Close Buffer" },
        ["e"] = { ":Telescope file_browser <CR>", "File Browser" },
        ["f"] = { ":Telescope find_files <CR>", "Find File" },
        ["h"] = { ":nohlsearch<CR>", "No Highlight" },
        b = {
            name = "Buffers",
            l = { ":Telescope buffers<CR>", "List Buffers" },
            b = { ":b#<cr>", "Previous" },
            d = { ":bd<cr>", "Delete" },
            f = { ":Telescope buffers <cr>", "Find" },
            n = { ":bn<cr>", "Next" },
            p = { ":bp<cr>", "Previous" },
        },
        p = {
            name = "Packer",
            c = { ":PackerCompile<cr>", "Compile" },
            i = { ":PackerInstall<cr>", "Install" },
            r = { ":lua require('lvim.utils').reload_lv_config()<cr>", "Reload" },
            s = { ":PackerSync<cr>", "Sync" },
            S = { ":PackerStatus<cr>", "Status" },
            u = { ":PackerUpdate<cr>", "Update" },
        },
        l = {
            name = "LSP",
            a = { ":Telescope lsp_code_actions<cr>", "Code Action" },
            d = {
                ":Telescope lsp_document_diagnostics<cr>",
                "Document Diagnostics",
            },
            w = {
                ":Telescope diagnostics<cr>",
                "Workspace Diagnostics",
            },
            f = { ":lua vim.lsp.buf.formatting()<cr>", "Format" },
            i = { ":LspInfo<cr>", "Info" },
            I = { ":LspInstallInfo<cr>", "Installer Info" },
            r = { ":lua vim.lsp.buf.rename()<cr>", "Rename" },
        },
        s = {
            name = "Search",
            b = { ":Telescope git_branches <cr>", "Checkout branch" },
            c = { ":Telescope colorscheme <cr>", "Colorscheme" },
            C = { ":Telescope commands <cr>", "Commands" },
            f = { ":Telescope find_files <cr>", "Find File" },
            h = { ":Telescope help_tags <cr>", "Find Help" },
            j = { ":Telescope jumplist <cr>", "Jumplist" },
            k = { ":Telescope keymaps <cr>", "Keymaps" },
            M = { ":Telescope man_pages <cr>", "Man Pages" },
            r = { ":Telescope oldfiles <cr>", "Open Recent File" },
            R = { ":Telescope registers <cr>", "Registers" },
            t = { ":Telescope live_grep <cr>", "Text" },
            n = { ":Telescope live_grep search_dirs={os.getenv('NOTES')} <cr>", "Notes" },
            p = {
                ":lua require('telescope.builtin.internal').colorscheme({enable_preview = true})<cr>",
                "Colorscheme with Preview",
            },
        },
        T = {
            name = "Treesitter",
            i = { ":TSConfigInfo<cr>", "Info" },
        },
        t = {
            name = "Diagnostics",
            t = { "<cmd>TroubleToggle<cr>", "trouble" },
            w = { "<cmd>TroubleToggle workspace_diagnostics<cr>", "workspace" },
            d = { "<cmd>TroubleToggle document_diagnostics<cr>", "document" },
            q = { "<cmd>TroubleToggle quickfix<cr>", "quickfix" },
            l = { "<cmd>TroubleToggle loclist<cr>", "loclist" },
            r = { "<cmd>TroubleToggle lsp_references<cr>", "references" },
        },
    },
}

function map(mode, lhs, rhs, opts)
        local options = { noremap = true, silent = true }
    if opts then
        options = vim.tbl_extend("force", options, opts)
    end
    vim.api.nvim_set_keymap(mode, lhs, rhs, options)
end

map("n", "H", ":bp<CR>")
map("n", "L", ":bn<CR>")

map("n", "<tab>", ":tabnext<CR>")
map("n", "<S-tab>", ":tabprevious<CR>")

map("n", "<C-h>", ":wincmd h<CR>")
map("n", "<C-j>", ":wincmd j<CR>")
map("n", "<C-k>", ":wincmd k<CR>")
map("n", "<C-l>", ":wincmd l<CR>")

local wk = require("which-key")
wk.setup(which_key.setup)

local opts = which_key.opts
local vopts = which_key.vopts

local mappings = which_key.mappings
local vmappings = which_key.vmappings

wk.register(mappings, opts)
wk.register(vmappings, vopts)

if which_key.on_config_done then
    which_key.on_config_done(wk)
end
Enter fullscreen mode Exit fullscreen mode

_状态线.lua

local lsp_status = require("lsp-status")

local function lsp_progress()
    return lsp_status.status()
end

local function extract_highlight_colors(color_group, scope)
    if vim.fn.hlexists(color_group) == 0 then
        return nil
    end
    local color = vim.api.nvim_get_hl_by_name(color_group, true)
    if color.background ~= nil then
        color.bg = string.format("#%06x", color.background)
        color.background = nil
    end
    if color.foreground ~= nil then
        color.fg = string.format("#%06x", color.foreground)
        color.foreground = nil
    end
    if scope then
        return color[scope]
    end
    return color
end

local colors = {
    gray = 8,
    red = 9,
    green = 10,
    yellow = 11,
    blue = 12,
    magenta = 13,
    cyan = 14,
    white = 15,
    background = extract_highlight_colors("StatusLine", "bg"),
    foreground = 7,
}

local custom_theme = {
    normal = {
        a = { bg = colors.foreground, fg = colors.background },
        b = { bg = colors.background, fg = colors.foreground },
        c = { bg = colors.background, fg = colors.foreground },
    },
    insert = {
        a = { bg = colors.cyan, fg = colors.background },
        b = { bg = colors.background, fg = colors.cyan },
        c = { bg = colors.background, fg = colors.foreground },
    },
    visual = {
        a = { bg = colors.yellow, fg = colors.background },
        b = { bg = colors.background, fg = colors.yellow },
        c = { bg = colors.background, fg = colors.foreground },
    },
    replace = {
        a = { bg = colors.red, fg = colors.background },
        b = { bg = colors.background, fg = colors.red },
        c = { bg = colors.background, fg = colors.foreground },
    },
    command = {
        a = { bg = colors.magenta, fg = colors.background },
        b = { bg = colors.background, fg = colors.magenta },
        c = { bg = colors.background, fg = colors.foreground },
    },
    inactive = {
        a = { bg = colors.background, fg = colors.gray },
        b = { bg = colors.background, fg = colors.gray },
        c = { bg = colors.background, fg = colors.gray },
    },
}

local components = {
    mode = {
        function()
            return " "
        end,
        padding = { left = 0, right = 0 },
    },
    filename = {
        "filename",
    },
    diagnostics = {
        "diagnostics",
        sources = { "nvim_diagnostic" },
        symbols = { error = " ", warn = " ", info = " ", hint = " " },
    },
    treesitter = {
        function()
            local b = vim.api.nvim_get_current_buf()
            if next(vim.treesitter.highlighter.active[b]) then
                return ""
            end
            return ""
        end,
    },
    location = { "location" },
    progress = { "progress" },
    encoding = {
        "o:encoding",
        fmt = string.upper,
    },
    filetype = { "filetype" },
}

require("lualine").setup({
    options = {
        theme = custom_theme,
        icons_enabled = false,
        component_separators = { left = "", right = "" },
        section_separators = { left = "", right = "" },
        disabled_filetypes = { "dashboard", "NvimTree", "Outline" },
    },
    sections = {
        lualine_a = {},
        lualine_b = {
            components.filename,
        },
        lualine_c = {
            components.diff,
        },
        lualine_x = {
            lsp_progress,
            components.diagnostics,
        },
        lualine_y = {
            components.treesitter,
        },
        lualine_z = {},
    },
    inactive_sections = {
        lualine_a = {},
        lualine_b = {
            "filename",
        },
        lualine_c = {},
        lualine_x = {},
        lualine_y = {},
        lualine_z = {},
    },
    tabline = {
        lualine_a = {},
        lualine_b = { { "buffers" } },
        lualine_c = {},
        lualine_x = {},
        lualine_y = { { "tabs", mode = 0 } },
        lualine_z = {},
    },
    extensions = { "nvim-tree" },
})
Enter fullscreen mode Exit fullscreen mode

结论

我知道配置太多了!对此我深感抱歉。但我认为有了这么多配置,任何人都可以自定义所有内容。我做的设置仅供参考,我对 Vim 应该如何设计有自己的看法。希望这只是一个基础,大家可以以此为基础,开始使用,然后根据自己的喜好进行修改。我还留下了一些注释掉的设置,以便进一步修改。

如果你已经做到这里,下一步就是尝试一下。看看它是如何工作的。

main.py 测试

创建一个文件nvim main.py。首次加载时nvim会报错,因为没有安装任何插件。继续输入q直到报错结束。我们需要重新启动已经安装好的插件。nvim然后现在我们有了打包程序,我们可以安装和更新定义在的所有插件。再关闭一次。现在我们完成了这些,然后开始测试我们的设置。(注意正在安装一些东西)让它在后台发生。等待完成后再退出。将此代码片段放入文件中。packerqanvim main.py:PackerSync_plugins.luanvim:qanvim main.pytree-sittertree-sitter

#!/usr/bin/env python

def echo(msg):
    print msg

if __name__ == "__main__":
    echo("Hello World!")
Enter fullscreen mode Exit fullscreen mode

节省:w

安装 pyright 服务器

现在我们需要安装 lsp 服务器来处理 Python 文件。这可以通过几种不同的方式完成,最快的是:LspInstall pyright,或者也可以使用 访问“仪表板” :LspInstallInfo,然后使用帮助?查看选项。u将升级当前光标下的服务器,并将i安装当前光标下的服务器。

现在pyright已安装完毕,让我们确保它能正常工作。

测试版权

注意,第 4 行msg带有下划线,并且E在 gitgutter 中显示为 。将光标移动到第 4 行并触发 行诊断gl。如果看到弹出窗口,则说明配置正确!

黑色格式化程序

我没有详细介绍安装的语言服务器的自定义设置,以及格式化和 linting 的设置。这里
使用 wblack作为 Python 格式化程序。我们在这里使用null-ls进行格式化。

after/ftplugin/python.lua

 local null_ls = require("null-ls")

local sources = {
    null_ls.builtins.formatting.black,
}

null_ls.setup({ sources = sources })
Enter fullscreen mode Exit fullscreen mode

注意, null_ls 可以像我们创建的其他 Lua 文件一样,以常规方式设置所有设置。使用 after dir / 延迟加载只是其中一种方法。

确保已安装 black pip install black。让我们用我们的文件试试看main.py。将其更新为以下内容,然后保存:w

#!/usr/bin/env python
def echo(msg):
    print(msg)
if __name__ == "__main__":
    echo("Hello World!")
Enter fullscreen mode Exit fullscreen mode
  • 注意leaderspace

现在告诉nvim使用定义的格式化程序发送格式化leader lf

结果应该是一个格式良好的python文件。

python 格式

这就是全部内容了。

文章来源:https://dev.to/casonadams/neovim-lsp-to-replace-vscode-n8c
PREV
Reactjs 中的刷新令牌实现
NEXT
20 个(高级开发人员)C# 面试问题及答案(2023)