1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 13:21:38 +03:00

Add PromptInputLine and examples of (re)naming tabs/workspaces

refs: https://github.com/wez/wezterm/issues/522
refs: https://github.com/wez/wezterm/issues/1598
This commit is contained in:
Wez Furlong 2023-04-04 21:11:08 -07:00
parent b2b5c64aff
commit 336fefb52f
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
8 changed files with 288 additions and 2 deletions

View File

@ -445,6 +445,14 @@ pub struct QuickSelectArguments {
pub scope_lines: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, FromDynamic, ToDynamic)]
pub struct PromptInputLine {
pub action: Box<KeyAssignment>,
/// Descriptive text to show ahead of prompt
#[dynamic(default)]
pub description: String,
}
#[derive(Debug, Clone, PartialEq, FromDynamic, ToDynamic)]
pub enum KeyAssignment {
SpawnTab(SpawnTabDomain),
@ -550,6 +558,7 @@ pub enum KeyAssignment {
ActivateWindow(usize),
ActivateWindowRelative(isize),
ActivateWindowRelativeNoWrap(isize),
PromptInputLine(PromptInputLine),
}
impl_lua_conversion_dynamic!(KeyAssignment);

View File

@ -39,6 +39,9 @@ As features stabilize some brief notes about them will accumulate here.
expect. #2644
#### New
* [PromptInputLine](config/lua/keyassignment/PromptInputLine.md) action for
prompting the user for a line of text and then doing something with it.
Can be used to prompt for (re)naming new or existing tabs, workspaces and so on.
* [pane:activate()](config/lua/pane/activate.md) and [tab:activate()](config/lua/MuxTab/activate.md). #3217
* [ulimit_nofile](config/lua/config/ulimit_nofile.md) and [ulimint_nproc](config/lua/config/ulimit_nproc.md) options. ?3353
* [serial_ports](config/lua/config/serial_ports.md) for more convenient access to serial ports

View File

@ -0,0 +1,90 @@
# `PromptInputLine`
{{since('nightly')}}
Activates an overlay to display a prompt and request a line of input
from the user.
When the user enters the line, emits an event and allows you to act
upon the input.
`PromptInputLine` accepts two fields:
* `description` - the text to show at the top of the display area. You may
embed escape sequences and/or use [wezterm.format](../wezterm/format.md).
* `action` - and event callback registerd via `wezterm.action_callback`. The
callback's function signature is `(window, pane, line)` where `window` and
`pane` are the [Window](../window/index.md) and [Pane](../pane/index.md)
objects from the current pane and window, and `line` is the text that the
user entered. `line` may be `nil` if they hit Escape without entering
anything, or CTRL-C to cancel the input.
## Example of interactively renaming the current tab
```lua
local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()
config.keys = {
{
key = 'E',
mods = 'CTRL|SHIFT',
action = act.PromptInputLine {
description = 'Enter new name for tab',
action = wezterm.action_callback(function(window, pane, line)
-- line will be `nil` if they hit escape without entering anything
-- An empty string if they just hit enter
-- Or the actual line of text they wrote
if line then
window:active_tab():set_title(line)
end
end),
},
},
}
return config
```
## Example of interactively picking a name and creating a new workspace
Similar to the above, but prompts for a name prior to creating
the workspace.
This example also shows the use of `wezterm.format` to emit colored text.
```lua
local wezterm = require 'wezterm'
local act = wezterm.action
local config = wezterm.config_builder()
config.keys = {
{
key = 'N',
mods = 'CTRL|SHIFT',
action = act.PromptInputLine {
description = wezterm.format {
{ Attribute = { Intensity = 'Bold' } },
{ Foreground = { AnsiColor = 'Fuchsia' } },
{ Text = 'Enter name for new workspace' },
},
action = wezterm.action_callback(function(window, pane, line)
-- line will be `nil` if they hit escape without entering anything
-- An empty string if they just hit enter
-- Or the actual line of text they wrote
if line then
window:perform_action(
act.SwitchToWorkspace {
name = line,
},
pane
)
end
end),
},
},
}
return config
```

View File

@ -48,5 +48,49 @@ config.keys = {
},
},
}
return config
```
## Prompting for the workspace name
{{since('nightly')}}
```lua
local act = wezterm.action
wezterm.on('update-right-status', function(window, pane)
window:set_right_status(window:active_workspace())
end)
config.keys = {
-- Prompt for a name to use for a new workspace and switch to it.
{
key = 'W',
mods = 'CTRL|SHIFT',
action = act.PromptInputLine {
description = wezterm.format {
{ Attribute = { Intensity = 'Bold' } },
{ Foreground = { AnsiColor = 'Fuchsia' } },
{ Text = 'Enter name for new workspace' },
},
action = wezterm.action_callback(function(window, pane, line)
-- line will be `nil` if they hit escape without entering anything
-- An empty string if they just hit enter
-- Or the actual line of text they wrote
if line then
window:perform_action(
act.SwitchToWorkspace {
name = line,
},
pane
)
end
end),
},
},
}
return config
```

View File

@ -736,6 +736,14 @@ pub fn derive_command_from_key_assignment(action: &KeyAssignment) -> Option<Comm
menubar: &["Help"],
icon: Some("cod_debug"),
},
PromptInputLine(_) => CommandDef {
brief: "Prompt the user for a line of text".into(),
doc: "Activates the prompt overlay and wait for input".into(),
keys: vec![],
args: &[ArgType::ActiveWindow],
menubar: &[],
icon: None,
},
QuickSelect => CommandDef {
brief: "Enter QuickSelect mode".into(),
doc: "Activates the quick selection UI for the current pane".into(),

View File

@ -10,6 +10,7 @@ pub mod confirm_close_pane;
pub mod copy;
pub mod debug;
pub mod launcher;
pub mod prompt;
pub mod quickselect;
pub use confirm_close_pane::{

View File

@ -0,0 +1,106 @@
use crate::scripting::guiwin::GuiWin;
use config::keyassignment::{KeyAssignment, PromptInputLine};
use mux::termwiztermtab::TermWizTerminal;
use mux_lua::MuxPane;
use std::rc::Rc;
use termwiz::input::{InputEvent, KeyCode, KeyEvent};
use termwiz::lineedit::*;
use termwiz::surface::Change;
use termwiz::terminal::Terminal;
struct PromptHost {
history: BasicHistory,
}
impl PromptHost {
fn new() -> Self {
Self {
history: BasicHistory::default(),
}
}
}
impl LineEditorHost for PromptHost {
fn history(&mut self) -> &mut dyn History {
&mut self.history
}
fn resolve_action(
&mut self,
event: &InputEvent,
editor: &mut LineEditor<'_>,
) -> Option<Action> {
let (line, _cursor) = editor.get_line_and_cursor();
if line.is_empty()
&& matches!(
event,
InputEvent::Key(KeyEvent {
key: KeyCode::Escape,
..
})
)
{
Some(Action::Cancel)
} else {
None
}
}
}
pub fn show_line_prompt_overlay(
mut term: TermWizTerminal,
args: PromptInputLine,
window: GuiWin,
pane: MuxPane,
) -> anyhow::Result<()> {
let name = match *args.action {
KeyAssignment::EmitEvent(id) => id,
_ => anyhow::bail!(
"PromptInputLine requires action to be defined by wezterm.action_callback"
),
};
term.no_grab_mouse_in_raw_mode();
let mut text = args.description.replace("\r\n", "\n").replace("\n", "\r\n");
text.push_str("\r\n");
term.render(&[Change::Text(text)])?;
let mut host = PromptHost::new();
let mut editor = LineEditor::new(&mut term);
editor.set_prompt("> ");
let line = editor.read_line(&mut host)?;
promise::spawn::spawn_into_main_thread(async move {
trampoline(name, window, pane, line);
anyhow::Result::<()>::Ok(())
})
.detach();
Ok(())
}
fn trampoline(name: String, window: GuiWin, pane: MuxPane, line: Option<String>) {
promise::spawn::spawn(async move {
config::with_lua_config_on_main_thread(move |lua| do_event(lua, name, window, pane, line))
.await
})
.detach();
}
async fn do_event(
lua: Option<Rc<mlua::Lua>>,
name: String,
window: GuiWin,
pane: MuxPane,
line: Option<String>,
) -> anyhow::Result<()> {
if let Some(lua) = lua {
let args = lua.pack_multi((window, pane, line))?;
if let Err(err) = config::lua::emit_event(&lua, (name.clone(), args)).await {
log::error!("while processing {} event: {:#}", name, err);
}
}
Ok(())
}

View File

@ -28,8 +28,8 @@ use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
use ::window::*;
use anyhow::{anyhow, ensure, Context};
use config::keyassignment::{
KeyAssignment, PaneDirection, Pattern, QuickSelectArguments, RotationDirection, SpawnCommand,
SplitSize,
KeyAssignment, PaneDirection, Pattern, PromptInputLine, QuickSelectArguments,
RotationDirection, SpawnCommand, SplitSize,
};
use config::{
configuration, AudibleBell, ConfigHandle, Dimension, DimensionContext, FrontEndSelection,
@ -2109,6 +2109,30 @@ impl TermWindow {
Ok(())
}
fn show_prompt_input_line(&mut self, args: &PromptInputLine) {
let mux = Mux::get();
let tab = match mux.get_active_tab_for_window(self.mux_window_id) {
Some(tab) => tab,
None => return,
};
let pane = match self.get_active_pane_or_overlay() {
Some(pane) => pane,
None => return,
};
let args = args.clone();
let gui_win = GuiWin::new(self);
let pane = MuxPane(pane.pane_id());
let (overlay, future) = start_overlay(self, &tab, move |_tab_id, term| {
crate::overlay::prompt::show_line_prompt_overlay(term, args, gui_win, pane)
});
self.assign_overlay(tab.tab_id(), overlay);
promise::spawn::spawn(future).detach();
}
fn show_debug_overlay(&mut self) {
let mux = Mux::get();
let tab = match mux.get_active_tab_for_window(self.mux_window_id) {
@ -2862,6 +2886,7 @@ impl TermWindow {
let modal = crate::termwindow::palette::CommandPalette::new(self);
self.set_modal(Rc::new(modal));
}
PromptInputLine(args) => self.show_prompt_input_line(args),
};
Ok(PerformAssignmentResult::Handled)
}