fix: surface rejected configuration in a floating startup diagnostic

This commit is contained in:
2026-10-09 19:49:50 +02:00
parent de6b4d47f3
commit 788638f34e
6 changed files with 301 additions and 129 deletions
+111 -81
View File
@@ -29,7 +29,7 @@ use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;
use crate::{
editor::{Editor, Hover, Input, Mode, Palette},
editor::{Editor, Hover, Input, Mode, Palette, StartupDiagnostic},
files::{DockSide, FilePicker, FilePresentation, FileView},
plugin::{Diagnostic, DiagnosticSeverity},
session::EditorSession,
@@ -605,18 +605,7 @@ fn draw(
}
if let Some(picker) = editor.file_picker() {
if picker.presentation == FilePresentation::Floating {
let [_, column, _] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(area.width.min(100)),
Constraint::Fill(1),
])
.areas(area);
let [_, panel, _] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(area.height.min(24)),
Constraint::Fill(1),
])
.areas(column);
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
draw_file_list(frame, picker, panel, editor.file_navigation_hint());
} else if let Some(panel) = dock {
@@ -634,6 +623,71 @@ fn draw(
if let Some(hover) = editor.hover() {
draw_hover(frame, hover, viewport);
}
if let Some(diagnostic) = editor.startup_diagnostic() {
draw_startup_diagnostic(frame, diagnostic, area);
}
}
fn floating_panel(area: Rect) -> Rect {
let [_, column, _] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(area.width.min(100)),
Constraint::Fill(1),
])
.areas(area);
let [_, panel, _] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(area.height.min(24)),
Constraint::Fill(1),
])
.areas(column);
panel
}
fn draw_startup_diagnostic(frame: &mut Frame, diagnostic: &StartupDiagnostic, area: Rect) {
let panel = floating_panel(area);
frame.render_widget(Clear, panel);
let bordered = panel.width >= 4 && panel.height >= 4;
let block = Block::default()
.borders(if bordered {
Borders::ALL
} else {
Borders::NONE
})
.border_style(Style::default().fg(Color::Red))
.title(" Configuration error ");
let content = if bordered { block.inner(panel) } else { panel };
if bordered {
frame.render_widget(block, panel);
}
let [text_area, help_area] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(u16::from(content.height >= 3)),
])
.areas(content);
let lines: Vec<Line> = diagnostic
.lines
.iter()
.skip(diagnostic.top)
.take(usize::from(text_area.height))
.map(|line| {
let line = display_text(line);
Line::from(visible_line(
&line,
display_column(&line, diagnostic.left),
usize::from(text_area.width),
))
})
.collect();
frame.render_widget(Paragraph::new(lines), text_area);
frame.render_widget(
Paragraph::new("Arrows scroll · Home reset · Enter/Esc continue")
.style(Style::default().fg(Color::DarkGray)),
help_area,
);
if text_area.width > 0 && text_area.height > 0 {
frame.set_cursor_position((text_area.x, text_area.y));
}
}
fn draw_hover(frame: &mut Frame, hover: &Hover, area: Rect) {
@@ -1746,74 +1800,6 @@ end
assert_eq!(viewports.syntax.len(), 1);
}
#[test]
fn highlighted_editor_text_stays_out_of_docked_and_floating_picker_surfaces() {
let directory = Directory::new();
let path = directory.file("panels.rs", &vec!["// note"; 30].join("\n"));
let area = Rect::new(3, 2, 100, 24);
for (name, side) in [
("left", Some(DockSide::Left)),
("right", Some(DockSide::Right)),
("top", Some(DockSide::Top)),
("bottom", Some(DockSide::Bottom)),
("floating", None),
] {
let mut editor = Editor::open(Some(path.clone()), vec![]).unwrap();
if side.is_some() {
editor.set_file_explorer_side(name).unwrap();
}
editor.handle(Input::Char(' '));
editor.handle(Input::Char(if side.is_some() { 'e' } else { 'o' }));
let mut terminal = Terminal::with_options(
ratatui::backend::TestBackend::new(110, 30),
ratatui::TerminalOptions {
viewport: ratatui::Viewport::Fixed(area),
},
)
.unwrap();
let mut viewports = BufferViewports::default();
terminal
.draw(|frame| viewports.draw(frame, &editor))
.unwrap();
let workspace = Rect::new(area.x, area.y, area.width, area.height - 2);
let (text, panel) = if let Some(side) = side {
let (text, panel) = dock_layout(workspace, side, true);
(text, panel.unwrap())
} else {
(workspace, Rect::new(area.x + 8, area.y + 3, 84, 18))
};
let mut expected = Terminal::with_options(
ratatui::backend::TestBackend::new(110, 30),
ratatui::TerminalOptions {
viewport: ratatui::Viewport::Fixed(panel),
},
)
.unwrap();
expected
.draw(|frame| {
draw_file_list(
frame,
editor.file_picker().unwrap(),
panel,
editor.file_navigation_hint(),
)
})
.unwrap();
let buffer = terminal.backend().buffer();
let expected = expected.backend().buffer();
for y in panel.y..panel.bottom() {
for x in panel.x..panel.right() {
assert_eq!(buffer[(x, y)], expected[(x, y)], "{name}: {x},{y}");
}
}
assert_eq!(buffer[(text.x + 3, text.y)].symbol(), "/");
assert_eq!(buffer[(text.x + 3, text.y)].fg, Color::DarkGray);
assert_eq!(buffer[(text.x, text.y)].fg, Color::Cyan);
assert_eq!(buffer[(area.x, area.bottom() - 2)].bg, Color::Cyan);
assert_eq!(buffer[(0, 0)].fg, Color::Reset);
}
}
#[test]
fn translate_distinguishes_control_j_from_enter_and_normalizes_control_letters() {
for (code, modifiers, expected) in [
@@ -2784,6 +2770,50 @@ end
assert_eq!(buffer[(0, 0)].symbol(), " ");
}
#[test]
fn startup_diagnostic_scrolls_long_unicode_reasons_and_dismisses_without_editing() {
let directory = Directory::new();
let source = directory.file("buffer.txt", "keep");
let config = directory.file("config.rb", "raise \"#{'x' * 160}界END\\nlast reason\"");
let mut session =
EditorSession::open(Some(source), vec!["text".into()], Some(&config)).unwrap();
let mut terminal = Terminal::new(ratatui::backend::TestBackend::new(110, 26)).unwrap();
let mut viewports = BufferViewports::default();
terminal
.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
let text = screen_text(&terminal);
assert!(text.contains("Configuration error"));
assert!(text.contains("config.rb"));
assert!(text.contains("default settings and bindings"));
assert!(text.contains("last reason"));
assert!(!text.contains("界END"));
for _ in 0..100 {
session.handle(Input::Right);
}
terminal
.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
let scrolled = screen_text(&terminal);
assert!(
scrolled.contains("界") && scrolled.contains("END"),
"{scrolled}"
);
for (width, height) in [(4, 4), (1, 1)] {
let mut tiny =
Terminal::new(ratatui::backend::TestBackend::new(width, height)).unwrap();
tiny.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
}
session.handle(Input::Enter);
terminal
.draw(|frame| viewports.draw(frame, session.editor()))
.unwrap();
assert!(!screen_text(&terminal).contains("Configuration error"));
assert_eq!(session.editor().lines(), &["keep"]);
assert_eq!(session.editor().cursor(), crate::editor::Cursor::default());
}
#[test]
fn picker_conversion_focus_palette_and_closure_preserve_buffer_viewport_cache() {
let directory = Directory::new();