mirror of
https://github.com/wez/wezterm.git
synced 2024-11-23 15:04:36 +03:00
merge copy and search overlay code
The copy overlay now has a notion of running in search mode vs. copy mode; it can be launched in either mode. Search mode has a separate key table called `search_mode`. Activating copy mode while search mode is active will now update the mode of the existing overlay, rather than cancelling and creating a new instance, and vice versa. Activating copy mode while search mode is active will replace the current key table activation (which is assumed to be `copy_mode`) with `search_mode`, and vice versa. The viewport is no longer scrolled to the bottom when activating search mode. refs: https://github.com/wez/wezterm/issues/993 refs: https://github.com/wez/wezterm/issues/1592
This commit is contained in:
parent
865d857050
commit
2710cefb3e
@ -104,6 +104,12 @@ pub enum Pattern {
|
||||
Regex(String),
|
||||
}
|
||||
|
||||
impl Default for Pattern {
|
||||
fn default() -> Self {
|
||||
Self::CaseSensitiveString("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Pattern {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &String {
|
||||
@ -391,6 +397,14 @@ pub enum CopyModeAssignment {
|
||||
PageUp,
|
||||
PageDown,
|
||||
Close,
|
||||
PriorMatch,
|
||||
NextMatch,
|
||||
PriorMatchPage,
|
||||
NextMatchPage,
|
||||
CycleMatchType,
|
||||
ClearPattern,
|
||||
EditPattern,
|
||||
AcceptPattern,
|
||||
}
|
||||
impl_lua_conversion!(CopyModeAssignment);
|
||||
|
||||
|
@ -176,7 +176,10 @@ impl InputMap {
|
||||
|
||||
keys.by_name
|
||||
.entry("copy_mode".to_string())
|
||||
.or_insert_with(crate::overlay::copy::key_table);
|
||||
.or_insert_with(crate::overlay::copy::copy_key_table);
|
||||
keys.by_name
|
||||
.entry("search_mode".to_string())
|
||||
.or_insert_with(crate::overlay::copy::search_key_table);
|
||||
|
||||
Self {
|
||||
keys,
|
||||
|
@ -4,15 +4,18 @@ use config::keyassignment::{
|
||||
CopyModeAssignment, KeyAssignment, KeyTable, KeyTableEntry, ScrollbackEraseMode,
|
||||
};
|
||||
use mux::domain::DomainId;
|
||||
use mux::pane::{Pane, PaneId};
|
||||
use mux::pane::{Pane, PaneId, Pattern, SearchResult};
|
||||
use mux::renderable::*;
|
||||
use portable_pty::PtySize;
|
||||
use rangeset::RangeSet;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use termwiz::surface::{CursorVisibility, SequenceNo};
|
||||
use termwiz::cell::{Cell, CellAttributes};
|
||||
use termwiz::color::AnsiColor;
|
||||
use termwiz::surface::{CursorVisibility, SequenceNo, SEQ_ZERO};
|
||||
use unicode_segmentation::*;
|
||||
use url::Url;
|
||||
use wezterm_term::color::ColorPalette;
|
||||
@ -33,6 +36,25 @@ struct CopyRenderable {
|
||||
viewport: Option<StableRowIndex>,
|
||||
/// We use this to cancel ourselves later
|
||||
window: ::window::Window,
|
||||
|
||||
/// The text that the user entered
|
||||
pattern: Pattern,
|
||||
/// The most recently queried set of matches
|
||||
results: Vec<SearchResult>,
|
||||
by_line: HashMap<StableRowIndex, Vec<MatchResult>>,
|
||||
last_result_seqno: SequenceNo,
|
||||
last_bar_pos: Option<StableRowIndex>,
|
||||
dirty_results: RangeSet<StableRowIndex>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
editing_search: bool,
|
||||
result_pos: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MatchResult {
|
||||
range: Range<usize>,
|
||||
result_index: usize,
|
||||
}
|
||||
|
||||
struct Dimensions {
|
||||
@ -41,33 +63,219 @@ struct Dimensions {
|
||||
top: StableRowIndex,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CopyModeParams {
|
||||
pub pattern: Pattern,
|
||||
pub editing_search: bool,
|
||||
}
|
||||
|
||||
impl CopyOverlay {
|
||||
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>,
|
||||
params: CopyModeParams,
|
||||
) -> Rc<dyn Pane> {
|
||||
let mut cursor = pane.get_cursor_position();
|
||||
cursor.shape = termwiz::surface::CursorShape::SteadyBlock;
|
||||
cursor.visibility = CursorVisibility::Visible;
|
||||
|
||||
let window = term_window.window.clone().unwrap();
|
||||
let render = CopyRenderable {
|
||||
let dims = pane.get_dimensions();
|
||||
let mut render = CopyRenderable {
|
||||
cursor,
|
||||
window,
|
||||
delegate: Rc::clone(pane),
|
||||
start: None,
|
||||
viewport: term_window.get_viewport(pane.pane_id()),
|
||||
results: vec![],
|
||||
by_line: HashMap::new(),
|
||||
dirty_results: RangeSet::default(),
|
||||
width: dims.cols,
|
||||
height: dims.viewport_rows,
|
||||
last_result_seqno: SEQ_ZERO,
|
||||
last_bar_pos: None,
|
||||
pattern: params.pattern,
|
||||
editing_search: params.editing_search,
|
||||
result_pos: None,
|
||||
};
|
||||
|
||||
let search_row = render.compute_search_row();
|
||||
render.dirty_results.add(search_row);
|
||||
render.update_search();
|
||||
|
||||
Rc::new(CopyOverlay {
|
||||
delegate: Rc::clone(pane),
|
||||
render: RefCell::new(render),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_params(&self) -> CopyModeParams {
|
||||
let render = self.render.borrow();
|
||||
CopyModeParams {
|
||||
pattern: render.pattern.clone(),
|
||||
editing_search: render.editing_search,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_params(&self, params: CopyModeParams) {
|
||||
let mut render = self.render.borrow_mut();
|
||||
render.editing_search = params.editing_search;
|
||||
if render.pattern != params.pattern {
|
||||
render.pattern = params.pattern;
|
||||
render.update_search();
|
||||
}
|
||||
let search_row = render.compute_search_row();
|
||||
render.dirty_results.add(search_row);
|
||||
}
|
||||
|
||||
pub fn viewport_changed(&self, viewport: Option<StableRowIndex>) {
|
||||
let mut r = self.render.borrow_mut();
|
||||
r.viewport = viewport;
|
||||
let mut render = self.render.borrow_mut();
|
||||
if render.viewport != viewport {
|
||||
if let Some(last) = render.last_bar_pos.take() {
|
||||
render.dirty_results.add(last);
|
||||
}
|
||||
if let Some(pos) = viewport.as_ref() {
|
||||
render.dirty_results.add(*pos);
|
||||
}
|
||||
render.viewport = viewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CopyRenderable {
|
||||
fn compute_search_row(&self) -> StableRowIndex {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
let top = self.viewport.unwrap_or_else(|| dims.physical_top);
|
||||
let bottom = (top + dims.viewport_rows as StableRowIndex).saturating_sub(1);
|
||||
bottom
|
||||
}
|
||||
|
||||
fn check_for_resize(&mut self) {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
if dims.cols == self.width && dims.viewport_rows == self.height {
|
||||
return;
|
||||
}
|
||||
|
||||
self.width = dims.cols;
|
||||
self.height = dims.viewport_rows;
|
||||
|
||||
let pos = self.result_pos;
|
||||
self.update_search();
|
||||
self.result_pos = pos;
|
||||
}
|
||||
|
||||
fn recompute_results(&mut self) {
|
||||
for (result_index, res) in self.results.iter().enumerate() {
|
||||
for idx in res.start_y..=res.end_y {
|
||||
let range = if idx == res.start_y && idx == res.end_y {
|
||||
// Range on same line
|
||||
res.start_x..res.end_x
|
||||
} else if idx == res.end_y {
|
||||
// final line of multi-line
|
||||
0..res.end_x
|
||||
} else if idx == res.start_y {
|
||||
// first line of multi-line
|
||||
res.start_x..self.width
|
||||
} else {
|
||||
// a middle line
|
||||
0..self.width
|
||||
};
|
||||
|
||||
let result = MatchResult {
|
||||
range,
|
||||
result_index,
|
||||
};
|
||||
|
||||
let matches = self.by_line.entry(idx).or_insert_with(|| vec![]);
|
||||
matches.push(result);
|
||||
|
||||
self.dirty_results.add(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_search(&mut self) {
|
||||
for idx in self.by_line.keys() {
|
||||
self.dirty_results.add(*idx);
|
||||
}
|
||||
if let Some(idx) = self.last_bar_pos.as_ref() {
|
||||
self.dirty_results.add(*idx);
|
||||
}
|
||||
|
||||
self.results.clear();
|
||||
self.by_line.clear();
|
||||
self.result_pos.take();
|
||||
|
||||
let bar_pos = self.compute_search_row();
|
||||
self.dirty_results.add(bar_pos);
|
||||
self.last_result_seqno = self.delegate.get_current_seqno();
|
||||
|
||||
if !self.pattern.is_empty() {
|
||||
let pane: Rc<dyn Pane> = self.delegate.clone();
|
||||
let window = self.window.clone();
|
||||
let pattern = self.pattern.clone();
|
||||
promise::spawn::spawn(async move {
|
||||
let mut results = pane.search(pattern).await?;
|
||||
results.sort();
|
||||
|
||||
let pane_id = pane.pane_id();
|
||||
let mut results = Some(results);
|
||||
window.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
|
||||
let state = term_window.pane_state(pane_id);
|
||||
if let Some(overlay) = state.overlay.as_ref() {
|
||||
if let Some(copy_overlay) = overlay.pane.downcast_ref::<CopyOverlay>() {
|
||||
let mut r = copy_overlay.render.borrow_mut();
|
||||
r.results = results.take().unwrap();
|
||||
r.recompute_results();
|
||||
let num_results = r.results.len();
|
||||
|
||||
if !r.results.is_empty() {
|
||||
r.activate_match_number(num_results - 1);
|
||||
} else {
|
||||
r.set_viewport(None);
|
||||
r.clear_selection();
|
||||
}
|
||||
}
|
||||
}
|
||||
})));
|
||||
anyhow::Result::<()>::Ok(())
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
self.clear_selection();
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_selection(&mut self) {
|
||||
let pane_id = self.delegate.pane_id();
|
||||
self.window
|
||||
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
|
||||
let mut selection = term_window.selection(pane_id);
|
||||
selection.origin.take();
|
||||
selection.range.take();
|
||||
})));
|
||||
}
|
||||
|
||||
fn activate_match_number(&mut self, n: usize) {
|
||||
self.result_pos.replace(n);
|
||||
let result = self.results[n].clone();
|
||||
self.cursor.y = result.end_y;
|
||||
self.cursor.x = result.end_x.saturating_sub(1);
|
||||
|
||||
let start = SelectionCoordinate {
|
||||
x: result.start_x,
|
||||
y: result.start_y,
|
||||
};
|
||||
let end = SelectionCoordinate {
|
||||
// inclusive range for selection, but the result
|
||||
// range is exclusive
|
||||
x: result.end_x.saturating_sub(1),
|
||||
y: result.end_y,
|
||||
};
|
||||
self.start.replace(start);
|
||||
self.adjust_selection(start, SelectionRange { start, end });
|
||||
}
|
||||
|
||||
fn clamp_cursor_to_scrollback(&mut self) {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
if self.cursor.x >= dims.cols {
|
||||
@ -180,6 +388,87 @@ impl CopyRenderable {
|
||||
self.select_to_cursor_pos();
|
||||
}
|
||||
|
||||
/// Move to prior match
|
||||
fn prior_match(&mut self) {
|
||||
if let Some(cur) = self.result_pos.as_ref() {
|
||||
let prior = if *cur > 0 {
|
||||
cur - 1
|
||||
} else {
|
||||
self.results.len() - 1
|
||||
};
|
||||
self.activate_match_number(prior);
|
||||
}
|
||||
}
|
||||
|
||||
/// Move to next match
|
||||
fn next_match(&mut self) {
|
||||
if let Some(cur) = self.result_pos.as_ref() {
|
||||
let next = if *cur + 1 >= self.results.len() {
|
||||
0
|
||||
} else {
|
||||
*cur + 1
|
||||
};
|
||||
self.activate_match_number(next);
|
||||
}
|
||||
}
|
||||
/// Skip this page of matches and move up to the first match from
|
||||
/// the prior page.
|
||||
fn prior_match_page(&mut self) {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
if let Some(cur) = self.result_pos {
|
||||
let top = self.viewport.unwrap_or(dims.physical_top);
|
||||
let prior = top - dims.viewport_rows as isize;
|
||||
if let Some(pos) = self
|
||||
.results
|
||||
.iter()
|
||||
.position(|res| res.start_y > prior && res.start_y < top)
|
||||
{
|
||||
self.activate_match_number(pos);
|
||||
} else {
|
||||
self.activate_match_number(cur.saturating_sub(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Skip this page of matches and move down to the first match from
|
||||
/// the next page.
|
||||
fn next_match_page(&mut self) {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
if let Some(cur) = self.result_pos {
|
||||
let top = self.viewport.unwrap_or(dims.physical_top);
|
||||
let bottom = top + dims.viewport_rows as isize;
|
||||
if let Some(pos) = self.results.iter().position(|res| res.start_y >= bottom) {
|
||||
self.activate_match_number(pos);
|
||||
} else {
|
||||
let len = self.results.len().saturating_sub(1);
|
||||
self.activate_match_number(cur.min(len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_pattern(&mut self) {
|
||||
self.pattern.clear();
|
||||
self.update_search();
|
||||
}
|
||||
|
||||
fn edit_pattern(&mut self) {
|
||||
self.editing_search = true;
|
||||
}
|
||||
|
||||
fn accept_pattern(&mut self) {
|
||||
self.editing_search = false;
|
||||
}
|
||||
|
||||
fn cycle_match_type(&mut self) {
|
||||
let pattern = match &self.pattern {
|
||||
Pattern::CaseSensitiveString(s) => Pattern::CaseInSensitiveString(s.clone()),
|
||||
Pattern::CaseInSensitiveString(s) => Pattern::Regex(s.clone()),
|
||||
Pattern::Regex(s) => Pattern::CaseSensitiveString(s.clone()),
|
||||
};
|
||||
self.pattern = pattern;
|
||||
self.update_search();
|
||||
}
|
||||
|
||||
fn move_to_viewport_middle(&mut self) {
|
||||
let dims = self.dimensions();
|
||||
self.cursor.y = dims.top + (dims.dims.viewport_rows as isize) / 2;
|
||||
@ -386,8 +675,12 @@ impl Pane for CopyOverlay {
|
||||
format!("Copy mode: {}", self.delegate.get_title())
|
||||
}
|
||||
|
||||
fn send_paste(&self, _text: &str) -> anyhow::Result<()> {
|
||||
anyhow::bail!("ignoring paste while copying");
|
||||
fn send_paste(&self, text: &str) -> anyhow::Result<()> {
|
||||
// paste into the search bar
|
||||
let mut r = self.render.borrow_mut();
|
||||
r.pattern.push_str(text);
|
||||
r.update_search();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reader(&self) -> anyhow::Result<Option<Box<dyn std::io::Read + Send>>> {
|
||||
@ -406,6 +699,28 @@ impl Pane for CopyOverlay {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_down(&self, key: KeyCode, mods: KeyModifiers) -> anyhow::Result<()> {
|
||||
let mut render = self.render.borrow_mut();
|
||||
if render.editing_search {
|
||||
match (key, mods) {
|
||||
(KeyCode::Char(c), KeyModifiers::NONE)
|
||||
| (KeyCode::Char(c), KeyModifiers::SHIFT) => {
|
||||
// Type to add to the pattern
|
||||
render.pattern.push(c);
|
||||
render.update_search();
|
||||
}
|
||||
(KeyCode::Backspace, KeyModifiers::NONE) => {
|
||||
// Backspace to edit the pattern
|
||||
render.pattern.pop();
|
||||
render.update_search();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn perform_assignment(&self, assignment: &KeyAssignment) -> bool {
|
||||
use CopyModeAssignment::*;
|
||||
match assignment {
|
||||
@ -431,6 +746,14 @@ impl Pane for CopyOverlay {
|
||||
PageUp => render.page_up(),
|
||||
PageDown => render.page_down(),
|
||||
Close => render.close(),
|
||||
PriorMatch => render.prior_match(),
|
||||
NextMatch => render.next_match(),
|
||||
PriorMatchPage => render.prior_match_page(),
|
||||
NextMatchPage => render.next_match_page(),
|
||||
CycleMatchType => render.cycle_match_type(),
|
||||
ClearPattern => render.clear_pattern(),
|
||||
EditPattern => render.edit_pattern(),
|
||||
AcceptPattern => render.accept_pattern(),
|
||||
}
|
||||
true
|
||||
}
|
||||
@ -438,10 +761,6 @@ impl Pane for CopyOverlay {
|
||||
}
|
||||
}
|
||||
|
||||
fn key_down(&self, _key: KeyCode, _mods: KeyModifiers) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_event(&self, _event: MouseEvent) -> anyhow::Result<()> {
|
||||
anyhow::bail!("ignoring mouse while copying");
|
||||
}
|
||||
@ -484,7 +803,18 @@ impl Pane for CopyOverlay {
|
||||
}
|
||||
|
||||
fn get_cursor_position(&self) -> StableCursorPosition {
|
||||
self.render.borrow().cursor
|
||||
let renderer = self.render.borrow();
|
||||
if renderer.editing_search {
|
||||
// place in the search box
|
||||
StableCursorPosition {
|
||||
x: 8 + wezterm_term::unicode_column_width(&renderer.pattern, None),
|
||||
y: renderer.compute_search_row(),
|
||||
shape: termwiz::surface::CursorShape::SteadyBlock,
|
||||
visibility: termwiz::surface::CursorVisibility::Visible,
|
||||
}
|
||||
} else {
|
||||
renderer.cursor
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_seqno(&self) -> SequenceNo {
|
||||
@ -500,7 +830,70 @@ impl Pane for CopyOverlay {
|
||||
}
|
||||
|
||||
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {
|
||||
self.delegate.get_lines(lines)
|
||||
let mut renderer = self.render.borrow_mut();
|
||||
if self.delegate.get_current_seqno() > renderer.last_result_seqno {
|
||||
renderer.update_search();
|
||||
}
|
||||
|
||||
renderer.check_for_resize();
|
||||
let dims = self.get_dimensions();
|
||||
|
||||
let (top, mut lines) = self.delegate.get_lines(lines);
|
||||
|
||||
// Process the lines; for the search row we want to render instead
|
||||
// the search UI.
|
||||
// For rows with search results, we want to highlight the matching ranges
|
||||
let search_row = renderer.compute_search_row();
|
||||
for (idx, line) in lines.iter_mut().enumerate() {
|
||||
let stable_idx = idx as StableRowIndex + top;
|
||||
renderer.dirty_results.remove(stable_idx);
|
||||
if stable_idx == search_row && (renderer.editing_search || !renderer.pattern.is_empty())
|
||||
{
|
||||
// Replace with search UI
|
||||
let rev = CellAttributes::default().set_reverse(true).clone();
|
||||
line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()), SEQ_ZERO);
|
||||
let mode = &match renderer.pattern {
|
||||
Pattern::CaseSensitiveString(_) => "case-sensitive",
|
||||
Pattern::CaseInSensitiveString(_) => "ignore-case",
|
||||
Pattern::Regex(_) => "regex",
|
||||
};
|
||||
line.overlay_text_with_attribute(
|
||||
0,
|
||||
&format!(
|
||||
"Search: {} ({}/{} matches. {})",
|
||||
*renderer.pattern,
|
||||
renderer.result_pos.map(|x| x + 1).unwrap_or(0),
|
||||
renderer.results.len(),
|
||||
mode
|
||||
),
|
||||
rev,
|
||||
SEQ_ZERO,
|
||||
);
|
||||
renderer.last_bar_pos = Some(search_row);
|
||||
} else if let Some(matches) = renderer.by_line.get(&stable_idx) {
|
||||
for m in matches {
|
||||
// highlight
|
||||
for cell_idx in m.range.clone() {
|
||||
if let Some(cell) = line.cells_mut_for_attr_changes_only().get_mut(cell_idx)
|
||||
{
|
||||
if Some(m.result_index) == renderer.result_pos {
|
||||
cell.attrs_mut()
|
||||
.set_background(AnsiColor::Yellow)
|
||||
.set_foreground(AnsiColor::Black)
|
||||
.set_reverse(false);
|
||||
} else {
|
||||
cell.attrs_mut()
|
||||
.set_background(AnsiColor::Fuchsia)
|
||||
.set_foreground(AnsiColor::Black)
|
||||
.set_reverse(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(top, lines)
|
||||
}
|
||||
|
||||
fn get_dimensions(&self) -> RenderableDimensions {
|
||||
@ -516,7 +909,66 @@ fn is_whitespace_word(word: &str) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_table() -> KeyTable {
|
||||
pub fn search_key_table() -> KeyTable {
|
||||
let mut table = KeyTable::default();
|
||||
for (key, mods, action) in [
|
||||
(
|
||||
WKeyCode::Char('\x1b'),
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::Close),
|
||||
),
|
||||
(
|
||||
WKeyCode::UpArrow,
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::PriorMatch),
|
||||
),
|
||||
(
|
||||
WKeyCode::Char('\r'),
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::PriorMatch),
|
||||
),
|
||||
(
|
||||
WKeyCode::Char('p'),
|
||||
Modifiers::CTRL,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::PriorMatch),
|
||||
),
|
||||
(
|
||||
WKeyCode::PageUp,
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::PriorMatchPage),
|
||||
),
|
||||
(
|
||||
WKeyCode::PageDown,
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::NextMatchPage),
|
||||
),
|
||||
(
|
||||
WKeyCode::Char('n'),
|
||||
Modifiers::CTRL,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::NextMatch),
|
||||
),
|
||||
(
|
||||
WKeyCode::DownArrow,
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::NextMatch),
|
||||
),
|
||||
(
|
||||
WKeyCode::Char('r'),
|
||||
Modifiers::CTRL,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::CycleMatchType),
|
||||
),
|
||||
(
|
||||
WKeyCode::Char('u'),
|
||||
Modifiers::CTRL,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::ClearPattern),
|
||||
),
|
||||
] {
|
||||
table.insert((key, mods), KeyTableEntry { action });
|
||||
}
|
||||
table
|
||||
}
|
||||
|
||||
pub fn copy_key_table() -> KeyTable {
|
||||
let mut table = KeyTable::default();
|
||||
for (key, mods, action) in [
|
||||
(
|
||||
@ -625,7 +1077,7 @@ pub fn key_table() -> KeyTable {
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::MoveToStartOfLine),
|
||||
),
|
||||
(
|
||||
WKeyCode::Char('\n'),
|
||||
WKeyCode::Char('\r'),
|
||||
Modifiers::NONE,
|
||||
KeyAssignment::CopyMode(CopyModeAssignment::MoveToStartOfNextLine),
|
||||
),
|
||||
|
@ -11,16 +11,14 @@ pub mod copy;
|
||||
pub mod debug;
|
||||
pub mod launcher;
|
||||
pub mod quickselect;
|
||||
pub mod search;
|
||||
|
||||
pub use confirm_close_pane::{
|
||||
confirm_close_pane, confirm_close_tab, confirm_close_window, confirm_quit_program,
|
||||
};
|
||||
pub use copy::CopyOverlay;
|
||||
pub use copy::{CopyModeParams, CopyOverlay};
|
||||
pub use debug::show_debug_overlay;
|
||||
pub use launcher::{launcher, LauncherArgs, LauncherFlags};
|
||||
pub use quickselect::QuickSelectOverlay;
|
||||
pub use search::SearchOverlay;
|
||||
|
||||
pub fn start_overlay<T, F>(
|
||||
term_window: &TermWindow,
|
||||
|
@ -1,524 +0,0 @@
|
||||
use crate::selection::{SelectionCoordinate, SelectionRange};
|
||||
use crate::termwindow::{TermWindow, TermWindowNotif};
|
||||
use config::keyassignment::ScrollbackEraseMode;
|
||||
use mux::domain::DomainId;
|
||||
use mux::pane::{Pane, PaneId, Pattern, SearchResult};
|
||||
use mux::renderable::*;
|
||||
use portable_pty::PtySize;
|
||||
use rangeset::RangeSet;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use termwiz::cell::{Cell, CellAttributes};
|
||||
use termwiz::color::AnsiColor;
|
||||
use termwiz::surface::{SequenceNo, SEQ_ZERO};
|
||||
use url::Url;
|
||||
use wezterm_term::color::ColorPalette;
|
||||
use wezterm_term::{Clipboard, KeyCode, KeyModifiers, Line, MouseEvent, StableRowIndex};
|
||||
use window::WindowOps;
|
||||
|
||||
pub struct SearchOverlay {
|
||||
renderer: RefCell<SearchRenderable>,
|
||||
delegate: Rc<dyn Pane>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MatchResult {
|
||||
range: Range<usize>,
|
||||
result_index: usize,
|
||||
}
|
||||
|
||||
struct SearchRenderable {
|
||||
delegate: Rc<dyn Pane>,
|
||||
/// The text that the user entered
|
||||
pattern: Pattern,
|
||||
/// The most recently queried set of matches
|
||||
results: Vec<SearchResult>,
|
||||
by_line: HashMap<StableRowIndex, Vec<MatchResult>>,
|
||||
last_result_seqno: SequenceNo,
|
||||
|
||||
viewport: Option<StableRowIndex>,
|
||||
last_bar_pos: Option<StableRowIndex>,
|
||||
|
||||
dirty_results: RangeSet<StableRowIndex>,
|
||||
result_pos: Option<usize>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
|
||||
/// We use this to cancel ourselves later
|
||||
window: ::window::Window,
|
||||
}
|
||||
|
||||
impl SearchOverlay {
|
||||
pub fn with_pane(
|
||||
term_window: &TermWindow,
|
||||
pane: &Rc<dyn Pane>,
|
||||
pattern: Pattern,
|
||||
) -> Rc<dyn Pane> {
|
||||
let viewport = term_window.get_viewport(pane.pane_id());
|
||||
let dims = pane.get_dimensions();
|
||||
|
||||
let window = term_window.window.clone().unwrap();
|
||||
let mut renderer = SearchRenderable {
|
||||
delegate: Rc::clone(pane),
|
||||
pattern,
|
||||
results: vec![],
|
||||
by_line: HashMap::new(),
|
||||
dirty_results: RangeSet::default(),
|
||||
viewport,
|
||||
last_bar_pos: None,
|
||||
last_result_seqno: SEQ_ZERO,
|
||||
window,
|
||||
result_pos: None,
|
||||
width: dims.cols,
|
||||
height: dims.viewport_rows,
|
||||
};
|
||||
|
||||
let search_row = renderer.compute_search_row();
|
||||
renderer.dirty_results.add(search_row);
|
||||
renderer.update_search();
|
||||
|
||||
Rc::new(SearchOverlay {
|
||||
renderer: RefCell::new(renderer),
|
||||
delegate: Rc::clone(pane),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn viewport_changed(&self, viewport: Option<StableRowIndex>) {
|
||||
let mut render = self.renderer.borrow_mut();
|
||||
if render.viewport != viewport {
|
||||
if let Some(last) = render.last_bar_pos.take() {
|
||||
render.dirty_results.add(last);
|
||||
}
|
||||
if let Some(pos) = viewport.as_ref() {
|
||||
render.dirty_results.add(*pos);
|
||||
}
|
||||
render.viewport = viewport;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pane for SearchOverlay {
|
||||
fn pane_id(&self) -> PaneId {
|
||||
self.delegate.pane_id()
|
||||
}
|
||||
|
||||
fn get_title(&self) -> String {
|
||||
self.delegate.get_title()
|
||||
}
|
||||
|
||||
fn send_paste(&self, text: &str) -> anyhow::Result<()> {
|
||||
// paste into the search bar
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
r.pattern.push_str(text);
|
||||
r.update_search();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reader(&self) -> anyhow::Result<Option<Box<dyn std::io::Read + Send>>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn writer(&self) -> RefMut<dyn std::io::Write> {
|
||||
self.delegate.writer()
|
||||
}
|
||||
|
||||
fn resize(&self, size: PtySize) -> anyhow::Result<()> {
|
||||
self.delegate.resize(size)
|
||||
}
|
||||
|
||||
fn key_up(&self, _key: KeyCode, _mods: KeyModifiers) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn key_down(&self, key: KeyCode, mods: KeyModifiers) -> anyhow::Result<()> {
|
||||
match (key, mods) {
|
||||
(KeyCode::Escape, KeyModifiers::NONE) => self.renderer.borrow().close(),
|
||||
(KeyCode::UpArrow, KeyModifiers::NONE)
|
||||
| (KeyCode::Enter, KeyModifiers::NONE)
|
||||
| (KeyCode::Char('p'), KeyModifiers::CTRL) => {
|
||||
// Move to prior match
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
if let Some(cur) = r.result_pos.as_ref() {
|
||||
let prior = if *cur > 0 {
|
||||
cur - 1
|
||||
} else {
|
||||
r.results.len() - 1
|
||||
};
|
||||
r.activate_match_number(prior);
|
||||
}
|
||||
}
|
||||
(KeyCode::PageUp, KeyModifiers::NONE) => {
|
||||
// Skip this page of matches and move up to the first match from
|
||||
// the prior page.
|
||||
let dims = self.delegate.get_dimensions();
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
if let Some(cur) = r.result_pos {
|
||||
let top = r.viewport.unwrap_or(dims.physical_top);
|
||||
let prior = top - dims.viewport_rows as isize;
|
||||
if let Some(pos) = r
|
||||
.results
|
||||
.iter()
|
||||
.position(|res| res.start_y > prior && res.start_y < top)
|
||||
{
|
||||
r.activate_match_number(pos);
|
||||
} else {
|
||||
r.activate_match_number(cur.saturating_sub(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
(KeyCode::PageDown, KeyModifiers::NONE) => {
|
||||
// Skip this page of matches and move down to the first match from
|
||||
// the next page.
|
||||
let dims = self.delegate.get_dimensions();
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
if let Some(cur) = r.result_pos {
|
||||
let top = r.viewport.unwrap_or(dims.physical_top);
|
||||
let bottom = top + dims.viewport_rows as isize;
|
||||
if let Some(pos) = r.results.iter().position(|res| res.start_y >= bottom) {
|
||||
r.activate_match_number(pos);
|
||||
} else {
|
||||
let len = r.results.len().saturating_sub(1);
|
||||
r.activate_match_number(cur.min(len));
|
||||
}
|
||||
}
|
||||
}
|
||||
(KeyCode::DownArrow, KeyModifiers::NONE) | (KeyCode::Char('n'), KeyModifiers::CTRL) => {
|
||||
// Move to next match
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
if let Some(cur) = r.result_pos.as_ref() {
|
||||
let next = if *cur + 1 >= r.results.len() {
|
||||
0
|
||||
} else {
|
||||
*cur + 1
|
||||
};
|
||||
r.activate_match_number(next);
|
||||
}
|
||||
}
|
||||
(KeyCode::Char('r'), KeyModifiers::CTRL) => {
|
||||
// CTRL-r cycles through pattern match types
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
let pattern = match &r.pattern {
|
||||
Pattern::CaseSensitiveString(s) => Pattern::CaseInSensitiveString(s.clone()),
|
||||
Pattern::CaseInSensitiveString(s) => Pattern::Regex(s.clone()),
|
||||
Pattern::Regex(s) => Pattern::CaseSensitiveString(s.clone()),
|
||||
};
|
||||
r.pattern = pattern;
|
||||
r.update_search();
|
||||
}
|
||||
(KeyCode::Char(c), KeyModifiers::NONE) | (KeyCode::Char(c), KeyModifiers::SHIFT) => {
|
||||
// Type to add to the pattern
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
r.pattern.push(c);
|
||||
r.update_search();
|
||||
}
|
||||
(KeyCode::Backspace, KeyModifiers::NONE) => {
|
||||
// Backspace to edit the pattern
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
r.pattern.pop();
|
||||
r.update_search();
|
||||
}
|
||||
(KeyCode::Char('u'), KeyModifiers::CTRL) => {
|
||||
// CTRL-u to clear the pattern
|
||||
let mut r = self.renderer.borrow_mut();
|
||||
r.pattern.clear();
|
||||
r.update_search();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mouse_event(&self, event: MouseEvent) -> anyhow::Result<()> {
|
||||
self.delegate.mouse_event(event)
|
||||
}
|
||||
|
||||
fn perform_actions(&self, actions: Vec<termwiz::escape::Action>) {
|
||||
self.delegate.perform_actions(actions)
|
||||
}
|
||||
|
||||
fn is_dead(&self) -> bool {
|
||||
self.delegate.is_dead()
|
||||
}
|
||||
|
||||
fn palette(&self) -> ColorPalette {
|
||||
self.delegate.palette()
|
||||
}
|
||||
fn domain_id(&self) -> DomainId {
|
||||
self.delegate.domain_id()
|
||||
}
|
||||
|
||||
fn erase_scrollback(&self, erase_mode: ScrollbackEraseMode) {
|
||||
self.delegate.erase_scrollback(erase_mode)
|
||||
}
|
||||
|
||||
fn is_mouse_grabbed(&self) -> bool {
|
||||
// Force grabbing off while we're searching
|
||||
false
|
||||
}
|
||||
|
||||
fn is_alt_screen_active(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn set_clipboard(&self, clipboard: &Arc<dyn Clipboard>) {
|
||||
self.delegate.set_clipboard(clipboard)
|
||||
}
|
||||
|
||||
fn get_current_working_dir(&self) -> Option<Url> {
|
||||
self.delegate.get_current_working_dir()
|
||||
}
|
||||
|
||||
fn get_cursor_position(&self) -> StableCursorPosition {
|
||||
// move to the search box
|
||||
let renderer = self.renderer.borrow();
|
||||
StableCursorPosition {
|
||||
x: 8 + wezterm_term::unicode_column_width(&renderer.pattern, None),
|
||||
y: renderer.compute_search_row(),
|
||||
shape: termwiz::surface::CursorShape::SteadyBlock,
|
||||
visibility: termwiz::surface::CursorVisibility::Visible,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_seqno(&self) -> SequenceNo {
|
||||
self.delegate.get_current_seqno()
|
||||
}
|
||||
|
||||
fn get_changed_since(
|
||||
&self,
|
||||
lines: Range<StableRowIndex>,
|
||||
seqno: SequenceNo,
|
||||
) -> RangeSet<StableRowIndex> {
|
||||
let mut dirty = self.delegate.get_changed_since(lines.clone(), seqno);
|
||||
dirty.add_set(&self.renderer.borrow().dirty_results);
|
||||
dirty.intersection_with_range(lines)
|
||||
}
|
||||
|
||||
fn get_lines(&self, lines: Range<StableRowIndex>) -> (StableRowIndex, Vec<Line>) {
|
||||
let mut renderer = self.renderer.borrow_mut();
|
||||
if self.delegate.get_current_seqno() > renderer.last_result_seqno {
|
||||
renderer.update_search();
|
||||
}
|
||||
|
||||
renderer.check_for_resize();
|
||||
let dims = self.get_dimensions();
|
||||
|
||||
let (top, mut lines) = self.delegate.get_lines(lines);
|
||||
|
||||
// Process the lines; for the search row we want to render instead
|
||||
// the search UI.
|
||||
// For rows with search results, we want to highlight the matching ranges
|
||||
let search_row = renderer.compute_search_row();
|
||||
for (idx, line) in lines.iter_mut().enumerate() {
|
||||
let stable_idx = idx as StableRowIndex + top;
|
||||
renderer.dirty_results.remove(stable_idx);
|
||||
if stable_idx == search_row {
|
||||
// Replace with search UI
|
||||
let rev = CellAttributes::default().set_reverse(true).clone();
|
||||
line.fill_range(0..dims.cols, &Cell::new(' ', rev.clone()), SEQ_ZERO);
|
||||
let mode = &match renderer.pattern {
|
||||
Pattern::CaseSensitiveString(_) => "case-sensitive",
|
||||
Pattern::CaseInSensitiveString(_) => "ignore-case",
|
||||
Pattern::Regex(_) => "regex",
|
||||
};
|
||||
line.overlay_text_with_attribute(
|
||||
0,
|
||||
&format!(
|
||||
"Search: {} ({}/{} matches. {})",
|
||||
*renderer.pattern,
|
||||
renderer.result_pos.map(|x| x + 1).unwrap_or(0),
|
||||
renderer.results.len(),
|
||||
mode
|
||||
),
|
||||
rev,
|
||||
SEQ_ZERO,
|
||||
);
|
||||
renderer.last_bar_pos = Some(search_row);
|
||||
} else if let Some(matches) = renderer.by_line.get(&stable_idx) {
|
||||
for m in matches {
|
||||
// highlight
|
||||
for cell_idx in m.range.clone() {
|
||||
if let Some(cell) = line.cells_mut_for_attr_changes_only().get_mut(cell_idx)
|
||||
{
|
||||
if Some(m.result_index) == renderer.result_pos {
|
||||
cell.attrs_mut()
|
||||
.set_background(AnsiColor::Yellow)
|
||||
.set_foreground(AnsiColor::Black)
|
||||
.set_reverse(false);
|
||||
} else {
|
||||
cell.attrs_mut()
|
||||
.set_background(AnsiColor::Fuchsia)
|
||||
.set_foreground(AnsiColor::Black)
|
||||
.set_reverse(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(top, lines)
|
||||
}
|
||||
|
||||
fn get_dimensions(&self) -> RenderableDimensions {
|
||||
self.delegate.get_dimensions()
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchRenderable {
|
||||
fn compute_search_row(&self) -> StableRowIndex {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
let top = self.viewport.unwrap_or_else(|| dims.physical_top);
|
||||
let bottom = (top + dims.viewport_rows as StableRowIndex).saturating_sub(1);
|
||||
bottom
|
||||
}
|
||||
|
||||
fn close(&self) {
|
||||
TermWindow::schedule_cancel_overlay_for_pane(self.window.clone(), self.delegate.pane_id());
|
||||
}
|
||||
|
||||
fn set_viewport(&self, row: Option<StableRowIndex>) {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
let pane_id = self.delegate.pane_id();
|
||||
self.window
|
||||
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
|
||||
term_window.set_viewport(pane_id, row, dims);
|
||||
})));
|
||||
}
|
||||
|
||||
fn check_for_resize(&mut self) {
|
||||
let dims = self.delegate.get_dimensions();
|
||||
if dims.cols == self.width && dims.viewport_rows == self.height {
|
||||
return;
|
||||
}
|
||||
|
||||
self.width = dims.cols;
|
||||
self.height = dims.viewport_rows;
|
||||
|
||||
let pos = self.result_pos;
|
||||
self.update_search();
|
||||
self.result_pos = pos;
|
||||
}
|
||||
|
||||
fn recompute_results(&mut self) {
|
||||
for (result_index, res) in self.results.iter().enumerate() {
|
||||
for idx in res.start_y..=res.end_y {
|
||||
let range = if idx == res.start_y && idx == res.end_y {
|
||||
// Range on same line
|
||||
res.start_x..res.end_x
|
||||
} else if idx == res.end_y {
|
||||
// final line of multi-line
|
||||
0..res.end_x
|
||||
} else if idx == res.start_y {
|
||||
// first line of multi-line
|
||||
res.start_x..self.width
|
||||
} else {
|
||||
// a middle line
|
||||
0..self.width
|
||||
};
|
||||
|
||||
let result = MatchResult {
|
||||
range,
|
||||
result_index,
|
||||
};
|
||||
|
||||
let matches = self.by_line.entry(idx).or_insert_with(|| vec![]);
|
||||
matches.push(result);
|
||||
|
||||
self.dirty_results.add(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_search(&mut self) {
|
||||
for idx in self.by_line.keys() {
|
||||
self.dirty_results.add(*idx);
|
||||
}
|
||||
if let Some(idx) = self.last_bar_pos.as_ref() {
|
||||
self.dirty_results.add(*idx);
|
||||
}
|
||||
|
||||
self.results.clear();
|
||||
self.by_line.clear();
|
||||
self.result_pos.take();
|
||||
|
||||
let bar_pos = self.compute_search_row();
|
||||
self.dirty_results.add(bar_pos);
|
||||
self.last_result_seqno = self.delegate.get_current_seqno();
|
||||
|
||||
if !self.pattern.is_empty() {
|
||||
let pane: Rc<dyn Pane> = self.delegate.clone();
|
||||
let window = self.window.clone();
|
||||
let pattern = self.pattern.clone();
|
||||
promise::spawn::spawn(async move {
|
||||
let mut results = pane.search(pattern).await?;
|
||||
results.sort();
|
||||
|
||||
let pane_id = pane.pane_id();
|
||||
let mut results = Some(results);
|
||||
window.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
|
||||
let state = term_window.pane_state(pane_id);
|
||||
if let Some(overlay) = state.overlay.as_ref() {
|
||||
if let Some(search_overlay) = overlay.pane.downcast_ref::<SearchOverlay>() {
|
||||
let mut r = search_overlay.renderer.borrow_mut();
|
||||
r.results = results.take().unwrap();
|
||||
r.recompute_results();
|
||||
let num_results = r.results.len();
|
||||
|
||||
if !r.results.is_empty() {
|
||||
r.activate_match_number(num_results - 1);
|
||||
} else {
|
||||
r.set_viewport(None);
|
||||
r.clear_selection();
|
||||
}
|
||||
}
|
||||
}
|
||||
})));
|
||||
anyhow::Result::<()>::Ok(())
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
self.set_viewport(None);
|
||||
self.clear_selection();
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_selection(&mut self) {
|
||||
let pane_id = self.delegate.pane_id();
|
||||
self.window
|
||||
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
|
||||
let mut selection = term_window.selection(pane_id);
|
||||
selection.origin.take();
|
||||
selection.range.take();
|
||||
})));
|
||||
}
|
||||
|
||||
fn activate_match_number(&mut self, n: usize) {
|
||||
self.result_pos.replace(n);
|
||||
let result = self.results[n].clone();
|
||||
|
||||
let pane_id = self.delegate.pane_id();
|
||||
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.origin = 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,
|
||||
},
|
||||
});
|
||||
})));
|
||||
|
||||
self.set_viewport(Some(result.start_y));
|
||||
}
|
||||
}
|
@ -8,8 +8,8 @@ use crate::glium::texture::SrgbTexture2d;
|
||||
use crate::inputmap::InputMap;
|
||||
use crate::overlay::{
|
||||
confirm_close_pane, confirm_close_tab, confirm_close_window, confirm_quit_program, launcher,
|
||||
start_overlay, start_overlay_pane, CopyOverlay, LauncherArgs, LauncherFlags,
|
||||
QuickSelectOverlay, SearchOverlay,
|
||||
start_overlay, start_overlay_pane, CopyModeParams, CopyOverlay, LauncherArgs, LauncherFlags,
|
||||
QuickSelectOverlay,
|
||||
};
|
||||
use crate::scripting::guiwin::GuiWin;
|
||||
use crate::scripting::pane::PaneObject;
|
||||
@ -22,7 +22,7 @@ use ::wezterm_term::input::{ClickPosition, MouseButton as TMB};
|
||||
use ::window::*;
|
||||
use anyhow::{anyhow, ensure, Context};
|
||||
use config::keyassignment::{
|
||||
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, QuickSelectArguments,
|
||||
ClipboardCopyDestination, ClipboardPasteSource, KeyAssignment, Pattern, QuickSelectArguments,
|
||||
SpawnCommand,
|
||||
};
|
||||
use config::{
|
||||
@ -1405,8 +1405,7 @@ impl TermWindow {
|
||||
if dirty.is_empty() {
|
||||
return;
|
||||
}
|
||||
if pane.downcast_ref::<SearchOverlay>().is_none()
|
||||
&& pane.downcast_ref::<CopyOverlay>().is_none()
|
||||
if pane.downcast_ref::<CopyOverlay>().is_none()
|
||||
&& pane.downcast_ref::<QuickSelectOverlay>().is_none()
|
||||
{
|
||||
// If any of the changed lines intersect with the
|
||||
@ -2252,9 +2251,29 @@ impl TermWindow {
|
||||
window.invalidate();
|
||||
}
|
||||
Search(pattern) => {
|
||||
if let Some(pane) = self.get_active_pane_no_overlay() {
|
||||
let search = SearchOverlay::with_pane(self, &pane, pattern.clone());
|
||||
self.assign_overlay_for_pane(pane.pane_id(), search);
|
||||
if let Some(pane) = self.get_active_pane_or_overlay() {
|
||||
let mut replace_current = false;
|
||||
if let Some(existing) = pane.downcast_ref::<CopyOverlay>() {
|
||||
let mut params = existing.get_params();
|
||||
params.editing_search = true;
|
||||
if !pattern.is_empty() {
|
||||
params.pattern = pattern.clone();
|
||||
}
|
||||
existing.apply_params(params);
|
||||
replace_current = true;
|
||||
} else {
|
||||
let search = CopyOverlay::with_pane(
|
||||
self,
|
||||
&pane,
|
||||
CopyModeParams {
|
||||
pattern: pattern.clone(),
|
||||
editing_search: true,
|
||||
},
|
||||
);
|
||||
self.assign_overlay_for_pane(pane.pane_id(), search);
|
||||
}
|
||||
self.key_table_state
|
||||
.activate("search_mode", None, replace_current, false);
|
||||
}
|
||||
}
|
||||
QuickSelect => {
|
||||
@ -2274,11 +2293,26 @@ impl TermWindow {
|
||||
}
|
||||
}
|
||||
ActivateCopyMode => {
|
||||
if let Some(pane) = self.get_active_pane_no_overlay() {
|
||||
let copy = CopyOverlay::with_pane(self, &pane);
|
||||
self.assign_overlay_for_pane(pane.pane_id(), copy);
|
||||
if let Some(pane) = self.get_active_pane_or_overlay() {
|
||||
let mut replace_current = false;
|
||||
if let Some(existing) = pane.downcast_ref::<CopyOverlay>() {
|
||||
let mut params = existing.get_params();
|
||||
params.editing_search = false;
|
||||
existing.apply_params(params);
|
||||
replace_current = true;
|
||||
} else {
|
||||
let copy = CopyOverlay::with_pane(
|
||||
self,
|
||||
&pane,
|
||||
CopyModeParams {
|
||||
pattern: Pattern::default(),
|
||||
editing_search: false,
|
||||
},
|
||||
);
|
||||
self.assign_overlay_for_pane(pane.pane_id(), copy);
|
||||
}
|
||||
self.key_table_state
|
||||
.activate("copy_mode", None, false, false);
|
||||
.activate("copy_mode", None, replace_current, false);
|
||||
}
|
||||
}
|
||||
AdjustPaneSize(direction, amount) => {
|
||||
@ -2593,9 +2627,7 @@ impl TermWindow {
|
||||
// This is a bit gross. If we add other overlays that need this information,
|
||||
// this should get extracted out into a trait
|
||||
if let Some(overlay) = state.overlay.as_ref() {
|
||||
if let Some(search_overlay) = overlay.pane.downcast_ref::<SearchOverlay>() {
|
||||
search_overlay.viewport_changed(pos);
|
||||
} else if let Some(copy) = overlay.pane.downcast_ref::<CopyOverlay>() {
|
||||
if let Some(copy) = overlay.pane.downcast_ref::<CopyOverlay>() {
|
||||
copy.viewport_changed(pos);
|
||||
} else if let Some(qs) = overlay.pane.downcast_ref::<QuickSelectOverlay>() {
|
||||
qs.viewport_changed(pos);
|
||||
|
Loading…
Reference in New Issue
Block a user