From 55f4f6951571d44862d5f1bc3ad7094953b788b6 Mon Sep 17 00:00:00 2001 From: lazytanuki Date: Mon, 20 Jun 2022 17:07:32 +0200 Subject: fix: do not color health summary when stdout is piped (#2836) * fix: do not color health summary when stdout is piped * fix: use crossterm instead of is-terminal--- helix-term/src/health.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'helix-term/src') diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index bd74f478..f64e121d 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -1,4 +1,7 @@ -use crossterm::style::{Color, Print, Stylize}; +use crossterm::{ + style::{Color, Print, Stylize}, + tty::IsTty, +}; use helix_core::config::{default_syntax_loader, user_syntax_loader}; use helix_loader::grammar::load_runtime_file; use std::io::Write; @@ -106,17 +109,19 @@ pub fn languages_all() -> std::io::Result<()> { let terminal_cols = crossterm::terminal::size().map(|(c, _)| c).unwrap_or(80); let column_width = terminal_cols as usize / headings.len(); + let is_terminal = std::io::stdout().is_tty(); let column = |item: &str, color: Color| { - let data = format!( + let mut data = format!( "{:width$}", item.get(..column_width - 2) .map(|s| format!("{}…", s)) .unwrap_or_else(|| item.to_string()), width = column_width, - ) - .stylize() - .with(color); + ); + if is_terminal { + data = data.stylize().with(color).to_string(); + } // We can't directly use println!() because of // https://github.com/crossterm-rs/crossterm/issues/589 -- cgit v1.2.3-70-g09d2 From 8c64c3dfa3be911344ae0acaeee8018ffccde643 Mon Sep 17 00:00:00 2001 From: Mathis Brossier Date: Mon, 20 Jun 2022 20:41:34 +0200 Subject: mouse selection now uses character indexing (#2839) --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'helix-term/src') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f074d9f1..192fa180 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1046,7 +1046,7 @@ impl EditorView { let mut selection = doc.selection(view.id).clone(); let primary = selection.primary_mut(); - *primary = Range::new(primary.anchor, pos); + *primary = primary.put_cursor(doc.text().slice(..), pos, true); doc.set_selection(view.id, selection); EventResult::Consumed(None) } -- cgit v1.2.3-70-g09d2 From 67f6c85792dbdbe0ff3f9328874c7ab23ff5569b Mon Sep 17 00:00:00 2001 From: Connor Lay (Clay) Date: Sat, 18 Jun 2022 14:24:01 -0700 Subject: text-objects: add test capture & elixir queries --- book/src/guides/textobject.md | 2 ++ book/src/keymap.md | 2 ++ book/src/usage.md | 1 + helix-term/src/commands.rs | 12 ++++++++++++ helix-term/src/keymap/default.rs | 2 ++ runtime/queries/elixir/textobjects.scm | 8 +++++++- 6 files changed, 26 insertions(+), 1 deletion(-) (limited to 'helix-term/src') diff --git a/book/src/guides/textobject.md b/book/src/guides/textobject.md index cccd4bbf..8a217354 100644 --- a/book/src/guides/textobject.md +++ b/book/src/guides/textobject.md @@ -20,6 +20,8 @@ The following [captures][tree-sitter-captures] are recognized: | `function.around` | | `class.inside` | | `class.around` | +| `test.inside` | +| `test.around` | | `parameter.inside` | | `comment.inside` | | `comment.around` | diff --git a/book/src/keymap.md b/book/src/keymap.md index fef76efb..7efbdd23 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -282,6 +282,8 @@ Mappings in the style of [vim-unimpaired](https://github.com/tpope/vim-unimpaire | `[a` | Go to previous argument/parameter (**TS**) | `goto_prev_parameter` | | `]o` | Go to next comment (**TS**) | `goto_next_comment` | | `[o` | Go to previous comment (**TS**) | `goto_prev_comment` | +| `]t` | Go to next test (**TS**) | `goto_next_test` | +| `]t` | Go to previous test (**TS**) | `goto_prev_test` | | `]p` | Go to next paragraph | `goto_next_paragraph` | | `[p` | Go to previous paragraph | `goto_prev_paragraph` | | `[space` | Add newline above | `add_newline_above` | diff --git a/book/src/usage.md b/book/src/usage.md index ad21a94c..ba631b62 100644 --- a/book/src/usage.md +++ b/book/src/usage.md @@ -143,6 +143,7 @@ Currently supported: `word`, `surround`, `function`, `class`, `parameter`. | `c` | Class | | `a` | Argument/parameter | | `o` | Comment | +| `t` | Test | > NOTE: `f`, `c`, etc need a tree-sitter grammar active for the current document and a special tree-sitter query file to work properly. [Only diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index c9c8e6a9..046351a3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -395,6 +395,8 @@ impl MappableCommand { goto_prev_parameter, "Goto previous parameter", goto_next_comment, "Goto next comment", goto_prev_comment, "Goto previous comment", + goto_next_test, "Goto next test", + goto_prev_test, "Goto previous test", goto_next_paragraph, "Goto next paragraph", goto_prev_paragraph, "Goto previous paragraph", dap_launch, "Launch debug target", @@ -4098,6 +4100,14 @@ fn goto_prev_comment(cx: &mut Context) { goto_ts_object_impl(cx, "comment", Direction::Backward) } +fn goto_next_test(cx: &mut Context) { + goto_ts_object_impl(cx, "test", Direction::Forward) +} + +fn goto_prev_test(cx: &mut Context) { + goto_ts_object_impl(cx, "test", Direction::Backward) +} + fn select_textobject_around(cx: &mut Context) { select_textobject(cx, textobject::TextObject::Around); } @@ -4141,6 +4151,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { 'f' => textobject_treesitter("function", range), 'a' => textobject_treesitter("parameter", range), 'o' => textobject_treesitter("comment", range), + 't' => textobject_treesitter("test", range), 'p' => textobject::textobject_paragraph(text, range, objtype, count), 'm' => textobject::textobject_surround_closest(text, range, objtype, count), // TODO: cancel new ranges if inconsistent surround matches across lines @@ -4170,6 +4181,7 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { ("f", "Function (tree-sitter)"), ("a", "Argument/parameter (tree-sitter)"), ("o", "Comment (tree-sitter)"), + ("t", "Test (tree-sitter)"), ("m", "Matching delimiter under cursor"), (" ", "... or any character acting as a pair"), ]; diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 0f0b09dd..c3695117 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -104,6 +104,7 @@ pub fn default() -> HashMap { "c" => goto_prev_class, "a" => goto_prev_parameter, "o" => goto_prev_comment, + "t" => goto_prev_test, "p" => goto_prev_paragraph, "space" => add_newline_above, }, @@ -114,6 +115,7 @@ pub fn default() -> HashMap { "c" => goto_next_class, "a" => goto_next_parameter, "o" => goto_next_comment, + "t" => goto_next_test, "p" => goto_next_paragraph, "space" => add_newline_below, }, diff --git a/runtime/queries/elixir/textobjects.scm b/runtime/queries/elixir/textobjects.scm index 52a6f66d..227a52f4 100644 --- a/runtime/queries/elixir/textobjects.scm +++ b/runtime/queries/elixir/textobjects.scm @@ -16,7 +16,7 @@ (pair value: (_) @function.inside))?)? (do_block (_)* @function.inside)?) - (#match? @_keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp|test|describe|setup)$")) @function.around + (#match? @_keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$")) @function.around (anonymous_function (stab_clause right: (body) @function.inside)) @function.around @@ -25,3 +25,9 @@ target: (identifier) @_keyword (do_block (_)* @class.inside)) (#match? @_keyword "^(defmodule|defprotocol|defimpl)$")) @class.around + +((call + target: (identifier) @_keyword + (arguments ((string) . (_)?)) + (do_block (_)* @test.inside)?) + (#match? @_keyword "^(test|describe)$")) @test.around -- cgit v1.2.3-70-g09d2 From fa4934cff9aa5b86b907e218313a7b370962ae67 Mon Sep 17 00:00:00 2001 From: Mathspy Date: Tue, 21 Jun 2022 12:35:25 -0400 Subject: Default rulers color to red (#2669) * Default rulers color to red Currently if the theme a user is using doesn't have `ui.virtual.rulers` set and they set up a ruler it just fails silently making it really hard to figure out what went wrong. Did they set incorrectly set the ruler? Are they using an outdated version of Helix that doesn't support rulers? This happened to me today, I even switched to the default theme with the assumption that maybe my theme just doesn't have the rulers setup properly and it still didn't work. Not sure if this is a good idea or not, feel free to suggest better alternatives! * Use builtin Style methods instead of Bevy style defaults Co-authored-by: Michael Davis * Only default the style if there's no ui or ui.virtual * Update themes style from ui.virtual to ui.virtual.whitespace * Revert ui.virtual change in onelight theme * Prefer unwrap_or_else Co-authored-by: Michael Davis --- helix-term/src/ui/editor.rs | 6 ++++-- runtime/themes/base16_default_dark.toml | 2 +- runtime/themes/base16_default_light.toml | 2 +- runtime/themes/base16_terminal.toml | 2 +- runtime/themes/bogster.toml | 2 +- runtime/themes/boo_berry.toml | 2 +- runtime/themes/catppuccin.toml | 2 +- runtime/themes/dark_plus.toml | 2 +- runtime/themes/dracula.toml | 2 +- runtime/themes/everforest_dark.toml | 2 +- runtime/themes/everforest_light.toml | 2 +- runtime/themes/gruvbox.toml | 2 +- runtime/themes/gruvbox_light.toml | 2 +- runtime/themes/ingrid.toml | 2 +- runtime/themes/monokai.toml | 2 +- runtime/themes/monokai_pro.toml | 2 +- runtime/themes/monokai_pro_machine.toml | 2 +- runtime/themes/monokai_pro_octagon.toml | 2 +- runtime/themes/monokai_pro_ristretto.toml | 2 +- runtime/themes/monokai_pro_spectrum.toml | 2 +- runtime/themes/nord.toml | 2 +- runtime/themes/rose_pine.toml | 2 +- runtime/themes/rose_pine_dawn.toml | 2 +- runtime/themes/serika-dark.toml | 2 +- runtime/themes/serika-light.toml | 2 +- runtime/themes/snazzy.toml | 2 +- runtime/themes/solarized_dark.toml | 2 +- runtime/themes/solarized_light.toml | 2 +- runtime/themes/spacebones_light.toml | 2 +- 29 files changed, 32 insertions(+), 30 deletions(-) (limited to 'helix-term/src') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 192fa180..a8027d1b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -20,7 +20,7 @@ use helix_core::{ use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, editor::{CompleteAction, CursorShapeConfig}, - graphics::{CursorKind, Modifier, Rect, Style}, + graphics::{Color, CursorKind, Modifier, Rect, Style}, input::KeyEvent, keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, @@ -170,7 +170,9 @@ impl EditorView { theme: &Theme, ) { let editor_rulers = &editor.config().rulers; - let ruler_theme = theme.get("ui.virtual.ruler"); + let ruler_theme = theme + .try_get("ui.virtual.ruler") + .unwrap_or_else(|| Style::default().bg(Color::Red)); let rulers = doc .language_config() diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml index 7516e492..33252330 100644 --- a/runtime/themes/base16_default_dark.toml +++ b/runtime/themes/base16_default_dark.toml @@ -1,7 +1,7 @@ # Author: RayGervais "ui.background" = { bg = "base00" } -"ui.virtual" = "base03" +"ui.virtual.whitespace" = "base03" "ui.menu" = { fg = "base05", bg = "base01" } "ui.menu.selected" = { fg = "base01", bg = "base04" } "ui.linenr" = { fg = "base03", bg = "base01" } diff --git a/runtime/themes/base16_default_light.toml b/runtime/themes/base16_default_light.toml index 36847459..bc2b8e67 100644 --- a/runtime/themes/base16_default_light.toml +++ b/runtime/themes/base16_default_light.toml @@ -12,7 +12,7 @@ "ui.statusline" = { fg = "base04", bg = "base01" } "ui.cursor" = { fg = "base04", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "base05", modifiers = ["reversed"] } -"ui.virtual" = "base03" +"ui.virtual.whitespace" = "base03" "ui.text" = "base05" "operator" = "base05" "ui.text.focus" = "base05" diff --git a/runtime/themes/base16_terminal.toml b/runtime/themes/base16_terminal.toml index 92848816..3a1d4845 100644 --- a/runtime/themes/base16_terminal.toml +++ b/runtime/themes/base16_terminal.toml @@ -13,7 +13,7 @@ "ui.help" = { fg = "white", bg = "black" } "ui.cursor" = { fg = "light-gray", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "light-gray", modifiers = ["reversed"] } -"ui.virtual" = "light-gray" +"ui.virtual.whitespace" = "light-gray" "variable" = "light-red" "constant.numeric" = "yellow" "constant" = "yellow" diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml index df3a7f31..76e24648 100644 --- a/runtime/themes/bogster.toml +++ b/runtime/themes/bogster.toml @@ -53,7 +53,7 @@ "ui.text" = { fg = "#e5ded6" } "ui.text.focus" = { fg = "#e5ded6", modifiers= ["bold"] } -"ui.virtual" = "#627d9d" +"ui.virtual.whitespace" = "#627d9d" "ui.selection" = { bg = "#313f4e" } # "ui.cursor.match" # TODO might want to override this because dimmed is not widely supported diff --git a/runtime/themes/boo_berry.toml b/runtime/themes/boo_berry.toml index 5cd25397..a79b75c5 100644 --- a/runtime/themes/boo_berry.toml +++ b/runtime/themes/boo_berry.toml @@ -44,7 +44,7 @@ "ui.menu" = { fg = "lilac", bg = "berry_saturated" } "ui.menu.selected" = { fg = "mint", bg = "berry_saturated" } "ui.selection" = { bg = "berry_saturated" } -"ui.virtual" = { fg = "berry_desaturated" } +"ui.virtual.whitespace" = { fg = "berry_desaturated" } "diff.plus" = { fg = "mint" } "diff.delta" = { fg = "gold" } diff --git a/runtime/themes/catppuccin.toml b/runtime/themes/catppuccin.toml index da9be4fb..eaa4ba51 100644 --- a/runtime/themes/catppuccin.toml +++ b/runtime/themes/catppuccin.toml @@ -48,7 +48,7 @@ label = "peach" "ui.text" = { fg = "pink" } "ui.text.focus" = { fg = "white" } -"ui.virtual" = { fg = "gray_0" } +"ui.virtual.whitespace" = { fg = "gray_0" } "ui.selection" = { bg = "#540099" } "ui.selection.primary" = { bg = "#540099" } diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index c785dd38..957ca61d 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -78,7 +78,7 @@ "ui.text" = { fg = "text" } "ui.text.focus" = { fg = "white" } -"ui.virtual" = { fg = "dark_gray" } +"ui.virtual.whitespace" = { fg = "dark_gray" } "ui.virtual.ruler" = { bg = "borders" } "warning" = { fg = "gold2" } diff --git a/runtime/themes/dracula.toml b/runtime/themes/dracula.toml index e32c3117..72b37d02 100644 --- a/runtime/themes/dracula.toml +++ b/runtime/themes/dracula.toml @@ -36,7 +36,7 @@ "ui.text" = { fg = "foreground" } "ui.text.focus" = { fg = "cyan" } "ui.window" = { fg = "foreground" } -"ui.virtual" = { fg = "comment" } +"ui.virtual.whitespace" = { fg = "comment" } "error" = { fg = "red" } "warning" = { fg = "cyan" } diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml index 5b6d1b7c..ef74ea9e 100644 --- a/runtime/themes/everforest_dark.toml +++ b/runtime/themes/everforest_dark.toml @@ -70,7 +70,7 @@ "ui.menu" = { fg = "fg", bg = "bg2" } "ui.menu.selected" = { fg = "bg0", bg = "green" } "ui.selection" = { bg = "bg3" } -"ui.virtual" = "grey0" +"ui.virtual.whitespace" = "grey0" "hint" = "blue" "info" = "aqua" diff --git a/runtime/themes/everforest_light.toml b/runtime/themes/everforest_light.toml index b0370165..60557ba0 100644 --- a/runtime/themes/everforest_light.toml +++ b/runtime/themes/everforest_light.toml @@ -70,7 +70,7 @@ "ui.menu" = { fg = "fg", bg = "bg2" } "ui.menu.selected" = { fg = "bg0", bg = "green" } "ui.selection" = { bg = "bg3" } -"ui.virtual" = "grey0" +"ui.virtual.whitespace" = "grey0" "hint" = "blue" "info" = "aqua" diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml index 6de35244..48fb14fa 100644 --- a/runtime/themes/gruvbox.toml +++ b/runtime/themes/gruvbox.toml @@ -53,7 +53,7 @@ "ui.cursor.match" = { bg = "bg2" } "ui.menu" = { fg = "fg1", bg = "bg2" } "ui.menu.selected" = { fg = "bg2", bg = "blue1", modifiers = ["bold"] } -"ui.virtual" = "bg2" +"ui.virtual.whitespace" = "bg2" "diagnostic" = { modifiers = ["underlined"] } diff --git a/runtime/themes/gruvbox_light.toml b/runtime/themes/gruvbox_light.toml index 2930dff0..02a32dec 100644 --- a/runtime/themes/gruvbox_light.toml +++ b/runtime/themes/gruvbox_light.toml @@ -54,7 +54,7 @@ "ui.cursor.match" = { bg = "bg2" } "ui.menu" = { fg = "fg1", bg = "bg2" } "ui.menu.selected" = { fg = "bg2", bg = "blue1", modifiers = ["bold"] } -"ui.virtual" = "bg2" +"ui.virtual.whitespace" = "bg2" "diagnostic" = { modifiers = ["underlined"] } diff --git a/runtime/themes/ingrid.toml b/runtime/themes/ingrid.toml index 79b749b1..58713704 100644 --- a/runtime/themes/ingrid.toml +++ b/runtime/themes/ingrid.toml @@ -53,7 +53,7 @@ "ui.text" = { fg = "#7B91B3" } "ui.text.focus" = { fg = "#250E07", modifiers= ["bold"] } -"ui.virtual" = "#A6B6CE" +"ui.virtual.whitespace" = "#A6B6CE" "ui.selection" = { bg = "#540099" } # "ui.cursor.match" # TODO might want to override this because dimmed is not widely supported diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml index 3fb1fadc..5a890615 100644 --- a/runtime/themes/monokai.toml +++ b/runtime/themes/monokai.toml @@ -32,7 +32,7 @@ "attribute" = { fg = "fn_declaration" } "comment" = { fg = "#88846F" } -"ui.virtual" = "#88846F" +"ui.virtual.whitespace" = "#88846F" "string" = { fg = "#e6db74" } "constant.character" = { fg = "#e6db74" } diff --git a/runtime/themes/monokai_pro.toml b/runtime/themes/monokai_pro.toml index 5580a33c..7c457d45 100644 --- a/runtime/themes/monokai_pro.toml +++ b/runtime/themes/monokai_pro.toml @@ -6,7 +6,7 @@ "ui.text.focus" = { fg = "yellow", modifiers= ["bold"] } "ui.menu" = { fg = "base8", bg = "base3" } "ui.menu.selected" = { fg = "base2", bg = "yellow" } -"ui.virtual" = "base5" +"ui.virtual.whitespace" = "base5" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_machine.toml b/runtime/themes/monokai_pro_machine.toml index abbe5bdc..bfc7031d 100644 --- a/runtime/themes/monokai_pro_machine.toml +++ b/runtime/themes/monokai_pro_machine.toml @@ -6,7 +6,7 @@ "ui.text.focus" = { fg = "yellow", modifiers= ["bold"] } "ui.menu" = { fg = "base8", bg = "base3" } "ui.menu.selected" = { fg = "base2", bg = "yellow" } -"ui.virtual" = "base5" +"ui.virtual.whitespace" = "base5" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_octagon.toml b/runtime/themes/monokai_pro_octagon.toml index b249cfe2..889e7624 100644 --- a/runtime/themes/monokai_pro_octagon.toml +++ b/runtime/themes/monokai_pro_octagon.toml @@ -6,7 +6,7 @@ "ui.text.focus" = { fg = "yellow", modifiers= ["bold"] } "ui.menu" = { fg = "base8", bg = "base3" } "ui.menu.selected" = { fg = "base2", bg = "yellow" } -"ui.virtual" = "base5" +"ui.virtual.whitespace" = "base5" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_ristretto.toml b/runtime/themes/monokai_pro_ristretto.toml index cd4cbd8e..f8ad8422 100644 --- a/runtime/themes/monokai_pro_ristretto.toml +++ b/runtime/themes/monokai_pro_ristretto.toml @@ -6,7 +6,7 @@ "ui.text.focus" = { fg = "yellow", modifiers= ["bold"] } "ui.menu" = { fg = "base8", bg = "base3" } "ui.menu.selected" = { fg = "base2", bg = "yellow" } -"ui.virtual" = "base5" +"ui.virtual.whitespace" = "base5" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_spectrum.toml b/runtime/themes/monokai_pro_spectrum.toml index 4160a15e..9f5864fc 100644 --- a/runtime/themes/monokai_pro_spectrum.toml +++ b/runtime/themes/monokai_pro_spectrum.toml @@ -6,7 +6,7 @@ "ui.text.focus" = { fg = "yellow", modifiers= ["bold"] } "ui.menu" = { fg = "base8", bg = "base3" } "ui.menu.selected" = { fg = "base2", bg = "yellow" } -"ui.virtual" = "base5" +"ui.virtual.whitespace" = "base5" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index a61c1715..3b994bb5 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -4,7 +4,7 @@ "ui.text.focus" = { fg = "nord8", modifiers= ["bold"] } "ui.menu" = { fg = "nord6", bg = "#232d38" } "ui.menu.selected" = { fg = "nord8", bg = "nord2" } -"ui.virtual" = "gray" +"ui.virtual.whitespace" = "gray" "info" = "nord8" "hint" = "nord8" diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml index f0575880..09b1e25c 100644 --- a/runtime/themes/rose_pine.toml +++ b/runtime/themes/rose_pine.toml @@ -14,7 +14,7 @@ "ui.text" = { fg = "text" } "ui.text.focus" = { fg = "foam", modifiers = ["bold"]} "ui.text.info" = {fg = "pine", modifiers = ["bold"]} -"ui.virtual" = "highlight" +"ui.virtual.whitespace" = "highlight" "operator" = "rose" "variable" = "text" "constant.numeric" = "iris" diff --git a/runtime/themes/rose_pine_dawn.toml b/runtime/themes/rose_pine_dawn.toml index 5ad304e3..9ba0959d 100644 --- a/runtime/themes/rose_pine_dawn.toml +++ b/runtime/themes/rose_pine_dawn.toml @@ -14,7 +14,7 @@ "ui.text" = { fg = "text" } "ui.text.focus" = { fg = "foam", modifiers = ["bold"]} "ui.text.info" = {fg = "pine", modifiers = ["bold"]} -"ui.virtual" = "highlight" +"ui.virtual.whitespace" = "highlight" "operator" = "rose" "variable" = "text" "number" = "iris" diff --git a/runtime/themes/serika-dark.toml b/runtime/themes/serika-dark.toml index 3dd982d1..3b4bc60f 100644 --- a/runtime/themes/serika-dark.toml +++ b/runtime/themes/serika-dark.toml @@ -50,7 +50,7 @@ "ui.menu" = { fg = "fg", bg = "bg2" } "ui.menu.selected" = { fg = "bg0", bg = "bg_yellow" } "ui.selection" = { bg = "bg3" } -"ui.virtual" = "grey2" +"ui.virtual.whitespace" = "grey2" "hint" = "blue" "info" = "aqua" diff --git a/runtime/themes/serika-light.toml b/runtime/themes/serika-light.toml index 67c8328b..3b0f8fb4 100644 --- a/runtime/themes/serika-light.toml +++ b/runtime/themes/serika-light.toml @@ -50,7 +50,7 @@ "ui.menu" = { fg = "bg0", bg = "bg3" } "ui.menu.selected" = { fg = "bg0", bg = "bg_yellow" } "ui.selection" = { fg = "bg0", bg = "bg3" } -"ui.virtual" = { fg = "bg2" } +"ui.virtual.whitespace" = { fg = "bg2" } "hint" = "blue" "info" = "aqua" diff --git a/runtime/themes/snazzy.toml b/runtime/themes/snazzy.toml index c0547f33..da47fd63 100644 --- a/runtime/themes/snazzy.toml +++ b/runtime/themes/snazzy.toml @@ -38,7 +38,7 @@ "ui.text" = { fg = "foreground" } "ui.text.focus" = { fg = "cyan" } "ui.window" = { fg = "foreground" } -"ui.virtual" = { fg = "comment" } +"ui.virtual.whitespace" = { fg = "comment" } "error" = { fg = "red" } "warning" = { fg = "cyan" } diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml index f15e1fa0..d8126f6e 100644 --- a/runtime/themes/solarized_dark.toml +++ b/runtime/themes/solarized_dark.toml @@ -39,7 +39,7 @@ # 背景 "ui.background" = { bg = "base03" } -"ui.virtual" = { fg = "base01" } +"ui.virtual.whitespace" = { fg = "base01" } # 行号栏 "ui.linenr" = { fg = "base0", bg = "base02" } diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml index eec4220d..cd1028bd 100644 --- a/runtime/themes/solarized_light.toml +++ b/runtime/themes/solarized_light.toml @@ -40,7 +40,7 @@ # background "ui.background" = { bg = "base03" } -"ui.virtual" = { fg = "base01" } +"ui.virtual.whitespace" = { fg = "base01" } # 行号栏 # line number column diff --git a/runtime/themes/spacebones_light.toml b/runtime/themes/spacebones_light.toml index 80a19375..b088e2d3 100644 --- a/runtime/themes/spacebones_light.toml +++ b/runtime/themes/spacebones_light.toml @@ -64,7 +64,7 @@ "ui.cursor.match" = { bg = "bg3" } "ui.menu" = { fg = "fg1", bg = "bg2" } "ui.menu.selected" = { fg = "#655370", bg = "#d1dcdf", modifiers = ["bold"] } -"ui.virtual" = "bg2" +"ui.virtual.whitespace" = "bg2" "diagnostic" = { modifiers = ["underlined"] } -- cgit v1.2.3-70-g09d2 From 6a3f7f2c399f0b92cef97b0c85ebe976fd7cfcac Mon Sep 17 00:00:00 2001 From: Matthew Toohey Date: Tue, 21 Jun 2022 12:36:36 -0400 Subject: feat: make `move_vertically` aware of tabs and wide characters (#2620) * feat: make `move_vertically` aware of tabs and wide characters * refactor: replace unnecessary checked_sub with comparison * refactor: leave pos_at_coords unchanged and introduce separate pos_at_visual_coords * style: include comment to explain `pos_at_visual_coords` breaking condition * refactor: use `pos_at_visual_coords` in `text_pos_at_screen_coords` * feat: make `copy_selection_on_line` aware of wide characters--- helix-core/src/lib.rs | 4 +- helix-core/src/movement.rs | 43 +++++++++--------- helix-core/src/position.rs | 106 +++++++++++++++++++++++++++++++++++++++++++-- helix-term/src/commands.rs | 26 ++++++----- helix-view/src/view.rs | 53 +++++------------------ 5 files changed, 153 insertions(+), 79 deletions(-) (limited to 'helix-term/src') diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 627b73bb..735a62c1 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -63,7 +63,9 @@ pub type Tendril = SmartString; pub use {regex, tree_sitter}; pub use graphemes::RopeGraphemes; -pub use position::{coords_at_pos, pos_at_coords, visual_coords_at_pos, Position}; +pub use position::{ + coords_at_pos, pos_at_coords, pos_at_visual_coords, visual_coords_at_pos, Position, +}; pub use selection::{Range, Selection}; pub use smallvec::{smallvec, SmallVec}; pub use syntax::Syntax; diff --git a/helix-core/src/movement.rs b/helix-core/src/movement.rs index f60b3c83..2155f77a 100644 --- a/helix-core/src/movement.rs +++ b/helix-core/src/movement.rs @@ -5,16 +5,15 @@ use tree_sitter::{Node, QueryCursor}; use crate::{ chars::{categorize_char, char_is_line_ending, CharCategory}, - coords_at_pos, graphemes::{ next_grapheme_boundary, nth_next_grapheme_boundary, nth_prev_grapheme_boundary, prev_grapheme_boundary, }, line_ending::rope_is_line_ending, - pos_at_coords, + pos_at_visual_coords, syntax::LanguageConfiguration, textobject::TextObject, - Position, Range, RopeSlice, + visual_coords_at_pos, Position, Range, RopeSlice, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -35,6 +34,7 @@ pub fn move_horizontally( dir: Direction, count: usize, behaviour: Movement, + _: usize, ) -> Range { let pos = range.cursor(slice); @@ -54,15 +54,12 @@ pub fn move_vertically( dir: Direction, count: usize, behaviour: Movement, + tab_width: usize, ) -> Range { let pos = range.cursor(slice); // Compute the current position's 2d coordinates. - // TODO: switch this to use `visual_coords_at_pos` rather than - // `coords_at_pos` as this will cause a jerky movement when the visual - // position does not match, like moving from a line with tabs/CJK to - // a line without - let Position { row, col } = coords_at_pos(slice, pos); + let Position { row, col } = visual_coords_at_pos(slice, pos, tab_width); let horiz = range.horiz.unwrap_or(col as u32); // Compute the new position. @@ -71,7 +68,7 @@ pub fn move_vertically( Direction::Backward => row.saturating_sub(count), }; let new_col = col.max(horiz as usize); - let new_pos = pos_at_coords(slice, Position::new(new_row, new_col), true); + let new_pos = pos_at_visual_coords(slice, Position::new(new_row, new_col), tab_width); // Special-case to avoid moving to the end of the last non-empty line. if behaviour == Movement::Extend && slice.line(new_row).len_chars() == 0 { @@ -446,6 +443,8 @@ pub fn goto_treesitter_object( mod test { use ropey::Rope; + use crate::{coords_at_pos, pos_at_coords}; + use super::*; const SINGLE_LINE_SAMPLE: &str = "This is a simple alphabetic line"; @@ -472,7 +471,7 @@ mod test { assert_eq!( coords_at_pos( slice, - move_vertically(slice, range, Direction::Forward, 1, Movement::Move).head + move_vertically(slice, range, Direction::Forward, 1, Movement::Move, 4).head ), (1, 3).into() ); @@ -496,7 +495,7 @@ mod test { ]; for ((direction, amount), coordinates) in moves_and_expected_coordinates { - range = move_horizontally(slice, range, direction, amount, Movement::Move); + range = move_horizontally(slice, range, direction, amount, Movement::Move, 0); assert_eq!(coords_at_pos(slice, range.head), coordinates.into()) } } @@ -522,7 +521,7 @@ mod test { ]; for ((direction, amount), coordinates) in moves_and_expected_coordinates { - range = move_horizontally(slice, range, direction, amount, Movement::Move); + range = move_horizontally(slice, range, direction, amount, Movement::Move, 0); assert_eq!(coords_at_pos(slice, range.head), coordinates.into()); assert_eq!(range.head, range.anchor); } @@ -544,7 +543,7 @@ mod test { ]; for (direction, amount) in moves { - range = move_horizontally(slice, range, direction, amount, Movement::Extend); + range = move_horizontally(slice, range, direction, amount, Movement::Extend, 0); assert_eq!(range.anchor, original_anchor); } } @@ -568,7 +567,7 @@ mod test { ]; for ((direction, amount), coordinates) in moves_and_expected_coordinates { - range = move_vertically(slice, range, direction, amount, Movement::Move); + range = move_vertically(slice, range, direction, amount, Movement::Move, 4); assert_eq!(coords_at_pos(slice, range.head), coordinates.into()); assert_eq!(range.head, range.anchor); } @@ -602,8 +601,8 @@ mod test { for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates { range = match axis { - Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move), - Axis::V => move_vertically(slice, range, direction, amount, Movement::Move), + Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move, 0), + Axis::V => move_vertically(slice, range, direction, amount, Movement::Move, 4), }; assert_eq!(coords_at_pos(slice, range.head), coordinates.into()); assert_eq!(range.head, range.anchor); @@ -627,18 +626,18 @@ mod test { let moves_and_expected_coordinates = [ // Places cursor at the fourth kana. ((Axis::H, Direction::Forward, 4), (0, 4)), - // Descent places cursor at the 4th character. - ((Axis::V, Direction::Forward, 1usize), (1, 4)), - // Moving back 1 character. - ((Axis::H, Direction::Backward, 1usize), (1, 3)), + // Descent places cursor at the 8th character. + ((Axis::V, Direction::Forward, 1usize), (1, 8)), + // Moving back 2 characters. + ((Axis::H, Direction::Backward, 2usize), (1, 6)), // Jumping back up 1 line. ((Axis::V, Direction::Backward, 1usize), (0, 3)), ]; for ((axis, direction, amount), coordinates) in moves_and_expected_coordinates { range = match axis { - Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move), - Axis::V => move_vertically(slice, range, direction, amount, Movement::Move), + Axis::H => move_horizontally(slice, range, direction, amount, Movement::Move, 0), + Axis::V => move_vertically(slice, range, direction, amount, Movement::Move, 4), }; assert_eq!(coords_at_pos(slice, range.head), coordinates.into()); assert_eq!(range.head, range.anchor); diff --git a/helix-core/src/position.rs b/helix-core/src/position.rs index ce37300a..f456eb98 100644 --- a/helix-core/src/position.rs +++ b/helix-core/src/position.rs @@ -109,9 +109,6 @@ pub fn visual_coords_at_pos(text: RopeSlice, pos: usize, tab_width: usize) -> Po /// with left-side block-cursor positions, as this prevents the the block cursor /// from jumping to the next line. Otherwise you typically want it to be `false`, /// such as when dealing with raw anchor/head positions. -/// -/// TODO: this should be changed to work in terms of visual row/column, not -/// graphemes. pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending: bool) -> usize { let Position { mut row, col } = coords; if limit_before_line_ending { @@ -135,6 +132,43 @@ pub fn pos_at_coords(text: RopeSlice, coords: Position, limit_before_line_ending line_start + col_char_offset } +/// Convert visual (line, column) coordinates to a character index. +/// +/// If the `line` coordinate is beyond the end of the file, the EOF +/// position will be returned. +/// +/// If the `column` coordinate is past the end of the given line, the +/// line-end position (in this case, just before the line ending +/// character) will be returned. +pub fn pos_at_visual_coords(text: RopeSlice, coords: Position, tab_width: usize) -> usize { + let Position { mut row, col } = coords; + row = row.min(text.len_lines() - 1); + let line_start = text.line_to_char(row); + let line_end = line_end_char_index(&text, row); + + let mut col_char_offset = 0; + let mut cols_remaining = col; + for grapheme in RopeGraphemes::new(text.slice(line_start..line_end)) { + let grapheme_width = if grapheme == "\t" { + tab_width - ((col - cols_remaining) % tab_width) + } else { + let grapheme = Cow::from(grapheme); + grapheme_width(&grapheme) + }; + + // If pos is in the middle of a wider grapheme (tab for example) + // return the starting offset. + if grapheme_width > cols_remaining { + break; + } + + cols_remaining -= grapheme_width; + col_char_offset += grapheme.chars().count(); + } + + line_start + col_char_offset +} + #[cfg(test)] mod test { use super::*; @@ -305,4 +339,70 @@ mod test { assert_eq!(pos_at_coords(slice, (0, 10).into(), true), 0); assert_eq!(pos_at_coords(slice, (10, 10).into(), true), 0); } + + #[test] + fn test_pos_at_visual_coords() { + let text = Rope::from("ḧëḷḷö\nẅöṛḷḋ"); + let slice = text.slice(..); + assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 5); // position on \n + assert_eq!(pos_at_visual_coords(slice, (0, 6).into(), 4), 5); // position after \n + assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 6); // position on w + assert_eq!(pos_at_visual_coords(slice, (1, 1).into(), 4), 7); // position on o + assert_eq!(pos_at_visual_coords(slice, (1, 4).into(), 4), 10); // position on d + + // Test with wide characters. + let text = Rope::from("今日はいい\n"); + let slice = text.slice(..); + assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 1); + assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 1); + assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 2); + assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 2); + assert_eq!(pos_at_visual_coords(slice, (0, 6).into(), 4), 3); + assert_eq!(pos_at_visual_coords(slice, (0, 7).into(), 4), 3); + assert_eq!(pos_at_visual_coords(slice, (0, 8).into(), 4), 4); + assert_eq!(pos_at_visual_coords(slice, (0, 9).into(), 4), 4); + // assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4, false), 5); + // assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4, true), 5); + assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 6); + + // Test with grapheme clusters. + let text = Rope::from("a̐éö̲\r\n"); + let slice = text.slice(..); + assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 2); + assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 4); + assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 7); // \r\n is one char here + assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 7); + assert_eq!(pos_at_visual_coords(slice, (1, 0).into(), 4), 9); + + // Test with wide-character grapheme clusters. + let text = Rope::from("किमपि"); + // 2 - 1 - 2 codepoints + // TODO: delete handling as per https://news.ycombinator.com/item?id=20058454 + let slice = text.slice(..); + assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 2); + assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 3); + + // Test with tabs. + let text = Rope::from("\tHello\n"); + let slice = text.slice(..); + assert_eq!(pos_at_visual_coords(slice, (0, 0).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 1).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 2).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 3).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 4).into(), 4), 1); + assert_eq!(pos_at_visual_coords(slice, (0, 5).into(), 4), 2); + + // Test out of bounds. + let text = Rope::new(); + let slice = text.slice(..); + assert_eq!(pos_at_visual_coords(slice, (10, 0).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (0, 10).into(), 4), 0); + assert_eq!(pos_at_visual_coords(slice, (10, 10).into(), 4), 0); + } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 046351a3..d7937ff5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -16,14 +16,14 @@ use helix_core::{ line_ending::{get_line_ending_of_str, line_end_char_index, str_is_line_ending}, match_brackets, movement::{self, Direction}, - object, pos_at_coords, + object, pos_at_coords, pos_at_visual_coords, regex::{self, Regex, RegexBuilder}, search::{self, CharMatcher}, selection, shellwords, surround, textobject, tree_sitter::Node, unicode::width::UnicodeWidthChar, - LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, - Transaction, + visual_coords_at_pos, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, + SmallVec, Tendril, Transaction, }; use helix_view::{ clipboard::ClipboardType, @@ -511,7 +511,7 @@ fn no_op(_cx: &mut Context) {} fn move_impl(cx: &mut Context, move_fn: F, dir: Direction, behaviour: Movement) where - F: Fn(RopeSlice, Range, Direction, usize, Movement) -> Range, + F: Fn(RopeSlice, Range, Direction, usize, Movement, usize) -> Range, { let count = cx.count(); let (view, doc) = current!(cx.editor); @@ -520,7 +520,7 @@ where let selection = doc .selection(view.id) .clone() - .transform(|range| move_fn(text, range, dir, count, behaviour)); + .transform(|range| move_fn(text, range, dir, count, behaviour, doc.tab_width())); doc.set_selection(view.id, selection); } @@ -1412,9 +1412,10 @@ fn copy_selection_on_line(cx: &mut Context, direction: Direction) { range.head }; - // TODO: this should use visual offsets / pos_at_screen_coords - let head_pos = coords_at_pos(text, head); - let anchor_pos = coords_at_pos(text, range.anchor); + let tab_width = doc.tab_width(); + + let head_pos = visual_coords_at_pos(text, head, tab_width); + let anchor_pos = visual_coords_at_pos(text, range.anchor, tab_width); let height = std::cmp::max(head_pos.row, anchor_pos.row) - std::cmp::min(head_pos.row, anchor_pos.row) @@ -1444,12 +1445,13 @@ fn copy_selection_on_line(cx: &mut Context, direction: Direction) { break; } - let anchor = pos_at_coords(text, Position::new(anchor_row, anchor_pos.col), true); - let head = pos_at_coords(text, Position::new(head_row, head_pos.col), true); + let anchor = + pos_at_visual_coords(text, Position::new(anchor_row, anchor_pos.col), tab_width); + let head = pos_at_visual_coords(text, Position::new(head_row, head_pos.col), tab_width); // skip lines that are too short - if coords_at_pos(text, anchor).col == anchor_pos.col - && coords_at_pos(text, head).col == head_pos.col + if visual_coords_at_pos(text, anchor, tab_width).col == anchor_pos.col + && visual_coords_at_pos(text, head, tab_width).col == head_pos.col { if is_primary { primary_index = ranges.len(); diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index a496fe33..bfae12a4 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -1,15 +1,9 @@ -use std::borrow::Cow; - use crate::{ graphics::Rect, gutter::{self, Gutter}, Document, DocumentId, ViewId, }; -use helix_core::{ - graphemes::{grapheme_width, RopeGraphemes}, - line_ending::line_end_char_index, - visual_coords_at_pos, Position, RopeSlice, Selection, -}; +use helix_core::{pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection}; use std::fmt; @@ -251,44 +245,21 @@ impl View { return None; } - let line_number = (row - inner.y) as usize + self.offset.row; - - if line_number > text.len_lines() - 1 { + let text_row = (row - inner.y) as usize + self.offset.row; + if text_row > text.len_lines() - 1 { return Some(text.len_chars()); } - let mut pos = text.line_to_char(line_number); - - let current_line = text.line(line_number); - - let target = (column - inner.x) as usize + self.offset.col; - let mut col = 0; - - // TODO: extract this part as pos_at_visual_coords - for grapheme in RopeGraphemes::new(current_line) { - if col >= target { - break; - } - - let width = if grapheme == "\t" { - tab_width - (col % tab_width) - } else { - let grapheme = Cow::from(grapheme); - grapheme_width(&grapheme) - }; - - // If pos is in the middle of a wider grapheme (tab for example) - // return the starting offset. - if col + width > target { - break; - } + let text_col = (column - inner.x) as usize + self.offset.col; - col += width; - // TODO: use byte pos that converts back to char pos? - pos += grapheme.chars().count(); - } - - Some(pos.min(line_end_char_index(&text.slice(..), line_number))) + Some(pos_at_visual_coords( + *text, + Position { + row: text_row, + col: text_col, + }, + tab_width, + )) } /// Translates a screen position to position in the text document. -- cgit v1.2.3-70-g09d2