From 6be38642f4f352a44bc6aabc5f0af6a0d941eecc Mon Sep 17 00:00:00 2001 From: zoey Date: Wed, 14 May 2025 10:52:00 -0300 Subject: [PATCH 1/8] Add nyxvamp themes (#12185) --- runtime/themes/nyxvamp-obsidian.toml | 83 ++++++++++++++++++ runtime/themes/nyxvamp-radiance.toml | 124 +++++++++++++++++++++++++++ runtime/themes/nyxvamp-veil.toml | 83 ++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 runtime/themes/nyxvamp-obsidian.toml create mode 100644 runtime/themes/nyxvamp-radiance.toml create mode 100644 runtime/themes/nyxvamp-veil.toml diff --git a/runtime/themes/nyxvamp-obsidian.toml b/runtime/themes/nyxvamp-obsidian.toml new file mode 100644 index 000000000..356b85351 --- /dev/null +++ b/runtime/themes/nyxvamp-obsidian.toml @@ -0,0 +1,83 @@ +# nyxvamp - obsidian variant +# author: zoedsoupe + +inherits = "nyxvamp-radiance" + +# Override specific styles for obsidian variant +"function" = { fg = "function_fg", modifiers = ["bold"] } +"function.builtin" = { fg = "function_builtin_fg", modifiers = ["bold"] } +"ui.match_paren" = { fg = "match_paren_fg", bg = "match_paren_bg", modifiers = ["bold"] } + +# Palette overrides +[palette] +# Very dark theme colors +background = "#000A0F" # Very dark background +foreground = "#C0C0CE" # Slightly muted foreground +cursor_fg = "#0E0E10" +cursor_bg = "#F28FAD" # Pink cursor +cursorline = "#1E1E20" # Slightly lighter background +selection = "#2E2E30" # Dark gray selection +line_nr = "#5E5A76" # Dark gray line numbers +line_nr_selected = "#C0C0CE" # Foreground color for selected line number + +# Status line colors +status_fg = "#C0C0CE" +status_bg = "#1E1E20" +status_inactive_fg = "#5E5A76" +status_inactive_bg = "#0E0E10" + +# Menu colors +menu_fg = "#C0C0CE" +menu_bg = "#0E0E10" +menu_sel_fg = "#0E0E10" +menu_sel_bg = "#F28FAD" +menu_scroll_fg = "#5E5A76" +menu_scroll_bg = "#0E0E10" +popup_fg = "#C0C0CE" +popup_bg = "#0E0E10" + +# UI virtual +virtual_ruler_bg = "#191921" # Slightly lighter than background, close to comment color + +# Syntax highlighting +match_paren_fg = "#F28FAD" +match_paren_bg = "#0E0E10" +comment_fg = "#5E5A76" # Dark gray comments +string_fg = "#8FBF8F" # Muted green strings +string_special_fg = "#F28FAD" # Pink special strings +constant_fg = "#F28FAD" # Pink constants +constant_builtin_fg = "#F28FAD" +number_fg = "#D8A080" # Muted peach numbers +boolean_fg = "#F28FAD" +function_fg = "#7FAFD7" # Muted blue functions +function_builtin_fg = "#7FAFD7" +keyword_fg = "#F5C2E7" # Pink keywords +keyword_control_fg = "#F5C2E7" +operator_fg = "#C0C0CE" # Foreground color for operators +variable_fg = "#C0C0CE" +variable_builtin_fg = "#F28FAD" +type_fg = "#A0A0D0" # Muted lavender types +type_builtin_fg = "#A0A0D0" +attribute_fg = "#F5C2E7" +namespace_fg = "#A090C0" # Muted purple namespaces +punctuation_fg = "#C0C0CE" +symbol_fg = "#F28FAD" + +# Diagnostics +error_fg = "#D78284" # Muted red errors +warning_fg = "#D5B880" # Muted yellow warnings +info_fg = "#7A9CCC" # Muted blue info +hint_fg = "#7BB5A8" # Muted cyan hints + +# Diff colors +diff_add_fg = "#86BA75" # Muted green additions +diff_delete_fg = "#D78284" # Muted red deletions +diff_change_fg = "#D5B880" # Muted yellow changes + +# Markup colors +markup_heading_fg = "#F5C2E7" +markup_bold_fg = "#F5C2E7" +markup_italic_fg = "#F5C2E7" +markup_link_url_fg = "#7FAFD7" +markup_link_text_fg = "#F5C2E7" +markup_quote_fg = "#5E5A76" diff --git a/runtime/themes/nyxvamp-radiance.toml b/runtime/themes/nyxvamp-radiance.toml new file mode 100644 index 000000000..e2d085f27 --- /dev/null +++ b/runtime/themes/nyxvamp-radiance.toml @@ -0,0 +1,124 @@ +# nyxvamp - radiance variant +# author: + +# UI Elements +"ui.background" = { bg = "background", fg = "foreground" } +"ui.text" = { fg = "foreground" } +"ui.cursor" = { fg = "cursor_fg", bg = "cursor_bg" } +"ui.cursorline.primary" = { bg = "cursorline" } +"ui.selection" = { bg = "selection" } +"ui.linenr" = { fg = "line_nr" } +"ui.linenr.selected" = { fg = "line_nr_selected", modifiers = ["bold"] } +"ui.statusline" = { fg = "status_fg", bg = "status_bg" } +"ui.statusline.inactive" = { fg = "status_inactive_fg", bg = "status_inactive_bg" } +"ui.menu" = { fg = "menu_fg", bg = "menu_bg" } +"ui.menu.selected" = { fg = "menu_sel_fg", bg = "menu_sel_bg" } +"ui.menu.scroll" = { fg = "menu_scroll_fg", bg = "menu_scroll_bg" } +"ui.popup" = { fg = "popup_fg", bg = "popup_bg" } +"ui.match_paren" = { fg = "match_paren_fg", bg = "match_paren_bg", modifiers = ["bold"] } +"ui.virtual" = { fg = "comment_fg" } +"ui.virtual.ruler" = { bg = "virtual_ruler_bg" } + +# Syntax Highlighting +"comment" = { fg = "comment_fg", modifiers = ["italic"] } +"string" = { fg = "string_fg" } +"string.special" = { fg = "string_special_fg" } +"constant" = { fg = "constant_fg" } +"constant.builtin" = { fg = "constant_builtin_fg", modifiers = ["bold"] } +"number" = { fg = "number_fg" } +"boolean" = { fg = "boolean_fg" } +"function" = { fg = "function_fg", modifiers = ["bold"] } # Added bold for better distinction +"function.builtin" = { fg = "function_builtin_fg", modifiers = ["bold"] } +"keyword" = { fg = "keyword_fg", modifiers = ["bold"] } +"keyword.control" = { fg = "keyword_control_fg", modifiers = ["bold"] } +"operator" = { fg = "operator_fg" } +"variable" = { fg = "variable_fg" } +"variable.builtin" = { fg = "variable_builtin_fg", modifiers = ["italic"] } +"type" = { fg = "type_fg", modifiers = ["italic"] } +"type.builtin" = { fg = "type_builtin_fg", modifiers = ["italic"] } +"attribute" = { fg = "attribute_fg" } +"namespace" = { fg = "namespace_fg" } +"punctuation" = { fg = "punctuation_fg" } +"symbol" = { fg = "symbol_fg", modifiers = ["italic"] } + +# Diagnostics +"diagnostic.error" = { fg = "error_fg", modifiers = ["bold"] } +"diagnostic.warning" = { fg = "warning_fg", modifiers = ["bold"] } +"diagnostic.info" = { fg = "info_fg", modifiers = ["bold"] } +"diagnostic.hint" = { fg = "hint_fg", modifiers = ["bold"] } + +# Diff +"diff.plus" = { fg = "diff_add_fg" } +"diff.minus" = { fg = "diff_delete_fg" } +"diff.delta" = { fg = "diff_change_fg" } + +# Markup +"markup.heading" = { fg = "markup_heading_fg", modifiers = ["bold"] } +"markup.bold" = { fg = "markup_bold_fg", modifiers = ["bold"] } +"markup.italic" = { fg = "markup_italic_fg", modifiers = ["italic"] } +"markup.link.url" = { fg = "markup_link_url_fg", modifiers = ["underlined"] } +"markup.link.text" = { fg = "markup_link_text_fg" } +"markup.quote" = { fg = "markup_quote_fg", modifiers = ["italic"] } + +# Palette +[palette] +background = "#F7F7FF" # Off-white background (from your visual identity) +foreground = "#1E1E2E" # Deep navy foreground (from your visual identity) +cursor_fg = "#F7F7FF" # Off-white cursor text +cursor_bg = "#9655FF" # Deep purple cursor (from your visual identity) +cursorline = "#E8E8F0" # Slightly darker than background +selection = "#FEF5BC" # Soft yellow selection (from your visual identity) +line_nr = "#6E6A86" # Medium gray for line numbers +line_nr_selected = "#1E1E2E" # Deep navy for selected line number +status_fg = "#1E1E2E" # Deep navy +status_bg = "#E8E8F0" # Light gray +status_inactive_fg = "#6E6A86" # Medium gray +status_inactive_bg = "#F7F7FF" # Off-white +menu_fg = "#1E1E2E" # Deep navy +menu_bg = "#E8E8F0" # Light gray +menu_sel_fg = "#F7F7FF" # Off-white +menu_sel_bg = "#FEF5BC" # Soft yellow selection +menu_scroll_fg = "#6E6A86" # Medium gray +menu_scroll_bg = "#E8E8F0" # Light gray +popup_fg = "#1E1E2E" # Deep navy +popup_bg = "#E8E8F0" # Light gray +match_paren_fg = "#1E1E2E" # Deep navy +match_paren_bg = "#FEF5BC" # Soft yellow +virtual_ruler_bg = "#EAEAF0" # Slightly lighter than comment color background + +comment_fg = "#6E6A86" # Medium gray comments +string_fg = "#FEF5BC" # Soft yellow strings (from your visual identity) +string_special_fg = "#F28FAD" # Pink special strings +constant_fg = "#F28FAD" # Pink constants +constant_builtin_fg = "#F28FAD" # Pink built-in constants +number_fg = "#F8BD96" # Peach numbers +boolean_fg = "#F28FAD" # Pink booleans +function_fg = "#005F87" # Dark blue functions (more distinguishable) +function_builtin_fg = "#005F87" # Dark blue built-in functions +keyword_fg = "#9655FF" # Deep purple keywords +keyword_control_fg = "#9655FF" # Deep purple control keywords +operator_fg = "#6E6A86" # Medium gray operators (adjusted for better contrast) +variable_fg = "#1E1E2E" # Deep navy variables +variable_builtin_fg = "#F28FAD" # Pink built-in variables +type_fg = "#C9CBFF" # Lavender types +type_builtin_fg = "#C9CBFF" # Lavender built-in types +attribute_fg = "#9655FF" # Deep purple attributes +namespace_fg = "#9655FF" # Deep purple namespaces +punctuation_fg = "#1E1E2E" # Deep navy punctuation +symbol_fg = "#F28FAD" # Pink symbols (e.g., Elixir atoms) + +error_fg = "#E78284" # Soft red errors +warning_fg = "#E5C890" # Soft yellow warnings +info_fg = "#8CAAEE" # Soft blue info +hint_fg = "#8BD5CA" # Soft cyan hints + +diff_add_fg = "#A6DA95" # Soft green additions +diff_delete_fg = "#E78284" # Soft red deletions +diff_change_fg = "#E5C890" # Soft yellow changes + +markup_heading_fg = "#9655FF" # Deep purple headings +markup_bold_fg = "#1E1E2E" # Deep navy bold text +markup_italic_fg = "#1E1E2E" # Deep navy italic text +markup_link_url_fg = "#005F87" # Dark blue links +markup_link_text_fg = "#9655FF" # Deep purple link text +markup_quote_fg = "#6E6A86" # Gray quotes diff --git a/runtime/themes/nyxvamp-veil.toml b/runtime/themes/nyxvamp-veil.toml new file mode 100644 index 000000000..60d5d7d68 --- /dev/null +++ b/runtime/themes/nyxvamp-veil.toml @@ -0,0 +1,83 @@ +# nyxvamp: veil variant +# author: zoedsoupe + +inherits = "nyxvamp-radiance" + +# Override specific styles for veil variant +"function" = { fg = "function_fg", modifiers = ["bold"] } +"function.builtin" = { fg = "function_builtin_fg", modifiers = ["bold"] } +"ui.match_paren" = { fg = "match_paren_fg", bg = "match_paren_bg", modifiers = ["bold"] } + +# Palette overrides +[palette] +# Dark theme colors - only override what's different from the base theme +background = "#1E1E2E" # Dark purple background +foreground = "#D9E0EE" # Light lavender foreground +cursor_fg = "#1E1E2E" +cursor_bg = "#F5C2E7" # Soft pink cursor +cursorline = "#2E2E3E" # Slightly lighter background +selection = "#494D64" # Grayish selection +line_nr = "#6E6A86" # Medium gray for line numbers +line_nr_selected = "#D9E0EE" # Foreground color for selected line number + +# Status line colors +status_fg = "#D9E0EE" +status_bg = "#302D41" +status_inactive_fg = "#6E6A86" +status_inactive_bg = "#1E1E2E" + +# Menu colors +menu_fg = "#D9E0EE" +menu_bg = "#1E1E2E" +menu_sel_fg = "#1E1E2E" +menu_sel_bg = "#F5C2E7" +menu_scroll_fg = "#6E6A86" +menu_scroll_bg = "#1E1E2E" +popup_fg = "#D9E0EE" +popup_bg = "#1E1E2E" + +# UI virtual +virtual_ruler_bg = "#2A2A3C" # Slightly lighter than background, close to comment color + +# Syntax highlighting +match_paren_fg = "#F28FAD" +match_paren_bg = "#1E1E2E" +comment_fg = "#6E6A86" # Gray comments +string_fg = "#ABE9B3" # Green strings +string_special_fg = "#F28FAD" # Pink special strings +constant_fg = "#F28FAD" # Pink constants +constant_builtin_fg = "#F28FAD" # Pink built-in constants +number_fg = "#F8BD96" # Peach numbers +boolean_fg = "#F28FAD" # Pink booleans +function_fg = "#96CDFB" # Blue functions +function_builtin_fg = "#96CDFB" # Blue built-in functions +keyword_fg = "#F5C2E7" # Pink keywords +keyword_control_fg = "#F5C2E7" # Pink control keywords +operator_fg = "#D9E0EE" # Foreground color for operators +variable_fg = "#D9E0EE" # Foreground color for variables +variable_builtin_fg = "#F28FAD" # Pink built-in variables +type_fg = "#C9CBFF" # Lavender types +type_builtin_fg = "#C9CBFF" # Lavender built-in types +attribute_fg = "#F5C2E7" # Pink attributes +namespace_fg = "#DDB6F2" # Purple namespaces +punctuation_fg = "#D9E0EE" # Foreground color for punctuation +symbol_fg = "#F28FAD" # Pink symbols (e.g., Elixir atoms) + +# Diagnostics +error_fg = "#E78284" # Red errors +warning_fg = "#E5C890" # Yellow warnings +info_fg = "#8CAAEE" # Blue info +hint_fg = "#8BD5CA" # Cyan hints + +# Diff colors +diff_add_fg = "#A6DA95" # Green additions +diff_delete_fg = "#E78284" # Red deletions +diff_change_fg = "#E5C890" # Yellow changes + +# Markup colors +markup_heading_fg = "#F5C2E7" # Pink headings +markup_bold_fg = "#F5C2E7" # Pink bold text +markup_italic_fg = "#F5C2E7" # Pink italic text +markup_link_url_fg = "#96CDFB" # Blue links +markup_link_text_fg = "#F5C2E7" # Pink link text +markup_quote_fg = "#6E6A86" # Gray quotes From 09bc67ad6d1bfa4368b065719e615d540f5f8dd7 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 14 May 2025 16:29:27 -0400 Subject: [PATCH 2/8] syntax: Fix language detection by shebang The switch to tree-house accidentally dropped some shebang parsing code from the loader's function to detect by shebang. This change restores that. The new code is slightly different as it's using a `regex_cursor` regex on the Rope rather than eagerly converting the text to a `Cow` and running a regular regex across it. --- helix-core/src/syntax.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index e232ee69b..86bb17b3d 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -312,7 +312,22 @@ impl Loader { } pub fn language_for_shebang(&self, text: RopeSlice) -> Option { - let shebang: Cow = text.into(); + // NOTE: this is slightly different than the one for injection markers in tree-house. It + // is anchored at the beginning. + use helix_stdx::rope::Regex; + use once_cell::sync::Lazy; + const SHEBANG: &str = r"^#!\s*(?:\S*[/\\](?:env\s+(?:\-\S+\s+)*)?)?([^\s\.\d]+)"; + static SHEBANG_REGEX: Lazy = Lazy::new(|| Regex::new(SHEBANG).unwrap()); + + let marker = SHEBANG_REGEX + .captures_iter(regex_cursor::Input::new(text)) + .map(|cap| text.byte_slice(cap.get_group(1).unwrap().range())) + .next()?; + self.language_for_shebang_marker(marker) + } + + fn language_for_shebang_marker(&self, marker: RopeSlice) -> Option { + let shebang: Cow = marker.into(); self.languages_by_shebang.get(shebang.as_ref()).copied() } @@ -351,7 +366,7 @@ impl LanguageLoader for Loader { let path: Cow = text.into(); self.language_for_filename(Path::new(path.as_ref())) } - InjectionLanguageMarker::Shebang(text) => self.language_for_shebang(text), + InjectionLanguageMarker::Shebang(text) => self.language_for_shebang_marker(text), } } From b0528bbac4397037f2c3c73a4e4857938a81b7d9 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 14 May 2025 17:16:55 -0400 Subject: [PATCH 3/8] statusline: Avoid showing only 'W' in workspace-diagnostics element If you configure a subset of severities to show in the workspace diagnostics statusline element you can see the 'W' (and surrounding space) without any diagnostic indicators. This is the case by default as it's configured to show warnings and errors only - if you have only hints in your workspace like if you open `application.rs` in Helix for example then you would see the 'W' and no indicators. This change checks that any of the configured diagnostics are non-zero and bails early if there are none. --- helix-term/src/ui/statusline.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 8fb07ebe7..117483e26 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -305,11 +305,21 @@ where }, ); - if hints > 0 || info > 0 || warnings > 0 || errors > 0 { - write(context, " W ".into(), None); + let sevs_to_show = &context.editor.config().statusline.workspace_diagnostics; + + // Avoid showing the " W " if no diagnostic counts will be shown. + if !sevs_to_show.iter().any(|sev| match sev { + Severity::Hint => hints != 0, + Severity::Info => info != 0, + Severity::Warning => warnings != 0, + Severity::Error => errors != 0, + }) { + return; } - for sev in &context.editor.config().statusline.workspace_diagnostics { + write(context, " W ".into(), None); + + for sev in sevs_to_show { match sev { Severity::Hint if hints > 0 => { write( From 702b1d0a0fc483935882cbd451a6898fa8f895eb Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 14 May 2025 18:27:46 -0400 Subject: [PATCH 4/8] statusline: Avoid unnecessary allocations for `&'static str` spans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the statusline `write` function only accepted a string and optional Style, so all rendering functions converted text to strings. Some elements write spans with `&'static str`s, however, making this unnecessary since `Span<'a>` is a wrapper around `Cow<'a, str>` and style, and a `Span<'static>` would outlive all required lifetimes. Moreover many elements could produce `Span<'a>` according to the lifetime in `RenderContext` in the future, potentially re-borrowing from the Editor borrow, so this change could save allocations for many file-type elements (with more future changes). This is not explored in this patch since the statusline functions currently add bespoke padding per-element, but with a future refactor to make spacing consistent this could be possible. This change refactors the write function to accept a `Span<'a>` and rewrites some related code to fit the codebase better (preferring `for` to iterator's `for_each` for example). The new code is more complicated lifetime-wise but avoids allocations in these cases: * spacer for mode name when a pane is not focused * LSP spinner frames * '●' (workspace) diagnostic indicators * " W " workspace diagnostic prefix * file modification indicators * read-only indicators * spacer element ... and opens the door to avoid allocation for file name elements in the future. --- helix-term/src/ui/statusline.rs | 291 ++++++++++++++------------------ 1 file changed, 126 insertions(+), 165 deletions(-) diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 117483e26..4d4b9f2fe 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use helix_core::{coords_at_pos, encoding, Position}; use helix_lsp::lsp::DiagnosticSeverity; use helix_view::document::DEFAULT_LANGUAGE_NAME; @@ -58,25 +60,16 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface surface.set_style(viewport.with_height(1), base_style); - let write_left = |context: &mut RenderContext, text, style| { - append(&mut context.parts.left, text, &base_style, style) - }; - let write_center = |context: &mut RenderContext, text, style| { - append(&mut context.parts.center, text, &base_style, style) - }; - let write_right = |context: &mut RenderContext, text, style| { - append(&mut context.parts.right, text, &base_style, style) - }; - // Left side of the status line. let config = context.editor.config(); - let element_ids = &config.statusline.left; - element_ids - .iter() - .map(|element_id| get_render_function(*element_id)) - .for_each(|render| render(context, write_left)); + for element_id in &config.statusline.left { + let render = get_render_function(*element_id); + (render)(context, |context, span| { + append(&mut context.parts.left, span, base_style) + }); + } surface.set_spans( viewport.x, @@ -87,11 +80,12 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface // Right side of the status line. - let element_ids = &config.statusline.right; - element_ids - .iter() - .map(|element_id| get_render_function(*element_id)) - .for_each(|render| render(context, write_right)); + for element_id in &config.statusline.right { + let render = get_render_function(*element_id); + (render)(context, |context, span| { + append(&mut context.parts.right, span, base_style) + }) + } surface.set_spans( viewport.x @@ -105,11 +99,12 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface // Center of the status line. - let element_ids = &config.statusline.center; - element_ids - .iter() - .map(|element_id| get_render_function(*element_id)) - .for_each(|render| render(context, write_center)); + for element_id in &config.statusline.center { + let render = get_render_function(*element_id); + (render)(context, |context, span| { + append(&mut context.parts.center, span, base_style) + }) + } // Width of the empty space between the left and center area and between the center and right area. let spacing = 1u16; @@ -126,16 +121,14 @@ pub fn render(context: &mut RenderContext, viewport: Rect, surface: &mut Surface ); } -fn append(buffer: &mut Spans, text: String, base_style: &Style, style: Option