diff --git a/default-plugins/status-bar/src/first_line.rs b/default-plugins/status-bar/src/first_line.rs index 2ffe5f72..6c10b883 100644 --- a/default-plugins/status-bar/src/first_line.rs +++ b/default-plugins/status-bar/src/first_line.rs @@ -20,7 +20,7 @@ enum CtrlKeyAction { Pane, Tab, Resize, - Scroll, + Search, Quit, Session, Move, @@ -40,7 +40,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Pane => String::from("PANE"), CtrlKeyAction::Tab => String::from("TAB"), CtrlKeyAction::Resize => String::from("RESIZE"), - CtrlKeyAction::Scroll => String::from("SCROLL"), + CtrlKeyAction::Search => String::from("SEARCH"), CtrlKeyAction::Quit => String::from("QUIT"), CtrlKeyAction::Session => String::from("SESSION"), CtrlKeyAction::Move => String::from("MOVE"), @@ -52,7 +52,7 @@ impl CtrlKeyShortcut { CtrlKeyAction::Pane => 'p', CtrlKeyAction::Tab => 't', CtrlKeyAction::Resize => 'n', - CtrlKeyAction::Scroll => 's', + CtrlKeyAction::Search => 's', CtrlKeyAction::Quit => 'q', CtrlKeyAction::Session => 'o', CtrlKeyAction::Move => 'h', @@ -328,7 +328,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::Disabled, CtrlKeyAction::Quit), ], @@ -343,7 +343,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], @@ -358,7 +358,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], @@ -373,14 +373,14 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], colored_elements, separator, ), - InputMode::Scroll => key_indicators( + InputMode::EnterSearch | InputMode::Scroll | InputMode::Search => key_indicators( max_len, &[ CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Lock), @@ -388,7 +388,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], @@ -403,7 +403,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], @@ -418,7 +418,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], @@ -433,7 +433,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Selected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], @@ -448,7 +448,7 @@ pub fn ctrl_keys(help: &ModeInfo, max_len: usize, separator: &str) -> LinePart { CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Tab), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Resize), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Move), - CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Scroll), + CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Search), CtrlKeyShortcut::new(CtrlKeyMode::Unselected, CtrlKeyAction::Session), CtrlKeyShortcut::new(CtrlKeyMode::UnselectedAlternate, CtrlKeyAction::Quit), ], diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 56a71e4b..40be3fcd 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -196,6 +196,7 @@ fn full_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { InputMode::Locked => locked_interface_indication(help.style.colors), InputMode::Tmux => full_tmux_mode_indication(help), InputMode::RenamePane => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help), + InputMode::EnterSearch => full_shortcut_list_nonstandard_mode(select_pane_shortcut)(help), _ => full_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), } } @@ -225,6 +226,9 @@ fn shortened_shortcut_list(help: &ModeInfo, tip: TipFn) -> LinePart { InputMode::RenamePane => { shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help) }, + InputMode::EnterSearch => { + shortened_shortcut_list_nonstandard_mode(select_pane_shortcut)(help) + }, _ => shortened_shortcut_list_nonstandard_mode(confirm_pane_selection)(help), } } diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap index 93bdc8b6..842eaed8 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__bracketed_paste.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1673 +assertion_line: 1636 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap index 628861b8..8a6f9f40 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 558 +assertion_line: 538 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap index 257be3a4..078f7d4e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__close_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 498 +assertion_line: 478 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap index af538e25..e4f8f1d3 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__detach_and_attach_session.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 967 +assertion_line: 948 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap index 350dcb07..21e1e7b1 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_pane_with_mouse.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1061 +assertion_line: 1042 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap index 5c7f369b..d07e3f2a 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__focus_tab_with_layout.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1757 +assertion_line: 1720 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  Tab #3  Tab #4  Tab #5  Tab #6  Tab #7  Tab #8  Tab #9  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap index df6f1ea3..f47db23e 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__lock_mode.snap @@ -1,7 +1,7 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 803 expression: last_snapshot - --- Zellij (e2e-test)  Tab #1  ┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  -- INTERFACE LOCKED -- diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap index 881e6289..c50ca954 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions-2.snap @@ -1,7 +1,7 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 1308 expression: second_runner_snapshot - --- Zellij (mirrored_sessions)  Tab #1  Tab #2  ┌ Pane #1 ─────────────────────────────────────────────────┐┌ Pane #2 ─────────────────────────────────────────────────┐ @@ -25,5 +25,5 @@ expression: second_runner_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  <←↓↑→> Move focus / New / Close / Rename / Sync / Toggle / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap index 7f10a19f..a12aa434 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__mirrored_sessions.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1344 +assertion_line: 1307 expression: first_runner_snapshot --- Zellij (mirrored_sessions)  Tab #1  Tab #2  @@ -25,5 +25,5 @@ expression: first_runner_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap index 49738bcf..3ad31dcf 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1523 +assertion_line: 1486 expression: second_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -25,5 +25,5 @@ expression: second_runner_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap index 1120822a..4abaae59 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_panes_and_same_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1522 +assertion_line: 1485 expression: first_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -25,5 +25,5 @@ expression: first_runner_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap index 99ce3e37..8c6b4fac 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1618 +assertion_line: 1581 expression: second_runner_snapshot --- Zellij (multiple_users_in_different_tabs)  Tab #1 [ ] Tab #2  @@ -25,5 +25,5 @@ expression: second_runner_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap index e8a89858..19f23c39 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_different_tabs.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1617 +assertion_line: 1580 expression: first_runner_snapshot --- Zellij (multiple_users_in_different_tabs)  Tab #1  Tab #2 [ ] @@ -25,5 +25,5 @@ expression: first_runner_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap index abc72d7c..74fb1a57 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab-2.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1433 +assertion_line: 1396 expression: second_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -25,5 +25,5 @@ expression: second_runner_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap index 874d94c8..2aec1c4d 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__multiple_users_in_same_pane_and_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1432 +assertion_line: 1395 expression: first_runner_snapshot --- Zellij (multiple_users_in_same_pane_and_tab)  Tab #1 [ ] @@ -25,5 +25,5 @@ expression: first_runner_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap index d973b994..032a03ca 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__open_new_tab.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 418 +assertion_line: 398 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  Tab #2  @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap index 5e6a3bf7..d78a61df 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__resize_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 764 +assertion_line: 744 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap index ab90a744..b66ba6c5 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 276 +assertion_line: 274 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││line20 │ │ ││li█e21 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  - <↓↑> Scroll / Scroll / Scroll / Edit / Select pane + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  + <↓↑> Scroll / Scroll / Scroll / Edit / Enter / Select pane diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap index 2dc3196b..2bcdfaed 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__scrolling_inside_a_pane_with_mouse.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1141 +assertion_line: 1117 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││line18 │ │ ││li█e19 │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap index 75aede28..76411adb 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__split_terminals_vertically.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 151 +assertion_line: 154 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap index 29882af4..51289dbb 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__start_without_pane_frames.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1200 +assertion_line: 1163 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ $ │$ █ │ │ │ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap index 31e254c2..c9969134 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__starts_with_one_terminal.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 104 +assertion_line: 107 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap index 4071f203..1c85d159 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__tmux_mode.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 1804 +assertion_line: 1767 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ ││ │ │ ││ │ └──────────────────────────────────────────────────────────┘└──────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap index b1633f02..b30448fe 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_floating_panes.snap @@ -1,7 +1,7 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 1683 expression: last_snapshot - --- Zellij (e2e-test)  Tab #1  ┌ Pane #1 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  (FLOATING PANES VISIBLE): Press Ctrl-p + to hide. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap index 3d7a47f9..ad24c740 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__toggle_pane_fullscreen.snap @@ -1,7 +1,7 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 334 expression: last_snapshot - --- Zellij (e2e-test)  Tab #1  ┌ Pane #2 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  (FULLSCREEN): + 1 hidden panes diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap index 4b37eb7d..81b3e554 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__typing_exit_closes_pane.snap @@ -1,6 +1,6 @@ --- source: src/tests/e2e/cases.rs -assertion_line: 702 +assertion_line: 682 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -25,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap index 469d5b7d..c5c77a31 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_pane.snap @@ -1,5 +1,6 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 1914 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap index 469d5b7d..f80671e1 100644 --- a/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap +++ b/src/tests/e2e/snapshots/zellij__tests__e2e__cases__undo_rename_tab.snap @@ -1,5 +1,6 @@ --- source: src/tests/e2e/cases.rs +assertion_line: 1865 expression: last_snapshot --- Zellij (e2e-test)  Tab #1  @@ -24,5 +25,5 @@ expression: last_snapshot │ │ │ │ └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ - Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SCROLL  SESSION  QUIT  + Ctrl + LOCK 

PANE  TAB  RESIZE  MOVE  SEARCH  SESSION  QUIT  Tip: Alt + => new pane. Alt + <←↓↑→ or hjkl> => navigate. Alt + <+-> => resize pane. diff --git a/zellij-server/src/output/mod.rs b/zellij-server/src/output/mod.rs index c13848ad..b2202f79 100644 --- a/zellij-server/src/output/mod.rs +++ b/zellij-server/src/output/mod.rs @@ -30,18 +30,22 @@ fn vte_goto_instruction(x_coords: usize, y_coords: usize, vte_output: &mut Strin } fn adjust_styles_for_possible_selection( - chunk_selection_and_background_color: Option<(Selection, AnsiCode)>, + chunk_selection_and_colors: Vec<(Selection, AnsiCode, Option)>, character_styles: CharacterStyles, chunk_y: usize, chunk_width: usize, ) -> CharacterStyles { - chunk_selection_and_background_color - .and_then(|(selection, background_color)| { - if selection.contains(chunk_y, chunk_width) { - Some(character_styles.background(Some(background_color))) - } else { - None + chunk_selection_and_colors + .iter() + .find(|(selection, _background_color, _foreground_color)| { + selection.contains(chunk_y, chunk_width) + }) + .map(|(_selection, background_color, foreground_color)| { + let mut character_styles = character_styles.background(Some(*background_color)); + if let Some(foreground_color) = foreground_color { + character_styles = character_styles.foreground(Some(*foreground_color)); } + character_styles }) .unwrap_or(character_styles) } @@ -76,14 +80,13 @@ fn serialize_chunks( let mut sixel_vte: Option = None; let link_handler = link_handler.map(|l_h| l_h.borrow()); for character_chunk in character_chunks { - let chunk_selection_and_background_color = character_chunk.selection_and_background_color(); let chunk_changed_colors = character_chunk.changed_colors(); let mut character_styles = CharacterStyles::new(); vte_goto_instruction(character_chunk.x, character_chunk.y, &mut vte_output); let mut chunk_width = character_chunk.x; for t_character in character_chunk.terminal_characters.iter() { let current_character_styles = adjust_styles_for_possible_selection( - chunk_selection_and_background_color, + character_chunk.selection_and_colors(), t_character.styles, character_chunk.y, chunk_width, @@ -642,7 +645,7 @@ pub struct CharacterChunk { pub x: usize, pub y: usize, pub changed_colors: Option<[Option; 256]>, - selection_and_background_color: Option<(Selection, AnsiCode)>, + selection_and_colors: Vec<(Selection, AnsiCode, Option)>, // Selection, background color, optional foreground color } #[derive(Debug, Clone, Copy, Default)] @@ -665,18 +668,23 @@ impl CharacterChunk { ..Default::default() } } - pub fn add_selection_and_background( + pub fn add_selection_and_colors( &mut self, selection: Selection, background_color: AnsiCode, + foreground_color: Option, offset_x: usize, offset_y: usize, ) { - self.selection_and_background_color = - Some((selection.offset(offset_x, offset_y), background_color)); + self.selection_and_colors.push(( + selection.offset(offset_x, offset_y), + background_color, + foreground_color, + )); } - pub fn selection_and_background_color(&self) -> Option<(Selection, AnsiCode)> { - self.selection_and_background_color + pub fn selection_and_colors(&self) -> Vec<(Selection, AnsiCode, Option)> { + // Selection, background color, optional foreground color + self.selection_and_colors.clone() } pub fn add_changed_colors(&mut self, changed_colors: Option<[Option; 256]>) { self.changed_colors = changed_colors; @@ -776,6 +784,15 @@ impl OutputBuffer { self.changed_lines.push(line_index); } } + pub fn update_lines(&mut self, start: usize, end: usize) { + if !self.should_update_all_lines { + for idx in start..=end { + if !self.changed_lines.contains(&idx) { + self.changed_lines.push(idx); + } + } + } + } pub fn update_all_lines(&mut self) { self.clear(); self.should_update_all_lines = true; diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index aef67f98..7739e10b 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -1,4 +1,5 @@ use super::sixel::{PixelRect, SixelGrid, SixelImageStore}; +use std::borrow::Cow; use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; @@ -29,6 +30,7 @@ use zellij_utils::{consts::VERSION, shared::version_number}; use crate::output::{CharacterChunk, OutputBuffer, SixelImageChunk}; use crate::panes::alacritty_functions::{parse_number, xparse_color}; use crate::panes::link_handler::LinkHandler; +use crate::panes::search::SearchResult; use crate::panes::selection::Selection; use crate::panes::terminal_character::{ AnsiCode, CharacterStyles, CharsetIndex, Cursor, CursorShape, StandardCharset, @@ -299,9 +301,9 @@ macro_rules! dump_screen { #[derive(Clone)] pub struct Grid { - lines_above: VecDeque, - viewport: Vec, - lines_below: Vec, + pub(crate) lines_above: VecDeque, + pub(crate) viewport: Vec, + pub(crate) lines_below: Vec, horizontal_tabstops: BTreeSet, alternate_screen_state: Option, cursor: Cursor, @@ -316,7 +318,7 @@ pub struct Grid { preceding_char: Option, terminal_emulator_colors: Rc>, terminal_emulator_color_codes: Rc>>, - output_buffer: OutputBuffer, + pub(crate) output_buffer: OutputBuffer, title_stack: Vec, character_cell_size: Rc>>, sixel_grid: SixelGrid, @@ -339,6 +341,7 @@ pub struct Grid { pub ring_bell: bool, scrollback_buffer_lines: usize, pub mouse_mode: bool, + pub search_results: SearchResult, } impl Debug for Grid { @@ -378,10 +381,13 @@ impl Debug for Grid { // display terminal characters with stripped styles for (i, row) in buffer.iter().enumerate() { + let mut cow_row = Cow::Borrowed(row); + self.search_results + .mark_search_results_in_row(&mut cow_row, i); if row.is_canonical { - writeln!(f, "{:02?} (C): {:?}", i, row)?; + writeln!(f, "{:02?} (C): {:?}", i, cow_row)?; } else { - writeln!(f, "{:02?} (W): {:?}", i, row)?; + writeln!(f, "{:02?} (W): {:?}", i, cow_row)?; } } Ok(()) @@ -439,6 +445,7 @@ impl Grid { scrollback_buffer_lines: 0, mouse_mode: false, character_cell_size, + search_results: Default::default(), sixel_grid, } } @@ -575,7 +582,9 @@ impl Grid { } y_coordinates } - pub fn scroll_up_one_line(&mut self) { + + pub fn scroll_up_one_line(&mut self) -> bool { + let mut found_something = false; if !self.lines_above.is_empty() && self.viewport.len() == self.height { self.is_scrolled = true; let line_to_push_down = self.viewport.pop().unwrap(); @@ -593,10 +602,16 @@ impl Grid { .saturating_sub(transferred_rows_height); self.selection.move_down(1); + // Move all search-selections down one line as well + found_something = self + .search_results + .move_down(1, &self.viewport, self.height); } self.output_buffer.update_all_lines(); + found_something } - pub fn scroll_down_one_line(&mut self) { + pub fn scroll_down_one_line(&mut self) -> bool { + let mut found_something = false; if !self.lines_below.is_empty() && self.viewport.len() == self.height { let mut line_to_push_up = self.viewport.remove(0); @@ -629,11 +644,16 @@ impl Grid { ); self.selection.move_up(1); + // Move all search-selections up one line as well + found_something = + self.search_results + .move_up(1, &self.viewport, &self.lines_below, self.height); self.output_buffer.update_all_lines(); } if self.lines_below.is_empty() { self.is_scrolled = false; } + found_something } fn force_change_size(&mut self, new_rows: usize, new_columns: usize) { // this is an ugly hack - it's here because sometimes we need to change_size to the @@ -844,6 +864,10 @@ impl Grid { self.set_scroll_region_to_viewport_size(); } self.scrollback_buffer_lines = self.recalculate_scrollback_buffer_count(); + self.search_results.selections.clear(); + self.search_viewport(); + // If we have thrown out the active element, set it to None + self.search_results.unset_active_selection_if_nonexistent(); self.output_buffer.update_all_lines(); } pub fn as_character_lines(&self) -> Vec> { @@ -1443,6 +1467,7 @@ impl Grid { self.output_buffer.update_all_lines(); self.changed_colors = None; self.scrollback_buffer_lines = 0; + self.search_results = Default::default(); self.sixel_scrolling = false; if let Some(images_to_reap) = self.sixel_grid.clear() { self.sixel_grid.reap_images(images_to_reap); diff --git a/zellij-server/src/panes/mod.rs b/zellij-server/src/panes/mod.rs index 569dbd42..1c4c84cb 100644 --- a/zellij-server/src/panes/mod.rs +++ b/zellij-server/src/panes/mod.rs @@ -3,6 +3,7 @@ mod floating_panes; pub mod grid; pub mod link_handler; mod plugin_pane; +mod search; pub mod selection; pub mod sixel; pub mod terminal_character; diff --git a/zellij-server/src/panes/search.rs b/zellij-server/src/panes/search.rs new file mode 100644 index 00000000..fcc2781e --- /dev/null +++ b/zellij-server/src/panes/search.rs @@ -0,0 +1,663 @@ +use crate::panes::selection::Selection; +use crate::panes::terminal_character::TerminalCharacter; +use crate::panes::{Grid, Row}; +use std::borrow::Cow; +use std::fmt::Debug; +use zellij_utils::input::actions::SearchDirection; +use zellij_utils::position::Position; + +// If char is neither alphanumeric nor an underscore do we consider it a word-boundary +fn is_word_boundary(x: &Option) -> bool { + x.map_or(true, |c| !c.is_ascii_alphanumeric() && c != '_') +} + +#[derive(Debug)] +enum SearchSource<'a> { + Main(&'a Row), + Tail(&'a Row), +} + +impl<'a> SearchSource<'a> { + /// Returns true, if a new source was found, false otherwise (reached the end of the tail). + /// If we are in the middle of a line, nothing will be changed. + /// Only, when we have to switch to a new line, will the source update itself, + /// as well as the corresponding indices. + fn get_next_source( + &mut self, + ridx: &mut usize, + hidx: &mut usize, + tailit: &mut std::slice::Iter<&'a Row>, + start: &Option, + ) -> bool { + match self { + SearchSource::Main(row) => { + // If we are at the end of the main row, we need to start looking into the tail + if hidx >= &mut row.columns.len() { + let curr_tail = tailit.next(); + // If we are at the end and found a partial hit, we have to extend the search into the next line + if let Some(curr_tail) = start.and(curr_tail) { + *ridx += 1; // Go one line down + *hidx = 0; // and start from the beginning of the new line + *self = SearchSource::Tail(curr_tail); + } else { + return false; // We reached the end of the tail + } + } + }, + SearchSource::Tail(tail) => { + if hidx >= &mut tail.columns.len() { + // If we are still searching (didn't hit a mismatch yet) and there is still more tail to go + // just continue with the next line + if let Some(curr_tail) = tailit.next() { + *ridx += 1; // Go one line down + *hidx = 0; // and start from the beginning of the new line + *self = SearchSource::Tail(curr_tail); + } else { + return false; // We reached the end of the tail + } + } + }, + } + // We have found a new source, or we are in the middle of a line, so no need to change anything + true + } + + // Get the char at hidx and, if existing, the following char as well + fn get_next_two_chars(&self, hidx: usize, whole_word_search: bool) -> (char, Option) { + // Get the current haystack character + let haystack_char = match self { + SearchSource::Main(row) => row.columns[hidx].character, + SearchSource::Tail(tail) => tail.columns[hidx].character, + }; + + // Get the next haystack character (relevant for whole-word search only) + let next_haystack_char = if whole_word_search { + // Everything (incl. end of line) that is not [a-zA-Z0-9_] is considered a word boundary + match self { + SearchSource::Main(row) => row.columns.get(hidx + 1).map(|c| c.character), + SearchSource::Tail(tail) => tail.columns.get(hidx + 1).map(|c| c.character), + } + } else { + None // Doesn't get used, when not doing whole-word search + }; + (haystack_char, next_haystack_char) + } +} + +#[derive(Debug, Clone, Default)] +pub struct SearchResult { + // What we have already found in the viewport + pub selections: Vec, + // Which of the selections we found is currently 'active' (highlighted differently) + pub active: Option, + // What we are looking for + pub needle: String, + // Does case matter? + pub case_insensitive: bool, + // Only search whole words, not parts inside a word + pub whole_word_only: bool, // TODO + // Jump from the bottom to the top (or vice versa), if we run out of lines to search + pub wrap_search: bool, +} + +impl SearchResult { + /// This is only used for Debug formatting Grid, which itself is only used + /// for tests. + #[allow(clippy::ptr_arg)] + pub(crate) fn mark_search_results_in_row(&self, row: &mut Cow, ridx: usize) { + for s in &self.selections { + if s.contains_row(ridx) { + let replacement_char = if Some(s) == self.active.as_ref() { + '_' + } else { + '#' + }; + + let (skip, take) = if ridx as isize == s.start.line() { + let skip = s.start.column(); + let take = if s.end.line() == s.start.line() { + s.end.column() - s.start.column() + } else { + // Just mark the rest of the line. This number is certainly too big but the iterator takes care of this + row.columns.len() + }; + (skip, take) + } else if ridx as isize == s.end.line() { + // We wrapped a line and the end is in this row, so take from the begging to the end + (0, s.end.column()) + } else { + // We are in the middle (start is above and end is below), so mark all + (0, row.columns.len()) + }; + + row.to_mut() + .columns + .iter_mut() + .skip(skip) + .take(take) + .for_each(|x| *x = TerminalCharacter::new(replacement_char)); + } + } + } + + pub fn has_modifiers_set(&self) -> bool { + self.wrap_search || self.whole_word_only || self.case_insensitive + } + + fn check_if_haystack_char_matches_needle( + &self, + nidx: usize, + needle_char: char, + haystack_char: char, + prev_haystack_char: Option, + ) -> bool { + let mut chars_match = if self.case_insensitive { + // Case insensitive search + // Currently only ascii, as this whole search-function is very sub-optimal anyways + haystack_char.to_ascii_lowercase() == needle_char.to_ascii_lowercase() + } else { + // Case sensitive search + haystack_char == needle_char + }; + + // Whole-word search + // It's a match only, if the first haystack char that is _not_ a hit, is a word-boundary + if chars_match + && self.whole_word_only + && nidx == 0 + && !is_word_boundary(&prev_haystack_char) + { + // Start of the match is not a word boundary, so this is not a hit + chars_match = false; + } + + chars_match + } + + /// Search a row and its tail. + /// The tail are all the non-canonical lines below `row`, with `row` not necessarily being canonical itself. + pub(crate) fn search_row(&self, mut ridx: usize, row: &Row, tail: &[&Row]) -> Vec { + let mut res = Vec::new(); + if self.needle.is_empty() || row.columns.is_empty() { + return res; + } + + let mut tailit = tail.iter(); + let mut source = SearchSource::Main(row); // Where we currently get the haystack-characters from + let orig_ridx = ridx; + let mut start = None; // If we find a hit, this is where it starts + let mut nidx = 0; // Needle index + let mut hidx = 0; // Haystack index + let mut prev_haystack_char: Option = None; + loop { + // Get the current and next haystack character + let (mut haystack_char, next_haystack_char) = + source.get_next_two_chars(hidx, self.whole_word_only); + + // Get current needle character + let needle_char = self.needle.chars().nth(nidx).unwrap(); // Unwrapping is safe here + + // Check if needle and haystack match (with search-options) + let chars_match = self.check_if_haystack_char_matches_needle( + nidx, + needle_char, + haystack_char, + prev_haystack_char, + ); + + if chars_match { + // If the needle is only 1 long, the next `if` could also happen, so we are not merging it into one big if-else + if nidx == 0 { + start = Some(Position::new(ridx as i32, hidx as u16)); + } + if nidx == self.needle.len() - 1 { + let mut end_found = true; + // If we search whole-word-only, the next non-needle char needs to be a word-boundary, + // otherwise its not a hit (e.g. some occurrence inside a longer word). + if self.whole_word_only && !is_word_boundary(&next_haystack_char) { + // The end of the match is not a word boundary, so this is not a hit! + // We have to jump back from where we started (plus one char) + nidx = 0; + ridx = start.unwrap().line() as usize; + hidx = start.unwrap().column(); // Will be incremented below + if start.unwrap().line() as usize == orig_ridx { + source = SearchSource::Main(row); + haystack_char = row.columns[hidx].character; // so that prev_char gets set correctly + } else { + // The -1 comes from the main row + let tail_idx = start.unwrap().line() as usize - orig_ridx - 1; + // We have to reset the tail-iterator as well. + tailit = tail[tail_idx..].iter(); + let trow = tailit.next().unwrap(); + haystack_char = trow.columns[hidx].character; // so that prev_char gets set correctly + source = SearchSource::Tail(trow); + } + start = None; + end_found = false; + } + if end_found { + let mut selection = Selection::default(); + selection.start(start.unwrap()); + selection.end(Position::new(ridx as i32, (hidx + 1) as u16)); + res.push(selection); + nidx = 0; + if matches!(source, SearchSource::Tail(..)) { + // When searching the tail, we can only find one additional selection, so stopping here + break; + } + } + } else { + nidx += 1; + } + } else { + // Chars don't match. Start searching the needle from the beginning + start = None; + nidx = 0; + if matches!(source, SearchSource::Tail(..)) { + // When searching the tail and we find a mismatch, just quit right now + break; + } + } + + hidx += 1; + prev_haystack_char = Some(haystack_char); + // We might need to switch to a new line in the tail + if !source.get_next_source(&mut ridx, &mut hidx, &mut tailit, &start) { + break; + } + } + + // The tail may have not been wrapped yet (when coming from lines_below), + // so it could be that the end extends across more characters than the row is wide. + // Therefore we need to reflow the end: + for s in res.iter_mut() { + while s.end.column() > row.width() { + s.end.column.0 -= row.width(); + s.end.line.0 += 1; + } + } + res + } + + pub(crate) fn move_active_selection_to_next(&mut self) { + if let Some(active_idx) = self.active { + self.active = self + .selections + .iter() + .skip_while(|s| *s != &active_idx) + .nth(1) + .cloned(); + } else { + self.active = self.selections.first().cloned(); + } + } + + pub(crate) fn move_active_selection_to_prev(&mut self) { + if let Some(active_idx) = self.active { + self.active = self + .selections + .iter() + .rev() + .skip_while(|s| *s != &active_idx) + .nth(1) + .cloned(); + } else { + self.active = self.selections.last().cloned(); + } + } + + pub(crate) fn unset_active_selection_if_nonexistent(&mut self) { + if let Some(active_idx) = self.active { + if !self.selections.contains(&active_idx) { + self.active = None; + } + } + } + + pub(crate) fn move_down( + &mut self, + amount: usize, + viewport: &[Row], + grid_height: usize, + ) -> bool { + let mut found_something = false; + self.selections + .iter_mut() + .chain(self.active.iter_mut()) + .for_each(|x| x.move_down(amount)); + + // Throw out all search-results outside of the new viewport + self.adjust_selections_to_moved_viewport(grid_height); + + // Search the new line for our needle + if !self.needle.is_empty() { + if let Some(row) = viewport.first() { + let mut tail = Vec::new(); + loop { + let tail_idx = 1 + tail.len(); + if tail_idx < viewport.len() && !viewport[tail_idx].is_canonical { + tail.push(&viewport[tail_idx]); + } else { + break; + } + } + let selections = self.search_row(0, row, &tail); + for selection in selections.iter().rev() { + self.selections.insert(0, *selection); + found_something = true; + } + } + } + found_something + } + + pub(crate) fn move_up( + &mut self, + amount: usize, + viewport: &[Row], + lines_below: &[Row], + grid_height: usize, + ) -> bool { + let mut found_something = false; + self.selections + .iter_mut() + .chain(self.active.iter_mut()) + .for_each(|x| x.move_up(amount)); + // Throw out all search-results outside of the new viewport + self.adjust_selections_to_moved_viewport(grid_height); + + // Search the new line for our needle + if !self.needle.is_empty() { + if let Some(row) = viewport.last() { + let tail: Vec<&Row> = lines_below.iter().take_while(|r| !r.is_canonical).collect(); + let selections = self.search_row(viewport.len() - 1, row, &tail); + for selection in selections { + // We are only interested in results that start in the this new row + if selection.start.line() as usize == viewport.len() - 1 { + self.selections.push(selection); + found_something = true; + } + } + } + } + found_something + } + + fn adjust_selections_to_moved_viewport(&mut self, grid_height: usize) { + // Throw out all search-results outside of the new viewport + self.selections + .retain(|s| (s.start.line() as usize) < grid_height && s.end.line() >= 0); + // If we have thrown out the active element, set it to None + self.unset_active_selection_if_nonexistent(); + } +} + +impl Grid { + pub fn search_down(&mut self) { + self.search_scrollbuffer(SearchDirection::Down); + } + + pub fn search_up(&mut self) { + self.search_scrollbuffer(SearchDirection::Up); + } + + pub fn clear_search(&mut self) { + // Clearing all previous highlights + for res in &self.search_results.selections { + self.output_buffer + .update_lines(res.start.line() as usize, res.end.line() as usize); + } + self.search_results = Default::default(); + } + + pub fn set_search_string(&mut self, needle: &str) { + self.search_results.needle = needle.to_string(); + self.search_viewport(); + // If the current viewport does not contain any hits, + // we jump around until we find something. Starting + // going backwards. + if self.search_results.selections.is_empty() { + self.search_up(); + } + if self.search_results.selections.is_empty() { + self.search_down(); + } + // We still don't want to pre-select anything at this stage + self.search_results.active = None; + self.is_scrolled = true; + } + + pub fn search_viewport(&mut self) { + for ridx in 0..self.viewport.len() { + let row = &self.viewport[ridx]; + let mut tail = Vec::new(); + loop { + let tail_idx = ridx + tail.len() + 1; + if tail_idx < self.viewport.len() && !self.viewport[tail_idx].is_canonical { + tail.push(&self.viewport[tail_idx]); + } else { + break; + } + } + let selections = self.search_results.search_row(ridx, row, &tail); + for sel in &selections { + // Cast works because we can' be negative here + self.output_buffer + .update_lines(sel.start.line() as usize, sel.end.line() as usize); + } + + for selection in selections { + self.search_results.selections.push(selection); + } + } + } + + pub fn toggle_search_case_sensitivity(&mut self) { + self.search_results.case_insensitive = !self.search_results.case_insensitive; + for line in self.search_results.selections.drain(..) { + self.output_buffer + .update_lines(line.start.line() as usize, line.end.line() as usize); + } + self.search_viewport(); + // Maybe the selection we had is now gone + self.search_results.unset_active_selection_if_nonexistent(); + } + + pub fn toggle_search_wrap(&mut self) { + self.search_results.wrap_search = !self.search_results.wrap_search; + } + + pub fn toggle_search_whole_words(&mut self) { + self.search_results.whole_word_only = !self.search_results.whole_word_only; + for line in self.search_results.selections.drain(..) { + self.output_buffer + .update_lines(line.start.line() as usize, line.end.line() as usize); + } + self.search_results.active = None; + self.search_viewport(); + // Maybe the selection we had is now gone + self.search_results.unset_active_selection_if_nonexistent(); + } + + fn search_scrollbuffer(&mut self, dir: SearchDirection) { + let first_sel = self.search_results.selections.first(); + let last_sel = self.search_results.selections.last(); + + let search_viewport_for_the_first_time = + self.search_results.active.is_none() && !self.search_results.selections.is_empty(); + + // We are not at the end yet, so we can iterate to the next search-result within the current viewport + let search_viewport_again = !self.search_results.selections.is_empty() + && self.search_results.active.is_some() + && match dir { + SearchDirection::Up => self.search_results.active.as_ref() != first_sel, + SearchDirection::Down => self.search_results.active.as_ref() != last_sel, + }; + + if search_viewport_for_the_first_time || search_viewport_again { + // We can stay in the viewport and just move the active selection + self.search_viewport_again(search_viewport_for_the_first_time, dir); + } else { + // Need to move the viewport + let found_something = self.search_viewport_move(dir); + + // We haven't found anything, but we are allowed to wrap around + if !found_something && self.search_results.wrap_search { + self.search_viewport_wrap(dir); + } + } + } + + fn search_viewport_again( + &mut self, + search_viewport_for_the_first_time: bool, + dir: SearchDirection, + ) { + let new_active = match dir { + SearchDirection::Up => self.search_results.selections.last().cloned().unwrap(), + SearchDirection::Down => self.search_results.selections.first().cloned().unwrap(), + }; + // We can stay in the viewport and just move the active selection + let active_idx = self.search_results.active.get_or_insert(new_active); + self.output_buffer.update_lines( + active_idx.start.line() as usize, + active_idx.end.line() as usize, + ); + if !search_viewport_for_the_first_time { + match dir { + SearchDirection::Up => self.search_results.move_active_selection_to_prev(), + SearchDirection::Down => self.search_results.move_active_selection_to_next(), + }; + if let Some(new_active) = self.search_results.active { + self.output_buffer.update_lines( + new_active.start.line() as usize, + new_active.end.line() as usize, + ); + } + } + } + + fn search_reached_opposite_end(&mut self, dir: SearchDirection) -> bool { + match dir { + SearchDirection::Up => self.lines_above.is_empty(), + SearchDirection::Down => self.lines_below.is_empty(), + } + } + + fn search_viewport_move(&mut self, dir: SearchDirection) -> bool { + // We need to move the viewport + let mut rows = 0; + let mut found_something = false; + + // We might loose the current selection, if we can't find anything + let current_active_selection = self.search_results.active; + while !found_something && !self.search_reached_opposite_end(dir) { + rows += 1; + found_something = match dir { + SearchDirection::Up => self.scroll_up_one_line(), + SearchDirection::Down => self.scroll_down_one_line(), + }; + } + + if found_something { + self.search_adjust_to_new_selection(dir); + } else { + // We didn't find something, so we scroll back to the start + for _ in 0..rows { + match dir { + SearchDirection::Up => self.scroll_down_one_line(), + SearchDirection::Down => self.scroll_up_one_line(), + }; + } + self.search_results.active = current_active_selection; + } + found_something + } + + fn search_adjust_to_new_selection(&mut self, dir: SearchDirection) { + match dir { + SearchDirection::Up => { + self.search_results.move_active_selection_to_prev(); + }, + SearchDirection::Down => { + // We may need to scroll a bit further, because we are at the beginning of the + // search result, but the end might be invisible + if let Some(last) = self.search_results.selections.last() { + let distance = (last.end.line() - last.start.line()) as usize; + if distance < self.height { + for _ in 0..distance { + self.scroll_down_one_line(); + } + } + } + self.search_results.move_active_selection_to_next(); + }, + } + self.output_buffer.update_all_lines(); + } + + fn search_viewport_wrap(&mut self, dir: SearchDirection) { + // We might loose the current selection, if we can't find anything + let current_active_selection = self.search_results.active; + // UP + // Go to the opposite end (bottom when searching up and top when searching down) + let mut rows = self.move_viewport_to_opposite_end(dir); + + // We are at the bottom or top. Maybe we found already something there + // If not, scroll back again, until we find something + let mut found_something = match dir { + SearchDirection::Up => self.search_results.selections.last().is_some(), + SearchDirection::Down => self.search_results.selections.first().is_some(), + }; + + // We didn't find anything at the opposing end of the scrollbuffer, so we scroll back until we find something + if !found_something { + while rows >= 0 && !found_something { + rows -= 1; + found_something = match dir { + SearchDirection::Up => self.scroll_up_one_line(), + SearchDirection::Down => self.scroll_down_one_line(), + }; + } + } + if found_something { + self.search_results.active = match dir { + SearchDirection::Up => self.search_results.selections.last().cloned(), + SearchDirection::Down => { + // We need to scroll until the found item is at the top + if let Some(first) = self.search_results.selections.first() { + for _ in 0..first.start.line() { + self.scroll_down_one_line(); + } + } + self.search_results.selections.first().cloned() + }, + }; + self.output_buffer.update_all_lines(); + } else { + // We didn't find anything, so we reset the old active selection + self.search_results.active = current_active_selection; + } + } + + fn move_viewport_to_opposite_end(&mut self, dir: SearchDirection) -> isize { + let mut rows = 0; + match dir { + SearchDirection::Up => { + // Go to the bottom + while !self.lines_below.is_empty() { + rows += 1; + self.scroll_down_one_line(); + } + }, + SearchDirection::Down => { + // Go to the top + while !self.lines_above.is_empty() { + rows += 1; + self.scroll_up_one_line(); + } + }, + } + rows + } +} diff --git a/zellij-server/src/panes/selection.rs b/zellij-server/src/panes/selection.rs index d9f3041b..cfe10c5c 100644 --- a/zellij-server/src/panes/selection.rs +++ b/zellij-server/src/panes/selection.rs @@ -4,7 +4,7 @@ use zellij_utils::position::Position; // The selection is empty when start == end // it includes the character at start, and everything before end. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Selection { pub start: Position, pub end: Position, diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index b6640037..666260e6 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -54,6 +54,7 @@ pub struct TerminalPane { frame: HashMap, borderless: bool, fake_cursor_locations: HashSet<(usize, usize)>, // (x, y) - these hold a record of previous fake cursors which we need to clear on render + search_term: String, } impl Pane for TerminalPane { @@ -225,12 +226,39 @@ impl Pane for TerminalPane { PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb), PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col), }; - character_chunk.add_selection_and_background( + character_chunk.add_selection_and_colors( self.grid.selection, background_color, + None, content_x, content_y, ); + } else if !self.grid.search_results.selections.is_empty() { + for res in self.grid.search_results.selections.iter() { + if res.contains_row(character_chunk.y.saturating_sub(content_y)) { + let (select_background_palette, select_foreground_palette) = + if Some(res) == self.grid.search_results.active.as_ref() { + (self.style.colors.orange, self.style.colors.black) + } else { + (self.style.colors.green, self.style.colors.black) + }; + let background_color = match select_background_palette { + PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb), + PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col), + }; + let foreground_color = match select_foreground_palette { + PaletteColor::Rgb(rgb) => AnsiCode::RgbCode(rgb), + PaletteColor::EightBit(col) => AnsiCode::ColorIndex(col), + }; + character_chunk.add_selection_and_colors( + *res, + background_color, + Some(foreground_color), + content_x, + content_y, + ); + } + } } } if self.grid.ring_bell { @@ -256,6 +284,31 @@ impl Pane for TerminalPane { && frame_params.is_main_client { String::from("Enter name...") + } else if input_mode == InputMode::EnterSearch + && frame_params.is_main_client + && self.search_term.is_empty() + { + String::from("Enter search...") + } else if (input_mode == InputMode::EnterSearch || input_mode == InputMode::Search) + && !self.search_term.is_empty() + { + let mut modifier_text = String::new(); + if self.grid.search_results.has_modifiers_set() { + let mut modifiers = Vec::new(); + modifier_text.push_str(" ["); + if self.grid.search_results.case_insensitive { + modifiers.push("c") + } + if self.grid.search_results.whole_word_only { + modifiers.push("o") + } + if self.grid.search_results.wrap_search { + modifiers.push("w") + } + modifier_text.push_str(&modifiers.join(", ")); + modifier_text.push(']'); + } + format!("SEARCHING: {}{}", self.search_term, modifier_text) } else if self.pane_name.is_empty() { self.grid .title @@ -264,6 +317,7 @@ impl Pane for TerminalPane { } else { self.pane_name.clone() }; + let frame = PaneFrame::new( self.current_geom().into(), self.grid.scrollback_position_and_length(), @@ -495,10 +549,60 @@ impl Pane for TerminalPane { fn mouse_mode(&self) -> bool { self.grid.mouse_mode } + fn get_line_number(&self) -> Option { // + 1 because the absolute position in the scrollback is 0 indexed and this should be 1 indexed Some(self.grid.absolute_position_in_scrollback() + 1) } + + fn update_search_term(&mut self, needle: &str) { + match needle { + "\0" => { + self.search_term = String::new(); + }, + "\u{007F}" | "\u{0008}" => { + //delete and backspace keys + self.search_term.pop(); + }, + c => { + self.search_term.push_str(c); + }, + } + self.grid.clear_search(); + if !self.search_term.is_empty() { + self.grid.set_search_string(&self.search_term); + } + self.set_should_render(true); + } + fn search_down(&mut self) { + if self.search_term.is_empty() { + return; // No-op + } + self.grid.search_down(); + self.set_should_render(true); + } + fn search_up(&mut self) { + if self.search_term.is_empty() { + return; // No-op + } + self.grid.search_up(); + self.set_should_render(true); + } + fn toggle_search_case_sensitivity(&mut self) { + self.grid.toggle_search_case_sensitivity(); + self.set_should_render(true); + } + fn toggle_search_whole_words(&mut self) { + self.grid.toggle_search_whole_words(); + self.set_should_render(true); + } + fn toggle_search_wrap(&mut self) { + self.grid.toggle_search_wrap(); + } + fn clear_search(&mut self) { + self.grid.clear_search(); + self.search_term.clear(); + } } impl TerminalPane { @@ -542,6 +646,7 @@ impl TerminalPane { prev_pane_name: pane_name, borderless: false, fake_cursor_locations: HashSet::new(), + search_term: String::new(), } } pub fn get_x(&self) -> usize { @@ -586,3 +691,7 @@ impl TerminalPane { #[cfg(test)] #[path = "./unit/terminal_pane_tests.rs"] mod grid_tests; + +#[cfg(test)] +#[path = "./unit/search_in_pane_tests.rs"] +mod search_tests; diff --git a/zellij-server/src/panes/unit/search_in_pane_tests.rs b/zellij-server/src/panes/unit/search_in_pane_tests.rs new file mode 100644 index 00000000..cdcc6bc0 --- /dev/null +++ b/zellij-server/src/panes/unit/search_in_pane_tests.rs @@ -0,0 +1,413 @@ +use super::super::TerminalPane; +use crate::panes::sixel::SixelImageStore; +use crate::panes::LinkHandler; +use crate::tab::Pane; +use insta::assert_snapshot; +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use zellij_utils::data::{Palette, Style}; +use zellij_utils::pane_size::PaneGeom; + +fn read_fixture() -> Vec { + let mut path_to_file = std::path::PathBuf::new(); + path_to_file.push("../src"); + path_to_file.push("tests"); + path_to_file.push("fixtures"); + path_to_file.push("grid_copy"); + std::fs::read(path_to_file) + .unwrap_or_else(|_| panic!("could not read fixture ../src/tests/fixtures/grid_copy")) +} + +fn create_pane() -> TerminalPane { + let mut fake_win_size = PaneGeom::default(); + fake_win_size.cols.set_inner(121); + fake_win_size.rows.set_inner(20); + + let pid = 1; + let style = Style::default(); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); + let mut terminal_pane = TerminalPane::new( + pid, + fake_win_size, + style, + 0, + String::new(), + Rc::new(RefCell::new(LinkHandler::new())), + Rc::new(RefCell::new(None)), + sixel_image_store, + Rc::new(RefCell::new(Palette::default())), + terminal_emulator_color_codes, + ); // 0 is the pane index + let content = read_fixture(); + terminal_pane.handle_pty_bytes(content); + terminal_pane +} + +#[test] +pub fn searching_inside_a_viewport() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("tortor"); + assert_snapshot!( + "grid_copy_tortor_highlighted", + format!("{:?}", terminal_pane.grid) + ); + terminal_pane.search_up(); + // snapshot-size optimization: We use a named one here to de-duplicate + assert_snapshot!( + "grid_copy_search_cursor_at_bottom", + format!("{:?}", terminal_pane.grid) + ); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_cursor_at_second", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_scroll_viewport() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("tortor"); + terminal_pane.search_up(); + // snapshot-size optimization: We use a named one here to de-duplicate + assert_snapshot!( + "grid_copy_search_cursor_at_bottom", + format!("{:?}", terminal_pane.grid) + ); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_cursor_at_second", + format!("{:?}", terminal_pane.grid) + ); + // Scroll away + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_scrolled_up", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_with_wrap() { + let mut terminal_pane = create_pane(); + // Searching for "tortor" + terminal_pane.update_search_term("tortor"); + // Selecting the last place tortor was found + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_cursor_at_bottom", + format!("{:?}", terminal_pane.grid) + ); + // Search backwards again + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_cursor_at_second", + format!("{:?}", terminal_pane.grid) + ); + terminal_pane.search_down(); + assert_snapshot!( + "grid_copy_search_cursor_at_bottom", + format!("{:?}", terminal_pane.grid) + ); + // Searching forward again should do nothing here + terminal_pane.search_down(); + assert_snapshot!( + "grid_copy_search_cursor_at_bottom", + format!("{:?}", terminal_pane.grid) + ); + // Only after wrapping search is active, do we actually jump in the scroll buffer + terminal_pane.toggle_search_wrap(); + terminal_pane.search_down(); + assert_snapshot!( + "grid_copy_search_cursor_at_top", + format!("{:?}", terminal_pane.grid) + ); + + // Deactivate wrap again + terminal_pane.toggle_search_wrap(); + // Should be a no-op again + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_cursor_at_top", + format!("{:?}", terminal_pane.grid) + ); + + // Re-activate wrap again + terminal_pane.toggle_search_wrap(); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_search_cursor_at_bottom", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_case_insensitive() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("quam"); + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + // sensitivity off + terminal_pane.toggle_search_case_sensitivity(); + + assert_snapshot!( + "grid_copy_quam_insensitive_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + // sensitivity on + terminal_pane.toggle_search_case_sensitivity(); + + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + // Select one and check that we keep the current selection, + // if it wasn't one that vanished + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_quam_highlighted_cursor_bottom", + format!("{:?}", terminal_pane.grid) + ); + + // sensitivity off + terminal_pane.toggle_search_case_sensitivity(); + + assert_snapshot!( + "grid_copy_quam_insensitive_cursor_bottom", + format!("{:?}", terminal_pane.grid) + ); + + // sensitivity on + terminal_pane.toggle_search_case_sensitivity(); + + assert_snapshot!( + "grid_copy_quam_highlighted_cursor_bottom", + format!("{:?}", terminal_pane.grid) + ); + + // sensitivity off + terminal_pane.toggle_search_case_sensitivity(); + + // Selecting the case insensitive result + terminal_pane.search_up(); + terminal_pane.search_up(); + terminal_pane.search_up(); + terminal_pane.search_up(); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_quam_insensitive_selection", + format!("{:?}", terminal_pane.grid) + ); + + // sensitivity on + terminal_pane.toggle_search_case_sensitivity(); + // Now the selected result vanished and we should be back at + // the beginning + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_quam_highlighted_cursor_bottom", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_inside_and_scroll() { + let fake_client_id = 1; + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("quam"); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_quam_highlighted_cursor_bottom", + format!("{:?}", terminal_pane.grid) + ); + assert_eq!( + terminal_pane.grid.search_results.active.as_ref(), + terminal_pane.grid.search_results.selections.last() + ); + // Scrolling up until a new search result appears + terminal_pane.scroll_up(4, fake_client_id); + + // Scrolling back down should give the same result as before + terminal_pane.scroll_down(4, fake_client_id); + assert_eq!( + terminal_pane.grid.search_results.active.as_ref(), + terminal_pane.grid.search_results.selections.last() + ); + assert_snapshot!( + "grid_copy_quam_highlighted_cursor_bottom", + format!("{:?}", terminal_pane.grid) + ); + + // Scrolling up until a the active marker goes out of view + terminal_pane.scroll_up(5, fake_client_id); + assert_eq!(terminal_pane.grid.search_results.active, None); + + terminal_pane.scroll_down(5, fake_client_id); + assert_eq!(terminal_pane.grid.search_results.active, None); + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_and_resize() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("tortor"); + assert_snapshot!( + "grid_copy_tortor_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + // Highlights should still be there, if pane gets resized + terminal_pane.grid.change_size(20, 150); + assert_snapshot!( + "grid_copy_tortor_highlighted_wide", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.grid.change_size(20, 80); + assert_snapshot!( + "grid_copy_tortor_highlighted_narrow", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_across_line_wrap() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("aliquam sem fringilla"); + // Spread across two lines + terminal_pane.grid.change_size(30, 60); + assert_snapshot!( + "grid_copy_multiline_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + // Spread across 4 lines + terminal_pane.grid.change_size(40, 4); + assert_snapshot!( + "grid_copy_multiline_highlighted_narrow", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_multiline_selected_narrow", + format!("{:?}", terminal_pane.grid) + ); + + // Wrap on + terminal_pane.toggle_search_wrap(); + terminal_pane.search_down(); + assert_snapshot!( + "grid_copy_multiline_selected_wrap_narrow", + format!("{:?}", terminal_pane.grid) + ); + + // Wrap off + terminal_pane.toggle_search_wrap(); + // Don't forget the current selection + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_multiline_selected_wrap_narrow", + format!("{:?}", terminal_pane.grid) + ); + + // Wrap on + terminal_pane.toggle_search_wrap(); + terminal_pane.search_up(); + assert_snapshot!( + "grid_copy_multiline_selected_narrow", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_whole_word() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("quam"); + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_whole_words(); + assert_snapshot!( + "grid_copy_quam_whole_word_only", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_whole_words(); + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_whole_word_across_line_wrap() { + let mut terminal_pane = create_pane(); + terminal_pane.handle_pty_bytes( + "a:--:aaaaaaaaa:--:--:--:aaaaaaaaaaa:--: :--: :--: aaa :--::--: aaa" + .as_bytes() + .to_vec(), + ); + terminal_pane.grid.change_size(20, 5); + terminal_pane.update_search_term(":--:"); + assert_snapshot!( + "grid_copy_multiline_not_whole_word", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_whole_words(); + assert_snapshot!( + "grid_copy_multiline_whole_word", + format!("{:?}", terminal_pane.grid) + ); +} + +#[test] +pub fn searching_whole_word_case_insensitive() { + let mut terminal_pane = create_pane(); + terminal_pane.update_search_term("quam"); + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_whole_words(); + assert_snapshot!( + "grid_copy_quam_whole_word_only", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_case_sensitivity(); + assert_snapshot!( + "grid_copy_quam_whole_word_case_insensitive", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_whole_words(); + assert_snapshot!( + "grid_copy_quam_insensitive_highlighted", + format!("{:?}", terminal_pane.grid) + ); + + terminal_pane.toggle_search_case_sensitivity(); + assert_snapshot!( + "grid_copy_quam_highlighted", + format!("{:?}", terminal_pane.grid) + ); +} diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_highlighted.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_highlighted.snap new file mode 100644 index 00000000..7cb9ce03 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_highlighted.snap @@ -0,0 +1,35 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): d elementum tempus egestas sed sed risus pretium quam vulput +01 (W): ate. Turpis egestas maecenas pharetra convallis. Arcu cursus +02 (W): vitae congue mauris rhoncus aenean vel. Augue ut lectus arc +03 (W): u bibendum. Scelerisque varius morbi enim nunc faucibus a pe +04 (W): llentesque. Mattis pellentesque id nibh tortor id aliquet le +05 (W): ctus proin nibh. In ##################### ut. Urna et pharet +06 (W): ra pharetra massa massa ultricies mi. Enim nulla aliquet por +07 (W): ttitor lacus luctus accumsan tortor posuere. Malesuada fames +08 (W): ac turpis egestas integer. Venenatis tellus in metus vulput +09 (W): ate eu scelerisque felis. Suspendisse faucibus interdum posu +10 (W): ere lorem ipsum dolor sit amet. +11 (C): +12 (C): Quam elementum pulvinar etiam non quam lacus suspendisse fau +13 (W): cibus. Egestas sed sed risus pretium quam vulputate dignissi +14 (W): m suspendisse. Risus nec feugiat in fermentum posuere urna. +15 (W): Vestibulum lorem sed risus ultricies. Egestas maecenas phare +16 (W): tra convallis posuere morbi. Egestas tellus rutrum tellus pe +17 (W): llentesque. Pulvinar etiam non quam lacus suspendisse faucib +18 (W): us. Lectus proin nibh nisl condimentum id venenatis a condim +19 (W): entum. Adipiscing elit pellentesque habitant morbi tristique +20 (W): senectus et netus. Nunc id cursus metus aliquam eleifend. U +21 (W): rna nec tincidunt praesent semper feugiat nibh sed pulvinar. +22 (W): Donec ultrices tincidunt arcu non sodales neque sodales ut +23 (W): etiam. Suspendisse sed nisi lacus sed viverra tellus in hac +24 (W): habitasse. Nunc scelerisque viverra mauris in ############## +25 (W): #######. +26 (C): ⏎ +27 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-b +28 (W): eta.3 +29 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_highlighted_narrow.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_highlighted_narrow.snap new file mode 100644 index 00000000..417ec055 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_highlighted_narrow.snap @@ -0,0 +1,45 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): a te +01 (W): llus +02 (W): in +03 (W): hac +04 (W): habi +05 (W): tass +06 (W): e. N +07 (W): unc +08 (W): scel +09 (W): eris +10 (W): que +11 (W): vive +12 (W): rra +13 (W): maur +14 (W): is i +15 (W): n ## +16 (W): #### +17 (W): #### +18 (W): #### +19 (W): #### +20 (W): ###. +21 (C): ⏎ +22 (C): zell +23 (W): ij o +24 (W): n  +25 (W): mous +26 (W): e-su +27 (W): ppor +28 (W): t [? +29 (W): ] is +30 (W): 📦 +31 (W): v0.1 +32 (W): 4.0 +33 (W): via +34 (W): 🦀 v +35 (W): 1.53 +36 (W): .0-b +37 (W): eta. +38 (W): 3 +39 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_not_whole_word.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_not_whole_word.snap new file mode 100644 index 00000000..707bb759 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_not_whole_word.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): v0.1 +01 (W): 4.0 v +02 (W): ia 🦀 +03 (W): v1.5 +04 (W): 3.0-b +05 (W): eta.3 +06 (C): ❯ a## +07 (W): ##aaa +08 (W): aaaaa +09 (W): a#### +10 (W): --### +11 (W): #aaaa +12 (W): aaaaa +13 (W): aa### +14 (W): # ### +15 (W): # ### +16 (W): # aaa +17 (W): #### +18 (W): #### +19 (W): aaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_selected_narrow.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_selected_narrow.snap new file mode 100644 index 00000000..4ed38e5c --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_selected_narrow.snap @@ -0,0 +1,45 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): a te +01 (W): llus +02 (W): in +03 (W): hac +04 (W): habi +05 (W): tass +06 (W): e. N +07 (W): unc +08 (W): scel +09 (W): eris +10 (W): que +11 (W): vive +12 (W): rra +13 (W): maur +14 (W): is i +15 (W): n __ +16 (W): ____ +17 (W): ____ +18 (W): ____ +19 (W): ____ +20 (W): ___. +21 (C): ⏎ +22 (C): zell +23 (W): ij o +24 (W): n  +25 (W): mous +26 (W): e-su +27 (W): ppor +28 (W): t [? +29 (W): ] is +30 (W): 📦 +31 (W): v0.1 +32 (W): 4.0 +33 (W): via +34 (W): 🦀 v +35 (W): 1.53 +36 (W): .0-b +37 (W): eta. +38 (W): 3 +39 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_selected_wrap_narrow.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_selected_wrap_narrow.snap new file mode 100644 index 00000000..ef8e2a83 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_selected_wrap_narrow.snap @@ -0,0 +1,45 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): n __ +01 (W): ____ +02 (W): ____ +03 (W): ____ +04 (W): ____ +05 (W): ___ +06 (W): ut. +07 (C): Vulp +08 (W): utat +09 (W): e eu +10 (W): sce +11 (W): leri +12 (W): sque +13 (W): fel +14 (W): is i +15 (W): mper +16 (W): diet +17 (W): pro +18 (W): in f +19 (W): erme +20 (W): ntum +21 (W): leo +22 (W): . +23 (C): Curs +24 (W): us r +25 (W): isus +26 (W): at +27 (W): ultr +28 (W): ices +29 (W): mi +30 (W): temp +31 (W): us. +32 (C): Laor +33 (W): eet +34 (W): id d +35 (W): onec +36 (W): ult +37 (W): rice +38 (W): s ti +39 (W): ncid + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_whole_word.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_whole_word.snap new file mode 100644 index 00000000..7641c5e5 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_multiline_whole_word.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): v0.1 +01 (W): 4.0 v +02 (W): ia 🦀 +03 (W): v1.5 +04 (W): 3.0-b +05 (W): eta.3 +06 (C): ❯ a:- +07 (W): -:aaa +08 (W): aaaaa +09 (W): a:--# +10 (W): ###-- +11 (W): :aaaa +12 (W): aaaaa +13 (W): aa:-- +14 (W): : ### +15 (W): # ### +16 (W): # aaa +17 (W): #### +18 (W): #### +19 (W): aaa + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_highlighted.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_highlighted.snap new file mode 100644 index 00000000..d9e59dcd --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_highlighted.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis ali#### malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In ali#### sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): Quam elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus ali#### eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in ali#### sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_highlighted_cursor_bottom.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_highlighted_cursor_bottom.snap new file mode 100644 index 00000000..70e987eb --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_highlighted_cursor_bottom.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis ali#### malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In ali#### sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): Quam elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus ali#### eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in ali____ sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_cursor_bottom.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_cursor_bottom.snap new file mode 100644 index 00000000..f9e56666 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_cursor_bottom.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis ali#### malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In ali#### sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): #### elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus ali#### eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in ali____ sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_highlighted.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_highlighted.snap new file mode 100644 index 00000000..0af99783 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_highlighted.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis ali#### malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In ali#### sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): #### elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus ali#### eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in ali#### sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_selection.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_selection.snap new file mode 100644 index 00000000..e2449cb8 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_insensitive_selection.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis ali#### malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In ali#### sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): ____ elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus ali#### eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in ali#### sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_whole_word_case_insensitive.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_whole_word_case_insensitive.snap new file mode 100644 index 00000000..2c9697c1 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_whole_word_case_insensitive.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis aliquam malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): #### elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in aliquam sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_whole_word_only.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_whole_word_only.snap new file mode 100644 index 00000000..fc53f4c9 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_quam_whole_word_only.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel ####. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis aliquam malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium #### vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id aliquet lectus +05 (W): proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): Quam elementum pulvinar etiam non #### lacus suspendisse faucibus. Egestas sed sed risus pretium #### vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non #### lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in aliquam sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_bottom.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_bottom.snap new file mode 100644 index 00000000..a4da4ce5 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_bottom.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel quam. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis aliquam malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium quam vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh ###### id aliquet lectus +05 (W): proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan ______ posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): Quam elementum pulvinar etiam non quam lacus suspendisse faucibus. Egestas sed sed risus pretium quam vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non quam lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in aliquam sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_second.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_second.snap new file mode 100644 index 00000000..f7194efc --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_second.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel quam. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis aliquam malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium quam vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh ______ id aliquet lectus +05 (W): proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan ###### posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): Quam elementum pulvinar etiam non quam lacus suspendisse faucibus. Egestas sed sed risus pretium quam vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non quam lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in aliquam sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_top.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_top.snap new file mode 100644 index 00000000..82a107ea --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_cursor_at_top.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): Velit ut ______ pretium viverra suspendisse potenti nullam ac ######. Adipiscing elit ut aliquam purus sit amet luctus ve +01 (W): nenatis. +02 (C): Duis ut diam quam nulla porttitor massa id neque aliquam. Suspendisse potenti nullam ac ###### vitae purus faucibus ornar +03 (W): e suspendisse. +04 (C): Vitae nunc sed velit dignissim sodales ut eu sem integer. +05 (C): Tortor id aliquet lectus proin nibh nisl. +06 (C): Commodo odio aenean sed adipiscing diam donec adipiscing tristique risus. +07 (C): Velit dignissim sodales ut eu sem. Lacus suspendisse faucibus interdum posuere lorem. Ac placerat vestibulum lectus mauri +08 (W): s ultrices eros. Elementum integer enim neque volutpat ac. Augue interdum velit euismod in. +09 (C): +10 (C): Egestas sed sed risus pretium quam vulputate dignissim. +11 (C): Gravida rutrum quisque non tellus orci ac auctor augue. +12 (C): Risus nec feugiat in fermentum posuere urna nec tincidunt praesent. +13 (C): Elementum eu facilisis sed odio morbi quis. +14 (C): Mattis ullamcorper velit sed ullamcorper morbi. +15 (C): Dui vivamus arcu felis bibendum. Sit amet aliquam id diam. +16 (C): Suscipit tellus mauris a diam maecenas sed enim. +17 (C): Odio ut sem nulla pharetra. +18 (C): Cras ornare arcu dui vivamus arcu felis bibendum. +19 (C): Egestas fringilla phasellus faucibus scelerisque eleifend. + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_scrolled_up.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_scrolled_up.snap new file mode 100644 index 00000000..916db85f --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_search_scrolled_up.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): Rutrum tellus pellentesque eu tincidunt ______ aliquam nulla. +01 (C): +02 (C): Nascetur ridiculus mus mauris vitae ultricies leo integer malesuada. +03 (C): Interdum posuere lorem ipsum dolor sit amet consectetur. +04 (C): Porta non pulvinar neque laoreet suspendisse interdum. +05 (C): Fames ac turpis egestas integer eget aliquet nibh praesent. +06 (C): Congue nisi vitae suscipit tellus mauris a diam maecenas sed. +07 (C): Nec ultrices dui sapien eget mi proin sed libero enim. +08 (C): Tellus rutrum tellus pellentesque eu tincidunt. +09 (C): Ultrices eros in cursus turpis massa tincidunt dui ut ornare. +10 (C): Arcu cursus vitae congue mauris rhoncus aenean vel elit scelerisque. +11 (C): Viverra mauris in aliquam sem fringilla ut. +12 (C): Vulputate eu scelerisque felis imperdiet proin fermentum leo. +13 (C): Cursus risus at ultrices mi tempus. +14 (C): Laoreet id donec ultrices tincidunt arcu non sodales. +15 (C): Amet dictum sit amet justo donec enim. +16 (C): Hac habitasse platea dictumst vestibulum rhoncus est pellentesque. +17 (C): Facilisi cras fermentum odio eu feugiat. +18 (C): Elit ut aliquam purus sit amet luctus venenatis lectus. +19 (C): Dignissim enim sit amet venenatis urna cursus. + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted.snap new file mode 100644 index 00000000..b57454d2 --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): +01 (C): Quisque id diam vel quam. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis aliquam malesuada bibendum +02 (W): arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium quam vulputat +03 (W): e. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu b +04 (W): ibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh ###### id aliquet lectus +05 (W): proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet porttito +06 (W): r lacus luctus accumsan ###### posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu +07 (W): scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. +08 (C): +09 (C): Quam elementum pulvinar etiam non quam lacus suspendisse faucibus. Egestas sed sed risus pretium quam vulputate dignissim +10 (W): suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetr +11 (W): a convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non quam lacus suspendisse faucibus. +12 (W): Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique sen +13 (W): ectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Done +14 (W): c ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habita +15 (W): sse. Nunc scelerisque viverra mauris in aliquam sem fringilla. +16 (C): ⏎ +17 (W): +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted_narrow.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted_narrow.snap new file mode 100644 index 00000000..adcd07da --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted_narrow.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (W): m nunc faucibus a pellentesque. Mattis pellentesque id nibh ###### id aliquet le +01 (W): ctus proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa ma +02 (W): ssa ultricies mi. Enim nulla aliquet porttitor lacus luctus accumsan ###### posu +03 (W): ere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulput +04 (W): ate eu scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolo +05 (W): r sit amet. +06 (C): +07 (C): Quam elementum pulvinar etiam non quam lacus suspendisse faucibus. Egestas sed s +08 (W): ed risus pretium quam vulputate dignissim suspendisse. Risus nec feugiat in ferm +09 (W): entum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas phare +10 (W): tra convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar +11 (W): etiam non quam lacus suspendisse faucibus. Lectus proin nibh nisl condimentum i +12 (W): d venenatis a condimentum. Adipiscing elit pellentesque habitant morbi tristique +13 (W): senectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt pr +14 (W): aesent semper feugiat nibh sed pulvinar. Donec ultrices tincidunt arcu non sodal +15 (W): es neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac +16 (W): habitasse. Nunc scelerisque viverra mauris in aliquam sem fringilla. +17 (C): ⏎ +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted_wide.snap b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted_wide.snap new file mode 100644 index 00000000..8059af8d --- /dev/null +++ b/zellij-server/src/panes/unit/snapshots/zellij_server__panes__terminal_pane__search_tests__grid_copy_tortor_highlighted_wide.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/panes/./unit/search_in_pane_tests.rs +expression: "format!(\"{:?}\", terminal_pane.grid)" +--- +00 (C): Dignissim enim sit amet venenatis urna cursus. +01 (C): Amet consectetur adipiscing elit ut aliquam purus. +02 (C): Elementum pulvinar etiam non quam lacus suspendisse. +03 (C): +04 (C): Quisque id diam vel quam. Id porta nibh venenatis cras sed felis eget velit aliquet. Sagittis aliquam malesuada bibendum arcu. Libero id faucibus nisl +05 (W): tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium quam vulputate. Turpis egestas maecenas pharetra convallis. Arcu cursus +06 (W): vitae congue mauris rhoncus aenean vel. Augue ut lectus arcu bibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesqu +07 (W): e id nibh ###### id aliquet lectus proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla aliquet por +08 (W): ttitor lacus luctus accumsan ###### posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus vulputate eu scelerisque felis. Susp +09 (W): endisse faucibus interdum posuere lorem ipsum dolor sit amet. +10 (C): +11 (C): Quam elementum pulvinar etiam non quam lacus suspendisse faucibus. Egestas sed sed risus pretium quam vulputate dignissim suspendisse. Risus nec feugi +12 (W): at in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pharetra convallis posuere morbi. Egestas tellus rutrum tellus pe +13 (W): llentesque. Pulvinar etiam non quam lacus suspendisse faucibus. Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellent +14 (W): esque habitant morbi tristique senectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. +15 (W): Donec ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus in hac habitasse. Nunc scelerisque vi +16 (W): verra mauris in aliquam sem fringilla. +17 (C): ⏎ +18 (C): zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 +19 (C): ❯ + diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 4420cf36..dc80814b 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -11,7 +11,7 @@ use zellij_utils::{ channels::SenderWithContext, data::Event, input::{ - actions::{Action, Direction, ResizeDirection}, + actions::{Action, Direction, ResizeDirection, SearchDirection, SearchOption}, command::TerminalAction, get_mode_info, }, @@ -417,6 +417,29 @@ fn route_action( _ => {}, }, Action::NoOp => {}, + Action::SearchInput(c) => { + session + .senders + .send_to_screen(ScreenInstruction::UpdateSearch(c, client_id)) + .unwrap(); + }, + Action::Search(d) => { + let instruction = match d { + SearchDirection::Down => ScreenInstruction::SearchDown(client_id), + SearchDirection::Up => ScreenInstruction::SearchUp(client_id), + }; + session.senders.send_to_screen(instruction).unwrap(); + }, + Action::SearchToggleOption(o) => { + let instruction = match o { + SearchOption::CaseSensitivity => { + ScreenInstruction::SearchToggleCaseSensitivity(client_id) + }, + SearchOption::WholeWord => ScreenInstruction::SearchToggleWholeWord(client_id), + SearchOption::Wrap => ScreenInstruction::SearchToggleWrap(client_id), + }; + session.senders.send_to_screen(instruction).unwrap(); + }, } should_break } diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index b5ebad53..7b354c16 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -31,6 +31,29 @@ use zellij_utils::{ ipc::{ClientAttributes, PixelDimensions}, }; +/// Get the active tab and call a closure on it +/// +/// If no active tab can be found, an error is logged instead. +/// +/// # Parameters +/// +/// - screen: An instance of `Screen` to operate on +/// - client_id: The client_id, usually taken from the `ScreenInstruction` that's being processed +/// - closure: A closure satisfying `|tab: &mut Tab| -> ()` +macro_rules! active_tab { + ($screen:ident, $client_id:ident, $closure:expr) => { + if let Some(active_tab) = $screen.get_active_tab_mut($client_id) { + // This could be made more ergonomic by declaring the type of 'active_tab' in the + // closure, known as "Type Ascription". Then we could hint the type here and forego the + // "&mut Tab" in all the closures below... + // See: https://github.com/rust-lang/rust/issues/23416 + $closure(active_tab); + } else { + log::error!("Active tab not found for client id: {:?}", $client_id); + } + }; +} + /// Instructions that can be sent to the [`Screen`]. #[derive(Debug, Clone)] pub enum ScreenInstruction { @@ -109,6 +132,12 @@ pub enum ScreenInstruction { RemoveOverlay(ClientId), ConfirmPrompt(ClientId), DenyPrompt(ClientId), + UpdateSearch(Vec, ClientId), + SearchDown(ClientId), + SearchUp(ClientId), + SearchToggleCaseSensitivity(ClientId), + SearchToggleWholeWord(ClientId), + SearchToggleWrap(ClientId), } impl From<&ScreenInstruction> for ScreenContext { @@ -203,6 +232,14 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::RemoveOverlay(..) => ScreenContext::RemoveOverlay, ScreenInstruction::ConfirmPrompt(..) => ScreenContext::ConfirmPrompt, ScreenInstruction::DenyPrompt(..) => ScreenContext::DenyPrompt, + ScreenInstruction::UpdateSearch(..) => ScreenContext::UpdateSearch, + ScreenInstruction::SearchDown(..) => ScreenContext::SearchDown, + ScreenInstruction::SearchUp(..) => ScreenContext::SearchUp, + ScreenInstruction::SearchToggleCaseSensitivity(..) => { + ScreenContext::SearchToggleCaseSensitivity + }, + ScreenInstruction::SearchToggleWholeWord(..) => ScreenContext::SearchToggleWholeWord, + ScreenInstruction::SearchToggleWrap(..) => ScreenContext::SearchToggleWrap, } } } @@ -777,6 +814,14 @@ impl Screen { .unwrap_or(&self.default_mode_info) .mode; + // If we leave the Search-related modes, we need to clear all previous searches + let search_related_modes = [InputMode::EnterSearch, InputMode::Search, InputMode::Scroll]; + if search_related_modes.contains(&previous_mode) + && !search_related_modes.contains(&mode_info.mode) + { + active_tab!(self, client_id, |tab: &mut Tab| tab.clear_search(client_id)); + } + if previous_mode == InputMode::Scroll && (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked) { @@ -845,29 +890,6 @@ impl Screen { } } -/// Get the active tab and call a closure on it -/// -/// If no active tab can be found, an error is logged instead. -/// -/// # Parameters -/// -/// - screen: An instance of `Screen` to operate on -/// - client_id: The client_id, usually taken from the `ScreenInstruction` that's being processed -/// - closure: A closure satisfying `|tab: &mut Tab| -> ()` -macro_rules! active_tab { - ($screen:ident, $client_id:ident, $closure:expr) => { - if let Some(active_tab) = $screen.get_active_tab_mut($client_id) { - // This could be made more ergonomic by declaring the type of 'active_tab' in the - // closure, known as "Type Ascription". Then we could hint the type here and forego the - // "&mut Tab" in all the closures below... - // See: https://github.com/rust-lang/rust/issues/23416 - $closure(active_tab); - } else { - log::error!("Active tab not found for client id: {:?}", $client_id); - } - }; -} - // The box is here in order to make the // NewClient enum smaller #[allow(clippy::boxed_local)] @@ -1334,6 +1356,35 @@ pub(crate) fn screen_thread_main( screen.render(); screen.unblock_input(); }, + ScreenInstruction::UpdateSearch(c, client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .update_search_term(c, client_id)); + screen.render(); + }, + ScreenInstruction::SearchDown(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .search_down(client_id)); + screen.render(); + }, + ScreenInstruction::SearchUp(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab.search_up(client_id)); + screen.render(); + }, + ScreenInstruction::SearchToggleCaseSensitivity(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .toggle_search_case_sensitivity(client_id)); + screen.render(); + }, + ScreenInstruction::SearchToggleWrap(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .toggle_search_wrap(client_id)); + screen.render(); + }, + ScreenInstruction::SearchToggleWholeWord(client_id) => { + active_tab!(screen, client_id, |tab: &mut Tab| tab + .toggle_search_whole_words(client_id)); + screen.render(); + }, } } } diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 2c4aa5ed..eb611a1e 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -298,6 +298,27 @@ pub trait Pane { fn get_line_number(&self) -> Option { None } + fn update_search_term(&mut self, _needle: &str) { + // No-op by default (only terminal-panes currently have search capability) + } + fn search_down(&mut self) { + // No-op by default (only terminal-panes currently have search capability) + } + fn search_up(&mut self) { + // No-op by default (only terminal-panes currently have search capability) + } + fn toggle_search_case_sensitivity(&mut self) { + // No-op by default (only terminal-panes currently have search capability) + } + fn toggle_search_whole_words(&mut self) { + // No-op by default (only terminal-panes currently have search capability) + } + fn toggle_search_wrap(&mut self) { + // No-op by default (only terminal-panes currently have search capability) + } + fn clear_search(&mut self) { + // No-op by default (only terminal-panes currently have search capability) + } } impl Tab { @@ -946,6 +967,7 @@ impl Tab { }); } pub fn write_to_active_terminal(&mut self, input_bytes: Vec, client_id: ClientId) { + self.clear_search(client_id); // this is an inexpensive operation if empty, if we need more such cleanups we should consider moving this and the rest to some sort of cleanup method let pane_id = if self.floating_panes.panes_are_visible() { self.floating_panes .get_active_pane_id(client_id) @@ -2022,6 +2044,53 @@ impl Tab { pub fn panes_to_hide_count(&self) -> usize { self.tiled_panes.panes_to_hide_count() } + + pub fn update_search_term(&mut self, buf: Vec, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + // It only allows printable unicode, delete and backspace keys. + let is_updatable = buf.iter().all(|u| matches!(u, 0x20..=0x7E | 0x08 | 0x7F)); + if is_updatable { + let s = str::from_utf8(&buf).unwrap(); + active_pane.update_search_term(s); + } + } + } + + pub fn search_down(&mut self, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.search_down(); + } + } + + pub fn search_up(&mut self, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.search_up(); + } + } + + pub fn toggle_search_case_sensitivity(&mut self, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.toggle_search_case_sensitivity(); + } + } + + pub fn toggle_search_wrap(&mut self, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.toggle_search_wrap(); + } + } + + pub fn toggle_search_whole_words(&mut self, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.toggle_search_whole_words(); + } + } + + pub fn clear_search(&mut self, client_id: ClientId) { + if let Some(active_pane) = self.get_active_pane_or_floating_pane_mut(client_id) { + active_pane.clear_search(); + } + } } #[cfg(test)] diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_floating_tab_highlight_fring.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_floating_tab_highlight_fring.snap new file mode 100644 index 00000000..fdb60046 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_floating_tab_highlight_fring.snap @@ -0,0 +1,26 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 1721 +expression: snapshot +--- +00 (C): +01 (C): +02 (C): +03 (C): +04 (C): +05 (C): ┌ SEARCHING: fring ──────────────────────── SCROLL: 0/103 ┐ +06 (C): │ │ +07 (C): │a mauris in aliquam sem fringilla. │ +08 (C): │ │ +09 (C): │ │ +10 (C): │ │ +11 (C): │ │ +12 (C): │ │ +13 (C): │ │ +14 (C): └──────────────────────────────────────────────────────────┘ +15 (C): +16 (C): +17 (C): +18 (C): +19 (C): + diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_floating_tab_nothing_highlighted.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_floating_tab_nothing_highlighted.snap new file mode 100644 index 00000000..eeb3b491 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_floating_tab_nothing_highlighted.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +expression: snapshot +--- +00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +01 (C): │ │ +02 (C): │ │ +03 (C): │ │ +04 (C): │ │ +05 (C): │ ┌ fish /home/thomas/Projects/zellij ─────── SCROLL: 0/103 ┐ │ +06 (C): │ │d viverra tellus in hac habitasse. Nunc scelerisque viverr│ │ +07 (C): │ │a mauris in aliquam sem fringilla. │ │ +08 (C): │ │⏎ │ │ +09 (C): │ │ │ │ +10 (C): │ │ │ │ +11 (C): │ │zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0│ │ +12 (C): │ │-beta.3 │ │ +13 (C): │ │❯ │ │ +14 (C): │ └──────────────────────────────────────────────────────────┘ │ +15 (C): │ │ +16 (C): │ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_highlight_tortor.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_highlight_tortor.snap new file mode 100644 index 00000000..db9499ab --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_highlight_tortor.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +expression: snapshot +--- +00 (C): ┌ SEARCHING: tortor ───────────────────────────────────────────────────────────────────────────────────── SCROLL: 0/58 ┐ +01 (C): │ │ +02 (C): │ │ +03 (C): │arcu bibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id alique│ +04 (C): │ │ +05 (C): │t porttitor lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus │ +06 (C): │ │ +07 (C): │ │ +08 (C): │ │ +09 (C): │ │ +10 (C): │ │ +11 (C): │ │ +12 (C): │ │ +13 (C): │ │ +14 (C): │ │ +15 (C): │ │ +16 (C): │ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_highlight_tortor_modified.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_highlight_tortor_modified.snap new file mode 100644 index 00000000..54357722 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_highlight_tortor_modified.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +expression: snapshot +--- +00 (C): ┌ SEARCHING: tortor [c, o, w] ─────────────────────────────────────────────────────────────────────────── SCROLL: 0/58 ┐ +01 (C): │ │ +02 (C): │ │ +03 (C): │arcu bibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id alique│ +04 (C): │ │ +05 (C): │t porttitor lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus │ +06 (C): │ │ +07 (C): │ │ +08 (C): │ │ +09 (C): │ │ +10 (C): │ │ +11 (C): │ │ +12 (C): │ │ +13 (C): │ │ +14 (C): │ │ +15 (C): │ │ +16 (C): │ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_nothing_highlighted.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_nothing_highlighted.snap new file mode 100644 index 00000000..9c4af499 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__search_tab_nothing_highlighted.snap @@ -0,0 +1,25 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +expression: snapshot +--- +00 (C): ┌ fish /home/thomas/Projects/zellij ───────────────────────────────────────────────────────────────────── SCROLL: 0/58 ┐ +01 (C): │m arcu. Libero id faucibus nisl tincidunt eget nullam non. Sed elementum tempus egestas sed sed risus pretium quam vulp│ +02 (C): │utate. Turpis egestas maecenas pharetra convallis. Arcu cursus vitae congue mauris rhoncus aenean vel. Augue ut lectus │ +03 (C): │arcu bibendum. Scelerisque varius morbi enim nunc faucibus a pellentesque. Mattis pellentesque id nibh tortor id alique│ +04 (C): │t lectus proin nibh. In aliquam sem fringilla ut. Urna et pharetra pharetra massa massa ultricies mi. Enim nulla alique│ +05 (C): │t porttitor lacus luctus accumsan tortor posuere. Malesuada fames ac turpis egestas integer. Venenatis tellus in metus │ +06 (C): │vulputate eu scelerisque felis. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. │ +07 (C): │ │ +08 (C): │Quam elementum pulvinar etiam non quam lacus suspendisse faucibus. Egestas sed sed risus pretium quam vulputate digniss│ +09 (C): │im suspendisse. Risus nec feugiat in fermentum posuere urna. Vestibulum lorem sed risus ultricies. Egestas maecenas pha│ +10 (C): │retra convallis posuere morbi. Egestas tellus rutrum tellus pellentesque. Pulvinar etiam non quam lacus suspendisse fau│ +11 (C): │cibus. Lectus proin nibh nisl condimentum id venenatis a condimentum. Adipiscing elit pellentesque habitant morbi trist│ +12 (C): │ique senectus et netus. Nunc id cursus metus aliquam eleifend. Urna nec tincidunt praesent semper feugiat nibh sed pulv│ +13 (C): │inar. Donec ultrices tincidunt arcu non sodales neque sodales ut etiam. Suspendisse sed nisi lacus sed viverra tellus i│ +14 (C): │n hac habitasse. Nunc scelerisque viverra mauris in aliquam sem fringilla. │ +15 (C): │⏎ │ +16 (C): │ │ +17 (C): │zellij on  mouse-support [?] is 📦 v0.14.0 via 🦀 v1.53.0-beta.3 │ +18 (C): │❯ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ + diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 24edc91a..bb6dbf65 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -25,7 +25,7 @@ use std::rc::Rc; use zellij_utils::nix; use zellij_utils::{ - data::{ModeInfo, Palette, Style}, + data::{InputMode, ModeInfo, Palette, Style}, input::command::TerminalAction, interprocess::local_socket::LocalSocketStream, ipc::{ClientToServerMsg, ServerToClientMsg}, @@ -98,7 +98,7 @@ impl ServerOsApi for FakeInputOutput { } // TODO: move to shared thingy with other test file -fn create_new_tab(size: Size) -> Tab { +fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { set_session_name("test".into()); let index = 0; let position = 0; @@ -108,7 +108,7 @@ fn create_new_tab(size: Size) -> Tab { }); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; - let mode_info = ModeInfo::default(); + let mode_info = default_mode; let style = Style::default(); let draw_pane_frames = true; let client_id = 1; @@ -221,7 +221,7 @@ fn read_fixture(fixture_name: &str) -> Vec { use crate::panes::grid::Grid; use crate::panes::link_handler::LinkHandler; -use ::insta::assert_snapshot; +use insta::assert_snapshot; use zellij_utils::vte; fn take_snapshot(ansi_instructions: &str, rows: usize, columns: usize, palette: Palette) -> String { @@ -307,7 +307,7 @@ fn dump_screen() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let map = Arc::new(Mutex::new(HashMap::new())); tab.os_api = Box::new(FakeInputOutput { file_dumps: map.clone(), @@ -331,7 +331,7 @@ fn new_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -357,7 +357,7 @@ fn floating_panes_persist_across_toggles() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -387,7 +387,7 @@ fn toggle_floating_panes_off() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -414,7 +414,7 @@ fn toggle_floating_panes_on() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -442,7 +442,7 @@ fn five_new_floating_panes() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -480,7 +480,7 @@ fn increase_floating_pane_size() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -507,7 +507,7 @@ fn decrease_floating_pane_size() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -534,7 +534,7 @@ fn resize_floating_pane_left() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -561,7 +561,7 @@ fn resize_floating_pane_right() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -588,7 +588,7 @@ fn resize_floating_pane_up() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -615,7 +615,7 @@ fn resize_floating_pane_down() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -642,7 +642,7 @@ fn move_floating_pane_focus_left() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -687,7 +687,7 @@ fn move_floating_pane_focus_right() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -733,7 +733,7 @@ fn move_floating_pane_focus_up() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -778,7 +778,7 @@ fn move_floating_pane_focus_down() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -824,7 +824,7 @@ fn move_floating_pane_focus_with_mouse() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -870,7 +870,7 @@ fn move_pane_focus_with_mouse_to_non_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -916,7 +916,7 @@ fn drag_pane_with_mouse() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -962,7 +962,7 @@ fn mark_text_inside_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -1016,7 +1016,7 @@ fn resize_tab_with_floating_panes() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -1059,7 +1059,7 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -1099,7 +1099,7 @@ fn shrink_whole_tab_with_floating_panes_horizontally_and_vertically_and_expand_b rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id_1 = PaneId::Terminal(2); let new_pane_id_2 = PaneId::Terminal(3); let new_pane_id_3 = PaneId::Terminal(4); @@ -1143,7 +1143,7 @@ fn embed_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.toggle_floating_panes(client_id, None); @@ -1170,7 +1170,7 @@ fn float_embedded_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.new_pane(new_pane_id, Some(client_id)); @@ -1196,7 +1196,7 @@ fn cannot_float_only_embedded_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); tab.handle_pty_bytes( 1, @@ -1223,7 +1223,7 @@ fn replacing_existing_wide_characters() { rows: 48, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); let pane_content = read_fixture("ncmpcpp-wide-chars"); tab.handle_pty_bytes(1, pane_content); @@ -1244,7 +1244,7 @@ fn rename_embedded_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); tab.handle_pty_bytes( 1, @@ -1268,7 +1268,7 @@ fn rename_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.new_pane(new_pane_id, Some(client_id)); @@ -1296,7 +1296,7 @@ fn wide_characters_in_left_title_side() { rows: 48, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); let pane_content = read_fixture("title-wide-chars"); tab.handle_pty_bytes(1, pane_content); @@ -1316,7 +1316,7 @@ fn save_cursor_position_across_resizes() { // resize the pane let size = Size { cols: 100, rows: 5 }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let mut output = Output::default(); tab.handle_pty_bytes( @@ -1413,7 +1413,7 @@ fn suppress_tiled_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); @@ -1435,7 +1435,7 @@ fn suppress_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); @@ -1461,7 +1461,7 @@ fn close_suppressing_tiled_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); @@ -1485,7 +1485,7 @@ fn close_suppressing_floating_pane() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); @@ -1513,7 +1513,7 @@ fn suppress_tiled_pane_float_it_and_close() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); @@ -1538,7 +1538,7 @@ fn suppress_floating_pane_embed_it_and_close_it() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); @@ -1567,7 +1567,7 @@ fn resize_whole_tab_while_tiled_pane_is_suppressed() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let mut output = Output::default(); tab.suppress_active_pane(new_pane_id, client_id); @@ -1593,7 +1593,7 @@ fn resize_whole_tab_while_floting_pane_is_suppressed() { rows: 20, }; let client_id = 1; - let mut tab = create_new_tab(size); + let mut tab = create_new_tab(size, ModeInfo::default()); let new_pane_id = PaneId::Terminal(2); let editor_pane_id = PaneId::Terminal(3); let mut output = Output::default(); @@ -1615,3 +1615,108 @@ fn resize_whole_tab_while_floting_pane_is_suppressed() { ); assert_snapshot!(snapshot); } + +#[test] +fn enter_search_pane() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let mode_info = ModeInfo { + mode: InputMode::Search, + ..Default::default() + }; + let mut tab = create_new_tab(size, mode_info); + let mut output = Output::default(); + let pane_content = read_fixture("grid_copy"); + tab.handle_pty_bytes(1, pane_content); + tab.render(&mut output, None); + let snapshot = take_snapshot( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!("search_tab_nothing_highlighted", snapshot); + + // Pane title should show 'tortor' as search term + // Only lines containing 'tortor' get marked as render-targets, so + // only those are updated (search-styling is not visible here). + tab.update_search_term("tortor".as_bytes().to_vec(), client_id); + tab.render(&mut output, None); + let snapshot = take_snapshot( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!("search_tab_highlight_tortor", snapshot); + + // Pane title should show search modifiers + tab.toggle_search_wrap(client_id); + tab.toggle_search_whole_words(client_id); + tab.toggle_search_case_sensitivity(client_id); + tab.render(&mut output, None); + let snapshot = take_snapshot( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!("search_tab_highlight_tortor_modified", snapshot); + + // And only the search term again + tab.toggle_search_wrap(client_id); + tab.toggle_search_whole_words(client_id); + tab.toggle_search_case_sensitivity(client_id); + + tab.render(&mut output, None); + let snapshot = take_snapshot( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!("search_tab_highlight_tortor", snapshot); +} + +#[test] +fn enter_search_floating_pane() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let mode_info = ModeInfo { + mode: InputMode::Search, + ..Default::default() + }; + let mut tab = create_new_tab(size, mode_info); + let new_pane_id = PaneId::Terminal(2); + let mut output = Output::default(); + tab.toggle_floating_panes(client_id, None); + tab.new_pane(new_pane_id, Some(client_id)); + + let pane_content = read_fixture("grid_copy"); + tab.handle_pty_bytes(2, pane_content); + tab.render(&mut output, None); + let snapshot = take_snapshot( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!("search_floating_tab_nothing_highlighted", snapshot); + + // Only the line inside the floating tab which contain 'fring' should be in the new snapshot + tab.update_search_term("fring".as_bytes().to_vec(), client_id); + tab.render(&mut output, None); + let snapshot = take_snapshot( + output.serialize().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!("search_floating_tab_highlight_fring", snapshot); +} diff --git a/zellij-utils/assets/config/default.yaml b/zellij-utils/assets/config/default.yaml index caff98a7..2aecc6d8 100644 --- a/zellij-utils/assets/config/default.yaml +++ b/zellij-utils/assets/config/default.yaml @@ -324,6 +324,90 @@ keybinds: # uncomment this and adjust key if using copy_on_select=false # - action: [Copy: ] # key: [ Alt: 'c'] + - action: [SwitchToMode: EnterSearch, SearchInput: [0],] + key: [Char: 's'] + entersearch: + - action: [SwitchToMode: Search,] + key: [Char: "\n"] + - action: [SearchInput: [27], SwitchToMode: Scroll,] + key: [Ctrl: 'c', Esc] + - action: [NewPane: ,] + key: [ Alt: 'n',] + - action: [MoveFocusOrTab: Left,] + key: [ Alt: 'h', Alt: Left] + - action: [MoveFocusOrTab: Right,] + key: [ Alt: 'l', Alt: Right] + - action: [MoveFocus: Down,] + key: [ Alt: 'j', Alt: Down] + - action: [MoveFocus: Up,] + key: [ Alt: 'k', Alt: Up] + - action: [Resize: Increase,] + key: [ Alt: '='] + - action: [Resize: Increase,] + key: [ Alt: '+'] + - action: [Resize: Decrease,] + key: [ Alt: '-'] + search: + - action: [SwitchToMode: Normal,] + key: [Ctrl: 's', Char: ' ', Char: "\n", Esc] + - action: [SwitchToMode: Tab,] + key: [Ctrl: 't',] + - action: [SwitchToMode: Locked,] + key: [Ctrl: 'g',] + - action: [SwitchToMode: Pane,] + key: [Ctrl: 'p',] + - action: [SwitchToMode: Move,] + key: [Ctrl: 'h',] + - action: [SwitchToMode: Tmux,] + key: [Ctrl: 'b',] + - action: [SwitchToMode: Session,] + key: [Ctrl: 'o',] + - action: [SwitchToMode: Resize,] + key: [Ctrl: 'n',] + - action: [ScrollToBottom, SwitchToMode: Normal,] + key: [Ctrl: 'c',] + - action: [Quit,] + key: [Ctrl: 'q',] + - action: [ScrollDown,] + key: [Char: 'j', Down,] + - action: [ScrollUp,] + key: [Char: 'k', Up,] + - action: [PageScrollDown,] + key: [Ctrl: 'f', PageDown, Right, Char: 'l',] + - action: [PageScrollUp,] + key: [Ctrl: 'b', PageUp, Left, Char: 'h',] + - action: [HalfPageScrollDown,] + key: [Char: 'd',] + - action: [HalfPageScrollUp,] + key: [Char: 'u',] + - action: [NewPane: ,] + key: [ Alt: 'n',] + - action: [MoveFocusOrTab: Left,] + key: [ Alt: 'h', Alt: Left] # The Alt: Left etc. variants are temporary hacks and will be removed in the future - please do not rely on them! + - action: [MoveFocusOrTab: Right,] + key: [ Alt: 'l', Alt: Right] + - action: [MoveFocus: Down,] + key: [ Alt: 'j', Alt: Down] + - action: [MoveFocus: Up,] + key: [ Alt: 'k', Alt: Up] + - action: [Resize: Increase,] + key: [ Alt: '='] + - action: [Resize: Increase,] + key: [ Alt: '+'] + - action: [Resize: Decrease,] + key: [ Alt: '-'] + - action: [SwitchToMode: EnterSearch, SearchInput: [0],] + key: [Char: 's'] + - action: [Search: Down] + key: [Char: 'n'] + - action: [Search: Up] + key: [Char: 'p'] + - action: [SearchToggleOption: CaseSensitivity] + key: [Char: 'c'] + - action: [SearchToggleOption: Wrap] + key: [Char: 'w'] + - action: [SearchToggleOption: WholeWord] + key: [Char: 'o'] renametab: - action: [SwitchToMode: Normal,] key: [Char: "\n", Ctrl: 'c', Esc] diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index c50ef479..82ed5da9 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -120,6 +120,12 @@ pub enum InputMode { /// `Scroll` mode allows scrolling up and down within a pane. #[serde(alias = "scroll")] Scroll, + /// `EnterSearch` mode allows for typing in the needle for a search in the scroll buffer of a pane. + #[serde(alias = "entersearch")] + EnterSearch, + /// `Search` mode allows for searching a term in a pane (superset of `Scroll`). + #[serde(alias = "search")] + Search, /// `RenameTab` mode allows assigning a new name to a tab. #[serde(alias = "renametab")] RenameTab, @@ -179,6 +185,8 @@ impl FromStr for InputMode { "pane" => Ok(InputMode::Pane), "tab" => Ok(InputMode::Tab), "scroll" => Ok(InputMode::Scroll), + "search" => Ok(InputMode::Search), + "entersearch" => Ok(InputMode::EnterSearch), "renametab" => Ok(InputMode::RenameTab), "session" => Ok(InputMode::Session), "move" => Ok(InputMode::Move), diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index d7a4ba8a..0e18bb9e 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -291,6 +291,12 @@ pub enum ScreenContext { RemoveOverlay, ConfirmPrompt, DenyPrompt, + UpdateSearch, + SearchDown, + SearchUp, + SearchToggleCaseSensitivity, + SearchToggleWholeWord, + SearchToggleWrap, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s. diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 0550792d..17778c4e 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -27,6 +27,19 @@ pub enum ResizeDirection { Decrease, } +#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] +pub enum SearchDirection { + Down, + Up, +} + +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +pub enum SearchOption { + CaseSensitivity, + WholeWord, + Wrap, +} + // As these actions are bound to the default config, please // do take care when refactoring - or renaming. // They might need to be adjusted in the default config @@ -121,6 +134,12 @@ pub enum Action { Deny, /// Confirm an action that invokes a prompt automatically SkipConfirm(Box), + /// Search for String + SearchInput(Vec), + /// Search for something + Search(SearchDirection), + /// Toggle case sensitivity of search + SearchToggleOption(SearchOption), } impl From for Action { diff --git a/zellij-utils/src/input/keybinds.rs b/zellij-utils/src/input/keybinds.rs index af561696..9f948f89 100644 --- a/zellij-utils/src/input/keybinds.rs +++ b/zellij-utils/src/input/keybinds.rs @@ -213,6 +213,7 @@ impl Keybinds { }, InputMode::RenameTab => mode_keybind_or_action(Action::TabNameInput(raw_bytes)), InputMode::RenamePane => mode_keybind_or_action(Action::PaneNameInput(raw_bytes)), + InputMode::EnterSearch => mode_keybind_or_action(Action::SearchInput(raw_bytes)), _ => mode_keybind_or_action(Action::NoOp), } } diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 84ae1e36..268a4e2c 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -59,6 +59,18 @@ pub fn get_mode_info(mode: InputMode, style: Style, capabilities: PluginCapabili "e".to_string(), "Edit Scrollback in Default Editor".to_string(), ), + ("s".to_string(), "Enter search term".to_string()), + ], + InputMode::EnterSearch => vec![("Enter".to_string(), "when done".to_string())], + InputMode::Search => vec![ + ("↓↑".to_string(), "Scroll".to_string()), + ("PgUp/PgDn".to_string(), "Scroll Page".to_string()), + ("u/d".to_string(), "Scroll Half Page".to_string()), + ("n".to_string(), "Search down".to_string()), + ("p".to_string(), "Search up".to_string()), + ("c".to_string(), "Case sensitivity".to_string()), + ("w".to_string(), "Wrap".to_string()), + ("o".to_string(), "Whole words".to_string()), ], InputMode::RenameTab => vec![("Enter".to_string(), "when done".to_string())], InputMode::RenamePane => vec![("Enter".to_string(), "when done".to_string())],