Skip to content

Commit

Permalink
Merge branch 'lexical-lsp:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
NickNeck authored Jan 16, 2024
2 parents c7f08a1 + ffff80c commit c33936d
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 6 deletions.
2 changes: 1 addition & 1 deletion apps/common/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule Common.MixProject do
{:lexical_shared, path: "../../projects/lexical_shared"},
{:lexical_test, path: "../../projects/lexical_test", only: :test},
{:snowflake, "~> 1.0"},
{:sourceror, "~> 0.14.1"},
{:sourceror, "~> 1.0"},
{:stream_data, "~> 0.6", only: [:test], runtime: false},
{:patch, "~> 0.12", only: [:test], optional: true, runtime: false}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ defmodule Lexical.RemoteControl.Search.Indexer.Metadata do
@moduledoc """
Utilities for extracting location information from AST metadata nodes.
"""
def location({_, metadata, _}) do
def location({_, metadata, _} = ast) do
if Keyword.has_key?(metadata, :do) do
position = position(metadata)
block_start = position(metadata, :do)
block_end = position(metadata, :end_of_expression) || position(metadata, :end)
{:block, position, block_start, block_end}
else
{:expression, position(metadata)}
maybe_handle_terse_function(ast)
end
end

Expand All @@ -35,4 +35,51 @@ defmodule Lexical.RemoteControl.Search.Indexer.Metadata do
|> Keyword.get(key, [])
|> position()
end

@defines [:def, :defp, :defmacro, :defmacrop]
# a terse function is one without a do/end block
defp maybe_handle_terse_function({define, metadata, [_name_and_args | [[block | _]]]})
when define in @defines do
case Sourceror.get_range(block) do
%{start: start_pos, end: end_pos} ->
position = position(metadata)

{:block, position, keyword_to_position(start_pos), keyword_to_position(end_pos)}

_ ->
{:expression, position(metadata)}
end
end

defp maybe_handle_terse_function({:fn, metadata, _} = ast) do
case Sourceror.get_range(ast) do
%{start: start_pos, end: end_pos} ->
position = position(metadata)
[line: line, column: column] = Keyword.take(end_pos, [:line, :column])

# Sourceror puts block ends after the ending token (usually `end`),
# but the rest of our code places it right before the end token.
# We should investigate just doing what sourceror does, it makes sense.
end_position = {line, column - 3}

{:block, position, keyword_to_position(start_pos), end_position}

_ ->
{:expression, position(metadata)}
end
end

defp maybe_handle_terse_function({_, metadata, _}) do
{:expression, position(metadata)}
end

defp keyword_to_position(keyword) do
case Keyword.take(keyword, [:line, :column]) do
[line: line, column: column] when is_number(line) and is_number(column) ->
{line, column}

_ ->
nil
end
end
end
2 changes: 1 addition & 1 deletion apps/remote_control/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ defmodule Lexical.RemoteControl.MixProject do
{:patch, "~> 0.12", only: [:dev, :test], optional: true, runtime: false},
{:path_glob, "~> 0.2", optional: true},
{:phoenix_live_view, "~> 0.19.5", only: [:test], optional: true, runtime: false},
{:sourceror, "~> 0.14.1"}
{:sourceror, "~> 1.0"}
]
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
defmodule Lexical.RemoteControl.Search.Indexer.MetadataTest do
alias Lexical.Ast
alias Lexical.Document
alias Lexical.Document.Position
alias Lexical.Document.Range
alias Lexical.RemoteControl.Search.Indexer.Metadata

use ExUnit.Case

import Lexical.Test.RangeSupport
import Lexical.Test.CodeSigil

describe "blocks in modules" do
test "finds a block in an empty module" do
code = ~q[
defmodule MyModule do
end
]t

assert "defmodule MyModule «do\n»end" == decorate_location(code)
end

test "finds a block in a module with an attribute" do
code = ~q[
defmodule WithAttribute do
@foo 32
end
]t

assert "defmodule WithAttribute «do\n @foo 32\n»end" = decorate_location(code)
end

test "finds a block in a module with functions" do
code = ~q[
defmodule WithFunctions do
def fun do
end
end
]t

expected = ~q[
defmodule WithFunctions «do
def fun do
end
»end
]t
assert expected == decorate_location(code)
end
end

describe "blocks in functions" do
test "are found in a public function with do/end and no body" do
code = ~q[
def my_fn do
end
]
assert "def my_fn «do\n»end" = decorate_location(code)
end

test "are found in a private function with do/end and no body" do
code = ~q[
defp my_fn do
end
]
assert "defp my_fn «do\n»end" = decorate_location(code)
end

test "are found in a public function with do/end and abody" do
code = ~q[
def my_fn do
x = 4
x * 6
end
]t

expected = ~q[
def my_fn «do
x = 4
x * 6
»end
]t

assert expected == decorate_location(code)
end

test "are found in a private function with do/end and abody" do
code = ~q[
defp my_fn do
x = 4
x * 6
end
]t

expected = ~q[
defp my_fn «do
x = 4
x * 6
»end
]t

assert expected == decorate_location(code)
end

test "are found in single line terse public function" do
code = ~q[
def my_fn(arg), do: arg * 10
]t

assert "def my_fn(arg), «do: arg * 10»" = decorate_location(code)
end

test "are found in single line terse private function" do
code = ~q[
defp my_fn(arg), do: arg * 10
]t

assert "defp my_fn(arg), «do: arg * 10»" = decorate_location(code)
end

test "are found in single line terse public macro" do
code = ~q[
defmacro my_fn(arg), do: arg * 10
]t

assert "defmacro my_fn(arg), «do: arg * 10»" = decorate_location(code)
end

test "are found in single line terse private macro" do
code = ~q[
defmacrop my_fn(arg), do: arg * 10
]t

assert "defmacrop my_fn(arg), «do: arg * 10»" = decorate_location(code)
end

test "are found in multiple line terse public function" do
code = ~q[
def my_fun(arg),
do: arg + 1
]

assert " «do: arg + 1»" == decorate_location(code)
end

test "are found in multiple line terse private function" do
code = ~q[
defp my_fun(arg),
do: arg + 1
]

assert " «do: arg + 1»" == decorate_location(code)
end

test "are found in anonymous functions with no body" do
code = "fn -> :ok end"
assert "«fn -> :ok »end" == decorate_location(code)
end

test "are found in single line anonymous functions" do
code = "fn arg -> arg + 1 end"
assert "«fn arg -> arg + 1 »end" == decorate_location(code)
end

test "are found in multiple line anonymous functions" do
code = ~q[
fn a, b ->
y = do_something_with(a)
z = do_something_with(b)
y + z
end
]

expected = ~q[
«fn a, b ->
y = do_something_with(a)
z = do_something_with(b)
y + z
»end
]t

assert expected == decorate_location(code)
end
end

defp decorate_location(code) do
document = Document.new("file:///file.ex", code, 1)
{:ok, ast} = Ast.from(document)

case Metadata.location(ast) do
{:block, _position, {start_line, start_char}, {end_line, end_char}} ->
range =
Range.new(
Position.new(document, start_line, start_char),
Position.new(document, end_line, end_char)
)

decorate(code, range)
end
end
end
2 changes: 1 addition & 1 deletion apps/server/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ defmodule Lexical.Server.MixProject do
{:path_glob, "~> 0.2"},
{:protocol, in_umbrella: true},
{:remote_control, in_umbrella: true, runtime: false},
{:sourceror, "~> 0.14.1"}
{:sourceror, "~> 1.0"}
]
end
end
2 changes: 1 addition & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
"plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
"snowflake": {:hex, :snowflake, "1.0.4", "8433b4e04fbed19272c55e1b7de0f7a1ee1230b3ae31a813b616fd6ef279e87a", [:mix], [], "hexpm", "badb07ebb089a5cff737738297513db3962760b10fe2b158ae3bebf0b4d5be13"},
"sourceror": {:hex, :sourceror, "0.14.1", "c6fb848d55bd34362880da671debc56e77fd722fa13b4dcbeac89a8998fc8b09", [:mix], [], "hexpm", "8b488a219e4c4d7d9ff29d16346fd4a5858085ccdd010e509101e226bbfd8efc"},
"sourceror": {:hex, :sourceror, "1.0.1", "ec2c41726d181adce888ac94b3f33b359a811b46e019c084509e02c70042e424", [:mix], [], "hexpm", "28225464ffd68bda1843c974f3ff7ccef35e29be09a65dfe8e3df3f7e3600c57"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
Expand Down

0 comments on commit c33936d

Please sign in to comment.