您当前的位置: 首页 >  Java

ITKEY_

暂无认证

  • 0浏览

    0关注

    732博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

【视频】零基础neovim搭建Java IDE

ITKEY_ 发布时间:2022-03-13 10:08:15 ,浏览量:0

文章目录
  • 视频教程
  • 系统环境
  • 准备工作
    • 文件下载
    • 安装neovim 0.6以上版本
  • 下载解压jdt-language-server
    • 安装JDK11
    • 安装curl git
  • neovim配置
    • 插件安装
    • 配置nvim-cmp
    • 配置LuaSnip
    • 配置nvim-jdtls
      • 核心配置
      • Lombok支持
      • 我的完整配置分享
  • 运行代码
  • 配置文件版本管理

视频教程

《【新手向】从neovim安装>>>变身Java IDE》https://www.bilibili.com/video/BV1tT4y1D7yi

系统环境

理论上这套环境是支持跨平台的,macOS,Linux,Windows都支持。为了防止一些微小的差异,这里我把我的环境信息说明一下。 OS: Ubuntu 20.04 LTS x86_64 CPU: Intel Xeon E5-2680 v4 (2) @ 2.394GHz 在这里插入图片描述

这是我云服务器上的环境,没有什么特别的。

准备工作 文件下载

为了方便大家,我把本文中用于的所有文件。都上传到网盘供大家使用。

链接: https://pan.baidu.com/s/1ngwkOpclgLCKUW_YFX-WOg?pwd=g8fu 提取码: g8fu

文件说明:

  • nvim-linux64.tar.gz 是neovim0.61的安装包
  • jdk-11.0.13_linux-x64_bin.tar.gz 是JDK的压缩包
  • jdt-language-server-1.9.0-202203031534.tar.gz用于智能提示的插件
安装neovim 0.6以上版本

采用任意一种方法都可以,只有一个要求neovim版本要0.6以上。 通用方法: 直接从github官网下载 https://github.com/neovim/neovim/releases

考虑到github比较慢,所以可以使用CSDN的镜像进行下载。 https://gitcode.net/mirrors/neovim/neovim

下载后安装示例:

##解压
tar -xvf nvim-linux64.tar.gz
mv nvim-linux64 /usr/local/
##创建软链接
ln -s /usr/local/nvim-linux64/bin/nvim /bin/nvim
下载解压jdt-language-server

下载jdt-language-server 不同版本下载导航 https://download.eclipse.org/jdtls/milestones/?d 我最终下载的版本是:

https://download.eclipse.org/jdtls/milestones/1.9.0/jdt-language-server-1.9.0-202203031534.tar.gz

以下我的路径是个人喜好,可以根据自己的实际情况修改保存路径:

#创建workspace目录,后面会用到
mkdir -p ~/.local/share/nvim/lsp/jdt-language-server/workspace/folder
cd ~/.local/share/nvim/lsp/jdt-language-server
# 下载jdt-language-server-xxxxx.tar.gz
wget https://download.eclipse.org/jdtls/milestones/1.9.0/jdt-language-server-1.9.0-202203031534.tar.gz
# 解压
tar -zxvf jdt-language-server-1.9.0-202203031534.tar.gz

我的目录结构如下图所示 在这里插入图片描述

安装JDK11

JDK版本选择,这里有一个小坑,就是JDK的版本要选择JDK11及以上版本才行。因为就目前来看,JDK8使用的概率还是非常高的。

如果你使用JDK8,使用java文件会报如下的错误: Client 1 quit with exit code 1 and signal 0

推荐使用JDK11,因为我实测JDK11是正常使用的,其他版本的JDK我没有一一测试。 我的版本信息如下:

java -version
java version "11.0.13" 2021-10-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.13+10-LTS-370)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.13+10-LTS-370, mixed mode)

环境变量设置参考:

export JAVA_HOME=/root/neovim-IDE-soft/jdk-11.0.13 							#JDK的主目录,建议使用JDK11,使用JDK8会报错
PATH=$PATH:$JAVA_HOME/bin
export JDTLS_HOME=$HOME/.local/share/nvim/lsp/jdt-language-server/ 			# 包含 plugin 和 configs 的目录,由jdt-language-server-xxx.tar.gz解压出的
export WORKSPACE=$HOME/.local/share/nvim/lsp/jdt-language-server/workspace/ # 不设置则默认是$HOME/workspace
安装curl git

curl git这两个软件,很多系统上默认安装了,我在这里提一下,因为在使用nvim安装插件时会用到,特别是git。

ubuntu中这样安装,其他系统安装方法自己百度。

apt-get install -y curl git

至此准备工作完成了✅

neovim配置 插件安装
#创建nvim用到的目录
mkdir -p ~/.config/nvim/lua
#创建插件管理器的配置文件
nvim ~/.config/nvim/lua/plugins.lua

~/.config/nvim/lua/plugins.lua文件内容如下:

---@diagnostic disable: undefined-global
--在没有安装packer的电脑上,自动安装packer插件
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
  fn.system(
    {"git", "clone", "--depth", "1", "https://gitcode.net/mirrors/wbthomason/packer.nvim", install_path}
  ) --csdn加速镜像
  vim.cmd "packadd packer.nvim"
end
-- Only required if you have packer configured as `opt`
--【国内加速】插件名称超长的说明:
--由于国内网络环境访问github及其不稳定,所以如果在gitcode.net上的镜像的(https://gitcode.net/mirrors/开头的),我们尽量使用。这样可以提高访问速度。
--gitcode.net没有镜像的部分(https://gitcode.net/lxyoucan开头的),是我手动clone到gitcode上的不定期更新。
--如果你访问github比较流畅,插件名称只保留后两段即如:neovim/nvim-lspconfig
vim.cmd [[packadd packer.nvim]]
return require("packer").startup(function()
      -- Packer可以管理自己的更新
      use "https://gitcode.net/mirrors/wbthomason/packer.nvim"      
      --Nvim LSP 客户端的快速入门配置
      use "https://gitcode.net/mirrors/neovim/nvim-lspconfig"
      --自动提示插件
      use {
        "https://gitcode.net/mirrors/hrsh7th/nvim-cmp",
        requires = {
          "https://gitcode.net/lxyoucan/cmp-nvim-lsp", --neovim 内置 LSP 客户端的 nvim-cmp 源
          "https://gitcode.net/lxyoucan/cmp-buffer", --从buffer中智能提示
          "https://gitcode.net/lxyoucan/cmp-path" --自动提示硬盘上的文件
        }
      }
      -- java语言支持
      use "https://gitcode.net/lxyoucan/nvim-jdtls.git"
      -- 代码段提示
      use {
        "https://gitcode.net/mirrors/L3MON4D3/LuaSnip",
        requires = {
          "https://gitcode.net/lxyoucan/cmp_luasnip", -- Snippets source for nvim-cmp
          "https://gitcode.net/lxyoucan/friendly-snippets" --代码段合集
        }
      }
      --主题安装
      use "https://gitcode.net/mirrors/sainnhe/gruvbox-material"
end)

配置主配置文件:

nvim ~/.config/nvim/init.lua

添加内容如下:

--插件管理器
require("plugins")
--主题设置
vim.cmd("colorscheme " .. "gruvbox-material")
------按键映射 start------
local opts = {noremap = true, silent = true}
local keymap = vim.api.nvim_set_keymap
--把空格键设置成
vim.g.mapleader = " "
--快速跳转行首与行尾  
keymap('n', 'L', '$', opts)
keymap('v', 'L', '$', opts)
keymap('n', 'H', '^', opts)
keymap('v', 'H', '^', opts)
--插入模式jk当Esc
keymap('i', 'jk', '', opts)
--保 存
keymap('n', '', ':w', opts)
keymap('i', '', ' :w', opts)
--全选
keymap('n', '', 'ggG', opts)
------按键映射 end  ------
-- 文件编码格式
vim.opt.fileencoding = "utf-8"
-- 显示行号
vim.opt.number=true
-- tab=4个空格
vim.opt.tabstop=4
vim.opt.shiftwidth=4


保存后,重新打开nvim。执行:PackerInstall,如下图所示: 在这里插入图片描述 插件安装成功,界面如下:

在这里插入图片描述 这样我们就完成了,Java开发所需要的核心插件的安装了。

-- Packer可以管理自己的更新
 	 use "wbthomason/packer.nvim"      
      --Nvim LSP 客户端的快速入门配置
      use "neovim/nvim-lspconfig"
      --自动提示插件
      use {
        "hrsh7th/nvim-cmp",
        requires = {
          "hrsh7th/cmp-nvim-lsp", --neovim 内置 LSP 客户端的 nvim-cmp 源
          "hrsh7th/cmp-buffer", --从buffer中智能提示
          "hrsh7th/cmp-path" --自动提示硬盘上的文件
        }
      }
	  -- 代码段提示
      use {
        "https://gitcode.net/mirrors/L3MON4D3/LuaSnip",
        requires = {
          "saadparwaiz1/cmp_luasnip", -- Snippets source for nvim-cmp
          "rafamadriz/friendly-snippets" --代码段合集
        }
      }

      -- java语言支持jdtls扩展插件,在lsp基础上扩展了一些实用的内容
      use "mfussenegger/nvim-jdtls"
      
配置nvim-cmp
#创建plugin配置目录
mkdir -p ~/.config/nvim/after/plugin
#编辑nvim-cmp配置文件
nvim  ~/.config/nvim/after/plugin/nvim-cmp.lua

~/.config/nvim/after/plugin/nvim-cmp.lua文件内容如下:

local status, nvim_lsp = pcall(require, "lspconfig")
if (not status) then
  return
end

-- Set completeopt to have a better completion experience
vim.o.completeopt = "menuone,noselect"

-- luasnip setup
local luasnip = require "luasnip"

-- nvim-cmp setup
local cmp = require "cmp"
cmp.setup {
  snippet = {
    expand = function(args)
      require("luasnip").lsp_expand(args.body)
    end
  },
  mapping = {
    [""] = cmp.mapping.select_prev_item(),
    [""] = cmp.mapping.select_next_item(),
    [""] = cmp.mapping.scroll_docs(-4),
    [""] = cmp.mapping.scroll_docs(4),
    [""] = cmp.mapping.complete(),
    [""] = cmp.mapping.close(),
    [""] = cmp.mapping.confirm {
      behavior = cmp.ConfirmBehavior.Replace,
      select = false
    },
    [""] = function(fallback)
      if cmp.visible() then
        cmp.select_next_item()
      elseif luasnip.expand_or_jumpable() then
        vim.fn.feedkeys(vim.api.nvim_replace_termcodes("luasnip-expand-or-jump", true, true, true), "")
      else
        fallback()
      end
    end,
    [""] = function(fallback)
      if cmp.visible() then
        cmp.select_prev_item()
      elseif luasnip.jumpable(-1) then
        vim.fn.feedkeys(vim.api.nvim_replace_termcodes("luasnip-jump-prev", true, true, true), "")
      else
        fallback()
      end
    end
  },
  sources = {
    {name = "nvim_lsp"},
    {name = "luasnip"},
    {
      name = "buffer",
      option = {
        get_bufnrs = function()
          return vim.api.nvim_list_bufs()
        end
      }
    },
    {name = "path"}
  }
}

配置LuaSnip
nvim ~/.config/nvim/after/plugin/snippets.lua

内容如下:

local ls = require("luasnip")
-- some shorthands...
local s = ls.snippet
local sn = ls.snippet_node
local t = ls.text_node
local i = ls.insert_node
local f = ls.function_node
local c = ls.choice_node
local d = ls.dynamic_node
local r = ls.restore_node
local l = require("luasnip.extras").lambda
local rep = require("luasnip.extras").rep
local p = require("luasnip.extras").partial
local m = require("luasnip.extras").match
local n = require("luasnip.extras").nonempty
local dl = require("luasnip.extras").dynamic_lambda
local fmt = require("luasnip.extras.fmt").fmt
local fmta = require("luasnip.extras.fmt").fmta
local types = require("luasnip.util.types")
local conds = require("luasnip.extras.expand_conditions")

-- If you're reading this file for the first time, best skip to around line 190
-- where the actual snippet-definitions start.

-- Every unspecified option will be set to the default.
ls.config.set_config({
	history = true,
	-- Update more often, :h events for more info.
	updateevents = "TextChanged,TextChangedI",
	-- Snippets aren't automatically removed if their text is deleted.
	-- `delete_check_events` determines on which events (:h events) a check for
	-- deleted snippets is performed.
	-- This can be especially useful when `history` is enabled.
	delete_check_events = "TextChanged",
	ext_opts = {
		[types.choiceNode] = {
			active = {
				virt_text = { { "choiceNode", "Comment" } },
			},
		},
	},
	-- treesitter-hl has 100, use something higher (default is 200).
	ext_base_prio = 300,
	-- minimal increase in priority.
	ext_prio_increase = 1,
	enable_autosnippets = true,
	-- mapping for cutting selected text so it's usable as SELECT_DEDENT,
	-- SELECT_RAW or TM_SELECTED_TEXT (mapped via xmap).
	store_selection_keys = "",
	-- luasnip uses this function to get the currently active filetype. This
	-- is the (rather uninteresting) default, but it's possible to use
	-- eg. treesitter for getting the current filetype by setting ft_func to
	-- require("luasnip.extras.filetype_functions").from_cursor (requires
	-- `nvim-treesitter/nvim-treesitter`). This allows correctly resolving
	-- the current filetype in eg. a markdown-code block or `vim.cmd()`.
	ft_func = function()
		return vim.split(vim.bo.filetype, ".", true)
	end,
})

-- args is a table, where 1 is the text in Placeholder 1, 2 the text in
-- placeholder 2,...
local function copy(args)
	return args[1]
end

-- 'recursive' dynamic snippet. Expands to some text followed by itself.
local rec_ls
rec_ls = function()
	return sn(
		nil,
		c(1, {
			-- Order is important, sn(...) first would cause infinite loop of expansion.
			t(""),
			sn(nil, { t({ "", "\t\\item " }), i(1), d(2, rec_ls, {}) }),
		})
	)
end

-- complicated function for dynamicNode.
local function jdocsnip(args, _, old_state)
	-- !!! old_state is used to preserve user-input here. DON'T DO IT THAT WAY!
	-- Using a restoreNode instead is much easier.
	-- View this only as an example on how old_state functions.
	local nodes = {
		t({ "/**", " * " }),
		i(1, "A short Description"),
		t({ "", "" }),
	}

	-- These will be merged with the snippet; that way, should the snippet be updated,
	-- some user input eg. text can be referred to in the new snippet.
	local param_nodes = {}

	if old_state then
		nodes[2] = i(1, old_state.descr:get_text())
	end
	param_nodes.descr = nodes[2]

	-- At least one param.
	if string.find(args[2][1], ", ") then
		vim.list_extend(nodes, { t({ " * ", "" }) })
	end

	local insert = 2
	for indx, arg in ipairs(vim.split(args[2][1], ", ", true)) do
		-- Get actual name parameter.
		arg = vim.split(arg, " ", true)[2]
		if arg then
			local inode
			-- if there was some text in this parameter, use it as static_text for this new snippet.
			if old_state and old_state[arg] then
				inode = i(insert, old_state["arg" .. arg]:get_text())
			else
				inode = i(insert)
			end
			vim.list_extend(
				nodes,
				{ t({ " * @param " .. arg .. " " }), inode, t({ "", "" }) }
			)
			param_nodes["arg" .. arg] = inode

			insert = insert + 1
		end
	end

	if args[1][1] ~= "void" then
		local inode
		if old_state and old_state.ret then
			inode = i(insert, old_state.ret:get_text())
		else
			inode = i(insert)
		end

		vim.list_extend(
			nodes,
			{ t({ " * ", " * @return " }), inode, t({ "", "" }) }
		)
		param_nodes.ret = inode
		insert = insert + 1
	end

	if vim.tbl_count(args[3]) ~= 1 then
		local exc = string.gsub(args[3][2], " throws ", "")
		local ins
		if old_state and old_state.ex then
			ins = i(insert, old_state.ex:get_text())
		else
			ins = i(insert)
		end
		vim.list_extend(
			nodes,
			{ t({ " * ", " * @throws " .. exc .. " " }), ins, t({ "", "" }) }
		)
		param_nodes.ex = ins
		insert = insert + 1
	end

	vim.list_extend(nodes, { t({ " */" }) })

	local snip = sn(nil, nodes)
	-- Error on attempting overwrite.
	snip.old_state = param_nodes
	return snip
end

-- Make sure to not pass an invalid command, as io.popen() may write over nvim-text.
local function bash(_, _, command)
	local file = io.popen(command, "r")
	local res = {}
	for line in file:lines() do
		table.insert(res, line)
	end
	return res
end

-- Returns a snippet_node wrapped around an insert_node whose initial
-- text value is set to the current date in the desired format.
local date_input = function(args, snip, old_state, fmt)
	local fmt = fmt or "%Y-%m-%d"
	return sn(nil, i(1, os.date(fmt)))
end

ls.snippets = {
	-- When trying to expand a snippet, luasnip first searches the tables for
	-- each filetype specified in 'filetype' followed by 'all'.
	-- If ie. the filetype is 'lua.c'
	--     - luasnip.lua
	--     - luasnip.c
	--     - luasnip.all
	-- are searched in that order.
	all = {
		-- trigger is `fn`, second argument to snippet-constructor are the nodes to insert into the buffer on expansion.
		s("fn", {
			-- Simple static text.
			t("//Parameters: "),
			-- function, first parameter is the function, second the Placeholders
			-- whose text it gets as input.
			f(copy, 2),
			t({ "", "function " }),
			-- Placeholder/Insert.
			i(1),
			t("("),
			-- Placeholder with initial text.
			i(2, "int foo"),
			-- Linebreak
			t({ ") {", "\t" }),
			-- Last Placeholder, exit Point of the snippet.
			i(0),
			t({ "", "}" }),
		}),
		s("class", {
			-- Choice: Switch between two different Nodes, first parameter is its position, second a list of nodes.
			c(1, {
				t("public "),
				t("private "),
			}),
			t("class "),
			i(2),
			t(" "),
			c(3, {
				t("{"),
				-- sn: Nested Snippet. Instead of a trigger, it has a position, just like insert-nodes. !!! These don't expect a 0-node!!!!
				-- Inside Choices, Nodes don't need a position as the choice node is the one being jumped to.
				sn(nil, {
					t("extends "),
					-- restoreNode: stores and restores nodes.
					-- pass position, store-key and nodes.
					r(1, "other_class", i(1)),
					t(" {"),
				}),
				sn(nil, {
					t("implements "),
					-- no need to define the nodes for a given key a second time.
					r(1, "other_class"),
					t(" {"),
				}),
			}),
			t({ "", "\t" }),
			i(0),
			t({ "", "}" }),
		}),
		-- Alternative printf-like notation for defining snippets. It uses format
		-- string with placeholders similar to the ones used with Python's .format().
		s(
			"fmt1",
			fmt("To {title} {} {}.", {
				i(2, "Name"),
				i(3, "Surname"),
				title = c(1, { t("Mr."), t("Ms.") }),
			})
		),
		-- To escape delimiters use double them, e.g. `{}` -> `{{}}`.
		-- Multi-line format strings by default have empty first/last line removed.
		-- Indent common to all lines is also removed. Use the third `opts` argument
		-- to control this behaviour.
		s(
			"fmt2",
			fmt(
				[[
			foo({1}, {3}) {{
				return {2} * {4}
			}}
			]],
				{
					i(1, "x"),
					rep(1),
					i(2, "y"),
					rep(2),
				}
			)
		),
		-- Empty placeholders are numbered automatically starting from 1 or the last
		-- value of a numbered placeholder. Named placeholders do not affect numbering.
		s(
			"fmt3",
			fmt("{} {a} {} {1} {}", {
				t("1"),
				t("2"),
				a = t("A"),
			})
		),
		-- The delimiters can be changed from the default `{}` to something else.
		s(
			"fmt4",
			fmt("foo() { return []; }", i(1, "x"), { delimiters = "[]" })
		),
		-- `fmta` is a convenient wrapper that uses `` instead of `{}`.
		s("fmt5", fmta("foo() { return ; }", i(1, "x"))),
		-- By default all args must be used. Use strict=false to disable the check
		s(
			"fmt6",
			fmt("use {} only", { t("this"), t("not this") }, { strict = false })
		),
		-- Use a dynamic_node to interpolate the output of a
		-- function (see date_input above) into the initial
		-- value of an insert_node.
		s("novel", {
			t("It was a dark and stormy night on "),
			d(1, date_input, {}, { user_args = { "%A, %B %d of %Y" } }),
			t(" and the clocks were striking thirteen."),
		}),
		-- Parsing snippets: First parameter: Snippet-Trigger, Second: Snippet body.
		-- Placeholders are parsed into choices with 1. the placeholder text(as a snippet) and 2. an empty string.
		-- This means they are not SELECTed like in other editors/Snippet engines.
		ls.parser.parse_snippet(
			"lspsyn",
			"Wow! This ${1:Stuff} really ${2:works. ${3:Well, a bit.}}"
		),

		-- When wordTrig is set to false, snippets may also expand inside other words.
		ls.parser.parse_snippet(
			{ trig = "te", wordTrig = false },
			"${1:cond} ? ${2:true} : ${3:false}"
		),

		-- When regTrig is set, trig is treated like a pattern, this snippet will expand after any number.
		ls.parser.parse_snippet({ trig = "%d", regTrig = true }, "A Number!!"),
		-- Using the condition, it's possible to allow expansion only in specific cases.
		s("cond", {
			t("will only expand in c-style comments"),
		}, {
			condition = function(line_to_cursor, matched_trigger, captures)
				-- optional whitespace followed by //
				return line_to_cursor:match("%s*//")
			end,
		}),
		-- there's some built-in conditions in "luasnip.extras.expand_conditions".
		s("cond2", {
			t("will only expand at the beginning of the line"),
		}, {
			condition = conds.line_begin,
		}),
		-- The last entry of args passed to the user-function is the surrounding snippet.
		s(
			{ trig = "a%d", regTrig = true },
			f(function(_, snip)
				return "Triggered with " .. snip.trigger .. "."
			end, {})
		),
		-- It's possible to use capture-groups inside regex-triggers.
		s(
			{ trig = "b(%d)", regTrig = true },
			f(function(_, snip)
				return "Captured Text: " .. snip.captures[1] .. "."
			end, {})
		),
		s({ trig = "c(%d+)", regTrig = true }, {
			t("will only expand for even numbers"),
		}, {
			condition = function(line_to_cursor, matched_trigger, captures)
				return tonumber(captures[1]) % 2 == 0
			end,
		}),
		-- Use a function to execute any shell command and print its text.
		s("bash", f(bash, {}, "ls")),
		-- Short version for applying String transformations using function nodes.
		s("transform", {
			i(1, "initial text"),
			t({ "", "" }),
			-- lambda nodes accept an l._1,2,3,4,5, which in turn accept any string transformations.
			-- This list will be applied in order to the first node given in the second argument.
			l(l._1:match("[^i]*$"):gsub("i", "o"):gsub(" ", "_"):upper(), 1),
		}),

		s("transform2", {
			i(1, "initial text"),
			t("::"),
			i(2, "replacement for e"),
			t({ "", "" }),
			-- Lambdas can also apply transforms USING the text of other nodes:
			l(l._1:gsub("e", l._2), { 1, 2 }),
		}),
		s({ trig = "trafo(%d+)", regTrig = true }, {
			-- env-variables and captures can also be used:
			l(l.CAPTURE1:gsub("1", l.TM_FILENAME), {}),
		}),
		-- Set store_selection_keys = "" (for example) in your
		-- luasnip.config.setup() call to populate
		-- TM_SELECTED_TEXT/SELECT_RAW/SELECT_DEDENT.
		-- In this case: select a URL, hit Tab, then expand this snippet.
		s("link_url", {
			t('            
关注
打赏
1665243900
查看更多评论
0.0446s