Skip to content

Latest commit

 

History

History
117 lines (109 loc) · 4.49 KB

vim_go_coc_gopls.md

File metadata and controls

117 lines (109 loc) · 4.49 KB
title author date subtitle image tags type
vim-go 和 coc.nvim 共享 gopls daemon
liyiheng
2023-08-02 22:34:24 +0800
通过共享 gopls 避免不必要的内存开销
golang
go
vim
coc.nvim
vim-go

TLDR: 指定环境变量即可 vimrc 中 let $XDG_RUNTIME_DIR='/tmp' 或 zshrc 中 export XDG_RUNTIME_DIR=/tmp

共享 gopls , gopls 又是什么呢?

gopls (pronounced "Go please") is the official Go language server developed by the Go team. It provides IDE features to any LSP-compatible editor.

gopls 是 go 语言官方的语言服务器,可以为支持 LSP 协议的编辑器提供 IDE 功能,例如自动提示、补全、跳转、查找引用、重命名等。

vim-go 是一个功能丰富的 vim golang 开发插件,其部分功能基于 gopls 实现; coc.nvim 是基于 LSP 的智能感知插件,提供了优秀的补全体验,同时在其基础上可以安装各种 coc 插件。

coc.nvim 与 vim-go 功能并不重叠,难免有同时使用的情况。 这时打开 go 项目,coc 和 vim-go 会各自启动一个 gopls 实例,产生四个 gopls 进程,耗费了两份内存。 四个进程分别为 2 个 forwarder 和 2 个 daemon,预期是 2 个 forwarder 和 1 个 daemon, 关于 forwarder 和 daemon 详见 gopls execution modes 此时 vimrc 中两者相关配置:

call plug#begin('~/.vim/plugged')
Plug 'fatih/vim-go'
Plug 'neoclide/coc.nvim', {'branch': 'release'}
call plug#end()
let g:coc_user_config['languageserver']['golang'] = {
		\   'command': 'gopls',
		\   'rootPatterns': ['go.mod'],
		\   'filetypes': ['go']
		\}

通过相关文档得知,可以通过 -remote=auto 参数使 vim-go 和 coc 共享同一个 gopls daemon。 于是调整配置:

let g:go_gopls_enabled = 1
let g:go_gopls_options = ['-remote=auto']
let g:coc_user_config['languageserver']['golang'] = {
		\   'command': 'gopls',
		\   'args': ['-remote=auto'],
		\   'rootPatterns': ['go.mod'],
		\   'filetypes': ['go']
		\}

重新打开 go 项目,发现仍有四个 gopls 进程,索性禁用 vim-go 的 gopls 功能:

let g:go_gopls_enabled = 0

近期想启用 vim-go 的 gopls 特性,于是重新排查这个问题。 无意间发现两个 daemon 使用的 socket 文件名相同,路径不同:

vim-go:
/tmp/gopls-cd85fd-daemon.liyiheng
coc.nvim:
/tmp/nvim.liyiheng/qUMdHS/gopls-cd85fd-daemon.liyiheng

猜测之所以未能共享 daemon 是因为 gopls -remote=auto 启动时检测不到 socket 导致启动了一个新实例, 那么问题的关键就在 socket 文件路径。 查阅 gopls 源码发现其路径规则:

runtimeDir := os.TempDir()
if xdg := os.Getenv("XDG_RUNTIME_DIR"); xdg != "" {
	runtimeDir = xdg
}
return "unix", filepath.Join(runtimeDir, fmt.Sprintf("%s-%s-daemon.%s%s", basename, shortHash, user, idComponent))

由此可见,gopls 将 socket 文件创建在 os.TempDir() 下。 当时竟未注意到 XDG_RUNTIME_DIR 的优先级更高。 于是查看 TempDir 的实现:

func TempDir() string {
	return tempDir()
}
func tempDir() string {
	dir := Getenv("TMPDIR")
	if dir == "" {
		if runtime.GOOS == "android" {
			dir = "/data/local/tmp"
		} else {
			dir = "/tmp"
		}
	}
	return dir
}

socket 所处路径由环境变量控制,难道 vim 内部会操作环境变量? 即便如此,vim-go 和 coc 都是在 vim 中运行, vim 的某些环境对两者都应有效。

最终带着疑问给 vim-go 提了 issue。 通过 @bhcleek 提供的信息, 了解到问题的根源是 vim-go 和 coc 使用了不同的 TMPDIR,并且早在 2020 年 gopls 就通过 优先使用 XDG_RUNTIME_DIR 解决了这个问题。 至于为什么 vim-go 和 coc 会使用不同的 TMPDIR 的疑问仍然未得到解答。 好在有了明确的解决方案:

let $XDG_RUNTIME_DIR='/tmp'

指定 XDG_RUNTIME_DIR 后, vim-go 和 coc 启动的 gopls 都会从同一个目录检查是否已有 daemon, 进行实现 daemon 共享从而节约资源