From e4f3483bd19b51ea7b80925b841f399d7eb411bb Mon Sep 17 00:00:00 2001 From: Dmitry Marakasov Date: Tue, 15 Oct 2024 19:52:50 +0300 Subject: [PATCH 1/8] Fix repology badge (#11895) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 90ebc9d16..11a909b26 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Note: Only certain languages have indentation definitions at the moment. Check [Installation documentation](https://docs.helix-editor.com/install.html). -[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg?exclude_unsupported=1)](https://repology.org/project/helix/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/helix-editor.svg?exclude_unsupported=1)](https://repology.org/project/helix-editor/versions) # Contributing From f2d54db24f8582a302fc21e64ed82be5d99a9aaa Mon Sep 17 00:00:00 2001 From: David Else <12832280+David-Else@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:53:59 +0100 Subject: [PATCH 2/8] Update repology URL after change from helix to helix-editor (#11877) * Update repology URL after change from helix to helix-editor * Update book/src/package-managers.md Co-authored-by: Michael Davis --------- Co-authored-by: Michael Davis --- book/src/package-managers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/package-managers.md b/book/src/package-managers.md index 3cfd31003..441de45e0 100644 --- a/book/src/package-managers.md +++ b/book/src/package-managers.md @@ -17,7 +17,7 @@ - [Chocolatey](#chocolatey) - [MSYS2](#msys2) -[![Packaging status](https://repology.org/badge/vertical-allrepos/helix.svg)](https://repology.org/project/helix/versions) +[![Packaging status](https://repology.org/badge/vertical-allrepos/helix-editor.svg)](https://repology.org/project/helix-editor/versions) ## Linux From d1b8129491124ce6068e95ccc58a7fefb1c9db45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 17:52:14 +0900 Subject: [PATCH 3/8] build(deps): bump cc in the rust-dependencies group (#11890) Bumps the rust-dependencies group with 1 update: [cc](https://github.com/rust-lang/cc-rs). Updates `cc` from 1.1.28 to 1.1.30 - [Release notes](https://github.com/rust-lang/cc-rs/releases) - [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.1.28...cc-v1.1.30) --- updated-dependencies: - dependency-name: cc dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rust-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e622fbe59..92795e743 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,9 +136,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.1.28" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "shlex", ] From 1437ba1e5a9e3c89b03a608cd24177c56b9cba15 Mon Sep 17 00:00:00 2001 From: langurmonkey Date: Fri, 18 Oct 2024 04:17:25 +0200 Subject: [PATCH 4/8] Add glsl_analyzer as default language server for GLSL (#11891) * Add glsl_analyzer as default language server for GLSL * Generate docs --- book/src/generated/lang-support.md | 2 +- languages.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index c5f79aa29..aac1e4746 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -69,7 +69,7 @@ | gjs | ✓ | ✓ | ✓ | `typescript-language-server`, `vscode-eslint-language-server`, `ember-language-server` | | gleam | ✓ | ✓ | | `gleam` | | glimmer | ✓ | | | `ember-language-server` | -| glsl | ✓ | ✓ | ✓ | | +| glsl | ✓ | ✓ | ✓ | `glsl_analyzer` | | gn | ✓ | | | | | go | ✓ | ✓ | ✓ | `gopls`, `golangci-lint-langserver` | | godot-resource | ✓ | ✓ | | | diff --git a/languages.toml b/languages.toml index 72b976dd0..898500ae6 100644 --- a/languages.toml +++ b/languages.toml @@ -41,6 +41,7 @@ forth-lsp = { command = "forth-lsp" } fortls = { command = "fortls", args = ["--lowercase_intrinsics"] } fsharp-ls = { command = "fsautocomplete", config = { AutomaticWorkspaceInit = true } } gleam = { command = "gleam", args = ["lsp"] } +glsl_analyzer = { command = "glsl_analyzer" } graphql-language-service = { command = "graphql-lsp", args = ["server", "-m", "stream"] } haskell-language-server = { command = "haskell-language-server-wrapper", args = ["--lsp"] } idris2-lsp = { command = "idris2-lsp" } @@ -1452,6 +1453,7 @@ file-types = ["glsl", "vert", "tesc", "tese", "geom", "frag", "comp" ] comment-token = "//" block-comment-tokens = { start = "/*", end = "*/" } indent = { tab-width = 4, unit = " " } +language-servers = [ "glsl_analyzer" ] injection-regex = "glsl" [[grammar]] From 5ab1f1eb5adb1abf7dfb899a49ad39aacab7edc5 Mon Sep 17 00:00:00 2001 From: "Ivan B." <6987136+bastaynav@users.noreply.github.com> Date: Fri, 18 Oct 2024 07:08:54 +0300 Subject: [PATCH 5/8] docs(themes): place `ui.highlight.frameline` and `ui.highlight` together (#11896) * docs(themes): place `ui.highlight.frameline` and `ui.highlight` together * docs(themes): small fix --- book/src/themes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/themes.md b/book/src/themes.md index 1bc2627dd..4e0142ddd 100644 --- a/book/src/themes.md +++ b/book/src/themes.md @@ -283,7 +283,6 @@ These scopes are used for theming the editor interface: | `ui.debug.active` | Indicator for the line at which debugging execution is paused at, found in the gutter | | `ui.gutter` | Gutter | | `ui.gutter.selected` | Gutter for the line the cursor is on | -| `ui.highlight.frameline` | Line at which debugging execution is paused at | | `ui.linenr` | Line numbers | | `ui.linenr.selected` | Line number for the line the cursor is on | | `ui.statusline` | Statusline | @@ -297,7 +296,7 @@ These scopes are used for theming the editor interface: | `ui.bufferline.background` | Style for bufferline background | | `ui.popup` | Documentation popups (e.g. Space + k) | | `ui.popup.info` | Prompt for multiple key options | -| `ui.picker.header` | Header row area in pickers with multiple columns | +| `ui.picker.header` | Header row area in pickers with multiple columns | | `ui.picker.header.column` | Column names in pickers with multiple columns | | `ui.picker.header.column.active` | The column name in pickers with multiple columns where the cursor is entering into. | | `ui.window` | Borderlines separating splits | @@ -320,6 +319,7 @@ These scopes are used for theming the editor interface: | `ui.selection` | For selections in the editing area | | `ui.selection.primary` | | | `ui.highlight` | Highlighted lines in the picker preview | +| `ui.highlight.frameline` | Line at which debugging execution is paused at | | `ui.cursorline.primary` | The line of the primary cursor ([if cursorline is enabled][editor-section]) | | `ui.cursorline.secondary` | The lines of any other cursors ([if cursorline is enabled][editor-section]) | | `ui.cursorcolumn.primary` | The column of the primary cursor ([if cursorcolumn is enabled][editor-section]) | From 855a43a266bec808b2dd06ca5a2e84326890eaea Mon Sep 17 00:00:00 2001 From: karei Date: Fri, 18 Oct 2024 07:09:11 +0300 Subject: [PATCH 6/8] Bump `jjdescription` grammar revision (#11857) --- languages.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languages.toml b/languages.toml index 898500ae6..8ade3cf8b 100644 --- a/languages.toml +++ b/languages.toml @@ -3272,7 +3272,7 @@ text-width = 72 [[grammar]] name = "jjdescription" -source = { git = "https://github.com/kareigu/tree-sitter-jjdescription", rev = "2ddec6cad07b366aee276a608e1daa2c29d3caf2" } +source = { git = "https://github.com/kareigu/tree-sitter-jjdescription", rev = "23dd3dd18ee29bdd761642511aa314215801afd8" } [[language]] name = "jq" From a1453350df5ecf9cb9a8a6cb9293e56f85cf5e5a Mon Sep 17 00:00:00 2001 From: Sebastian Dall <80460441+SebastianDall@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:12:36 +0200 Subject: [PATCH 7/8] Adding snakemake to language (#11858) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: snakemake language * feat: snakemake syntax highlighting * doc: xtask docgen - snakemake * Addressed feedback: removed redundant grammar * fixed indentation * removed has-ancestor predicate --------- Co-authored-by: “SebastianDall” <“semoda@bio.auu.dk”> --- book/src/generated/lang-support.md | 1 + languages.toml | 14 +++++ runtime/queries/snakemake/LICENSE | 20 +++++++ runtime/queries/snakemake/folds.scm | 8 +++ runtime/queries/snakemake/highlights.scm | 76 ++++++++++++++++++++++++ runtime/queries/snakemake/indents.scm | 27 +++++++++ runtime/queries/snakemake/injections.scm | 5 ++ runtime/queries/snakemake/locals.scm | 1 + 8 files changed, 152 insertions(+) create mode 100755 runtime/queries/snakemake/LICENSE create mode 100755 runtime/queries/snakemake/folds.scm create mode 100755 runtime/queries/snakemake/highlights.scm create mode 100755 runtime/queries/snakemake/indents.scm create mode 100755 runtime/queries/snakemake/injections.scm create mode 100755 runtime/queries/snakemake/locals.scm diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index aac1e4746..4f9b64de0 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -186,6 +186,7 @@ | smali | ✓ | | ✓ | | | smithy | ✓ | | | `cs` | | sml | ✓ | | | | +| snakemake | ✓ | | ✓ | `pylsp` | | solidity | ✓ | ✓ | | `solc` | | spicedb | ✓ | | | | | sql | ✓ | ✓ | | | diff --git a/languages.toml b/languages.toml index 8ade3cf8b..57d220b61 100644 --- a/languages.toml +++ b/languages.toml @@ -3835,3 +3835,17 @@ language-servers = ["circom-lsp"] [[grammar]] name = "circom" source = { git = "https://github.com/Decurity/tree-sitter-circom", rev = "02150524228b1e6afef96949f2d6b7cc0aaf999e" } + +[[language]] +name = "snakemake" +scope = "source.snakemake" +roots = ["Snakefile", "config.yaml", "environment.yaml", "workflow/"] +file-types = ["smk", "Snakefile"] +comment-tokens = ["#", "##"] +indent = { tab-width = 2, unit = " " } +language-servers = ["pylsp" ] + + +[[grammar]] +name = "snakemake" +source = { git = "https://github.com/osthomas/tree-sitter-snakemake", rev = "e909815acdbe37e69440261ebb1091ed52e1dec6" } diff --git a/runtime/queries/snakemake/LICENSE b/runtime/queries/snakemake/LICENSE new file mode 100755 index 000000000..3dea16274 --- /dev/null +++ b/runtime/queries/snakemake/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 Max Brunsfeld +Copyright (c) 2023 Oliver Thomas + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/runtime/queries/snakemake/folds.scm b/runtime/queries/snakemake/folds.scm new file mode 100755 index 000000000..d154f3cde --- /dev/null +++ b/runtime/queries/snakemake/folds.scm @@ -0,0 +1,8 @@ +; inherits: python + +[ + (rule_definition) + (rule_inheritance) + (module_definition) + (checkpoint_definition) +] @fold diff --git a/runtime/queries/snakemake/highlights.scm b/runtime/queries/snakemake/highlights.scm new file mode 100755 index 000000000..18d81811f --- /dev/null +++ b/runtime/queries/snakemake/highlights.scm @@ -0,0 +1,76 @@ +; inherits: python + +; Compound directives +[ + "rule" + "checkpoint" + "module" +] @keyword + +; Top level directives (eg. configfile, include) +(module + (directive + name: _ @keyword)) + +; Subordinate directives (eg. input, output) +((_) + body: (_ + (directive + name: _ @label))) + +; rule/module/checkpoint names +(rule_definition + name: (identifier) @type) + +(module_definition + name: (identifier) @type) + +(checkpoint_definition + name: (identifier) @type) + +; Rule imports +(rule_import + "use" @keyword.import + "rule" @keyword.import + "from" @keyword.import + "exclude"? @keyword.import + "as"? @keyword.import + "with"? @keyword.import) + +; Rule inheritance +(rule_inheritance + "use" @keyword + "rule" @keyword + "with" @keyword) + +; Wildcard names +(wildcard (identifier) @variable) +(wildcard (flag) @variable.parameter.builtin) + +; builtin variables +((identifier) @variable.builtin + (#any-of? @variable.builtin "checkpoints" "config" "gather" "rules" "scatter" "workflow")) + +; References to directive labels in wildcard interpolations +; the #any-of? queries are moved above the #has-ancestor? queries to +; short-circuit the potentially expensive tree traversal, if possible +; see: +; https://github.com/nvim-treesitter/nvim-treesitter/pull/4302#issuecomment-1685789790 +; directive labels in wildcard context +((wildcard + (identifier) @label) + (#any-of? @label "input" "log" "output" "params" "resources" "threads" "wildcards")) + +((wildcard + (attribute + object: (identifier) @label)) + (#any-of? @label "input" "log" "output" "params" "resources" "threads" "wildcards")) + +((wildcard + (subscript + value: (identifier) @label)) + (#any-of? @label "input" "log" "output" "params" "resources" "threads" "wildcards")) + +; directive labels in block context (eg. within 'run:') +((identifier) @label + (#any-of? @label "input" "log" "output" "params" "resources" "threads" "wildcards")) diff --git a/runtime/queries/snakemake/indents.scm b/runtime/queries/snakemake/indents.scm new file mode 100755 index 000000000..6685fccb1 --- /dev/null +++ b/runtime/queries/snakemake/indents.scm @@ -0,0 +1,27 @@ +; inherits: python + + +[ + (rule_definition) + (checkpoint_definition) + (rule_inheritance) + (module_definition) +] @indent + +[ + (rule_definition) + (checkpoint_definition) + (rule_inheritance) + (module_definition) +] @extend + + +(directive) @indent +(directive) @extend + +(rule_import + "with" + ":") @indent +(rule_import + "with" + ":") @extend diff --git a/runtime/queries/snakemake/injections.scm b/runtime/queries/snakemake/injections.scm new file mode 100755 index 000000000..fa56daabb --- /dev/null +++ b/runtime/queries/snakemake/injections.scm @@ -0,0 +1,5 @@ +; inherits: python + +(wildcard + (constraint) @injection.content + (#set! injection.language "regex")) diff --git a/runtime/queries/snakemake/locals.scm b/runtime/queries/snakemake/locals.scm new file mode 100755 index 000000000..0b920cbf9 --- /dev/null +++ b/runtime/queries/snakemake/locals.scm @@ -0,0 +1 @@ +; inherits: python From be2884d80061493ab1999d73ffa51dfb4f59639d Mon Sep 17 00:00:00 2001 From: TornaxO7 Date: Sat, 19 Oct 2024 11:48:07 +0200 Subject: [PATCH 8/8] Continue line comments (#10996) --- helix-core/src/comment.rs | 169 +++++++++++++++++++++++++++---------- helix-term/src/commands.rs | 97 +++++++++++++++------ 2 files changed, 192 insertions(+), 74 deletions(-) diff --git a/helix-core/src/comment.rs b/helix-core/src/comment.rs index 536b710ab..427021874 100644 --- a/helix-core/src/comment.rs +++ b/helix-core/src/comment.rs @@ -9,6 +9,24 @@ use crate::{ use helix_stdx::rope::RopeSliceExt; use std::borrow::Cow; +pub const DEFAULT_COMMENT_TOKEN: &str = "//"; + +/// Returns the longest matching comment token of the given line (if it exists). +pub fn get_comment_token<'a, S: AsRef>( + text: RopeSlice, + tokens: &'a [S], + line_num: usize, +) -> Option<&'a str> { + let line = text.line(line_num); + let start = line.first_non_whitespace_char()?; + + tokens + .iter() + .map(AsRef::as_ref) + .filter(|token| line.slice(start..).starts_with(token)) + .max_by_key(|token| token.len()) +} + /// Given text, a comment token, and a set of line indices, returns the following: /// - Whether the given lines should be considered commented /// - If any of the lines are uncommented, all lines are considered as such. @@ -28,21 +46,20 @@ fn find_line_comment( let mut min = usize::MAX; // minimum col for first_non_whitespace_char let mut margin = 1; let token_len = token.chars().count(); + for line in lines { let line_slice = text.line(line); if let Some(pos) = line_slice.first_non_whitespace_char() { let len = line_slice.len_chars(); - if pos < min { - min = pos; - } + min = std::cmp::min(min, pos); // line can be shorter than pos + token len let fragment = Cow::from(line_slice.slice(pos..std::cmp::min(pos + token.len(), len))); + // as soon as one of the non-blank lines doesn't have a comment, the whole block is + // considered uncommented. if fragment != token { - // as soon as one of the non-blank lines doesn't have a comment, the whole block is - // considered uncommented. commented = false; } @@ -56,6 +73,7 @@ fn find_line_comment( to_change.push(line); } } + (commented, to_change, min, margin) } @@ -63,7 +81,7 @@ fn find_line_comment( pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction { let text = doc.slice(..); - let token = token.unwrap_or("//"); + let token = token.unwrap_or(DEFAULT_COMMENT_TOKEN); let comment = Tendril::from(format!("{} ", token)); let mut lines: Vec = Vec::with_capacity(selection.len()); @@ -317,56 +335,87 @@ pub fn split_lines_of_selection(text: RopeSlice, selection: &Selection) -> Selec mod test { use super::*; - #[test] - fn test_find_line_comment() { - // four lines, two space indented, except for line 1 which is blank. - let mut doc = Rope::from(" 1\n\n 2\n 3"); - // select whole document - let mut selection = Selection::single(0, doc.len_chars() - 1); + mod find_line_comment { + use super::*; - let text = doc.slice(..); + #[test] + fn not_commented() { + // four lines, two space indented, except for line 1 which is blank. + let doc = Rope::from(" 1\n\n 2\n 3"); - let res = find_line_comment("//", text, 0..3); - // (commented = true, to_change = [line 0, line 2], min = col 2, margin = 0) - assert_eq!(res, (false, vec![0, 2], 2, 0)); + let text = doc.slice(..); - // comment - let transaction = toggle_line_comments(&doc, &selection, None); - transaction.apply(&mut doc); - selection = selection.map(transaction.changes()); + let res = find_line_comment("//", text, 0..3); + // (commented = false, to_change = [line 0, line 2], min = col 2, margin = 0) + assert_eq!(res, (false, vec![0, 2], 2, 0)); + } - assert_eq!(doc, " // 1\n\n // 2\n // 3"); + #[test] + fn is_commented() { + // three lines where the second line is empty. + let doc = Rope::from("// hello\n\n// there"); - // uncomment - let transaction = toggle_line_comments(&doc, &selection, None); - transaction.apply(&mut doc); - selection = selection.map(transaction.changes()); - assert_eq!(doc, " 1\n\n 2\n 3"); - assert!(selection.len() == 1); // to ignore the selection unused warning + let res = find_line_comment("//", doc.slice(..), 0..3); - // 0 margin comments - doc = Rope::from(" //1\n\n //2\n //3"); - // reset the selection. - selection = Selection::single(0, doc.len_chars() - 1); + // (commented = true, to_change = [line 0, line 2], min = col 0, margin = 1) + assert_eq!(res, (true, vec![0, 2], 0, 1)); + } + } - let transaction = toggle_line_comments(&doc, &selection, None); - transaction.apply(&mut doc); - selection = selection.map(transaction.changes()); - assert_eq!(doc, " 1\n\n 2\n 3"); - assert!(selection.len() == 1); // to ignore the selection unused warning + // TODO: account for uncommenting with uneven comment indentation + mod toggle_line_comment { + use super::*; - // 0 margin comments, with no space - doc = Rope::from("//"); - // reset the selection. - selection = Selection::single(0, doc.len_chars() - 1); + #[test] + fn comment() { + // four lines, two space indented, except for line 1 which is blank. + let mut doc = Rope::from(" 1\n\n 2\n 3"); + // select whole document + let selection = Selection::single(0, doc.len_chars() - 1); - let transaction = toggle_line_comments(&doc, &selection, None); - transaction.apply(&mut doc); - selection = selection.map(transaction.changes()); - assert_eq!(doc, ""); - assert!(selection.len() == 1); // to ignore the selection unused warning + let transaction = toggle_line_comments(&doc, &selection, None); + transaction.apply(&mut doc); - // TODO: account for uncommenting with uneven comment indentation + assert_eq!(doc, " // 1\n\n // 2\n // 3"); + } + + #[test] + fn uncomment() { + let mut doc = Rope::from(" // 1\n\n // 2\n // 3"); + let mut selection = Selection::single(0, doc.len_chars() - 1); + + let transaction = toggle_line_comments(&doc, &selection, None); + transaction.apply(&mut doc); + selection = selection.map(transaction.changes()); + + assert_eq!(doc, " 1\n\n 2\n 3"); + assert!(selection.len() == 1); // to ignore the selection unused warning + } + + #[test] + fn uncomment_0_margin_comments() { + let mut doc = Rope::from(" //1\n\n //2\n //3"); + let mut selection = Selection::single(0, doc.len_chars() - 1); + + let transaction = toggle_line_comments(&doc, &selection, None); + transaction.apply(&mut doc); + selection = selection.map(transaction.changes()); + + assert_eq!(doc, " 1\n\n 2\n 3"); + assert!(selection.len() == 1); // to ignore the selection unused warning + } + + #[test] + fn uncomment_0_margin_comments_with_no_space() { + let mut doc = Rope::from("//"); + let mut selection = Selection::single(0, doc.len_chars() - 1); + + let transaction = toggle_line_comments(&doc, &selection, None); + transaction.apply(&mut doc); + selection = selection.map(transaction.changes()); + assert_eq!(doc, ""); + assert!(selection.len() == 1); // to ignore the selection unused warning + } } #[test] @@ -413,4 +462,32 @@ mod test { transaction.apply(&mut doc); assert_eq!(doc, ""); } + + /// Test, if `get_comment_tokens` works, even if the content of the file includes chars, whose + /// byte size unequal the amount of chars + #[test] + fn test_get_comment_with_char_boundaries() { + let rope = Rope::from("··"); + let tokens = ["//", "///"]; + + assert_eq!( + super::get_comment_token(rope.slice(..), tokens.as_slice(), 0), + None + ); + } + + /// Test for `get_comment_token`. + /// + /// Assuming the comment tokens are stored as `["///", "//"]`, `get_comment_token` should still + /// return `///` instead of `//` if the user is in a doc-comment section. + #[test] + fn test_use_longest_comment() { + let text = Rope::from(" /// amogus"); + let tokens = ["///", "//"]; + + assert_eq!( + super::get_comment_token(text.slice(..), tokens.as_slice(), 0), + Some("///") + ); + } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b1c29378d..ee2949fa0 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -22,8 +22,8 @@ use helix_core::{ encoding, find_workspace, graphemes::{self, next_grapheme_boundary, RevRopeGraphemes}, history::UndoKind, - increment, indent, - indent::IndentStyle, + increment, + indent::{self, IndentStyle}, line_ending::{get_line_ending_of_str, line_end_char_index}, match_brackets, movement::{self, move_vertically_visual, Direction}, @@ -3467,31 +3467,51 @@ fn open(cx: &mut Context, open: Open) { ) }; - let indent = indent::indent_for_newline( - doc.language_config(), - doc.syntax(), - &doc.config.load().indent_heuristic, - &doc.indent_style, - doc.tab_width(), - text, - line_num, - line_end_index, - cursor_line, - ); + let continue_comment_token = doc + .language_config() + .and_then(|config| config.comment_tokens.as_ref()) + .and_then(|tokens| comment::get_comment_token(text, tokens, cursor_line)); + + let line = text.line(cursor_line); + let indent = match line.first_non_whitespace_char() { + Some(pos) if continue_comment_token.is_some() => line.slice(..pos).to_string(), + _ => indent::indent_for_newline( + doc.language_config(), + doc.syntax(), + &doc.config.load().indent_heuristic, + &doc.indent_style, + doc.tab_width(), + text, + line_num, + line_end_index, + cursor_line, + ), + }; let indent_len = indent.len(); let mut text = String::with_capacity(1 + indent_len); text.push_str(doc.line_ending.as_str()); text.push_str(&indent); + + if let Some(token) = continue_comment_token { + text.push_str(token); + text.push(' '); + } + let text = text.repeat(count); // calculate new selection ranges let pos = offs + line_end_index + line_end_offset_width; + let comment_len = continue_comment_token + .map(|token| token.len() + 1) // `+ 1` for the extra space added + .unwrap_or_default(); for i in 0..count { // pos -> beginning of reference line, - // + (i * (1+indent_len)) -> beginning of i'th line from pos - // + indent_len -> -> indent for i'th line - ranges.push(Range::point(pos + (i * (1 + indent_len)) + indent_len)); + // + (i * (1+indent_len + comment_len)) -> beginning of i'th line from pos (possibly including comment token) + // + indent_len + comment_len -> -> indent for i'th line + ranges.push(Range::point( + pos + (i * (1 + indent_len + comment_len)) + indent_len + comment_len, + )); } offs += text.chars().count(); @@ -3929,6 +3949,11 @@ pub mod insert { let mut new_text = String::new(); + let continue_comment_token = doc + .language_config() + .and_then(|config| config.comment_tokens.as_ref()) + .and_then(|tokens| comment::get_comment_token(text, tokens, current_line)); + // If the current line is all whitespace, insert a line ending at the beginning of // the current line. This makes the current line empty and the new line contain the // indentation of the old line. @@ -3938,17 +3963,22 @@ pub mod insert { (line_start, line_start, new_text.chars().count()) } else { - let indent = indent::indent_for_newline( - doc.language_config(), - doc.syntax(), - &doc.config.load().indent_heuristic, - &doc.indent_style, - doc.tab_width(), - text, - current_line, - pos, - current_line, - ); + let line = text.line(current_line); + + let indent = match line.first_non_whitespace_char() { + Some(pos) if continue_comment_token.is_some() => line.slice(..pos).to_string(), + _ => indent::indent_for_newline( + doc.language_config(), + doc.syntax(), + &doc.config.load().indent_heuristic, + &doc.indent_style, + doc.tab_width(), + text, + current_line, + pos, + current_line, + ), + }; // If we are between pairs (such as brackets), we want to // insert an additional line which is indented one level @@ -3958,19 +3988,30 @@ pub mod insert { .and_then(|pairs| pairs.get(prev)) .map_or(false, |pair| pair.open == prev && pair.close == curr); - let local_offs = if on_auto_pair { + let local_offs = if let Some(token) = continue_comment_token { + new_text.push_str(doc.line_ending.as_str()); + new_text.push_str(&indent); + new_text.push_str(token); + new_text.push(' '); + new_text.chars().count() + } else if on_auto_pair { + // line where the cursor will be let inner_indent = indent.clone() + doc.indent_style.as_str(); new_text.reserve_exact(2 + indent.len() + inner_indent.len()); new_text.push_str(doc.line_ending.as_str()); new_text.push_str(&inner_indent); + + // line where the matching pair will be let local_offs = new_text.chars().count(); new_text.push_str(doc.line_ending.as_str()); new_text.push_str(&indent); + local_offs } else { new_text.reserve_exact(1 + indent.len()); new_text.push_str(doc.line_ending.as_str()); new_text.push_str(&indent); + new_text.chars().count() };