1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-20 19:27:22 +03:00

add window:dead_key_is_active window:leader_is_active

This commit adds a couple of helper methods that provide insight into
the state of the keyboard layer.

The intent is for users to add status information about the keyboard
state.

This commit also ensures that we schedule an update when the leader
key duration expires, and ensures that we close out the leader state
for an invalid key press.

refs: #686
closes: #688

```lua
local wezterm = require 'wezterm';

wezterm.on("update-right-status", function(window, pane)
  local leader = ""
  if window:leader_is_active() then
    leader = "LEADER"
  end
  local dead = ""
  if window:dead_key_active() then
    dead = "DEAD"
  end
  window:set_right_status(leader .. " " .. dead)
end);

return {
  leader = { key="a", mods="CTRL" },
  colors = {
    dead_key_cursor = "orange",
  },
}
```
This commit is contained in:
Wez Furlong 2022-01-02 19:41:21 -07:00
parent c29212be49
commit 028026049d
7 changed files with 171 additions and 35 deletions

View File

@ -12,6 +12,10 @@ usually the best available version.
As features stabilize some brief notes about them will accumulate here.
#### New
* [window:dead_key_is_active()](config/lua/window/dead_key_is_active.md) and [window:leader_is_active()](config/lua/window/leader_is_active.md) methods that can help populate [window:set_right_status()](config/lua/window/set_right_status.md) [#686](https://github.com/wez/wezterm/issues/686)
* You may now use `colors = { dead_key_cursor = "orange" }` to change the cursor color when either a dead key or leader key is active.
#### Changed
#### Updated and Improved
#### Fixed

View File

@ -0,0 +1,30 @@
# wezterm:dead_key_is_active()
*Since: nightly builds only*
Returns `true` if a dead key is active in the window, or false otherwise.
This example shows `DEAD` in the right status area, and turns the cursor orange,
when a dead key is active:
```lua
local wezterm = require 'wezterm';
wezterm.on("update-right-status", function(window, pane)
local dead = ""
if window:dead_key_is_active() then
dead = "DEAD"
end
window:set_right_status(dead)
end);
return {
leader = { key="a", mods="CTRL" },
colors = {
dead_key_cursor = "orange",
},
}
```
See also: [window:leader_is_active()](leader_is_active.md).

View File

@ -0,0 +1,29 @@
# wezterm:leader_is_active()
*Since: nightly builds only*
Returns `true` if the [Leader Key](../../keys.md) is active in the window, or false otherwise.
This example shows `LEADER` in the right status area, and turns the cursor orange,
when the leader is active:
```lua
local wezterm = require 'wezterm';
wezterm.on("update-right-status", function(window, pane)
local leader = ""
if window:leader_is_active() then
leader = "LEADER"
end
window:set_right_status(leader)
end);
return {
leader = { key="a", mods="CTRL" },
colors = {
dead_key_cursor = "orange",
},
}
```
See also: [window:dead_key_is_active()](dead_key_is_active.md).

View File

@ -132,5 +132,33 @@ impl UserData for GuiWin {
.notify(TermWindowNotif::SetConfigOverrides(value.0));
Ok(())
});
methods.add_async_method("leader_is_active", |_, this, _: ()| async move {
let (tx, rx) = smol::channel::bounded(1);
this.window
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
tx.try_send(term_window.leader_is_active()).ok();
})));
let result = rx
.recv()
.await
.map_err(|e| anyhow::anyhow!("{:#}", e))
.map_err(luaerr)?;
Ok(result)
});
methods.add_async_method("dead_key_is_active", |_, this, _: ()| async move {
let (tx, rx) = smol::channel::bounded(1);
this.window
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
tx.try_send(term_window.dead_key_active()).ok();
})));
let result = rx
.recv()
.await
.map_err(|e| anyhow::anyhow!("{:#}", e))
.map_err(luaerr)?;
Ok(result)
});
}
}

View File

@ -1,5 +1,6 @@
use ::window::{KeyCode, KeyEvent, Modifiers, RawKeyEvent, WindowOps};
use ::window::{DeadKeyStatus, KeyCode, KeyEvent, Modifiers, RawKeyEvent, WindowOps};
use mux::pane::Pane;
use smol::Timer;
use std::rc::Rc;
pub fn window_mods_to_termwiz_mods(modifiers: ::window::Modifiers) -> termwiz::input::Modifiers {
@ -56,8 +57,18 @@ impl super::TermWindow {
// Check to see if this key-press is the leader activating
if let Some(duration) = self.input_map.is_leader(&keycode, raw_modifiers) {
// Yes; record its expiration
self.leader_is_down
.replace(std::time::Instant::now() + duration);
let target = std::time::Instant::now() + duration;
self.leader_is_down.replace(target);
self.update_title();
// schedule an invalidation so that the cursor or status
// area will be repainted at the right time
if let Some(window) = self.window.clone() {
promise::spawn::spawn(async move {
Timer::at(target).await;
window.invalidate();
})
.detach();
}
return true;
}
}
@ -72,7 +83,7 @@ impl super::TermWindow {
if leader_active {
// A successful leader key-lookup cancels the leader
// virtual modifier state
self.leader_is_down.take();
self.leader_done();
}
return true;
}
@ -144,17 +155,11 @@ impl super::TermWindow {
// The leader key is a kind of modal modifier key.
// It is allowed to be active for up to the leader timeout duration,
// after which it auto-deactivates.
let (leader_active, leader_mod) = match self.leader_is_down.as_ref() {
Some(expiry) if *expiry > std::time::Instant::now() => {
// Currently active
(true, Modifiers::LEADER)
}
Some(_) => {
// Expired; clear out the old expiration time
self.leader_is_down.take();
(false, Modifiers::NONE)
}
_ => (false, Modifiers::NONE),
let (leader_active, leader_mod) = if self.leader_is_active_mut() {
// Currently active
(true, Modifiers::LEADER)
} else {
(false, Modifiers::NONE)
};
let pane = match self.get_active_pane_or_overlay() {
@ -220,6 +225,43 @@ impl super::TermWindow {
}
}
pub fn leader_is_active(&self) -> bool {
match self.leader_is_down.as_ref() {
Some(expiry) if *expiry > std::time::Instant::now() => {
self.update_next_frame_time(Some(*expiry));
true
}
Some(_) => false,
None => false,
}
}
pub fn leader_is_active_mut(&mut self) -> bool {
match self.leader_is_down.as_ref() {
Some(expiry) if *expiry > std::time::Instant::now() => {
self.update_next_frame_time(Some(*expiry));
true
}
Some(_) => {
self.leader_done();
false
}
None => false,
}
}
pub fn dead_key_active(&self) -> bool {
self.dead_key_status == DeadKeyStatus::Holding
}
fn leader_done(&mut self) {
self.leader_is_down.take();
self.update_title();
if let Some(window) = &self.window {
window.invalidate();
}
}
pub fn key_event_impl(&mut self, window_key: KeyEvent, context: &dyn WindowOps) {
if !window_key.key_is_down {
return;
@ -239,17 +281,11 @@ impl super::TermWindow {
// The leader key is a kind of modal modifier key.
// It is allowed to be active for up to the leader timeout duration,
// after which it auto-deactivates.
let (leader_active, leader_mod) = match self.leader_is_down.as_ref() {
Some(expiry) if *expiry > std::time::Instant::now() => {
// Currently active
(true, Modifiers::LEADER)
}
Some(_) => {
// Expired; clear out the old expiration time
self.leader_is_down.take();
(false, Modifiers::NONE)
}
_ => (false, Modifiers::NONE),
let (leader_active, leader_mod) = if self.leader_is_active_mut() {
// Currently active
(true, Modifiers::LEADER)
} else {
(false, Modifiers::NONE)
};
let modifiers = window_mods_to_termwiz_mods(window_key.modifiers);
@ -318,8 +354,17 @@ impl super::TermWindow {
}
let key = self.win_key_code_to_termwiz_key_code(&window_key.key);
match key {
Key::Code(key) => {
if leader_active && !key.is_modifier() {
// Leader was pressed and this non-modifier keypress isn't
// a registered key binding; swallow this event and cancel
// the leader modifier.
self.leader_done();
return;
}
if pane.key_down(key, modifiers).is_ok() {
if !key.is_modifier() && self.pane_state(pane.pane_id()).overlay.is_none() {
self.maybe_scroll_to_bottom_for_input(&pane);
@ -335,12 +380,12 @@ impl super::TermWindow {
// Leader was pressed and this non-modifier keypress isn't
// a registered key binding; swallow this event and cancel
// the leader modifier.
self.leader_is_down.take();
} else {
pane.writer().write_all(s.as_bytes()).ok();
self.maybe_scroll_to_bottom_for_input(&pane);
context.invalidate();
self.leader_done();
return;
}
pane.writer().write_all(s.as_bytes()).ok();
self.maybe_scroll_to_bottom_for_input(&pane);
context.invalidate();
}
Key::None => {}
}

View File

@ -812,9 +812,9 @@ impl TermWindow {
Ok(true)
}
WindowEvent::AdviseDeadKeyStatus(status) => {
log::warn!("DeadKeyStatus now: {:?}", status);
log::trace!("DeadKeyStatus now: {:?}", status);
self.dead_key_status = status;
window.invalidate();
self.update_title();
Ok(true)
}
WindowEvent::NeedRepaint => Ok(self.do_paint(window)),

View File

@ -315,7 +315,7 @@ impl super::TermWindow {
}
}
fn update_next_frame_time(&self, next_due: Option<Instant>) {
pub fn update_next_frame_time(&self, next_due: Option<Instant>) {
if let Some(next_due) = next_due {
let mut has_anim = self.has_animation.borrow_mut();
match *has_anim {
@ -2408,7 +2408,7 @@ impl super::TermWindow {
}
let dead_key_or_leader =
self.dead_key_status == DeadKeyStatus::Holding || self.leader_is_down.is_some();
self.dead_key_status == DeadKeyStatus::Holding || self.leader_is_active();
if dead_key_or_leader {
let (fg_color, bg_color) = if self.config.force_reverse_video_cursor {