Skip to content

Commit

Permalink
Merge pull request #1 from tobias-z/feat/lsp-rename
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-z authored Aug 1, 2022
2 parents 08211ec + 640af6a commit 1defdbc
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ codes = true

self = false

max_code_line_length = 125
max_comment_line_length = false

read_globals = {
"vim",
}
4 changes: 4 additions & 0 deletions .luarc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"Lua.diagnostics.disable": ["undefined-doc-param", "doc-field-no-class"]
}
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ format:

lint:
luacheck lua

gendoc:
nvim --headless --noplugin -u scripts/minimal_doc.vim -c "luafile ./scripts/generate_doc.lua" -c 'qa'
31 changes: 31 additions & 0 deletions doc/java_util.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
================================================================================
LSP *java_util.lsp*

The Java Util lsp module exposes a collection of functions that extend or
change the functionality of the standard java language server

The behaviour of a lot of these functions are partly inspired by how IntelliJ
functions

lsp.rename({new_name}, {opts}) *java_util.lsp.rename()*
Rename what you are currently hovering. If you are hovering a field and you
are using lombok, it will also rename your getters and setters to the
chosen new name.


Parameters: ~
{new_name} (string|nil) If new_name is passed you will not be
prompted for a new new_name
{opts} (table|nil) options which will be passed to the
|vim.lsp.buf.rename|

Options: ~
{filter} (function|nil) Predicate to filter clients used for rename.
Receives the attached clients as argument and
must return a list of clients.
{name} (string|nil) Restrict clients used for rename to ones
where client.name matches this field.



vim:tw=78:ts=8:ft=help:norl:
2 changes: 2 additions & 0 deletions doc/tags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
java_util.lsp java_util.txt /*java_util.lsp*
java_util.lsp.rename() java_util.txt /*java_util.lsp.rename()*
32 changes: 32 additions & 0 deletions lua/java_util/lsp/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---@tag java_util.lsp

---@config { ['field_heading'] = "Options", ["module"] = "java_util.lsp" }

---@brief [[
--- The Java Util lsp module exposes a collection of functions that extend or change the functionality of the standard java language server
---
--- The behaviour of a lot of these functions are partly inspired by how IntelliJ functions
---@brief ]]

local lsp = {}

-- Ref: https:/tjdevries/lazy.nvim
local function require_on_exported_call(mod)
return setmetatable({}, {
__index = function(_, picker)
return function(...)
return require(mod)[picker](...)
end
end,
})
end

--- Rename what you are currently hovering.
--- If you are hovering a field and you are using lombok, it will also rename your getters and setters to the chosen new name.
---@param new_name string|nil: If new_name is passed you will not be prompted for a new new_name
---@param opts table|nil: options which will be passed to the |vim.lsp.buf.rename|
---@field filter function|nil: Predicate to filter clients used for rename. Receives the attached clients as argument and must return a list of clients.
---@field name string|nil: Restrict clients used for rename to ones where client.name matches this field.
lsp.rename = require_on_exported_call("java_util.lsp.internal.rename").rename

return lsp
108 changes: 108 additions & 0 deletions lua/java_util/lsp/internal/rename.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
local string_util = require("java_util.string_util")
local lsp_util = require("java_util.lsp.util")
local ts_utils = require("nvim-treesitter.ts_utils")

local rename = {}

local function is_field(node)
local first_parent = node:parent()

if first_parent:type() == "field_access" then
return true
end

if first_parent:parent():type() == "field_declaration" then
return true
end

return false
end

function rename.rename(new_name, opts)
opts = opts or {}

if vim.o.filetype == "java" then
local node_at_cursor = ts_utils.get_node_at_cursor(0)
if is_field(node_at_cursor) then
rename._rename_field({ new_name = new_name, opts = opts, node_at_cursor = node_at_cursor })
return
end
end

vim.lsp.buf.rename(new_name, opts)
end

local function with_name(new_name, callback)
local old_name = vim.fn.expand("<cword>")
if new_name ~= nil then
callback(new_name, old_name)
else
vim.ui.input({ prompt = "New Name:", default = old_name }, function(n_name)
if n_name then
callback(n_name, old_name)
end
end)
end
end

function rename._rename_field(opts)
local params = vim.lsp.util.make_position_params(0)
params.context = { includeDeclaration = true }
lsp_util.request_all({
{ bufnr = 0, method = "textDocument/references", params = params },
{ bufnr = 0, method = "textDocument/definition", params = params },
}, function(results)
local references = results["textDocument/references"][1]
local def_results = results["textDocument/definition"]
local definitions = def_results[1]

if #references == 0 and #definitions == 0 then
return
end

-- If we are currently on the definition then textDocument/definition will not provide it to using
-- So we have to create the entry ourselves
local is_on_definition = #definitions == 0
if is_on_definition then
local context = def_results[2]
local range = ts_utils.node_to_lsp_range(opts.node_at_cursor)
table.insert(references, {
uri = context.params.textDocument.uri,
range = range,
})
else
for _, def in ipairs(definitions) do
table.insert(references, def)
end
end

with_name(opts.new_name, function(new_name, old_name)
local uppercase_old_name = string_util.first_to_upper(old_name)
local uppercase_new_name = string_util.first_to_upper(new_name)
local getter = string.format("%s%s", "get", uppercase_old_name)
local setter = string.format("%s%s", "set", uppercase_old_name)

for _, reference in ipairs(references) do
local bufnr = vim.uri_to_bufnr(reference.uri)
vim.fn.bufload(bufnr)
local start = reference.range.start
local the_end = reference.range["end"]
local line = vim.api.nvim_buf_get_text(bufnr, start.line, start.character, the_end.line, the_end.character, {})[1]
local original_len = string.len(line)

if string_util.starts_with(line, getter) or string_util.starts_with(line, setter) then
local beginning = string.sub(line, 0, 3)
local ending = string.sub(line, string.len(string.format("%s%s", beginning, uppercase_old_name)) + 1)
line = string.format("%s%s%s", beginning, uppercase_new_name, ending)
else
local ending = string.sub(line, string.len(old_name) + 1)
line = string.format("%s%s", new_name, ending)
end

vim.api.nvim_buf_set_text(bufnr, start.line, start.character, start.line, start.character + original_len, { line })
end
end)
end)
end

return rename
21 changes: 21 additions & 0 deletions lua/java_util/lsp/util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local lsp_util = {}

function lsp_util.request_all(requests, handler)
local completed = {}
local completed_count = 0
for _, request in ipairs(requests) do
vim.lsp.buf_request(request.bufnr, request.method, request.params, function(err, ...)
if err then
vim.api.nvim_err_writeln(string.format("Error when requesting method '%s': %s", request.method, err.message))
return
end
completed[request.method] = { ... }
completed_count = completed_count + 1
if completed_count == #requests then
handler(completed)
end
end)
end
end

return lsp_util
11 changes: 11 additions & 0 deletions lua/java_util/string_util.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
local string_util = {}

function string_util.first_to_upper(str)
return str:gsub("^%l", string.upper)
end

function string_util.starts_with(str, pattern)
return str:find("^" .. pattern) ~= nil
end

return string_util
60 changes: 60 additions & 0 deletions lua/tests/automated/lsp/util_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
local mock = require("luassert.mock")

describe("util", function()
local util = require("java_util.lsp.util")

describe("request_all", function()
local mock_lsp

after_each(function()
mock.revert(mock_lsp)
end)

local function was_called(params, fn)
mock_lsp = mock(vim.lsp, true)
mock_lsp.buf_request.invokes(fn)
local called = false
util.request_all(params, function()
called = true
end)
return called
end

it("given 4 requests when all are succesful then call the callback", function()
local called = was_called({
{ bufnr = 0, method = "textDocument/references", params = {} },
{ bufnr = 0, method = "textDocument/references", params = {} },
{ bufnr = 0, method = "textDocument/references", params = {} },
{ bufnr = 0, method = "textDocument/definition", params = {} },
}, function(_, _, _, fn)
fn()
end)

assert.is_true(called)
end)

it("given 2 requests when one fails then dont call the callback", function()
local count = 0
local called = was_called({
{ bufnr = 0, method = "textDocument/references", params = {} },
{ bufnr = 0, method = "textDocument/definition", params = {} },
}, function(_, _, _, fn)
if count < 1 then
fn()
end
count = count + 1
end)

assert.is_false(called)
end)

it("given 2 requests when all fails then dont call the callback", function()
local called = was_called({
{ bufnr = 0, method = "textDocument/references", params = {} },
{ bufnr = 0, method = "textDocument/definition", params = {} },
}, function(_, _, _, _) end)

assert.is_false(called)
end)
end)
end)
35 changes: 35 additions & 0 deletions scripts/generate_doc.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local docgen = require("docgen")

local docs = {}

docs.test = function()
-- Filepaths that should generate docs
local input_files = {
"./lua/java_util/lsp/init.lua",
}

-- Maybe sort them that depends what you want and need
table.sort(input_files, function(a, b)
return #a < #b
end)

-- Output file
local output_file = "./doc/java_util.txt"
local output_file_handle = io.open(output_file, "w")

for _, input_file in ipairs(input_files) do
docgen.write(input_file, output_file_handle)
end

if not output_file_handle then
return
end

output_file_handle:write(" vim:tw=78:ts=8:ft=help:norl:\n")
output_file_handle:close()
vim.cmd([[checktime]])
end

docs.test()

return docs
7 changes: 7 additions & 0 deletions scripts/minimal_doc.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set rtp+=.

set rtp+=../plenary.nvim
set rtp+=../tree-sitter-lua/

runtime! plugin/plenary.vim
runtime! plugin/ts_lua.vim
2 changes: 1 addition & 1 deletion stylua.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
column_width = 120
column_width = 125
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
Expand Down

0 comments on commit 1defdbc

Please sign in to comment.