Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
* feat: allow overriding layouts at runtime (https://github.com/zellij-org/zellij/pull/4566)
* build: Update Rust toolchain to 1.92.0 (https://github.com/zellij-org/zellij/pull/4579)
* docs: Explain Rust toolchain update strategy in CONTRIBUTING (https://github.com/zellij-org/zellij/pull/4585)
* feat: new `layout-manager` interface and plugin API commands (https://github.com/zellij-org/zellij/pull/4601)
* feat: new `layout-manager` interface and plugin API commands (https://github.com/zellij-org/zellij/pull/4601 and https://github.com/zellij-org/zellij/pull/4831)
* fix: keep serializing sessions resurrected from the welcome screen (https://github.com/zellij-org/zellij/pull/4604)
* fix: sanitize session names when deleting them from the CLI (https://github.com/zellij-org/zellij/pull/4583)
* fix: properly close the welcome screen session when switching sessions away from it (https://github.com/zellij-org/zellij/pull/4605)
Expand Down
101 changes: 78 additions & 23 deletions default-plugins/layout-manager/src/screens/layout_list/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ impl Default for LayoutListScreen {
retain_plugin_panes: false,
apply_only_to_active_tab: false,
show_more_override_options: false,
search_state: SearchState::new(),
search_state: SearchState::new_in_search_mode(),
last_rows: 0,
last_cols: 0,
}
Expand All @@ -42,7 +42,7 @@ impl LayoutListScreen {
retain_plugin_panes: false,
apply_only_to_active_tab: false,
show_more_override_options: false,
search_state: SearchState::new(),
search_state: SearchState::new_in_search_mode(),
last_rows: 0,
last_cols: 0,
}
Expand Down Expand Up @@ -101,7 +101,7 @@ impl LayoutListScreen {
self.open_selected_layout(display_layouts);
KeyResponse::none()
},
BareKey::Tab if key.has_no_modifiers() => {
BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
self.apply_selected_layout(display_layouts);
KeyResponse::none()
},
Expand All @@ -125,8 +125,8 @@ impl LayoutListScreen {
KeyResponse::render()
},
BareKey::Esc if key.has_no_modifiers() => {
self.clear_filter();
KeyResponse::render()
close_self();
KeyResponse::none()
},
_ => KeyResponse::none(),
}
Expand All @@ -138,27 +138,50 @@ impl LayoutListScreen {
key: KeyWithModifier,
display_layouts: &[DisplayLayout],
) -> KeyResponse {
// Special handling for Enter and Esc
// Search-first mode: Enter opens, Ctrl+O applies, Tab autocompletes,
// arrows navigate, Esc exits to management mode
match key.bare_key {
BareKey::Esc if key.has_no_modifiers() => {
self.clear_filter();
// Clear text first; only exit to management mode if already empty
if self.search_state.get_filter_input().is_empty() {
self.clear_filter();
} else {
self.search_state.get_filter_input_mut().clear();
self.update_filter(display_layouts);
}
return KeyResponse::render();
},
BareKey::Enter if key.has_no_modifiers() => {
if self.search_state.get_filter_input().is_empty()
|| self.search_state.get_search_results().is_empty()
{
self.clear_filter();
} else {
self.search_state.stop_typing();
show_cursor(None);
// Open the currently selected layout as new tab(s)
self.open_selected_layout(display_layouts);
return KeyResponse::none();
},
BareKey::Char('o') if key.has_modifiers(&[KeyModifier::Ctrl]) => {
// Apply/override the currently selected layout to the session
self.apply_selected_layout(display_layouts);
return KeyResponse::none();
},
BareKey::Tab if key.has_no_modifiers() => {
// Complete: fill input with selected match name
if self.search_state.fill_input_with_selected_match() {
self.update_filter(display_layouts);
}
return KeyResponse::render();
},
BareKey::Up if key.has_no_modifiers() => {
// Navigate filtered results while typing
self.navigate_up(display_layouts);
return KeyResponse::render();
},
BareKey::Down if key.has_no_modifiers() => {
// Navigate filtered results while typing
self.navigate_down(display_layouts);
return KeyResponse::render();
},
_ => {},
}

// Pass all keys to TextInput
// Pass remaining keys to TextInput
let action = self.search_state.get_filter_input_mut().handle_key(key);

match action {
Expand All @@ -167,8 +190,13 @@ impl LayoutListScreen {
KeyResponse::render()
},
InputAction::Cancel => {
// Ctrl-C or Esc - clear filter
self.clear_filter();
// Ctrl-C - clear text first; only exit to management mode if already empty
if self.search_state.get_filter_input().is_empty() {
self.clear_filter();
} else {
self.search_state.get_filter_input_mut().clear();
self.update_filter(display_layouts);
}
KeyResponse::render()
},
InputAction::Submit => {
Expand Down Expand Up @@ -304,6 +332,7 @@ impl LayoutListScreen {
self.apply_only_to_active_tab,
Default::default(),
);
close_self();
}
}

Expand Down Expand Up @@ -364,10 +393,16 @@ impl LayoutListScreen {
}

fn open_selected_layout(&self, display_layouts: &[DisplayLayout]) {
if let Some(DisplayLayout::Valid(chosen_layout)) =
display_layouts.get(self.selected_layout_index)
{
new_tabs_with_layout_info(chosen_layout);
let selected = display_layouts.get(self.selected_layout_index);
if let Some(DisplayLayout::Valid(chosen_layout)) = selected {
let tab_ids = new_tabs_with_layout_info(chosen_layout);
if self.should_default_to_current_tab(display_layouts) {
if let Some(&tab_id) = tab_ids.first() {
let layout_name = selected.unwrap().name();
rename_tab_with_id(tab_id as u64, layout_name);
}
}
close_self();
}
}

Expand All @@ -385,6 +420,13 @@ impl LayoutListScreen {
let (base_x, base_y) =
self.calculate_base_coordinates(rows, cols, total_width, total_height);

// In search mode, shift everything down by 1 row
let base_y = if self.search_state.is_typing() || self.search_state.is_active() {
base_y + 1
} else {
base_y
};

let layouts_to_render = self.effective_layouts(display_layouts);
let (content_height, controls_y) = self.calculate_layout(rows, &display_layouts);

Expand Down Expand Up @@ -461,13 +503,17 @@ impl LayoutListScreen {
let rows_in_table = display_layouts.len() + 1; // 1 for the title row
let controls_height = self.get_controls_height();
let filter_row_height = if self.is_searching() { 1 } else { 0 };
let search_mode_offset = if self.is_searching() { 1 } else { 0 };
let padding = 1;
let mut content_height = std::cmp::max(rows_in_table, 5);
if content_height + controls_height + padding + filter_row_height >= rows {
if content_height + controls_height + padding + filter_row_height + search_mode_offset
>= rows
{
content_height = rows
.saturating_sub(controls_height)
.saturating_sub(padding)
.saturating_sub(filter_row_height)
.saturating_sub(search_mode_offset)
}
let controls_y = content_height + padding + filter_row_height;

Expand Down Expand Up @@ -520,10 +566,12 @@ impl LayoutListScreen {
display_layouts: &[DisplayLayout],
) -> (usize, usize) {
let filter_row_height = if self.is_searching() { 1 } else { 0 };
let search_mode_offset = if self.is_searching() { 1 } else { 0 };
let (content_height, _) = self.calculate_layout(rows, display_layouts);
let padding = 1;
let controls_height = self.get_controls_height();
let total_height = filter_row_height + content_height + padding + controls_height;
let total_height =
filter_row_height + search_mode_offset + content_height + padding + controls_height;

let controls = Controls::new(
self.retain_terminal_panes,
Expand Down Expand Up @@ -612,6 +660,13 @@ impl LayoutListScreen {
total_height,
);

// In search mode, cursor must account for the extra row offset
let base_y = if self.search_state.is_typing() || self.search_state.is_active() {
base_y + 1
} else {
base_y
};

self.search_state
.update_filter(display_layouts, base_x, base_y);

Expand Down
30 changes: 20 additions & 10 deletions default-plugins/layout-manager/src/screens/layout_list/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,22 @@ impl Default for SearchState {
}

impl SearchState {
pub fn new() -> Self {
Self::default()
pub fn new_in_search_mode() -> Self {
Self {
typing_filter: true,
..Self::default()
}
}

pub fn fill_input_with_selected_match(&mut self) -> bool {
if let Some(result) = self.search_results.get(self.selected_search_index) {
let name = result.layout.name();
if self.filter_input.get_text() != name {
self.filter_input.set_text(name);
return true;
}
}
false
}

pub fn is_active(&self) -> bool {
Expand All @@ -50,10 +64,6 @@ impl SearchState {
self.typing_filter = true;
}

pub fn stop_typing(&mut self) {
self.typing_filter = false;
}

pub fn get_filter_input(&self) -> &TextInput {
&self.filter_input
}
Expand All @@ -80,7 +90,7 @@ impl SearchState {
base_x: usize,
base_y: usize,
) {
let filter_prompt = "Filter:";
let filter_prompt = "Layout:";
let filter_text = self.filter_input.get_text();

// Clear results if filter is empty
Expand Down Expand Up @@ -155,15 +165,15 @@ impl SearchState {
let filter_text_str = self.filter_input.get_text();
let filter_text = if self.typing_filter {
let mut filter_line =
Text::new(format!("Filter: {}", filter_text_str)).color_substring(2, "Filter:");
Text::new(format!("Layout: {}", filter_text_str)).color_substring(2, "Layout:");
if !filter_text_str.is_empty() {
filter_line = filter_line.color_last_substring(3, filter_text_str)
}
filter_line
} else {
Text::new(format!("Filter: {} (<Esc> - clear)", filter_text_str))
Text::new(format!("Layout: {} (<Esc> - clear)", filter_text_str))
.color_substring(3, "<Esc>")
.color_substring(2, "Filter:")
.color_substring(2, "Layout:")
};
print_text_with_coordinates(filter_text, base_x, base_y, None, None);
}
Expand Down
42 changes: 26 additions & 16 deletions default-plugins/layout-manager/src/ui/layout_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,45 +184,55 @@ impl Controls {
}

fn get_typing_filter_controls(&self, max_cols: usize) -> (&str, &[&str]) {
let long_text = "- <Enter> - accept filter";
let short_text = "<Enter> - accept filter";
let minimum_text = "<Enter> ...";
let long_text =
"- <↑↓> Navigate, <Tab> Complete, <Enter> Open, <Ctrl+o> Apply, <Esc> Manage & New";
let short_text = "<↑↓>/<Tab>/<Enter>/<Ctrl+o>/<Esc> Nav/Complete/Open/Apply/Manage";
let minimum_text = "<↑↓>/<Tab>/<Enter>/<Ctrl+o> ...";
let text = if max_cols >= long_text.chars().count() {
long_text
} else if max_cols >= short_text.chars().count() {
short_text
} else {
minimum_text
};
(text, &["<Enter>"])
(text, &["<↑↓>", "<Tab>", "<Enter>", "<Ctrl+o>", "<Esc>"])
}

fn get_filter_active_controls(&self, max_cols: usize) -> (&str, &[&str]) {
let long_text = "- <Enter> Open, <↓↑> Nav, <e> Edit, <r> Rename, <Del> - Delete";
let short_text = "<Enter>/<↓↑>/<e>/<r>/<Del> Open/Nav/Edit/Rename/Del";
let minimum_text = "<Enter>/<↓↑>/<e>/<r>/<Del> ...";
let long_text =
"- <Enter> Open, <↓↑> Nav, <Ctrl+o> Apply, <e> Edit, <r> Rename, <Del> Delete";
let short_text = "<Enter>/<↓↑>/<Ctrl+o>/<e>/<r>/<Del> Open/Nav/Apply/Edit/Rename/Del";
let minimum_text = "<Enter>/<↓↑>/<Ctrl+o>/<e>/<r>/<Del> ...";
let text = if max_cols >= long_text.chars().count() {
long_text
} else if max_cols >= short_text.chars().count() {
short_text
} else {
minimum_text
};
(text, &["<Enter>", "<↓↑>", "<e>", "<r>", "<Del>"])
(
text,
&["<Enter>", "<↓↑>", "<Ctrl+o>", "<e>", "<r>", "<Del>"],
)
}

fn get_default_controls(&self, max_cols: usize) -> (&str, &[&str]) {
let long_text = "- <Enter> Open, <↓↑> Nav, </> Filter, <e> Edit, <r> Rename, <Del> - Del";
let short_text = "<Enter>/<↓↑>/</>/<e>/<r>/<Del> Open/Nav/Filter/Edit/Rename/Del";
let minimum_text = "<Enter>/<↓↑>/</>/<e>/<r>/<Del> ...";
let long_text =
"- <Enter> Open, <↓↑> Nav, </> Search, <Ctrl+o> Apply, <e> Edit, <r> Rename, <Del> Del";
let short_text =
"<Enter>/<↓↑>/</>/<Ctrl+o>/<e>/<r>/<Del> Open/Nav/Search/Apply/Edit/Rename/Del";
let minimum_text = "<Enter>/<↓↑>/</>/<Ctrl+o>/<e>/<r>/<Del> ...";
let text = if max_cols >= long_text.chars().count() {
long_text
} else if max_cols >= short_text.chars().count() {
short_text
} else {
minimum_text
};
(text, &["<Enter>", "<↓↑>", "</>", "<e>", "<r>", "<Del>"])
(
text,
&["<Enter>", "<↓↑>", "</>", "<Ctrl+o>", "<e>", "<r>", "<Del>"],
)
}

fn get_basic_controls_text_and_keys(&self, max_cols: usize) -> (&str, &[&str]) {
Expand All @@ -240,19 +250,19 @@ impl Controls {
"more"
};
let long_text = format!(
"- <Tab> Override Session Layout, <?> {} options",
"- <Ctrl+o> Override Session Layout, <?> {} options",
toggle_word
);
let short_text = format!("<Tab> Override, <?> {} options", toggle_word);
let minimum_text = format!("<Tab>/<?> ...");
let short_text = format!("<Ctrl+o> Override, <?> {} options", toggle_word);
let minimum_text = format!("<Ctrl+o>/<?> ...");
let text = if max_cols >= long_text.chars().count() {
long_text
} else if max_cols >= short_text.chars().count() {
short_text
} else {
minimum_text
};
(text, &["<Tab>", "<?>"])
(text, &["<Ctrl+o>", "<?>"])
}

fn get_new_layout_text_and_keys(&self, max_cols: usize) -> (&str, &[&str]) {
Expand Down
1 change: 1 addition & 0 deletions default-plugins/session-manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ impl ZellijPlugin for State {
EventType::RunCommandResult,
EventType::Timer,
]);
rename_plugin_pane(get_plugin_ids().plugin_id, "Session Manager");
}

fn pipe(&mut self, pipe_message: PipeMessage) -> bool {
Expand Down
Loading