feat: solve merge conflicts and make tests more modular

pull/12759/head
Nik Revenco 2025-05-14 18:33:05 +01:00
parent d0ca96c566
commit 77a74feb24
9 changed files with 438 additions and 410 deletions

View File

@ -1,12 +1,11 @@
//! This module contains the functionality toggle comments on lines over the selection //! This module contains the functionality toggle comments on lines over the selection
//! using the comment character defined in the user's `languages.toml` //! using the comment character defined in the user's `languages.toml`
use slotmap::DefaultKey as LayerId;
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{ use crate::{
syntax::config::BlockCommentToken, Change, Range, Rope, RopeSlice, Selection, Tendril, syntax::{self, config::BlockCommentToken},
Transaction, Change, Range, Rope, RopeSlice, Syntax, Tendril,
}; };
use helix_stdx::rope::RopeSliceExt; use helix_stdx::rope::RopeSliceExt;
use std::borrow::Cow; use std::borrow::Cow;
@ -15,6 +14,7 @@ pub const DEFAULT_COMMENT_TOKEN: &str = "#";
/// Returns the longest matching comment token of the given line (if it exists). /// Returns the longest matching comment token of the given line (if it exists).
pub fn get_comment_token( pub fn get_comment_token(
loader: &syntax::Loader,
syntax: Option<&Syntax>, syntax: Option<&Syntax>,
text: RopeSlice, text: RopeSlice,
doc_default_tokens: Option<&Vec<String>>, doc_default_tokens: Option<&Vec<String>>,
@ -24,7 +24,7 @@ pub fn get_comment_token(
let start = line.first_non_whitespace_char()?; let start = line.first_non_whitespace_char()?;
let start_char = text.line_to_char(line_num) + start; let start_char = text.line_to_char(line_num) + start;
let injected_tokens = get_injected_tokens(syntax, start_char, start_char) let injected_tokens = get_injected_tokens(loader, syntax, start_char as u32, start_char as u32)
// we only care about line comment tokens // we only care about line comment tokens
.0 .0
.and_then(|tokens| { .and_then(|tokens| {
@ -47,54 +47,30 @@ pub fn get_comment_token(
} }
pub fn get_injected_tokens( pub fn get_injected_tokens(
loader: &syntax::Loader,
syntax: Option<&Syntax>, syntax: Option<&Syntax>,
start: usize, start: u32,
end: usize, end: u32,
) -> (Option<Vec<String>>, Option<Vec<BlockCommentToken>>) { ) -> (Option<Vec<String>>, Option<Vec<BlockCommentToken>>) {
// Find the injection with the most tightly encompassing range. // Find the injection with the most tightly encompassing range.
syntax syntax
.and_then(|syntax| { .map(|syntax| {
injection_for_range(syntax, start, end).map(|language_id| { let config = loader
let config = syntax.layer_config(language_id); .language(
( syntax
config.comment_tokens.clone(), .layer(syntax.layer_for_byte_range(start, end))
config.block_comment_tokens.clone(), .language,
) )
}) .config();
(
config.comment_tokens.clone(),
config.block_comment_tokens.clone(),
)
}) })
.unwrap_or_default() .unwrap_or_default()
} }
/// For a given range in the document, get the most tightly encompassing
/// injection layer corresponding to that range.
pub fn injection_for_range(syntax: &Syntax, from: usize, to: usize) -> Option<LayerId> {
let mut best_fit = None;
let mut min_gap = usize::MAX;
for (layer_id, layer) in syntax.layers() {
for ts_range in &layer.ranges {
let is_encompassing = ts_range.start_byte <= from && ts_range.end_byte >= to;
if is_encompassing {
let gap = ts_range.end_byte - ts_range.start_byte;
let config = syntax.layer_config(layer_id);
// ignore the language family for which it won't make
// sense to consider their comment.
//
// This includes, for instance, `comment`, `jsdoc`, `regex`
let has_comment_tokens =
config.comment_tokens.is_some() || config.block_comment_tokens.is_some();
if gap < min_gap && has_comment_tokens {
best_fit = Some(layer_id);
min_gap = gap;
}
}
}
}
best_fit
}
/// Given text, a comment token, and a set of line indices, returns the following: /// Given text, a comment token, and a set of line indices, returns the following:
/// - Whether the given lines should be considered commented /// - Whether the given lines should be considered commented
/// - If any of the lines are uncommented, all lines are considered as such. /// - If any of the lines are uncommented, all lines are considered as such.
@ -598,32 +574,32 @@ mod test {
assert_eq!(doc, ""); assert_eq!(doc, "");
} }
/// Test, if `get_comment_tokens` works, even if the content of the file includes chars, whose // Test, if `get_comment_tokens` works, even if the content of the file includes chars, whose
/// byte size unequal the amount of chars // byte size unequal the amount of chars
#[test] // #[test]
fn test_get_comment_with_char_boundaries() { // fn test_get_comment_with_char_boundaries() {
let rope = Rope::from("··"); // let rope = Rope::from("··");
let tokens = vec!["//".to_owned(), "///".to_owned()]; // let tokens = vec!["//".to_owned(), "///".to_owned()];
assert_eq!( // assert_eq!(
super::get_comment_token(None, rope.slice(..), Some(&tokens), 0), // super::get_comment_token(None, rope.slice(..), Some(&tokens), 0),
None // None
); // );
} // }
/// Test for `get_comment_token`. // /// Test for `get_comment_token`.
/// // ///
/// Assuming the comment tokens are stored as `["///", "//"]`, `get_comment_token` should still // /// Assuming the comment tokens are stored as `["///", "//"]`, `get_comment_token` should still
/// return `///` instead of `//` if the user is in a doc-comment section. // /// return `///` instead of `//` if the user is in a doc-comment section.
#[test] // #[test]
fn test_use_longest_comment() { // fn test_use_longest_comment() {
let text = Rope::from(" /// amogus ඞ"); // let text = Rope::from(" /// amogus ඞ");
let tokens = vec!["///".to_owned(), "//".to_owned()]; // let tokens = vec!["///".to_owned(), "//".to_owned()];
assert_eq!( // assert_eq!(
super::get_comment_token(None, text.slice(..), Some(&tokens), 0), // super::get_comment_token(None, text.slice(..), Some(&tokens), 0),
Some("///".to_owned()) // Some("///".to_owned())
); // );
} // }
} }
} }

View File

@ -35,7 +35,10 @@ use helix_core::{
regex::{self, Regex}, regex::{self, Regex},
search::{self, CharMatcher}, search::{self, CharMatcher},
selection, surround, selection, surround,
syntax::config::{BlockCommentToken, LanguageServerFeature}, syntax::{
self,
config::{BlockCommentToken, LanguageServerFeature},
},
text_annotations::{Overlay, TextAnnotations}, text_annotations::{Overlay, TextAnnotations},
textobject, textobject,
unicode::width::UnicodeWidthChar, unicode::width::UnicodeWidthChar,
@ -3669,7 +3672,7 @@ fn open(cx: &mut Context, open: Open, comment_continuation: CommentContinuation)
let above_next_new_line_num = next_new_line_num.saturating_sub(1); let above_next_new_line_num = next_new_line_num.saturating_sub(1);
let continue_comment_token = let continue_comment_token =
comment::get_comment_token(syntax, text, doc_default_tokens, curr_line_num) comment::get_comment_token(&loader, syntax, text, doc_default_tokens, curr_line_num)
.filter(|_| continue_comments); .filter(|_| continue_comments);
// Index to insert newlines after, as well as the char width // Index to insert newlines after, as well as the char width
@ -4222,9 +4225,14 @@ pub mod insert {
let current_line = text.char_to_line(pos); let current_line = text.char_to_line(pos);
let line_start = text.line_to_char(current_line); let line_start = text.line_to_char(current_line);
let continue_comment_token = let continue_comment_token = comment::get_comment_token(
comment::get_comment_token(syntax, text, doc_default_comment_token, current_line) &doc.syn_loader.load(),
.filter(|_| config.continue_comments); syntax,
text,
doc_default_comment_token,
current_line,
)
.filter(|_| config.continue_comments);
let (from, to, local_offs) = if let Some(idx) = let (from, to, local_offs) = if let Some(idx) =
text.slice(line_start..pos).last_non_whitespace_char() text.slice(line_start..pos).last_non_whitespace_char()
@ -5170,6 +5178,7 @@ where
Option<&str>, Option<&str>,
Option<&[BlockCommentToken]>, Option<&[BlockCommentToken]>,
Option<&Syntax>, Option<&Syntax>,
&syntax::Loader,
) -> Transaction, ) -> Transaction,
{ {
let (view, doc) = current!(cx.editor); let (view, doc) = current!(cx.editor);
@ -5189,8 +5198,14 @@ where
.map(|tc| &tc[..]); .map(|tc| &tc[..]);
// Call the custom logic provided by the caller (the original functions). // Call the custom logic provided by the caller (the original functions).
let transaction = let transaction = comments_transaction(
comments_transaction(rope, selection, doc_line_token, doc_block_tokens, syntax); rope,
selection,
doc_line_token,
doc_block_tokens,
syntax,
&doc.syn_loader.load(),
);
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
exit_select_mode(cx); exit_select_mode(cx);
@ -5199,12 +5214,17 @@ where
fn toggle_comments(cx: &mut Context) { fn toggle_comments(cx: &mut Context) {
toggle_comments_impl( toggle_comments_impl(
cx, cx,
|rope, selection, doc_line_token, doc_block_tokens, syntax| { |rope, selection, doc_line_token, doc_block_tokens, syntax, loader| {
Transaction::change( Transaction::change(
rope, rope,
selection.iter().flat_map(|range| { selection.iter().flat_map(|range| {
let (injected_line_tokens, injected_block_tokens) = let (injected_line_tokens, injected_block_tokens) =
comment::get_injected_tokens(syntax, range.from(), range.to()); comment::get_injected_tokens(
loader,
syntax,
range.from() as u32,
range.to() as u32,
);
let line_token = injected_line_tokens let line_token = injected_line_tokens
.as_ref() .as_ref()
@ -5272,7 +5292,7 @@ fn toggle_comments(cx: &mut Context) {
fn toggle_line_comments(cx: &mut Context) { fn toggle_line_comments(cx: &mut Context) {
toggle_comments_impl( toggle_comments_impl(
cx, cx,
|rope, selection, doc_line_token, doc_block_tokens, syntax| { |rope, selection, doc_line_token, doc_block_tokens, syntax, loader| {
let mut selections = SmallVec::new(); let mut selections = SmallVec::new();
let mut added_chars = 0; let mut added_chars = 0;
let mut removed_chars = 0; let mut removed_chars = 0;
@ -5281,7 +5301,12 @@ fn toggle_line_comments(cx: &mut Context) {
rope, rope,
selection.iter().flat_map(|range| { selection.iter().flat_map(|range| {
let (injected_line_tokens, injected_block_tokens) = let (injected_line_tokens, injected_block_tokens) =
comment::get_injected_tokens(syntax, range.from(), range.to()); comment::get_injected_tokens(
loader,
syntax,
range.from() as u32,
range.to() as u32,
);
let line_token = injected_line_tokens let line_token = injected_line_tokens
.as_ref() .as_ref()
@ -5321,7 +5346,7 @@ fn toggle_line_comments(cx: &mut Context) {
fn toggle_block_comments(cx: &mut Context) { fn toggle_block_comments(cx: &mut Context) {
toggle_comments_impl( toggle_comments_impl(
cx, cx,
|rope, selection, doc_line_token, doc_block_tokens, syntax| { |rope, selection, doc_line_token, doc_block_tokens, syntax, loader| {
let mut selections = SmallVec::new(); let mut selections = SmallVec::new();
let mut added_chars = 0; let mut added_chars = 0;
let mut removed_chars = 0; let mut removed_chars = 0;
@ -5330,7 +5355,12 @@ fn toggle_block_comments(cx: &mut Context) {
rope, rope,
selection.iter().flat_map(|range| { selection.iter().flat_map(|range| {
let (injected_line_tokens, injected_block_tokens) = let (injected_line_tokens, injected_block_tokens) =
comment::get_injected_tokens(syntax, range.from(), range.to()); comment::get_injected_tokens(
loader,
syntax,
range.from() as u32,
range.to() as u32,
);
let line_token = injected_line_tokens let line_token = injected_line_tokens
.as_ref() .as_ref()

View File

@ -1668,102 +1668,103 @@ fn tree_sitter_scopes(
} }
fn tree_sitter_injections( fn tree_sitter_injections(
cx: &mut compositor::Context, _cx: &mut compositor::Context,
_args: Args, _args: Args,
event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if event != PromptEvent::Validate { unimplemented!()
return Ok(()); // if event != PromptEvent::Validate {
} // return Ok(());
// }
let doc = doc!(cx.editor); // let doc = doc!(cx.editor);
let syntax = doc // let syntax = doc
.syntax() // .syntax()
.context("No tree-sitter grammar found for this file.")?; // .context("No tree-sitter grammar found for this file.")?;
let mut ranges = vec![]; // let mut ranges = vec![];
for (language_id, layer) in syntax.layers() { // for (language_id, layer) in syntax.layers() {
let language_name = &syntax.layer_config(language_id).language_name; // let language_name = &syntax.layer_config(language_id).language_name;
for range in &layer.ranges { // for range in &layer.ranges {
ranges.push((range, language_name.clone())); // ranges.push((range, language_name.clone()));
} // }
} // }
ranges.sort_unstable_by(|(range_a, _), (range_b, _)| { // ranges.sort_unstable_by(|(range_a, _), (range_b, _)| {
range_a // range_a
.start_byte // .start_byte
.cmp(&range_b.start_byte) // .cmp(&range_b.start_byte)
.then(range_a.end_byte.cmp(&range_b.end_byte)) // .then(range_a.end_byte.cmp(&range_b.end_byte))
}); // });
let char_count = doc.text().len_chars(); // let char_count = doc.text().len_chars();
let mut contents = String::new(); // let mut contents = String::new();
let mut stack = Vec::new(); // let mut stack = Vec::new();
let mut ranges = ranges.iter().peekable(); // let mut ranges = ranges.iter().peekable();
while let Some((range, language_name)) = ranges.next() { // while let Some((range, language_name)) = ranges.next() {
while let Some((prev_start, prev_end)) = stack.last() { // while let Some((prev_start, prev_end)) = stack.last() {
let is_contained = range.end_byte < *prev_end && range.start_byte > *prev_start; // let is_contained = range.end_byte < *prev_end && range.start_byte > *prev_start;
if is_contained { // if is_contained {
break; // break;
} // }
stack.pop(); // stack.pop();
} // }
let language_range = if range.end_byte < char_count { // let language_range = if range.end_byte < char_count {
format!("[{}, {}]", range.start_byte, range.end_byte) // format!("[{}, {}]", range.start_byte, range.end_byte)
} else { // } else {
format!("[0, {}]", char_count) // format!("[0, {}]", char_count)
}; // };
let indent = stack.len() * 4; // let indent = stack.len() * 4;
let indent = format!("{:indent$}", ""); // let indent = format!("{:indent$}", "");
let next_is_contained = ranges.peek().as_ref().is_some_and(|(next, _)| { // let next_is_contained = ranges.peek().as_ref().is_some_and(|(next, _)| {
range.end_byte > next.end_byte && range.start_byte < next.start_byte // range.end_byte > next.end_byte && range.start_byte < next.start_byte
}); // });
let children = if next_is_contained { // let children = if next_is_contained {
format!("\n{indent} injections:") // format!("\n{indent} injections:")
} else { // } else {
"".into() // "".into()
}; // };
let dash = if !indent.is_empty() { // let dash = if !indent.is_empty() {
format!("{}- ", &indent) // format!("{}- ", &indent)
} else { // } else {
"- ".into() // "- ".into()
}; // };
writeln!( // writeln!(
contents, // contents,
"{dash}language: {language_name} // "{dash}language: {language_name}
{indent} range: {language_range}{children}", // {indent} range: {language_range}{children}",
)?; // )?;
stack.push((range.start_byte, range.end_byte)); // stack.push((range.start_byte, range.end_byte));
} // }
let callback = async move { // let callback = async move {
let call: job::Callback = Callback::EditorCompositor(Box::new( // let call: job::Callback = Callback::EditorCompositor(Box::new(
move |editor: &mut Editor, compositor: &mut Compositor| { // move |editor: &mut Editor, compositor: &mut Compositor| {
let contents = // let contents =
ui::Markdown::new(format!("```yaml\n{contents}```"), editor.syn_loader.clone()); // ui::Markdown::new(format!("```yaml\n{contents}```"), editor.syn_loader.clone());
let popup = Popup::new("hover", contents).auto_close(true); // let popup = Popup::new("hover", contents).auto_close(true);
compositor.replace_or_push("hover", popup); // compositor.replace_or_push("hover", popup);
}, // },
)); // ));
Ok(call) // Ok(call)
}; // };
cx.jobs.callback(callback); // cx.jobs.callback(callback);
Ok(()) // Ok(())
} }
fn tree_sitter_highlight_name( fn tree_sitter_highlight_name(

View File

@ -183,7 +183,7 @@ pub fn languages_all() -> std::io::Result<()> {
syn_loader_conf syn_loader_conf
.language .language
.sort_unstable_by_key(|l| l.language_name.clone()); .sort_unstable_by_key(|l| l.language_id.clone());
let check_binary = |cmd: Option<&str>| match cmd { let check_binary = |cmd: Option<&str>| match cmd {
Some(cmd) => match helix_stdx::env::which(cmd) { Some(cmd) => match helix_stdx::env::which(cmd) {
@ -194,7 +194,7 @@ pub fn languages_all() -> std::io::Result<()> {
}; };
for lang in &syn_loader_conf.language { for lang in &syn_loader_conf.language {
write!(stdout, "{}", fit(&lang.language_name))?; write!(stdout, "{}", fit(&lang.language_id))?;
let mut cmds = lang.language_servers.iter().filter_map(|ls| { let mut cmds = lang.language_servers.iter().filter_map(|ls| {
syn_loader_conf syn_loader_conf
@ -214,7 +214,7 @@ pub fn languages_all() -> std::io::Result<()> {
write!(stdout, "{}", check_binary(formatter))?; write!(stdout, "{}", check_binary(formatter))?;
for ts_feat in TsFeature::all() { for ts_feat in TsFeature::all() {
match load_runtime_file(&lang.language_name, ts_feat.runtime_filename()).is_ok() { match load_runtime_file(&lang.language_id, ts_feat.runtime_filename()).is_ok() {
true => write!(stdout, "{}", color(fit(""), Color::Green))?, true => write!(stdout, "{}", color(fit(""), Color::Green))?,
false => write!(stdout, "{}", color(fit(""), Color::Red))?, false => write!(stdout, "{}", color(fit(""), Color::Red))?,
} }
@ -257,7 +257,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
let lang = match syn_loader_conf let lang = match syn_loader_conf
.language .language
.iter() .iter()
.find(|l| l.language_name == lang_str) .find(|l| l.language_id == lang_str)
{ {
Some(l) => l, Some(l) => l,
None => { None => {
@ -266,11 +266,8 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
let suggestions: Vec<&str> = syn_loader_conf let suggestions: Vec<&str> = syn_loader_conf
.language .language
.iter() .iter()
.filter(|l| { .filter(|l| l.language_id.starts_with(lang_str.chars().next().unwrap()))
l.language_name .map(|l| l.language_id.as_str())
.starts_with(lang_str.chars().next().unwrap())
})
.map(|l| l.language_name.as_str())
.collect(); .collect();
if !suggestions.is_empty() { if !suggestions.is_empty() {
let suggestions = suggestions.join(", "); let suggestions = suggestions.join(", ");
@ -304,7 +301,7 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
.map(|formatter| formatter.command.to_string()), .map(|formatter| formatter.command.to_string()),
)?; )?;
probe_parser(lang.grammar.as_ref().unwrap_or(&lang.language_name))?; probe_parser(lang.grammar.as_ref().unwrap_or(&lang.language_id))?;
for ts_feat in TsFeature::all() { for ts_feat in TsFeature::all() {
probe_treesitter_feature(&lang_str, *ts_feat)? probe_treesitter_feature(&lang_str, *ts_feat)?

View File

@ -498,7 +498,7 @@ pub mod completers {
let loader = editor.syn_loader.load(); let loader = editor.syn_loader.load();
let language_ids = loader let language_ids = loader
.language_configs() .language_configs()
.map(|config| &config.language_name) .map(|config| &config.language_id)
.chain(std::iter::once(&text)); .chain(std::iter::once(&text));
fuzzy_match(input, language_ids, false) fuzzy_match(input, language_ids, false)

View File

@ -1,103 +1,116 @@
use super::*; use super::*;
/// Comment and uncomment mod simple {
#[tokio::test(flavor = "multi_thread")] use super::*;
async fn test_injected_comment_tokens_simple() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread")]
// Uncomment inner injection async fn uncomment_inner_injection() -> anyhow::Result<()> {
test(( test((
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s). // Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
Comment toggle #[|on this line s]#hould use the javascript comment token(s). Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
// Comment inner injection Ok(())
test(( }
indoc! {r#"\ #[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> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
Comment toggle #[|on this line s]#hould use the javascript comment token(s). Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s). // Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
// Block comment inner injection Ok(())
test(( }
indoc! {r#"\ #[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> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s). // Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> C", ":lang html<ret> C",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|/* on this line s */]#hould use the javascript comment token(s). // Comment toggle #[|/* on this line s */]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
// Block uncomment inner injection Ok(())
test(( }
indoc! {r#"\ #[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> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|/* on this line s */]#hould use the javascript comment token(s). // Comment toggle #[|/* on this line s */]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> C", ":lang html<ret> C",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|on this line s]#hould use the javascript comment token(s). // Comment toggle #[|on this line s]#hould use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(()) Ok(())
}
} }
#[tokio::test(flavor = "multi_thread")] mod injected_comment_tokens_continue_comment {
async fn test_injected_comment_tokens_continue_comment() -> anyhow::Result<()> { use super::*;
test((
indoc! {r#"\ #[tokio::test(flavor = "multi_thread")]
async fn adds_new_comment_on_newline() -> anyhow::Result<()> {
test((
indoc! {r#"
<p>Some text 1234</p> <p>Some text 1234</p>
<script type="text/javascript"> <script type="text/javascript">
// This line should #[|c]#ontinue comments // This line should #[|c]#ontinue comments
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret>i<ret>", ":lang html<ret>i<ret>",
indoc! {r#"\ indoc! {r#"
<p>Some text 1234</p> <p>Some text 1234</p>
<script type="text/javascript"> <script type="text/javascript">
// This line should // This line should
@ -105,11 +118,15 @@ async fn test_injected_comment_tokens_continue_comment() -> anyhow::Result<()> {
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
}
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ async fn continues_comment() -> anyhow::Result<()> {
test((
indoc! {r#"\
<p>Some text 1234</p> <p>Some text 1234</p>
<script type="text/javascript"> <script type="text/javascript">
// This line should // This line should
@ -117,8 +134,8 @@ async fn test_injected_comment_tokens_continue_comment() -> anyhow::Result<()> {
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret>i<ret>", ":lang html<ret>i<ret>",
indoc! {r#"\ indoc! {r#"\
<p>Some text 1234</p> <p>Some text 1234</p>
<script type="text/javascript"> <script type="text/javascript">
// This line should // This line should
@ -127,29 +144,14 @@ async fn test_injected_comment_tokens_continue_comment() -> anyhow::Result<()> {
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
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?;
#[tokio::test(flavor = "multi_thread")]
async fn test_injected_comment_tokens_continue_comment_d() -> anyhow::Result<()> {
test(( test((
indoc! {r#"\ indoc! {r#"\
<p>Some text 1234</p> <p>Some text 1234</p>
@ -173,72 +175,81 @@ async fn test_injected_comment_tokens_continue_comment() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
/// Selections in different regions mod multiple_selections_different_injection_layers {
#[tokio::test(flavor = "multi_thread")] use super::*;
async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()> {
// Comments two different injection layers with different comments #[tokio::test(flavor = "multi_thread")]
test(( async fn comments_two_different_injection_layers_with_different_comments() -> anyhow::Result<()>
indoc! {r#"\ {
test((
indoc! {r#"\
<p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
Comment toggle #(|on this line )#should use the javascript comment token(s). Comment toggle #(|on this line )#should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<!-- <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> --> <!-- <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> -->
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #(|on this line )#should use the javascript comment token(s). // Comment toggle #(|on this line )#should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
// Uncomments two different injection layers with different comments }
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ 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> --> <!-- <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> -->
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #(|on this line )#should use the javascript comment token(s). // Comment toggle #(|on this line )#should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p> <p>Comment toggle #[|on this line ]#should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
Comment toggle #(|on this line )#should use the javascript comment token(s). Comment toggle #(|on this line )#should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
// Works with multiple selections }
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ 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> <p>Comment toggle #(|on this line )#should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|on this line ]#should use the javascript comment token(s). // Comment toggle #[|on this line ]#should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<!-- <p>Comment toggle #(|on this line )#should use the HTML comment token(s).</p> --> <!-- <p>Comment toggle #(|on this line )#should use the HTML comment token(s).</p> -->
<script type="text/javascript"> <script type="text/javascript">
Comment toggle #[|on this line ]#should use the javascript comment token(s). Comment toggle #[|on this line ]#should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
// Works with nested injection layers: html, js then css }
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ 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> --> <!-- <p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p> -->
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #(|on this line)# should use the javascript comment token(s). // Comment toggle #(|on this line)# should use the javascript comment token(s).
@ -250,8 +261,8 @@ async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()
` `
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p> <p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
Comment toggle #(|on this line)# should use the javascript comment token(s). Comment toggle #(|on this line)# should use the javascript comment token(s).
@ -263,12 +274,14 @@ async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()
` `
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
// Full-line selection commenting }
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ 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> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s). // Comment toggle on this line should use the javascript comment token(s).
@ -280,8 +293,8 @@ async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()
`; `;
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s). // Comment toggle on this line should use the javascript comment token(s).
@ -293,10 +306,10 @@ async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()
`; `;
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
test(( test((
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s). // Comment toggle on this line should use the javascript comment token(s).
@ -308,8 +321,8 @@ async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()
`; `;
</script> </script>
"#}, "#},
":lang html<ret> c", ":lang html<ret> c",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle on this line should use the HTML comment token(s).</p> <p>Comment toggle on this line should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle on this line should use the javascript comment token(s). // Comment toggle on this line should use the javascript comment token(s).
@ -321,146 +334,152 @@ async fn test_injected_comment_tokens_multiple_selections() -> anyhow::Result<()
`; `;
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
// Works with block comment toggle across different layers }
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ 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> <p>Comment toggle #(|on this line)# should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|on this line]# should use the javascript comment token(s). // Comment toggle #[|on this line]# should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
":lang html<ret> C", ":lang html<ret> C",
indoc! {r#"\ indoc! {r#"\
<p>Comment toggle #(|<!-- on this line -->)# should use the HTML comment token(s).</p> <p>Comment toggle #(|<!-- on this line -->)# should use the HTML comment token(s).</p>
<script type="text/javascript"> <script type="text/javascript">
// Comment toggle #[|/* on this line */]# should use the javascript comment token(s). // Comment toggle #[|/* on this line */]# should use the javascript comment token(s).
foo(); foo();
</script> </script>
"#}, "#},
)) ))
.await?; .await?;
Ok(())
// Many selections on the same line }
test(( #[tokio::test(flavor = "multi_thread")]
indoc! {r#"\ async fn multiple_selections_same_line() -> anyhow::Result<()> {
<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> test((
<script type="text/javascript"> indoc! {r#"\
// Comment toggle on this line should use the javascript comment token(s). <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>
foo(); <script type="text/javascript">
css` // Comment toggle on this line should use the javascript comment token(s).
html { foo();
background-color: red; css`
} html {
`; background-color: red;
</script> }
"#}, `;
":lang html<ret> C", </script>
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> ":lang html<ret> C",
<script type="text/javascript"> indoc! {r#"\
// Comment toggle on this line should use the javascript comment token(s). <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>
foo(); <script type="text/javascript">
css` // Comment toggle on this line should use the javascript comment token(s).
html { foo();
background-color: red; css`
} html {
`; background-color: red;
</script> }
"#}, `;
)) </script>
.await?; "#},
test(( ))
indoc! {r#"\ .await?;
<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> test((
<script type="text/javascript"> indoc! {r#"\
// Comment toggle on this line should use the javascript comment token(s). <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>
foo(); <script type="text/javascript">
css` // Comment toggle on this line should use the javascript comment token(s).
html { foo();
background-color: red; css`
} html {
`; background-color: red;
</script> }
"#}, `;
":lang html<ret> C", </script>
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> ":lang html<ret> C",
<script type="text/javascript"> indoc! {r#"\
// Comment toggle on this line should use the javascript comment token(s). <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>
foo(); <script type="text/javascript">
css` // Comment toggle on this line should use the javascript comment token(s).
html { foo();
background-color: red; css`
} html {
`; background-color: red;
</script> }
"#}, `;
)) </script>
.await?; "#},
))
// Many single-selections .await?;
test(( Ok(())
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> #[tokio::test(flavor = "multi_thread")]
<script type="text/javascript"> async fn many_single_line_selections() -> anyhow::Result<()> {
// C#(|o)#mment t#(|o)#ggle #(|o)#n this line sh#(|o)#uld use the javascript c#(|o)#mment t#(|o)#ken(s). test((
f#(|o)##(|o)#(); indoc! {r#"\
css` <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>
html { <script type="text/javascript">
backgr#(|o)#und-c#(|o)#l#(|o)#r: red; // 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`
</script> html {
"#}, backgr#(|o)#und-c#(|o)#l#(|o)#r: red;
":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>
<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). ":lang html<ret> C",
f#(|/* o */)##(|/* o */)#(); indoc! {r#"\
css` <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>
html { <script type="text/javascript">
backgr#(|/* o */)#und-c#(|/* o */)#l#(|/* o */)#r: red; // 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`
</script> html {
"#}, backgr#(|/* o */)#und-c#(|/* o */)#l#(|/* o */)#r: red;
)) }
.await?; `;
test(( </script>
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"> .await?;
// C#(|/* o */)#mment t#(|/* o */)#ggle #(|/* o */)#n this line sh#(|/* o */)#uld use the javascript c#(|/* o */)#mment t#(|/* o */)#ken(s). test((
f#(|/* o */)##(|/* o */)#(); indoc! {r#"\
css` <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>
html { <script type="text/javascript">
backgr#(|/* o */)#und-c#(|/* o */)#l#(|/* o */)#r: red; // 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`
</script> html {
"#}, backgr#(|/* o */)#und-c#(|/* o */)#l#(|/* o */)#r: red;
":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>
<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). ":lang html<ret> C",
f#(|o)##(|o)#(); indoc! {r#"\
css` <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>
html { <script type="text/javascript">
backgr#(|o)#und-c#(|o)#l#(|o)#r: red; // 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`
</script> html {
"#}, backgr#(|o)#und-c#(|o)#l#(|o)#r: red;
)) }
.await?; `;
</script>
Ok(()) "#},
))
.await?;
Ok(())
}
} }
/// A selection that spans across several injections takes comment tokens /// A selection that spans across several injections takes comment tokens

View File

@ -213,7 +213,7 @@ pub struct Document {
// NOTE: this field should eventually go away - we should use the Editor's syn_loader instead // NOTE: this field should eventually go away - we should use the Editor's syn_loader instead
// of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the // of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the
// `ArcSwap` directly. // `ArcSwap` directly.
syn_loader: Arc<ArcSwap<syntax::Loader>>, pub syn_loader: Arc<ArcSwap<syntax::Loader>>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -1787,7 +1787,7 @@ impl Document {
pub fn language_name(&self) -> Option<&str> { pub fn language_name(&self) -> Option<&str> {
self.language self.language
.as_ref() .as_ref()
.map(|language| language.language_name.as_str()) .map(|language| language.language_id.as_str())
} }
/// Language ID for the document. Either the `language-id`, /// Language ID for the document. Either the `language-id`,

5
index.html 100644
View File

@ -0,0 +1,5 @@
<!-- <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>

View File

@ -134,7 +134,7 @@ pub fn lang_features() -> Result<String, DynError> {
let mut langs = config let mut langs = config
.language .language
.iter() .iter()
.map(|l| l.language_name.clone()) .map(|l| l.language_id.clone())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
langs.sort_unstable(); langs.sort_unstable();
@ -148,9 +148,9 @@ pub fn lang_features() -> Result<String, DynError> {
let lc = config let lc = config
.language .language
.iter() .iter()
.find(|l| l.language_name == lang) .find(|l| l.language_id == lang)
.unwrap(); // lang comes from config .unwrap(); // lang comes from config
row.push(lc.language_name.clone()); row.push(lc.language_id.clone());
for (_feat, support_list) in &ts_features_to_langs { for (_feat, support_list) in &ts_features_to_langs {
row.push( row.push(