From bf691a1a02cd0b15ab13cb3522d2fcc1ab1104fd Mon Sep 17 00:00:00 2001 From: etienne-k <2804556+etienne-k@users.noreply.github.com> Date: Sun, 8 May 2022 13:02:15 +0200 Subject: [PATCH] refactor(statusline): move the statusline implementation into an own struct --- helix-term/src/ui/editor.rs | 160 ++----------------------------- helix-term/src/ui/mod.rs | 2 + helix-term/src/ui/statusline.rs | 162 ++++++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+), 150 deletions(-) create mode 100644 helix-term/src/ui/statusline.rs diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 9fba6243e..f6f49d6be 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,11 +3,10 @@ use crate::{ compositor::{Component, Context, EventResult}, key, keymap::{KeymapResult, Keymaps}, - ui::{Completion, ProgressSpinners}, + ui::{Completion, ProgressSpinners, StatusLine}, }; use helix_core::{ - coords_at_pos, encoding, graphemes::{ ensure_grapheme_boundary_next_byte, next_grapheme_boundary, prev_grapheme_boundary, }, @@ -17,7 +16,7 @@ use helix_core::{ LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ - document::{Mode, SCRATCH_BUFFER_NAME}, + document::Mode, editor::{CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::KeyEvent, @@ -739,153 +738,14 @@ impl EditorView { surface: &mut Surface, is_focused: bool, ) { - use tui::text::{Span, Spans}; - - //------------------------------- - // Left side of the status line. - //------------------------------- - - let theme = &editor.theme; - let (mode, mode_style) = match doc.mode() { - Mode::Insert => (" INS ", theme.get("ui.statusline.insert")), - Mode::Select => (" SEL ", theme.get("ui.statusline.select")), - Mode::Normal => (" NOR ", theme.get("ui.statusline.normal")), - }; - let progress = doc - .language_server() - .and_then(|srv| { - self.spinners - .get(srv.id()) - .and_then(|spinner| spinner.frame()) - }) - .unwrap_or(""); - - let base_style = if is_focused { - theme.get("ui.statusline") - } else { - theme.get("ui.statusline.inactive") - }; - // statusline - surface.set_style(viewport.with_height(1), base_style); - if is_focused { - let color_modes = editor.config().color_modes; - surface.set_string( - viewport.x, - viewport.y, - mode, - if color_modes { mode_style } else { base_style }, - ); - } - surface.set_string(viewport.x + 5, viewport.y, progress, base_style); - - //------------------------------- - // Right side of the status line. - //------------------------------- - - let mut right_side_text = Spans::default(); - - // Compute the individual info strings and add them to `right_side_text`. - - // Diagnostics - let diags = doc.diagnostics().iter().fold((0, 0), |mut counts, diag| { - use helix_core::diagnostic::Severity; - match diag.severity { - Some(Severity::Warning) => counts.0 += 1, - Some(Severity::Error) | None => counts.1 += 1, - _ => {} - } - counts - }); - let (warnings, errors) = diags; - let warning_style = theme.get("warning"); - let error_style = theme.get("error"); - for i in 0..2 { - let (count, style) = match i { - 0 => (warnings, warning_style), - 1 => (errors, error_style), - _ => unreachable!(), - }; - if count == 0 { - continue; - } - let style = base_style.patch(style); - right_side_text.0.push(Span::styled("●", style)); - right_side_text - .0 - .push(Span::styled(format!(" {} ", count), base_style)); - } - - // Selections - let sels_count = doc.selection(view.id).len(); - right_side_text.0.push(Span::styled( - format!( - " {} sel{} ", - sels_count, - if sels_count == 1 { "" } else { "s" } - ), - base_style, - )); - - // Position - let pos = coords_at_pos( - doc.text().slice(..), - doc.selection(view.id) - .primary() - .cursor(doc.text().slice(..)), - ); - right_side_text.0.push(Span::styled( - format!(" {}:{} ", pos.row + 1, pos.col + 1), // Convert to 1-indexing. - base_style, - )); - - // Encoding - let enc = doc.encoding(); - if enc != encoding::UTF_8 { - right_side_text - .0 - .push(Span::styled(format!(" {} ", enc.name()), base_style)); - } - - // File type - let file_type = doc.language_id().unwrap_or("text"); - right_side_text - .0 - .push(Span::styled(format!(" {} ", file_type), base_style)); - - // Render to the statusline. - surface.set_spans( - viewport.x - + viewport - .width - .saturating_sub(right_side_text.width() as u16), - viewport.y, - &right_side_text, - right_side_text.width() as u16, - ); - - //------------------------------- - // Middle / File path / Title - //------------------------------- - let title = { - let rel_path = doc.relative_path(); - let path = rel_path - .as_ref() - .map(|p| p.to_string_lossy()) - .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); - format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }) - }; - - surface.set_string_truncated( - viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space - viewport.y, - &title, - viewport - .width - .saturating_sub(6) - .saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info - |_| base_style, - true, - true, + StatusLine::render( + editor, + doc, + view, + viewport, + surface, + is_focused, + &self.spinners, ); } diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index ca4cedb55..70b01432b 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -8,6 +8,7 @@ mod picker; mod popup; mod prompt; mod spinner; +mod statusline; mod text; pub use completion::Completion; @@ -18,6 +19,7 @@ pub use picker::{FileLocation, FilePicker, Picker}; pub use popup::Popup; pub use prompt::{Prompt, PromptEvent}; pub use spinner::{ProgressSpinners, Spinner}; +pub use statusline::StatusLine; pub use text::Text; use helix_core::regex::Regex; diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs new file mode 100644 index 000000000..a123cb1fd --- /dev/null +++ b/helix-term/src/ui/statusline.rs @@ -0,0 +1,162 @@ +use helix_core::{coords_at_pos, encoding}; +use helix_view::{ + document::{Mode, SCRATCH_BUFFER_NAME}, + graphics::Rect, + Document, Editor, View, +}; + +use crate::ui::ProgressSpinners; + +use tui::buffer::Buffer as Surface; + +pub struct StatusLine; + +impl StatusLine { + pub fn render( + editor: &Editor, + doc: &Document, + view: &View, + viewport: Rect, + surface: &mut Surface, + is_focused: bool, + spinners: &ProgressSpinners, + ) { + use tui::text::{Span, Spans}; + + //------------------------------- + // Left side of the status line. + //------------------------------- + + let mode = match doc.mode() { + Mode::Insert => "INS", + Mode::Select => "SEL", + Mode::Normal => "NOR", + }; + let progress = doc + .language_server() + .and_then(|srv| spinners.get(srv.id()).and_then(|spinner| spinner.frame())) + .unwrap_or(""); + + let base_style = if is_focused { + editor.theme.get("ui.statusline") + } else { + editor.theme.get("ui.statusline.inactive") + }; + // statusline + surface.set_style(viewport.with_height(1), base_style); + if is_focused { + surface.set_string(viewport.x + 1, viewport.y, mode, base_style); + } + surface.set_string(viewport.x + 5, viewport.y, progress, base_style); + + //------------------------------- + // Right side of the status line. + //------------------------------- + + let mut right_side_text = Spans::default(); + + // Compute the individual info strings and add them to `right_side_text`. + + // Diagnostics + let diags = doc.diagnostics().iter().fold((0, 0), |mut counts, diag| { + use helix_core::diagnostic::Severity; + match diag.severity { + Some(Severity::Warning) => counts.0 += 1, + Some(Severity::Error) | None => counts.1 += 1, + _ => {} + } + counts + }); + let (warnings, errors) = diags; + let warning_style = editor.theme.get("warning"); + let error_style = editor.theme.get("error"); + for i in 0..2 { + let (count, style) = match i { + 0 => (warnings, warning_style), + 1 => (errors, error_style), + _ => unreachable!(), + }; + if count == 0 { + continue; + } + let style = base_style.patch(style); + right_side_text.0.push(Span::styled("●", style)); + right_side_text + .0 + .push(Span::styled(format!(" {} ", count), base_style)); + } + + // Selections + let sels_count = doc.selection(view.id).len(); + right_side_text.0.push(Span::styled( + format!( + " {} sel{} ", + sels_count, + if sels_count == 1 { "" } else { "s" } + ), + base_style, + )); + + // Position + let pos = coords_at_pos( + doc.text().slice(..), + doc.selection(view.id) + .primary() + .cursor(doc.text().slice(..)), + ); + right_side_text.0.push(Span::styled( + format!(" {}:{} ", pos.row + 1, pos.col + 1), // Convert to 1-indexing. + base_style, + )); + + // Encoding + let enc = doc.encoding(); + if enc != encoding::UTF_8 { + right_side_text + .0 + .push(Span::styled(format!(" {} ", enc.name()), base_style)); + } + + // File type + let file_type = doc.language_id().unwrap_or("text"); + right_side_text + .0 + .push(Span::styled(format!(" {} ", file_type), base_style)); + + // Render to the statusline. + surface.set_spans( + viewport.x + + viewport + .width + .saturating_sub(right_side_text.width() as u16), + viewport.y, + &right_side_text, + right_side_text.width() as u16, + ); + + //------------------------------- + // Middle / File path / Title + //------------------------------- + let title = { + let rel_path = doc.relative_path(); + let path = rel_path + .as_ref() + .map(|p| p.to_string_lossy()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); + format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }) + }; + + surface.set_string_truncated( + viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space + viewport.y, + &title, + viewport + .width + .saturating_sub(6) + .saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info + |_| base_style, + true, + true, + ); + } +}