Skip to content
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

Minibuffer overhaul #777

Closed
Ambrevar opened this issue Jun 4, 2020 · 15 comments
Closed

Minibuffer overhaul #777

Ambrevar opened this issue Jun 4, 2020 · 15 comments
Labels
high prompt-buffer Related to the prompt buffer.

Comments

@Ambrevar
Copy link
Member

Ambrevar commented Jun 4, 2020

The minibuffer needs a rewrite to fix a couple of current issues and implement
many core features.

The code needs to be extracted:

  • A text editing library (see what Lem has to offer).
  • A "readline" library, that does the candidate suggesting, fuzzy matching,
    sorting, etc. It would be heavily inspired by Emacs Helm.
  • Finally, the minibuffer rendering in Next.

One of the biggest flaws of our current implementation is that it uses
Javascript to know which candidate is selected. If I'm not mistaken, there is
no need for that. Instead, would could do all the logic in the readline
library, only using HTML to render.
A great benefit of this is that the minibuffer queries would not be asynchronous
anymore. In particular, instead of the convoluted

(with-result (result (read-from-minibuffer
                      (make-minibuffer ...))
  (use result here)))

we could have

(let ((result (minibuffer-read ...)))
  (use result here))

Another thing that can be improved is the vocabulary:

  • candidate or suggestion?
  • mark or select?
  • The currently highlighted item could be called current candidate or current suggestion.
  • Now we have select-next and select-previous to shift the current
    suggestion to the nexet. Since it's ambiguous (select has the meaning of
    mark), I suggest to simply name it next. With the package prefix, that
    would be readline:next.

New features:

  • Support paging (Support Page-Up / Page-Down keys in minibuffer #454). This
    means we need to know the number of suggestions currently displayed. It's not
    trivial, especially if the suggestion contains a picture for instance. This
    is why each suggestion item needs to report how much space it takes, but
    horizontally and vertically.

  • Support multi-columns: currently execute-command has 3 columns and we want
    to add a 4th one (Add one-liner documentation to M-x command list #624).
    For the sake a clear display and better control, we need to allow for
    tabulated display and add a command to allow the user to choose which column
    to toggle.
    Also add a column to choose which columns (yes, plural!) to sort by.

  • Support filters. Find a way or an input syntax to filter suggestions
    depending on their characteristic. The filtering should be done by a callback
    in the suggestion itself, so that each different suggestion type understands
    their own filtering.

  • Support multiple actions. Each suggestion should specify a list of actions to
    operate on it. Hitting return runs the default action, but hitting some
    other key allows for choosing the action.

  • Support multiple sources. With multiple action support we could mix-match
    suggestions of different types (e.g. bookmarks and history). Then we miss the
    possibility to sort items by type, and more importantly, to browse the types
    (like Emacs Helm allows for going from one source to the next). Maybe
    filtering would do here.

  • Emacs Helm has a persistent action feature that show the result of the
    action in another buffer, leaving the minibuffer open. Useful to display
    contextual help for instance. Maybe we can generalize this.

  • Currently our filters are very repetitive: They all call fuzzy-match. Maybe
    we can do better:

    • a suggestions key argument,
    • an optional display-suggestion key argument (useful if you don't want to
      create object just for the minibuffer), otherwise object-display is used.
    • a matching function that default to fuzzy-match; better, we could have a
      higher-order-function that builds the fuzzy-matcher according to a selection
      of rules (fixes Use extensible fuzzy-matching everywhere #274).
  • Support asynchronous suggestion computation.

  • Support Helm "follow mode", e.g. when navigating a minibuffer displaying
    buffers, it would temporarily display the current suggestion in place of the
    current buffer. On cancel, it restores the state.

  • Support resuming minibuffers. This is especially useful to resume searches.

  • From Next itselt, use a GTK (or Qt) widget to get the input. This should fix
    Input methods don't work in the minibuffer #324.

  • Still from Next, allow VI / CUA / Emacs binding schemes.

@Ambrevar Ambrevar added high prompt-buffer Related to the prompt buffer. labels Jun 4, 2020
@jmercouris
Copy link
Member

I have been working on this in the background, doing some research, and I believe I have some insights to offer:

The minibuffer needs a rewrite to fix a couple of current issues and implement
many core features.

The code needs to be extracted:

  • A text editing library (see what Lem has to offer).

I've investigated Lem. I do not think much can be extracted from it. It is not written in a way that is conducive to that.

  • A "readline" library, that does the candidate suggesting, fuzzy matching,
    sorting, etc. It would be heavily inspired by Emacs Helm.

What is a "readline" library? A completion engine? If so, I have a lot to say about completion, prediction, etc. Though Emacs is good, I have some ideas about this domain that I think could help boost it into the stratosphere! :-)

  • Finally, the minibuffer rendering in Next.

One of the biggest flaws of our current implementation is that it uses
Javascript to know which candidate is selected. If I'm not mistaken, there is
no need for that. Instead, would could do all the logic in the readline
library, only using HTML to render.

You are mistaken :-)

It does not use javascript to know which candidate is selected.

A great benefit of this is that the minibuffer queries would not be asynchronous
anymore. In particular, instead of the convoluted

(with-result (result (read-from-minibuffer
                      (make-minibuffer ...))
  (use result here)))

we could have

(let ((result (minibuffer-read ...)))
  (use result here))

The reason we use continuation passing style to gather data from the minibuffer is to avoid blocking decisions on the main thread. This asynchronicity provides us many benefits (such as allowing single thread execution while waiting for input). We could do it with multiple threads, but it is not necessary for something like the minibuffer, and actually the code would probably be more complex.

Another thing that can be improved is the vocabulary:

  • candidate or suggestion?

suggestion

  • mark or select?

select

  • The currently highlighted item could be called current candidate or current suggestion.
  • Now we have select-next and select-previous to shift the current
    suggestion to the nexet. Since it's ambiguous (select has the meaning of
    mark), I suggest to simply name it next. With the package prefix, that
    would be readline:next.

how about suggestion-cursor-next?

New features:

  • Support paging (Support Page-Up / Page-Down keys in minibuffer #454). This
    means we need to know the number of suggestions currently displayed. It's not
    trivial, especially if the suggestion contains a picture for instance. This
    is why each suggestion item needs to report how much space it takes, but
    horizontally and vertically.

It is not necessary that each element report how much space it takes to support paging. It would be enough for the view to know the height of a group of elements.

Probably we can support this by having 2D arrays as potential input.

For the sake a clear display and better control, we need to allow for
tabulated display and add a command to allow the user to choose which column
to toggle.

I disagree, being able to toggle columns within the minibuffer will probably be complex. Sometimes more choice = more complexity.

Also add a column to choose which columns (yes, plural!) to sort by.

This goes to my previous thinking about Emacs sorting. While it is great, there is a lot to be said about predictive technologies :-D

I really like this idea for simple applications, implementing it (from a UX perspective), will be challenging.

  • Support filters. Find a way or an input syntax to filter suggestions
    depending on their characteristic. The filtering should be done by a callback
    in the suggestion itself, so that each different suggestion type understands
    their own filtering.

I do not believe this is necessary, since we can pass filters to the rendering, they can have the knowledge of how to sort things, why must the object contain the knowledge about itself?

  • Support multiple actions. Each suggestion should specify a list of actions to
    operate on it. Hitting return runs the default action, but hitting some
    other key allows for choosing the action.

Isn't this already the case?

  • Support multiple sources. With multiple action support we could mix-match
    suggestions of different types (e.g. bookmarks and history). Then we miss the
    possibility to sort items by type, and more importantly, to browse the types
    (like Emacs Helm allows for going from one source to the next). Maybe
    filtering would do here.

  • Emacs Helm has a persistent action feature that show the result of the
    action in another buffer, leaving the minibuffer open. Useful to display
    contextual help for instance. Maybe we can generalize this.

  • Currently our filters are very repetitive: They all call fuzzy-match. Maybe
    we can do better:

    • a suggestions key argument,
    • an optional display-suggestion key argument (useful if you don't want to
      create object just for the minibuffer), otherwise object-display is used.
    • a matching function that default to fuzzy-match; better, we could have a
      higher-order-function that builds the fuzzy-matcher according to a selection
      of rules (fixes Use extensible fuzzy-matching everywhere #274).
  • Support asynchronous suggestion computation.

Certainly a VERY good idea! This would make the interface far more snappy. One thing to keep in mind though is that we probably want to prepare all of the suggestions before displaying them such that they don't resort themselves as the user is looking at them.

  • Support Helm "follow mode", e.g. when navigating a minibuffer displaying
    buffers, it would temporarily display the current suggestion in place of the
    current buffer. On cancel, it restores the state.

what do you mean? like if we are doing switch-buffer it would preview the buffer we are moused over? that could be pretty cool!

  • Support resuming minibuffers. This is especially useful to resume searches.

maybe for this we could capture state in a closure?

@Ambrevar
Copy link
Member Author

Ambrevar commented Jun 4, 2020 via email

@jmercouris
Copy link
Member

I've started from the original post, with some of my feedback and ideas.

Support paging (#454):
There are two possibilities I see:

  • All suggestions should have a fixed size. If a suggestion is larger than the fixed size, it should be scrollable in its own container. For example, if we have a large picture, you should be able to scroll down the picture in its container rather than it taking more size on screen (think, iframe).
  • Each suggestion is aware of its size. This is significantly more difficult, more computationally expensive, and makes it harder to view long lists when the size of each entry is non-uniform.

Support multi-columns (#624):

  • The minibuffer should be able to accept objects (extending the minibuffer-suggestion class) with one or more dimensions. Each dimension corresponds to a column. The objects must have sort functions available for each dimension. These sort functions can be toggled by the user on top of each column. Therefore, we call SORT and pass the object the minibuffer input to compute its position in the minibuffer results. This can be parallelized to improve performance.

Support multiple actions:
I disagree with this premise. The minibuffer starts with a verb. I want to do X. Then you find noun Y to operate on X. It never is the other way around.

Support multiple sources:
Minibuffer sources will be top level forms. They will be able to provide a set of minibuffer suggestion objects from a given source. For example, there may be a source to create a list of minibuffer suggestion objects from the set of current buffers. In the minibuffer, these sources will grouped together.

Support asynchronous suggestion computation:
Minibuffer results should be able to be appended in the background while the user is already typing in their inputs.

Support multiple binding schemes:
Add support for VI/CUA bindings.

@Ambrevar
Copy link
Member Author

Ambrevar commented Sep 29, 2020 via email

@Ambrevar
Copy link
Member Author

Ambrevar commented Oct 2, 2020

On the GTK side, I think we should use a GTK text input widget for the input. This would solve the CJK input and the like.

@Ambrevar
Copy link
Member Author

Ambrevar commented Oct 2, 2020

Minibuffer sessions should be first class objects so that they can be resumed. See #744.

@jmercouris
Copy link
Member

We can use the widget, if only as a virtual input. I would not like to render it, we do not have enough control over its appearance and behavior.

@Ambrevar
Copy link
Member Author

Ambrevar commented Oct 2, 2020

Finally, we need to make sure our code is testable, that is to say make the minibuffer "mockable" to simulate user input. Having non-testable minibuffers is the single big blocker for writing tests in Nyxt.

@Ambrevar
Copy link
Member Author

Ambrevar commented Oct 2, 2020 via email

@jmercouris
Copy link
Member

If we are already doing that, then I do not imagine that CJK input will miraculously start working :-D

maybe it is a widget configuration or something. We can try with a minimal test where we use cl-cffi-gtk and see if we can make a text widget that accepts CJK first.

@Ambrevar
Copy link
Member Author

Ambrevar commented Oct 2, 2020 via email

@Ambrevar
Copy link
Member Author

Ambrevar commented Oct 2, 2020 via email

@Ambrevar
Copy link
Member Author

Other features:

  • :ignore-when-zero-suggestions: Don't proceed when minibuffer has zero suggesetions. This is useful e.g. when prompting for session restore and nothing is there.

  • :auto-select-unique-suggestion: Don't prompt the minibuffer and proceed with the only suggestion. Similarly, it's useful e.g. to automatically pick the session to restore when there is only one.

@Ambrevar
Copy link
Member Author

More features:

@Ambrevar
Copy link
Member Author

Most of these features are now on master.
See atlas-engineer/prompter#32 for a follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
high prompt-buffer Related to the prompt buffer.
Development

No branches or pull requests

2 participants