From 8aa0b8eacfaacf0b2c1a9bad84e6ae34fb51ce14 Mon Sep 17 00:00:00 2001 From: chunghha Date: Fri, 24 Dec 2021 23:20:20 -0600 Subject: [PATCH 01/21] chore: update rose pine themes to support markup (#1353) --- runtime/themes/rose_pine.toml | 10 ++++++++++ runtime/themes/rose_pine_dawn.toml | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml index 256b33c8d..a3c12bcef 100644 --- a/runtime/themes/rose_pine.toml +++ b/runtime/themes/rose_pine.toml @@ -1,4 +1,5 @@ # Author: RayGervais +# Author: ChrisHa "ui.background" = { bg = "base" } "ui.menu" = "surface" @@ -44,6 +45,15 @@ "diagnostic" = "rose" "error" = "love" +"markup.heading" = { fg = "rose" } +"markup.raw.inline" = { fg = "foam" } +"markup.bold" = { fg = "gold", modifiers = ["bold"] } +"markup.italic" = { fg = "iris", modifiers = ["italic"] } +"markup.list" = { fg = "love" } +"markup.quote" = { fg = "rose" } +"markup.link.url" = { fg = "pine", modifiers = ["underlined"]} +"markup.link.label" = { fg = "foam" } + [palette] base = "#191724" surface = "#1f1d2e" diff --git a/runtime/themes/rose_pine_dawn.toml b/runtime/themes/rose_pine_dawn.toml index 43ba24ed0..6654c8c9a 100644 --- a/runtime/themes/rose_pine_dawn.toml +++ b/runtime/themes/rose_pine_dawn.toml @@ -1,8 +1,8 @@ -# Author: ChrisHa # Author: RayGervais +# Author: ChrisHa -"ui.background" = { bg = "base" } -"ui.menu" = "surface" +"ui.background" = { bg = "surface" } +"ui.menu" = "base" "ui.menu.selected" = { fg = "iris", bg = "surface" } "ui.linenr" = {fg = "subtle" } "ui.popup" = { bg = "overlay" } @@ -45,6 +45,15 @@ "diagnostic" = "rose" "error" = "love" +"markup.heading" = { fg = "rose" } +"markup.raw.inline" = { fg = "foam" } +"markup.bold" = { fg = "gold", modifiers = ["bold"] } +"markup.italic" = { fg = "iris", modifiers = ["italic"] } +"markup.list" = { fg = "love" } +"markup.quote" = { fg = "rose" } +"markup.link.url" = { fg = "pine", modifiers = ["underlined"]} +"markup.link.label" = { fg = "foam" } + [palette] base = "#faf4ed" surface = "#fffaf3" From 60f3225c7f3375b546e8ec9032739d073a7c363c Mon Sep 17 00:00:00 2001 From: BB Date: Sat, 25 Dec 2021 05:24:29 +0000 Subject: [PATCH 02/21] Truncate the start of file paths in the StatusLine (#1351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Truncate the start of file paths in the StatusLine * cargo fmt Co-authored-by: Bódi Balázs <97936@4ig.hu> --- helix-term/src/ui/editor.rs | 40 +++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c5b438986..390b37595 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -548,21 +548,6 @@ impl EditorView { } surface.set_string(viewport.x + 5, viewport.y, progress, base_style); - let rel_path = doc.relative_path(); - let path = rel_path - .as_ref() - .map(|p| p.to_string_lossy()) - .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); - - let title = format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }); - surface.set_stringn( - viewport.x + 8, - viewport.y, - title, - viewport.width.saturating_sub(6) as usize, - base_style, - ); - //------------------------------- // Right side of the status line. //------------------------------- @@ -646,6 +631,31 @@ impl EditorView { &right_side_text, right_side_text.width() as u16, ); + + //------------------------------- + // Middle / File path / Title + //------------------------------- + let title = { + let rel_path = doc.relative_path(); + let path = rel_path + .as_ref() + .map(|p| p.to_string_lossy()) + .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); + format!("{}{}", path, if doc.is_modified() { "[+]" } else { "" }) + }; + + surface.set_string_truncated( + viewport.x + 8, // 8: 1 space + 3 char mode string + 1 space + 1 spinner + 1 space + viewport.y, + title, + viewport + .width + .saturating_sub(6) + .saturating_sub(right_side_text.width() as u16 + 1) as usize, // "+ 1": a space between the title and the selection info + base_style, + true, + true, + ); } /// Handle events by looking them up in `self.keymaps`. Returns None From 0e7d757869bbae914a7e832e2511c2071eeacee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 25 Dec 2021 06:32:43 +0100 Subject: [PATCH 03/21] feat(lsp): configurable diagnostic severity (#1325) * feat(lsp): configurable diagnostic severity Allow severity of diagnostic messages to be configured. E.g. allow turning of Hint level diagnostics. Fixes: https://github.com/helix-editor/helix/issues/1007 * Use language_config() method * Add documentation for diagnostic_severity * Use unreachable for unknown severity level * fix: documentation for diagnostic_severity config --- book/src/guides/adding_languages.md | 25 ++++++++++++----------- book/src/languages.md | 1 - helix-core/src/diagnostic.rs | 15 ++++++++++---- helix-core/src/indent.rs | 2 ++ helix-core/src/syntax.rs | 3 +++ helix-term/src/application.rs | 31 ++++++++++++++++++++--------- 6 files changed, 51 insertions(+), 26 deletions(-) diff --git a/book/src/guides/adding_languages.md b/book/src/guides/adding_languages.md index 987ad088c..5844a48ee 100644 --- a/book/src/guides/adding_languages.md +++ b/book/src/guides/adding_languages.md @@ -27,18 +27,19 @@ directory](../configuration.md). These are the available keys and descriptions for the file. -| Key | Description | -| ---- | ----------- | -| name | The name of the language | -| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.` or `text.` in case of markup languages | -| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. | -| file-types | The filetypes of the language, for example `["yml", "yaml"]` | -| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` | -| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` | -| auto-format | Whether to autoformat this language when saving | -| comment-token | The token to use as a comment-token | -| indent | The indent to use. Has sub keys `tab-width` and `unit` | -| config | Language server configuration | +| Key | Description | +| ---- | ----------- | +| name | The name of the language | +| scope | A string like `source.js` that identifies the language. Currently, we strive to match the scope names used by popular TextMate grammars and by the Linguist library. Usually `source.` or `text.` in case of markup languages | +| injection-regex | regex pattern that will be tested against a language name in order to determine whether this language should be used for a potential [language injection][treesitter-language-injection] site. | +| file-types | The filetypes of the language, for example `["yml", "yaml"]` | +| shebangs | The interpreters from the shebang line, for example `["sh", "bash"]` | +| roots | A set of marker files to look for when trying to find the workspace root. For example `Cargo.lock`, `yarn.lock` | +| auto-format | Whether to autoformat this language when saving | +| diagnostic-severity | Minimal severity of diagnostic for it to be displayed. (Allowed values: `Error`, `Warning`, `Info`, `Hint`) | +| comment-token | The token to use as a comment-token | +| indent | The indent to use. Has sub keys `tab-width` and `unit` | +| config | Language server configuration | ## Queries diff --git a/book/src/languages.md b/book/src/languages.md index cef61501f..4c4dc326d 100644 --- a/book/src/languages.md +++ b/book/src/languages.md @@ -11,4 +11,3 @@ Changes made to the `languages.toml` file in a user's [configuration directory]( name = "rust" auto-format = false ``` - diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index 4fcf51c9c..210ad6391 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,12 +1,19 @@ //! LSP diagnostic utility types. +use serde::{Deserialize, Serialize}; /// Describes the severity level of a [`Diagnostic`]. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Deserialize, Serialize)] pub enum Severity { - Error, - Warning, - Info, Hint, + Info, + Warning, + Error, +} + +impl Default for Severity { + fn default() -> Self { + Self::Hint + } } /// A range of `char`s within the text. diff --git a/helix-core/src/indent.rs b/helix-core/src/indent.rs index b6f5081ac..c2baf3ccc 100644 --- a/helix-core/src/indent.rs +++ b/helix-core/src/indent.rs @@ -442,6 +442,7 @@ where ); let doc = Rope::from(doc); + use crate::diagnostic::Severity; use crate::syntax::{ Configuration, IndentationConfiguration, LanguageConfiguration, Loader, }; @@ -459,6 +460,7 @@ where roots: vec![], comment_token: None, auto_format: false, + diagnostic_severity: Severity::Warning, language_server: None, indent: Some(IndentationConfiguration { tab_width: 4, diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index ef35fc756..cdae02103 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1,5 +1,6 @@ use crate::{ chars::char_is_line_ending, + diagnostic::Severity, regex::Regex, transaction::{ChangeSet, Operation}, Rope, RopeSlice, Tendril, @@ -63,6 +64,8 @@ pub struct LanguageConfiguration { #[serde(default)] pub auto_format: bool, + #[serde(default)] + pub diagnostic_severity: Severity, // content_regex #[serde(default, skip_serializing, deserialize_with = "deserialize_regex")] diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 9a46a7fe6..c7202feba 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -378,6 +378,7 @@ impl Application { let doc = self.editor.document_by_path_mut(&path); if let Some(doc) = doc { + let lang_conf = doc.language_config(); let text = doc.text(); let diagnostics = params @@ -415,19 +416,31 @@ impl Application { return None; }; + let severity = + diagnostic.severity.map(|severity| match severity { + DiagnosticSeverity::ERROR => Error, + DiagnosticSeverity::WARNING => Warning, + DiagnosticSeverity::INFORMATION => Info, + DiagnosticSeverity::HINT => Hint, + severity => unreachable!( + "unrecognized diagnostic severity: {:?}", + severity + ), + }); + + if let Some(lang_conf) = lang_conf { + if let Some(severity) = severity { + if severity < lang_conf.diagnostic_severity { + return None; + } + } + }; + Some(Diagnostic { range: Range { start, end }, line: diagnostic.range.start.line as usize, message: diagnostic.message, - severity: diagnostic.severity.map( - |severity| match severity { - DiagnosticSeverity::ERROR => Error, - DiagnosticSeverity::WARNING => Warning, - DiagnosticSeverity::INFORMATION => Info, - DiagnosticSeverity::HINT => Hint, - severity => unimplemented!("{:?}", severity), - }, - ), + severity, // code // source }) From 5d7b5db8ab284e0c2a41e6fbda08857f87406780 Mon Sep 17 00:00:00 2001 From: Gabriel Berto Date: Sat, 25 Dec 2021 07:00:57 -0300 Subject: [PATCH 04/21] Resolve completion item (#1315) Co-authored-by: Gabriel Berto --- helix-lsp/src/client.rs | 8 +++++++ helix-term/src/ui/completion.rs | 40 +++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/helix-lsp/src/client.rs b/helix-lsp/src/client.rs index f1de87524..43804daa9 100644 --- a/helix-lsp/src/client.rs +++ b/helix-lsp/src/client.rs @@ -556,6 +556,14 @@ impl Client { self.call::(params) } + pub async fn resolve_completion_item( + &self, + completion_item: lsp::CompletionItem, + ) -> Result { + self.request::(completion_item) + .await + } + pub fn text_document_signature_help( &self, text_document: lsp::TextDocumentIdentifier, diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index a55201ff2..274330c0e 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -154,8 +154,19 @@ impl Completion { ); doc.apply(&transaction, view.id); - if let Some(additional_edits) = &item.additional_text_edits { - // gopls uses this to add extra imports + // apply additional edits, mostly used to auto import unqualified types + let resolved_additional_text_edits = if item.additional_text_edits.is_some() { + None + } else { + Completion::resolve_completion_item(doc, item.clone()) + .and_then(|item| item.additional_text_edits) + }; + + if let Some(additional_edits) = item + .additional_text_edits + .as_ref() + .or_else(|| resolved_additional_text_edits.as_ref()) + { if !additional_edits.is_empty() { let transaction = util::generate_transaction_from_edits( doc.text(), @@ -181,6 +192,31 @@ impl Completion { completion } + fn resolve_completion_item( + doc: &Document, + completion_item: lsp::CompletionItem, + ) -> Option { + let language_server = doc.language_server()?; + let completion_resolve_provider = language_server + .capabilities() + .completion_provider + .as_ref()? + .resolve_provider; + if completion_resolve_provider != Some(true) { + return None; + } + + let future = language_server.resolve_completion_item(completion_item); + let response = helix_lsp::block_on(future); + match response { + Ok(completion_item) => Some(completion_item), + Err(err) => { + log::error!("execute LSP command: {}", err); + None + } + } + } + pub fn recompute_filter(&mut self, editor: &Editor) { // recompute menu based on matches let menu = self.popup.contents_mut(); From ec878e40114d8992c3ed1221f77271a4508d3cde Mon Sep 17 00:00:00 2001 From: Sebastian Neubauer Date: Sat, 25 Dec 2021 16:10:19 +0100 Subject: [PATCH 05/21] Add textobjects and indents to cmake (#1307) --- book/src/generated/lang-support.md | 2 +- runtime/queries/cmake/indents.toml | 12 ++++++++++++ runtime/queries/cmake/textobjects.scm | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 runtime/queries/cmake/indents.toml create mode 100644 runtime/queries/cmake/textobjects.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index c70542011..2777dc4e7 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -3,7 +3,7 @@ | bash | ✓ | | | `bash-language-server` | | c | ✓ | | | `clangd` | | c-sharp | ✓ | | | | -| cmake | ✓ | | | `cmake-language-server` | +| cmake | ✓ | ✓ | ✓ | `cmake-language-server` | | comment | ✓ | | | | | cpp | ✓ | | | `clangd` | | css | ✓ | | | | diff --git a/runtime/queries/cmake/indents.toml b/runtime/queries/cmake/indents.toml new file mode 100644 index 000000000..8b886a4fb --- /dev/null +++ b/runtime/queries/cmake/indents.toml @@ -0,0 +1,12 @@ +indent = [ + "if_condition", + "foreach_loop", + "while_loop", + "function_def", + "macro_def", + "normal_command", +] + +outdent = [ + ")" +] diff --git a/runtime/queries/cmake/textobjects.scm b/runtime/queries/cmake/textobjects.scm new file mode 100644 index 000000000..b0d1b1083 --- /dev/null +++ b/runtime/queries/cmake/textobjects.scm @@ -0,0 +1,3 @@ +(macro_def) @function.around + +(argument) @parameter.inside From 4b0b1a5657b78693efe609647360de30264fcc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Sat, 25 Dec 2021 16:10:46 +0100 Subject: [PATCH 06/21] feat(ui): file encoding in statusline (#1355) * feat(ui): file encoding in statusline Display file encoding in statusline if the encoding isn't UTF-8. * Re-export encoding_rs from core From there it can be imported by other mods that rely on it. --- Cargo.lock | 2 +- helix-core/Cargo.toml | 1 + helix-core/src/lib.rs | 2 ++ helix-term/src/ui/editor.rs | 9 ++++++++- helix-view/Cargo.toml | 1 - helix-view/src/document.rs | 33 +++++++++++++++++---------------- 6 files changed, 29 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27268feb0..40afe4e50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,6 +370,7 @@ version = "0.5.0" dependencies = [ "arc-swap", "chrono", + "encoding_rs", "etcetera", "helix-syntax", "log", @@ -471,7 +472,6 @@ dependencies = [ "chardetng", "clipboard-win", "crossterm", - "encoding_rs", "futures-util", "helix-core", "helix-lsp", diff --git a/helix-core/Cargo.toml b/helix-core/Cargo.toml index 839b07ac1..3d7fe8662 100644 --- a/helix-core/Cargo.toml +++ b/helix-core/Cargo.toml @@ -35,6 +35,7 @@ toml = "0.5" similar = "2.1" etcetera = "0.3" +encoding_rs = "0.8" chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] } diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 92a59f31e..1c78e7c0b 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -1,3 +1,5 @@ +pub use encoding_rs as encoding; + pub mod auto_pairs; pub mod chars; pub mod comment; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 390b37595..dd0503981 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -7,7 +7,7 @@ use crate::{ }; use helix_core::{ - coords_at_pos, + coords_at_pos, encoding, graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary}, movement::Direction, syntax::{self, HighlightEvent}, @@ -621,6 +621,13 @@ impl EditorView { base_style, )); + let enc = doc.encoding(); + if enc != encoding::UTF_8 { + right_side_text + .0 + .push(Span::styled(format!(" {} ", enc.name()), base_style)); + } + // Render to the statusline. surface.set_spans( viewport.x diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 9c3a83f81..121a518c3 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -29,7 +29,6 @@ futures-util = { version = "0.3", features = ["std", "async-await"], default-fea slotmap = "1" -encoding_rs = "0.8" chardetng = "0.1" serde = { version = "1.0", features = ["derive"] } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index c71d1850a..9652d7b3c 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use std::sync::Arc; use helix_core::{ + encoding, history::History, indent::{auto_detect_indent_style, IndentStyle}, line_ending::auto_detect_line_ending, @@ -74,7 +75,7 @@ pub struct Document { pub(crate) selections: HashMap, path: Option, - encoding: &'static encoding_rs::Encoding, + encoding: &'static encoding::Encoding, /// Current editing mode. pub mode: Mode, @@ -143,8 +144,8 @@ impl fmt::Debug for Document { /// be used to override encoding auto-detection. pub fn from_reader( reader: &mut R, - encoding: Option<&'static encoding_rs::Encoding>, -) -> Result<(Rope, &'static encoding_rs::Encoding), Error> { + encoding: Option<&'static encoding::Encoding>, +) -> Result<(Rope, &'static encoding::Encoding), Error> { // These two buffers are 8192 bytes in size each and are used as // intermediaries during the decoding process. Text read into `buf` // from `reader` is decoded into `buf_out` as UTF-8. Once either @@ -212,11 +213,11 @@ pub fn from_reader( total_read += read; total_written += written; match result { - encoding_rs::CoderResult::InputEmpty => { + encoding::CoderResult::InputEmpty => { debug_assert_eq!(slice.len(), total_read); break; } - encoding_rs::CoderResult::OutputFull => { + encoding::CoderResult::OutputFull => { debug_assert!(slice.len() > total_read); builder.append(&buf_str[..total_written]); total_written = 0; @@ -251,7 +252,7 @@ pub fn from_reader( /// replacement characters may appear in the encoded text. pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( writer: &'a mut W, - encoding: &'static encoding_rs::Encoding, + encoding: &'static encoding::Encoding, rope: &'a Rope, ) -> Result<(), Error> { // Text inside a `Rope` is stored as non-contiguous blocks of data called @@ -286,12 +287,12 @@ pub async fn to_writer<'a, W: tokio::io::AsyncWriteExt + Unpin + ?Sized>( total_read += read; total_written += written; match result { - encoding_rs::CoderResult::InputEmpty => { + encoding::CoderResult::InputEmpty => { debug_assert_eq!(chunk.len(), total_read); debug_assert!(buf.len() >= total_written); break; } - encoding_rs::CoderResult::OutputFull => { + encoding::CoderResult::OutputFull => { debug_assert!(chunk.len() > total_read); writer.write_all(&buf[..total_written]).await?; total_written = 0; @@ -322,8 +323,8 @@ use helix_lsp::lsp; use url::Url; impl Document { - pub fn from(text: Rope, encoding: Option<&'static encoding_rs::Encoding>) -> Self { - let encoding = encoding.unwrap_or(encoding_rs::UTF_8); + pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self { + let encoding = encoding.unwrap_or(encoding::UTF_8); let changes = ChangeSet::new(&text); let old_state = None; @@ -356,7 +357,7 @@ impl Document { /// overwritten with the `encoding` parameter. pub fn open( path: &Path, - encoding: Option<&'static encoding_rs::Encoding>, + encoding: Option<&'static encoding::Encoding>, theme: Option<&Theme>, config_loader: Option<&syntax::Loader>, ) -> Result { @@ -366,7 +367,7 @@ impl Document { std::fs::File::open(path).context(format!("unable to open {:?}", path))?; from_reader(&mut file, encoding)? } else { - let encoding = encoding.unwrap_or(encoding_rs::UTF_8); + let encoding = encoding.unwrap_or(encoding::UTF_8); (Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding) }; @@ -548,7 +549,7 @@ impl Document { /// Sets the [`Document`]'s encoding with the encoding correspondent to `label`. pub fn set_encoding(&mut self, label: &str) -> Result<(), Error> { - match encoding_rs::Encoding::for_label(label.as_bytes()) { + match encoding::Encoding::for_label(label.as_bytes()) { Some(encoding) => self.encoding = encoding, None => return Err(anyhow::anyhow!("unknown encoding")), } @@ -556,7 +557,7 @@ impl Document { } /// Returns the [`Document`]'s current encoding. - pub fn encoding(&self) -> &'static encoding_rs::Encoding { + pub fn encoding(&self) -> &'static encoding::Encoding { self.encoding } @@ -1123,7 +1124,7 @@ mod test { macro_rules! test_decode { ($label:expr, $label_override:expr) => { - let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap(); + let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap(); let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding"); let path = base_path.join(format!("{}_in.txt", $label)); let ref_path = base_path.join(format!("{}_in_ref.txt", $label)); @@ -1142,7 +1143,7 @@ mod test { macro_rules! test_encode { ($label:expr, $label_override:expr) => { - let encoding = encoding_rs::Encoding::for_label($label_override.as_bytes()).unwrap(); + let encoding = encoding::Encoding::for_label($label_override.as_bytes()).unwrap(); let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/encoding"); let path = base_path.join(format!("{}_out.txt", $label)); let ref_path = base_path.join(format!("{}_out_ref.txt", $label)); From fd31662b70ee32d199950ba2873680fc9043c975 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 24 Dec 2021 12:44:45 -0600 Subject: [PATCH 07/21] add gitcommit grammar and language configuration --- .gitmodules | 6 +++++- book/src/generated/lang-support.md | 1 + helix-syntax/languages/tree-sitter-gitcommit | 1 + languages.toml | 8 ++++++++ 4 files changed, 15 insertions(+), 1 deletion(-) create mode 160000 helix-syntax/languages/tree-sitter-gitcommit diff --git a/.gitmodules b/.gitmodules index edfe3c391..ad100a003 100644 --- a/.gitmodules +++ b/.gitmodules @@ -165,8 +165,12 @@ [submodule "helix-syntax/languages/tree-sitter-dockerfile"] path = helix-syntax/languages/tree-sitter-dockerfile url = https://github.com/camdencheek/tree-sitter-dockerfile.git - shallow = true + shallow = true [submodule "helix-syntax/languages/tree-sitter-fish"] path = helix-syntax/languages/tree-sitter-fish url = https://github.com/ram02z/tree-sitter-fish shallow = true +[submodule "helix-syntax/languages/tree-sitter-gitcommit"] + path = helix-syntax/languages/tree-sitter-gitcommit + url = https://github.com/the-mikedavis/tree-sitter-gitcommit.git + shallow = true diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 2777dc4e7..d6e0d9af3 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -11,6 +11,7 @@ | dockerfile | ✓ | | | `docker-langserver` | | elixir | ✓ | | | `elixir-ls` | | fish | ✓ | ✓ | ✓ | | +| gitcommit | ✓ | | | | | glsl | ✓ | | ✓ | | | go | ✓ | ✓ | ✓ | `gopls` | | html | ✓ | | | | diff --git a/helix-syntax/languages/tree-sitter-gitcommit b/helix-syntax/languages/tree-sitter-gitcommit new file mode 160000 index 000000000..6a2ddbecd --- /dev/null +++ b/helix-syntax/languages/tree-sitter-gitcommit @@ -0,0 +1 @@ +Subproject commit 6a2ddbecd49fa8e7e1fda24d43e363cfd9171ca0 diff --git a/languages.toml b/languages.toml index dd18fa190..f73011c8e 100644 --- a/languages.toml +++ b/languages.toml @@ -473,3 +473,11 @@ file-types = ["Dockerfile", "dockerfile"] comment-token = "#" indent = { tab-width = 2, unit = " " } language-server = { command = "docker-langserver", args = ["--stdio"] } + +[[language]] +name = "gitcommit" +scope = "git.commitmsg" +roots = [] +file-types = ["COMMIT_EDITMSG"] +comment-token = "#" +indent = { tab-width = 2, unit = " " } From 78f93239b5814d1b1be7d1fbd2fce1a7aec12432 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Dec 2021 09:01:50 -0600 Subject: [PATCH 08/21] add gitcommit highlights --- runtime/queries/gitcommit/highlights.scm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 runtime/queries/gitcommit/highlights.scm diff --git a/runtime/queries/gitcommit/highlights.scm b/runtime/queries/gitcommit/highlights.scm new file mode 100644 index 000000000..04d704161 --- /dev/null +++ b/runtime/queries/gitcommit/highlights.scm @@ -0,0 +1,18 @@ +(subject) @markup.heading +(path) @string.special.path +(branch) @string.special.symbol +(commit) @constant +(item) @markup.link.url +(header) @tag + +(change kind: "new file" @diff.plus) +(change kind: "deleted" @diff.minus) +(change kind: "modified" @diff.delta) +(change kind: "renamed" @diff.delta.moved) + +[":" "->"] @punctuation.delimeter +(comment) @comment + +; once we have diff injections, @comment should become @none +((comment (scissors)) + (message)+ @comment) From 3b800025af0bdf7e8179cbb42ad0b4374b9005f1 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Dec 2021 09:54:23 -0600 Subject: [PATCH 09/21] add diff.{plus,minus,delta} to themes --- base16_theme.toml | 4 ++++ runtime/themes/base16_default_dark.toml | 4 ++++ runtime/themes/base16_default_light.toml | 4 ++++ runtime/themes/base16_terminal.toml | 4 ++++ runtime/themes/bogster.toml | 4 ++++ runtime/themes/dark_plus.toml | 4 ++++ runtime/themes/dracula.toml | 4 ++++ runtime/themes/everforest_dark.toml | 4 ++++ runtime/themes/gruvbox.toml | 4 ++++ runtime/themes/ingrid.toml | 4 ++++ runtime/themes/monokai.toml | 6 +++++- runtime/themes/monokai_pro.toml | 5 +++++ runtime/themes/monokai_pro_machine.toml | 5 +++++ runtime/themes/monokai_pro_octagon.toml | 5 +++++ runtime/themes/monokai_pro_ristretto.toml | 5 +++++ runtime/themes/monokai_pro_spectrum.toml | 5 +++++ runtime/themes/nord.toml | 5 +++++ runtime/themes/onedark.toml | 4 ++++ runtime/themes/rose_pine.toml | 3 +++ runtime/themes/rose_pine_dawn.toml | 3 +++ runtime/themes/solarized_dark.toml | 4 ++++ runtime/themes/solarized_light.toml | 4 ++++ runtime/themes/spacebones_light.toml | 4 ++++ theme.toml | 4 ++++ 24 files changed, 101 insertions(+), 1 deletion(-) diff --git a/base16_theme.toml b/base16_theme.toml index 5ec74bcc6..bb60a3ea5 100644 --- a/base16_theme.toml +++ b/base16_theme.toml @@ -29,6 +29,10 @@ "namespace" = "magenta" "ui.help" = { fg = "white", bg = "black" } +"diff.plus" = "green" +"diff.delta" = "yellow" +"diff.minus" = "red" + "diagnostic" = { modifiers = ["underlined"] } "ui.gutter" = { bg = "black" } "info" = "blue" diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml index fd4b0fea3..33ff87fae 100644 --- a/runtime/themes/base16_default_dark.toml +++ b/runtime/themes/base16_default_dark.toml @@ -33,6 +33,10 @@ "namespace" = "base0E" "ui.help" = { fg = "base06", bg = "base01" } +"diff.plus" = "base0B" +"diff.delta" = "base09" +"diff.minus" = "base08" + "diagnostic" = { modifiers = ["underlined"] } "ui.gutter" = { bg = "base01" } "info" = "base0D" diff --git a/runtime/themes/base16_default_light.toml b/runtime/themes/base16_default_light.toml index 596990dad..3f4de2654 100644 --- a/runtime/themes/base16_default_light.toml +++ b/runtime/themes/base16_default_light.toml @@ -33,6 +33,10 @@ "namespace" = "base0E" "ui.help" = { fg = "base06", bg = "base01" } +"diff.plus" = "base0B" +"diff.delta" = "base09" +"diff.minus" = "base08" + "diagnostic" = { modifiers = ["underlined"] } "ui.gutter" = { bg = "base01" } "info" = "base0D" diff --git a/runtime/themes/base16_terminal.toml b/runtime/themes/base16_terminal.toml index 123ceaea6..cbbfbf241 100644 --- a/runtime/themes/base16_terminal.toml +++ b/runtime/themes/base16_terminal.toml @@ -30,6 +30,10 @@ "namespace" = "light-magenta" "ui.help" = { fg = "white", bg = "black" } +"diff.plus" = "light-green" +"diff.delta" = "yellow" +"diff.minus" = "light-red" + "diagnostic" = { modifiers = ["underlined"] } "ui.gutter" = { bg = "black" } "info" = "light-blue" diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml index 86a6c34bf..ea6844f2c 100644 --- a/runtime/themes/bogster.toml +++ b/runtime/themes/bogster.toml @@ -28,6 +28,10 @@ "module" = "#d32c5d" +"diff.plus" = "#59dcb7" +"diff.delta" = "#dc7759" +"diff.minus" = "#dc597f" + "ui.background" = { bg = "#161c23" } "ui.linenr" = { fg = "#415367" } "ui.linenr.selected" = { fg = "#e5ded6" } # TODO diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index 0554f827f..784376493 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -39,6 +39,10 @@ "constant.numeric" = { fg = "pale_green" } "constant.character.escape" = { fg = "gold" } +"diff.plus" = { fg = "pale_green" } +"diff.delta" = { fg = "gold" } +"diff.minus" = { fg = "red" } + "ui.background" = { fg = "light_gray", bg = "dark_gray2" } "ui.window" = { bg = "widget" } diff --git a/runtime/themes/dracula.toml b/runtime/themes/dracula.toml index 956fd0f5d..f01c73230 100644 --- a/runtime/themes/dracula.toml +++ b/runtime/themes/dracula.toml @@ -15,6 +15,10 @@ "variable.builtin" = { fg = "cyan", modifiers = ["italic"] } "variable.parameter" = { fg ="orange", modifiers = ["italic"] } +"diff.plus" = { fg = "green" } +"diff.delta" = { fg = "orange" } +"diff.minus" = { fg = "red" } + "ui.background" = { fg = "foreground", bg = "background" } "ui.cursor" = { fg = "background", bg = "orange", modifiers = ["dim"] } "ui.cursor.match" = { fg = "green", modifiers = ["underlined"] } diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml index bbd005e6a..3be9f1f0e 100644 --- a/runtime/themes/everforest_dark.toml +++ b/runtime/themes/everforest_dark.toml @@ -34,6 +34,10 @@ "module" = "blue" "special" = "orange" +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "red" + "ui.background" = { bg = "bg0" } "ui.cursor" = { fg = "bg0", bg = "fg" } "ui.cursor.match" = { fg = "orange", bg = "bg_yellow" } diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml index 0ff039eab..9eae09152 100644 --- a/runtime/themes/gruvbox.toml +++ b/runtime/themes/gruvbox.toml @@ -28,6 +28,10 @@ "label" = "aqua1" "module" = "aqua1" +"diff.plus" = "green1" +"diff.delta" = "orange1" +"diff.minus" = "red1" + "warning" = { fg = "orange1", bg = "bg1" } "error" = { fg = "red1", bg = "bg1" } "info" = { fg = "aqua1", bg = "bg1" } diff --git a/runtime/themes/ingrid.toml b/runtime/themes/ingrid.toml index 308294759..df8a922fd 100644 --- a/runtime/themes/ingrid.toml +++ b/runtime/themes/ingrid.toml @@ -28,6 +28,10 @@ "module" = "#839A53" +"diff.plus" = "#839A53" +"diff.delta" = "#D4A520" +"diff.minus" = "#D74E50" + "ui.background" = { bg = "#FFFCFD" } "ui.linenr" = { fg = "#bbbbbb" } "ui.linenr.selected" = { fg = "#F3EAE9" } # TODO diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml index 38f9f1707..c8bf8ebd8 100644 --- a/runtime/themes/monokai.toml +++ b/runtime/themes/monokai.toml @@ -39,6 +39,10 @@ "constant.numeric" = { fg = "#ae81ff" } "constant.character.escape" = { fg = "#ae81ff" } +"diff.plus" = { fg = "#a6e22e" } +"diff.delta" = { fg = "#fd971f" } +"diff.minus" = { fg = "#f92672" } + "ui.background" = { fg = "text", bg = "background" } "ui.window" = { bg = "widget" } @@ -65,7 +69,7 @@ "warning" = { fg = "#cca700" } "error" = { fg = "#f48771" } "info" = { fg = "#75beff" } -"hint" = { fg = "#eeeeeeb3" } +"hint" = { fg = "#eeeeeb3" } diagnostic = { modifiers = ["underlined"] } diff --git a/runtime/themes/monokai_pro.toml b/runtime/themes/monokai_pro.toml index bf8a4a84e..5daeaf6cf 100644 --- a/runtime/themes/monokai_pro.toml +++ b/runtime/themes/monokai_pro.toml @@ -77,6 +77,11 @@ # integer, floating point "constant.numeric" = "purple" +# vcs +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "red" + # make diagnostic underlined, to distinguish with selection text. diagnostic = { modifiers = ["underlined"] } diff --git a/runtime/themes/monokai_pro_machine.toml b/runtime/themes/monokai_pro_machine.toml index d8a701f11..0763a5fb6 100644 --- a/runtime/themes/monokai_pro_machine.toml +++ b/runtime/themes/monokai_pro_machine.toml @@ -77,6 +77,11 @@ # integer, floating point "constant.numeric" = "purple" +# vcs +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "red" + # make diagnostic underlined, to distinguish with selection text. diagnostic = { modifiers = ["underlined"] } diff --git a/runtime/themes/monokai_pro_octagon.toml b/runtime/themes/monokai_pro_octagon.toml index 74459472a..6a74a8d0a 100644 --- a/runtime/themes/monokai_pro_octagon.toml +++ b/runtime/themes/monokai_pro_octagon.toml @@ -77,6 +77,11 @@ # integer, floating point "constant.numeric" = "purple" +# vcs +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "red" + # make diagnostic underlined, to distinguish with selection text. diagnostic = { modifiers = ["underlined"] } diff --git a/runtime/themes/monokai_pro_ristretto.toml b/runtime/themes/monokai_pro_ristretto.toml index a9cf4b34a..1a1a32ffa 100644 --- a/runtime/themes/monokai_pro_ristretto.toml +++ b/runtime/themes/monokai_pro_ristretto.toml @@ -77,6 +77,11 @@ # integer, floating point "constant.numeric" = "purple" +# vcs +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "red" + # make diagnostic underlined, to distinguish with selection text. diagnostic = { modifiers = ["underlined"] } diff --git a/runtime/themes/monokai_pro_spectrum.toml b/runtime/themes/monokai_pro_spectrum.toml index 232adfbdf..366304939 100644 --- a/runtime/themes/monokai_pro_spectrum.toml +++ b/runtime/themes/monokai_pro_spectrum.toml @@ -77,6 +77,11 @@ # integer, floating point "constant.numeric" = "purple" +# vcs +"diff.plus" = "green" +"diff.delta" = "orange" +"diff.minus" = "red" + # make diagnostic underlined, to distinguish with selection text. diagnostic = { modifiers = ["underlined"] } diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index a619f902e..ae1ea29be 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -84,6 +84,11 @@ # nord15 - integer, floating point "constant.numeric" = "nord15" +# vcs +"diff.plus" = "nord14" +"diff.delta" = "nord12" +"diff.minus" = "nord11" + [palette] nord0 = "#2e3440" nord1 = "#3b4252" diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index 4ab1dd95b..dfeadbb29 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -36,6 +36,10 @@ "markup.link.url" = { fg = "cyan", modifiers = ["underlined"]} "markup.link.label" = { fg = "purple" } +"diff.plus" = "green" +"diff.delta" = "gold" +"diff.minus" = "red" + diagnostic = { modifiers = ["underlined"] } "info" = { fg = "blue", modifiers = ["bold"] } "hint" = { fg = "green", modifiers = ["bold"] } diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml index a3c12bcef..bf3a040ba 100644 --- a/runtime/themes/rose_pine.toml +++ b/runtime/themes/rose_pine.toml @@ -38,6 +38,9 @@ "ui.window" = { bg = "base" } "ui.help" = { bg = "overlay", fg = "foam" } "text" = "text" +"diff.plus" = "foam" +"diff.delta" = "rose" +"diff.minus" = "love" "info" = "gold" "hint" = "gold" diff --git a/runtime/themes/rose_pine_dawn.toml b/runtime/themes/rose_pine_dawn.toml index 6654c8c9a..cc6d287e0 100644 --- a/runtime/themes/rose_pine_dawn.toml +++ b/runtime/themes/rose_pine_dawn.toml @@ -38,6 +38,9 @@ "ui.window" = { bg = "base" } "ui.help" = { bg = "overlay", fg = "foam" } "text" = "text" +"diff.plus" = "foam" +"diff.delta" = "rose" +"diff.minus" = "love" "info" = "gold" "hint" = "gold" diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml index 979fdaac7..ab345c8db 100644 --- a/runtime/themes/solarized_dark.toml +++ b/runtime/themes/solarized_dark.toml @@ -22,6 +22,10 @@ "module" = { fg = "violet" } "tag" = { fg = "magenta" } +"diff.plus" = { fg = "green" } +"diff.delta" = { fg = "orange" } +"diff.minus" = { fg = "red" } + # 背景 "ui.background" = { bg = "base03" } diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml index ded90cc48..6b0137fb7 100644 --- a/runtime/themes/solarized_light.toml +++ b/runtime/themes/solarized_light.toml @@ -22,6 +22,10 @@ "module" = { fg = "violet" } "tag" = { fg = "magenta" } +"diff.plus" = { fg = "green" } +"diff.delta" = { fg = "orange" } +"diff.minus" = { fg = "red" } + # 背景 "ui.background" = { bg = "base03" } diff --git a/runtime/themes/spacebones_light.toml b/runtime/themes/spacebones_light.toml index 92f116ab9..fcbe7b371 100644 --- a/runtime/themes/spacebones_light.toml +++ b/runtime/themes/spacebones_light.toml @@ -30,6 +30,10 @@ "label" = "#b1951d" "module" = "#b1951d" +"diff.plus" = "#2d9574" +"diff.delta" = "#715ab1" +"diff.minus" = "#ba2f59" + "warning" = { fg = "#da8b55" } "error" = { fg = "#e0211d" } "info" = { fg = "#b1951d" } diff --git a/theme.toml b/theme.toml index b316e8142..ca0b28057 100644 --- a/theme.toml +++ b/theme.toml @@ -34,6 +34,10 @@ label = "honey" "markup.link.url" = { fg = "silver", modifiers = ["underlined"] } "markup.raw" = "almond" +"diff.plus" = "#35bf86" +"diff.minus" = "#f22c86" +"diff.delta" = "#6f44f0" + # TODO: diferentiate doc comment # concat (ERROR) @error.syntax and "MISSING ;" selectors for errors From c1f4c0e67acc65a263440fce98514c9e6de1940d Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 22 Dec 2021 09:58:51 -0600 Subject: [PATCH 10/21] add new scopes to themes docs --- book/src/themes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/book/src/themes.md b/book/src/themes.md index fce2c0ca4..8eee334b9 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -178,6 +178,12 @@ We use a similar set of scopes as - `inline` - `block` +- `diff` - version control changes + - `plus` - additions + - `minus` - deletions + - `delta` - modifications + - `moved` - renamed or moved files/changes + #### Interface These scopes are used for theming the editor interface. From 28c9afdd0e83da37c782fc82b859d8451df1b877 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Thu, 23 Dec 2021 09:53:01 -0600 Subject: [PATCH 11/21] add commented-out diff and rebase injection queries --- runtime/queries/gitcommit/injections.scm | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 runtime/queries/gitcommit/injections.scm diff --git a/runtime/queries/gitcommit/injections.scm b/runtime/queries/gitcommit/injections.scm new file mode 100644 index 000000000..2837a5865 --- /dev/null +++ b/runtime/queries/gitcommit/injections.scm @@ -0,0 +1,15 @@ +; once a diff grammar is available, we can inject diff highlighting into the +; trailer after scissors (git commit --verbose) +; see https://github.com/helix-editor/helix/pull/1338#issuecomment-1000013539 +; +; ((comment (scissors)) +; (message) @injection.content +; (#set! injection.language "diff")) + +; --- + +; once a rebase grammar is available, we can inject rebase highlighting into +; interactive rebase summary sections like so: +; +; ((rebase_command) @injection.content +; (#set! injection.language "git-rebase")) From c3fb86cbaa5c6973fe014a4401c4e0d6f663384d Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 24 Dec 2021 16:49:27 -0600 Subject: [PATCH 12/21] tree-sitter-gitcommit->tree-sitter-git-commit --- .gitmodules | 6 +++--- book/src/generated/lang-support.md | 2 +- helix-syntax/languages/tree-sitter-git-commit | 1 + helix-syntax/languages/tree-sitter-gitcommit | 1 - languages.toml | 2 +- runtime/queries/{gitcommit => git-commit}/highlights.scm | 0 runtime/queries/{gitcommit => git-commit}/injections.scm | 0 7 files changed, 6 insertions(+), 6 deletions(-) create mode 160000 helix-syntax/languages/tree-sitter-git-commit delete mode 160000 helix-syntax/languages/tree-sitter-gitcommit rename runtime/queries/{gitcommit => git-commit}/highlights.scm (100%) rename runtime/queries/{gitcommit => git-commit}/injections.scm (100%) diff --git a/.gitmodules b/.gitmodules index ad100a003..d5bd61c9e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -170,7 +170,7 @@ path = helix-syntax/languages/tree-sitter-fish url = https://github.com/ram02z/tree-sitter-fish shallow = true -[submodule "helix-syntax/languages/tree-sitter-gitcommit"] - path = helix-syntax/languages/tree-sitter-gitcommit - url = https://github.com/the-mikedavis/tree-sitter-gitcommit.git +[submodule "helix-syntax/languages/tree-sitter-git-commit"] + path = helix-syntax/languages/tree-sitter-git-commit + url = https://github.com/the-mikedavis/tree-sitter-git-commit.git shallow = true diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index d6e0d9af3..9c42005bb 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -11,7 +11,7 @@ | dockerfile | ✓ | | | `docker-langserver` | | elixir | ✓ | | | `elixir-ls` | | fish | ✓ | ✓ | ✓ | | -| gitcommit | ✓ | | | | +| git-commit | ✓ | | | | | glsl | ✓ | | ✓ | | | go | ✓ | ✓ | ✓ | `gopls` | | html | ✓ | | | | diff --git a/helix-syntax/languages/tree-sitter-git-commit b/helix-syntax/languages/tree-sitter-git-commit new file mode 160000 index 000000000..5cd4776c8 --- /dev/null +++ b/helix-syntax/languages/tree-sitter-git-commit @@ -0,0 +1 @@ +Subproject commit 5cd4776c86c82d9d6afdc8c73a47a08057aef618 diff --git a/helix-syntax/languages/tree-sitter-gitcommit b/helix-syntax/languages/tree-sitter-gitcommit deleted file mode 160000 index 6a2ddbecd..000000000 --- a/helix-syntax/languages/tree-sitter-gitcommit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6a2ddbecd49fa8e7e1fda24d43e363cfd9171ca0 diff --git a/languages.toml b/languages.toml index f73011c8e..616ef234e 100644 --- a/languages.toml +++ b/languages.toml @@ -475,7 +475,7 @@ indent = { tab-width = 2, unit = " " } language-server = { command = "docker-langserver", args = ["--stdio"] } [[language]] -name = "gitcommit" +name = "git-commit" scope = "git.commitmsg" roots = [] file-types = ["COMMIT_EDITMSG"] diff --git a/runtime/queries/gitcommit/highlights.scm b/runtime/queries/git-commit/highlights.scm similarity index 100% rename from runtime/queries/gitcommit/highlights.scm rename to runtime/queries/git-commit/highlights.scm diff --git a/runtime/queries/gitcommit/injections.scm b/runtime/queries/git-commit/injections.scm similarity index 100% rename from runtime/queries/gitcommit/injections.scm rename to runtime/queries/git-commit/injections.scm From 6af0d51dc5510be43dd6688ee1053ce01b601d83 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 24 Dec 2021 18:35:29 -0600 Subject: [PATCH 13/21] highlight rebase-commands as markup.raw --- runtime/queries/git-commit/highlights.scm | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime/queries/git-commit/highlights.scm b/runtime/queries/git-commit/highlights.scm index 04d704161..a74bb3817 100644 --- a/runtime/queries/git-commit/highlights.scm +++ b/runtime/queries/git-commit/highlights.scm @@ -4,6 +4,7 @@ (commit) @constant (item) @markup.link.url (header) @tag +(rebase_command) @markup.raw (change kind: "new file" @diff.plus) (change kind: "deleted" @diff.minus) From a306a1052a51c686b24a6f339190878b8029a894 Mon Sep 17 00:00:00 2001 From: Tamo Date: Sun, 26 Dec 2021 02:04:33 +0100 Subject: [PATCH 14/21] Update settings at runtime (#798) * feat: Update settings at runtime fix the clippy warning * update the documentation * use to_value instead of to_vec+from_value * drop the equal * remove an useless comment * apply suggestion --- book/src/generated/typable-cmd.md | 1 + helix-term/src/commands.rs | 37 +++++++++++++++++++++++++++++++ helix-term/src/ui/mod.rs | 27 ++++++++++++++++++++++ helix-view/src/editor.rs | 20 +++++++++++++---- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/book/src/generated/typable-cmd.md b/book/src/generated/typable-cmd.md index bb21fd6b3..f12082bb1 100644 --- a/book/src/generated/typable-cmd.md +++ b/book/src/generated/typable-cmd.md @@ -41,3 +41,4 @@ | `:hsplit`, `:hs`, `:sp` | Open the file in a horizontal split. | | `:tutor` | Open the tutorial. | | `:goto`, `:g` | Go to line number. | +| `:set-option`, `:set` | Set a config option at runtime | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ee6a59894..7b1235f81 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2637,6 +2637,36 @@ pub mod cmd { let (view, doc) = current!(cx.editor); view.ensure_cursor_in_view(doc, line); + Ok(()) + } + + fn setting( + cx: &mut compositor::Context, + args: &[Cow], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let runtime_config = &mut cx.editor.config; + + if args.len() != 2 { + anyhow::bail!("Bad arguments. Usage: `:set key field`"); + } + + let (key, arg) = (&args[0].to_lowercase(), &args[1]); + + match key.as_ref() { + "scrolloff" => runtime_config.scrolloff = arg.parse()?, + "scroll-lines" => runtime_config.scroll_lines = arg.parse()?, + "mouse" => runtime_config.mouse = arg.parse()?, + "line-number" => runtime_config.line_number = arg.parse()?, + "middle-click_paste" => runtime_config.middle_click_paste = arg.parse()?, + "smart-case" => runtime_config.smart_case = arg.parse()?, + "auto-pairs" => runtime_config.auto_pairs = arg.parse()?, + "auto-completion" => runtime_config.auto_completion = arg.parse()?, + "completion-trigger-len" => runtime_config.completion_trigger_len = arg.parse()?, + "auto-info" => runtime_config.auto_info = arg.parse()?, + "true-color" => runtime_config.true_color = arg.parse()?, + _ => anyhow::bail!("Unknown key `{}`.", args[0]), + } Ok(()) } @@ -2928,6 +2958,13 @@ pub mod cmd { doc: "Go to line number.", fun: goto_line_number, completer: None, + }, + TypableCommand { + name: "set-option", + aliases: &["set"], + doc: "Set a config option at runtime", + fun: setting, + completer: Some(completers::setting), } ]; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index f57e2e2bd..9e096311f 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -174,7 +174,9 @@ pub mod completers { use crate::ui::prompt::Completion; use fuzzy_matcher::skim::SkimMatcherV2 as Matcher; use fuzzy_matcher::FuzzyMatcher; + use helix_view::editor::Config; use helix_view::theme; + use once_cell::sync::Lazy; use std::borrow::Cow; use std::cmp::Reverse; @@ -208,6 +210,31 @@ pub mod completers { names } + pub fn setting(input: &str) -> Vec { + static KEYS: Lazy> = Lazy::new(|| { + serde_json::to_value(Config::default()) + .unwrap() + .as_object() + .unwrap() + .keys() + .cloned() + .collect() + }); + + let matcher = Matcher::default(); + + let mut matches: Vec<_> = KEYS + .iter() + .filter_map(|name| matcher.fuzzy_match(name, input).map(|score| (name, score))) + .collect(); + + matches.sort_unstable_by_key(|(_file, score)| Reverse(*score)); + matches + .into_iter() + .map(|(name, _)| ((0..), name.into())) + .collect() + } + pub fn filename(input: &str) -> Vec { filename_impl(input, |entry| { let is_dir = entry.file_type().map_or(false, |entry| entry.is_dir()); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index fd6eb4d57..f4b0f73e7 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -27,7 +27,7 @@ pub use helix_core::register::Registers; use helix_core::syntax; use helix_core::{Position, Selection}; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; fn deserialize_duration_millis<'de, D>(deserializer: D) -> Result where @@ -37,7 +37,7 @@ where Ok(Duration::from_millis(millis)) } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct FilePickerConfig { /// IgnoreOptions @@ -77,7 +77,7 @@ impl Default for FilePickerConfig { } } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case", default, deny_unknown_fields)] pub struct Config { /// Padding to keep between the edge of the screen and the cursor when scrolling. Defaults to 5. @@ -109,7 +109,7 @@ pub struct Config { pub true_color: bool, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { /// Show absolute line number @@ -119,6 +119,18 @@ pub enum LineNumber { Relative, } +impl std::str::FromStr for LineNumber { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "absolute" | "abs" => Ok(Self::Absolute), + "relative" | "rel" => Ok(Self::Relative), + _ => anyhow::bail!("Line number can only be `absolute` or `relative`."), + } + } +} + impl Default for Config { fn default() -> Self { Self { From c7a59e24e6e1609dd65e5ee66fef52f76aabe9b7 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 12 Dec 2021 21:09:01 +0800 Subject: [PATCH 15/21] Switch macro Q and q --- book/src/keymap.md | 4 ++-- helix-term/src/keymap.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index d84b17b8e..e1df545d5 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -77,8 +77,8 @@ | `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | | `Ctrl-a` | Increment object (number) under cursor | `increment` | | `Ctrl-x` | Decrement object (number) under cursor | `decrement` | -| `q` | Start/stop macro recording to the selected register | `record_macro` | -| `Q` | Play back a recorded macro from the selected register | `play_macro` | +| `Q` | Start/stop macro recording to the selected register | `record_macro` | +| `q` | Play back a recorded macro from the selected register | `play_macro` | #### Shell diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 257d5f296..917a0990f 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -593,8 +593,8 @@ impl Default for Keymaps { // paste_all "P" => paste_before, - "q" => record_macro, - "Q" => play_macro, + "Q" => record_macro, + "q" => play_macro, ">" => indent, "<" => unindent, From 5326a051176d30060e60a8c8d6e718c9ca8a32d7 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 12 Dec 2021 21:32:55 +0800 Subject: [PATCH 16/21] Improve macro error handling --- helix-term/src/commands.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7b1235f81..e3944cd75 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6031,23 +6031,17 @@ fn record_macro(cx: &mut Context) { fn play_macro(cx: &mut Context) { let reg = cx.register.unwrap_or('@'); - let keys = match cx - .editor - .registers - .get(reg) - .and_then(|reg| reg.read().get(0)) - .context("Register empty") - .and_then(|s| { - s.split_whitespace() - .map(str::parse::) - .collect::, _>>() - .context("Failed to parse macro") - }) { - Ok(keys) => keys, - Err(e) => { - cx.editor.set_error(format!("{}", e)); - return; + let keys: Vec = if let Some([keys]) = cx.editor.registers.read(reg) { + match keys.split_whitespace().map(str::parse).collect() { + Ok(keys) => keys, + Err(err) => { + cx.editor.set_error(format!("Invalid macro: {}", err)); + return; + } } + } else { + cx.editor.set_error(format!("Register [{}] empty", reg)); + return; }; let count = cx.count(); From 9a32617b3093ce6ab3b925d9f1c6b17218c1b491 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 12 Dec 2021 21:51:57 +0800 Subject: [PATCH 17/21] Rename play macro to replay macro Macro needs to be defined first before playing so replay is more accurate. Also, replay have the same length as record which makes it looks nice. --- book/src/keymap.md | 2 +- helix-term/src/commands.rs | 6 +++--- helix-term/src/keymap.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index e1df545d5..b7a1e54b7 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -78,7 +78,7 @@ | `Ctrl-a` | Increment object (number) under cursor | `increment` | | `Ctrl-x` | Decrement object (number) under cursor | `decrement` | | `Q` | Start/stop macro recording to the selected register | `record_macro` | -| `q` | Play back a recorded macro from the selected register | `play_macro` | +| `q` | Play back a recorded macro from the selected register | `replay_macro` | #### Shell diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e3944cd75..ac42682f1 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -396,7 +396,7 @@ impl MappableCommand { increment, "Increment", decrement, "Decrement", record_macro, "Record macro", - play_macro, "Play macro", + replay_macro, "Replay macro", ); } @@ -6015,7 +6015,7 @@ fn record_macro(cx: &mut Context) { keys.pop(); let s = keys .into_iter() - .map(|key| format!("{}", key)) + .map(|key| key.to_string()) .collect::>() .join(" "); cx.editor.registers.get_mut(reg).write(vec![s]); @@ -6029,7 +6029,7 @@ fn record_macro(cx: &mut Context) { } } -fn play_macro(cx: &mut Context) { +fn replay_macro(cx: &mut Context) { let reg = cx.register.unwrap_or('@'); let keys: Vec = if let Some([keys]) = cx.editor.registers.read(reg) { match keys.split_whitespace().map(str::parse).collect() { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 917a0990f..08c8032bb 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -594,7 +594,7 @@ impl Default for Keymaps { "P" => paste_before, "Q" => record_macro, - "q" => play_macro, + "q" => replay_macro, ">" => indent, "<" => unindent, From b9cb3930e2838347a97f501e025ee52ea377ed52 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 12 Dec 2021 21:54:14 +0800 Subject: [PATCH 18/21] Mark macros as experimental in docs Given that currently macro does not integrate well with registers and the internal representation of macros is expected to be changed. --- book/src/keymap.md | 66 +++++++++++++++++++------------------- helix-term/src/commands.rs | 1 + 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/book/src/keymap.md b/book/src/keymap.md index b7a1e54b7..581e70e93 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -46,39 +46,39 @@ ### Changes -| Key | Description | Command | -| ----- | ----------- | ------- | -| `r` | Replace with a character | `replace` | -| `R` | Replace with yanked text | `replace_with_yanked` | -| `~` | Switch case of the selected text | `switch_case` | -| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | -| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | -| `i` | Insert before selection | `insert_mode` | -| `a` | Insert after selection (append) | `append_mode` | -| `I` | Insert at the start of the line | `prepend_to_line` | -| `A` | Insert at the end of the line | `append_to_line` | -| `o` | Open new line below selection | `open_below` | -| `O` | Open new line above selection | `open_above` | -| `.` | Repeat last change | N/A | -| `u` | Undo change | `undo` | -| `U` | Redo change | `redo` | -| `Alt-u` | Move backward in history | `earlier` | -| `Alt-U` | Move forward in history | `later` | -| `y` | Yank selection | `yank` | -| `p` | Paste after selection | `paste_after` | -| `P` | Paste before selection | `paste_before` | -| `"` `` | Select a register to yank to or paste from | `select_register` | -| `>` | Indent selection | `indent` | -| `<` | Unindent selection | `unindent` | -| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` | -| `d` | Delete selection | `delete_selection` | -| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` | -| `c` | Change selection (delete and enter insert mode) | `change_selection` | -| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | -| `Ctrl-a` | Increment object (number) under cursor | `increment` | -| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | -| `Q` | Start/stop macro recording to the selected register | `record_macro` | -| `q` | Play back a recorded macro from the selected register | `replay_macro` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `r` | Replace with a character | `replace` | +| `R` | Replace with yanked text | `replace_with_yanked` | +| `~` | Switch case of the selected text | `switch_case` | +| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | +| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | +| `i` | Insert before selection | `insert_mode` | +| `a` | Insert after selection (append) | `append_mode` | +| `I` | Insert at the start of the line | `prepend_to_line` | +| `A` | Insert at the end of the line | `append_to_line` | +| `o` | Open new line below selection | `open_below` | +| `O` | Open new line above selection | `open_above` | +| `.` | Repeat last change | N/A | +| `u` | Undo change | `undo` | +| `U` | Redo change | `redo` | +| `Alt-u` | Move backward in history | `earlier` | +| `Alt-U` | Move forward in history | `later` | +| `y` | Yank selection | `yank` | +| `p` | Paste after selection | `paste_after` | +| `P` | Paste before selection | `paste_before` | +| `"` `` | Select a register to yank to or paste from | `select_register` | +| `>` | Indent selection | `indent` | +| `<` | Unindent selection | `unindent` | +| `=` | Format selection (currently nonfunctional/disabled) (**LSP**) | `format_selections` | +| `d` | Delete selection | `delete_selection` | +| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` | +| `c` | Change selection (delete and enter insert mode) | `change_selection` | +| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | +| `Ctrl-a` | Increment object (number) under cursor | `increment` | +| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | +| `Q` | Start/stop macro recording to the selected register (experimental) | `record_macro` | +| `q` | Play back a recorded macro from the selected register (experimental) | `replay_macro` | #### Shell diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ac42682f1..d0a941608 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6031,6 +6031,7 @@ fn record_macro(cx: &mut Context) { fn replay_macro(cx: &mut Context) { let reg = cx.register.unwrap_or('@'); + // TODO: macro keys should be parsed one by one and not space delimited (see kak) let keys: Vec = if let Some([keys]) = cx.editor.registers.read(reg) { match keys.split_whitespace().map(str::parse).collect() { Ok(keys) => keys, From ee3eb4057a1c8a0c979371674451105ac5453b58 Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sun, 12 Dec 2021 22:05:11 +0800 Subject: [PATCH 19/21] Update macro display as [q] in message --- helix-term/src/commands.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index d0a941608..fdc242fe6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6020,12 +6020,12 @@ fn record_macro(cx: &mut Context) { .join(" "); cx.editor.registers.get_mut(reg).write(vec![s]); cx.editor - .set_status(format!("Recorded to register {}", reg)); + .set_status(format!("Recorded to register [{}]", reg)); } else { let reg = cx.register.take().unwrap_or('@'); cx.editor.macro_recording = Some((reg, Vec::new())); cx.editor - .set_status(format!("Recording to register {}", reg)); + .set_status(format!("Recording to register [{}]", reg)); } } From 2d4bc0aec743c6d62344da6fda2c0c84e75a82be Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sun, 12 Dec 2021 10:36:52 -0500 Subject: [PATCH 20/21] Change how macros separate keypresses * Keypresses are no longer separated by spaces * Single-character keypresses are serialized as-is * Multi-character keypresses are delimited by `<>` --- helix-term/src/commands.rs | 46 +++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index fdc242fe6..4e0f3ef78 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6015,9 +6015,15 @@ fn record_macro(cx: &mut Context) { keys.pop(); let s = keys .into_iter() - .map(|key| key.to_string()) - .collect::>() - .join(" "); + .map(|key| { + let s = key.to_string(); + if s.chars().count() == 1 { + s + } else { + format!("<{}>", s) + } + }) + .collect::(); cx.editor.registers.get_mut(reg).write(vec![s]); cx.editor .set_status(format!("Recorded to register [{}]", reg)); @@ -6032,8 +6038,38 @@ fn record_macro(cx: &mut Context) { fn replay_macro(cx: &mut Context) { let reg = cx.register.unwrap_or('@'); // TODO: macro keys should be parsed one by one and not space delimited (see kak) - let keys: Vec = if let Some([keys]) = cx.editor.registers.read(reg) { - match keys.split_whitespace().map(str::parse).collect() { + let keys: Vec = if let Some([keys_str]) = cx.editor.registers.read(reg) { + let mut keys_res: anyhow::Result<_> = Ok(Vec::new()); + let mut i = 0; + while let Ok(keys) = &mut keys_res { + if i >= keys_str.len() { + break; + } + if !keys_str.is_char_boundary(i) { + i += 1; + continue; + } + + let s = &keys_str[i..]; + let mut end_i = 1; + while !s.is_char_boundary(end_i) { + end_i += 1; + } + let c = &s[..end_i]; + if c != "<" { + keys.push(c); + i += end_i; + } else { + match s.find('>').context("'>' expected") { + Ok(end_i) => { + keys.push(&s[1..end_i]); + i += end_i + 1; + } + Err(err) => keys_res = Err(err), + } + } + } + match keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect()) { Ok(keys) => keys, Err(err) => { cx.editor.set_error(format!("Invalid macro: {}", err)); From 8340d73545a0757ff3ddd83d019b3d41a923f017 Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sat, 25 Dec 2021 03:38:14 -0500 Subject: [PATCH 21/21] Extract macro parsing to `helix-view` and add unit tests --- helix-term/src/commands.rs | 35 +-------- helix-view/src/input.rs | 153 +++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 33 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4e0f3ef78..524c50ce5 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6037,39 +6037,8 @@ fn record_macro(cx: &mut Context) { fn replay_macro(cx: &mut Context) { let reg = cx.register.unwrap_or('@'); - // TODO: macro keys should be parsed one by one and not space delimited (see kak) let keys: Vec = if let Some([keys_str]) = cx.editor.registers.read(reg) { - let mut keys_res: anyhow::Result<_> = Ok(Vec::new()); - let mut i = 0; - while let Ok(keys) = &mut keys_res { - if i >= keys_str.len() { - break; - } - if !keys_str.is_char_boundary(i) { - i += 1; - continue; - } - - let s = &keys_str[i..]; - let mut end_i = 1; - while !s.is_char_boundary(end_i) { - end_i += 1; - } - let c = &s[..end_i]; - if c != "<" { - keys.push(c); - i += end_i; - } else { - match s.find('>').context("'>' expected") { - Ok(end_i) => { - keys.push(&s[1..end_i]); - i += end_i + 1; - } - Err(err) => keys_res = Err(err), - } - } - } - match keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect()) { + match helix_view::input::parse_macro(keys_str) { Ok(keys) => keys, Err(err) => { cx.editor.set_error(format!("Invalid macro: {}", err)); @@ -6080,8 +6049,8 @@ fn replay_macro(cx: &mut Context) { cx.editor.set_error(format!("Register [{}] empty", reg)); return; }; - let count = cx.count(); + let count = cx.count(); cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { for _ in 0..count { diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 92caa5176..14dadc3b9 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -254,6 +254,43 @@ impl From for crossterm::event::KeyEvent { } } +pub fn parse_macro(keys_str: &str) -> anyhow::Result> { + use anyhow::Context; + let mut keys_res: anyhow::Result<_> = Ok(Vec::new()); + let mut i = 0; + while let Ok(keys) = &mut keys_res { + if i >= keys_str.len() { + break; + } + if !keys_str.is_char_boundary(i) { + i += 1; + continue; + } + + let s = &keys_str[i..]; + let mut end_i = 1; + while !s.is_char_boundary(end_i) { + end_i += 1; + } + let c = &s[..end_i]; + if c == ">" { + keys_res = Err(anyhow!("Unmatched '>'")); + } else if c != "<" { + keys.push(c); + i += end_i; + } else { + match s.find('>').context("'>' expected") { + Ok(end_i) => { + keys.push(&s[1..end_i]); + i += end_i + 1; + } + Err(err) => keys_res = Err(err), + } + } + } + keys_res.and_then(|keys| keys.into_iter().map(str::parse).collect()) +} + #[cfg(test)] mod test { use super::*; @@ -339,4 +376,120 @@ mod test { assert!(str::parse::("123").is_err()); assert!(str::parse::("S--").is_err()); } + + #[test] + fn parsing_valid_macros() { + assert_eq!( + parse_macro("xdo").ok(), + Some(vec![ + KeyEvent { + code: KeyCode::Char('x'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('d'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('o'), + modifiers: KeyModifiers::NONE, + }, + ]), + ); + + assert_eq!( + parse_macro("vhxx").ok(), + Some(vec![ + KeyEvent { + code: KeyCode::Char('w'), + modifiers: KeyModifiers::CONTROL, + }, + KeyEvent { + code: KeyCode::Char('v'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('w'), + modifiers: KeyModifiers::CONTROL, + }, + KeyEvent { + code: KeyCode::Char('h'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('o'), + modifiers: KeyModifiers::CONTROL, + }, + KeyEvent { + code: KeyCode::Char('x'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('x'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('s'), + modifiers: KeyModifiers::ALT, + }, + ]) + ); + + assert_eq!( + parse_macro(":o foo.bar").ok(), + Some(vec![ + KeyEvent { + code: KeyCode::Char(':'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('o'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char(' '), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('f'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('o'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('o'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('.'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('b'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('a'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Char('r'), + modifiers: KeyModifiers::NONE, + }, + KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::NONE, + }, + ]) + ); + } + + #[test] + fn parsing_invalid_macros_fails() { + assert!(parse_macro("abc123").is_err()); + assert!(parse_macro("wd").is_err()); + } }