-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: expose manipulate pipes command #521
fix: expose manipulate pipes command #521
Conversation
It seems that this fix is needed: elixir-lang/elixir#10864 (comment) |
So, a commit was added changing this behavior in Elixir. Due to the fact that we need to be retrocompatible, I've added a function to treat added escapes whenever they are added. The main assumption is that |
@@ -64,4 +64,17 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes.AST d | |||
defp do_to_pipe(node, acc) do | |||
{node, acc} | |||
end | |||
|
|||
defp fix_escape_chars(parsed, original) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fix doesn't work properly because sigil delimiters changed to "
prior to 1.10
Some parsing of the AST will be needed to treat sigils
@@ -64,4 +64,77 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes.AST d | |||
defp do_to_pipe(node, acc) do | |||
{node, acc} | |||
end | |||
|
|||
def ast_to_string(ast) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an attempt to handle escape characters as expected. Local tests passes successfully, but I'm not sure if this is the best way to approach the problem
I've been trying this out a bit with (https://github.com/elixir-lsp/vscode-elixir-ls/pull/182/files) and I'm running into some issues. It appears that sometimes the extracted text . Here's a partial failing test (partial because it should check the result): test "can handle multiple calls" do
{:ok, _} =
JsonRpcMock.start_link(success_reply: {:ok, %{"applied" => true}}, test_pid: self())
uri = "file:/some_file.ex"
text = """
defmodule BasicEx do
def add(a) do
require Logger
Logger.info(to_string(add_num(a, 12)))
end
def add_num(a, num), do: a + num
end
"""
assert {:ok, nil} =
ManipulatePipes.execute(
["fromPipe", uri, 4, 16],
%Server{
source_files: %{
uri => %SourceFile{
text: text
}
}
}
)
That test results in:
Where I would expect the value of |
@axelson could you elaborate on "once with each character position"? Do you mean something like The command is currently highly dependent on where the cursor is because there can be multiple function calls and multple pipes in a line. Anyway, your case is indeed a bug and I'll investigate on what's causing it |
@@ -230,7 +238,11 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do | |||
col + tail_length | |||
end | |||
|
|||
{:ok, pipe_call, range(start_line, start_col, end_line, end_col)} | |||
if String.contains?(pipe_call, "|>") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@axelson I've added this because your test actually was calling "fromPipe", which shouldn't do anything if there aren't any pipes in the selected text
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another possible option which would require some rework is to scan until a pipe is found, but this could have unexpected results
@@ -280,7 +292,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipes do | |||
|> String.reverse() | |||
|> String.graphemes() | |||
|> Enum.reduce_while([], fn c, acc -> | |||
if String.match?(c, ~r/\s/) do | |||
if String.match?(c, ~r/[\s\(\[\{]/) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was needed if the suggested test is changed to "toPipe", because Logger.info(to_string(...)
doesn't have any spaces, but the first (
actually breaks the identifier to_string
"start" => %{"character" => 16, "line" => 4} | ||
} | ||
|
||
expected_substitution = "to_string(a |> add_num(12))" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one might bring up some discussion. The way that our macro-walking function is implemented, the code infers that the innermost function call is the one that should be piped.
Does this need to be reworked so the outermost one is the piped call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that would be preferred. On the other hand this is an ambiguous case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lukaszsamson I've fixed this in the latest commit. The issue was that there was a and t != []
clause which disallowed 1-arg functions from being piped. No other tests broke, so that's a good sign, I guess
Yeah,
Yeah, makes sense 👍 |
@@ -760,4 +889,42 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand.ManipulatePipesTest d | |||
) == edited_text | |||
end | |||
end | |||
|
|||
defp assert_never_raises(text, uri, command) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@axelson I've added this helper assertion that travels through the whole text calling the corresponding command ("toPipe" or "fromPipe"). This should ensure that no position in each test file raises in the code. Some bugs have been found and fixed this way already :)
# fix which was introduced in | ||
# https://github.com/elixir-lang/elixir/commit/88d82f059756ed1eb56a562fae7092f77d941de8 | ||
# is needed for proper treatment of sigils in our use-case. | ||
defp sigil_call({sigil, meta, [{:<<>>, _, _} = parts, args]} = ast, fun) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a TODO with info which part may be removed when we require elixir 1.13 (or 1.12 if the fix gets backported)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, taking a deeper look at this. Sorry for the delay.
I have a high-level comment, that I wish I had flagged earlier. Given this snippet:
Ash.Changeset.for_create(Track, :create, data)
|> GenTracker.API.create()
Running toPipe
on the first line I would expect to end up with
Track
|> Ash.Changeset.for_create(:create, data)
|> GenTracker.API.create()
But instead end up with
Track |> Ash.Changeset.for_create(:create, data)
|> GenTracker.API.create()
But I suppose this would be hard to handle for code that has multiple function calls on one line, so perhaps it's best handled as a future improvement.
Also (with the above code) I would expect the same result no matter where on the first line I called toPipe
, but instead there's a bug where if you call toPipe
at the end of the line you end up with:
Ash.Changeset.for_create(Track, :create, data)GenTracker.API.create()
Which is obviously wrong and does not compile.
to_pipe_at_cursor
and from_pipe_at_cursor
suggested changes:
- For easier testing I think we should make them public functions and call them from the tests.
- I think we should have those functions return a
%ElixirLS.LanguageServer.Protocol.TextEdit{}
(which needs to be created) instead a raw map - Then those two can be combined with
ElixirLS.LanguageServer.Test.TestUtils.apply_text_edit/2
to greatly simplify the tests - I have some code related to this that I hope to post soon
Thanks for the review! About changing multi-line to single-line pipes, I'd rather leave that as a future improvement, since most [citation needed] people that will be using this command will also be using "format on save" or similiar features.
Nice catch! I'll add a test and a fix for this as soon as I can get back to this PR.
Which tests are you talking about? The integration ones, which receive the command itself?
Sounds good!
Should I wait for said code? |
Make `to_pipe_at_cursor/3` public to facilitate testing. I think we should still keep some integration-level testing, but I think the majority of the test cases can be simplified. I didn't implement making `from_pipe_at_cursor/3` public, but I think it would make sense as well. Introduce `ElixirLS.LanguageServer.Protocol.TextEdit` and return it from `to_pipe_at_cursor/3` and `from_pipe_at_cursor/3` Add an example of a working simpler test, and add a few examples of broken tests (as mentioned in elixir-lsp#521 (review))
…d-changes Proposed changes
defp get_function_call(line, col, head, cur, original_tail) when cur in ["\n", "\r", "\r\n"] do | ||
{head, new_cur} = String.split_at(head, -1) | ||
get_function_call(line, col - 1, head, new_cur, cur <> original_tail) | ||
end | ||
|
||
defp get_function_call(line, col, head, ")", original_tail) do | ||
{head, cur} = String.split_at(head, -1) | ||
get_function_call(line, col - 1, head, cur, ")" <> original_tail) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@axelson here's what caused the tests you added to break: do to how do_get_function_call
works, when we're at a closing paren, it will jump and try to pipe the next function call. The same for when we're already at the end of a line.
I'm thinking that maybe the correct fix is to track back the text until we reach a "(" character instead (updating indices properly).
I haven't tested, but this one probably breaks if the line ends as ") \n"
WDYT? Any other ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, tracing back blind yields other problems when the cursor is in the leading whitespace for the line :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I"ve added a test which demonstrates the ")\s\n" case still breaking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do to how do_get_function_call works, when we're at a closing paren, it will jump and try to pipe the next function call. The same for when we're already at the end of a line.
Do we need to keep the "jumping forward" behavior? What is it buying us?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more of an artifact on how the "function call finding" code works. for instance on a multi-line function call like:
f(
x,
y,
z)
Having the cursor at the beggining of the line containing x
will still find the function correctly.
However, we can just for the cursor to be, for instance at a "(" character or a non-whitespace character, for instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I see.
FYI, I found another breaking case so I'm working on fixing that.
Changed the function call detection to not "jump forward", as part of this if the user requests an index that is not on a function call, then no action is performed. Also fix handling of piping function calls that span multiple lines by adjusting the returned line and column to point to the correct start location by counting backward.
@polvalente okay, I've pushed my proposed changes to a new PR: polvalente#2 This makes all the tests pass, but I want to do some more local testing as well. |
Handle more cases for manipulating pipes Co-Authored-By: Jason Axelson <[email protected]>
37de054
to
6599fb8
Compare
@lukaszsamson Would you mind taking another look at the code as well? It's changed a bit since your review. |
@axelson Looks good to me, LGTM |
* fix: expose manipulate pipes command * test: add test for known bug * fix: make test retrocompatible * feat: treat escape chars after macro to_string * docs: add comment for treatment * feat: import code from Elixir source code to deal with sigils * fix: use ast_to_string instead of Macro.to_string * chore: remove redundant test * fix: various fixes due to new tests * test: make test retrocompatible * fix: do not raise for any position in the file * docs: add todo comment * fix: also pipe 1-arg functions * test: fix test assertion * Proposed changes Make `to_pipe_at_cursor/3` public to facilitate testing. I think we should still keep some integration-level testing, but I think the majority of the test cases can be simplified. I didn't implement making `from_pipe_at_cursor/3` public, but I think it would make sense as well. Introduce `ElixirLS.LanguageServer.Protocol.TextEdit` and return it from `to_pipe_at_cursor/3` and `from_pipe_at_cursor/3` Add an example of a working simpler test, and add a few examples of broken tests (as mentioned in elixir-lsp#521 (review)) * chore: unbreak tests * fix: make tests pass * test: add broken test * Handle more cases for manipulating pipes Changed the function call detection to not "jump forward", as part of this if the user requests an index that is not on a function call, then no action is performed. Also fix handling of piping function calls that span multiple lines by adjusting the returned line and column to point to the correct start location by counting backward. * chore: add suggestions from code review * chore: format code Co-authored-by: Jason Axelson <[email protected]>
This PR also adds a test that shows a bug which seems to be in Macro.to_string itself :( I've opened an issue in the Elixir repo elixir-lang/elixir#10864 for discussion. Maybe this is expected and is something we have to deal with