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

quickselect: add QuickSelectArgs and helpers for opening urls

This commit expands quick select mode so that you can trigger it
with distinct sets of patterns (eg: urls on one key assignment,
hashes on a different key assignment), different alphabets,
and lastly, the option to perform a different action from
the default copy action.

You can pair this with `action_callback` to run lua code to
do something with the selected text.

This commit also adds `wezterm.open_with`, a helper function
for opening documents/URLs.

refs: #846
refs: #1362
This commit is contained in:
Wez Furlong 2021-12-23 10:55:44 -07:00
parent 6a265d9f72
commit 668d41ad5d
10 changed files with 183 additions and 37 deletions

1
Cargo.lock generated
View File

@ -681,6 +681,7 @@ dependencies = [
"luahelper",
"mlua",
"notify",
"open",
"portable-pty",
"pretty_env_logger",
"promise",

View File

@ -28,6 +28,7 @@ luahelper = { path = "../luahelper" }
mlua = {version="0.5", features=["vendored", "lua54", "async", "send"]}
# file change notification
notify = "4.0"
open = "2.0"
portable-pty = { path = "../pty", features = ["serde_support"]}
promise = { path = "../promise" }
serde = {version="1.0", features = ["rc", "derive"]}

View File

@ -149,6 +149,18 @@ impl Default for ClipboardPasteSource {
}
}
#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct QuickSelectArguments {
/// Overrides the main quick_select_alphabet config
#[serde(default)]
pub alphabet: String,
/// Overrides the main quick_select_patterns config
#[serde(default)]
pub patterns: Vec<String>,
#[serde(default)]
pub action: Option<Box<KeyAssignment>>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum KeyAssignment {
SpawnTab(SpawnTabDomain),
@ -208,6 +220,7 @@ pub enum KeyAssignment {
CloseCurrentPane { confirm: bool },
EmitEvent(String),
QuickSelect,
QuickSelectArgs(QuickSelectArguments),
Multiple(Vec<KeyAssignment>),
}

View File

@ -259,6 +259,9 @@ pub fn make_lua_context(config_file: &Path) -> anyhow::Result<Lua> {
"background_child_process",
lua.create_async_function(background_child_process)?,
)?;
wezterm_mod.set(
"open_with",
lua.create_function(open_with)?)?;
wezterm_mod.set("on", lua.create_function(register_event)?)?;
wezterm_mod.set("emit", lua.create_async_function(emit_event)?)?;
wezterm_mod.set("sleep_ms", lua.create_async_function(sleep_ms)?)?;
@ -445,6 +448,15 @@ async fn sleep_ms<'lua>(_: &'lua Lua, milliseconds: u64) -> mlua::Result<()> {
Ok(())
}
fn open_with<'lua>(_:&'lua Lua, (url, app): (String, Option<String>)) -> mlua::Result<()> {
if let Some(app) = app {
open::with_in_background(url, app);
} else {
open::that_in_background(url);
}
Ok(())
}
/// Returns the system hostname.
/// Errors may occur while retrieving the hostname from the system,
/// or if the hostname isn't a UTF-8 string.

View File

@ -20,6 +20,8 @@ As features stabilize some brief notes about them will accumulate here.
* [mux-is-process-stateful](config/lua/mux-events/mux-is-process-stateful.md) event for finer control over prompting when closing panes. [#1412](https://github.com/wez/wezterm/issues/1412)
* [harfbuzz_features](config/font-shaping.md), [freetype_load_target](config/lua/config/freetype_load_target.md), [freetype_render_target](config/lua/config/freetype_render_target.md) and [freetype_load_flags](config/lua/config/freetype_load_flags.md) can now be overridden on a per-font basis as described in [wezterm.font](config/lua/wezterm/font.md) and [wezterm.font_with_fallback](config/lua/wezterm/font_with_fallback.md).
* [ActivateTabRelativeNoWrap](config/lua/keyassignment/ActivateTabRelativeNoWrap.md) key assignment [#1414](https://github.com/wez/wezterm/issues/1414)
* [QuickSelectArgs](config/lua/keyassignment/QuickSelectArgs.md) key assignment [#846](https://github.com/wez/wezterm/issues/846) [#1362](https://github.com/wez/wezterm/issues/1362)
* [wezterm.open_wth](config/lua/wezterm/open_with.md) function for opening URLs/documents with the default or a specific application [#1362](https://github.com/wez/wezterm/issues/1362)
#### Changed

View File

@ -4,7 +4,6 @@
Activates [Quick Select Mode](../../../quickselect.md).
```lua
```lua
local wezterm = require 'wezterm';
return {
@ -13,3 +12,5 @@ return {
}
}
```
See also [QuickSelectArgs](QuickSelectArgs.md)

View File

@ -0,0 +1,60 @@
# QuickSelectArgs
*Since: nightly builds only*
Activates [Quick Select Mode](../../../quickselect.md) but with the option
to override the global configuration.
This example shows how to pop up a quick select that is scoped solely to
a very basic http regex; it will only match those regexes regardless of
the default or the [quick_select_patterns](../config/quick_select_patterns.md)
configuration:
```lua
local wezterm = require 'wezterm'
return {
keys = {
{key="P", mods="CTRL",
action=wezterm.action{QuickSelectArgs={
patterns={
"https?://\\S+"
},
}}
},
},
}
```
The `QuickSelectArgs` struct allows for the following fields:
* `patterns` - if present, completely overrides the normal set of patterns and uses only the patterns specified
* `alphabet` - if present, this alphabet is used instead of [quick_select_alphabet](../config/quick_select_alphabet.md)
* `action` - if present, this key assignment action is performed as if by [window:perform_action](../window/perform_action.md) when an item is selected. The normal clipboard action is NOT performed in this case.
Here's an example that shows how to trigger some lua code to operate on the
quick-selected text, instead of copying it to the clipboard. Here, we open
the selected URL using the web browser:
```lua
local wezterm = require 'wezterm'
return {
keys = {
{key="P", mods="CTRL",
action=wezterm.action{QuickSelectArgs={
patterns={
"https?://\\S+"
},
action = wezterm.action_callback(function(window, pane)
local url = window:get_selection_text_for_pane(pane)
wezterm.log_info("opening: " .. url)
wezterm.open_with(url)
end)
}}
},
},
}
```
See also [wezterm.open_with](../wezterm/open_with.md).

View File

@ -0,0 +1,16 @@
# `wezterm.open_with(path_or_url [, application])`
*Since: nightly builds only*
This function opens the specified `path_or_url` with either the specified
`application` or uses the default application if `application` was not passed
in.
```lua
-- Opens a URL in your default browser
wezterm.open_with("http://example.com")
-- Opens a URL specifically in firefox
wezterm.open_with("http://example.com", "firefox")
```

View File

@ -1,6 +1,6 @@
use crate::selection::{SelectionCoordinate, SelectionRange};
use crate::termwindow::{TermWindow, TermWindowNotif};
use config::keyassignment::{ClipboardCopyDestination, ScrollbackEraseMode};
use config::keyassignment::{ClipboardCopyDestination, QuickSelectArguments, ScrollbackEraseMode};
use config::ConfigHandle;
use mux::domain::DomainId;
use mux::pane::{Pane, PaneId, Pattern, SearchResult};
@ -169,29 +169,43 @@ struct QuickSelectRenderable {
window: ::window::Window,
config: ConfigHandle,
args: QuickSelectArguments,
}
impl QuickSelectOverlay {
pub fn with_pane(term_window: &TermWindow, pane: &Rc<dyn Pane>) -> Rc<dyn Pane> {
pub fn with_pane(
term_window: &TermWindow,
pane: &Rc<dyn Pane>,
args: &QuickSelectArguments,
) -> Rc<dyn Pane> {
let viewport = term_window.get_viewport(pane.pane_id());
let dims = pane.get_dimensions();
let config = term_window.config.clone();
let mut pattern = "(".to_string();
if !config.disable_default_quick_select_patterns {
for p in &PATTERNS {
if !args.patterns.is_empty() {
for p in &args.patterns {
if pattern.len() > 1 {
pattern.push('|');
}
pattern.push_str(p);
}
}
for p in &config.quick_select_patterns {
if pattern.len() > 1 {
pattern.push('|');
} else {
if !config.disable_default_quick_select_patterns {
for p in &PATTERNS {
if pattern.len() > 1 {
pattern.push('|');
}
pattern.push_str(p);
}
}
for p in &config.quick_select_patterns {
if pattern.len() > 1 {
pattern.push('|');
}
pattern.push_str(p);
}
pattern.push_str(p);
}
pattern.push(')');
@ -213,6 +227,7 @@ impl QuickSelectOverlay {
width: dims.cols,
height: dims.viewport_rows,
config,
args: args.clone(),
};
let search_row = renderer.compute_search_row();
@ -530,8 +545,14 @@ impl QuickSelectRenderable {
let uniq_results = compute_uniq_results(&self.results);
// Label each unique result
let labels =
compute_labels_for_alphabet(&self.config.quick_select_alphabet, uniq_results.len());
let labels = compute_labels_for_alphabet(
if !self.args.alphabet.is_empty() {
&self.args.alphabet
} else {
&self.config.quick_select_alphabet
},
uniq_results.len(),
);
self.by_label.clear();
// Keep track of match_id -> label
@ -663,37 +684,45 @@ impl QuickSelectRenderable {
let result = self.results[n].clone();
let pane_id = self.delegate.pane_id();
let action = self.args.action.clone();
self.window
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
{
let mut selection = term_window.selection(pane_id);
let start = SelectionCoordinate {
x: result.start_x,
y: result.start_y,
};
selection.start = Some(start);
selection.range = Some(SelectionRange {
start,
end: SelectionCoordinate {
// inclusive range for selection, but the result
// range is exclusive
x: result.end_x.saturating_sub(1),
y: result.end_y,
},
});
}
let mux = mux::Mux::get().unwrap();
if let Some(pane) = mux.get_pane(pane_id) {
{
let mut selection = term_window.selection(pane_id);
let start = SelectionCoordinate {
x: result.start_x,
y: result.start_y,
};
selection.start = Some(start);
selection.range = Some(SelectionRange {
start,
end: SelectionCoordinate {
// inclusive range for selection, but the result
// range is exclusive
x: result.end_x.saturating_sub(1),
y: result.end_y,
},
});
// Ensure that selection doesn't get invalidated when
// the overlay is closed
selection.seqno = pane.get_current_seqno();
}
let text = term_window.selection_text(&pane);
if !text.is_empty() {
if paste {
let _ = pane.send_paste(&text);
}
term_window.copy_to_clipboard(
ClipboardCopyDestination::ClipboardAndPrimarySelection,
text,
);
if let Some(action) = action {
let _ = term_window.perform_key_assignment(&pane, &action);
} else {
term_window.copy_to_clipboard(
ClipboardCopyDestination::ClipboardAndPrimarySelection,
text,
);
}
}
}
})));

View File

@ -19,7 +19,8 @@ use ::window::*;
use anyhow::Context;
use anyhow::{anyhow, ensure};
use config::keyassignment::{
ClipboardCopyDestination, ClipboardPasteSource, InputMap, KeyAssignment, SpawnCommand,
ClipboardCopyDestination, ClipboardPasteSource, InputMap, KeyAssignment, QuickSelectArguments,
SpawnCommand,
};
use config::{
configuration, AudibleBell, ConfigHandle, DimensionContext, GradientOrientation, TermConfig,
@ -1108,7 +1109,7 @@ impl TermWindow {
}
}
fn emit_window_event(&mut self, name: &str, pane_id: Option<PaneId>) {
pub fn emit_window_event(&mut self, name: &str, pane_id: Option<PaneId>) {
if self.get_active_pane_or_overlay().is_none() || self.window.is_none() {
return;
}
@ -1977,7 +1978,17 @@ impl TermWindow {
}
QuickSelect => {
if let Some(pane) = self.get_active_pane_no_overlay() {
let qa = QuickSelectOverlay::with_pane(self, &pane);
let qa = QuickSelectOverlay::with_pane(
self,
&pane,
&QuickSelectArguments::default(),
);
self.assign_overlay_for_pane(pane.pane_id(), qa);
}
}
QuickSelectArgs(args) => {
if let Some(pane) = self.get_active_pane_no_overlay() {
let qa = QuickSelectOverlay::with_pane(self, &pane, args);
self.assign_overlay_for_pane(pane.pane_id(), qa);
}
}