Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/tools/gopls: v0.16.1 breaking omnifunc completion in neovim #68764

Closed
abennett opened this issue Aug 7, 2024 · 12 comments
Closed

x/tools/gopls: v0.16.1 breaking omnifunc completion in neovim #68764

abennett opened this issue Aug 7, 2024 · 12 comments
Labels
gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Milestone

Comments

@abennett
Copy link

abennett commented Aug 7, 2024

gopls version

Build info
----------
golang.org/x/tools/gopls v0.16.1
    golang.org/x/tools/[email protected] h1:1hO/dCeUvjEYx3V0rVvCtOkwnpEpqS29paE Jw4dcAc=
    github.com/BurntSushi/[email protected] h1:9F2/ DoOYIOksmaJFPw1tGFy1eDnIJXg UHjuD8lTak=
    github.com/google/[email protected] h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
    golang.org/x/exp/[email protected] h1:2O2DON6y3XMJiQRAS1UWU 54aec2uopH3x7MAiqGW6Y=
    golang.org/x/[email protected] h1:5 9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
    golang.org/x/[email protected] h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
    golang.org/x/[email protected] h1:3Wt8mZlbFwG8llny t18kh7AXxyWePFycXMuVdHxnyM=
    golang.org/x/[email protected] h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
    golang.org/x/[email protected] h1:Kd Z5Pm6uwYx3T2KEkeHMHUMZxDPb/q6b1m zEcy62c=
    golang.org/x/[email protected] h1:SP0mPeg2PmGCu03V 61EcQiOjmpri2XijexKdzv8Z1I=
    honnef.co/go/[email protected] h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs=
    mvdan.cc/[email protected] h1:G3QvahNDmpD Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo=
    mvdan.cc/xurls/[email protected] h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh 8=
go: go1.22.5

go env

GO111MODULE=''
GOARCH='arm64'
GOBIN=''
GOCACHE='/Users/aaron/Library/Caches/go-build'
GOENV='/Users/aaron/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/aaron/go/pkg/mod'
GOOS='darwin'
GOPATH='/Users/aaron/go'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/homebrew/Cellar/go/1.22.5/libexec'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/homebrew/Cellar/go/1.22.5/libexec/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.22.5'
GCCGO='gccgo'
AR='ar'
CC='cc'
CXX='c  '
CGO_ENABLED='1'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/5p/fpck_9g5367f7lk3p50bvdbc0000gn/T/go-build356802509=/tmp/go-build -gno-record-gcc-switches -fno-common

What did you do?

  1. Upgrade gopls to v0.16.1.
  2. Open a Go file with neovim.
  3. Attempt to use omnifunc completion with <ctrl-x><ctrl-o>
  4. neovim throws an indexing error
  5. Reverting back to gopls version v0.15.3 works as expected.

What did you see happen?

Attempting to use omnifunc completion with <ctrl-x><ctrl-o> result in the following indexing error from neovim.

Error executing vim.schedule lua callback: ...im/0.10.1/share/nvim/runtime/lua/vim/lsp/_completion.lua:154: attempt to index field 'range' (a nil value)                                                                                                                      
stack traceback:                                                                                                                                                                                                                                                                        
        ...im/0.10.1/share/nvim/runtime/lua/vim/lsp/_completion.lua:154: in function 'adjust_start_col'                                                                                                                                                                                 
        ...im/0.10.1/share/nvim/runtime/lua/vim/lsp/_completion.lua:202: in function '_convert_results'                                                                                                                                                                                 
        ...im/0.10.1/share/nvim/runtime/lua/vim/lsp/_completion.lua:253: in function 'handler'                                                                                                                                                                                          
        .../neovim/0.10.1/share/nvim/runtime/lua/vim/lsp/client.lua:687: in function ''                                                                                                                                                                                                 
        vim/_editor.lua: in function <vim/_editor.lua:0> 

What did you expect to see?

Successful autocompletion recommendations without an error.

Editor and settings

Running neovim version v0.10.1.

-- Set leader
vim.g.mapleader = ","

-- Plug-Ins below
-- Bootstrap lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
  local lazyrepo = "https://github.com/folke/lazy.nvim.git"
  local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
  if vim.v.shell_error ~= 0 then
    vim.api.nvim_echo({
      { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
      { out, "WarningMsg" },
      { "\nPress any key to exit..." },
    }, true, {})
    vim.fn.getchar()
    os.exit(1)
  end
end
vim.opt.rtp:prepend(lazypath)

local plugins = {
  {
    'catppuccin/nvim',
    lazy = false,
    priority = 1000,
    config = function()
      vim.cmd([[colorscheme catppuccin]])
    end,
  },
  'neovim/nvim-lspconfig',
  'windwp/nvim-autopairs',
  {
    'nvim-lualine/lualine.nvim',
    dependencies = {'nvim-tree/nvim-web-devicons'},
  },
  'mattn/emmet-vim',
  {
    'nvim-treesitter/nvim-treesitter',
    cmd = {"TSUpdateSync"},
  },
  'ray-x/lsp_signature.nvim',
  {
    'lukas-reineke/indent-blankline.nvim',
    main = "ibl",
  },
  'glench/vim-jinja2-syntax',
  'tsandall/vim-rego',
  'lewis6991/gitsigns.nvim',

  --  auto completion,
  {
    'hrsh7th/nvim-cmp',
    dependencies = {
      'hrsh7th/cmp-nvim-lsp',
      'hrsh7th/cmp-buffer',
      'hrsh7th/cmp-path',
      'hrsh7th/cmp-cmdline',
    }
  },

  'hrsh7th/cmp-vsnip',
  'hrsh7th/vim-vsnip',
}

require("lazy").setup({
    spec = plugins,
})

require('catppuccin').setup {
  flavor = "mocha"
}

-- treesitter
require('nvim-treesitter.configs').setup {
  ensure_installed = "all",
  ignore_install = { "phpdoc" },
  highlight = {
    enable = true,
  },
  indent = {
    enable = true
  },
  additional_vim_regex_highlighting = false,
}

-- completion
local cmp = require('cmp')
cmp.setup({
  snippet = {
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body)
    end
  },
  mapping = cmp.mapping.preset.insert({
    ['<C-b>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.abort(),
    ['<CR>'] = cmp.mapping.confirm({ select = true }),
  }),
  sources = cmp.config.sources({
    { name = "nvim_lsp" },
    { name = "vsnip" },
  }, {
      { name = "buffer" },
  })
})

-- indent guides
require('ibl').setup()

-- lualine
require('lualine').setup()

-- gitsigns
require('gitsigns').setup()

-- autopairs
require('nvim-autopairs').setup()

-- Use an on_attach function to only map the following keys
-- after the language server attaches to the current buffer
local on_attach = function(client, bufnr)
  require "lsp_signature".on_attach({
    bind = true,
    handler_opts = {
      border = "rounded"
    }
  }, bufnr)

  vim.lsp.set_log_level("DEBUG")
  local function buf_set_keymap(...) vim.api.nvim_buf_set_keymap(bufnr, ...) end
  local function buf_set_option(...) vim.api.nvim_buf_set_option(bufnr, ...) end

  -- Enable completion triggered by <c-x><c-o>
  buf_set_option('omnifunc', 'v:lua.vim.lsp.omnifunc')

  -- Mappings.
  local opts = { noremap=true, silent=true }

  -- See `:help vim.lsp.*` for documentation on any of the below functions
  buf_set_keymap('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', opts)
  buf_set_keymap('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', opts)
  buf_set_keymap('n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', opts)
  buf_set_keymap('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opts)
  buf_set_keymap('n', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opts)
  buf_set_keymap('n', '<space>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', opts)
  buf_set_keymap('n', '<space>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', opts)
  buf_set_keymap('n', '<space>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', opts)
  buf_set_keymap('n', '<space>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', opts)
  buf_set_keymap('n', '<space>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opts)
  buf_set_keymap('n', '<space>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opts)
  buf_set_keymap('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opts)
  buf_set_keymap('n', '<space>e', '<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>', opts)
  buf_set_keymap('n', '[d', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>', opts)
  buf_set_keymap('n', ']d', '<cmd>lua vim.lsp.diagnostic.goto_next()<CR>', opts)
  buf_set_keymap('n', '<space>q', '<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>', opts)
  buf_set_keymap('n', '<space>f', '<cmd>lua vim.lsp.buf.format()<CR>', opts)
end

-- Use a loop to conveniently call 'setup' on multiple servers and
-- map buffer local keybindings when the language server attaches
local nvim_lsp = require('lspconfig')
local capabilities = require('cmp_nvim_lsp').default_capabilities()
local shared = {
  capabilities = capabilities,
  on_attach=on_attach,
}

local servers = { 
  gopls = {
    cmd = {"gopls", "--remote", "auto"}
  },
  pyright = {},
  rust_analyzer = {
    settings = {
      ["rust-analyzer"] = {
        assist = {
          importGranularity = "module",
          importPrefix = "by_self",
        },
        cargo = {
          loadOutDirsFromCheck = true
        },
        procMacro = {
          enable = true
        },
      }
    }
  },
  solargraph = {},
  terraformls = {},
  tsserver = {},
  lua_ls = {}
}

for srv, srv_cfg in pairs(servers) do
  cfg = vim.tbl_deep_extend("error", srv_cfg, shared)
  nvim_lsp[srv].setup(cfg)
end

vim.opt.syntax = "ON"
vim.opt.laststatus = 2
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.autoindent = true
vim.opt.expandtab = true
vim.opt.nu = true
vim.opt.showcmd = true
vim.opt.wildmenu = true
vim.opt.lazyredraw = true
vim.opt.showmatch = true
vim.opt.hlsearch = true

vim.g.editorconfig = false
vim.keymap.set('n', '<leader><space>', ':nohlsearch<CR>')

-- double spaces
vim.api.nvim_create_autocmd({"FileType"}, {
  desc = "Some files are just double spaced",
  pattern = {"terraform", "css", "html.handlebars", "html", "javascript", "json", "yaml", "lua"},
  command = "setlocal shiftwidth=2 tabstop=2 softtabstop=2"
})

vim.api.nvim_create_autocmd({"BufWritePre"}, {
  pattern = {"*.tfvars", "*.tf"},
  command = "lua vim.lsp.buf.format()"
})

-- attempt to autoformat on save
vim.api.nvim_create_autocmd({"BufWritePre"}, {
  pattern = {"go.mod", "go.sum", "*.go"},
  command = "lua vim.lsp.buf.format()"
})

Logs

gopls logs were empty. I can provide the lsp logs, but they're pretty brutal to read.

@abennett abennett added gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository. labels Aug 7, 2024
@gopherbot gopherbot added this to the Unreleased milestone Aug 7, 2024
@abennett
Copy link
Author

abennett commented Aug 7, 2024

What's odd is that autocompletion still works if triggered by entering a . or the like. Only omnifunc completion with <ctrl-x><ctrl-o> is failing.

Still working out how to debug this, and the problem could exist elsewhere. But given that I can revert to 0.15.3 to fix the problem makes me think there's a regression somewhere in 0.16.1.

@hyangah
Copy link
Contributor

hyangah commented Aug 8, 2024

gopls v0.16.0 started to use InsertReplaceEdits if the client sets the capability. (https://github.com/golang/tools/blob/3057be8f634fdb03e1da1cad9fff3415299ad3ad/gopls/internal/protocol/tsprotocol.go#L1024). I think that is related.

@abennett Can you please share the LSP trace that includes the initialize message and the completion request/response messages (both failure case and successful case (.-triggered)?

@hyangah hyangah added the WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided. label Aug 8, 2024
@abennett
Copy link
Author

abennett commented Aug 8, 2024

I'll try to get whatever you ask for! 🙇

@abennett
Copy link
Author

abennett commented Aug 8, 2024

From 0.16.1 (the "bad" one)

[DEBUG][2024-08-08 11:43:30] .../lua/vim/lsp.lua:1010	"omnifunc.findstart"	{
  base = "",
  findstart = 1
}
[DEBUG][2024-08-08 11:43:30] ...m/lsp/client.lua:678	"LSP[gopls]"	"client.request"	1	"textDocument/completion"	{
  position = {
    character = 8,
    line = 78
  },
  textDocument = {
    uri = "file:///Users/aaron/mine/clickmon/main.go"
  }
}	<function 1>	1
[DEBUG][2024-08-08 11:43:30] .../vim/lsp/rpc.lua:286	"rpc.send"	{
  id = 11,
  jsonrpc = "2.0",
  method = "textDocument/completion",
  params = {
    position = {
      character = 8,
      line = 78
    },
    textDocument = {
      uri = "file:///Users/aaron/mine/clickmon/main.go"
    }
  }
}
[DEBUG][2024-08-08 11:43:30] .../vim/lsp/rpc.lua:408	"rpc.receive"	{
  id = 11,
  jsonrpc = "2.0",
  result = {
    isIncomplete = true,
    items = { {
        detail = "func(d time.Duration) time.Time",
        documentation = {
          kind = "markdown",
          value = "Add returns the time t d.\n"
        },
        filterText = "Add",
        insertTextFormat = 2,
        kind = 2,
        label = "Add",
        preselect = true,
        sortText = "00000",
        textEdit = {
          insert = {
            ["end"] = {
              character = 8,
              line = 78
            },
            start = {
              character = 8,
              line = 78
            }
          },
          newText = "Add(${1:})",
          replace = {
            ["end"] = {
              character = 8,
              line = 78
            },
            start = {
              character = 8,
              line = 78
            }
          }
        }
      }

From 0.15.3 (the "good" one)

[DEBUG][2024-08-08 11:45:22] .../lua/vim/lsp.lua:1010	"omnifunc.findstart"	{
  base = "",
  findstart = 1
}
[DEBUG][2024-08-08 11:45:22] ...m/lsp/client.lua:678	"LSP[gopls]"	"client.request"	1	"textDocument/completion"	{
  position = {
    character = 8,
    line = 78
  },
  textDocument = {
    uri = "file:///Users/aaron/mine/clickmon/main.go"
  }
}	<function 1>	1
[DEBUG][2024-08-08 11:45:22] .../vim/lsp/rpc.lua:286	"rpc.send"	{
  id = 10,
  jsonrpc = "2.0",
  method = "textDocument/completion",
  params = {
    position = {
      character = 8,
      line = 78
    },
    textDocument = {
      uri = "file:///Users/aaron/mine/clickmon/main.go"
    }
  }
}
[DEBUG][2024-08-08 11:45:22] .../vim/lsp/rpc.lua:408	"rpc.receive"	{
  id = 10,
  jsonrpc = "2.0",
  result = {
    isIncomplete = true,
    items = { {
        detail = "func(d time.Duration) time.Time",
        documentation = {
          kind = "markdown",
          value = "Add returns the time t d.\n"
        },
        filterText = "Add",
        insertTextFormat = 2,
        kind = 2,
        label = "Add",
        preselect = true,
        sortText = "00000",
        textEdit = {
          newText = "Add(${1:})",
          range = {
            ["end"] = {
              character = 8,
              line = 78
            },
            start = {
              character = 8,
              line = 78
            }
          }
        }
      }

I truncated items from each.

@abennett
Copy link
Author

abennett commented Aug 8, 2024

Looks like the difference is the textEdit using inserts like you said, and neovim is explicitly looking for textEdit.range which no longer exists in gopls v0.16.1. Hence the error.
https://github.com/neovim/neovim/blob/v0.10.1/runtime/lua/vim/lsp/_completion.lua#L154

    if item.textEdit and item.textEdit.range.start.line == lnum then

@abennett
Copy link
Author

abennett commented Aug 8, 2024

neovim/neovim#14560 (comment)

This is an upstream bug, we don't support InsertReplaceEdits (since we're using omnifunc which doesn't support that ofc), yet that is the response we are getting for completion results (even though we do not send this capability)

@abennett
Copy link
Author

abennett commented Aug 8, 2024

So this is neovim's omnifunc not supporting InsertReplaceEdits. Not sure how to proceed offhand. 🤔

@findleyr
Copy link
Contributor

findleyr commented Aug 8, 2024

So this is neovim's omnifunc not supporting InsertReplaceEdits.

and advertising the capability. I wonder if there's a minimally invasive way to suppress this capability in neovim.

@hyangah
Copy link
Contributor

hyangah commented Aug 9, 2024

neovim/neovim#16909 and hrsh7th/cmp-nvim-lsp#53 (comment) may be related.

The gopls uses InsertReplaceEdits only if the client advertises the capability (insertReplaceSupport) following the LSP spec. So, this is a client-side issue. hrsh7th/cmp-nvim-lsp#53 (comment) has an example snippet to suppress the capability @abennett

@abennett
Copy link
Author

abennett commented Aug 9, 2024

Agreee. vim-cmp autocomplete is compatible, so this should work.

vim.keymap.set("i", "<C-x><C-o>", cmp.complete)

@abennett
Copy link
Author

abennett commented Aug 9, 2024

Thank you fair bearing with me, @hyangah. 🙇‍♂️

@abennett abennett closed this as completed Aug 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
gopls Issues related to the Go language server, gopls. Tools This label describes issues relating to any tools in the x/tools repository. WaitingForInfo Issue is not actionable because of missing required information, which needs to be provided.
Projects
None yet
Development

No branches or pull requests

5 participants