1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 15:04:36 +03:00

keys: add ActivateKeyTable::until_unknown=true

This will implicitly pop key table entries if they don't match keys.

refs: https://github.com/wez/wezterm/issues/2178
This commit is contained in:
Wez Furlong 2022-06-30 07:14:22 -07:00
parent 556a6b855a
commit 1e85e79fc7
5 changed files with 93 additions and 31 deletions

View File

@ -385,6 +385,8 @@ pub enum KeyAssignment {
replace_current: bool,
#[dynamic(default = "crate::default_true")]
one_shot: bool,
#[dynamic(default)]
until_unknown: bool,
},
PopKeyTable,
ClearKeyTableStack,

View File

@ -11,6 +11,9 @@ usually the best available version.
As features stabilize some brief notes about them will accumulate here.
#### New
* [ActivateKeyTable](config/lua/keyassignment/ActivateKeyTable.md) now supports `until_unknown=true` to implicitly pop the table when a key not defined by that table is pressed. [#2178](https://github.com/wez/wezterm/issues/2178)
#### Fixed
* [ActivateKeyTable](config/lua/keyassignment/ActivateKeyTable.md)'s `replace_current` field was not actually optional. Made it optional. [#2179](https://github.com/wez/wezterm/issues/2179)
* `winget` causes toast notification spam [#2185](https://github.com/wez/wezterm/issues/2185)

View File

@ -11,4 +11,5 @@ The following parameters are possible:
* `name` - the name of the table to activate. The name must match up to an entry in the `key_tables` configuration.
* `timeout_milliseconds` - an optional duration expressed in milliseconds. If specified, then the activation will automatically expire and pop itself from the key table stack once that duration elapses. If omitted, this activation will not expire due to time.
* `one_shot` - an optional boolean that controls whether the activation will pop itself after a single additional key press. The default if left unspecified is `one_shot=true`. When set to `false`, pressing a key will not automatically pop the activation and you will need to use either a timeout or an explicit key assignment that triggers [PopKeyTable](PopKeyTable.md) to cancel the activation.
* `replace_current` - an optional boolean. Defaults to `false` is unspecified. If set to `true` then behave as though [PopKeyTable](PopKeyTable.md) was triggered before pushing this new activation on the stack. This is most useful for key assignments in a table that was activated using `one_shot=false`.
* `replace_current` - an optional boolean. Defaults to `false` if unspecified. If set to `true` then behave as though [PopKeyTable](PopKeyTable.md) was triggered before pushing this new activation on the stack. This is most useful for key assignments in a table that was activated using `one_shot=false`.
* `until_unknown` - an optional boolean. Defaults to `false` if unspecified. If set to `true` then a key press that doesn't match any entries in the named key table will implicitly pop this entry from the stack. This can be used together with `timeout_milliseconds`. (*Since: nightly builds only*)

View File

@ -15,6 +15,16 @@ pub struct KeyTableStateEntry {
expiration: Option<Instant>,
/// Whether this activation pops itself after recognizing a key press
one_shot: bool,
until_unknown: bool,
}
#[derive(Debug, Clone)]
pub struct KeyTableArgs<'a> {
pub name: &'a str,
pub timeout_milliseconds: Option<u64>,
pub replace_current: bool,
pub one_shot: bool,
pub until_unknown: bool,
}
#[derive(Debug, Default, Clone)]
@ -23,20 +33,17 @@ pub struct KeyTableState {
}
impl KeyTableState {
pub fn activate(
&mut self,
name: &str,
timeout_milliseconds: Option<u64>,
replace_current: bool,
one_shot: bool,
) {
if replace_current {
pub fn activate(&mut self, args: KeyTableArgs) {
if args.replace_current {
self.pop();
}
self.stack.push(KeyTableStateEntry {
name: name.to_string(),
expiration: timeout_milliseconds.map(|ms| Instant::now() + Duration::from_millis(ms)),
one_shot,
name: args.name.to_string(),
expiration: args
.timeout_milliseconds
.map(|ms| Instant::now() + Duration::from_millis(ms)),
one_shot: args.one_shot,
until_unknown: args.until_unknown,
});
}
@ -64,6 +71,17 @@ impl KeyTableState {
true
}
pub fn pop_until_unknown(&mut self) {
while self
.stack
.last()
.map(|entry| entry.until_unknown)
.unwrap_or(false)
{
self.pop();
}
}
pub fn current_table(&mut self) -> Option<&str> {
while self.process_expiration() {}
self.stack.last().map(|entry| entry.name.as_str())
@ -74,15 +92,42 @@ impl KeyTableState {
input_map: &InputMap,
key: &KeyCode,
mods: Modifiers,
) -> Option<(KeyTableEntry, Option<&str>)> {
) -> Option<(KeyTableEntry, Option<String>)> {
while self.process_expiration() {}
let mut pop_count = 0;
let mut result = None;
for entry in self.stack.iter().rev() {
let name = entry.name.as_str();
if let Some(entry) = input_map.lookup_key(key, mods, Some(name)) {
return Some((entry, Some(name)));
result = Some((entry, Some(name.to_string())));
break;
}
if entry.until_unknown {
pop_count += 1;
}
}
None
// This is a little bit tricky: until_unknown needs to
// pop entries if we didn't match, but since we need to
// make three separate passes to resolve a key using its
// various physical, mapped and raw forms, we cannot
// unilaterally pop here without breaking a later pass.
// It is only safe to pop here if we did match something:
// in that case we know that we won't make additional
// passes.
// It is important that `pop_until_unknown` is called
// in the final "no keys matched" case to correctly
// manage that state transition.
if result.is_some() {
for _ in 0..pop_count {
self.pop();
}
}
result
}
pub fn did_process_key(&mut self) {
@ -501,6 +546,13 @@ impl super::TermWindow {
return;
}
// If we get here, then none of the keys matched
// any key table rules. Therefore, we should pop all `until_unknown`
// entries from the stack.
if window_key.key_is_down {
self.key_table_state.pop_until_unknown();
}
let key = self.win_key_code_to_termwiz_key_code(&window_key.key);
match key {

View File

@ -20,7 +20,7 @@ use crate::tabbar::{TabBarItem, TabBarState};
use crate::termwindow::background::{
load_background_image, reload_background_image, LoadedBackgroundLayer,
};
use crate::termwindow::keyevent::KeyTableState;
use crate::termwindow::keyevent::{KeyTableArgs, KeyTableState};
use crate::termwindow::modal::Modal;
use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
use ::window::*;
@ -1978,18 +1978,20 @@ impl TermWindow {
timeout_milliseconds,
replace_current,
one_shot,
until_unknown,
} => {
anyhow::ensure!(
self.input_map.has_table(name),
"ActivateKeyTable: no key_table named {}",
name
);
self.key_table_state.activate(
self.key_table_state.activate(KeyTableArgs {
name,
*timeout_milliseconds,
*replace_current,
*one_shot,
);
timeout_milliseconds: *timeout_milliseconds,
replace_current: *replace_current,
one_shot: *one_shot,
until_unknown: *until_unknown,
});
self.update_title();
}
PopKeyTable => {
@ -2224,12 +2226,13 @@ impl TermWindow {
.overlay
.as_mut()
.map(|overlay| {
overlay.key_table_state.activate(
"search_mode",
None,
overlay.key_table_state.activate(KeyTableArgs {
name: "search_mode",
timeout_milliseconds: None,
replace_current,
false,
);
one_shot: false,
until_unknown: false,
});
});
}
}
@ -2272,12 +2275,13 @@ impl TermWindow {
.overlay
.as_mut()
.map(|overlay| {
overlay.key_table_state.activate(
"copy_mode",
None,
overlay.key_table_state.activate(KeyTableArgs {
name: "copy_mode",
timeout_milliseconds: None,
replace_current,
false,
);
one_shot: false,
until_unknown: false,
});
});
}
}