doorgan / sourceror

Utilities to manipulate Elixir source code
Apache License 2.0
326 stars 22 forks source link

improvement: add move_to_cursor/2 #147

Closed zachdaniel closed 4 months ago

zachdaniel commented 5 months ago

So, I'm sure that there will need to be lots of workshopping or even that the implementation should be fully changed, but this at a minimum starts the conversation :)

Its relatively simple, but does technically break if someone is using the variables/functions ___cursor___ or ___skip___ (note the triple underscores).

NickNeck commented 5 months ago

That looks like a great idea. I was asking myself if we could use Code.Fragment. container_cursor_to_quoted/2 (available since Elixir 1.17) here.

iex>  Code.Fragment.container_cursor_to_quoted("if ... do")
{:ok, {:if, [line: 1], [{:..., [line: 1], []}, [do: {:__cursor__, [line: 1], []}]]}}
iex>  Code.Fragment.container_cursor_to_quoted("... |> Enum.filter(")
{:ok,
 {:|>, [line: 1],
  [
    {:..., [line: 1], []},
    {{:., [line: 1], [{:__aliases__, [line: 1], [:Enum]}, :filter]}, [line: 1],
     [{:__cursor__, [line: 1], []}]}
  ]}}

The ... for skipping would be nice. The function move_to_cursor could accept opts to set the skip marker.

Zipper.move_to_cursor(zipper, "if ... do")
Zipper.move_to_cursor(zipper, "if :ignore do", skip: :ignore)
zachdaniel commented 5 months ago

Hmm....that is pretty nice. I like that better than ___skip___ 😆

zachdaniel commented 5 months ago

curious to see if it will play out

zachdaniel commented 5 months ago

So, I've used __cursor__ again to line up with that function, and ... for skipping, but I did just realize that IIRC ... is going to be an operator used by Elixir in 1.18. So we probably can't use that. I think it will have to be something else. Maybe __?

zachdaniel commented 5 months ago

I think a variation using container_cursor_to_quoted would make sense, but it would be a different function since it only has to match on what "leads up to" the cursor, whereas this has to match up before and after.

NickNeck commented 5 months ago

Yes, with container_cursor_to_quoted it would be a different function but a variant that could make sense. It's a shame that we can't use .... I think __ could also fit there.

When move_to_cursor works we could try something like Regex.named_caputres 😄

{zipper, caputres} = Zipper.move_to_coursor_and_capture(zipper, "__cursor__ |> Enum.map(__map) |> Enum.join(__join) ")
captures.map #=> AST for Enum.map args
captures.join #=> AST ...

But, one step at a time.

doorgan commented 5 months ago

Maybe __?

When I first took a stab at this, I went with __ because it felt like a really weird thing to type in most elixir code, so I'm good with using this to ignore values!

zachdaniel commented 5 months ago

__ is especially good, because you get this:

iex(1)> defmodule Thing do
...(1)> def thing(__), do: 10
...(1)> end
warning: unknown compiler variable "__" (expected one of __MODULE__, __ENV__, __DIR__, __CALLER__, __STACKTRACE__)
└─ iex:2: Thing.thing/1