我第一次学习 Vim 是在大学,从那时起,它一直是我软件工程职业生涯的主要伙伴。 使用 Vim 使用 Python 和 Go 程序感觉很自然,而且我总是感觉很有效率。 然而,Java 始终是一个难以驯服的野兽。 每当有机会使用 Java 时,我都会不可避免地尝试 Vim 一段时间,但会回过头来使用 IntelliJ 和 IdeaVim 插件,以利用功能齐全的 IDE 提供的丰富语言功能。
不幸的是,IntelliJ 有它自己的问题。 在随机的,有时是不合时宜的时候,它会停止运行,直到重建所有缓存,重新加载项目,并完成半天或更长时间的故障排除以使 IDE 正常工作。 直到几个月前,看到了 Vim、Neovim 和语言服务器协议规范和实现的进步,我想也许是时候重新审视一下使用 Neovim 作为 Java IDE 了。
是否可以? 是的。 我会推荐它吗? 或许。 我疯了吗? 大概。
我们开始吧。
概览
将 Neovim 从一个简单的文本编辑器推向一个全功能的 IDE 需要一些部件,所以最好花一分钟时间先了解所有涉及的部件以及它们如何相互交互,然后再深入了解一些神秘的配置 选项。
我从 IDE 中寻找的功能是代码导航(转到定义、查找参考、转到实现)、代码完成、签名提示、重构和调试。 语言服务器协议的现代实现涵盖了大多数这些情况,而一个更新的配套项目——调试适配器协议——处理调试。
语言服务器、调试器和调试适配器一起与您的代码交互,如下图所示。
基于 Vim 的 IDE 的组件
这个图的好处是它不是特定于 Java 的。 一旦您了解了如何让一种语言工作,您就可以对任何实现语言服务器协议和调试适配器协议的语言重复这个过程。 对于 Java,我们使用 Eclipse JDT LS 作为语言服务器实现,使用 vscode-java-debug 作为调试适配器(利用 java-debug)。
开始
Neovim 将 Lua 5.1 脚本引擎和 LuaJIT 编译器嵌入到编辑器中。 这意味着随时可以使用功能齐全且高性能的语言。 它还显著减少了对替代语言支持的需求。 我想简化 Neovim 的足迹,所以我做的第一件事就是禁用对我不使用的语言提供程序的支持。
--禁用语言提供程序支持(仅限 lua 和 vimscript 插件)
vim.g.loaded_perl_provider = 0
vim.g.loaded_ruby_provider = 0
vim.g.loaded_node_provider = 0
vim.g.loaded_python_provider = 0
vim.g.loaded_python3_provider = 0
这一变化的实际效果意味着我选择的所有插件都是原生的 vimscript 和 Lua。 到目前为止,我还没有发现这种变化的局限,但时间会证明一切。 将 Lua 集成到 Neovim 中导致可供选择的插件在质量和数量上都出现爆炸式增长。
有许多替代的 Neovim 插件,它们的工作方式略有不同,您可能更喜欢我的设置。 Awesome Neovim 项目收集了许多最好和最成熟的插件。
最后,我选择使用 neovim 内置的 LSP 客户端,这减少了所需的依赖项数量。 如果您优先考虑易用性而不是简单性,您可能更喜欢 coc.nvim。
插件管理器
将 Neovim 变成功能齐全的 IDE 需要使用插件对其进行扩展。 我选择 packer.nvim 作为我的纯 Lua 插件管理器。 要开始,您需要将 packer 克隆到您的包路径,这是您的 Neovim 安装找到包的目录。 完成此步骤后,packer.nvim 将自行管理,从此时起您无需担心 packpath。 macOS 上的默认配置是这样的:
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
然后你可以在 Lua 中编写你的插件规范。 例如,使用有效的插件规范编辑文件 ~/.config/nvim/lua/plugins.lua,然后在 init.lua 文件中使用 require('plugins') 加载该规范。
例如,这是我的 plugins.lua 文件的内容:
return require('packer').startup(function(use)
-- Packer 能自行管理
use 'wbthomason/packer.nvim'
use 'mfussenegger/nvim-dap'
use 'mfussenegger/nvim-jdtls'
use 'nvim-lua/plenary.nvim'
end)
Packer 非常复杂,允许您指定依赖项和设置插件作为插件规范的一部分,但我发现单独设置更复杂的插件并让 packer 简单地处理安装更简单。 像往常一样,使用您自己的判断并根据需要调整。
一旦你有一个有效的packer配置,在 ~/.config/nvim/init.lua 你可以使用 require('plugins') 导入规范。 从那里执行 :PackerInstall 命令来安装您在规范中列出的任何插件。
如果您正在通过本指南将 Noevim 设置为 Java IDE,最简单的方法是一次添加一个插件,了解如何配置它、如何使用它以及它提供的功能,然后添加更多插件 . 通过这种方式,您可以更好地了解您对 Neovim 环境所做的更改,而不会不知所措。
语言服务 — eclipse.jdt.ls
Neovim IDE 体验的核心是由语言服务器协议提供的。 要启用对语言的类似 IDE 的支持,需要运行语言服务器。 对于 Java,事实上的标准是 eclipse.jdt.ls — Eclipse JDT 语言服务器。
您可以在 macOS 上使用 Homebrew 安装它,确保记下安装位置(特别是版本号):
$ > brew install jdtls
...
==> Pouring jdtls--1.18.0.all.bottle.tar.gz
/opt/homebrew/Cellar/jdtls/1.18.0: 99 files, 42.8MB
在我的机器上,安装位置是 /opt/homebrew/Cellar/jdtls/1.18.0,稍后我们将需要它来设置 LSP 客户端。
语言服务客户端- Neovim 和 nvim-jdtls
Neovim 开箱即用地支持语言服务器协议 (LSP),充当 LSP 服务器的客户端,并包含一个名为 vim.lsp 的 Lua 框架,用于构建增强的 LSP 工具。 开始使用内置客户端的一般建议是使用 nvim-lspconfig,它为许多不同的语言提供默认配置。
某些语言具有支持更丰富的 LSP 功能的插件。 Java 就是其中之一。 nvim-jdtls 为内置的 LSP 客户端提供扩展,例如组织导入、提取变量和代码生成。 nvim-lspconfig 和 nvim-jdtls 都使用 Neovim 内置的客户端,主要区别在于 nvim-jdtls 添加了一些额外的处理程序和功能,并简化了配置。 使用 nvim-jdtls 的优点之一是,一旦启动并运行,您可以使用您可能已经用于其他语言的相同 Neovim 键绑定和客户端功能,而无需学习特定于插件的交互方式。
下图来自 nvim-jdtls 文档,显示了它与 nvim-lspconfig 的不同之处。 两者都使用 Neovim 内置的 Lua 绑定,但设置和配置略有不同。
┌────────────┐ ┌────────────────┐
│ nvim-jdtls │ │ nvim-lspconfig │
└────────────┘ └────────────────┘
| |
start_or_attach nvim_lsp.jdtls.setup
│ |
│ setup java filetype hook
│ ┌─────────┐ │
└───►│ vim.lsp │◄─────────────────┘
└─────────┘
配置 nvim-jdtls 可能会令人生畏。 以下示例配置被注释以显示我如何在我的开发机器上设置 nvim-jdtls。 大多数选项直接来自 Eclipse JDTLS 文档并且特定于 jdtls。
local home = os.getenv('HOME')
local jdtls = require('jdtls')
-- 表示 Java 项目根目录的文件类型。 eclipse 将使用它来确定什么构成工作区
local root_markers = {'gradlew', 'mvnw', '.git'}
local root_dir = require('jdtls.setup').find_root(root_markers)
--eclipse.jdt.ls 将项目特定数据存储在一个文件夹中。 如果您正在处理多个不同的项目,每个项目都必须使用专用的数据目录。
-- 此变量用于配置 eclipse,以使用使用 root_marker 找到的当前项目的目录名称作为项目特定数据的文件夹。
local workspace_folder = home .. "/.local/share/eclipse/" .. vim.fn.fnamemodify(root_dir, ":p:h:t")
-- 用于创建键盘映射的辅助函数
function nnoremap(rhs, lhs, bufopts, desc)
bufopts.desc = desc
vim.keymap.set("n", rhs, lhs, bufopts)
end
-- on_attach 函数用于在语言服务器附加到当前缓冲区后设置键映射
local on_attach = function(client, bufnr)
-- Regular Neovim LSP client keymappings
local bufopts = { noremap=true, silent=true, buffer=bufnr }
nnoremap('gD', vim.lsp.buf.declaration, bufopts, "Go to declaration")
nnoremap('gd', vim.lsp.buf.definition, bufopts, "Go to definition")
nnoremap('gi', vim.lsp.buf.implementation, bufopts, "Go to implementation")
nnoremap('K', vim.lsp.buf.hover, bufopts, "Hover text")
nnoremap('<C-k>', vim.lsp.buf.signature_help, bufopts, "Show signature")
nnoremap('<space>wa', vim.lsp.buf.add_workspace_folder, bufopts, "Add workspace folder")
nnoremap('<space>wr', vim.lsp.buf.remove_workspace_folder, bufopts, "Remove workspace folder")
nnoremap('<space>wl', function()
print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
end, bufopts, "List workspace folders")
nnoremap('<space>D', vim.lsp.buf.type_definition, bufopts, "Go to type definition")
nnoremap('<space>rn', vim.lsp.buf.rename, bufopts, "Rename")
nnoremap('<space>ca', vim.lsp.buf.code_action, bufopts, "Code actions")
vim.keymap.set('v', "<space>ca", "<ESC><CMD>lua vim.lsp.buf.range_code_action()<CR>",
{ noremap=true, silent=true, buffer=bufnr, desc = "Code actions" })
nnoremap('<space>f', function() vim.lsp.buf.format { async = true } end, bufopts, "Format file")
-- jdtls 提供的 Java 扩展
nnoremap("<C-o>", jdtls.organize_imports, bufopts, "Organize imports")
nnoremap("<space>ev", jdtls.extract_variable, bufopts, "Extract variable")
nnoremap("<space>ec", jdtls.extract_constant, bufopts, "Extract constant")
vim.keymap.set('v', "<space>em", [[<ESC><CMD>lua require('jdtls').extract_method(true)<CR>]],
{ noremap=true, silent=true, buffer=bufnr, desc = "Extract method" })
end
local config = {
flags = {
debounce_text_changes = 80,
},
on_attach = on_attach, -- We pass our on_attach keybindings to the configuration map
root_dir = root_dir, -- Set the root directory to our found root_marker
-- 这里可以配置eclipse.jdt.ls具体设置
-- 这些由 eclipse.jdt.ls 项目定义,并在启动时传递给 eclipse。
settings = {
java = {
format = {
settings = {
-- 使用 Google Java 样式指南进行格式化
-- 要使用,请确保从 https://github.com/google/styleguide/blob/gh-pages/eclipse-java-google-style.xml 下载文件并将其放在 ~/.local/share/eclipse 目录
url = "/.local/share/eclipse/eclipse-java-google-style.xml",
profile = "GoogleStyle",
},
},
signatureHelp = { enabled = true },
contentProvider = { preferred = 'fernflower' }, -- 使用fernflower反编译库代码
-- Specify any completion options
completion = {
favoriteStaticMembers = {
"org.hamcrest.MatcherAssert.assertThat",
"org.hamcrest.Matchers.*",
"org.hamcrest.CoreMatchers.*",
"org.junit.jupiter.api.Assertions.*",
"java.util.Objects.requireNonNull",
"java.util.Objects.requireNonNullElse",
"org.mockito.Mockito.*"
},
filteredTypes = {
"com.sun.*",
"io.micrometer.shaded.*",
"java.awt.*",
"jdk.*", "sun.*",
},
},
-- 指定用于组织导入选项
sources = {
organizeImports = {
starThreshold = 9999;
staticStarThreshold = 9999;
},
},
-- 代码生成应该如何操作
codeGeneration = {
toString = {
template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
},
hashCodeEquals = {
useJava7Objects = true,
},
useBlocks = true,
},
--如果您在具有不同 Java 版本的项目中进行开发,则需要告诉 eclipse.jdt.ls 使用您的 Java 版本的 JDK 的位置
-- `name` 不是任意的,必须与上面链接中的 `enum ExecutionEnvironment` 中的元素之一匹配
configuration = {
runtimes = {
{
name = "JavaSE-17",
path = home .. "/.asdf/installs/java/corretto-17.0.4.9.1",
},
{
name = "JavaSE-11",
path = home .. "/.asdf/installs/java/corretto-11.0.16.9.1",
},
{
name = "JavaSE-1.8",
path = home .. "/.asdf/installs/java/corretto-8.352.08.1"
},
}
}
}
},
-- cmd 是启动语言服务器的命令。 放在这里的就是传递给命令行执行jdtls的东西。
-- 注意eclipse.jdt.ls必须用17以上的Java版本启动
cmd = {
home .. "/.asdf/installs/java/corretto-17.0.4.9.1/bin/java",
'-Declipse.application=org.eclipse.jdt.ls.core.id1',
'-Dosgi.bundles.defaultStartLevel=4',
'-Declipse.product=org.eclipse.jdt.ls.core.product',
'-Dlog.protocol=true',
'-Dlog.level=ALL',
'-Xmx4g',
'--add-modules=ALL-SYSTEM',
'--add-opens', 'java.base/java.util=ALL-UNNAMED',
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
--如果您使用 lombok,请下载 lombok jar 并将其放在 ~/.local/share/eclipse 中
'-javaagent:' .. home .. '/.local/share/eclipse/lombok.jar',
-- jar 文件位于安装 jdtls 的位置。 这将需要更新到您安装 jdtls 的位置
'-jar', vim.fn.glob('/opt/homebrew/Cellar/jdtls/1.18.0/libexec/plugins/org.eclipse.equinox.launcher_*.jar'),
-- jdtls 的配置也放在安装 jdtls 的地方。 这将需要根据您的环境进行更新
'-configuration', '/opt/homebrew/Cellar/jdtls/1.18.0/libexec/config_mac',
-- 使用上面定义的 workspace_folder 来存储这个项目的数据
'-data', workspace_folder,
},
}
-- 最后,启动jdtls。 这将使用我们指定的配置运行语言服务器,设置键映射,并将 LSP 客户端附加到当前缓冲区
jdtls.start_or_attach(config)
要使用此配置启动 jdtls,请将上面的文件放在文件夹 .config\nvim\ftplugin\java.lua 中。 每当将 Java 类型的文件加载到当前缓冲区中时,Neovim 将自动执行此代码。 (ftplugin 是文件类型插件的简写)。
虽然配置看起来很多,但可以分解成几个部分。 首先,我们为 LSP 客户端创建所需的键映射。 然后我们指定要传递给 eclipse.jdt.ls 的选项,最后,我们设置用于启动 eclipse.jdt.ls 的命令。 一旦我们获得该配置,我们将其作为参数传递给 jdtls.start_or_attach,它将启动语言服务器或附加到现有的运行实例(如果服务器已经启动)。
假设您能够启动并运行 jdtls,以下截屏视频显示了如何使用 jdtls 提取方法。 可用的代码操作是使用 telescope.nvim 呈现的。
调试— nvim-dap
调试适配器协议 (DAP) 是语言服务器协议的配套项目。 调试适配器协议 (DAP) 背后的想法是抽象出开发工具的调试支持如何与调试器或运行时通信。 因为许多语言已经存在调试器,所以 DAP 与适配器一起工作以将现有调试器或运行时与调试适配器协议相匹配,而不是假设需要编写新的调试器来匹配协议。
nvim-dap 是一个 DAP 客户端实现。 与调试适配器一起工作,nvim-dap 可以启动应用程序进行调试、附加到正在运行的应用程序、设置断点、逐步执行代码以及检查应用程序的状态。
nvim-dap 需要一个调试适配器作为 nvim-dap(客户端)和特定语言调试器之间的促进者。 下图来自 nvim-dap 文档,显示了这些部分如何交互。
DAP-Client ----- Debug Adapter ------- Debugger ------ Debugee
(nvim-dap) | (per language) | (per language) (your app)
| |
| Implementation specific communication
| Debug adapter and debugger could be the same process
|
Communication via the Debug Adapter Protocol
与 LSP 协议一样,DAP 协议需要我们安装额外的组件。 不幸的是,也许由于 DAP 协议相对不成熟,该过程比 LSP 服务器涉及更多。
Java 调试服务器是 Github 上可用的调试适配器协议的实现。 该实现基于 Java 调试接口 (JDI)。 它作为插件与 Eclipse JDT 语言服务器一起工作,通过将调试服务器包装在与 jdtls 一起工作的 Eclipse 插件中来提供调试功能。 要将 java-debug 注册为 Eclipse 插件,我们需要将 jar 文件的位置作为初始化选项传递给 Eclipse。 这需要首先编译插件,然后配置 Eclipse 以使用插件。
编译插件是通过 Maven 完成的:
- 克隆 java-debug
- 导航到克隆的存储库(cd java-debug)
- 运行 ./mvnw 全新安装
完成后,您可以将 jar 文件的位置作为 Eclipse 的配置选项传递。 您的 jdtls 配置需要扩展如下:
local bundles = {
vim.fn.glob('<path-to-java-debug>/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-*.jar'),
}
local config = {
...
on_attach = on_attach,
init_options = {
bundles = bundles
},
...
}
然后,您需要通知 nvim-jdtls 调试适配器可供使用。 在你的 on_attach 函数中,添加 require('jdtls').setup_dap() 让它注册一个 java 适配器。
config['on_attach'] = function(client, bufnr)
-- 使用 `hotcodereplace = 'auto' 调试适配器将尝试立即应用您在调试会话期间所做的代码更改。
-- 如果不需要,请删除该选项。
require('jdtls').setup_dap({ hotcodereplace = 'auto' })
end
nvim-dap 支持用于在 Visual Studio Code 中配置调试适配器的 launch.json 文件格式的子集。 要加载 launch.json 文件,请使用 dap.ext.vscode 模块中的 load_launchjs 函数。 以下代码将加载当前项目中可用的启动配置:
require('dap.ext.vscode').load_launchjs()
最后,您需要配置调试键映射。 这些是我使用的,您可能需要编辑它们以满足您的需要。
function nnoremap(rhs, lhs, bufopts, desc)
bufopts.desc = desc
vim.keymap.set("n", rhs, lhs, bufopts)
end
-- nvim-dap
nnoremap("<leader>bb", "<cmd>lua require'dap'.toggle_breakpoint()<cr>", "Set breakpoint")
nnoremap("<leader>bc", "<cmd>lua require'dap'.set_breakpoint(vim.fn.input('Breakpoint condition: '))<cr>", "Set conditional breakpoint")
nnoremap("<leader>bl", "<cmd>lua require'dap'.set_breakpoint(nil, nil, vim.fn.input('Log point message: '))<cr>", "Set log point")
nnoremap('<leader>br', "<cmd>lua require'dap'.clear_breakpoints()<cr>", "Clear breakpoints")
nnoremap('<leader>ba', '<cmd>Telescope dap list_breakpoints<cr>', "List breakpoints")
nnoremap("<leader>dc", "<cmd>lua require'dap'.continue()<cr>", "Continue")
nnoremap("<leader>dj", "<cmd>lua require'dap'.step_over()<cr>", "Step over")
nnoremap("<leader>dk", "<cmd>lua require'dap'.step_into()<cr>", "Step into")
nnoremap("<leader>do", "<cmd>lua require'dap'.step_out()<cr>", "Step out")
nnoremap('<leader>dd', "<cmd>lua require'dap'.disconnect()<cr>", "Disconnect")
nnoremap('<leader>dt', "<cmd>lua require'dap'.terminate()<cr>", "Terminate")
nnoremap("<leader>dr", "<cmd>lua require'dap'.repl.toggle()<cr>", "Open REPL")
nnoremap("<leader>dl", "<cmd>lua require'dap'.run_last()<cr>", "Run last")
nnoremap('<leader>di', function() require"dap.ui.widgets".hover() end, "Variables")
nnoremap('<leader>d?', function() local widgets=require"dap.ui.widgets";widgets.centered_float(widgets.scopes) end, "Scopes")
nnoremap('<leader>df', '<cmd>Telescope dap frames<cr>', "List frames")
nnoremap('<leader>dh', '<cmd>Telescope dap commands<cr>', "List commands")
遗憾的是,java-debug 项目不支持调试测试,我们需要为此设置另一个插件。 值得庆幸的是,它遵循类似的过程。 为了能够调试测试,有必要使用我们用于 java-debug 的相同过程从 vscode-java-test 安装包:
首先从项目构建 jar 文件。
- 克隆存储库
- 导航到文件夹(cd vscode-java-test)
- 运行 npm 安装
- 运行 npm run build-plugin
然后,扩展 nvim-jdtls 配置中的包以包含来自 vs-code-java-test 的包:
-- 这个 bundle 定义和上一节(java-debug 安装)一样
local bundles = {
vim.fn.glob("<path-to-java-debug>/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-*.jar", 1),
};
-- This is the new part
vim.list_extend(bundles, vim.split(vim.fn.glob("<path-to-vscode-java-test>/server/*.jar", 1), "\n"))
local config = {
...
on_attach = on_attach,
init_options = {
bundles = bundles
},
...
}
这公开了我使用以下键映射配置的 nvim-jdtls 可用的两个新功能。
nnoremap("<leader>vc", jdtls.test_class, bufopts, "Test class (DAP)")
nnoremap("<leader>vm", jdtls.test_nearest_method, bufopts, "Test method (DAP)")
以下截屏视频显示了使用 nvim-dap 运行和调试测试。 命中断点后,我打开范围视图以检查当前堆栈帧的状态。
代码补全——nvim-cmp
创建全功能 IDE 体验所需的下一个功能是代码完成。 为此,我求助于 Neovim 的通用补全插件 nvim-cmp。 nvim-cmp 作为一个核心插件使用补全源进行扩展。 源可以是代码片段、LSP 符号或来自当前缓冲区的单词。
要开始使用 nvim-cmp,首先安装 nvim-cmp 插件以及您需要的任何补全源。 在这里,我安装了 nvim-cmp 以及我使用的 lsp 和代码片段源。
return require('packer').startup(function(use)
...
use 'hrsh7th/nvim-cmp'
use 'hrsh7th/cmp-nvim-lsp'
use 'hrsh7th/cmp-vsnip'
use 'hrsh7th/vim-vsnip'
...
end)
语言服务器根据客户端的能力提供不同的完成结果。 nvim-cmp 比 Neovim 默认的 omnifunc 支持更多类型的完成候选,因此我们必须通告发送到服务器的可用功能,以便它可以在完成请求期间提供这些候选。 这些功能是通过辅助函数 require('cmp_nvim_lsp').default_capabilities 提供的,可以将其添加到我们的 jdtls 配置中。
-- nvim-cmp 支持额外的 LSP 功能,因此我们需要将其通告给 LSP 服务器。
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = require('cmp_nvim_lsp').default_capabilities(capabilities)
local config = {
...
capabilities = capabilities,
on_attach = on_attach,
...
}
然后,我们需要配置 nvim-cmp 本身。 以下代码片段命名了我们要使用的补全源、我们正在使用的代码片段插件,并配置了 Tab 键以循环显示补全选项和 Enter 键以选择补全。
local cmp = require('cmp')
cmp.setup {
sources = {
{ name = 'nvim_lsp' },
{ name = 'nvim_lsp_signature_help' },
{ name = 'vsnip' },
},
snippet = {
expand = function(args)
vim.fn["vsnip#anonymous"](args.body) --因为我们使用的是 vsnip cmp 插件
end,
},
mapping = cmp.mapping.preset.insert({
['<C-d>'] = cmp.mapping.scroll_docs(-4),
['<C-f>'] = cmp.mapping.scroll_docs(4),
['<C-Space>'] = cmp.mapping.complete(),
['<CR>'] = cmp.mapping.confirm {
behavior = cmp.ConfirmBehavior.Replace,
select = true,
},
['<Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
else
fallback()
end
end, { 'i', 's' }),
['<S-Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item()
else
fallback()
end
end, { 'i', 's' }),
}),
}
而且,如果您想在完成结果旁边显示符号,请安装 onsails/lspkind.nvim 插件,并通过在我们的 cmp 配置中添加格式化块来配置它。
local lspkind = require('lspkind')
cmp.setup {
...
formatting = {
format = lspkind.cmp_format({
mode = 'symbol_text',
maxwidth = 50,
ellipsis_char = '...',
before = function (_, vim_item)
return vim_item
end
})
}
...
}
在下面的截屏视频中,我展示了 nvim-cmp 如何显示 LSP 协议给出的可用补全集。 每个完成类型旁边的图标来自 lspkind。
查找— telescope-nvim
telescope.nvim 是一个高度可扩展的列表模糊查找器。 telescope 提供用于从列表中过滤和选择项目的界面和功能。 像 nvim-cmp 一样,telescope 可以通过添加 telescope 将显示和过滤的额外列表源来扩展。
我的望远镜配置使用 fzf 来提高性能,这需要使用以下配置安装 telescope-fzf-native:
use {'nvim-telescope/telescope-fzf-native.nvim', run = 'make' }
使用 Java 项目时,默认项目结构会导致目录名称过长。 要截断目录名称并使用 fzf 提高性能,我使用以下配置:
require('telescope').setup({
defaults = {
path_display = {
shorten = {
len = 3, exclude = {1, -1}
},
truncate = true
},
dynamic_preview_title = true,
},
extensions = {
fzf = {
fuzzy = true, -- false will only do exact matching
override_generic_sorter = true, -- override the generic sorter
override_file_sorter = true, -- override the file sorter
case_mode = "smart_case", -- or "ignore_case" or "respect_case"
-- the default case_mode is "smart_case"
}
}
})
require('telescope').load_extension('fzf')
我大量使用了telescope,并使用一组以 f 为前缀的键映射来查找。
-- telescope
nnoremap("<leader>ff", "<cmd>Telescope find_files<cr>", "Find file")
nnoremap("<leader>fg", "<cmd>Telescope live_grep<cr>", "Grep")
nnoremap("<leader>fb", "<cmd>Telescope buffers<cr>", "Find buffer")
nnoremap("<leader>fm", "<cmd>Telescope marks<cr>", "Find mark")
nnoremap("<leader>fr", "<cmd>Telescope lsp_references<cr>", "Find references (LSP)")
nnoremap("<leader>fs", "<cmd>Telescope lsp_document_symbols<cr>", "Find symbols (LSP)")
nnoremap("<leader>fc", "<cmd>Telescope lsp_incoming_calls<cr>", "Find incoming calls (LSP)")
nnoremap("<leader>fo", "<cmd>Telescope lsp_outgoing_calls<cr>", "Find outgoing calls (LSP)")
nnoremap("<leader>fi", "<cmd>Telescope lsp_implementations<cr>", "Find implementations (LSP)")
nnoremap("<leader>fx", "<cmd>Telescope diagnostics bufnr=0<cr>", "Find errors (LSP)")
在此截屏视频中,我展示了如何将telescope用作文件浏览器来快速查找和打开文件。
文件结构 — symbols-outline
我利用我们的文件结构的另一个 IDE 功能。 此功能提供当前文件中符号的分层树状视图以及它们之间的关系。 为此,我求助于一个相对简单的插件,称为 symbols-outline。 默认选项适用于我的用例,有一个小的补充:当我做出选择时自动关闭大纲。 我使用以下配置来自动关闭大纲:
require("symbols-outline").setup {
auto_close = true,
}
以下键映射还可以使用 CTRL-SHIFT-右箭头和 CTRL-SHIFT-左箭头轻松调整轮廓大小。
-- 窗口管理
nnoremap("<C-S-Right>", "<cmd>:vertical resize -1<cr>", "Minimize window")
nnoremap("<C-S-Left>", "<cmd>:vertical resize +1<cr>", "Maximize window")
在这个截屏视频中,我打开一个文件,然后使用大纲插件浏览顶级符号,然后选择一个跳转到。
文件浏览— nvim-tree
nvim-tree 插件是一个用 Lua 编写的文件浏览器。 安装后,我使用以下键映射打开和关闭文件浏览器:
-- nvim-tree
nnoremap("<leader>nn", "<cmd>NvimTreeToggle<cr>", "Open file browser")
nnoremap("<leader>nf", "<cmd>NvimTreeFindFile<cr>", "Find in file browser")
我还禁用了 netrw,因为我不使用它,它可能与 nvim-tree 冲突。 我还将文件浏览器窗口配置为在我进行选择时自动关闭,并自动将其调整为正确的宽度。
require("nvim-tree").setup({
disable_netrw = true,
view = {
adaptive_size = true,
float = {
enable = true,
},
},
actions = {
open_file = {
quit_on_open = true,
}
}
})
下面的截屏视频显示了 nvim-tree 被用作文件浏览器。
状态栏- lualine
lualine 是一个用 Lua 编写的状态行插件。 statusline 插件显示有关当前文件的有用信息,例如文件类型、git 分支和编码。 我对 lualine 所做的唯一更改是将主题设置为与我的终端颜色主题相匹配:
require('lualine').setup {
options = { theme = 'onedark' },
}
最终结果
语言服务器协议为开发全功能 IDE 提供了极好的支柱。 在 Neovim 中添加 LSP 客户端,以及有助于用户界面的插件,完成了我的目标。 在花了一些时间进行我在这篇文章中描述的配置和设置之后,我已经能够将 Neovim 变成我用作日常工作环境的 Java IDE。