diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-01-13 11:27:07 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-13 11:27:07 +0100 |
| commit | 4a27ceea0d99b036ca607e05714cef15689d7c26 (patch) | |
| tree | 22c0148718a82082bdb196c5e867697f514cc541 /default-plugins/layout-manager/src/errors.rs | |
| parent | docs(CONTRIBUTING): Describe rust toolchain versioning policy (#4585) (diff) | |
| download | sl0ppij-4a27ceea0d99b036ca607e05714cef15689d7c26.tar.gz sl0ppij-4a27ceea0d99b036ca607e05714cef15689d7c26.tar.xz sl0ppij-4a27ceea0d99b036ca607e05714cef15689d7c26.zip | |
Layout manager (#4601)
* ad plugin override_layout command
* watch available layouts and report to plugin thread
* Event::AvailableLayoutInfo
* save layout command
* refactor save_layout
* fix api and permissions
* delete_layout API command
* edit layout in place API
* edit layout response through api
* propagate layout parsing errors
* generate random name plugin api
* dump stringified layout API
* synchronicity dump_session_layout and include LayoutMetadata in sync response
* parse_layout plugin api
* fix: default to default layout dir in plugin api
* rename_layout plugin command API
* initial implementation of multi-tab override
* apply to multiple tabs
* allow applying to only the active tab
* add tab/pane names to layout parsing metadata
* indicate is_builtin_plugin in PaneMetadata
* get_layout_dir plugin API command
* consolidate (and fix) is_builtin_plugin logic
* fix bad ret
* allow dumping the layout for just a specific tab in the session
* get focused pane and tab sync api
* color last substring variants
* add plugin
* update assets
* fix tests
* add e2e tests for new plugin apis
* update ui with new shortcut
* rustfmt
* docs(changelog): add PR
Diffstat (limited to 'default-plugins/layout-manager/src/errors.rs')
| -rw-r--r-- | default-plugins/layout-manager/src/errors.rs | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/default-plugins/layout-manager/src/errors.rs b/default-plugins/layout-manager/src/errors.rs new file mode 100644 index 00000000..a86af8bf --- /dev/null +++ b/default-plugins/layout-manager/src/errors.rs @@ -0,0 +1,155 @@ +use crate::screens::{KeyResponse, Screen}; +use crate::ui::{truncate_line_with_ansi, wrap_text_to_width, ErrorMessage, MultiLineErrorMessage}; +use zellij_tile::prelude::*; + +/// Format a layout parsing error into a detailed error string +pub fn format_kdl_error(error: LayoutParsingError) -> String { + match error { + LayoutParsingError::KdlError { + mut kdl_error, + file_name, + source_code, + } => { + use miette::{GraphicalReportHandler, NamedSource, Report}; + + kdl_error.help_message = + Some("https://zellij.dev/documentation/creating-a-layout.html".to_owned()); + let report: Report = kdl_error.into(); + let report = report.with_source_code(NamedSource::new(file_name, source_code)); + + let handler = GraphicalReportHandler::new(); + let mut output = String::new(); + handler.render_report(&mut output, report.as_ref()).unwrap(); + output + }, + LayoutParsingError::SyntaxError => { + format!("Failed to deserialize KDL node. \nPossible reasons:\n{}\n{}\n{}\n{}", + "- Missing `;` after a node name, eg. { node; another_node; }", + "- Missing quotations (\") around an argument node eg. { first_node \"argument_node\"; }", + "- Missing an equal sign (=) between node arguments on a title line. eg. argument=\"value\"", + "- Found an extraneous equal sign (=) between node child arguments and their values. eg. { argument=\"value\" }") + }, + } +} + +#[derive(Clone)] +pub struct ErrorScreen { + pub message: String, + pub return_to_screen: Box<Screen>, +} + +impl ErrorScreen { + pub fn handle_key(&mut self, _key: KeyWithModifier) -> KeyResponse { + KeyResponse::new_screen((*self.return_to_screen).clone()) + } + + pub fn render(&self, rows: usize, cols: usize) { + if self.message.chars().count() > cols.saturating_sub(4) { + let max_width = cols.saturating_sub(4); + let max_rows = rows.saturating_sub(4); + let lines = wrap_text_to_width(&self.message, max_width); + let base_x = 2; + let base_y = rows.saturating_sub(4 + lines.len()) / 2; + MultiLineErrorMessage::new(lines).render(base_x, base_y, max_rows); + } else { + let desired_width = self.message.chars().count(); + let base_y = rows.saturating_sub(5) / 2; + let base_x = cols.saturating_sub(desired_width) / 2; + ErrorMessage::new(&self.message).render(base_x, base_y); + } + } +} + +#[derive(Clone)] +pub struct ErrorDetailScreen { + pub layout_name: String, + pub detailed_error: String, + pub return_to_screen: Box<Screen>, +} + +impl ErrorDetailScreen { + pub fn new(layout_name: String, detailed_error: String, return_to_screen: Box<Screen>) -> Self { + Self { + layout_name, + detailed_error, + return_to_screen, + } + } + + pub fn handle_key(&mut self, _key: KeyWithModifier) -> KeyResponse { + KeyResponse::new_screen((*self.return_to_screen).clone()) + } + + pub fn render(&self, rows: usize, cols: usize) { + // Header: show layout name + let header = format!("Error in layout: {}", self.layout_name); + let header_text = Text::new(&header).error_color_all(); + print_text_with_coordinates(header_text, 1, 0, None, None); + + // Calculate available space for error content + let header_height = 2; // Header + gap + let available_rows = rows.saturating_sub(header_height); + let available_cols = cols.saturating_sub(2); // Padding on sides + + // Render error lines with middle truncation + let error_lines: Vec<&str> = self.detailed_error.lines().collect(); + let total_lines = error_lines.len(); + + if total_lines <= available_rows { + // All lines fit, show them all + for (i, line) in error_lines.iter().enumerate() { + let truncated = truncate_line_with_ansi(line, available_cols); + print!("\u{1b}[{};{}H{}", header_height + i + 1, 2, truncated); + } + } else { + // Need to truncate middle - show beginning, indicator, and end + let omitted_indicator_lines = 1; // Reserve 1 line for "... X lines omitted ..." + let lines_for_content = available_rows.saturating_sub(omitted_indicator_lines); + + // Split content space: 60% for beginning, 40% for end + let beginning_lines = (lines_for_content as f32 * 0.6).ceil() as usize; + let end_lines = lines_for_content.saturating_sub(beginning_lines); + + let omitted_count = total_lines.saturating_sub(beginning_lines + end_lines); + + let mut current_row = 0; + + // Render beginning lines + for line in error_lines.iter().take(beginning_lines) { + let truncated = truncate_line_with_ansi(line, available_cols); + print!( + "\u{1b}[{};{}H{}", + header_height + current_row + 1, + 2, + truncated + ); + current_row += 1; + } + + // Render omission indicator + let indicator = format!("... {} lines omitted ...", omitted_count); + let indicator_text = Text::new(&indicator).color_range(0, ..); + print_text_with_coordinates( + indicator_text, + (cols.saturating_sub(indicator.chars().count())) / 2, + header_height + current_row, + None, + None, + ); + current_row += 1; + + // Render end lines + let start_index = total_lines.saturating_sub(end_lines); + for line in error_lines.iter().skip(start_index) { + let truncated = truncate_line_with_ansi(line, available_cols); + print!( + "\u{1b}[{};{}H{}", + header_height + current_row + 1, + 2, + truncated + ); + current_row += 1; + } + } + } +} |
