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

Additions to the InputSelector (resolving #4226) #4227

Merged
merged 23 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions config/src/keyassignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,27 @@ pub struct InputSelector {

#[dynamic(default)]
pub fuzzy: bool,

#[dynamic(default = "default_num_alphabet")]
pub alphabet: String,

#[dynamic(default = "default_description")]
pub description: String,

#[dynamic(default = "default_fuzzy_description")]
pub fuzzy_description: String,
Copy link
Contributor

@bew bew Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, very nice PR 👌
I think the word description isn't a great here, and prompt would better fit

Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could go through the code and docs and change it, but I am not sure prompt would be a good idea. I would like to keep the naming similar to what you see in PromptInputLine (see https://wezfurlong.org/wezterm/config/lua/keyassignment/PromptInputLine.html ) and at the same time avoid confusion with this overlay.

Of course, we could change description to prompt in PromptInputLine too. What do you think @wez?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think description is fine; prompt doesn't feel like quite the right word anyway, and this is consistent with promptinputline.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prompt doesn't feel like quite the right word anyway

🤔 huh? My understanding of these words:

  • description for me is a text that describes something, can be long, and doesn't ask for any action
  • prompt for me is the text just before a text field input, it's a 'call-to-action' (e.g. ends with a : )

And from where I see description & fuzzy_description being used in the code, prompt matches more than description.

Or maybe I'm missing some meaning of the english language? (I'm not english native)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think description can be used to describe pretty much anything; also something asking for an action. In this case description can be used to describe either what your current InputSelector does, what different keys do or really anything else that you think helps you when using the InputSelector, which I think is better than prompt (it is better to be a bit more general, since it has different use cases).

The way description is currently used in the code is not the only way to use it, it is just what I had in mind for myself.

Copy link
Contributor

@bew bew Sep 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure I see how description is more generic here, but it definitely feels weird to me to have what I see as the prompt in a description field.

For the different use cases I'd use a separate options: one for the prompt and one for the description, if any.
👉 This is what fzf does for example, where both the prompt (named prompt) and the description (named header) exist.
And since they are separate the layout can be changed so that for example the header is displayed before the prompt line or after (but before the items to select)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prompt is a compatible word, it just feels a bit more "active" or stronger than I think is necessary here. If I hadn't used description elsewhere I'd be more into it here, but as it stands: I don't have sufficiently strong feelings about it to actively want to change it

}

fn default_num_alphabet() -> String {
"1234567890abcdefghilmnopqrstuvwxyz".to_string()
}

fn default_description() -> String {
"Select an item and press Enter = accept, Esc = cancel, / = filter".to_string()
}

fn default_fuzzy_description() -> String {
"Fuzzy matching: ".to_string()
}

#[derive(Debug, Clone, PartialEq, FromDynamic, ToDynamic)]
Expand Down
112 changes: 110 additions & 2 deletions docs/config/lua/keyassignment/InputSelector.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ to select from.
When the user accepts a line, emits an event that allows you to act
upon the input.

`InputSelector` accepts three fields:
`InputSelector` accepts the following fields:

* `title` - the title that will be set for the overlay pane
* `choices` - a lua table consisting of the potential choices. Each entry
Expand All @@ -22,6 +22,51 @@ upon the input.
objects from the current pane and window, and `id` and `label` hold the
corresponding fields from the selected choice. Both will be `nil` if
the overlay is cancelled without selecting anything.
* `fuzzy` - a boolean that defaults to `false`. If `true`, InputSelector will start
Danielkonge marked this conversation as resolved.
Show resolved Hide resolved
in its fuzzy finding mode (this is equivalent to starting the InputSelector and
pressing / in the default mode).

{{since('nightly')}}

These additional fields are also available:

* `alphabet` - a string of unique characters. The characters in the string are used
Danielkonge marked this conversation as resolved.
Show resolved Hide resolved
to calculate one or two click shortcuts that can be used to quickly choose from
the InputSelector when in the default mode. Defaults to:
`"1234567890abcdefghilmnopqrstuvwxyz"`. (Without j/k so they can be used for movement
up and down.)
* `description` - a string to display when in the default mode. Defaults to:
`"Select an item and press Enter = accept, Esc = cancel, / = filter"`.
* `fuzzy_description` - a string to display when in fuzzy finding mode. Defaults to:
`"Fuzzy matching: "`.


### Key Assignments

The default key assignments in the InputSelector are as follows:

| Action | Key Assignment |
|---------|-------------------|
| Add to selection string until a match is found (if in the default mode) | Any key in `alphabet` {{since('nightly', inline=True)}} |
| Select matching number (if in the default mode) | <kbd>1</kbd> to <kbd>9</kbd> {{since('20230408-112425-69ae8472', inline=True)}} |
| Start fuzzy search (if in the default mode) | <kbd>/</kbd> |
| Add to filtering string (if in fuzzy finding mode) | Any key not listed below |
| Remove from selection or filtering string | <kbd>Backspace</kbd> |
| Pick currently highlighted line | <kbd>Enter</kbd> |
| | <kbd>LeftClick</kbd> (with mouse) |
| Move Down | <kbd>DownArrow</kbd> |
| | <kbd>Ctrl</kbd> + <kbd>N</kbd> |
| | <kbd>Ctrl</kbd> + <kbd>J</kbd> {{since('nightly', inline=True)}} |
| | <kbd>j</kbd> (if not in `alphabet`) |
| Move Up | <kbd>UpArrow</kbd> |
| | <kbd>Ctrl</kbd> + <kbd>P</kbd> |
| | <kbd>Ctrl</kbd> + <kbd>K</kbd> {{since('nightly', inline=True)}} |
| | <kbd>k</kbd> (if not in `alphabet`) |
| Quit | <kbd>Ctrl</kbd> + <kbd>G</kbd> |
| | <kbd>Ctrl</kbd> + <kbd>C</kbd> {{since('nightly', inline=True)}} |
| | <kbd>Escape</kbd> |

Note: If the InputSelector is started with `fuzzy` set to `false`, then <kbd>Backspace</kbd> can go from fuzzy finding mode back to the default mode when pressed while the filtering string is empty.

## Example of choosing some canned text to enter into the terminal

Expand Down Expand Up @@ -95,7 +140,7 @@ config.keys = {
-- could read or compute data from other sources

local choices = {}
for n = 1, 10 do
for n = 1, 20 do
table.insert(choices, { label = tostring(n) })
end

Expand All @@ -113,6 +158,8 @@ config.keys = {
end),
title = 'I am title',
choices = choices,
alphabet = '123456789',
description = 'Write the number you want to choose or press / to search.',
},
pane
)
Expand All @@ -123,5 +170,66 @@ config.keys = {
return config
```

## Example of switching between a list of workspaces with the InputSelector

```lua
local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()

config.keys = {
{
key = 'S',
mods = 'CTRL|SHIFT',
action = wezterm.action_callback(function(window, pane)
-- Here you can dynamically construct a longer list if needed

local home = wezterm.home_dir
local workspaces = {
{ id = home, label = 'Home' },
{ id = home .. '/work', label = 'Work' },
{ id = home .. '/personal', label = 'Personal' },
{ id = home .. '/.config', label = 'Config' },
}

window:perform_action(
act.InputSelector {
action = wezterm.action_callback(
function(inner_window, inner_pane, id, label)
if not id and not label then
wezterm.log_info 'cancelled'
else
wezterm.log_info('id = ' .. id)
wezterm.log_info('label = ' .. label)
inner_window:perform_action(
act.SwitchToWorkspace {
name = label,
spawn = {
label = 'Workspace: ' .. label,
cwd = id,
},
},
inner_pane
)
end
end
),
title = 'Choose Workspace',
choices = workspaces,
fuzzy = true,
fuzzy_description = 'Fuzzy find and/or make a workspace',
},
pane
)
end),
},
}

return config
```




See also [PromptInputLine](PromptInputLine.md).

54 changes: 50 additions & 4 deletions wezterm-gui/src/overlay/quickselect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,32 @@ const PATTERNS: [&str; 14] = [
/// It is derived from https://github.com/fcsonline/tmux-thumbs/blob/master/src/alphabets.rs
/// which is Copyright (c) 2019 Ferran Basora and provided under the MIT license
pub fn compute_labels_for_alphabet(alphabet: &str, num_matches: usize) -> Vec<String> {
let alphabet = alphabet
.chars()
.map(|c| c.to_lowercase().to_string())
.collect::<Vec<String>>();
compute_labels_for_alphabet_impl(alphabet, num_matches, true)
}

pub fn compute_labels_for_alphabet_with_preserved_case(
alphabet: &str,
num_matches: usize,
) -> Vec<String> {
compute_labels_for_alphabet_impl(alphabet, num_matches, false)
}

fn compute_labels_for_alphabet_impl(
alphabet: &str,
num_matches: usize,
make_lowercase: bool,
) -> Vec<String> {
let alphabet = if make_lowercase {
alphabet
.chars()
.map(|c| c.to_lowercase().to_string())
.collect::<Vec<String>>()
} else {
alphabet
.chars()
.map(|c| c.to_string())
.collect::<Vec<String>>()
};
// Prefer to use single character matches to represent everything
let mut primary = alphabet.clone();
let mut secondary = vec![];
Expand Down Expand Up @@ -144,6 +166,30 @@ mod alphabet_test {
vec!["aa", "ab", "ba", "bb"]
);
}

#[test]
fn composed_capital() {
assert_eq!(
compute_labels_for_alphabet_with_preserved_case("AB", 4),
vec!["AA", "AB", "BA", "BB"]
);
}

#[test]
fn composed_mixed() {
assert_eq!(
compute_labels_for_alphabet_with_preserved_case("aA", 4),
vec!["aa", "aA", "Aa", "AA"]
);
}

#[test]
fn lowercase_alphabet_equal() {
assert_eq!(
compute_labels_for_alphabet_with_preserved_case("abc123", 12),
compute_labels_for_alphabet("abc123", 12)
);
}
}

pub struct QuickSelectOverlay {
Expand Down
Loading
Loading