mfussenegger / nvim-jdtls

Extensions for the built-in LSP support in Neovim for eclipse.jdt.ls
GNU General Public License v3.0
1k stars 62 forks source link

Consecutive rename in same file fails with "out of sync" error #537

Closed mikehaertl closed 1 year ago

mikehaertl commented 1 year ago

LSP client configuration

-- See `:help vim.lsp.start_client` for an overview of the supported `config` options.

-- nvim data and config path (:h stdpath)
local nvim_data_path = vim.fn.stdpath('data') .. '/'
local nvim_config_path = vim.fn.stdpath('config') .. '/'
-- location where mason installs jdts
local mason_package_path = nvim_data_path .. 'mason/packages/'
local jdtls_path = mason_package_path .. 'jdtls/'
-- root project dir of currently opened file
local project_dir = require('jdtls.setup').find_root({'.git', 'mvnw', 'gradlew'})
-- last part of the project_dir
local project_name = vim.fn.fnamemodify(project_dir, ':p:h:t')

local jdtls = require('jdtls')
local util = require('jdtls.util')
local dap = require('dap')
local dapui = require('dapui')
local widgets = require('dap.ui.widgets')
local whichkey = require('which-key')

local config = {
  -- The command that starts the language server
  -- See: https://github.com/eclipse/eclipse.jdt.ls#running-from-the-command-line
  cmd = {

    '/home/mike/.sdkman/candidates/java/17.0.6-tem/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',
    '-Xmx1g',
    '--add-modules=ALL-SYSTEM',
    '--add-opens', 'java.base/java.util=ALL-UNNAMED',
    '--add-opens', 'java.base/java.lang=ALL-UNNAMED',

    '-javaagent:' .. jdtls_path .. 'lombok.jar',
    '-jar',  vim.fn.glob(jdtls_path .. 'plugins/org.eclipse.equinox.launcher_*.jar', 1),
    '-configuration', (jdtls_path .. 'config_linux'),

    -- directory where jdtls stores project related data
    '-data', (nvim_data_path .. 'jdtls_data/' .. project_name),
  },

  -- One dedicated LSP server & client will be started per unique root_dir
  root_dir = project_dir,

  -- Inject nvim-cmp capabilities, e.g. to support snippets provided by jdtls
  capabilities = require('cmp_nvim_lsp').default_capabilities(),

  -- Here you can configure eclipse.jdt.ls specific settings
  -- See https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request
  -- for a list of options
  settings = {
    java = {
      autobuild = { enabled = false },
      codeGeneration = {
        hashCodeEquals = {
          useJava7Objects = true,
        },
        useBlocks = true,
        toString = {
          template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
        },
      },
      completion = {
        -- Static members or types with static members. Content assist will
        -- propose those static members even if the import is missing.
        favoriteStaticMembers = {
          "java.lang.String.*",
          "org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*",
          "org.springframework.security.test.web.servlet.response.SecurityMockMvcResultHandlers.*",
          "org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*",
          "org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*",
          "org.springframework.test.web.client.match.MockRestRequestMatchers.*",
          "org.springframework.test.web.client.response.MockRestResponseCreators.*",
          "org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*",
          "org.springframework.test.web.servlet.result.MockMvcResultMatchers.*",
          "org.assertj.core.api.Assertions.*",
          "org.awaitility.Awaitility.*",
          "org.hamcrest.Matchers.*",
          "org.mockito.BDDMockito.*",
          "org.mockito.Mockito.*",
          "org.mockito.ArgumentMatchers.*",
          -- Defaults:
          "org.junit.Assert.*",
          "org.junit.Assume.*",
          "org.junit.jupiter.api.Assertions.*",
          "org.junit.jupiter.api.Assumptions.*",
          "org.junit.jupiter.api.DynamicContainer.*",
          "org.junit.jupiter.api.DynamicTest.*",
        },

        -- Members or types that should be excluded from completion.
        -- Useful for nasty suggestions that are never used and only add noise
        -- to suggestions e.g  "java.awt.*"
        filteredTypes = {
        },
      },
      configuration = {
        -- See https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request
        -- And search for `interface RuntimeOption`
        -- The `name` is NOT arbitrary, but must match one of the elements from `enum ExecutionEnvironment` in the link above
        runtimes = {
          {
            name = "JavaSE-11",
            path = "/usr/lib/jvm/java-11-openjdk-amd64/",
          },
          {
            name = "JavaSE-17",
            path = "/home/mike/.sdkman/candidates/java/17.0.6-tem/",
          },
          {
            name = "JavaSE-20",
            path = "/home/mike/.sdkman/candidates/java/20.0.1-tem/",
          },
        },
      },
      contentProvider = {
        preferred = 'fernflower'
      },
      format = {
        settings = {
          -- Formatter specifications are in an XML format as used by Eclipse [1].
          -- Our file is a copy from vscode-java [2] with local modifications.
          --
          --  [1] https://github.com/redhat-developer/vscode-java/wiki/Formatter-settings
          --  [2] https://github.com/redhat-developer/vscode-java/blob/master/formatters/eclipse-formatter.xml
          url = nvim_config_path .. 'eclipse-formatter.xml',
          profile = 'Eclipse'
        },
      },
      -- Should make gradle use java 11 instead of system default, but has no effect:
      -- import = {
      --   gradle = {
      --     java = {
      --       home = '/usr/lib/jvm/java-11-openjdk-amd64/',
      --     }
      --   }
      -- },
      signatureHelp = {
        description = { enabled = true },
      },
      sources = {
        organizeImports = {
          starThreshold = 9999;
          staticStarThreshold = 9999;
        },
      },
    },
  },

  -- Language server `initializationOptions`
  -- You need to extend the `bundles` with paths to jar files
  -- if you want to use additional eclipse.jdt.ls plugins.
  --
  -- See https://github.com/mfussenegger/nvim-jdtls#java-debug-installation
  init_options = {
    bundles = vim.list_extend(
      {
        -- Required for DAP
        vim.fn.glob(mason_package_path .. 'java-debug-adapter/extension/server/com.microsoft.java.debug.plugin-*.jar', 1),
      },

      -- Required for test_class / test_nearest_method
      vim.split(vim.fn.glob(mason_package_path .. 'java-test/extension/server/{junit,org,com.microsoft.java.test.plugin}*.jar', 1), "\n")
    )
  },

  on_attach = function(client, bufnr)
    -- With `hotcodereplace = 'auto' the debug adapter will try to apply code changes
    -- you make during a debug session immediately.
    -- You can use the `JdtHotcodeReplace` command to trigger it manually
    jdtls.setup_dap({ hotcodereplace = 'auto' })

    jdtls.setup.add_commands()

    -- Opens the DAP UI as soon as debugger runs/continues
    dap.listeners.after.event_initialized["dapui_config"] = function()
      dapui.open()
    end

    whichkey.register({
      ['<A-o>'] = { jdtls.organize_imports, 'Java: Organize imports'},
      ['<F9>'] = { dap.step_over, 'Debugger: step over'},
      ['<F10>'] = { dap.step_over, 'Debugger: step into'},
      ['<F12>'] = { dap.step_over, 'Debugger: step out'},
      ['<space>'] = {
        d = {
          name = 'debug (DAP)',
          a = { '<Cmd>:vsplit | terminal w3m https://docs.oracle.com/javase/8/docs/api/compact3-summary.html<CR>', 'Browse Java 8 API'},
          b = { dap.toggle_breakpoint, 'Toggle breakpoint' },
          B = { function() dap.set_breakpoint(vim.fn.input('Breakpoint condition: ')) end, 'Set conditional breakpoint' },
          c = { dap.continue, 'Continue' },
          d = { dap.clear_breakpoints, 'Clear breakpoints' },
          e = { jdtls.test_class, 'Debug test class' },
          E = { jdtls.test_nearest_method, 'Debug test method' },
          f = { function() widgets.centered_float(widgets.frames) end, 'Show frames' },
          h = { widgets.hover, 'Show hover', mode = {'n', 'v'} },
          i = { function()
            require('jdtls.dap').setup_dap_main_class_configs({
              verbose = true,
            })
          end, 'Init autostart main class' },

          I = { function()

            -- Configuration for the debug adapter
            dap.configurations.java = {
              {
                type = 'java',
                -- attach to a running Java program that was started with --debug-jvm
                -- and that is waiting on 5005 for a debug adapter to attach
                request = 'attach',
                name = "Java attach",
                hostName = "127.0.0.1",
                port = 5005,
                projectName = project_name,
              },
            }
          end, 'Init external (req. --debug-jvm)' },
          r = { dap.repl.open, 'Open REPL' },
          s = { function() widgets.centered_float(widgets.scopes) end, 'Show scopes' },
          t = { dapui.toggle, 'Toggle UI' },
        },
      },
      ['<leader>se']= { vim.lsp.buf.format, 'Format code'},
    })
  end
}
-- This starts a new client & server,
-- or attaches to an existing client & server depending on the `root_dir`.
require('jdtls').start_or_attach(config)

Eclipse.jdt.ls version

1.25.0

Steps to Reproduce

Run lua vim.lsp.buf.rename("<random name>") two times in the same file.

Expected Result

It should rename the method also the second time.

I suspect a problem in jdtls because with other languages the same works fine (tried with Typescript/Volar).

Actual Result

The method is not renamed. It seems to work again if I just wait some time or restart neovim.

The LSP debug log has a "Resource '...' is out of sync with file system." message:

[DEBUG][2023-07-26 12:56:19] .../lua/vim/lsp.lua:1392   "LSP[jdtls]"    "client.request"    1   "textDocument/prepareRename"    {  position = {    character = 46,    line = 30  },  textDocument = {    uri = "file:///home/repos/src/main/java/com/service/organization/OrganizationReadService.java"  }} <function 1>    231
[DEBUG][2023-07-26 12:56:19] .../vim/lsp/rpc.lua:284    "rpc.send"  {  id = 31,  jsonrpc = "2.0",  method = "textDocument/prepareRename",  params = {    position = {      character = 46,      line = 30    },    textDocument = {      uri = "file:///home/repos/src/main/java/com/service/organization/OrganizationReadService.java"    }  }}
[DEBUG][2023-07-26 12:56:19] .../vim/lsp/rpc.lua:387    "rpc.receive"   {  id = 31,  jsonrpc = "2.0",  result = {    ["end"] = {      character = 61,      line = 30    },    start = {      character = 46,      line = 30    }  }}
[DEBUG][2023-07-26 12:56:19] .../lua/vim/lsp.lua:1392   "LSP[jdtls]"    "client.request"    1   "textDocument/rename"   {  newName = "findActiveById2",  position = {    character = 46,    line = 30  },  textDocument = {    uri = "file:///home/repos/src/main/java/com/service/organization/OrganizationReadService.java"  }}   <function 1>    231
[DEBUG][2023-07-26 12:56:19] .../vim/lsp/rpc.lua:284    "rpc.send"  {  id = 32,  jsonrpc = "2.0",  method = "textDocument/rename",  params = {    newName = "findActiveById2",    position = {      character = 46,      line = 30    },    textDocument = {      uri = "file:///home/repos/src/main/java/com/service/organization/OrganizationReadService.java"    }  }}
[DEBUG][2023-07-26 12:56:21] .../vim/lsp/rpc.lua:387    "rpc.receive"   {  error = {    code = -32600,    message = "Resource 'src/main/java/com/controller/v1/organization/OrganizationMembershipController.java' is out of sync with file system."  },  id = 32,  jsonrpc = "2.0"}

Note: path names redacted to obfuscate project details.

mfussenegger commented 1 year ago

I can't reproduce the issue and this would be either a neovim-core or server issue. nvim-jdtls doesn't contain any logic in regards to rename.

Maybe you can try with neovim nightly if you're on an earlier version, I think there have been some changes in that area.

mikehaertl commented 1 year ago

Maybe you can try with neovim nightly if you're on an earlier version, I think there have been some changes in that area.

It's fixed there, indeed. Thanks for the pointer.