fix: render floating windows and explorer outside workspace panes

This commit is contained in:
2026-10-11 18:18:10 +02:00
parent f595455479
commit 503342bff0
3 changed files with 209 additions and 77 deletions
+205 -75
View File
@@ -378,7 +378,19 @@ impl BufferViewports {
} else {
Some(document_layout(area, editor))
};
let workspace = chrome.map_or(area, |areas| areas[0]);
let full_workspace = chrome.map_or(area, |areas| areas[0]);
let (workspace, dock) = match editor.file_picker() {
Some(picker) if picker.presentation == FilePresentation::Docked => dock_layout(
full_workspace,
if editor.diff().is_some() {
DockSide::Right
} else {
editor.file_explorer_side()
},
picker.focused,
),
_ => (full_workspace, None),
};
editor.set_pane_bounds(pane_rect(workspace));
self.rects.clear();
editor
@@ -394,6 +406,7 @@ impl BufferViewports {
continue;
}
self.activate(id, editor);
self.panels.workspace_managed = true;
if multiple {
let block =
Block::default()
@@ -444,6 +457,43 @@ impl BufferViewports {
}
}
self.activate(focused, editor);
self.panels.workspace_managed = false;
if let Some(panel) = dock
&& let Some(picker) = editor.file_picker()
{
draw_file_list(
frame,
picker,
panel,
editor.file_navigation_hint(),
&mut self.panels,
editor
.diff()
.and_then(|diff| diff.comparison.as_ref())
.map(|comparison| comparison.files.as_slice()),
);
if let Some(geometry) = &mut self.panels.diff {
geometry.regions[0] = (panel, Focus::Files);
editor.set_diff_pane_bounds(
geometry
.regions
.map(|(area, focus)| (focus, pane_rect(area))),
);
}
}
if let Some(diff) = editor.diff().filter(|diff| diff.phase != Phase::View) {
draw_diff(
frame,
diff,
area,
editor.selection_navigation_hint(),
editor.file_navigation_hint(),
&mut self.panels,
Some(editor),
);
} else {
draw_overlays(frame, editor, area, self.panels.caret, &mut self.panels);
}
}
fn draw_session(&mut self, frame: &mut Frame, session: &mut EditorSession) {
@@ -901,6 +951,9 @@ fn draw_document(
let focused = editor.render_pane_focused();
if let Some(diff) = editor.diff() {
draw_status(frame, editor, syntax, status_area);
if panels.workspace_managed && diff.phase != Phase::View {
return;
}
draw_diff(
frame,
diff,
@@ -918,7 +971,11 @@ fn draw_document(
}
let prompting = matches!(editor.mode(), Mode::Command | Mode::Search);
let (viewport, dock) = match editor.file_picker() {
Some(picker) if focused && picker.presentation == FilePresentation::Docked => {
Some(picker)
if !panels.workspace_managed
&& focused
&& picker.presentation == FilePresentation::Docked =>
{
dock_layout(workspace, editor.file_explorer_side(), picker.focused)
}
_ => (workspace, None),
@@ -1294,24 +1351,11 @@ fn draw_document(
if !focused {
return;
}
if let Some(completion) = editor.completion()
&& let Some(caret) = document_caret
{
panels.completion = draw_completion(frame, completion, viewport, caret);
}
if let Some(picker) = editor.file_picker() {
if picker.presentation == FilePresentation::Floating {
let panel = floating_panel(overlay_area);
frame.render_widget(Clear, panel);
draw_file_list(
frame,
picker,
panel,
editor.file_navigation_hint(),
panels,
None,
);
} else if let Some(panel) = dock {
panels.caret = document_caret.map(|caret| (viewport, caret));
if !panels.workspace_managed {
if let Some(panel) = dock
&& let Some(picker) = editor.file_picker()
{
draw_file_list(
frame,
picker,
@@ -1321,6 +1365,38 @@ fn draw_document(
None,
);
}
draw_overlays(frame, editor, overlay_area, panels.caret, panels);
}
}
fn draw_overlays(
frame: &mut Frame,
editor: &Editor,
area: Rect,
caret: Option<(Rect, Position)>,
panels: &mut WheelPanels,
) {
if let Some(completion) = editor.completion()
&& let Some((_, caret)) = caret
{
panels.completion = draw_completion(frame, completion, area, caret);
}
if let Some(picker) = editor.file_picker()
&& picker.presentation == FilePresentation::Floating
{
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
draw_file_list(
frame,
picker,
panel,
editor.file_navigation_hint(),
panels,
editor
.diff()
.and_then(|diff| diff.comparison.as_ref())
.map(|comparison| comparison.files.as_slice()),
);
}
if let Some(palette) = editor.palette() {
draw_palette(
@@ -1331,10 +1407,10 @@ fn draw_document(
);
}
if let Some(hover) = editor.hover() {
draw_hover(frame, hover, viewport, panels);
draw_hover(frame, hover, area, panels);
}
if let Some(diagnostic) = editor.startup_diagnostic() {
draw_startup_diagnostic(frame, diagnostic, overlay_area);
draw_startup_diagnostic(frame, diagnostic, area);
}
}
@@ -1680,11 +1756,6 @@ fn draw_diff_popups(
if !editor.render_pane_focused() {
return;
}
if let Some((text_area, caret)) = caret
&& let Some(completion) = editor.completion()
{
panels.completion = draw_completion(frame, completion, text_area, caret);
}
if area.width > 0 && area.height > 0 {
let footer = Rect::new(area.x, area.bottom() - 1, area.width, 1);
if matches!(editor.mode(), Mode::Command | Mode::Search) {
@@ -1707,19 +1778,9 @@ fn draw_diff_popups(
frame.render_widget(Paragraph::new(display_text(editor.message())), footer);
}
}
if let Some(palette) = editor.palette() {
draw_palette(
frame,
palette,
editor.mode(),
editor.selection_navigation_hint(),
);
}
if let Some(hover) = editor.hover() {
draw_hover(frame, hover, caret.map_or(area, |(area, _)| area), panels);
}
if let Some(diagnostic) = editor.startup_diagnostic() {
draw_startup_diagnostic(frame, diagnostic, area);
panels.caret = caret;
if !panels.workspace_managed {
draw_overlays(frame, editor, area, caret, panels);
}
}
@@ -1740,9 +1801,9 @@ fn draw_diff(
if area.width == 0 || area.height == 0 {
return;
}
frame.render_widget(Clear, area);
if diff.phase != Phase::View {
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
let stage = if diff.phase == Phase::Before {
"Before"
} else {
@@ -1933,6 +1994,7 @@ fn draw_diff(
}
return;
}
frame.render_widget(Clear, area);
let syntax_error = panels
.diff_syntax
@@ -1998,7 +2060,8 @@ fn draw_diff(
notice_area,
);
}
let (panes, navigator) = if diff.files_visible
let (panes, navigator) = if !panels.workspace_managed
&& diff.files_visible
&& diff
.files
.as_ref()
@@ -2254,22 +2317,6 @@ fn draw_diff(
text_widths,
wrapping,
});
if let Some(picker) = &diff.files
&& diff.files_visible
&& picker.presentation == FilePresentation::Floating
&& editor.is_none_or(|editor| editor.render_pane_focused())
{
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
draw_file_list(
frame,
picker,
panel,
file_navigation_hint,
panels,
comparison.map(|comparison| comparison.files.as_slice()),
);
}
if let Some(editor) = editor {
draw_diff_popups(frame, editor, area, document_caret, panels);
}
@@ -2862,6 +2909,8 @@ fn diff_visible_rows(
#[derive(Default)]
struct WheelPanels {
workspace_managed: bool,
caret: Option<(Rect, Position)>,
completion: Option<Rect>,
hover: Option<(Rect, usize)>,
hover_syntax: SyntaxCache,
@@ -2903,6 +2952,7 @@ impl WheelPanels {
self.manual_files = None;
}
self.hover = None;
self.caret = None;
self.completion = None;
self.files = None;
self.previous_diff = self.diff.take();
@@ -2959,10 +3009,6 @@ impl WheelPanels {
{
return true;
}
let pane = session.editor().pane_id();
if self.diff.is_some() && self.scroll_comparison(session, mouse, pane) {
return true;
}
let editor = session.editor();
if editor.palette().is_some() || editor.startup_diagnostic().is_some() {
return true;
@@ -2994,6 +3040,17 @@ impl WheelPanels {
}
return true;
}
let pane = session.editor().pane_id();
let floating_files = editor.file_picker().is_some_and(|picker| {
picker.presentation == FilePresentation::Floating
&& self
.files
.is_some_and(|(area, _, _)| area.contains(position))
});
if !floating_files && self.diff.is_some() && self.scroll_comparison(session, mouse, pane) {
return true;
}
let editor = session.editor();
if let Some(picker) = editor.file_picker()
&& let Some((panel, height, drawn_top)) = self.files
&& panel.contains(position)
@@ -4823,6 +4880,66 @@ mod tests {
.join("\n")
}
#[test]
fn explorer_dock_reserves_space_outside_all_workspace_panes() {
let directory = Directory::new();
let mut editor =
Editor::open(Some(directory.file("shared.txt", "first\nsecond")), vec![]).unwrap();
editor.set_project_root(directory.0.clone());
editor.handle(Input::Char('|'));
editor.handle(Input::Char('_'));
editor.handle(Input::Char(' '));
editor.handle(Input::Char('e'));
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(120, 30)).unwrap();
let mut views = BufferViewports::default();
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
let dock = views.panels.files.unwrap().0;
assert!(views.rects.iter().all(|(_, pane)| pane.right() <= dock.x));
assert_eq!(dock.right(), 120);
assert_eq!(dock.y, 0);
assert_eq!(dock.height, 28);
}
#[test]
fn revision_selector_overlays_and_clears_workspace_pane_borders() {
let mut editor = Editor::open(None, vec![]).unwrap();
editor.handle(Input::Char('|'));
editor.execute("diff");
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(120, 30)).unwrap();
let mut views = BufferViewports::default();
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert!(screen_text(&terminal).contains("Choose Before"));
let results = views.panels.diff.as_ref().unwrap().regions[0].0;
assert!(results.x < 60 && results.right() > 60);
for x in [59, 60] {
assert_eq!(terminal.backend().buffer()[(x, 20)].symbol(), " ");
}
}
#[test]
fn diff_floating_picker_overlays_the_whole_workspace() {
let directory = Directory::new();
let mut editor =
Editor::open(Some(directory.file("source.txt", "workspace")), vec![]).unwrap();
editor.handle(Input::Char('|'));
install_readonly_diff(&mut editor, syntax_diff_file("history.txt", "old", "new"));
editor.handle(Input::Char(' '));
editor.handle(Input::Char('o'));
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(120, 30)).unwrap();
let mut views = BufferViewports::default();
terminal
.draw(|frame| views.draw(frame, &mut editor))
.unwrap();
assert_eq!(
views.panels.files.unwrap().0,
floating_panel(Rect::new(0, 0, 120, 30))
);
}
#[test]
fn nested_workspace_panes_share_edits_and_undo_but_not_carets() {
let directory = Directory::new();
@@ -6184,14 +6301,10 @@ mod tests {
#[test]
fn hover_scroll_renders_whole_graphemes_in_tiny_workspace() {
let mut editor = Editor::open(None, vec![]).unwrap();
editor.show_hover("x界e\u{301}👩‍🚀z\nlast".into()).unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(6, 1)).unwrap();
let mut syntax = SyntaxCache::new();
let (mut top, mut left) = (0, 0);
for _ in 0..2 {
editor.handle(Input::Right);
}
let rendered = terminal
terminal
.draw(|frame| {
draw(
frame,
@@ -6201,16 +6314,33 @@ mod tests {
&mut syntax,
&mut DocumentViewport::default(),
&mut WheelPanels::default(),
);
})
.unwrap();
let document_cursor = terminal.get_cursor_position().unwrap();
editor.show_hover("x界e\u{301}👩‍🚀z\nlast".into()).unwrap();
for _ in 0..2 {
editor.handle(Input::Right);
}
let mut panels = WheelPanels::default();
let rendered = terminal
.draw(|frame| {
draw(
frame,
&editor,
&mut top,
&mut left,
&mut syntax,
&mut DocumentViewport::default(),
&mut panels,
)
})
.unwrap();
assert_eq!(rendered.buffer[(2, 0)].symbol(), "e\u{301}");
assert_eq!(rendered.buffer[(3, 0)].symbol(), "👩‍🚀");
assert_eq!(rendered.buffer[(5, 0)].symbol(), "z");
assert_eq!(
terminal.get_cursor_position().unwrap(),
ratatui::layout::Position::new(2, 0)
);
let origin = panels.hover.unwrap().0;
assert_eq!(rendered.buffer[(origin.x, origin.y)].symbol(), "e\u{301}");
assert_eq!(rendered.buffer[(origin.x + 1, origin.y)].symbol(), "👩‍🚀");
assert_eq!(rendered.buffer[(origin.x + 3, origin.y)].symbol(), "z");
assert_eq!(terminal.get_cursor_position().unwrap(), document_cursor);
assert_eq!((top, left), (0, 0));
editor.handle(Input::Down);
editor.handle(Input::Home);
@@ -6227,7 +6357,7 @@ mod tests {
)
})
.unwrap();
assert!(screen_text(&terminal).ends_with("last"));
assert!(screen_text(&terminal).trim().ends_with("last"));
for (width, height) in [(1, 1), (2, 2), (3, 3), (4, 4)] {
let mut terminal =
Terminal::new(ratatui::backend::TestBackend::new(width, height)).unwrap();