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:
parent
6a265d9f72
commit
668d41ad5d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -681,6 +681,7 @@ dependencies = [
|
||||
"luahelper",
|
||||
"mlua",
|
||||
"notify",
|
||||
"open",
|
||||
"portable-pty",
|
||||
"pretty_env_logger",
|
||||
"promise",
|
||||
|
@ -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"]}
|
||||
|
@ -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>),
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
60
docs/config/lua/keyassignment/QuickSelectArgs.md
Normal file
60
docs/config/lua/keyassignment/QuickSelectArgs.md
Normal 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).
|
16
docs/config/lua/wezterm/open_with.md
Normal file
16
docs/config/lua/wezterm/open_with.md
Normal 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")
|
||||
```
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
})));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user