aboutsummaryrefslogtreecommitdiffstats
path: root/default-plugins/layout-manager/src/errors.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-01-13 11:27:07 +0100
committerGitHub <noreply@github.com>2026-01-13 11:27:07 +0100
commit4a27ceea0d99b036ca607e05714cef15689d7c26 (patch)
tree22c0148718a82082bdb196c5e867697f514cc541 /default-plugins/layout-manager/src/errors.rs
parentdocs(CONTRIBUTING): Describe rust toolchain versioning policy (#4585) (diff)
downloadsl0ppij-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.rs155
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;
+ }
+ }
+ }
+}