Skip to content

Commit 916bba8

Browse files
committed
Add support for changing every ui elements color based on mode
This commit adds a generic way of changing the color based on mode from the theme struct. This saves us from having to implement a match statement under every render function and makes the code rather readable whilst adding a lot more customisation to the user. All theme options now support adding ".insert", ".select" and ".normal" to the key, and will apply those color/style choices directly. Specifying the theme key without those suffixes will be used as a fallback. So current themes should still work as expected.
1 parent d37dc6c commit 916bba8

File tree

20 files changed

+291
-162
lines changed

20 files changed

+291
-162
lines changed

helix-term/src/commands.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2480,9 +2480,12 @@ fn global_search(cx: &mut Context) {
24802480
let config = GlobalSearchConfig {
24812481
smart_case: config.search.smart_case,
24822482
file_picker_config: config.file_picker.clone(),
2483-
directory_style: cx.editor.theme.get("ui.text.directory"),
2484-
number_style: cx.editor.theme.get("constant.numeric.integer"),
2485-
colon_style: cx.editor.theme.get("punctuation"),
2483+
directory_style: cx.editor.theme.get(cx.editor.mode, "ui.text.directory"),
2484+
number_style: cx
2485+
.editor
2486+
.theme
2487+
.get(cx.editor.mode, "constant.numeric.integer"),
2488+
colon_style: cx.editor.theme.get(cx.editor.mode, "punctuation"),
24862489
};
24872490

24882491
let columns = [
@@ -3348,11 +3351,11 @@ fn changed_file_picker(cx: &mut Context) {
33483351
return;
33493352
}
33503353

3351-
let added = cx.editor.theme.get("diff.plus");
3352-
let modified = cx.editor.theme.get("diff.delta");
3353-
let conflict = cx.editor.theme.get("diff.delta.conflict");
3354-
let deleted = cx.editor.theme.get("diff.minus");
3355-
let renamed = cx.editor.theme.get("diff.delta.moved");
3354+
let added = cx.editor.theme.get(cx.editor.mode, "diff.plus");
3355+
let modified = cx.editor.theme.get(cx.editor.mode, "diff.delta");
3356+
let conflict = cx.editor.theme.get(cx.editor.mode, "diff.delta.conflict");
3357+
let deleted = cx.editor.theme.get(cx.editor.mode, "diff.minus");
3358+
let renamed = cx.editor.theme.get(cx.editor.mode, "diff.delta.moved");
33563359

33573360
let columns = [
33583361
PickerColumn::new("change", |change: &FileChange, data: &FileChangeData| {

helix-term/src/commands/dap.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -549,9 +549,9 @@ pub fn dap_variables(cx: &mut Context) {
549549
let mut variables = Vec::new();
550550

551551
let theme = &cx.editor.theme;
552-
let scope_style = theme.get("ui.linenr.selected");
553-
let type_style = theme.get("ui.text");
554-
let text_style = theme.get("ui.text.focus");
552+
let scope_style = theme.get(cx.editor.mode, "ui.linenr.selected");
553+
let type_style = theme.get(cx.editor.mode, "ui.text");
554+
let text_style = theme.get(cx.editor.mode, "ui.text.focus");
555555

556556
for scope in scopes.iter() {
557557
// use helix_view::graphics::Style;

helix-term/src/commands/lsp.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,10 @@ fn diag_picker(
239239
});
240240

241241
let styles = DiagnosticStyles {
242-
hint: cx.editor.theme.get("hint"),
243-
info: cx.editor.theme.get("info"),
244-
warning: cx.editor.theme.get("warning"),
245-
error: cx.editor.theme.get("error"),
242+
hint: cx.editor.theme.get(cx.editor.mode, "hint"),
243+
info: cx.editor.theme.get(cx.editor.mode, "info"),
244+
warning: cx.editor.theme.get(cx.editor.mode, "warning"),
245+
error: cx.editor.theme.get(cx.editor.mode, "error"),
246246
};
247247

248248
let mut columns = vec![

helix-term/src/ui/completion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl Completion {
133133
let preview_completion_insert = editor.config().preview_completion_insert;
134134
let replace_mode = editor.config().completion_replace;
135135

136-
let dir_style = editor.theme.get("ui.text.directory");
136+
let dir_style = editor.theme.get(editor.mode, "ui.text.directory");
137137

138138
// Then create the menu
139139
let menu = Menu::new(items, dir_style, move |editor: &mut Editor, item, event| {
@@ -568,7 +568,7 @@ impl Component for Completion {
568568
};
569569

570570
// clear area
571-
let background = cx.editor.theme.get("ui.popup");
571+
let background = cx.editor.theme.get(cx.editor.mode, "ui.popup");
572572
surface.clear_with(doc_area, background);
573573

574574
if cx.editor.popup_border() {

helix-term/src/ui/document.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use helix_view::editor::{WhitespaceConfig, WhitespaceRenderValue};
1111
use helix_view::graphics::Rect;
1212
use helix_view::theme::Style;
1313
use helix_view::view::ViewPosition;
14-
use helix_view::{Document, Theme};
14+
use helix_view::{Document, Editor, Theme};
1515
use tui::buffer::Buffer as Surface;
1616

1717
use crate::ui::text_decorations::DecorationManager;
@@ -29,6 +29,7 @@ pub struct LinePos {
2929

3030
#[allow(clippy::too_many_arguments)]
3131
pub fn render_document(
32+
editor: &Editor,
3233
surface: &mut Surface,
3334
viewport: Rect,
3435
doc: &Document,
@@ -40,6 +41,7 @@ pub fn render_document(
4041
decorations: DecorationManager,
4142
) {
4243
let mut renderer = TextRenderer::new(
44+
editor,
4345
surface,
4446
doc,
4547
theme,
@@ -199,6 +201,7 @@ pub struct GraphemeStyle {
199201

200202
impl<'a> TextRenderer<'a> {
201203
pub fn new(
204+
editor: &Editor,
202205
surface: &'a mut Surface,
203206
doc: &Document,
204207
theme: &Theme,
@@ -242,7 +245,7 @@ impl<'a> TextRenderer<'a> {
242245
" ".to_owned()
243246
};
244247

245-
let text_style = theme.get("ui.text");
248+
let text_style = theme.get(editor.mode, "ui.text");
246249

247250
let indent_width = doc.indent_style.indent_width(tab_width) as u16;
248251

@@ -255,15 +258,15 @@ impl<'a> TextRenderer<'a> {
255258
space,
256259
tab,
257260
virtual_tab,
258-
whitespace_style: theme.get("ui.virtual.whitespace"),
261+
whitespace_style: theme.get(editor.mode, "ui.virtual.whitespace"),
259262
indent_width,
260263
starting_indent: offset.col / indent_width as usize
261264
+ (offset.col % indent_width as usize != 0) as usize
262265
+ editor_config.indent_guides.skip_levels as usize,
263266
indent_guide_style: text_style.patch(
264267
theme
265-
.try_get("ui.virtual.indent-guide")
266-
.unwrap_or_else(|| theme.get("ui.virtual.whitespace")),
268+
.try_get(editor.mode, "ui.virtual.indent-guide")
269+
.unwrap_or_else(|| theme.get(editor.mode, "ui.virtual.whitespace")),
267270
),
268271
text_style,
269272
draw_indent_guides: editor_config.indent_guides.render,

helix-term/src/ui/editor.rs

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,25 @@ impl EditorView {
9595
let mut decorations = DecorationManager::default();
9696

9797
if is_focused && config.cursorline {
98-
decorations.add_decoration(Self::cursorline(doc, view, theme));
98+
decorations.add_decoration(Self::cursorline(editor, doc, view, theme));
9999
}
100100

101101
if is_focused && config.cursorcolumn {
102-
Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
102+
Self::highlight_cursorcolumn(
103+
editor,
104+
doc,
105+
view,
106+
surface,
107+
theme,
108+
inner,
109+
&text_annotations,
110+
);
103111
}
104112

105113
// Set DAP highlights, if needed.
106114
if let Some(frame) = editor.current_stack_frame() {
107115
let dap_line = frame.line.saturating_sub(1);
108-
let style = theme.get("ui.highlight.frameline");
116+
let style = theme.get(editor.mode, "ui.highlight.frameline");
109117
let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
110118
if pos.doc_line != dap_line {
111119
return;
@@ -192,11 +200,13 @@ impl EditorView {
192200
decorations.add_decoration(InlineDiagnostics::new(
193201
doc,
194202
theme,
203+
editor.mode,
195204
primary_cursor,
196205
inline_diagnostic_config,
197206
config.end_of_line_diagnostics,
198207
));
199208
render_document(
209+
editor,
200210
surface,
201211
inner,
202212
doc,
@@ -211,7 +221,7 @@ impl EditorView {
211221
// if we're not at the edge of the screen, draw a right border
212222
if viewport.right() != view.area.right() {
213223
let x = area.right();
214-
let border_style = theme.get("ui.window");
224+
let border_style = theme.get(editor.mode, "ui.window");
215225
for y in area.top()..area.bottom() {
216226
surface[(x, y)]
217227
.set_symbol(tui::symbols::line::VERTICAL)
@@ -223,7 +233,7 @@ impl EditorView {
223233
if config.inline_diagnostics.disabled()
224234
&& config.end_of_line_diagnostics == DiagnosticFilter::Disable
225235
{
226-
Self::render_diagnostics(doc, view, inner, surface, theme);
236+
Self::render_diagnostics(editor, doc, view, inner, surface, theme);
227237
}
228238

229239
let statusline_area = view
@@ -247,7 +257,7 @@ impl EditorView {
247257
) {
248258
let editor_rulers = &editor.config().rulers;
249259
let ruler_theme = theme
250-
.try_get("ui.virtual.ruler")
260+
.try_get(editor.mode, "ui.virtual.ruler")
251261
.unwrap_or_else(|| Style::default().bg(Color::Red));
252262

253263
let rulers = doc
@@ -598,19 +608,19 @@ impl EditorView {
598608
viewport,
599609
editor
600610
.theme
601-
.try_get("ui.bufferline.background")
602-
.unwrap_or_else(|| editor.theme.get("ui.statusline")),
611+
.try_get(editor.mode, "ui.bufferline.background")
612+
.unwrap_or_else(|| editor.theme.get(editor.mode, "ui.statusline")),
603613
);
604614

605615
let bufferline_active = editor
606616
.theme
607-
.try_get("ui.bufferline.active")
608-
.unwrap_or_else(|| editor.theme.get("ui.statusline.active"));
617+
.try_get(editor.mode, "ui.bufferline.active")
618+
.unwrap_or_else(|| editor.theme.get(editor.mode, "ui.statusline.active"));
609619

610620
let bufferline_inactive = editor
611621
.theme
612-
.try_get("ui.bufferline")
613-
.unwrap_or_else(|| editor.theme.get("ui.statusline.inactive"));
622+
.try_get(editor.mode, "ui.bufferline")
623+
.unwrap_or_else(|| editor.theme.get(editor.mode, "ui.statusline.inactive"));
614624

615625
let mut x = viewport.x;
616626
let current_doc = view!(editor).doc;
@@ -662,10 +672,10 @@ impl EditorView {
662672

663673
let mut offset = 0;
664674

665-
let gutter_style = theme.get("ui.gutter");
666-
let gutter_selected_style = theme.get("ui.gutter.selected");
667-
let gutter_style_virtual = theme.get("ui.gutter.virtual");
668-
let gutter_selected_style_virtual = theme.get("ui.gutter.selected.virtual");
675+
let gutter_style = theme.get(editor.mode, "ui.gutter");
676+
let gutter_selected_style = theme.get(editor.mode, "ui.gutter.selected");
677+
let gutter_style_virtual = theme.get(editor.mode, "ui.gutter.virtual");
678+
let gutter_selected_style_virtual = theme.get(editor.mode, "ui.gutter.selected.virtual");
669679

670680
for gutter_type in view.gutters() {
671681
let mut gutter = gutter_type.style(editor, doc, view, theme, is_focused);
@@ -710,6 +720,7 @@ impl EditorView {
710720
}
711721

712722
pub fn render_diagnostics(
723+
editor: &Editor,
713724
doc: &Document,
714725
view: &View,
715726
viewport: Rect,
@@ -732,13 +743,13 @@ impl EditorView {
732743
diagnostic.range.start <= cursor && diagnostic.range.end >= cursor
733744
});
734745

735-
let warning = theme.get("warning");
736-
let error = theme.get("error");
737-
let info = theme.get("info");
738-
let hint = theme.get("hint");
746+
let warning = theme.get(editor.mode, "warning");
747+
let error = theme.get(editor.mode, "error");
748+
let info = theme.get(editor.mode, "info");
749+
let hint = theme.get(editor.mode, "hint");
739750

740751
let mut lines = Vec::new();
741-
let background_style = theme.get("ui.background");
752+
let background_style = theme.get(editor.mode, "ui.background");
742753
for diagnostic in diagnostics {
743754
let style = Style::reset()
744755
.patch(background_style)
@@ -773,7 +784,12 @@ impl EditorView {
773784
}
774785

775786
/// Apply the highlighting on the lines where a cursor is active
776-
pub fn cursorline(doc: &Document, view: &View, theme: &Theme) -> impl Decoration {
787+
pub fn cursorline(
788+
editor: &Editor,
789+
doc: &Document,
790+
view: &View,
791+
theme: &Theme,
792+
) -> impl Decoration {
777793
let text = doc.text().slice(..);
778794
// TODO only highlight the visual line that contains the cursor instead of the full visual line
779795
let primary_line = doc.selection(view.id).primary().cursor_line(text);
@@ -790,8 +806,8 @@ impl EditorView {
790806
.map(|range| range.cursor_line(text))
791807
.collect();
792808

793-
let primary_style = theme.get("ui.cursorline.primary");
794-
let secondary_style = theme.get("ui.cursorline.secondary");
809+
let primary_style = theme.get(editor.mode, "ui.cursorline.primary");
810+
let secondary_style = theme.get(editor.mode, "ui.cursorline.secondary");
795811
let viewport = view.area;
796812

797813
move |renderer: &mut TextRenderer, pos: LinePos| {
@@ -806,6 +822,7 @@ impl EditorView {
806822

807823
/// Apply the highlighting on the columns where a cursor is active
808824
pub fn highlight_cursorcolumn(
825+
editor: &Editor,
809826
doc: &Document,
810827
view: &View,
811828
surface: &mut Surface,
@@ -818,13 +835,13 @@ impl EditorView {
818835
// Manual fallback behaviour:
819836
// ui.cursorcolumn.{p/s} -> ui.cursorcolumn -> ui.cursorline.{p/s}
820837
let primary_style = theme
821-
.try_get_exact("ui.cursorcolumn.primary")
822-
.or_else(|| theme.try_get_exact("ui.cursorcolumn"))
823-
.unwrap_or_else(|| theme.get("ui.cursorline.primary"));
838+
.try_get_exact(editor.mode, "ui.cursorcolumn.primary")
839+
.or_else(|| theme.try_get_exact(editor.mode, "ui.cursorcolumn"))
840+
.unwrap_or_else(|| theme.get(editor.mode, "ui.cursorline.primary"));
824841
let secondary_style = theme
825-
.try_get_exact("ui.cursorcolumn.secondary")
826-
.or_else(|| theme.try_get_exact("ui.cursorcolumn"))
827-
.unwrap_or_else(|| theme.get("ui.cursorline.secondary"));
842+
.try_get_exact(editor.mode, "ui.cursorcolumn.secondary")
843+
.or_else(|| theme.try_get_exact(editor.mode, "ui.cursorcolumn"))
844+
.unwrap_or_else(|| theme.get(editor.mode, "ui.cursorline.secondary"));
828845

829846
let inner_area = view.inner_area(doc);
830847

@@ -1524,7 +1541,7 @@ impl Component for EditorView {
15241541

15251542
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
15261543
// clear with background color
1527-
surface.set_style(area, cx.editor.theme.get("ui.background"));
1544+
surface.set_style(area, cx.editor.theme.get(cx.editor.mode, "ui.background"));
15281545
let config = cx.editor.config();
15291546

15301547
// check if bufferline should be rendered
@@ -1568,9 +1585,9 @@ impl Component for EditorView {
15681585
status_msg_width = status_msg.width();
15691586
use helix_view::editor::Severity;
15701587
let style = if *severity == Severity::Error {
1571-
cx.editor.theme.get("error")
1588+
cx.editor.theme.get(cx.editor.mode, "error")
15721589
} else {
1573-
cx.editor.theme.get("ui.text")
1590+
cx.editor.theme.get(cx.editor.mode, "ui.text")
15741591
};
15751592

15761593
surface.set_string(
@@ -1592,7 +1609,7 @@ impl Component for EditorView {
15921609
for key in &self.pseudo_pending {
15931610
disp.push_str(&key.key_sequence_format());
15941611
}
1595-
let style = cx.editor.theme.get("ui.text");
1612+
let style = cx.editor.theme.get(cx.editor.mode, "ui.text");
15961613
let macro_width = if cx.editor.macro_recording.is_some() {
15971614
3
15981615
} else {

helix-term/src/ui/info.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use tui::widgets::{Block, Paragraph, Widget};
77

88
impl Component for Info {
99
fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
10-
let text_style = cx.editor.theme.get("ui.text.info");
11-
let popup_style = cx.editor.theme.get("ui.popup.info");
10+
let text_style = cx.editor.theme.get(cx.editor.mode, "ui.text.info");
11+
let popup_style = cx.editor.theme.get(cx.editor.mode, "ui.popup.info");
1212

1313
// Calculate the area of the terminal to modify. Because we want to
1414
// render at the bottom right, we use the viewport's width and height

helix-term/src/ui/lsp/hover.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ impl Component for Hover {
8080
// show header and border only when more than one results
8181
if let Some(header) = header {
8282
// header LSP Name
83-
let header = header.parse(Some(&cx.editor.theme));
83+
let header = header.parse(Some((&cx.editor.theme, cx.editor.mode)));
8484
let header = Paragraph::new(&header);
8585
header.render(area.with_height(HEADER_HEIGHT), surface);
8686

@@ -95,7 +95,7 @@ impl Component for Hover {
9595
}
9696

9797
// hover content
98-
let contents = contents.parse(Some(&cx.editor.theme));
98+
let contents = contents.parse(Some((&cx.editor.theme, cx.editor.mode)));
9999
let contents_area = area.clip_top(if self.has_header() {
100100
HEADER_HEIGHT + SEPARATOR_HEIGHT
101101
} else {

0 commit comments

Comments
 (0)