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

Pickers "v2" #9647

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open

Pickers "v2" #9647

wants to merge 20 commits into from

Conversation

the-mikedavis
Copy link
Member

@the-mikedavis the-mikedavis commented Feb 17, 2024

I was hoping to make these picker changes as separate PRs but they layer in a way that makes reviewing them independently awkward: one commit changes a block and the next changes it again, so I think it's easier to review altogether. This PR is a kind of "version 2" for the Picker component that resolves a bunch of things we've wanted to change about Pickers:

Shout out to @pascalkuthe's excellent work with the event system (#8021) and Nucleo (#7814) that make this change possible.

The table format and filtering by columns (originally posted in #7265):

table-picker demo

Dynamic global search running against a very large directory (originally posted in #196):

global search demo

Related to #9629
Closes #196
Closes #5714
Closes #5446
Closes #3543
Closes #4956
Closes #2109

@the-mikedavis the-mikedavis added C-enhancement Category: Improvements A-helix-term Area: Helix term improvements E-hard Call for participation: Experience needed to fix: Hard / a lot S-waiting-on-review Status: Awaiting review from a maintainer. labels Feb 17, 2024
@archseer
Copy link
Member

For pickers with a single column, could we just hide the header in that case? That way it ends up looking like the original picker

helix-term/src/ui/picker/handlers.rs Outdated Show resolved Hide resolved
helix-term/src/ui/picker.rs Outdated Show resolved Hide resolved
helix-term/src/ui/picker/handlers.rs Show resolved Hide resolved
@pascalkuthe
Copy link
Member

Just a couple things I noticed I haven't read trough all of it yet

@the-mikedavis
Copy link
Member Author

For pickers with a single column, could we just hide the header in that case? That way it ends up looking like the original picker

Yep actually we don't render the header if columns.len() isn't > 1:

// -- Header
if self.columns.len() > 1 {
let header_text_style = cx.editor.theme.get("ui.picker.header.text");
let header_separator_style = cx.editor.theme.get("ui.picker.header.separator");
table = table.header(
fn header_height(&self) -> u16 {
if self.columns.len() > 1 {
// The header is one line for the column names and
// one line for the separator bars.
2
} else {
0
}
}

So the file picker doesn't look or behave any differently for example

@sicher
Copy link

sicher commented Feb 17, 2024

Very nice!

For a slightly more compact look the column headers could be rendered with a different background - instead of having the line below.

@the-mikedavis
Copy link
Member Author

Yeah the separator lines were a little bulky. I removed the separators and added a theme key only for the header text

@univerz
Copy link

univerz commented Feb 17, 2024

Yeah the separator lines were a little bulky. I removed the separators and added a theme key only for the header text

or even move them into the separator below the input, similar to dbg mockups?

@the-mikedavis the-mikedavis force-pushed the pickers-v2 branch 2 times, most recently from 02554fb to 71e1d2f Compare February 17, 2024 15:46
@the-mikedavis
Copy link
Member Author

or even move them into the separator below the input, similar to dbg mockups?

That's much more complicated implementation-wise - we would probably need to change the table rendering in helix-tui. I'll leave that for future work

helix-term/src/ui/picker/handlers.rs Outdated Show resolved Hide resolved
helix-term/src/ui/picker/handlers.rs Outdated Show resolved Hide resolved
helix-term/src/ui/picker/handlers.rs Outdated Show resolved Hide resolved
helix-term/src/ui/picker/query.rs Outdated Show resolved Hide resolved
@thiagomajesk
Copy link

Wow, we have to get this on master! This solves my main issue with Helix. Brilliant work @the-mikedavis!!!

@Philipp-M
Copy link
Contributor

Philipp-M commented Mar 2, 2024

Just started testing this.

First of all, great work!

One suggestion: Make debounce timeouts configurable.

I have reduced them significantly (highlight: 150 -> 33 (I have a key-repeat synced to frame-rate, reason why it's so low while still debouncing effectively when scrolling), dynamic query: 275 -> 75).
I think everyone has a (slightly) different preference here, since no one types the same speed (but as a fast typer + impatiance, the current ones just feel way too slow to me).
I could imagine that flashes (when not typing that fast) could annoy others, which is why I think it makes sense to make this configurable (with sane defaults).

I have noticed flashes when using the DynamicQueryHandler via the global search, although items look the same and there's picker.matcher.restart(false) already. It's still slightly less (but still) noticeable when picker.matcher.restart(true) (paradoxically).
So maybe worth looking into?

@the-mikedavis
Copy link
Member Author

What do you mean by flashes? Can you capture it in a gif / video / asciinema cast?

W.r.t. configurable timeouts see #9668 (comment)

@Philipp-M
Copy link
Contributor

Philipp-M commented Mar 2, 2024

Sure, here's an asciinema

(edit: Just to confirm, since this was tested on a branch merged with all kinds of things, it also occurs on this one)

@jscarrott
Copy link
Contributor

Something I have noticed is that the global search no longer reads from the search selection. Which is a minor annoyance for something that completely replaces my use of fzf/rg.

@the-mikedavis
Copy link
Member Author

In the latest push I switched the query syntax to be the one mentioned at the end of #9647 (comment). So now it's %path file.rs instead. Each column is fuzzy matched.

@everestmz
Copy link

In the latest push I switched the query syntax to be the one mentioned at the end of #9647 (comment). So now it's %path file.rs instead. Each column is fuzzy matched.

Ah, I should have read more closely. Can confirm this works, thank you for these updates. Search is awesome now!

@the-mikedavis
Copy link
Member Author

I had originally intended for this to be a follow-up but it was pretty straightforward to add with the new syntax. We can highlight the column that is being entered with a ui.picker.header.active theme key. As a basic set of highlights I would recommend something like:

"ui.picker.header" = { modifiers = ["underlined"] }
"ui.picker.header.active" = { modifiers = ["underlined", "bold"] }

After merging we may want to add those keys to the existing themes in the repo as a baseline and then theme authors can customize from there.

@daedroza
Copy link
Contributor

daedroza commented Apr 26, 2024

The original patch also contained the "contents" column (in the very first gif), can we please add that back and hide it behind default flag? I don't use the preview often and rely on the original search contents to help myself narrow down to the result.

Usecase is something like jump to a function call using gw, *, global_search then use %c boolean, to search for function definition instead of other function calls

@daedroza
Copy link
Contributor

daedroza commented Apr 26, 2024

Hmm, so I found the hidden function, I made it's constructor hidden as false and filter as true

diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index f87c18bb..38e6c66c 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -217,8 +217,8 @@ pub fn hidden(name: impl Into<Arc<str>>) -> Self {
         Self {
             name: name.into(),
             format,
-            filter: false,
-            hidden: true,
+            filter: true,
+            hidden: false,
         }
     }

But helix crashes...
thread 'tokio-runtime-worker' panicked at 'called Result::unwrap() on an Err value: Any { .. }', /home/kalpaj/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ignore-0.4.22/src/walk.rs:1301:31
stack backtrace:8afd686d - std::backtrace_rs::backtrace::libunwind::trace::h9a6b80bbf328ba5d

@the-mikedavis
Copy link
Member Author

That panic is because hidden uses unreachable! for its format function - it's asserting that hidden columns shouldn't have text. If you want to restore how that column looked you will need to use a regular column (Column::new).

@pascalkuthe and I discussed it (also see above: #9647 (comment)) and we think it'd be nice to be able to change how the preview works, so you can use the preview as it is now or alternatively show the matching line and hide the preview. That should be future work though and in the meantime I'm not interested in adding the column back to this PR since it takes up a lot of space and almost always needs to be truncated. Being able to change the preview from showing the context of the currently selected item to being the line contents of each item could be useful for other pickers in the future as well.

@rockboynton
Copy link

Is scrolling in the picker covered in this PR or is that still an open issue? #5686

@the-mikedavis
Copy link
Member Author

No this PR doesn't cover that, it's left for other PRs (I believe there is one or two open for it)

@davxy
Copy link
Contributor

davxy commented Apr 27, 2024

for global search would be nice to show line number at the end of the path (much like what you get when you use gr over a symbol)

@useche
Copy link
Contributor

useche commented Apr 27, 2024

for global search would be nice to show line number at the end of the path (much like what you get when you use gr over a symbol)

Yes, I would like that too. I had this implemented with a separate column in the global search picker:

diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 1c91510d..428d7d0c 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -2265,6 +2265,9 @@ struct GlobalSearchConfig {
     };

     let columns = [
+        PickerColumn::new("line#", |item: &FileResult, _| {
+            (item.line_num + 1).to_string().into()
+        }),
         PickerColumn::new("path", |item: &FileResult, _| {
             helix_stdx::path::get_relative_path(&item.path)
                 .to_string_lossy()
@@ -2404,7 +2407,7 @@ struct GlobalSearchConfig {

     let picker = Picker::new(
         columns,
-        1, // contents
+        2, // contents
         [],
         config,
         move |cx, FileResult { path, line_num, .. }, action| {

But for uniformity with the references picker, this would be better:

diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 1c91510d..592e26a2 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -2266,10 +2266,12 @@ struct GlobalSearchConfig {

     let columns = [
         PickerColumn::new("path", |item: &FileResult, _| {
-            helix_stdx::path::get_relative_path(&item.path)
-                .to_string_lossy()
-                .into_owned()
-                .into()
+            format!(
+                "{}:{}",
+                helix_stdx::path::get_relative_path(&item.path).to_string_lossy(),
+                item.line_num + 1
+            )
+            .into()
         }),
         PickerColumn::hidden("contents"),
     ];

@the-mikedavis
Copy link
Member Author

I think we should avoid adding an extra column for it - I don't think you would want to filter on the line number very often. We could use it in the path column so we show filename.ext:123 for line 123

the-mikedavis and others added 11 commits April 29, 2024 09:46
We could expand on this in the future to have different preview modes
that you can toggle between with C-t. Currently that binding just hides
the preview but it could switch between different preview modes and in
one mode hide the path and just show the line contents.
This fixes the changed files picker when used against a clean worktree
for example. Without it the running indicator does not disappear. It
also simplifies the dynamic query handler's implementation so that it
doesn't need to request a redraw explicitly.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
This introduces a custom URI type in core meant to be extended later
if we want to support other schemes. For now it's just a wrapper over a
PathBuf. We use this new URI type to firewall `lsp::Url`. This was
previously done in 8141a4a but using a custom URI type is more flexible
and will improve the way Pickers handle paths for previews in the child
commit(s).

Co-authored-by: soqb <cb.setho@gmail.com>
The `FileLocation` and `PathOrId` types can borrow paths rather than
requiring them to be owned. This takes a refactor of the preview
functions and preview internals within `Picker`. With this change we
avoid an unnecessary `PathBuf` clone per render for any picker with a
file preview function (i.e. most pickers).

This refactor is not fully complete. The `PathOrId` is _sometimes_ an
owned `PathBuf`. This is for pragmatic reasons rather than technical
ones. We need a further refactor to introduce more core types like
`Location` in order to eliminate the Cow and only use `&Path`s within
`PathOrId`. This is left for future work as it will be a larger refactor
almost entirely fitting into the LSP commands module and helix-core -
i.e. mostly unrelated to refactoring the `Picker` code itself.

Co-authored-by: Pascal Kuthe <pascalkuthe@pm.me>
`Picker::new` loops through the input options to inject each of them, so
there's no need to collect into an intermediary Vec. This removes some
unnecessary collections. Also, pickers that start with no initial
options can now pass an empty slice instead of an empty Vec.

Co-authored-by: Luis Useche <useche@gmail.com>
This allows us to replace any `vec![..]`s of columns where all columns
are static with static slices `[..]`.
We can track the ranges in the input text that correspond to each column
and use this information during rendering to apply a new theme key that
makes the "active column" stand out. This makes it easier to tell at
a glance which column you're entering.
@krish-r
Copy link
Contributor

krish-r commented Apr 30, 2024

I noticed that the column width can only grow in an active picker, due to the existing logic -

// helix/helix-term/src/ui/picker.rs
if width as u16 > *max_width {
    *max_width = width as u16;
}

(Noticeable only in a couple of pickers with multiple columns, especially if one of them contains lengthy text.)

Do you think it would be better to have default column widths (perhaps not in this PR)?
This way, if the contents aren't too long, it can shrink / fallback to the defaults instead.

for ex. workspace symbol picker - current vs with default widths

current:
workspace_symbol_current

with default widths:
workspace_symbol_default_widths

I also tried resetting it to the max width of current contents instead, but the constant change in widths made it difficult to follow the results.

@the-mikedavis
Copy link
Member Author

The current width handling is pretty naive. I was thinking that improving it could be a follow-up. What we have now can take up a lot of space but minimizes how much the columns move around

@the-mikedavis the-mikedavis modified the milestones: 24.05, next May 2, 2024
@hakan-demirli
Copy link

hakan-demirli commented May 13, 2024

Small Feedback:
Global search is updated/performed after I stop writing. There is a small delay between when I stop writing and global search is updated. It feels slow/less responsive compared to both neovim telescope and current helix workaround tmux+fzf+rg combo which updates global search instantly after each key press.

Ok. this has been addressed here.

Small Question:
Is fuzzy global word(s) search disabled by default?
When I am searching for a text file content lets say clipboard_tts. Searching with clipboard tts or cliptts returns nothing. I have to type exactly clipboard_tts.
image
image

On the other hand, when I am filtering/searching paths using %path It is fuzzy by default. So, like path I expect fuzzy search for word(s) by default. Or a config option to enable it.
image

@pascalkuthe
Copy link
Member

Global search is not a fuzzy search its an interactive regex/grep search. It's the same as the current global search just that the results are updated as you type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-helix-term Area: Helix term improvements C-enhancement Category: Improvements E-hard Call for participation: Experience needed to fix: Hard / a lot S-waiting-on-review Status: Awaiting review from a maintainer.
Projects
None yet