From a29d11bb7489da0c7c2e0068ea187a79e7f80fa0 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 | 124 ++++++++++++++------------- helix-term/src/ui/editor.rs | 48 ++++++++++- runtime/themes/catppuccin_mocha.toml | 6 ++ theme.toml | 5 ++ 4 files changed, 120 insertions(+), 63 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 41a3fe101497a..7192e17796a71 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -265,65 +265,69 @@ These scopes are used for theming the editor interface: - `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.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` | Cursor with primary selection | +| `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.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 16940e337ec0e..26d22ee8cba64 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -43,6 +43,8 @@ pub struct EditorView { pub(crate) last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, spinners: ProgressSpinners, + /// Tracks if the terminal window is focused by reaction to terminal focus events + terminal_focused: bool, } #[derive(Debug, Clone)] @@ -71,6 +73,7 @@ impl EditorView { last_insert: (commands::MappableCommand::normal_mode, Vec::new()), completion: None, spinners: ProgressSpinners::default(), + terminal_focused: true, } } @@ -151,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); @@ -394,6 +398,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); @@ -405,16 +410,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"), @@ -422,6 +439,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"), @@ -429,14 +452,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. @@ -1367,13 +1405,17 @@ impl Component for EditorView { Event::Mouse(event) => self.handle_mouse_event(event, &mut cx), Event::IdleTimeout => self.handle_idle_timeout(&mut cx), - Event::FocusGained => EventResult::Ignored(None), + Event::FocusGained => { + self.terminal_focused = true; + EventResult::Consumed(None) + } Event::FocusLost => { if context.editor.config().auto_save { if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); } } + self.terminal_focused = false; EventResult::Consumed(None) } } diff --git a/runtime/themes/catppuccin_mocha.toml b/runtime/themes/catppuccin_mocha.toml index 126613bc70b8a..e9f1172c3f2eb 100644 --- a/runtime/themes/catppuccin_mocha.toml +++ b/runtime/themes/catppuccin_mocha.toml @@ -90,9 +90,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.cursorline.primary" = { bg = "cursorline" } @@ -146,3 +149,6 @@ crust = "#11111b" # derived colors by blending existing palette colors cursorline = "#2a2b3c" secondary_cursor = "#b5a6a8" +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"