fix: wrap explorer root paths in narrow panes
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.29"
|
crossterm = "0.29"
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
ratatui = { version = "0.30", default-features = false, features = ["crossterm_0_29", "layout-cache", "underline-color", "unstable-backend-writer"] }
|
ratatui = { version = "0.30", default-features = false, features = ["crossterm_0_29", "layout-cache", "underline-color", "unstable-backend-writer", "unstable-rendered-line-info"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
similar = { version = "2", default-features = false }
|
similar = { version = "2", default-features = false }
|
||||||
|
|||||||
@@ -191,6 +191,8 @@ The floating explorer is centered, capped at 100 columns by 24 rows, and shrinks
|
|||||||
|
|
||||||
The dock defaults to the right of the editor workspace. See [configuration preferences](configuration.md#preferences) to choose another side. Status and command/search prompts remain full-width; editor text, gutter, highlights, and cursors use the remaining rectangle. Focused panes prioritize their query on tiny terminals; unfocused docks shrink or hide to preserve editing space.
|
The dock defaults to the right of the editor workspace. See [configuration preferences](configuration.md#preferences) to choose another side. Status and command/search prompts remain full-width; editor text, gutter, highlights, and cursors use the remaining rectangle. Focused panes prioritize their query on tiny terminals; unfocused docks shrink or hide to preserve editing space.
|
||||||
|
|
||||||
|
The explorer root path wraps within the pane width in floating, docked, and diff file views; file rows and keybinding help do not wrap. The path header grows to fit where space permits, reserving at least one file row. Very short panes clip the remaining path rather than hiding file navigation.
|
||||||
|
|
||||||
Both presentations default to Tree; [configuration preferences](configuration.md#preferences) can select List independently for each. Ctrl+t toggles the current focused instance without rescanning. List retains searchable full relative paths. Tree shows folders first, two-cell indentation, and basename-only files. Folder names end in `/` and use cyan text; files use the normal foreground color. Fold-state arrows are omitted. Maximal single-child directory chains share one row, such as `src/client/http/`; a directory containing one file still gets its own row.
|
Both presentations default to Tree; [configuration preferences](configuration.md#preferences) can select List independently for each. Ctrl+t toggles the current focused instance without rescanning. List retains searchable full relative paths. Tree shows folders first, two-cell indentation, and basename-only files. Folder names end in `/` and use cyan text; files use the normal foreground color. Fold-state arrows are omitted. Maximal single-child directory chains share one row, such as `src/client/http/`; a directory containing one file still gets its own row.
|
||||||
|
|
||||||
Floating Tree opens in explorer **INSERT** mode; a new docked Tree opens in **NORMAL**. Press `i` in NORMAL to resume editing the existing filter; the `i` itself is not added to the query. Typing and Backspace filter full relative paths by case-insensitive whitespace tokens. These are explorer-local modes, not changes to the file buffer's mode. Matching nodes appear with ancestor context, and query changes initially select a direct match. Escape in INSERT changes only to NORMAL: the filter, visible results, highlighted item, and explorer focus remain. NORMAL displays the active filter and navigates that filtered tree. Escape from NORMAL returns focus to the buffer (or closes floating search). List always accepts query typing immediately.
|
Floating Tree opens in explorer **INSERT** mode; a new docked Tree opens in **NORMAL**. Press `i` in NORMAL to resume editing the existing filter; the `i` itself is not added to the query. Typing and Backspace filter full relative paths by case-insensitive whitespace tokens. These are explorer-local modes, not changes to the file buffer's mode. Matching nodes appear with ancestor context, and query changes initially select a direct match. Escape in INSERT changes only to NORMAL: the filter, visible results, highlighted item, and explorer focus remain. NORMAL displays the active filter and navigates that filtered tree. Escape from NORMAL returns focus to the buffer (or closes floating search). List always accepts query typing immediately.
|
||||||
|
|||||||
+45
-8
@@ -26,7 +26,7 @@ use ratatui::{
|
|||||||
layout::{Constraint, Layout, Position, Rect},
|
layout::{Constraint, Layout, Position, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
text::{Line, Span},
|
text::{Line, Span},
|
||||||
widgets::{Block, Borders, Clear, Paragraph},
|
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||||
};
|
};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
@@ -2263,9 +2263,22 @@ fn draw_file_list(
|
|||||||
let show_root = content.height >= 4 && !picker.root.as_os_str().is_empty();
|
let show_root = content.height >= 4 && !picker.root.as_os_str().is_empty();
|
||||||
let show_error = picker.error.is_some() && content.height >= 2;
|
let show_error = picker.error.is_some() && content.height >= 2;
|
||||||
let show_help = content.height >= 5 + u16::from(show_error);
|
let show_help = content.height >= 5 + u16::from(show_error);
|
||||||
|
let root = show_root.then(|| {
|
||||||
|
Paragraph::new(display_text(&picker.root.to_string_lossy()))
|
||||||
|
.style(Style::default().fg(Color::DarkGray))
|
||||||
|
.wrap(Wrap { trim: false })
|
||||||
|
});
|
||||||
|
// Reserve the query, optional notices/footer, and at least one file row.
|
||||||
|
let root_rows = root.as_ref().map_or(0, |root| {
|
||||||
|
root.line_count(content.width).min(usize::from(
|
||||||
|
content
|
||||||
|
.height
|
||||||
|
.saturating_sub(2 + u16::from(show_error) + u16::from(show_help)),
|
||||||
|
)) as u16
|
||||||
|
});
|
||||||
let [query_area, root_area, error_area, results_area, help_area] = Layout::vertical([
|
let [query_area, root_area, error_area, results_area, help_area] = Layout::vertical([
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
Constraint::Length(u16::from(show_root)),
|
Constraint::Length(root_rows),
|
||||||
Constraint::Length(u16::from(show_error)),
|
Constraint::Length(u16::from(show_error)),
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
Constraint::Length(u16::from(show_help)),
|
Constraint::Length(u16::from(show_help)),
|
||||||
@@ -2304,12 +2317,8 @@ fn draw_file_list(
|
|||||||
let cursor_x = if browsing { 0 } else { query_width - offset };
|
let cursor_x = if browsing { 0 } else { query_width - offset };
|
||||||
frame.set_cursor_position((query_area.x + cursor_x as u16, query_area.y));
|
frame.set_cursor_position((query_area.x + cursor_x as u16, query_area.y));
|
||||||
}
|
}
|
||||||
if show_root {
|
if let Some(root) = root {
|
||||||
frame.render_widget(
|
frame.render_widget(root, root_area);
|
||||||
Paragraph::new(display_text(&picker.root.to_string_lossy()))
|
|
||||||
.style(Style::default().fg(Color::DarkGray)),
|
|
||||||
root_area,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if show_error {
|
if show_error {
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
@@ -5824,6 +5833,34 @@ end
|
|||||||
assert_eq!(buffer[(1, selected_y)].bg, Color::Blue);
|
assert_eq!(buffer[(1, selected_y)].bg, Color::Blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn explorer_wraps_only_root_path_and_reserves_file_rows_in_short_panes() {
|
||||||
|
let root = "/a/very/long/path/to/project/root";
|
||||||
|
let picker = FilePicker::from_paths(root.into(), [(0, "entry.txt".into())]);
|
||||||
|
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(20, 12)).unwrap();
|
||||||
|
let mut panels = WheelPanels::default();
|
||||||
|
terminal
|
||||||
|
.draw(|frame| draw_file_list(frame, &picker, frame.area(), "", &mut panels, None))
|
||||||
|
.unwrap();
|
||||||
|
let buffer = terminal.backend().buffer();
|
||||||
|
let path = (2..4)
|
||||||
|
.map(|y| (1..19).map(|x| buffer[(x, y)].symbol()).collect::<String>())
|
||||||
|
.collect::<String>();
|
||||||
|
assert_eq!(path.trim_end(), root);
|
||||||
|
assert!(
|
||||||
|
screen_text(&terminal)
|
||||||
|
.lines()
|
||||||
|
.nth(4)
|
||||||
|
.unwrap()
|
||||||
|
.contains("entry.txt")
|
||||||
|
);
|
||||||
|
terminal.backend_mut().resize(20, 6);
|
||||||
|
terminal
|
||||||
|
.draw(|frame| draw_file_list(frame, &picker, frame.area(), "", &mut panels, None))
|
||||||
|
.unwrap();
|
||||||
|
assert!(screen_text(&terminal).contains("entry.txt"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_footers_show_remapped_and_disabled_selection_keys() {
|
fn list_footers_show_remapped_and_disabled_selection_keys() {
|
||||||
let mut editor = Editor::open(None, vec![]).unwrap();
|
let mut editor = Editor::open(None, vec![]).unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user