elves / elvish

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

Complete longest common prefix #716

Closed kwshi closed 6 years ago

kwshi commented 6 years ago

Currently, with no third-party modules installed, elvish tab-completes by bringing up a menu of items and selecting the first item, with successive tab presses used to select the next menu item.

For example, if I have a documents/ and downloads/ folder, pressing d<tab> brings up a menu list with both options, and autocompletes first to documents/, then selects downloads/ upon a repeat <tab>:

~> cd desktop/ # on the keyboard: `cd d<tab>`
 COMPLETING argument  
 documents  downloads 

In the cases that I carelessly press upon typing d, but expecting to go to the downloads folder, I have to press d<tab><tab> to navigate and select the downloads folder. This seems trivial, but becomes more cumbersome when I have a multitude of similarly-prefixed files, and my intended destination sits in the middle of the list. As an example of this, suppose I have folders named "ksbar-desktops-bspwm", "ksbar-battery", "ksbar-clock", "ksbar-volume", "ksbarutil", "ksbar-title", "ksbar-light". I intend to go to "ksbar-light". I type "ks", which immediately completes to "ksbar-battery":

~/go/src/github.com/kwshi/ksbar-component> cd ksbar-battery/
 COMPLETING argument  
 ksbar-battery  ksbar-desktops-bspwm  ksbar-title  
 ksbar-clock    ksbar-light           ksbar-volume 

I now have to press tab "many" times (i.e. ks<tab><tab><tab><tab>), or look through the menu, find the target folder, and then arrow-key (i.e. ks<tab><down><right>) my way to it, when it would simply have been much faster to type the whole folder name itself.

Coming from bash, I much prefer bash's tab-completion behavior, wherein pressing tab calls up the longest unambiguous completion, and requires me to continue filling in the file name before I can tab-complete again. In the case of the previous example, typing ks<tab> would simply complete to ksbar-, and if I wanted a list, I'd press <tab> again to bring up a list, and then I'd continue typing l<tab> to complete the whole thing to ksbar-light. That is, for the keyboard input ks<tab>l<tab>, I would get the completion ksbar-light, with no arbitrary selection of undesired completions. This kind of behavior is desirable because most of the time I know where I intend to go and am simply using tab to type fewer things; having to look through a menu to figure out where I'm going or backspace my way out of an incorrect completion ends up taking even more time, whereas bash's only-unambiguous completion does as much as it can, without doing too much.

For reference, this kind of behavior is configured in zsh with

setopt no_auto_menu # disable menu completion, i.e. disables auto-selecting first menu item
setopt bash_auto_list # enable listing of options on second tab; `setopt auto_list` is also good

I understand that the default menu-style completion can be desirable in some cases, and probably desirable to some people, so I do not criticize the choice of having this behavior, or even having this behavior as a default. What I would like is that there is some way, possibly via options of some sort (speaking of, does elvish have option-based configuration?), to use bash-style no-menu completion instead, for those of us who might prefer that instead.

I have not managed to find instructions on option-based configuration in the documentation or instructions on selecting bash-style completion. If I am wrong about this feature not already being a part of elvish, and I've been just bad at reading documentation, feel free to point me in the right direction and close this issue. And perhaps also consider adding some of this documentation to the "getting started" guide.

SolitudeSF commented 6 years ago

The bash behaviour youre describing is not about menu, is about completing to biggest common prefix. Issue is elvish can have custom completers and not only prefix one.

kwshi commented 6 years ago

I'm not entirely sure how elvish completion works. For now, I've called it menu completion because the relevant options controlling this behavior in zsh are called auto_menu and no_auto_menu.

kwshi commented 6 years ago

Alternatively, we could temporarily compromise by delaying menu-completion until the second tab, so that the first tab only brings up the menu without selecting any of the completions, and the second tab begins to scroll through the menu items. This seems like perhaps a saner default than is currently in place, and it is also zsh's default (i.e. unconfigured) behavior.

xiaq commented 6 years ago

This issue has actually been discussed recently in the user group.

The completion mode has a filter function that can be triggered by ^F, which serves the use case probably 99% of the time. For instance, if you have foobar-lorem, foobar-ipsum, foobar-dolar, typing f, Tab, and then ^F, l will get you foobar-lorem. In fact, I realized that it is more useful for the completion mode to immediately enter filtering when it starts (#678). So you will type f, Tab, l and then Enter to get foobar-lorem.

This is the same number of keystrokes with Bash's behavior, where you type f, Tab, (get foobar-), l and Tab. However, with Elvish's approach you get much more feedback: you see the list of candidates with the first Tab stroke, and you get real-time feedback whilst you are filtering, so as soon as you type l you know the candidate is now unique and you can accept it: with Bash there is comparatively little feedback, you may end up typing lor before you are confident that another Tab will give you exactly what you want.

The 1% case where it doesn't work is when the candidate you want reduplicates the common prefix. So for instance, if you have foobar-lorem, foobar-ipsum and foobar-foobar and you want foobar-foobar, filtering for foobar doesn't give you anything more specific. However, I feel that this use case is likely pretty rare and you can usually work around this by extending the filter a little bit to the left, e.g. filter for -foobar gives what you want.

Also, completing for the longest common prefix used to be Elvish's behavior as well, but this was removed (#637). See that issue for the rationale.

kwshi commented 6 years ago

This seems like a fair solution. Filtering mode is quite nice, and I can see myself getting used to it. I'm still inclined to suggest that having some customizability (perhaps by options, perhaps by some modules) for behavior like this is useful, and that we make it possible to easily/conveniently enable old-bash-style longest-common-prefix completions. Maybe some might still want to stick with what they're most comfortable with, even if it's a little less smart. Is that already easily doable (if so, perhaps it'd be nice to have a line or so for that in the documentation)?

P.S. It's also worth noting that, prior to the #678 change, ^F for filtering conflicts with the (right/forward) readline-bindings binding, which behaves equivalently to right-arrow. That would've made it difficult to keep readline-bindings and also enable filtering.

xiaq commented 6 years ago

To be honest, I am not sure. I am a bit torn about this one.

On this one hand, adding the option for completing the longest common prefix is not hard, likely < 10 lines of code, and it has certain appeals to people used to how bash and zsh work by default (which is probably a majority of new Elvish users).

On the other hand, adding an option means adding more complexity, even if just a bit. Worse, as documented in #637, it does not interact well with custom matchers. Finding out that two options interact with each other in surprising ways is not a pleasant experience for anybody. You will know that if you have played with zsh's options.

But again maybe this particular option is really not that harmful, maybe I shouldn't try to change people's habits? I am not sure. Maybe Elvish's user base is too small to make a data-based judgement.

If the lack of longest common prefix completion does not drive you away from Elvish, how about you come back later to report your experience - does the filtering mode make up for the loss of this feature?

On the other hand, at some point, the completion mode should have a complete Elvish API that allows you to change its behavior not by configuring an option, but by writing code. That will be another way to address this feature as well.

xiaq commented 6 years ago

Re. the readline binding: Conceptually it probably should conflict, but actually it does not. Elvish's key binding is per-mode. ^F only applies in insert mode, while completion mode has its own bindings.

kwshi commented 6 years ago

Well, prior to the update ^F did not activate filtering for me but rather moved my completion selection right.

Regarding the completion mode, I'm definitely okay with filtering for now. I think eventually having an API for this would be great, and actually probably a cleaner solution than option-based configurations. I'll go ahead and close this for now.