From 5640161e38cc72617f1360e4fa3d38998181ea79 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 26 Mar 2025 02:34:53 +0000 Subject: [PATCH 01/26] feat: Welcome Screen _ _ --- book/src/editor.md | 1 + helix-term/src/application.rs | 4 +- helix-term/src/ui/editor.rs | 87 ++++++++++++++++++++++++++++++++++- helix-view/src/document.rs | 9 ++++ helix-view/src/editor.rs | 11 +++++ 5 files changed, 109 insertions(+), 3 deletions(-) diff --git a/book/src/editor.md b/book/src/editor.md index 1e5c2a507..f521c393d 100644 --- a/book/src/editor.md +++ b/book/src/editor.md @@ -61,6 +61,7 @@ | `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable" | `clipboard-provider` | Which API to use for clipboard interaction. One of `pasteboard` (MacOS), `wayland`, `x-clip`, `x-sel`, `win-32-yank`, `termux`, `tmux`, `windows`, `termcode`, `none`, or a custom command set. | Platform and environment specific. | | `editor-config` | Whether to read settings from [EditorConfig](https://editorconfig.org) files | `true` | +| `welcome-screen` | Whether to enable the welcome screen | `true` | ### `[editor.clipboard-provider]` Section diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 3bc324395..b6e09dfff 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -215,11 +215,11 @@ impl Application { editor.new_file(Action::VerticalSplit); } } else if stdin().is_tty() || cfg!(feature = "integration") { - editor.new_file(Action::VerticalSplit); + editor.new_file_welcome(Action::VerticalSplit); } else { editor .new_file_from_stdin(Action::VerticalSplit) - .unwrap_or_else(|_| editor.new_file(Action::VerticalSplit)); + .unwrap_or_else(|_| editor.new_file_welcome(Action::VerticalSplit)); } #[cfg(windows)] diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 6be565747..dd41d97d5 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -22,6 +22,7 @@ use helix_core::{ unicode::width::UnicodeWidthStr, visual_offset_from_block, Change, Position, Range, Selection, Transaction, }; +use helix_loader::VERSION_AND_GIT_HASH; use helix_view::{ annotations::diagnostics::DiagnosticFilter, document::{Mode, SCRATCH_BUFFER_NAME}, @@ -33,7 +34,10 @@ use helix_view::{ }; use std::{mem::take, num::NonZeroUsize, path::PathBuf, rc::Rc}; -use tui::{buffer::Buffer as Surface, text::Span}; +use tui::{ + buffer::Buffer as Surface, + text::{Span, Spans}, +}; pub struct EditorView { pub keymaps: Keymaps, @@ -74,6 +78,83 @@ impl EditorView { &mut self.spinners } + pub fn render_welcome(theme: &Theme, view: &View, surface: &mut Surface) { + #[derive(PartialEq, PartialOrd, Eq, Ord)] + enum Align { + Left, + Center, + } + + macro_rules! welcome { + ( + $([$align:ident] $line:expr, $(if $cond:expr;)?)* $(,)? + ) => {{ + let mut lines = vec![]; + let mut longest_left = 0; + let mut longest_center = 0; + $( + let line = Spans::from($line); + let width = line.width(); + lines.push((line, Align::$align)); + match Align::$align { + Align::Left => longest_left = longest_left.max(width), + Align::Center => longest_center = longest_center.max(width), + } + )* + (lines, longest_left, longest_center) + }}; + } + + let (lines, longest_left, longest_center) = welcome! { + [Center] vec!["helix ".into(), Span::styled(VERSION_AND_GIT_HASH, theme.get("comment"))], + [Left] "", + [Center] Span::styled( + "A post-modern modal text editor", + theme.get("ui.text").add_modifier(Modifier::ITALIC), + ), + [Left] "", + [Left] vec![ + "type ".into(), + Span::styled(":tutor", theme.get("markup.raw")), + Span::styled("", theme.get("comment")), + " to learn helix".into(), + ], + [Left] vec![ + "type ".into(), + Span::styled(":theme", theme.get("markup.raw")), + " to choose a color scheme".into(), + ], + [Left] vec![ + "type ".into(), + Span::styled("f", theme.get("markup.raw")), + " to open a file".into(), + ], + [Left] "", + [Center] vec![ + Span::styled("docs: ", theme.get("ui.text")), + Span::styled("docs.helix-editor.com", theme.get("markup.link.url")), + ], + }; + + let lines_count = lines.len(); + let longest_line_length = longest_left.max(longest_center); + + if longest_line_length as u16 > view.area.width || lines_count as u16 >= view.area.height { + return; + } + + let y_start = view.area.y + view.area.height / 2 - lines_count as u16 / 2; + let x_start_left = view.area.x + view.area.width / 2 - longest_left as u16 / 2; + + for (lines_drawn, (line, align)) in lines.iter().enumerate() { + let x = match align { + Align::Left => x_start_left, + Align::Center => view.area.x + view.area.width / 2 - line.width() as u16 / 2, + }; + surface.set_spans(x, y_start + lines_drawn as u16, line, line.width() as u16); + } + } + pub fn render_view( &self, editor: &Editor, @@ -178,6 +259,10 @@ impl EditorView { Self::render_rulers(editor, doc, view, inner, surface, theme); + if config.welcome_screen && doc.version() == 0 && doc.is_welcome { + Self::render_welcome(theme, view, surface); + } + let primary_cursor = doc .selection(view.id) .primary() diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 41c9ee1ef..3402335c8 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -207,6 +207,9 @@ pub struct Document { // NOTE: ideally this would live on the handler for color swatches. This is blocked on a // large refactor that would make `&mut Editor` available on the `DocumentDidChange` event. pub color_swatch_controller: TaskController, + + /// Whether to render the dashboard when opening the document + pub is_welcome: bool, } #[derive(Debug, Clone, Default)] @@ -719,6 +722,7 @@ impl Document { jump_labels: HashMap::new(), color_swatches: None, color_swatch_controller: TaskController::new(), + is_welcome: false, } } @@ -728,6 +732,11 @@ impl Document { Self::from(text, None, config) } + pub fn with_welcome(mut self) -> Self { + self.is_welcome = true; + self + } + // TODO: async fn? /// Create a new document from `path`. Encoding is auto-detected, but it can be manually /// overwritten with the `encoding` parameter. diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index dfade86ba..408f39fce 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -247,6 +247,8 @@ where #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { + /// Whether to enable the dashboard + pub welcome_screen: bool, /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. pub scrolloff: usize, /// Number of lines to scroll at once. Defaults to 3 @@ -960,6 +962,7 @@ pub enum PopupBorderConfig { impl Default for Config { fn default() -> Self { Self { + welcome_screen: true, scrolloff: 5, scroll_lines: 3, mouse: true, @@ -1736,6 +1739,14 @@ impl Editor { self.new_file_from_document(action, Document::default(self.config.clone())) } + /// Use when Helix is opened with no arguments passed + pub fn new_file_welcome(&mut self, action: Action) -> DocumentId { + self.new_file_from_document( + action, + Document::default(self.config.clone()).with_welcome(), + ) + } + pub fn new_file_from_stdin(&mut self, action: Action) -> Result { let (stdin, encoding, has_bom) = crate::document::read_to_string(&mut stdin(), None)?; let doc = Document::from( From 810ac6023e894573ea723eda2cb3f50cb5c31753 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 26 Mar 2025 13:34:20 +0000 Subject: [PATCH 02/26] feat: update dashboard --- helix-term/src/ui/editor.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dd41d97d5..f892fd94a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -117,17 +117,34 @@ impl EditorView { "type ".into(), Span::styled(":tutor", theme.get("markup.raw")), Span::styled("", theme.get("comment")), - " to learn helix".into(), + " to learn helix".into(), ], [Left] vec![ "type ".into(), Span::styled(":theme", theme.get("markup.raw")), - " to choose a color scheme".into(), + " to choose a color scheme".into(), ], [Left] vec![ "type ".into(), Span::styled("f", theme.get("markup.raw")), - " to open a file".into(), + " to open a file".into(), + ], + [Left] vec![ + "type ".into(), + Span::styled("?", theme.get("markup.raw")), + " to see all commands".into(), + ], + [Left] vec![ + "type ".into(), + Span::styled(":config-open", theme.get("markup.raw")), + Span::styled("", theme.get("comment")), + " to configure helix".into(), + ], + [Left] vec![ + "type ".into(), + Span::styled(":quit", theme.get("markup.raw")), + Span::styled("", theme.get("comment")), + " to exit helix".into(), ], [Left] "", [Center] vec![ From 7e9bf8a3bbceb513024d5a09433fa72a26690d1e Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 26 Mar 2025 13:37:29 +0000 Subject: [PATCH 03/26] feat: shift the type to the left _ --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f892fd94a..62d73225f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -165,7 +165,7 @@ impl EditorView { for (lines_drawn, (line, align)) in lines.iter().enumerate() { let x = match align { - Align::Left => x_start_left, + Align::Left => x_start_left + 4, Align::Center => view.area.x + view.area.width / 2 - line.width() as u16 / 2, }; surface.set_spans(x, y_start + lines_drawn as u16, line, line.width() as u16); From 871f12b751cf459d57a5c611d877acf862d764f8 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 26 Mar 2025 14:18:37 +0000 Subject: [PATCH 04/26] fix: 2 panics _ _ --- helix-term/src/ui/editor.rs | 41 ++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 62d73225f..353311caf 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -153,20 +153,47 @@ impl EditorView { ], }; + // how many total lines there are in the welcome screen let lines_count = lines.len(); - let longest_line_length = longest_left.max(longest_center); - if longest_line_length as u16 > view.area.width || lines_count as u16 >= view.area.height { + // the y-coordinate where we start drawing the welcome screen + let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count as u16 / 2); + let y_center = view.area.x + view.area.width / 2; + + // largest possible padding that still allows the text to fit within the screen + let max_padding = view.area.width as i16 / 2 - longest_left as i16 / 2 - view.area.x as i16; + + // Despite being in the mathematical left, we want to start drawing the "left" + // lines a little bit extra to the right so it looks good + // + // this is because of text density: at the start the text density is high, but + // towards the end it is low. Therefore to achieve an optical balance we must + // do a little offset + // + // this padding of 4 is not cruicial though, so if we can't fit it on the screen + // we just decrease it until it is 0. Once that happens, if it still overflows + // we don't want to draw the welcome screen. + let padding = 4.min(max_padding.max(0) as u16); + + let x_start_left = + padding + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2); + + let has_x_left_overflow = (x_start_left + longest_left as u16) > view.area.width; + let has_x_center_overflow = longest_center as u16 > view.area.width; + let has_x_overflow = has_x_left_overflow || has_x_center_overflow; + + // we want lines_count < view.area.height so it does not get drawn + // over the status line + let has_y_overflow = lines_count as u16 >= view.area.height; + + if has_x_overflow || has_y_overflow { return; } - let y_start = view.area.y + view.area.height / 2 - lines_count as u16 / 2; - let x_start_left = view.area.x + view.area.width / 2 - longest_left as u16 / 2; - for (lines_drawn, (line, align)) in lines.iter().enumerate() { let x = match align { - Align::Left => x_start_left + 4, - Align::Center => view.area.x + view.area.width / 2 - line.width() as u16 / 2, + Align::Left => x_start_left, + Align::Center => y_center - line.width() as u16 / 2, }; surface.set_spans(x, y_start + lines_drawn as u16, line, line.width() as u16); } From b68aa35bf4af8d80276e092558123732784c497e Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:17:55 +0000 Subject: [PATCH 05/26] feat: change text of :theme in welcome message _ --- helix-term/src/ui/editor.rs | 6 +++--- helix-view/src/document.rs | 2 +- helix-view/src/editor.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 353311caf..53f809319 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -122,7 +122,7 @@ impl EditorView { [Left] vec![ "type ".into(), Span::styled(":theme", theme.get("markup.raw")), - " to choose a color scheme".into(), + " to choose a theme".into(), ], [Left] vec![ "type ".into(), @@ -170,10 +170,10 @@ impl EditorView { // towards the end it is low. Therefore to achieve an optical balance we must // do a little offset // - // this padding of 4 is not cruicial though, so if we can't fit it on the screen + // this padding of 3 is not cruicial though, so if we can't fit it on the screen // we just decrease it until it is 0. Once that happens, if it still overflows // we don't want to draw the welcome screen. - let padding = 4.min(max_padding.max(0) as u16); + let padding = 3.min(max_padding.max(0) as u16); let x_start_left = padding + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2); diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 3402335c8..a9a78d4fa 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -208,7 +208,7 @@ pub struct Document { // large refactor that would make `&mut Editor` available on the `DocumentDidChange` event. pub color_swatch_controller: TaskController, - /// Whether to render the dashboard when opening the document + /// Whether to render the welcome screen when opening the document pub is_welcome: bool, } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 408f39fce..d3a3c6dc4 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -247,7 +247,7 @@ where #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { - /// Whether to enable the dashboard + /// Whether to enable the welcome screen pub welcome_screen: bool, /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. pub scrolloff: usize, From 1e2f1363bc4e6c97c318a86e99d0a87e1f86b7ef Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:48:37 +0000 Subject: [PATCH 06/26] feat: update welcome screen 1. remove `type ` prefix 2. add `` to themes point --- helix-term/src/ui/editor.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 53f809319..ff09a23cf 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -114,34 +114,29 @@ impl EditorView { ), [Left] "", [Left] vec![ - "type ".into(), Span::styled(":tutor", theme.get("markup.raw")), Span::styled("", theme.get("comment")), " to learn helix".into(), ], [Left] vec![ - "type ".into(), Span::styled(":theme", theme.get("markup.raw")), - " to choose a theme".into(), + Span::styled("", theme.get("comment")), + " to choose a theme".into(), ], [Left] vec![ - "type ".into(), Span::styled("f", theme.get("markup.raw")), " to open a file".into(), ], [Left] vec![ - "type ".into(), Span::styled("?", theme.get("markup.raw")), " to see all commands".into(), ], [Left] vec![ - "type ".into(), Span::styled(":config-open", theme.get("markup.raw")), Span::styled("", theme.get("comment")), " to configure helix".into(), ], [Left] vec![ - "type ".into(), Span::styled(":quit", theme.get("markup.raw")), Span::styled("", theme.get("comment")), " to exit helix".into(), @@ -170,10 +165,10 @@ impl EditorView { // towards the end it is low. Therefore to achieve an optical balance we must // do a little offset // - // this padding of 3 is not cruicial though, so if we can't fit it on the screen + // this padding is not cruicial though, so if we can't fit it on the screen // we just decrease it until it is 0. Once that happens, if it still overflows // we don't want to draw the welcome screen. - let padding = 3.min(max_padding.max(0) as u16); + let padding = 2.min(max_padding.max(0) as u16); let x_start_left = padding + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2); From 3471d82f879e73fd07e4cb35c9ebba186fda7e60 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:02:02 +0000 Subject: [PATCH 07/26] refactor: make padding more naive no need for such a complex solution, this is already pretty good --- helix-term/src/ui/editor.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index ff09a23cf..ef3fd9a01 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -155,23 +155,8 @@ impl EditorView { let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count as u16 / 2); let y_center = view.area.x + view.area.width / 2; - // largest possible padding that still allows the text to fit within the screen - let max_padding = view.area.width as i16 / 2 - longest_left as i16 / 2 - view.area.x as i16; - - // Despite being in the mathematical left, we want to start drawing the "left" - // lines a little bit extra to the right so it looks good - // - // this is because of text density: at the start the text density is high, but - // towards the end it is low. Therefore to achieve an optical balance we must - // do a little offset - // - // this padding is not cruicial though, so if we can't fit it on the screen - // we just decrease it until it is 0. Once that happens, if it still overflows - // we don't want to draw the welcome screen. - let padding = 2.min(max_padding.max(0) as u16); - let x_start_left = - padding + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2); + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2) + 2; let has_x_left_overflow = (x_start_left + longest_left as u16) > view.area.width; let has_x_center_overflow = longest_center as u16 > view.area.width; From 593504bb2721b8548419b093f0c2124187812b15 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:03:56 +0000 Subject: [PATCH 08/26] feat: left padding 2 -> 1 --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index ef3fd9a01..4136aa739 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -156,7 +156,7 @@ impl EditorView { let y_center = view.area.x + view.area.width / 2; let x_start_left = - view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2) + 2; + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2) + 1; let has_x_left_overflow = (x_start_left + longest_left as u16) > view.area.width; let has_x_center_overflow = longest_center as u16 > view.area.width; From 603d95f5d3e7aee474117c1c50fed391e3723d4b Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:34:36 +0000 Subject: [PATCH 09/26] feat: recommend e over f _ --- helix-term/src/ui/editor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 4136aa739..7c8faffdc 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -124,8 +124,8 @@ impl EditorView { " to choose a theme".into(), ], [Left] vec![ - Span::styled("f", theme.get("markup.raw")), - " to open a file".into(), + Span::styled("e", theme.get("markup.raw")), + " to open file explorer".into(), ], [Left] vec![ Span::styled("?", theme.get("markup.raw")), From 55ef555f872725b9d85e62bebc096e2752e444e4 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:38:50 +0000 Subject: [PATCH 10/26] feat: improve wording in start menu --- helix-term/src/ui/editor.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7c8faffdc..3df7b37ab 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -116,30 +116,30 @@ impl EditorView { [Left] vec![ Span::styled(":tutor", theme.get("markup.raw")), Span::styled("", theme.get("comment")), - " to learn helix".into(), + " learn helix".into(), ], [Left] vec![ Span::styled(":theme", theme.get("markup.raw")), Span::styled("", theme.get("comment")), - " to choose a theme".into(), + " choose a theme".into(), ], [Left] vec![ Span::styled("e", theme.get("markup.raw")), - " to open file explorer".into(), + " file explorer".into(), ], [Left] vec![ Span::styled("?", theme.get("markup.raw")), - " to see all commands".into(), + " see all commands".into(), ], [Left] vec![ Span::styled(":config-open", theme.get("markup.raw")), Span::styled("", theme.get("comment")), - " to configure helix".into(), + " configure helix".into(), ], [Left] vec![ Span::styled(":quit", theme.get("markup.raw")), Span::styled("", theme.get("comment")), - " to exit helix".into(), + " quit helix".into(), ], [Left] "", [Center] vec![ From a51334f00b1e3bdde4154d6ce544dab0b7854765 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:42:01 +0000 Subject: [PATCH 11/26] feat: increase padding by 1 --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 3df7b37ab..55d289e91 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -156,7 +156,7 @@ impl EditorView { let y_center = view.area.x + view.area.width / 2; let x_start_left = - view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2) + 1; + view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2) + 2; let has_x_left_overflow = (x_start_left + longest_left as u16) > view.area.width; let has_x_center_overflow = longest_center as u16 > view.area.width; From b1c7bd91b9c297a5b9a0527a0e9389653f93193d Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:00:49 +0100 Subject: [PATCH 12/26] refactor: do not pass argument unnecessarily --- helix-term/src/application.rs | 4 ++-- helix-view/src/editor.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b6e09dfff..9006a501e 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -215,11 +215,11 @@ impl Application { editor.new_file(Action::VerticalSplit); } } else if stdin().is_tty() || cfg!(feature = "integration") { - editor.new_file_welcome(Action::VerticalSplit); + editor.new_file_welcome(); } else { editor .new_file_from_stdin(Action::VerticalSplit) - .unwrap_or_else(|_| editor.new_file_welcome(Action::VerticalSplit)); + .unwrap_or_else(|_| editor.new_file_welcome()); } #[cfg(windows)] diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index d3a3c6dc4..79e18939f 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1740,9 +1740,9 @@ impl Editor { } /// Use when Helix is opened with no arguments passed - pub fn new_file_welcome(&mut self, action: Action) -> DocumentId { + pub fn new_file_welcome(&mut self) -> DocumentId { self.new_file_from_document( - action, + Action::VerticalSplit, Document::default(self.config.clone()).with_welcome(), ) } From 3c0fcb0679502f3815f66cf559775d32cfc785c2 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:02:40 +0100 Subject: [PATCH 13/26] chore: justify welcome! macro with comment --- helix-term/src/ui/editor.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 55d289e91..41506cc82 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -85,6 +85,10 @@ impl EditorView { Center, } + /// Declare the welcome screen declaratively using this macro + /// It avoids the performance overhead of calling `Vec>::flatten` and + /// makes it easy to get the longest line in the center and the left, without + /// having to iterate over the `Vec<_>` again. macro_rules! welcome { ( $([$align:ident] $line:expr, $(if $cond:expr;)?)* $(,)? From 761a62df86a2f8e1b594779d142ae79857cc1357 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:08:37 +0100 Subject: [PATCH 14/26] refactor: add comments explaining various variables + rename variables --- helix-term/src/ui/editor.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 41506cc82..dae421b95 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -109,7 +109,7 @@ impl EditorView { }}; } - let (lines, longest_left, longest_center) = welcome! { + let (lines, len_of_longest_left, len_of_longest_center) = welcome! { [Center] vec!["helix ".into(), Span::styled(VERSION_AND_GIT_HASH, theme.get("comment"))], [Left] "", [Center] Span::styled( @@ -157,13 +157,16 @@ impl EditorView { // the y-coordinate where we start drawing the welcome screen let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count as u16 / 2); + // y-coordinate of the center of the viewport let y_center = view.area.x + view.area.width / 2; + // the x-coordinate where we start drawing the welcome screen + // +2 to make the text look like its in the center instead of on the side let x_start_left = - view.area.x + (view.area.width / 2).saturating_sub(longest_left as u16 / 2) + 2; + view.area.x + (view.area.width / 2).saturating_sub(len_of_longest_left as u16 / 2) + 2; - let has_x_left_overflow = (x_start_left + longest_left as u16) > view.area.width; - let has_x_center_overflow = longest_center as u16 > view.area.width; + let has_x_left_overflow = (x_start_left + len_of_longest_left as u16) > view.area.width; + let has_x_center_overflow = len_of_longest_center as u16 > view.area.width; let has_x_overflow = has_x_left_overflow || has_x_center_overflow; // we want lines_count < view.area.height so it does not get drawn From 7c7bd2015916530e064575724612c6e2f4bb9999 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:10:10 +0100 Subject: [PATCH 15/26] chore: incorrect comment fix --- helix-term/src/ui/editor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index dae421b95..3802a629c 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -86,8 +86,7 @@ impl EditorView { } /// Declare the welcome screen declaratively using this macro - /// It avoids the performance overhead of calling `Vec>::flatten` and - /// makes it easy to get the longest line in the center and the left, without + /// It makes it easy to get the longest line in the center and the left, without /// having to iterate over the `Vec<_>` again. macro_rules! welcome { ( From 13df887cca37fee105846551c20ebdb1be2d7b3b Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Mon, 14 Apr 2025 17:01:01 +0100 Subject: [PATCH 16/26] refactor: cast to u16 in a single place --- helix-term/src/ui/editor.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 3802a629c..90c18fd6d 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -104,7 +104,7 @@ impl EditorView { Align::Center => longest_center = longest_center.max(width), } )* - (lines, longest_left, longest_center) + (lines, longest_left as u16, longest_center as u16) }}; } @@ -152,25 +152,25 @@ impl EditorView { }; // how many total lines there are in the welcome screen - let lines_count = lines.len(); + let lines_count = lines.len() as u16; // the y-coordinate where we start drawing the welcome screen - let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count as u16 / 2); + let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count / 2); // y-coordinate of the center of the viewport let y_center = view.area.x + view.area.width / 2; // the x-coordinate where we start drawing the welcome screen // +2 to make the text look like its in the center instead of on the side let x_start_left = - view.area.x + (view.area.width / 2).saturating_sub(len_of_longest_left as u16 / 2) + 2; + view.area.x + (view.area.width / 2).saturating_sub(len_of_longest_left / 2) + 2; - let has_x_left_overflow = (x_start_left + len_of_longest_left as u16) > view.area.width; - let has_x_center_overflow = len_of_longest_center as u16 > view.area.width; + let has_x_left_overflow = (x_start_left + len_of_longest_left) > view.area.width; + let has_x_center_overflow = len_of_longest_center > view.area.width; let has_x_overflow = has_x_left_overflow || has_x_center_overflow; // we want lines_count < view.area.height so it does not get drawn // over the status line - let has_y_overflow = lines_count as u16 >= view.area.height; + let has_y_overflow = lines_count >= view.area.height; if has_x_overflow || has_y_overflow { return; From 1c3efb9160665028a8c55a75a14205440a66dfb5 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Fri, 9 May 2025 17:28:52 +0100 Subject: [PATCH 17/26] fix: remove `` hint --- helix-term/src/ui/editor.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 90c18fd6d..e7ffa5633 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -123,8 +123,7 @@ impl EditorView { ], [Left] vec![ Span::styled(":theme", theme.get("markup.raw")), - Span::styled("", theme.get("comment")), - " choose a theme".into(), + " choose a theme".into(), ], [Left] vec![ Span::styled("e", theme.get("markup.raw")), From b1c2ca50214660bd4d86dd58272c262cac7fd1dd Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Fri, 9 May 2025 17:29:23 +0100 Subject: [PATCH 18/26] fix: replace `` with `` --- helix-term/src/ui/editor.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index e7ffa5633..8c84e6575 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -123,7 +123,8 @@ impl EditorView { ], [Left] vec![ Span::styled(":theme", theme.get("markup.raw")), - " choose a theme".into(), + Span::styled("", theme.get("comment")), + " choose a theme".into(), ], [Left] vec![ Span::styled("e", theme.get("markup.raw")), From 1288b72b32fa570b7aa7d316b92602c4e5c57ceb Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 16:24:10 +0100 Subject: [PATCH 19/26] feat: render the logo of Helix in the welcome screen if there is enough space --- helix-term/src/ui/editor.rs | 141 +++++++++++++++++++++++++++++++----- helix-tui/src/text.rs | 6 ++ helix-view/src/editor.rs | 2 +- 3 files changed, 131 insertions(+), 18 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 3afd51130..5e030842a 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -32,7 +32,7 @@ use helix_view::{ keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, }; -use std::{mem::take, num::NonZeroUsize, ops, path::PathBuf, rc::Rc}; +use std::{mem::take, num::NonZeroUsize, ops, path::PathBuf, rc::Rc, sync::LazyLock}; use tui::{ buffer::Buffer as Surface, @@ -78,13 +78,80 @@ impl EditorView { &mut self.spinners } - pub fn render_welcome(theme: &Theme, view: &View, surface: &mut Surface) { + pub fn render_welcome(theme: &Theme, view: &View, surface: &mut Surface, is_colorful: bool) { #[derive(PartialEq, PartialOrd, Eq, Ord)] enum Align { Left, Center, } + /// Logo for Helix + const LOGO: &str = "\ +** +***** :: + ******** ::::: + **::::::: + ::::::::***= +::::::: ==== +:::: ======= +:---======== + =======-- +===== -------- +== ----- + --"; + + /// Size of the maximum line of the logo + static LOGO_WIDTH: LazyLock = LazyLock::new(|| { + LOGO.lines() + .max_by(|line, other| line.len().cmp(&other.len())) + .unwrap_or("") + .len() as u16 + }); + + /// Use when true color is not supported + static COLORLESS_LOGO: LazyLock> = LazyLock::new(|| { + LOGO.lines() + .map(|line| Spans(vec![Span::raw(line)])) + .collect::>() + }); + + /// The logo is colored using Helix's colors + static COLORED_LOGO: LazyLock> = LazyLock::new(|| { + LOGO.lines() + .map(|line| { + line.chars() + .map(|ch| match ch { + '*' | ':' | '=' | '-' => { + let (ch, fg) = match ch { + '*' => ("*", Color::Rgb(112, 107, 200)), + ':' => (":", Color::Rgb(132, 221, 234)), + '=' => ("=", Color::Rgb(153, 123, 200)), + '-' => ("-", Color::Rgb(85, 197, 228)), + _ => unreachable!(), + }; + Span::styled(ch, Style::new().fg(fg)) + } + ' ' => Span::raw(" "), + _ => unreachable!(), + }) + .collect::() + }) + .collect::>() + }); + + let logo = if is_colorful { + &COLORED_LOGO + } else { + &COLORLESS_LOGO + }; + + /// How much space to put between the help text and the logo + const LOGO_LEFT_PADDING: u16 = 6; + + // Shift the help text to the right by this amount, to add space + // for the logo + let help_offset: u16 = *LOGO_WIDTH / 2 + LOGO_LEFT_PADDING / 2; + /// Declare the welcome screen declaratively using this macro /// It makes it easy to get the longest line in the center and the left, without /// having to iterate over the `Vec<_>` again. @@ -108,7 +175,7 @@ impl EditorView { }}; } - let (lines, len_of_longest_left, len_of_longest_center) = welcome! { + let (help_lines, len_of_longest_left_help_line, len_of_longest_center_help_line) = welcome! { [Center] vec!["helix ".into(), Span::styled(VERSION_AND_GIT_HASH, theme.get("comment"))], [Left] "", [Center] Span::styled( @@ -152,36 +219,71 @@ impl EditorView { }; // how many total lines there are in the welcome screen - let lines_count = lines.len() as u16; + let lines_count = help_lines.len() as u16; // the y-coordinate where we start drawing the welcome screen let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count / 2); - // y-coordinate of the center of the viewport - let y_center = view.area.x + view.area.width / 2; + // x-coordinate of the center of the viewport + let x_center = view.area.x + view.area.width / 2; // the x-coordinate where we start drawing the welcome screen // +2 to make the text look like its in the center instead of on the side - let x_start_left = - view.area.x + (view.area.width / 2).saturating_sub(len_of_longest_left / 2) + 2; + let x_start_left_help_line = view.area.x + + (view.area.width / 2).saturating_sub(len_of_longest_left_help_line / 2) + + 2; - let has_x_left_overflow = (x_start_left + len_of_longest_left) > view.area.width; - let has_x_center_overflow = len_of_longest_center > view.area.width; - let has_x_overflow = has_x_left_overflow || has_x_center_overflow; + let has_x_left_help_overflow = + (x_start_left_help_line + len_of_longest_left_help_line) > view.area.width; + let has_x_center_help_overflow = len_of_longest_center_help_line > view.area.width; + let has_x_help_overflow = has_x_left_help_overflow || has_x_center_help_overflow; // we want lines_count < view.area.height so it does not get drawn // over the status line - let has_y_overflow = lines_count >= view.area.height; + let has_y_help_overflow = lines_count >= view.area.height; - if has_x_overflow || has_y_overflow { + // Not enough space to render the help text even without the logo. Render nothing. + if has_x_help_overflow || has_y_help_overflow { return; } - for (lines_drawn, (line, align)) in lines.iter().enumerate() { + // At this point we know that there is enough vertical + // and horizontal space to render the help text + + // +3 +3 for extra padding at either side + let width_of_help_with_logo = + 3 + *LOGO_WIDTH + LOGO_LEFT_PADDING + len_of_longest_left_help_line + 3; + + // If there is not enough space to show LOGO + HELP, then don't show the logo at all + // + // If we get here we know that there IS enough space to show just the help + let show_logo = width_of_help_with_logo < view.area.width; + + for (lines_drawn, (line, align)) in help_lines.iter().enumerate() { + let x_start_left = if show_logo { + x_start_left_help_line + help_offset + } else { + x_start_left_help_line + }; + + let x_start_center = + x_center - line.width() as u16 / 2 + if show_logo { help_offset } else { 0 }; + let x = match align { Align::Left => x_start_left, - Align::Center => y_center - line.width() as u16 / 2, + Align::Center => x_start_center, }; - surface.set_spans(x, y_start + lines_drawn as u16, line, line.width() as u16); + + let y = y_start + lines_drawn as u16; + surface.set_spans(x, y, line, line.width() as u16); + + if show_logo { + surface.set_spans( + x_start_left - LOGO_LEFT_PADDING - *LOGO_WIDTH, + y, + &logo[lines_drawn], + *LOGO_WIDTH, + ); + } } } @@ -273,7 +375,12 @@ impl EditorView { Self::render_rulers(editor, doc, view, inner, surface, theme); if config.welcome_screen && doc.version() == 0 && doc.is_welcome { - Self::render_welcome(theme, view, surface); + Self::render_welcome( + theme, + view, + surface, + config.true_color || crate::true_color(), + ); } let primary_cursor = doc diff --git a/helix-tui/src/text.rs b/helix-tui/src/text.rs index c4313e15f..5b77d8a65 100644 --- a/helix-tui/src/text.rs +++ b/helix-tui/src/text.rs @@ -249,6 +249,12 @@ impl<'a> From> for Spans<'a> { } } +impl<'a> FromIterator> for Spans<'a> { + fn from_iter>>(iter: T) -> Self { + Spans(iter.into_iter().collect()) + } +} + impl<'a> From>> for Spans<'a> { fn from(spans: Vec>) -> Spans<'a> { Spans(spans) diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index bd3833dd7..97b9bbb87 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1753,7 +1753,7 @@ impl Editor { pub fn new_file_welcome(&mut self) -> DocumentId { self.new_file_from_document( Action::VerticalSplit, - Document::default(self.config.clone()).with_welcome(), + Document::default(self.config.clone(), self.syn_loader.clone()).with_welcome(), ) } From fdcbc4e594ccc29f5e534c6bed1d40cca72e8fdd Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 16:34:06 +0100 Subject: [PATCH 20/26] feat: add tip to use `tab` to select theme --- helix-term/src/ui/editor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 5e030842a..b8080b507 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -190,8 +190,8 @@ impl EditorView { ], [Left] vec![ Span::styled(":theme", theme.get("markup.raw")), - Span::styled("", theme.get("comment")), - " choose a theme".into(), + Span::styled("", theme.get("comment")), + " choose a theme".into(), ], [Left] vec![ Span::styled("e", theme.get("markup.raw")), From 59b3fbbb2c9957bf5860c1497c41d7243a4c65ec Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 20:05:25 +0100 Subject: [PATCH 21/26] feat: remove tip about `:config-open` --- helix-term/src/ui/editor.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index b8080b507..f15e1a3ef 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -201,11 +201,6 @@ impl EditorView { Span::styled("?", theme.get("markup.raw")), " see all commands".into(), ], - [Left] vec![ - Span::styled(":config-open", theme.get("markup.raw")), - Span::styled("", theme.get("comment")), - " configure helix".into(), - ], [Left] vec![ Span::styled(":quit", theme.get("markup.raw")), Span::styled("", theme.get("comment")), @@ -216,6 +211,7 @@ impl EditorView { Span::styled("docs: ", theme.get("ui.text")), Span::styled("docs.helix-editor.com", theme.get("markup.link.url")), ], + [Left] "", }; // how many total lines there are in the welcome screen From 1b8228a2e82fa2baceee04eab978ac293f05ed55 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 21:18:48 +0100 Subject: [PATCH 22/26] refactor: Don't use a macro + rename variables --- helix-term/src/ui/editor.rs | 275 ++++++++++++++++++++---------------- 1 file changed, 154 insertions(+), 121 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f15e1a3ef..9aef68066 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -79,14 +79,8 @@ impl EditorView { } pub fn render_welcome(theme: &Theme, view: &View, surface: &mut Surface, is_colorful: bool) { - #[derive(PartialEq, PartialOrd, Eq, Ord)] - enum Align { - Left, - Center, - } - /// Logo for Helix - const LOGO: &str = "\ + const LOGO_STR: &str = "\ ** ***** :: ******** ::::: @@ -102,174 +96,213 @@ impl EditorView { /// Size of the maximum line of the logo static LOGO_WIDTH: LazyLock = LazyLock::new(|| { - LOGO.lines() + LOGO_STR + .lines() .max_by(|line, other| line.len().cmp(&other.len())) .unwrap_or("") .len() as u16 }); /// Use when true color is not supported - static COLORLESS_LOGO: LazyLock> = LazyLock::new(|| { - LOGO.lines() + static LOGO_NO_COLOR: LazyLock> = LazyLock::new(|| { + LOGO_STR + .lines() .map(|line| Spans(vec![Span::raw(line)])) - .collect::>() + .collect() }); /// The logo is colored using Helix's colors - static COLORED_LOGO: LazyLock> = LazyLock::new(|| { - LOGO.lines() + static LOGO_WITH_COLOR: LazyLock> = LazyLock::new(|| { + LOGO_STR + .lines() .map(|line| { line.chars() .map(|ch| match ch { - '*' | ':' | '=' | '-' => { - let (ch, fg) = match ch { - '*' => ("*", Color::Rgb(112, 107, 200)), - ':' => (":", Color::Rgb(132, 221, 234)), - '=' => ("=", Color::Rgb(153, 123, 200)), - '-' => ("-", Color::Rgb(85, 197, 228)), + '*' | ':' | '=' | '-' => Span::styled( + ch.to_string(), + Style::new().fg(match ch { + '*' => Color::Rgb(112, 107, 200), + ':' => Color::Rgb(132, 221, 234), + '=' => Color::Rgb(153, 123, 200), + '-' => Color::Rgb(85, 197, 228), _ => unreachable!(), - }; - Span::styled(ch, Style::new().fg(fg)) - } + }), + ), ' ' => Span::raw(" "), - _ => unreachable!(), + _ => unreachable!("logo should only contain '*', ':', '=', '-' or ' '"), }) - .collect::() + .collect() }) - .collect::>() + .collect() }); - let logo = if is_colorful { - &COLORED_LOGO - } else { - &COLORLESS_LOGO - }; - /// How much space to put between the help text and the logo const LOGO_LEFT_PADDING: u16 = 6; // Shift the help text to the right by this amount, to add space // for the logo - let help_offset: u16 = *LOGO_WIDTH / 2 + LOGO_LEFT_PADDING / 2; + static HELP_X_LOGO_OFFSET: LazyLock = + LazyLock::new(|| *LOGO_WIDTH / 2 + LOGO_LEFT_PADDING / 2); - /// Declare the welcome screen declaratively using this macro - /// It makes it easy to get the longest line in the center and the left, without - /// having to iterate over the `Vec<_>` again. - macro_rules! welcome { - ( - $([$align:ident] $line:expr, $(if $cond:expr;)?)* $(,)? - ) => {{ - let mut lines = vec![]; - let mut longest_left = 0; - let mut longest_center = 0; - $( - let line = Spans::from($line); - let width = line.width(); - lines.push((line, Align::$align)); - match Align::$align { - Align::Left => longest_left = longest_left.max(width), - Align::Center => longest_center = longest_center.max(width), - } - )* - (lines, longest_left as u16, longest_center as u16) - }}; + #[derive(PartialEq, PartialOrd, Eq, Ord)] + enum AlignLine { + Left, + Center, } + use AlignLine::*; - let (help_lines, len_of_longest_left_help_line, len_of_longest_center_help_line) = welcome! { - [Center] vec!["helix ".into(), Span::styled(VERSION_AND_GIT_HASH, theme.get("comment"))], - [Left] "", - [Center] Span::styled( - "A post-modern modal text editor", - theme.get("ui.text").add_modifier(Modifier::ITALIC), - ), - [Left] "", - [Left] vec![ - Span::styled(":tutor", theme.get("markup.raw")), - Span::styled("", theme.get("comment")), - " learn helix".into(), - ], - [Left] vec![ - Span::styled(":theme", theme.get("markup.raw")), - Span::styled("", theme.get("comment")), - " choose a theme".into(), - ], - [Left] vec![ - Span::styled("e", theme.get("markup.raw")), - " file explorer".into(), - ], - [Left] vec![ - Span::styled("?", theme.get("markup.raw")), - " see all commands".into(), - ], - [Left] vec![ - Span::styled(":quit", theme.get("markup.raw")), - Span::styled("", theme.get("comment")), - " quit helix".into(), - ], - [Left] "", - [Center] vec![ - Span::styled("docs: ", theme.get("ui.text")), - Span::styled("docs.helix-editor.com", theme.get("markup.link.url")), - ], - [Left] "", + let logo = if is_colorful { + &LOGO_WITH_COLOR + } else { + &LOGO_NO_COLOR }; - // how many total lines there are in the welcome screen - let lines_count = help_lines.len() as u16; + let empty_line = || (Spans::from(""), Left); + + let raw_help_lines: [(Spans, AlignLine); 12] = [ + ( + vec![ + Span::raw("helix "), + Span::styled(VERSION_AND_GIT_HASH, theme.get("comment")), + ] + .into(), + Center, + ), + empty_line(), + ( + Span::styled( + "A post-modern modal text editor", + theme.get("ui.text").add_modifier(Modifier::ITALIC), + ) + .into(), + Center, + ), + empty_line(), + ( + vec![ + Span::styled(":tutor", theme.get("markup.raw")), + Span::styled("", theme.get("comment")), + Span::raw(" learn helix"), + ] + .into(), + Left, + ), + ( + vec![ + Span::styled(":theme", theme.get("markup.raw")), + Span::styled("", theme.get("comment")), + Span::raw(" choose a theme"), + ] + .into(), + Left, + ), + ( + vec![ + Span::styled("e", theme.get("markup.raw")), + Span::raw(" file explorer"), + ] + .into(), + Left, + ), + ( + vec![ + Span::styled("?", theme.get("markup.raw")), + Span::raw(" see all commands"), + ] + .into(), + Left, + ), + ( + vec![ + Span::styled(":quit", theme.get("markup.raw")), + Span::styled("", theme.get("comment")), + Span::raw(" quit helix"), + ] + .into(), + Left, + ), + empty_line(), + ( + vec![ + Span::styled("docs: ", theme.get("ui.text")), + Span::styled("docs.helix-editor.com", theme.get("markup.link.url")), + ] + .into(), + Center, + ), + empty_line(), + ]; + + let mut help_lines = Vec::with_capacity(raw_help_lines.len()); + let mut len_of_longest_left_align = 0; + let mut len_of_longest_center_align = 0; + + for (spans, align) in raw_help_lines { + let width = spans.width(); + match align { + Left => len_of_longest_left_align = len_of_longest_left_align.max(width), + Center => len_of_longest_center_align = len_of_longest_center_align.max(width), + } + help_lines.push((spans, align)); + } + + let len_of_longest_left_align = len_of_longest_left_align as u16; // the y-coordinate where we start drawing the welcome screen - let y_start = view.area.y + (view.area.height / 2).saturating_sub(lines_count / 2); + let start_drawing_at_y = + view.area.y + (view.area.height / 2).saturating_sub(help_lines.len() as u16 / 2); + // x-coordinate of the center of the viewport - let x_center = view.area.x + view.area.width / 2; + let x_view_center = view.area.x + view.area.width / 2; - // the x-coordinate where we start drawing the welcome screen - // +2 to make the text look like its in the center instead of on the side - let x_start_left_help_line = view.area.x - + (view.area.width / 2).saturating_sub(len_of_longest_left_help_line / 2) - + 2; + // the x-coordinate where we start drawing the `AlignLine::Left` lines + // +2 to make the text look like more balanced relative to the center of the help + let start_drawing_left_align_at_x = + view.area.x + (view.area.width / 2).saturating_sub(len_of_longest_left_align / 2) + 2; - let has_x_left_help_overflow = - (x_start_left_help_line + len_of_longest_left_help_line) > view.area.width; - let has_x_center_help_overflow = len_of_longest_center_help_line > view.area.width; - let has_x_help_overflow = has_x_left_help_overflow || has_x_center_help_overflow; + let are_any_left_aligned_lines_overflowing_x = + (start_drawing_left_align_at_x + len_of_longest_left_align) > view.area.width; - // we want lines_count < view.area.height so it does not get drawn - // over the status line - let has_y_help_overflow = lines_count >= view.area.height; + let are_any_center_aligned_lines_overflowing_x = + len_of_longest_center_align as u16 > view.area.width; + + let is_help_x_overflowing = + are_any_left_aligned_lines_overflowing_x || are_any_center_aligned_lines_overflowing_x; + + // we want `<` so it does not get drawn over the status line + let is_help_y_overflowing = (help_lines.len() as u16) >= view.area.height; // Not enough space to render the help text even without the logo. Render nothing. - if has_x_help_overflow || has_y_help_overflow { + if is_help_x_overflowing || is_help_y_overflowing { return; } // At this point we know that there is enough vertical // and horizontal space to render the help text - // +3 +3 for extra padding at either side - let width_of_help_with_logo = - 3 + *LOGO_WIDTH + LOGO_LEFT_PADDING + len_of_longest_left_help_line + 3; + let width_of_help_with_logo = *LOGO_WIDTH + LOGO_LEFT_PADDING + len_of_longest_left_align; // If there is not enough space to show LOGO + HELP, then don't show the logo at all // // If we get here we know that there IS enough space to show just the help - let show_logo = width_of_help_with_logo < view.area.width; + let show_logo = width_of_help_with_logo <= view.area.width; for (lines_drawn, (line, align)) in help_lines.iter().enumerate() { - let x_start_left = if show_logo { - x_start_left_help_line + help_offset - } else { - x_start_left_help_line - }; + // Where to start drawing `AlignLine::Left` rows + let x_start_left = + start_drawing_left_align_at_x + if show_logo { *HELP_X_LOGO_OFFSET } else { 0 }; - let x_start_center = - x_center - line.width() as u16 / 2 + if show_logo { help_offset } else { 0 }; + // Where to start drawing `AlignLine::Center` rows + let x_start_center = x_view_center - line.width() as u16 / 2 + + if show_logo { *HELP_X_LOGO_OFFSET } else { 0 }; let x = match align { - Align::Left => x_start_left, - Align::Center => x_start_center, + Left => x_start_left, + Center => x_start_center, }; - let y = y_start + lines_drawn as u16; + let y = start_drawing_at_y + lines_drawn as u16; + surface.set_spans(x, y, line, line.width() as u16); if show_logo { From 211a8d9e9694a2a3a04ca4d94375eeff2b99dc19 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 21:21:48 +0100 Subject: [PATCH 23/26] refactor: rename variable --- helix-term/src/ui/editor.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 9aef68066..24e1e8da2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -289,25 +289,27 @@ impl EditorView { for (lines_drawn, (line, align)) in help_lines.iter().enumerate() { // Where to start drawing `AlignLine::Left` rows - let x_start_left = + let x_start_left_help = start_drawing_left_align_at_x + if show_logo { *HELP_X_LOGO_OFFSET } else { 0 }; // Where to start drawing `AlignLine::Center` rows - let x_start_center = x_view_center - line.width() as u16 / 2 + let x_start_center_help = x_view_center - line.width() as u16 / 2 + if show_logo { *HELP_X_LOGO_OFFSET } else { 0 }; - let x = match align { - Left => x_start_left, - Center => x_start_center, + // Where to start drawing rows for the "help" section + // Includes tips about commands. Excludes the logo. + let x_start_help = match align { + Left => x_start_left_help, + Center => x_start_center_help, }; let y = start_drawing_at_y + lines_drawn as u16; - surface.set_spans(x, y, line, line.width() as u16); + surface.set_spans(x_start_help, y, line, line.width() as u16); if show_logo { surface.set_spans( - x_start_left - LOGO_LEFT_PADDING - *LOGO_WIDTH, + x_start_left_help - LOGO_LEFT_PADDING - *LOGO_WIDTH, y, &logo[lines_drawn], *LOGO_WIDTH, From 99dee0b5ac3ca4db732e7a266f6c99d65294c32a Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 21:23:17 +0100 Subject: [PATCH 24/26] chore: add comments --- helix-term/src/ui/editor.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 24e1e8da2..885f32746 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -121,9 +121,13 @@ impl EditorView { '*' | ':' | '=' | '-' => Span::styled( ch.to_string(), Style::new().fg(match ch { + // Dark purple '*' => Color::Rgb(112, 107, 200), + // Dark blue ':' => Color::Rgb(132, 221, 234), + // Bright purple '=' => Color::Rgb(153, 123, 200), + // Bright blue '-' => Color::Rgb(85, 197, 228), _ => unreachable!(), }), From 89da5199069ed8167a40ff376dd0854a7b3151d1 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 21:29:51 +0100 Subject: [PATCH 25/26] chore: add comments --- helix-term/src/ui/editor.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 885f32746..f6acabf38 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -273,7 +273,9 @@ impl EditorView { let is_help_x_overflowing = are_any_left_aligned_lines_overflowing_x || are_any_center_aligned_lines_overflowing_x; - // we want `<` so it does not get drawn over the status line + // we want `>=` so it does not get drawn over the status line + // (essentially, it WON'T be marked as "overflowing" if the help + // fully fits vertically in the viewport without touching the status line) let is_help_y_overflowing = (help_lines.len() as u16) >= view.area.height; // Not enough space to render the help text even without the logo. Render nothing. @@ -291,6 +293,7 @@ impl EditorView { // If we get here we know that there IS enough space to show just the help let show_logo = width_of_help_with_logo <= view.area.width; + // Each "help" line is effectively "chained" with a line of the logo (if present). for (lines_drawn, (line, align)) in help_lines.iter().enumerate() { // Where to start drawing `AlignLine::Left` rows let x_start_left_help = @@ -309,9 +312,11 @@ impl EditorView { let y = start_drawing_at_y + lines_drawn as u16; + // Draw a single line of the help text surface.set_spans(x_start_help, y, line, line.width() as u16); if show_logo { + // Draw a single line of the logo surface.set_spans( x_start_left_help - LOGO_LEFT_PADDING - *LOGO_WIDTH, y, From c24ee4fe2dcd4a2d0dfbaa8680934c8ef2e55623 Mon Sep 17 00:00:00 2001 From: Nik Revenco <154856872+NikitaRevenco@users.noreply.github.com> Date: Wed, 21 May 2025 21:47:36 +0100 Subject: [PATCH 26/26] chore: add debug assert --- helix-term/src/ui/editor.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f6acabf38..788ab3d04 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -237,6 +237,12 @@ impl EditorView { empty_line(), ]; + debug_assert!( + raw_help_lines.len() >= LOGO_STR.lines().count(), + "help lines get chained with lines of logo. if there are not \ + enough help lines, logo will be cut off. add `empty_line()`s if necessary" + ); + let mut help_lines = Vec::with_capacity(raw_help_lines.len()); let mut len_of_longest_left_align = 0; let mut len_of_longest_center_align = 0;