haskell / haskeline

A Haskell library for line input in command-line programs.
https://hackage.haskell.org/package/haskeline
BSD 3-Clause "New" or "Revised" License
223 stars 75 forks source link

Tab clobbers input when completions' common prefix is shorter than input string #100

Open pchiusano opened 5 years ago

pchiusano commented 5 years ago

Example is you are doing fuzzy completion, with drop completed to Text.drop and Sequence.drop. In this case, the common prefix of the completions is the empty string, and the user's input of drop is deleted (and no suggestions get printed).

Suggested behavior: Don't replace the user input if the common prefix is shorter than the input.

Another possible idea: somehow give the user more control over what happens to the line when completions are chosen (so they can override this behavior).

judah commented 5 years ago

Thanks for the report! This sounds related to #7, though perhaps more general.

I'm in the process of putting together a new completions API which should give more control around the "common prefix" computation. Can you explain a little more what kind of fuzzy completion you have in mind? Is it just case-insensitivity, something else that operates per-character (e.g. equating "-" and "_"), or something more general?

pchiusano commented 5 years ago

Oh okay, my bad, somehow #7 didn't jump out at me as likely being the same root issue.

In our case, the fuzzy matching we are doing is some hand-tuned combination of looking for prefix / suffix / substring / subsequence matches, and/or close edit distance, with some logic for ranking results that will probably get tweaked a lot. My feeling is that it's super unlikely that there will be a one size fits all fuzzy matching solution, so the CompletionFunc type should support arbitrary user-specified behavior for how to manipulate the current line in response to a list of completions. So perhaps something like:

type CompletionFunc m = Line -> m (Line, ShowCompletions, [Completions])
data Line = Line { beforeCursor :: String, afterCursor :: String }
type ShowCompletions = Bool

So the CompletionFunc gets to decide how the entire line is rewritten in response to the list of completions, which means it has the ability to do the same stuff it does now, or some complicated custom thing. Another thing is that the CompletionFunc gets to choose whether the completions are shown. Right now, I think the hardcoded behavior is that if the cursor advances, completions are not shown.

With a type like the above, the current behavior can just be a helper function, but people who want to do something weird or custom can do whatever.

We ended up working around the current behavior with a hack: if the list of completions is not length 1 and has a common prefix which is shorter than the input, we set the replacement string for all completions to be equal to the input. This has the effect of keeping the cursor where it is and just showing the completions.

Hope that is helpful! By the way, thanks for writing this library, it's pretty nice and does what it says on the tin. :) We found it was very painless to integrate with (me, @runarorama and @aryairani are using it for command line interface for Unison).