Compare commits

...

63 Commits

Author SHA1 Message Date
Nik Revenco ed4e7567dd
Merge a23b0bc3a9 into 4281228da3 2025-07-25 00:22:06 +07:00
Valtteri Koskivuori 4281228da3
fix(queries): Fix filesystem permissions for snakemake (#14061) 2025-07-24 13:09:40 -04:00
Nik Revenco a23b0bc3a9 perf: Do not unnecessarily call `commen::get_line_comment_token`
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-06-17 15:17:40 +01:00
Nik Revenco aca1b0e2f0 chore: Do not expose `Syntax.inner` and `Document.syn_loader`
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-06-17 15:05:37 +01:00
Nik Revenco bdd953e5b1 chore: Use `&[T]` instead of `&Vec<T>`
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-06-17 15:05:37 +01:00
Nik Revenco 6e52d9439f refactor: use function pointer `fn` instead of a generic `Fn`
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
2025-06-17 14:58:53 +01:00
Nik Revenco a2841103a1 style: `cargo fmt` 2025-06-17 12:04:20 +01:00
Nik Revenco f8d1f943e8 Merge branch 'master' into determine-comment-tokens 2025-06-17 11:33:54 +01:00
Nik Revenco 76687f5389 refactor: Rename varibles + Remove accidentally commited files 2025-05-25 00:57:00 +01:00
Nik Revenco d6d7a3c9e2 feat: Remove `:tree-sitter-injections`
This would fit in a different PR.
2025-05-25 00:13:26 +01:00
Nik Revenco 76fb79e4c1 chore: update version of my PR 2025-05-16 21:44:41 +01:00
Nik Revenco c7dd5c1ad9 chore: move comment to a doc comment 2025-05-15 17:18:59 +01:00
Nik Revenco 600bdae69f chore: update to new version of my PR to tree-sitter 2025-05-15 17:18:13 +01:00
Nik Revenco 96f8c58dde chore: use my tree-house fork 2025-05-14 19:57:15 +01:00
Nik Revenco f07e6973fe fix: do not consider languages that do not have comment tokens 2025-05-14 19:41:18 +01:00
Nik Revenco 77a74feb24 feat: solve merge conflicts and make tests more modular 2025-05-14 18:33:05 +01:00
Nik Revenco d0ca96c566 Merge branch 'master' into determine-comment-tokens 2025-05-14 17:07:54 +01:00
Nikita Revenco d09f7730ec feat: fix merge conflicts 2025-03-25 14:41:27 +00:00
Nikita Revenco f8a38a1229 fix: crash when using block comment if no block comment but yes line comment 2025-03-25 14:41:27 +00:00
Nikita Revenco f18a2d7c5a refactor: do not use pointless assertion 2025-03-25 14:41:27 +00:00
Nikita Revenco c3829c3d91 fix: call geter instead of trying to access private field 2025-03-25 14:41:27 +00:00
Nikita Revenco a07819b497 refactor: remove `pub`, use a getter instead 2025-03-25 14:41:27 +00:00
Nikita Revenco ec94fbdf3b refactor: collapse 2 `map` intoa a single `map` 2025-03-25 14:41:27 +00:00
Nikita Revenco 18aaf93da0 chore: remove Clone derive from Syntax 2025-03-25 14:41:27 +00:00
Nikita Revenco 4c5ceb5bed test: add test for continuing comment in injection layers 2025-03-25 14:41:27 +00:00
Nikita Revenco c72755437a feat: continue comment uses injected comment tokens 2025-03-25 14:41:27 +00:00
Nikita Revenco 5b30bfe36e fix: YAML formatting issues with injections 2025-03-25 14:41:27 +00:00
Nikita Revenco 4bb33459fa feat: improve presentation of tree-sitter-injections 2025-03-25 14:41:27 +00:00
Nikita Revenco 37f8cbed3c feat: upgrade `tree-sitter-injection` to show injections for entire file 2025-03-25 14:41:27 +00:00
Nikita Revenco 92469b431a docs: add information on `tree-sitter-injection` 2025-03-25 14:41:27 +00:00
Nikita Revenco 56dedd10a7 fix: add `regex` to ignored layer configs 2025-03-25 14:41:27 +00:00
Nikita Revenco d451077978 feat: add typable command to get injection layer for current range 2025-03-25 14:41:27 +00:00
Nikita Revenco 99d16170dc fix: commenting full lines 2025-03-25 14:41:27 +00:00
Nikita Revenco 7d53290dd2 fix: panic 2025-03-25 14:41:27 +00:00
Nikita Revenco e9683381b6 refactor: extract a separate toggle_comment_impl function 2025-03-25 14:41:27 +00:00
Nikita Revenco 093805b62c perf: get rid of a `.clone()` 2025-03-25 14:41:27 +00:00
Nikita Revenco 63fb49c1b4 chore: clean up code
chore: remove comment
2025-03-25 14:41:27 +00:00
Nikita Revenco 6e451fe201 chore: remove useless file 2025-03-25 14:41:27 +00:00
Nikita Revenco 3714fc0cee chore: remove unused imports 2025-03-25 14:41:27 +00:00
Nikita Revenco c22eba38d5 chore: move all comment integration tests into a separate module 2025-03-25 14:41:27 +00:00
Nikita Revenco ad3f9ececb chore: remove unneeded files 2025-03-25 14:41:27 +00:00
Nikita Revenco 570911e589 fix: many single width selections panic 2025-03-25 14:41:27 +00:00
Nikita Revenco ee0f22471e chore: add brainstorm thoughts 2025-03-25 14:41:27 +00:00
Nikita Revenco 13b52e9d97 refactor: rename variables ,use iterator methods, separate vars for added and removed chars 2025-03-25 14:41:27 +00:00
Nikita Revenco 7a39fb8164 refactor: rename variable 2025-03-25 14:41:27 +00:00
Nikita Revenco bbd7cb7bfb fix: incorrect order of index additions 2025-03-25 14:41:27 +00:00
Nikita Revenco 76b3e6778d chore: remove log statement 2025-03-25 14:41:27 +00:00
Nikita Revenco 31e2f739ee fix: panic as no ranges were inserted 2025-03-25 14:41:27 +00:00
Nikita Revenco 5562e7ae8e chore: add announcement file for PR 2025-03-25 14:41:27 +00:00
Nikita Revenco b94d3a70e7 fix: multiple selections having incorrect range and not properly accounting for comment tokens 2025-03-25 14:41:27 +00:00
Nikita Revenco 621dec74be test: write more tests for toggle comment, and add tests for toggle block comment 2025-03-25 14:41:27 +00:00
Nikita Revenco 7c24110061 feat: add integration tests for commenting through injection layers 2025-03-25 14:41:27 +00:00
Nikita Revenco 0a882107ed fix: restore selections when created comment 2025-03-25 14:41:27 +00:00
Nikita Revenco de7884c7dd test: uncomment previosly skipped tests 2025-03-25 14:41:27 +00:00
Nikita Revenco 371dec3774 feat: block comment toggle and single line comment toggle 2025-03-25 14:41:27 +00:00
Nikita Revenco 544e460ac4 feat: implement toggle comment funcitonality for multiple comment tokens 2025-03-25 14:41:27 +00:00
Nikita Revenco 29e0a00eb0 feat: implement toggle comment for the simplest case 2025-03-25 14:41:27 +00:00
Nikita Revenco d719f1572b feat: gain access to injection-specific line and block comment tokens 2025-03-25 14:41:27 +00:00
Nikita Revenco b10fc21169 chore: clean up code 2025-03-25 14:41:27 +00:00
Nikita Revenco 8fe3f90cbb feat: use FnMut 2025-03-25 14:41:27 +00:00
Nikita Revenco 38bede20ef feat: add new params 2025-03-25 14:41:27 +00:00
Nikita Revenco 70f27b390d feat: add function to get LanguageConfiguration from a LayerId
Co-authored-by: the-mikedavis <mcarsondavis@gmail.com>
2025-03-25 14:41:27 +00:00
Nikita Revenco 48dec3ee8f chore: add testing files 2025-02-28 14:53:15 +00:00
13 changed files with 1116 additions and 327 deletions

View File

@ -4,43 +4,98 @@
use smallvec::SmallVec;
use crate::{
syntax::config::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril,
Transaction,
syntax::{self, config::BlockCommentToken},
Change, Range, Rope, RopeSlice, Syntax, Tendril,
};
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<str>>(
/// Returns the longest matching line comment token of the given line (if it exists).
pub fn get_line_comment_token(
loader: &syntax::Loader,
syntax: Option<&Syntax>,
text: RopeSlice,
tokens: &'a [S],
doc_default_tokens: Option<&[String]>,
line_num: usize,
) -> Option<&'a str> {
) -> Option<String> {
let line = text.line(line_num);
let start = line.first_non_whitespace_char()?;
let start_char = text.line_to_char(line_num) + start;
tokens
.iter()
.map(AsRef::as_ref)
.filter(|token| line.slice(start..).starts_with(token))
.max_by_key(|token| token.len())
let injected_line_comment_tokens =
injected_tokens_for_range(loader, syntax, start_char as u32, start_char as u32)
.0
.and_then(|tokens| {
tokens
.into_iter()
.filter(|token| line.slice(start..).starts_with(token))
.max_by_key(|token| token.len())
});
injected_line_comment_tokens.or_else(||
// no line comment tokens found for injection, use doc comments if exists
doc_default_tokens.and_then(|tokens| {
tokens
.iter()
.filter(|token| line.slice(start..).starts_with(token))
.max_by_key(|token| token.len())
.cloned()
}))
}
/// Given text, a comment token, and a set of line indices, returns the following:
/// - Whether the given lines should be considered commented
/// Get the injected line and block comment of the smallest
/// injection around the range which fully includes `start..=end`.
///
/// Injections that do not have any comment tokens are skipped.
pub fn injected_tokens_for_range(
loader: &syntax::Loader,
syntax: Option<&Syntax>,
start: u32,
end: u32,
) -> (Option<Vec<String>>, Option<Vec<BlockCommentToken>>) {
syntax
.and_then(|syntax| {
syntax
.layers_for_byte_range(start, end)
.collect::<Vec<_>>()
.into_iter()
.rev()
.find_map(|layer| {
let lang_config = loader.language(syntax.layer(layer).language).config();
let has_any_comment_tokens = lang_config.comment_tokens.is_some()
|| lang_config.block_comment_tokens.is_some();
// if the language does not have any comment tokens, it does not make
// any sense to consider it.
//
// This includes languages such as `comment`, `jsdoc` and `regex`.
// These languages are injected and never found in files by themselves
has_any_comment_tokens.then_some((
lang_config.comment_tokens.clone(),
lang_config.block_comment_tokens.clone(),
))
})
})
.unwrap_or_default()
}
/// Given `text`, a comment `token`, and a set of line indices `lines_to_modify`,
/// Returns the following:
/// 1. Whether the given lines should be considered commented
/// - If any of the lines are uncommented, all lines are considered as such.
/// - The lines to change for toggling comments
/// 2. The lines to change for toggling comments
/// - This is all provided lines excluding blanks lines.
/// - The column of the comment tokens
/// 3. The column of the comment tokens
/// - Column of existing tokens, if the lines are commented; column to place tokens at otherwise.
/// - The margin to the right of the comment tokens
/// 4. The margin to the right of the comment tokens
/// - Defaults to `1`. If any existing comment token is not followed by a space, changes to `0`.
fn find_line_comment(
token: &str,
text: RopeSlice,
lines: impl IntoIterator<Item = usize>,
lines_to_modify: impl IntoIterator<Item = usize>,
) -> (bool, Vec<usize>, usize, usize) {
let mut commented = true;
let mut to_change = Vec::new();
@ -48,7 +103,7 @@ fn find_line_comment(
let mut margin = 1;
let token_len = token.chars().count();
for line in lines {
for line in lines_to_modify {
let line_slice = text.line(line);
if let Some(pos) = line_slice.first_non_whitespace_char() {
let len = line_slice.len_chars();
@ -78,42 +133,55 @@ fn find_line_comment(
(commented, to_change, min, margin)
}
/// Returns the edits required to toggle the comment `token` for the `range` in the `doc`
#[must_use]
pub fn toggle_line_comments(doc: &Rope, selection: &Selection, token: Option<&str>) -> Transaction {
pub fn toggle_line_comments(doc: &Rope, range: &Range, token: Option<&str>) -> Vec<Change> {
let text = doc.slice(..);
let token = token.unwrap_or(DEFAULT_COMMENT_TOKEN);
// Add a space between the comment token and the line.
let comment = Tendril::from(format!("{} ", token));
let mut lines: Vec<usize> = Vec::with_capacity(selection.len());
let line_count = text.len_lines();
let mut min_next_line = 0;
for selection in selection {
let (start, end) = selection.line_range(text);
let start = start.clamp(min_next_line, text.len_lines());
let end = (end + 1).min(text.len_lines());
let start = text.char_to_line(range.from()).clamp(0, line_count);
let end = (text.char_to_line(range.to().saturating_sub(1)) + 1).min(line_count);
lines.extend(start..end);
min_next_line = end;
}
let lines_to_modify = start..end;
let (commented, to_change, min, margin) = find_line_comment(token, text, lines);
let (
was_commented,
lines_to_modify,
column_to_place_comment_tokens_at,
comment_tokens_right_margin,
) = find_line_comment(token, text, lines_to_modify);
let mut changes: Vec<Change> = Vec::with_capacity(to_change.len());
lines_to_modify
.into_iter()
.map(|line| {
let place_comment_tokens_at =
text.line_to_char(line) + column_to_place_comment_tokens_at;
for line in to_change {
let pos = text.line_to_char(line) + min;
if !commented {
// comment line
changes.push((pos, pos, Some(comment.clone())));
} else {
// uncomment line
changes.push((pos, pos + token.len() + margin, None));
}
}
Transaction::change(doc, changes.into_iter())
if !was_commented {
// comment line
(
place_comment_tokens_at,
place_comment_tokens_at,
// insert the token
Some(comment.clone()),
)
} else {
// uncomment line
(
place_comment_tokens_at,
place_comment_tokens_at + token.len() + comment_tokens_right_margin,
// remove the token - replace range with nothing
None,
)
}
})
.collect()
}
#[derive(Debug, PartialEq, Eq)]
@ -142,11 +210,11 @@ pub enum CommentChange {
pub fn find_block_comments(
tokens: &[BlockCommentToken],
text: RopeSlice,
selection: &Selection,
ranges: &[Range],
) -> (bool, Vec<CommentChange>) {
let mut commented = true;
let mut was_commented = true;
let mut only_whitespace = true;
let mut comment_changes = Vec::with_capacity(selection.len());
let mut comment_changes = Vec::with_capacity(ranges.len());
let default_tokens = tokens.first().cloned().unwrap_or_default();
let mut start_token = default_tokens.start.clone();
let mut end_token = default_tokens.end.clone();
@ -160,7 +228,7 @@ pub fn find_block_comments(
b.start.len().cmp(&a.start.len())
}
});
for range in selection {
for range in ranges {
let selection_slice = range.slice(text);
if let (Some(start_pos), Some(end_pos)) = (
selection_slice.first_non_whitespace_char(),
@ -199,7 +267,7 @@ pub fn find_block_comments(
start_token: default_tokens.start.clone(),
end_token: default_tokens.end.clone(),
});
commented = false;
was_commented = false;
} else {
comment_changes.push(CommentChange::Commented {
range: *range,
@ -218,23 +286,22 @@ pub fn find_block_comments(
}
}
if only_whitespace {
commented = false;
was_commented = false;
}
(commented, comment_changes)
(was_commented, comment_changes)
}
#[must_use]
pub fn create_block_comment_transaction(
doc: &Rope,
selection: &Selection,
commented: bool,
ranges: &[Range],
was_commented: bool,
comment_changes: Vec<CommentChange>,
) -> (Transaction, SmallVec<[Range; 1]>) {
let mut changes: Vec<Change> = Vec::with_capacity(selection.len() * 2);
let mut ranges: SmallVec<[Range; 1]> = SmallVec::with_capacity(selection.len());
) -> (Vec<Change>, SmallVec<[Range; 1]>) {
let mut changes: Vec<Change> = Vec::with_capacity(ranges.len() * 2);
let mut ranges: SmallVec<[Range; 1]> = SmallVec::with_capacity(ranges.len());
let mut offs = 0;
for change in comment_changes {
if commented {
if was_commented {
if let CommentChange::Commented {
range,
start_pos,
@ -246,16 +313,12 @@ pub fn create_block_comment_transaction(
} = change
{
let from = range.from();
changes.push((
from + start_pos,
from + start_pos + start_token.len() + start_margin as usize,
None,
));
changes.push((
from + end_pos - end_token.len() - end_margin as usize + 1,
from + end_pos + 1,
None,
));
let keep_from = from + start_pos + start_token.len() + start_margin as usize;
changes.push((from + start_pos, keep_from, None));
let keep_until = from + end_pos - end_token.len() - end_margin as usize + 1;
changes.push((keep_until, from + end_pos + 1, None));
// The range of characters keep_from..keep_until remain in the document
ranges.push(Range::new(keep_from, keep_until).with_direction(range.direction()));
}
} else {
// uncommented so manually map ranges through changes
@ -292,37 +355,92 @@ pub fn create_block_comment_transaction(
}
}
}
(Transaction::change(doc, changes.into_iter()), ranges)
(changes, ranges)
}
#[must_use]
pub fn toggle_block_comments(
doc: &Rope,
selection: &Selection,
ranges: &[Range],
tokens: &[BlockCommentToken],
) -> Transaction {
selections: &mut SmallVec<[Range; 1]>,
added_chars: &mut usize,
removed_chars: &mut usize,
) -> Vec<Change> {
let text = doc.slice(..);
let (commented, comment_changes) = find_block_comments(tokens, text, selection);
let (mut transaction, ranges) =
create_block_comment_transaction(doc, selection, commented, comment_changes);
if !commented {
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
let (was_commented, comment_changes) = find_block_comments(tokens, text, ranges);
let (changes, new_ranges) =
create_block_comment_transaction(ranges, was_commented, comment_changes);
if was_commented {
for (range, changes) in new_ranges.iter().zip(changes.chunks_exact(2)) {
// every 2 elements (from, to) in `changes` corresponds
// the `from` - `to` represents the range of text that will be deleted.
// to 1 element in `new_ranges`
//
// Left token:
//
// "<!-- "
// ^ left_from
// ^ left_to
//
// Right token:
//
// " -->"
// ^ right_from
// ^ right_to
let [(left_from, left_to, _), (right_from, right_to, _)] = changes else {
unreachable!()
};
*removed_chars += left_to - left_from;
// We slide the range to the left by the amount of characters
// we've deleted so far + the amount of chars deleted for
// the left comment token of the current iteration
selections.push(Range::new(
range.anchor + *added_chars - *removed_chars,
range.head + *added_chars - *removed_chars,
));
*removed_chars += right_to - right_from;
}
changes
} else {
// we're never removing or
// creating ranges. Only shifting / increasing size
// of existing ranges to accomodate the newly added
// comment tokens.
//
// when we add comment tokens, we want to extend our selection to
// also include the added tokens.
for (range, old_range) in new_ranges.iter().zip(ranges) {
// Will not underflow because the new range must always be
// at least the same size as the old range, since we're
// adding comment token characters, never removing.
let range = Range::new(
range.anchor + *added_chars - *removed_chars,
range.head + *added_chars - *removed_chars,
);
selections.push(range);
*added_chars += range.len() - old_range.len();
}
changes
}
transaction
}
pub fn split_lines_of_selection(text: RopeSlice, selection: &Selection) -> Selection {
let mut ranges = SmallVec::new();
for range in selection.ranges() {
let (line_start, line_end) = range.line_range(text.slice(..));
let mut pos = text.line_to_char(line_start);
for line in text.slice(pos..text.line_to_char(line_end + 1)).lines() {
let start = pos;
pos += line.len_chars();
ranges.push(Range::new(start, pos));
}
pub fn split_lines_of_range(text: RopeSlice, range: &Range) -> Vec<Range> {
let mut ranges = vec![];
let (line_start, line_end) = range.line_range(text.slice(..));
let mut pos = text.line_to_char(line_start);
for line in text.slice(pos..text.line_to_char(line_end + 1)).lines() {
let start = pos;
pos += line.len_chars();
ranges.push(Range::new(start, pos));
}
Selection::new(ranges, 0)
ranges
}
#[cfg(test)]
@ -358,6 +476,8 @@ mod test {
// TODO: account for uncommenting with uneven comment indentation
mod toggle_line_comment {
use crate::Transaction;
use super::*;
#[test]
@ -365,9 +485,11 @@ mod test {
// 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 range = Range::new(0, doc.len_chars() - 1);
let changes = toggle_line_comments(&doc, &range, None);
let transaction = Transaction::change(&doc, changes.into_iter());
let transaction = toggle_line_comments(&doc, &selection, None);
transaction.apply(&mut doc);
assert_eq!(doc, " # 1\n\n # 2\n # 3");
@ -376,112 +498,108 @@ mod test {
#[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 range = Range::new(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
let changes = toggle_line_comments(&doc, &range, None);
let transaction = Transaction::change(&doc, changes.into_iter());
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
_ = range.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 range = Range::new(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
let changes = toggle_line_comments(&doc, &range, None);
let transaction = Transaction::change(&doc, changes.into_iter());
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
_ = range.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 range = Range::new(0, doc.len_chars() - 1);
let transaction = toggle_line_comments(&doc, &selection, None);
let changes = toggle_line_comments(&doc, &range, None);
let transaction = Transaction::change(&doc, changes.into_iter());
transaction.apply(&mut doc);
_ = range.map(transaction.changes());
assert_eq!(doc, "");
}
#[test]
fn test_find_block_comments() {
// three lines 5 characters.
let mut doc = Rope::from("1\n2\n3");
// select whole document
let range = Range::new(0, doc.len_chars());
let text = doc.slice(..);
let res = find_block_comments(&[BlockCommentToken::default()], text, &[range]);
assert_eq!(
res,
(
false,
vec![CommentChange::Uncommented {
range: Range::new(0, 5),
start_pos: 0,
end_pos: 4,
start_token: "/*".to_string(),
end_token: "*/".to_string(),
}]
)
);
// comment
let changes = toggle_block_comments(
&doc,
&[range],
&[BlockCommentToken::default()],
&mut SmallVec::new(),
&mut 0,
&mut 0,
);
let transaction = Transaction::change(&doc, changes.into_iter());
transaction.apply(&mut doc);
assert_eq!(doc, "/* 1\n2\n3 */");
// uncomment
let range = Range::new(0, doc.len_chars());
let changes = toggle_block_comments(
&doc,
&[range],
&[BlockCommentToken::default()],
&mut SmallVec::new(),
&mut 0,
&mut 0,
);
let transaction = Transaction::change(&doc, changes.into_iter());
transaction.apply(&mut doc);
assert_eq!(doc, "1\n2\n3");
// don't panic when there is just a space in comment
doc = Rope::from("/* */");
let range = Range::new(0, doc.len_chars());
let changes = toggle_block_comments(
&doc,
&[range],
&[BlockCommentToken::default()],
&mut SmallVec::new(),
&mut 0,
&mut 0,
);
let transaction = Transaction::change(&doc, changes.into_iter());
transaction.apply(&mut doc);
selection = selection.map(transaction.changes());
assert_eq!(doc, "");
assert!(selection.len() == 1); // to ignore the selection unused warning
}
}
#[test]
fn test_find_block_comments() {
// three lines 5 characters.
let mut doc = Rope::from("1\n2\n3");
// select whole document
let selection = Selection::single(0, doc.len_chars());
let text = doc.slice(..);
let res = find_block_comments(&[BlockCommentToken::default()], text, &selection);
assert_eq!(
res,
(
false,
vec![CommentChange::Uncommented {
range: Range::new(0, 5),
start_pos: 0,
end_pos: 4,
start_token: "/*".to_string(),
end_token: "*/".to_string(),
}]
)
);
// comment
let transaction = toggle_block_comments(&doc, &selection, &[BlockCommentToken::default()]);
transaction.apply(&mut doc);
assert_eq!(doc, "/* 1\n2\n3 */");
// uncomment
let selection = Selection::single(0, doc.len_chars());
let transaction = toggle_block_comments(&doc, &selection, &[BlockCommentToken::default()]);
transaction.apply(&mut doc);
assert_eq!(doc, "1\n2\n3");
// don't panic when there is just a space in comment
doc = Rope::from("/* */");
let selection = Selection::single(0, doc.len_chars());
let transaction = toggle_block_comments(&doc, &selection, &[BlockCommentToken::default()]);
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("///")
);
}
}

View File

@ -513,6 +513,14 @@ impl Syntax {
self.inner.layer_for_byte_range(start, end)
}
pub fn layers_for_byte_range(
&self,
start: u32,
end: u32,
) -> impl Iterator<Item = Layer> + use<'_> {
self.inner.layers_for_byte_range(start, end)
}
pub fn root_language(&self) -> Language {
self.layer(self.root_layer()).language
}

View File

@ -22,7 +22,8 @@ pub use typed::*;
use helix_core::{
char_idx_at_visual_offset,
chars::char_is_word,
command_line, comment,
command_line,
comment::{self},
doc_formatter::TextFormat,
encoding, find_workspace,
graphemes::{self, next_grapheme_boundary},
@ -36,7 +37,10 @@ use helix_core::{
regex::{self, Regex},
search::{self, CharMatcher},
selection, surround,
syntax::config::{BlockCommentToken, LanguageServerFeature},
syntax::{
self,
config::{BlockCommentToken, LanguageServerFeature},
},
text_annotations::{Overlay, TextAnnotations},
textobject,
unicode::width::UnicodeWidthChar,
@ -3653,13 +3657,14 @@ fn open(cx: &mut Context, open: Open, comment_continuation: CommentContinuation)
let mut ranges = SmallVec::with_capacity(selection.len());
let continue_comment_tokens =
if comment_continuation == CommentContinuation::Enabled && config.continue_comments {
doc.language_config()
.and_then(|config| config.comment_tokens.as_ref())
} else {
None
};
let continue_comments =
comment_continuation == CommentContinuation::Enabled && config.continue_comments;
let doc_default_tokens = doc
.language_config()
.and_then(|config| config.comment_tokens.as_deref());
let syntax = doc.syntax();
let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
// the line number, where the cursor is currently
@ -3676,8 +3681,17 @@ fn open(cx: &mut Context, open: Open, comment_continuation: CommentContinuation)
let above_next_new_line_num = next_new_line_num.saturating_sub(1);
let continue_comment_token = continue_comment_tokens
.and_then(|tokens| comment::get_comment_token(text, tokens, curr_line_num));
let continue_comment_token = continue_comments
.then(|| {
comment::get_line_comment_token(
&loader,
syntax,
text,
doc_default_tokens,
curr_line_num,
)
})
.flatten();
// Index to insert newlines after, as well as the char width
// to use to compensate for those inserted newlines.
@ -3711,7 +3725,7 @@ fn open(cx: &mut Context, open: Open, comment_continuation: CommentContinuation)
if open == Open::Above && next_new_line_num == 0 {
text.push_str(&indent);
if let Some(token) = continue_comment_token {
if let Some(ref token) = continue_comment_token {
text.push_str(token);
text.push(' ');
}
@ -3720,7 +3734,7 @@ fn open(cx: &mut Context, open: Open, comment_continuation: CommentContinuation)
text.push_str(doc.line_ending.as_str());
text.push_str(&indent);
if let Some(token) = continue_comment_token {
if let Some(ref token) = continue_comment_token {
text.push_str(token);
text.push(' ');
}
@ -4211,12 +4225,11 @@ pub mod insert {
let mut global_offs = 0;
let mut new_text = String::new();
let continue_comment_tokens = if config.continue_comments {
doc.language_config()
.and_then(|config| config.comment_tokens.as_ref())
} else {
None
};
let doc_default_comment_token = doc
.language_config()
.and_then(|config| config.comment_tokens.as_deref());
let syntax = doc.syntax();
let mut last_pos = 0;
let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
@ -4234,8 +4247,14 @@ pub mod insert {
let current_line = text.char_to_line(pos);
let line_start = text.line_to_char(current_line);
let continue_comment_token = continue_comment_tokens
.and_then(|tokens| comment::get_comment_token(text, tokens, current_line));
let continue_comment_token = comment::get_line_comment_token(
&cx.editor.syn_loader.load(),
syntax,
text,
doc_default_comment_token,
current_line,
)
.filter(|_| config.continue_comments);
let (from, to, local_offs) = if let Some(idx) =
text.slice(line_start..pos).last_non_whitespace_char()
@ -4271,7 +4290,7 @@ pub mod insert {
new_text.reserve_exact(line_ending.len() + indent.len() + token.len() + 1);
new_text.push_str(line_ending);
new_text.push_str(&indent);
new_text.push_str(token);
new_text.push_str(&token);
new_text.push(' ');
new_text.chars().count()
} else if on_auto_pair {
@ -5167,122 +5186,239 @@ pub fn completion(cx: &mut Context) {
}
// comments
type CommentTransactionFn = fn(
line_token: Option<&str>,
block_tokens: Option<&[BlockCommentToken]>,
doc: &Rope,
/// Perform a `Transaction` to toggle comments
type CommentTransaction = fn(
text: &Rope,
selection: &Selection,
doc_line_token: Option<&str>,
doc_block_tokens: Option<&[BlockCommentToken]>,
syntax: Option<&Syntax>,
loader: &syntax::Loader,
) -> Transaction;
fn toggle_comments_impl(cx: &mut Context, comment_transaction: CommentTransactionFn) {
/// Commenting behavior, for each range in selection:
///
/// 1. Only line comment tokens -> line comment
/// 2. Each line block commented -> uncomment all lines
/// 3. Whole selection block commented -> uncomment selection
/// 4. All lines not commented and block tokens -> comment uncommented lines
/// 5. No comment tokens and not block commented -> line comment
fn toggle_comments_impl(cx: &mut Context, comments_transaction: CommentTransaction) {
let (view, doc) = current!(cx.editor);
let line_token: Option<&str> = doc
let syntax = doc.syntax();
let rope = doc.text();
let selection = doc.selection(view.id);
// The comment tokens to fallback to if no comment tokens are found for the injection layer.
let doc_line_token: Option<&str> = doc
.language_config()
.and_then(|lc| lc.comment_tokens.as_ref())
.and_then(|tc| tc.first())
.map(|tc| tc.as_str());
let block_tokens: Option<&[BlockCommentToken]> = doc
let doc_block_tokens: Option<&[BlockCommentToken]> = doc
.language_config()
.and_then(|lc| lc.block_comment_tokens.as_ref())
.map(|tc| &tc[..]);
let transaction =
comment_transaction(line_token, block_tokens, doc.text(), doc.selection(view.id));
// Call the custom logic provided by the caller (the original functions).
let transaction = comments_transaction(
rope,
selection,
doc_line_token,
doc_block_tokens,
syntax,
&cx.editor.syn_loader.load(),
);
doc.apply(&transaction, view.id);
exit_select_mode(cx);
}
/// commenting behavior:
/// 1. only line comment tokens -> line comment
/// 2. each line block commented -> uncomment all lines
/// 3. whole selection block commented -> uncomment selection
/// 4. all lines not commented and block tokens -> comment uncommented lines
/// 5. no comment tokens and not block commented -> line comment
fn toggle_comments(cx: &mut Context) {
toggle_comments_impl(cx, |line_token, block_tokens, doc, selection| {
let text = doc.slice(..);
toggle_comments_impl(
cx,
|rope, selection, doc_line_token, doc_block_tokens, syntax, loader| {
Transaction::change(
rope,
selection.iter().flat_map(|range| {
let (injected_line_tokens, injected_block_tokens) =
comment::injected_tokens_for_range(
loader,
syntax,
range.from() as u32,
range.to() as u32,
);
// only have line comment tokens
if line_token.is_some() && block_tokens.is_none() {
return comment::toggle_line_comments(doc, selection, line_token);
}
let line_token = injected_line_tokens
.as_ref()
.and_then(|token| token.first())
.map(|token| token.as_str())
.or(doc_line_token);
let split_lines = comment::split_lines_of_selection(text, selection);
let block_tokens = injected_block_tokens.as_deref().or(doc_block_tokens);
let default_block_tokens = &[BlockCommentToken::default()];
let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
if line_token.is_some() && block_tokens.is_none() {
return comment::toggle_line_comments(rope, range, line_token);
}
let (line_commented, line_comment_changes) =
comment::find_block_comments(block_comment_tokens, text, &split_lines);
let split_lines = comment::split_lines_of_range(rope.slice(..), range);
// block commented by line would also be block commented so check this first
if line_commented {
return comment::create_block_comment_transaction(
doc,
&split_lines,
line_commented,
line_comment_changes,
let default_block_tokens = &[BlockCommentToken::default()];
let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
let (line_commented, line_comment_changes) = comment::find_block_comments(
block_comment_tokens,
rope.slice(..),
&split_lines,
);
if line_commented {
return comment::create_block_comment_transaction(
&split_lines,
line_commented,
line_comment_changes,
)
.0;
}
let (block_commented, comment_changes) = comment::find_block_comments(
block_comment_tokens,
rope.slice(..),
&[*range],
);
if block_commented {
return comment::create_block_comment_transaction(
&[*range],
block_commented,
comment_changes,
)
.0;
}
if line_token.is_none() && block_tokens.is_some() {
return comment::create_block_comment_transaction(
&split_lines,
line_commented,
line_comment_changes,
)
.0;
}
comment::toggle_line_comments(rope, range, line_token)
}),
)
.0;
}
let (block_commented, comment_changes) =
comment::find_block_comments(block_comment_tokens, text, selection);
// check if selection has block comments
if block_commented {
return comment::create_block_comment_transaction(
doc,
selection,
block_commented,
comment_changes,
)
.0;
}
// not commented and only have block comment tokens
if line_token.is_none() && block_tokens.is_some() {
return comment::create_block_comment_transaction(
doc,
&split_lines,
line_commented,
line_comment_changes,
)
.0;
}
// not block commented at all and don't have any tokens
comment::toggle_line_comments(doc, selection, line_token)
})
},
);
}
fn toggle_line_comments(cx: &mut Context) {
toggle_comments_impl(cx, |line_token, block_tokens, doc, selection| {
if line_token.is_none() && block_tokens.is_some() {
let default_block_tokens = &[BlockCommentToken::default()];
let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
comment::toggle_block_comments(
doc,
&comment::split_lines_of_selection(doc.slice(..), selection),
block_comment_tokens,
)
} else {
comment::toggle_line_comments(doc, selection, line_token)
}
});
toggle_comments_impl(
cx,
|rope, selection, doc_line_token, doc_block_tokens, syntax, loader| {
let mut selections = SmallVec::new();
let mut added_chars = 0;
let mut removed_chars = 0;
let transaction = Transaction::change(
rope,
selection.iter().flat_map(|range| {
let (injected_line_tokens, injected_block_tokens) =
comment::injected_tokens_for_range(
loader,
syntax,
range.from() as u32,
range.to() as u32,
);
let line_token = injected_line_tokens
.as_ref()
.and_then(|tokens| tokens.first())
.map(|token| token.as_str())
.or(doc_line_token);
let block_tokens = injected_block_tokens.as_deref().or(doc_block_tokens);
if line_token.is_none() && block_tokens.is_some() {
let default_block_tokens = &[BlockCommentToken::default()];
let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
let ranges = &comment::split_lines_of_range(rope.slice(..), range);
comment::toggle_block_comments(
rope,
ranges,
block_comment_tokens,
&mut selections,
&mut added_chars,
&mut removed_chars,
)
} else {
comment::toggle_line_comments(rope, range, line_token)
}
}),
);
if selections.is_empty() {
transaction
} else {
transaction.with_selection(Selection::new(selections, selection.primary_index()))
}
},
);
}
fn toggle_block_comments(cx: &mut Context) {
toggle_comments_impl(cx, |line_token, block_tokens, doc, selection| {
if line_token.is_some() && block_tokens.is_none() {
comment::toggle_line_comments(doc, selection, line_token)
} else {
let default_block_tokens = &[BlockCommentToken::default()];
let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
comment::toggle_block_comments(doc, selection, block_comment_tokens)
}
});
toggle_comments_impl(
cx,
|rope, selection, doc_line_token, doc_block_tokens, syntax, loader| {
let mut selections = SmallVec::new();
let mut added_chars = 0;
let mut removed_chars = 0;
let transaction = Transaction::change(
rope,
selection.iter().flat_map(|range| {
let (injected_line_tokens, injected_block_tokens) =
comment::injected_tokens_for_range(
loader,
syntax,
range.from() as u32,
range.to() as u32,
);
let line_token = injected_line_tokens
.as_ref()
.and_then(|token| token.first())
.map(|token| token.as_str())
.or(doc_line_token);
let block_tokens = injected_block_tokens.as_deref().or(doc_block_tokens);
if line_token.is_some() && block_tokens.is_none() {
comment::toggle_line_comments(rope, range, line_token)
} else {
let default_block_tokens = &[BlockCommentToken::default()];
let block_comment_tokens = block_tokens.unwrap_or(default_block_tokens);
let ranges = vec![*range];
comment::toggle_block_comments(
rope,
&ranges,
block_comment_tokens,
&mut selections,
&mut added_chars,
&mut removed_chars,
)
}
}),
);
if selections.is_empty() {
transaction
} else {
transaction.with_selection(Selection::new(selections, selection.primary_index()))
}
},
);
}
fn rotate_selections(cx: &mut Context, direction: Direction) {

View File

@ -19,6 +19,7 @@ mod test {
mod auto_pairs;
mod command_line;
mod commands;
mod comments;
mod languages;
mod movement;
mod splits;

View File

@ -653,49 +653,6 @@ async fn test_join_selections_space() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_join_selections_comment() -> anyhow::Result<()> {
test((
indoc! {"\
/// #[a|]#bc
/// def
"},
":lang rust<ret>J",
indoc! {"\
/// #[a|]#bc def
"},
))
.await?;
// Only join if the comment token matches the previous line.
test((
indoc! {"\
#[| // a
// b
/// c
/// d
e
/// f
// g]#
"},
":lang rust<ret>J",
indoc! {"\
#[| // a b /// c d e f // g]#
"},
))
.await?;
test((
"#[|\t// Join comments
\t// with indent]#",
":lang go<ret>J",
"#[|\t// Join comments with indent]#",
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_read_file() -> anyhow::Result<()> {
let mut file = tempfile::NamedTempFile::new()?;

View File

@ -0,0 +1,570 @@
use super::*;
mod simple {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn uncomment_inner_injection() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn comment_inner_injection() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn block_comment_inner_injection() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|/* on this line s */]#hould use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn block_uncomment_inner_injection() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|/* on this line s */]#hould use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
}
mod injected_comment_tokens_continue_comment {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn adds_new_comment_on_newline() -> anyhow::Result<()> {
test((
indoc! {r#"
<p>Some text 1234</p>
<script type="text/javascript">
// This line should #[|c]#ontinue comments
foo();
</script>
"#},
":lang html<ret>i<ret>",
indoc! {r#"
<p>Some text 1234</p>
<script type="text/javascript">
// This line should
// #[|c]#ontinue comments
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn continues_comment() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Some text 1234</p>
<script type="text/javascript">
// This line should
// #[|c]#ontinue comments
foo();
</script>
"#},
":lang html<ret>i<ret>",
indoc! {r#"\
<p>Some text 1234</p>
<script type="text/javascript">
// This line should
//
// #[|c]#ontinue comments
foo();
</script>
"#},
))
.await?;
Ok(())
}
}
#[tokio::test(flavor = "multi_thread")]
async fn test_injected_comment_tokens_continue_comment_d() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Some text 1234</p>
<script type="text/javascript">
// This line should #[|c]#ontinue comments
foo();
</script>
"#},
":lang html<ret>O",
indoc! {r#"\
<p>Some text 1234</p>
<script type="text/javascript">
// #[
|]# // This line should continue comments
foo();
</script>
"#},
))
.await?;
Ok(())
}
mod multiple_selections_different_injection_layers {
use super::*;
#[tokio::test(flavor = "multi_thread")]
async fn comments_two_different_injection_layers_with_different_comments() -> anyhow::Result<()>
{
test((
indoc! {r#"\
<p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p>
<script type="text/javascript">
Comment toggle #(|on this line )#should use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<!-- <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> -->
<script type="text/javascript">
// Comment toggle #(|on this line )#should use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn uncomments_two_different_injection_layers_with_different_comments(
) -> anyhow::Result<()> {
test((
indoc! {r#"\
<!-- <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> -->
<script type="text/javascript">
// Comment toggle #(|on this line )#should use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p>
<script type="text/javascript">
Comment toggle #(|on this line )#should use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn works_with_multiple_selections() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle #(|on this line )#should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|on this line ]#should use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<!-- <p>Comment toggle #(|on this line )#should use the HTML comment token(s).</p> -->
<script type="text/javascript">
Comment toggle #[|on this line ]#should use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn works_with_nested_injection_layers_html_js_then_css() -> anyhow::Result<()> {
test((
indoc! {r#"\
<!-- <p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p> -->
<script type="text/javascript">
// Comment toggle #(|on this line)# should use the javascript comment token(s).
foo();
css`
h#[tml {
background-color: |]#red;
}
`
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p>
<script type="text/javascript">
Comment toggle #(|on this line)# should use the javascript comment token(s).
foo();
css`
/* h#[tml { */
/* background-color: |]#red; */
}
`
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn full_line_selection_commenting() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
#[ foo();
css`
|]# html {
background-color: red;
}
`;
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
#[ // foo();
// css`
|]# html {
background-color: red;
}
`;
</script>
"#},
))
.await?;
test((
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
#[ // foo();
// css`
|]# html {
background-color: red;
}
`;
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
#[ foo();
css`
|]# html {
background-color: red;
}
`;
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn block_comment_toggle_across_different_layers() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|on this line]# should use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>Comment toggle #(|<!-- on this line -->)# should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment toggle #[|/* on this line */]# should use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn multiple_selections_same_line() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>C#[|o]#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the HTML c#(|o)#mment t#(|o)#ken(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
foo();
css`
html {
background-color: red;
}
`;
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>C#[|<!-- o -->]#mment t#(|<!-- o -->)#ggle #(|<!-- o -->)#n this line sh#(|<!-- o -->)#uld use the HTML c#(|<!-- o -->)#mment t#(|<!-- o -->)#ken(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
foo();
css`
html {
background-color: red;
}
`;
</script>
"#},
))
.await?;
test((
indoc! {r#"\
<p>C#[|<!-- o -->]#mment t#(|<!-- o -->)#ggle #(|<!-- o -->)#n this line sh#(|<!-- o -->)#uld use the HTML c#(|<!-- o -->)#mment t#(|<!-- o -->)#ken(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
foo();
css`
html {
background-color: red;
}
`;
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>C#[|o]#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the HTML c#(|o)#mment t#(|o)#ken(s).</p>
<script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s).
foo();
css`
html {
background-color: red;
}
`;
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn many_single_line_selections() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>C#[|o]#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the HTML c#(|o)#mment t#(|o)#ken(s).</p>
<script type="text/javascript">
// C#(|o)#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the javascript c#(|o)#mment t#(|o)#ken(s).
f#(|o)##(|o)#();
css`
html {
backgr#(|o)#und-c#(|o)#l#(|o)#r: red;
}
`;
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>C#[|<!-- o -->]#mment t#(|<!-- o -->)#ggle #(|<!-- o -->)#n this line sh#(|<!-- o -->)#uld use the HTML c#(|<!-- o -->)#mment t#(|<!-- o -->)#ken(s).</p>
<script type="text/javascript">
// C#(|/* o */)#mment t#(|/* o */)#ggle #(|/* o */)#n this line sh#(|/* o */)#uld use the javascript c#(|/* o */)#mment t#(|/* o */)#ken(s).
f#(|/* o */)##(|/* o */)#();
css`
html {
backgr#(|/* o */)#und-c#(|/* o */)#l#(|/* o */)#r: red;
}
`;
</script>
"#},
))
.await?;
test((
indoc! {r#"\
<p>C#[|<!-- o -->]#mment t#(|<!-- o -->)#ggle #(|<!-- o -->)#n this line sh#(|<!-- o -->)#uld use the HTML c#(|<!-- o -->)#mment t#(|<!-- o -->)#ken(s).</p>
<script type="text/javascript">
// C#(|/* o */)#mment t#(|/* o */)#ggle #(|/* o */)#n this line sh#(|/* o */)#uld use the javascript c#(|/* o */)#mment t#(|/* o */)#ken(s).
f#(|/* o */)##(|/* o */)#();
css`
html {
backgr#(|/* o */)#und-c#(|/* o */)#l#(|/* o */)#r: red;
}
`;
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>C#[|o]#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the HTML c#(|o)#mment t#(|o)#ken(s).</p>
<script type="text/javascript">
// C#(|o)#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the javascript c#(|o)#mment t#(|o)#ken(s).
f#(|o)##(|o)#();
css`
html {
backgr#(|o)#und-c#(|o)#l#(|o)#r: red;
}
`;
</script>
"#},
))
.await?;
Ok(())
}
}
/// A selection that spans across several injections takes comment tokens
/// from the injection with the bigger scope
#[tokio::test(flavor = "multi_thread")]
async fn test_injected_comment_tokens_selection_across_different_layers() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Comment tog#[|gle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment togg]#le on this line should use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> c",
indoc! {r#"\
<!-- <p>Comment tog#[|gle on this line should use the HTML comment token(s).</p> -->
<!-- <script type="text/javascript"> -->
<!-- // Comment togg]#le on this line should use the javascript comment token(s). -->
foo();
</script>
"#},
))
.await?;
test((
indoc! {r#"\
<p>Comment tog#[|gle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment togg]#le on this line should use the javascript comment token(s).
foo();
</script>
"#},
":lang html<ret> C",
indoc! {r#"\
<p>Comment tog#[|<!-- gle on this line should use the HTML comment token(s).</p>
<script type="text/javascript">
// Comment togg -->]#le on this line should use the javascript comment token(s).
foo();
</script>
"#},
))
.await?;
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn test_join_selections_comment() -> anyhow::Result<()> {
test((
indoc! {"\
/// #[a|]#bc
/// def
"},
":lang rust<ret>J",
indoc! {"\
/// #[a|]#bc def
"},
))
.await?;
// Only join if the comment token matches the previous line.
test((
indoc! {"\
#[| // a
// b
/// c
/// d
e
/// f
// g]#
"},
":lang rust<ret>J",
indoc! {"\
#[| // a b /// c d e f // g]#
"},
))
.await?;
test((
"#[|\t// Join comments
\t// with indent]#",
":lang go<ret>J",
"#[|\t// Join comments with indent]#",
))
.await?;
Ok(())
}

View File

@ -1426,8 +1426,7 @@ scope = "source.svelte"
injection-regex = "svelte"
file-types = ["svelte"]
indent = { tab-width = 2, unit = " " }
comment-token = "//"
block-comment-tokens = { start = "/*", end = "*/" }
block-comment-tokens = { start = "<!--", end = "-->" }
language-servers = [ "svelteserver" ]
[[grammar]]

0
runtime/queries/snakemake/LICENSE 100755 → 100644
View File

View File

View File

View File

View File

View File