From a9d5df192a5f341dbbdc4b3faaeb339209d57968 Mon Sep 17 00:00:00 2001 From: Hendrik Norkowski Date: Fri, 21 Apr 2023 22:59:33 +0200 Subject: [PATCH] feat(ui): dimm block cursor and selections when Helix loses focus --- book/src/themes.md | 130 ++++++++++++++------------- helix-term/src/ui/editor.rs | 39 +++++++- runtime/themes/catppuccin_mocha.toml | 6 ++ theme.toml | 5 ++ 4 files changed, 114 insertions(+), 66 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 495b768566bc0..c506328e6ae76 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -269,69 +269,71 @@ These scopes are used for theming the editor interface: - `completion` - for completion doc popup UI - `hover` - for hover popup UI - -| Key | Notes | -| --- | --- | -| `ui.background` | | -| `ui.background.separator` | Picker separator below input line | -| `ui.cursor` | | -| `ui.cursor.normal` | | -| `ui.cursor.insert` | | -| `ui.cursor.select` | | -| `ui.cursor.match` | Matching bracket etc. | -| `ui.cursor.primary` | Cursor with primary selection | -| `ui.cursor.primary.normal` | | -| `ui.cursor.primary.insert` | | -| `ui.cursor.primary.select` | | -| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter | -| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter | -| `ui.gutter` | Gutter | -| `ui.gutter.selected` | Gutter for the line the cursor is on | -| `ui.highlight.frameline` | Line at which debugging execution is paused at | -| `ui.linenr` | Line numbers | -| `ui.linenr.selected` | Line number for the line the cursor is on | -| `ui.statusline` | Statusline | -| `ui.statusline.inactive` | Statusline (unfocused document) | -| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | -| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | -| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | -| `ui.statusline.separator` | Separator character in statusline | -| `ui.bufferline` | Style for the buffer line | -| `ui.bufferline.active` | Style for the active buffer in buffer line | -| `ui.bufferline.background` | Style for bufferline background | -| `ui.popup` | Documentation popups (e.g. Space + k) | -| `ui.popup.info` | Prompt for multiple key options | -| `ui.window` | Borderlines separating splits | -| `ui.help` | Description box for commands | -| `ui.text` | Command prompts, popup text, etc. | -| `ui.text.focus` | The currently selected line in the picker | -| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) | -| `ui.text.info` | The key: command text in `ui.popup.info` boxes | -| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) | -| `ui.virtual.whitespace` | Visible whitespace characters | -| `ui.virtual.indent-guide` | Vertical indent width guides | -| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds | -| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) | -| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) | -| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) | -| `ui.menu` | Code and command completion menus | -| `ui.menu.selected` | Selected autocomplete item | -| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | -| `ui.selection` | For selections in the editing area | -| `ui.selection.primary` | | -| `ui.highlight` | Highlighted lines in the picker preview | -| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) | -| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) | -| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) | -| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) | -| `warning` | Diagnostics warning (gutter) | -| `error` | Diagnostics error (gutter) | -| `info` | Diagnostics info (gutter) | -| `hint` | Diagnostics hint (gutter) | -| `diagnostic` | Diagnostics fallback style (editing area) | -| `diagnostic.hint` | Diagnostics hint (editing area) | -| `diagnostic.info` | Diagnostics info (editing area) | -| `diagnostic.warning` | Diagnostics warning (editing area) | -| `diagnostic.error` | Diagnostics error (editing area) | +| Key | Notes | +| --- | --- | +| `ui.background` | | +| `ui.background.separator` | Picker separator below input line | +| `ui.cursor` | | +| `ui.cursor.normal` | | +| `ui.cursor.insert` | | +| `ui.cursor.select` | | +| `ui.cursor.match` | Matching bracket etc. | +| `ui.cursor.primary.unfocused` | Cursor with primary selection when Helix is out of focus | +| `ui.cursor.primary.normal` | | +| `ui.cursor.primary.normal.unfocused` | | +| `ui.cursor.primary.insert` | | +| `ui.cursor.primary.insert.unfocused` | | +| `ui.cursor.primary.select` | | +| `ui.cursor.primary.select.unfocused` | | +| `ui.debug.breakpoint` | Breakpoint indicator, found in the gutter | +| `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter | +| `ui.gutter` | Gutter | +| `ui.gutter.selected` | Gutter for the line the cursor is on | +| `ui.highlight.frameline` | Line at which debugging execution is paused at | +| `ui.linenr` | Line numbers | +| `ui.linenr.selected` | Line number for the line the cursor is on | +| `ui.statusline` | Statusline | +| `ui.statusline.inactive` | Statusline (unfocused document) | +| `ui.statusline.normal` | Statusline mode during normal mode ([only if `editor.color-modes` is enabled][editor-section]) | +| `ui.statusline.insert` | Statusline mode during insert mode ([only if `editor.color-modes` is enabled][editor-section]) | +| `ui.statusline.select` | Statusline mode during select mode ([only if `editor.color-modes` is enabled][editor-section]) | +| `ui.statusline.separator` | Separator character in statusline | +| `ui.bufferline` | Style for the buffer line | +| `ui.bufferline.active` | Style for the active buffer in buffer line | +| `ui.bufferline.background` | Style for bufferline background | +| `ui.popup` | Documentation popups (e.g. Space + k) | +| `ui.popup.info` | Prompt for multiple key options | +| `ui.window` | Borderlines separating splits | +| `ui.help` | Description box for commands | +| `ui.text` | Command prompts, popup text, etc. | +| `ui.text.focus` | The currently selected line in the picker | +| `ui.text.inactive` | Same as `ui.text` but when the text is inactive (e.g. suggestions) | +| `ui.text.info` | The key: command text in `ui.popup.info` boxes | +| `ui.virtual.ruler` | Ruler columns (see the [`editor.rulers` config][editor-section]) | +| `ui.virtual.whitespace` | Visible whitespace characters | +| `ui.virtual.indent-guide` | Vertical indent width guides | +| `ui.virtual.inlay-hint` | Default style for inlay hints of all kinds | +| `ui.virtual.inlay-hint.parameter` | Style for inlay hints of kind `parameter` (LSPs are not required to set a kind) | +| `ui.virtual.inlay-hint.type` | Style for inlay hints of kind `type` (LSPs are not required to set a kind) | +| `ui.virtual.wrap` | Soft-wrap indicator (see the [`editor.soft-wrap` config][editor-section]) | +| `ui.menu` | Code and command completion menus | +| `ui.menu.selected` | Selected autocomplete item | +| `ui.menu.scroll` | `fg` sets thumb color, `bg` sets track color of scrollbar | +| `ui.selection` | For selections in the editing area | +| `ui.selection.primary` | | +| `ui.highlight` | Highlighted lines in the picker preview | +| `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) | +| `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) | +| `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) | +| `ui.cursorcolumn.secondary` | The columns of any other cursors ([if cursorcolumn is enabled][editor-section]) | +| `warning` | Diagnostics warning (gutter) | +| `error` | Diagnostics error (gutter) | +| `info` | Diagnostics info (gutter) | +| `hint` | Diagnostics hint (gutter) | +| `diagnostic` | Diagnostics fallback style (editing area) | +| `diagnostic.hint` | Diagnostics hint (editing area) | +| `diagnostic.info` | Diagnostics info (editing area) | +| `diagnostic.warning` | Diagnostics warning (editing area) | +| `diagnostic.error` | Diagnostics error (editing area) | [editor-section]: ./configuration.md#editor-section diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 31195a4e557a6..2f31725c16cc1 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -154,6 +154,7 @@ impl EditorView { view, theme, &config.cursor_shape, + self.terminal_focused, ), ); let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme); @@ -400,6 +401,7 @@ impl EditorView { view: &View, theme: &Theme, cursor_shape_config: &CursorShapeConfig, + is_terminal_focused: bool, ) -> Vec<(usize, std::ops::Range)> { let text = doc.text().slice(..); let selection = doc.selection(view.id); @@ -411,16 +413,28 @@ impl EditorView { let selection_scope = theme .find_scope_index_exact("ui.selection") .expect("could not find `ui.selection` scope in the theme!"); + let selection_unfocused_scope = theme + .find_scope_index_exact("ui.selection.unfocused") + .unwrap_or(selection_scope); let primary_selection_scope = theme .find_scope_index_exact("ui.selection.primary") .unwrap_or(selection_scope); + let primary_selection_unfocused_scope = theme + .find_scope_index_exact("ui.selection.primary.unfocused") + .unwrap_or(primary_selection_scope); let base_cursor_scope = theme .find_scope_index_exact("ui.cursor") .unwrap_or(selection_scope); + let base_cursor_unfocused_scope = theme + .find_scope_index_exact("ui.cursor.unfocused") + .unwrap_or(base_cursor_scope); let base_primary_cursor_scope = theme .find_scope_index("ui.cursor.primary") .unwrap_or(base_cursor_scope); + let base_primary_cursor_unfocused_scope = theme + .find_scope_index("ui.cursor.primary.unfocused") + .unwrap_or(base_primary_cursor_scope); let cursor_scope = match mode { Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"), @@ -428,6 +442,12 @@ impl EditorView { Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"), } .unwrap_or(base_cursor_scope); + let cursor_unfocused_scope = match mode { + Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert.unfocused"), + Mode::Select => theme.find_scope_index_exact("ui.cursor.select.unfocused"), + Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal.unfocused"), + } + .unwrap_or(base_cursor_unfocused_scope); let primary_cursor_scope = match mode { Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"), @@ -435,14 +455,29 @@ impl EditorView { Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"), } .unwrap_or(base_primary_cursor_scope); + let primary_cursor_unfocused_scope = match mode { + Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert.unfocused"), + Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select.unfocused"), + Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal.unfocused"), + } + .unwrap_or(base_primary_cursor_unfocused_scope); let mut spans: Vec<(usize, std::ops::Range)> = Vec::new(); for (i, range) in selection.iter().enumerate() { let selection_is_primary = i == primary_idx; let (cursor_scope, selection_scope) = if selection_is_primary { - (primary_cursor_scope, primary_selection_scope) - } else { + if is_terminal_focused { + (primary_cursor_scope, primary_selection_scope) + } else { + ( + primary_cursor_unfocused_scope, + primary_selection_unfocused_scope, + ) + } + } else if is_terminal_focused { (cursor_scope, selection_scope) + } else { + (cursor_unfocused_scope, selection_unfocused_scope) }; // Special-case: cursor at end of the rope. diff --git a/runtime/themes/catppuccin_mocha.toml b/runtime/themes/catppuccin_mocha.toml index 1ea57e9608aba..9bc8603b4cf8b 100644 --- a/runtime/themes/catppuccin_mocha.toml +++ b/runtime/themes/catppuccin_mocha.toml @@ -91,9 +91,12 @@ "ui.virtual.inlay-hint" = { fg = "surface1", bg = "mantle" } "ui.selection" = { bg = "surface1" } +"ui.selection.unfocused" = { bg = "surface1_dimmed" } "ui.cursor" = { fg = "base", bg = "secondary_cursor" } +"ui.cursor.unfocused" = { fg = "base", bg = "secondary_cursor_dimmed" } "ui.cursor.primary" = { fg = "base", bg = "rosewater" } +"ui.cursor.primary.unfocused" = { fg = "base", bg = "rosewater_dimmed" } "ui.cursor.match" = { fg = "peach", modifiers = ["bold"] } "ui.cursor.primary.normal" = { fg = "base", bg = "lavender" } @@ -157,3 +160,6 @@ cursorline = "#2a2b3c" secondary_cursor = "#b5a6a8" secondary_cursor_normal = "#878ec0" secondary_cursor_insert = "#7da87e" +secondary_cursor_dimmed = "#595455" +rosewater_dimmed = "#6b6265" +surface1_dimmed = "#3E3F4B" diff --git a/theme.toml b/theme.toml index dd1a5d889ac1d..feab01dfd6856 100644 --- a/theme.toml +++ b/theme.toml @@ -61,12 +61,16 @@ label = "honey" "ui.virtual.indent-guide" = { fg = "comet" } "ui.selection" = { bg = "#540099" } +"ui.selection.unfocused" = { bg = "selection_dimmed" } "ui.selection.primary" = { bg = "#540099" } +"ui.selection.primary.unfocused" = { bg = "delta" } # TODO: namespace ui.cursor as ui.selection.cursor? "ui.cursor.select" = { bg = "delta" } "ui.cursor.insert" = { bg = "white" } "ui.cursor.match" = { fg = "#212121", bg = "#6C6999" } "ui.cursor" = { modifiers = ["reversed"] } +"ui.cursor.unfocused" = { bg = "selection_dimmed" } +"ui.cursor.primary.unfocused" = { bg = "revolver" } "ui.cursorline.primary" = { bg = "bossanova" } "ui.highlight" = { bg = "bossanova" } "ui.highlight.frameline" = { bg = "#634450" } @@ -105,3 +109,4 @@ honey = "#efba5d" apricot = "#f47868" lightning = "#ffcd1c" delta = "#6F44F0" +selection_dimmed = "#49167A"