From df44481ad2b5e9ca820dcbba16b5a441a79c3da6 Mon Sep 17 00:00:00 2001 From: Rin Date: Tue, 7 May 2024 13:11:33 +0800 Subject: [PATCH 1/4] fix: offset column by 1 to make goto def/refs work when cursor is at end of symbol (#1038) Co-authored-by: Cocoa --- .../lib/language_server/providers/definition.ex | 10 +++++++++- .../lib/language_server/providers/references.ex | 13 ++++++++++++- .../providers/references/locator.ex | 14 +++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/apps/language_server/lib/language_server/providers/definition.ex b/apps/language_server/lib/language_server/providers/definition.ex index 25ea03bf1..ddf972c3b 100644 --- a/apps/language_server/lib/language_server/providers/definition.ex +++ b/apps/language_server/lib/language_server/providers/definition.ex @@ -16,7 +16,15 @@ defmodule ElixirLS.LanguageServer.Providers.Definition do result = case Locator.definition(source_file.text, line, character, metadata: metadata) do nil -> - nil + case Locator.definition(source_file.text, line, max(character - 1, 1), + metadata: metadata + ) do + nil -> + nil + + %ElixirLS.LanguageServer.Location{} = location -> + Protocol.Location.new(location, uri, source_file.text, project_dir) + end %ElixirLS.LanguageServer.Location{} = location -> Protocol.Location.new(location, uri, source_file.text, project_dir) diff --git a/apps/language_server/lib/language_server/providers/references.ex b/apps/language_server/lib/language_server/providers/references.ex index e03302128..830abf48e 100644 --- a/apps/language_server/lib/language_server/providers/references.ex +++ b/apps/language_server/lib/language_server/providers/references.ex @@ -25,7 +25,18 @@ defmodule ElixirLS.LanguageServer.Providers.References do Build.with_build_lock(fn -> trace = ElixirLS.LanguageServer.Tracer.get_trace() - Locator.references(source_file.text, line, character, trace, metadata: metadata) + references = + case Locator.references(source_file.text, line, character, trace, metadata: metadata) do + [] -> + Locator.references(source_file.text, line, max(character - 1, 1), trace, + metadata: metadata + ) + + references -> + references + end + + references |> Enum.map(fn elixir_sense_reference -> elixir_sense_reference |> build_reference(uri, source_file.text, project_dir) diff --git a/apps/language_server/lib/language_server/providers/references/locator.ex b/apps/language_server/lib/language_server/providers/references/locator.ex index 5e96a6724..b96b39e32 100644 --- a/apps/language_server/lib/language_server/providers/references/locator.ex +++ b/apps/language_server/lib/language_server/providers/references/locator.ex @@ -20,7 +20,19 @@ defmodule ElixirLS.LanguageServer.Providers.References.Locator do alias ElixirSense.Core.Parser def references(code, line, column, trace, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do + {context, column} = + case NormalizedCode.Fragment.surround_context(code, {line, column}) do + :none -> + {:none, column} + + %{context: {:dot, _, _}} -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}), column - 1} + + context -> + {context, column} + end + + case context do :none -> [] From 7ca7d95e921368ece85ffb5d732a68ba9e0705c5 Mon Sep 17 00:00:00 2001 From: Rin Date: Tue, 7 May 2024 17:23:05 +0800 Subject: [PATCH 2/4] wrap `surround_context/3` --- .../lib/language_server/ast_utils.ex | 16 ++++++++++++++ .../language_server/providers/definition.ex | 10 +-------- .../providers/definition/locator.ex | 8 +++---- .../language_server/providers/hover/docs.ex | 8 ++++--- .../providers/implementation/locator.ex | 7 +++--- .../language_server/providers/references.ex | 13 +---------- .../providers/references/locator.ex | 22 +++++-------------- 7 files changed, 36 insertions(+), 48 deletions(-) diff --git a/apps/language_server/lib/language_server/ast_utils.ex b/apps/language_server/lib/language_server/ast_utils.ex index d9c0305f3..6cde73ce0 100644 --- a/apps/language_server/lib/language_server/ast_utils.ex +++ b/apps/language_server/lib/language_server/ast_utils.ex @@ -7,6 +7,7 @@ defmodule ElixirLS.LanguageServer.AstUtils do import ElixirLS.LanguageServer.Protocol alias ElixirLS.LanguageServer.SourceFile + alias ElixirSense.Core.Normalized.Code, as: NormalizedCode @binary_operators ~w[| . ** * / + - ++ -- +++ --- .. <> in |> <<< >>> <<~ ~>> <~ ~> <~> < > <= >= == != === !== =~ && &&& and || ||| or = => :: when <- -> \\]a @unary_operators ~w[@ + - ! ^ not &]a @@ -412,4 +413,19 @@ defmodule ElixirLS.LanguageServer.AstUtils do {line + length(lines) - 1, String.length(last_line)} end end + + def surround_context_with_fallback(code, {line, column}, options \\ []) do + case NormalizedCode.Fragment.surround_context(code, {line, column}, options) do + :none -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), + column - 1} + + %{context: {:dot, _, _}} -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), + column - 1} + + context -> + {context, column} + end + end end diff --git a/apps/language_server/lib/language_server/providers/definition.ex b/apps/language_server/lib/language_server/providers/definition.ex index ddf972c3b..25ea03bf1 100644 --- a/apps/language_server/lib/language_server/providers/definition.ex +++ b/apps/language_server/lib/language_server/providers/definition.ex @@ -16,15 +16,7 @@ defmodule ElixirLS.LanguageServer.Providers.Definition do result = case Locator.definition(source_file.text, line, character, metadata: metadata) do nil -> - case Locator.definition(source_file.text, line, max(character - 1, 1), - metadata: metadata - ) do - nil -> - nil - - %ElixirLS.LanguageServer.Location{} = location -> - Protocol.Location.new(location, uri, source_file.text, project_dir) - end + nil %ElixirLS.LanguageServer.Location{} = location -> Protocol.Location.new(location, uri, source_file.text, project_dir) diff --git a/apps/language_server/lib/language_server/providers/definition/locator.ex b/apps/language_server/lib/language_server/providers/definition/locator.ex index 60cc0e3ce..924fb4378 100644 --- a/apps/language_server/lib/language_server/providers/definition/locator.ex +++ b/apps/language_server/lib/language_server/providers/definition/locator.ex @@ -25,14 +25,14 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.Locator do alias ElixirSense.Core.Parser alias ElixirLS.LanguageServer.Plugins.Phoenix.Scope - alias ElixirSense.Core.Normalized.Code, as: NormalizedCode + alias ElixirLS.LanguageServer.AstUtils def definition(code, line, column, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case AstUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> nil - context -> + {context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/lib/language_server/providers/hover/docs.ex b/apps/language_server/lib/language_server/providers/hover/docs.ex index 99b1aa41b..f2f90a652 100644 --- a/apps/language_server/lib/language_server/providers/hover/docs.ex +++ b/apps/language_server/lib/language_server/providers/hover/docs.ex @@ -21,6 +21,8 @@ defmodule ElixirLS.LanguageServer.Providers.Hover.Docs do alias ElixirSense.Core.TypeInfo alias ElixirSense.Core.Parser + alias ElixirLS.LanguageServer.AstUtils + @type markdown :: String.t() @type module_doc :: %{kind: :module, docs: markdown, metadata: map, module: module()} @@ -71,11 +73,11 @@ defmodule ElixirLS.LanguageServer.Providers.Hover.Docs do |> Kernel.--([:exception, :message]) def docs(code, line, column, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case AstUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> nil - %{begin: begin_pos, end: end_pos} = context -> + {%{begin: begin_pos, end: end_pos} = context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/lib/language_server/providers/implementation/locator.ex b/apps/language_server/lib/language_server/providers/implementation/locator.ex index 78b28f8bc..ef198d59d 100644 --- a/apps/language_server/lib/language_server/providers/implementation/locator.ex +++ b/apps/language_server/lib/language_server/providers/implementation/locator.ex @@ -19,15 +19,16 @@ defmodule ElixirLS.LanguageServer.Providers.Implementation.Locator do alias ElixirLS.LanguageServer.Location alias ElixirSense.Core.Parser alias ElixirSense.Core.Normalized.Code, as: NormalizedCode + alias ElixirLS.LanguageServer.AstUtils require ElixirSense.Core.Introspection, as: Introspection def implementations(code, line, column, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> + case AstUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> [] - context -> + {context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) diff --git a/apps/language_server/lib/language_server/providers/references.ex b/apps/language_server/lib/language_server/providers/references.ex index 830abf48e..e03302128 100644 --- a/apps/language_server/lib/language_server/providers/references.ex +++ b/apps/language_server/lib/language_server/providers/references.ex @@ -25,18 +25,7 @@ defmodule ElixirLS.LanguageServer.Providers.References do Build.with_build_lock(fn -> trace = ElixirLS.LanguageServer.Tracer.get_trace() - references = - case Locator.references(source_file.text, line, character, trace, metadata: metadata) do - [] -> - Locator.references(source_file.text, line, max(character - 1, 1), trace, - metadata: metadata - ) - - references -> - references - end - - references + Locator.references(source_file.text, line, character, trace, metadata: metadata) |> Enum.map(fn elixir_sense_reference -> elixir_sense_reference |> build_reference(uri, source_file.text, project_dir) diff --git a/apps/language_server/lib/language_server/providers/references/locator.ex b/apps/language_server/lib/language_server/providers/references/locator.ex index b96b39e32..7e5038483 100644 --- a/apps/language_server/lib/language_server/providers/references/locator.ex +++ b/apps/language_server/lib/language_server/providers/references/locator.ex @@ -19,26 +19,14 @@ defmodule ElixirLS.LanguageServer.Providers.References.Locator do alias ElixirSense.Core.SurroundContext alias ElixirSense.Core.Parser - def references(code, line, column, trace, options \\ []) do - {context, column} = - case NormalizedCode.Fragment.surround_context(code, {line, column}) do - :none -> - {:none, column} - - %{context: {:dot, _, _}} -> - {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}), column - 1} + alias ElixirLS.LanguageServer.AstUtils - context -> - {context, column} - end - - case context do - :none -> + def references(code, line, column, trace, options \\ []) do + case AstUtils.surround_context_with_fallback(code, {line, column}) do + {:none, _} -> [] - %{ - begin: {begin_line, begin_col} - } = context -> + {%{begin: {begin_line, begin_col}} = context, column} -> metadata = Keyword.get_lazy(options, :metadata, fn -> Parser.parse_string(code, true, true, {line, column}) From 6e2d46176d7d81f813733440df898c9f776018a0 Mon Sep 17 00:00:00 2001 From: Rin Date: Tue, 7 May 2024 21:40:08 +0800 Subject: [PATCH 3/4] test (wip) --- apps/language_server/test/server_test.exs | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/apps/language_server/test/server_test.exs b/apps/language_server/test/server_test.exs index 9c04ad05e..372e75177 100644 --- a/apps/language_server/test/server_test.exs +++ b/apps/language_server/test/server_test.exs @@ -1384,6 +1384,70 @@ defmodule ElixirLS.LanguageServer.ServerTest do wait_until_compiled(server) end) end + + test "definition at end of symbol", %{server: server} do + in_fixture(__DIR__, "clean", fn -> + uri = "file:///file.ex" + code = ~S( + defmodule Test do + def test, do: nil + end + defmodule OtherModule do + def test do + Test + Test.test(\) + _ = &Test.test/0 + end + end + ) + fake_initialize(server) + Server.receive_packet(server, did_open(uri, "elixir", 1, code)) + Server.receive_packet(server, definition_req(1, uri, 6, 17)) + + assert_receive( + response(1, %{ + "range" => %{ + "end" => %{"character" => 8, "line" => 1}, + "start" => %{"character" => 8, "line" => 1} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + + Server.receive_packet(server, definition_req(2, uri, 7, 17)) + + assert_receive( + response(2, %{ + "range" => %{ + "end" => %{"character" => 8, "line" => 1}, + "start" => %{"character" => 8, "line" => 1} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + + Server.receive_packet(server, definition_req(3, uri, 8, 22)) + + assert_receive( + response(3, %{ + "range" => %{ + "end" => %{"character" => 8, "line" => 1}, + "start" => %{"character" => 8, "line" => 1} + }, + "uri" => ^uri + }), + 3000 + ) + + wait_until_compiled(server) + end) + end end describe "textDocument/implementation" do From b768edce472b12a41791f7d1e65c198fadb3e39b Mon Sep 17 00:00:00 2001 From: Rin Date: Tue, 11 Jun 2024 13:07:35 +0100 Subject: [PATCH 4/4] move `surround_context/3` to `CodeFragmentUtils` --- .../lib/language_server/ast_utils.ex | 16 ---------------- .../lib/language_server/codefragment_utils.ex | 18 ++++++++++++++++++ .../providers/definition/locator.ex | 4 ++-- .../language_server/providers/hover/docs.ex | 4 ++-- .../providers/implementation/locator.ex | 4 ++-- .../providers/references/locator.ex | 4 ++-- 6 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 apps/language_server/lib/language_server/codefragment_utils.ex diff --git a/apps/language_server/lib/language_server/ast_utils.ex b/apps/language_server/lib/language_server/ast_utils.ex index 6cde73ce0..d9c0305f3 100644 --- a/apps/language_server/lib/language_server/ast_utils.ex +++ b/apps/language_server/lib/language_server/ast_utils.ex @@ -7,7 +7,6 @@ defmodule ElixirLS.LanguageServer.AstUtils do import ElixirLS.LanguageServer.Protocol alias ElixirLS.LanguageServer.SourceFile - alias ElixirSense.Core.Normalized.Code, as: NormalizedCode @binary_operators ~w[| . ** * / + - ++ -- +++ --- .. <> in |> <<< >>> <<~ ~>> <~ ~> <~> < > <= >= == != === !== =~ && &&& and || ||| or = => :: when <- -> \\]a @unary_operators ~w[@ + - ! ^ not &]a @@ -413,19 +412,4 @@ defmodule ElixirLS.LanguageServer.AstUtils do {line + length(lines) - 1, String.length(last_line)} end end - - def surround_context_with_fallback(code, {line, column}, options \\ []) do - case NormalizedCode.Fragment.surround_context(code, {line, column}, options) do - :none -> - {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), - column - 1} - - %{context: {:dot, _, _}} -> - {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), - column - 1} - - context -> - {context, column} - end - end end diff --git a/apps/language_server/lib/language_server/codefragment_utils.ex b/apps/language_server/lib/language_server/codefragment_utils.ex new file mode 100644 index 000000000..c80943d70 --- /dev/null +++ b/apps/language_server/lib/language_server/codefragment_utils.ex @@ -0,0 +1,18 @@ +defmodule ElixirLS.LanguageServer.CodeFragmentUtils do + alias ElixirSense.Core.Normalized.Code, as: NormalizedCode + + def surround_context_with_fallback(code, {line, column}, options \\ []) do + case NormalizedCode.Fragment.surround_context(code, {line, column}, options) do + :none -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), + column - 1} + + %{context: {:dot, _, _}} -> + {NormalizedCode.Fragment.surround_context(code, {line, max(column - 1, 1)}, options), + column - 1} + + context -> + {context, column} + end + end +end diff --git a/apps/language_server/lib/language_server/providers/definition/locator.ex b/apps/language_server/lib/language_server/providers/definition/locator.ex index 924fb4378..0b2a0db52 100644 --- a/apps/language_server/lib/language_server/providers/definition/locator.ex +++ b/apps/language_server/lib/language_server/providers/definition/locator.ex @@ -25,10 +25,10 @@ defmodule ElixirLS.LanguageServer.Providers.Definition.Locator do alias ElixirSense.Core.Parser alias ElixirLS.LanguageServer.Plugins.Phoenix.Scope - alias ElixirLS.LanguageServer.AstUtils + alias ElixirLS.LanguageServer.CodeFragmentUtils def definition(code, line, column, options \\ []) do - case AstUtils.surround_context_with_fallback(code, {line, column}) do + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do {:none, _} -> nil diff --git a/apps/language_server/lib/language_server/providers/hover/docs.ex b/apps/language_server/lib/language_server/providers/hover/docs.ex index f2f90a652..65fe19f30 100644 --- a/apps/language_server/lib/language_server/providers/hover/docs.ex +++ b/apps/language_server/lib/language_server/providers/hover/docs.ex @@ -21,7 +21,7 @@ defmodule ElixirLS.LanguageServer.Providers.Hover.Docs do alias ElixirSense.Core.TypeInfo alias ElixirSense.Core.Parser - alias ElixirLS.LanguageServer.AstUtils + alias ElixirLS.LanguageServer.CodeFragmentUtils @type markdown :: String.t() @@ -73,7 +73,7 @@ defmodule ElixirLS.LanguageServer.Providers.Hover.Docs do |> Kernel.--([:exception, :message]) def docs(code, line, column, options \\ []) do - case AstUtils.surround_context_with_fallback(code, {line, column}) do + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do {:none, _} -> nil diff --git a/apps/language_server/lib/language_server/providers/implementation/locator.ex b/apps/language_server/lib/language_server/providers/implementation/locator.ex index ef198d59d..8aed79dad 100644 --- a/apps/language_server/lib/language_server/providers/implementation/locator.ex +++ b/apps/language_server/lib/language_server/providers/implementation/locator.ex @@ -19,12 +19,12 @@ defmodule ElixirLS.LanguageServer.Providers.Implementation.Locator do alias ElixirLS.LanguageServer.Location alias ElixirSense.Core.Parser alias ElixirSense.Core.Normalized.Code, as: NormalizedCode - alias ElixirLS.LanguageServer.AstUtils + alias ElixirLS.LanguageServer.CodeFragmentUtils require ElixirSense.Core.Introspection, as: Introspection def implementations(code, line, column, options \\ []) do - case AstUtils.surround_context_with_fallback(code, {line, column}) do + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do {:none, _} -> [] diff --git a/apps/language_server/lib/language_server/providers/references/locator.ex b/apps/language_server/lib/language_server/providers/references/locator.ex index 7e5038483..eb8623f70 100644 --- a/apps/language_server/lib/language_server/providers/references/locator.ex +++ b/apps/language_server/lib/language_server/providers/references/locator.ex @@ -19,10 +19,10 @@ defmodule ElixirLS.LanguageServer.Providers.References.Locator do alias ElixirSense.Core.SurroundContext alias ElixirSense.Core.Parser - alias ElixirLS.LanguageServer.AstUtils + alias ElixirLS.LanguageServer.CodeFragmentUtils def references(code, line, column, trace, options \\ []) do - case AstUtils.surround_context_with_fallback(code, {line, column}) do + case CodeFragmentUtils.surround_context_with_fallback(code, {line, column}) do {:none, _} -> []