Skip to content

Commit

Permalink
Additions to the InputSelector (resolving #4226) (#4227)
Browse files Browse the repository at this point in the history
* First step towards implementing #4226.
* Added InputSelector config options.
* Format + fix to prepare for PR to resolve #4226.
* Accept suggestion about pop().
* Fix typo.
* Tried fixing stuff from the feedback.
* Fixed typo.
* Fixed small mistake in docs.
* Small fix for label computation.
* Allow uppercase alphabet + add fuzzy_description.
* Minor cleanup.
* Use more standard alphabet (without j/k).
* Fixed docs after previous commit.
* Added key assignments to docs.
* Apply suggestions from code review (2 remaining)
* Updated arcording to feedback (added tests).
* Update docs (1 thing left to do)
* Added version details to the key table.

---------

Co-authored-by: Wez Furlong <wez@wezfurlong.org>
  • Loading branch information
Danielkonge and wez authored Sep 22, 2023
1 parent a061bde commit 525080b
Show file tree
Hide file tree
Showing 4 changed files with 232 additions and 33 deletions.
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,
}

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
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
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

0 comments on commit 525080b

Please sign in to comment.