elves / elvish

Powerful scripting language & versatile interactive shell
https://elv.sh/
BSD 2-Clause "Simplified" License
5.67k stars 299 forks source link

Less simplistic splitting for edit:last-cmd #1491

Open hanche opened 2 years ago

hanche commented 2 years ago

If I want to reuse an argument from the middle of the previous command, I hit Meta-comma. But if that argument contains spaces, I can't get it as a single unit (see example below). This in contrast with Meta-period, which always gets the last argument, spaces and all. This could be improved upon, quite a lot I think.

⬥ grep foo 'file with spaces  in the filename' > blah
⬥ grep -c foo← here I hit Meta-comma, with the result below
 LASTCMD
    grep foo 'file with spaces  in the filename' > blah
  0 grep
  1 foo
  2 'file
  3 with
  4 spaces
  5 in
  6 the
  7 filename'
  8 >
  9 blah

What's more, with complex commands it would be nice to be able to drill down into the distinct arguments. To explain what I mean, assume the previous command was

⬥ foo (bar {|x y| put [$y $x]) 'abc def' # silly example

and I hit Meta-command, I would like to see the completion list

  0 foo (bar {|x y| put [$y $x]) 'abc def' # silly example
  1 foo
  2 (bar {|x y| put [$y $x]) 
  3 'abc def'
  4 # silly example

and now, if I choose item 2, I would want the list to be replaced by

  0 (bar {|x y| put [$y $x]) 
  1 bar
  2 {|x y| put [$y $x])

with further drilling down if I were to choose item 2 again. This “drilling down” could also be applied to lists, braced lists, functions, perhaps even concatenations such as (styled …)(styled …) or a{b c d}.

I have no idea how hard this would be to implement, but one can always wish. 😉

aca commented 2 years ago

Seems like there's already todo comment for this https://github.com/elves/elvish/blob/2c8bae8496bc0cd5ccc128646926923ad2deee7e/pkg/edit/listing.go#L86

krader1961 commented 2 years ago

@hanche: I agree the current behavior is suboptimal but the devil is in the details. What is an "argument" in the context of exposing the tokens of the "last command"? Consider a silly, but seemingly trivial, example such as this:

> put (put $pwd"/tmp")
▶ /Users/krader/tmp
# Press [Alt-,]
 LASTCMD
    put (put $pwd"/tmp")
  0 put
  1 (put
  2 $pwd"/tmp")

What should the "last command" tokens be in that example? Should it instead be the entire command line plus ["put", "(", "put", "$pwd"/tmp", "$pwd", "/tmp", ")"]? Something else?

hanche commented 2 years ago

the devil is in the details

Indeed. 😉

I want it to decompose the command into syntactically (and semantically!) meaningful units. In a sense, I want it to go just one level down in the parse tree. For your concrete example, I would like to see this:

LASTCMD
    put (put $pwd"/tmp")
  0 put
  1 put $pwd"/tmp"

I skipped the the enclosing parentheses in item 1 because it is easier to insert parentheses than removing them: Just be sure to insert an opening parenthesis before invoking edit:last-cmd.

To continue that, if you now hit 1, you should see

LASTCMD
    put $pwd"/tmp"
 0 put
 1 $pwd"/tmp"

Of course, implementing this requires access to the parse tree, along with the substring of the original command corresponding to each node in the parse tree. So this may not be easy! Or it could be quite easy, if the elvish internals give easy access to all this information. In that case, the difficult part is to decide on the specification, more than the implementation.

Is a BNF or similar grammar for the language available somewhere? With that in hand, I could try my hand at writing a more detailed proposal. It won't happen very fast, though. (What would be lovely is to have the parsing information available at the elvish level, for experimentation purposes.)

Edit: I know, this is very much a “pie in the sky” sort of feature request at the moment. Perhaps other approaches make more sense; I don't know. But perhaps we can find an answer.

krader1961 commented 2 years ago

@hanche: You wrote "This in contrast with Meta-period, which always gets the last argument, spaces and all." But with the example in my previous comment that results in (put $pwd"/tmp") being inserted. I would argue that both [Alt-.] and [Alt-,] produce surprising results in many non-trivial cases. The question is how to make the behavior of each mechanism consistent with each other in a manner that maximizes the usefulness of both features while minimizing surprise by the user. I confess I don't have any answers, but then I just noticed the "last command" mode after using Elvish for two years. 😄

Note that [Alt-.] is bound by default to edit:insert-last-word which doesn't explain what a "word" is in that context but definitely doesn't match any of the examples at https://elv.sh/ref/edit.html#word-types.

krader1961 commented 2 years ago

I want it to go just one level down in the parse tree.

This is an important observation and should guide the replacement for the current behavior. This appears to be how edit:insert-last-word behaves; although, I've tried just a handful of examples as I write this and have not examined the implementation to understand how it actually behaves. Nonetheless, I think the idea of displaying/selecting semantic units only one or two levels deep is what we want for both mechanisms. Given that restriction I don't think it should remove parens from either level.

hanche commented 2 years ago

I don't think it should remove parens from either level.

On further thought, I guess I agree. My reason for suggesting it was based on convenience, but it would violate POLA. And the convenience argument isn't that strong: In the most likely situation where you want the body of a parenthesised expression, you are likely to insert it into an empty command line, where removing the parens is just a quick C-a C-d C-e backspace away.