-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Mitchell Hanberg <[email protected]>
- Loading branch information
Showing
8 changed files
with
677 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
defmodule NextLS.Commands.ToPipe do | ||
@moduledoc false | ||
import Schematic | ||
|
||
alias GenLSP.Enumerations.ErrorCodes | ||
alias GenLSP.Structures.Position | ||
alias GenLSP.Structures.Range | ||
alias GenLSP.Structures.TextEdit | ||
alias GenLSP.Structures.WorkspaceEdit | ||
alias NextLS.EditHelpers | ||
alias Sourceror.Zipper, as: Z | ||
|
||
defp opts do | ||
map(%{ | ||
position: Position.schematic(), | ||
uri: str(), | ||
text: list(str()) | ||
}) | ||
end | ||
|
||
def run(opts) do | ||
with {:ok, %{text: text, uri: uri, position: position}} <- unify(opts(), Map.new(opts)), | ||
{:ok, ast} = parse(text), | ||
{:ok, {t, m, [argument | rest]} = original} <- get_node(ast, position) do | ||
dbg(original) | ||
range = Sourceror.get_range(original) | ||
dbg(range) | ||
text |> Enum.join("\n") |> NextLS.Commands.ToPipe.decorate(range) |> dbg() | ||
range = make_range(original) | ||
indent = EditHelpers.get_indent(text, range.start.line) | ||
piped = {:|>, [], [argument, {t, m, rest}]} | ||
|
||
%WorkspaceEdit{ | ||
changes: %{ | ||
uri => [ | ||
%TextEdit{ | ||
new_text: | ||
EditHelpers.add_indent_to_edit( | ||
Macro.to_string(piped), | ||
indent | ||
), | ||
range: range | ||
} | ||
] | ||
} | ||
} | ||
else | ||
{:error, message} -> | ||
%GenLSP.ErrorResponse{code: ErrorCodes.parse_error(), message: inspect(message)} | ||
end | ||
end | ||
|
||
defp parse(lines) do | ||
lines | ||
|> Enum.join("\n") | ||
|> Spitfire.parse() | ||
|> case do | ||
{:error, ast, _errors} -> | ||
{:ok, ast} | ||
|
||
other -> | ||
other | ||
end | ||
end | ||
|
||
def decorate(code, range) do | ||
code | ||
|> Sourceror.patch_string([%{range: range, change: &"«#{&1}»"}]) | ||
|> String.trim_trailing() | ||
end | ||
|
||
defp make_range(original_ast) do | ||
range = Sourceror.get_range(original_ast) | ||
|
||
%Range{ | ||
start: %Position{line: range.start[:line] - 1, character: range.start[:column] - 1}, | ||
end: %Position{line: range.end[:line] - 1, character: range.end[:column] - 1} | ||
} | ||
end | ||
|
||
def get_node(ast, pos) do | ||
pos = [line: pos.line + 1, column: pos.character + 1] | ||
|
||
result = | ||
ast | ||
|> Z.zip() | ||
|> Z.traverse(nil, fn tree, acc -> | ||
node = Z.node(tree) | ||
range = Sourceror.get_range(node) | ||
|
||
if not is_nil(range) and | ||
(match?({{:., _, _}, _, [_ | _]}, node) or | ||
match?({t, _, [_ | _]} when t not in [:., :__aliases__], node)) do | ||
if Sourceror.compare_positions(range.start, pos) == :lt && | ||
Sourceror.compare_positions(range.end, pos) == :gt do | ||
{tree, node} | ||
else | ||
{tree, acc} | ||
end | ||
else | ||
{tree, acc} | ||
end | ||
end) | ||
|
||
case result do | ||
{_, nil} -> | ||
{:error, "could not find an argument to extract at the cursor position"} | ||
|
||
{_, {_t, _m, []}} -> | ||
{:error, "could not find an argument to extract at the cursor position"} | ||
|
||
{_, {_t, _m, [_argument | _rest]} = node} -> | ||
{:ok, node} | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
defmodule NextLS.EditHelpers do | ||
@moduledoc false | ||
|
||
@doc """ | ||
This adds indentation to all lines except the first since the LSP expects a range for edits, | ||
where we get the range with the already original indentation for starters. | ||
It also skips empty lines since they don't need indentation. | ||
""" | ||
@spec add_indent_to_edit(text :: String.t(), indent :: String.t()) :: String.t() | ||
@blank_lines ["", "\n"] | ||
def add_indent_to_edit(text, indent) do | ||
[first | rest] = String.split(text, "\n") | ||
|
||
if rest != [] do | ||
indented = | ||
Enum.map_join(rest, "\n", fn line -> | ||
if line not in @blank_lines do | ||
indent <> line | ||
else | ||
line | ||
end | ||
end) | ||
|
||
first <> "\n" <> indented | ||
else | ||
first | ||
end | ||
end | ||
|
||
@doc """ | ||
Gets the indentation level at the line number desired | ||
""" | ||
@spec get_indent(text :: [String.t()], line :: non_neg_integer()) :: String.t() | ||
def get_indent(text, line) do | ||
text | ||
|> Enum.at(line) | ||
|> then(&Regex.run(~r/^(\s*).*/, &1)) | ||
|> List.last() | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
defmodule NextLS.Commands.PipeTest do | ||
use ExUnit.Case, async: true | ||
|
||
import GenLSP.Test | ||
import NextLS.Support.Utils | ||
|
||
@moduletag :tmp_dir | ||
@moduletag root_paths: ["my_proj"] | ||
|
||
setup %{tmp_dir: tmp_dir} do | ||
File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib")) | ||
File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), mix_exs()) | ||
|
||
cwd = Path.join(tmp_dir, "my_proj") | ||
|
||
foo_path = Path.join(cwd, "lib/foo.ex") | ||
|
||
foo = """ | ||
defmodule Foo do | ||
def to_list() do | ||
Enum.to_list(Map.new()) | ||
end | ||
end | ||
""" | ||
|
||
File.write!(foo_path, foo) | ||
|
||
bar_path = Path.join(cwd, "lib/bar.ex") | ||
|
||
bar = """ | ||
defmodule Bar do | ||
def to_list() do | ||
Map.new() |> Enum.to_list() | ||
end | ||
end | ||
""" | ||
|
||
File.write!(bar_path, bar) | ||
|
||
[foo: foo, foo_path: foo_path, bar: bar, bar_path: bar_path] | ||
end | ||
|
||
setup :with_lsp | ||
|
||
setup context do | ||
assert :ok == notify(context.client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) | ||
assert_is_ready(context, "my_proj") | ||
assert_compiled(context, "my_proj") | ||
assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} | ||
|
||
did_open(context.client, context.foo_path, context.foo) | ||
did_open(context.client, context.bar_path, context.bar) | ||
context | ||
end | ||
|
||
test "transforms nested function expressions to pipes", %{client: client, foo_path: foo} do | ||
foo_uri = uri(foo) | ||
id = 1 | ||
|
||
request client, %{ | ||
method: "workspace/executeCommand", | ||
id: id, | ||
jsonrpc: "2.0", | ||
params: %{ | ||
command: "to-pipe", | ||
arguments: [%{uri: foo_uri, position: %{line: 2, character: 19}}] | ||
} | ||
} | ||
|
||
assert_request(client, "workspace/applyEdit", 500, fn params -> | ||
assert %{"edit" => edit, "label" => "Pipe"} = params | ||
|
||
assert %{ | ||
"changes" => %{ | ||
^foo_uri => [%{"newText" => text, "range" => range}] | ||
} | ||
} = edit | ||
|
||
expected = "Map.new() |> Enum.to_list()" | ||
assert text == expected | ||
assert range["start"] == %{"character" => 4, "line" => 2} | ||
assert range["end"] == %{"character" => 27, "line" => 2} | ||
end) | ||
end | ||
end |
Oops, something went wrong.