diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index 273c3139..19f02b20 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -109,7 +109,7 @@ mod tests { fn run(config: TestConfig) -> impl Fn(&mut LocalContext, PathBuf) { fn test(ctx: &mut LocalContext, id: TypstFileId) { let source = ctx.source_by_id(id).unwrap(); - let rng = find_test_range(&source); + let rng = find_test_range_(&source); let text = source.text()[rng.clone()].to_string(); let docs = find_module_level_docs(&source).unwrap_or_default(); diff --git a/crates/tinymist-query/src/fixtures/on_enter/enum.typ b/crates/tinymist-query/src/fixtures/on_enter/enum.typ new file mode 100644 index 00000000..b8089ae7 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/enum.typ @@ -0,0 +1,2 @@ +/* range after 3..3 */ ++ a diff --git a/crates/tinymist-query/src/fixtures/on_enter/enum2.typ b/crates/tinymist-query/src/fixtures/on_enter/enum2.typ new file mode 100644 index 00000000..8281253c --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/enum2.typ @@ -0,0 +1,2 @@ +/* range after 3..3 */ ++ diff --git a/crates/tinymist-query/src/fixtures/on_enter/list.typ b/crates/tinymist-query/src/fixtures/on_enter/list.typ new file mode 100644 index 00000000..feb5fcb4 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/list.typ @@ -0,0 +1,2 @@ +/* range after 3..3 */ +- a diff --git a/crates/tinymist-query/src/fixtures/on_enter/list2.typ b/crates/tinymist-query/src/fixtures/on_enter/list2.typ new file mode 100644 index 00000000..11592d43 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/list2.typ @@ -0,0 +1,2 @@ +/* range after 3..3 */ +- diff --git a/crates/tinymist-query/src/fixtures/on_enter/math.typ b/crates/tinymist-query/src/fixtures/on_enter/math.typ new file mode 100644 index 00000000..771354ec --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/math.typ @@ -0,0 +1,2 @@ +/* range after 2..2 */ +$$ diff --git a/crates/tinymist-query/src/fixtures/on_enter/math2.typ b/crates/tinymist-query/src/fixtures/on_enter/math2.typ new file mode 100644 index 00000000..d96c55a7 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/math2.typ @@ -0,0 +1,2 @@ +/* range after 1..2 */ +$$ diff --git a/crates/tinymist-query/src/fixtures/on_enter/math3.typ b/crates/tinymist-query/src/fixtures/on_enter/math3.typ new file mode 100644 index 00000000..6fad2665 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/math3.typ @@ -0,0 +1,2 @@ +/* range after 2..3 */ +$$ diff --git a/crates/tinymist-query/src/fixtures/on_enter/nothing.typ b/crates/tinymist-query/src/fixtures/on_enter/nothing.typ new file mode 100644 index 00000000..945b7539 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/nothing.typ @@ -0,0 +1,6 @@ +/* range after 1..2 */ + + + + +1 diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@enum.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@enum.typ.snap new file mode 100644 index 00000000..f91ebc30 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@enum.typ.snap @@ -0,0 +1,12 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on 3..3 */\n+ ||a\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/enum.typ +--- +[ + { + "newText": "\n+ $0", + "range": "1:2:1:2" + } +] diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@enum2.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@enum2.typ.snap new file mode 100644 index 00000000..57e0cf5c --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@enum2.typ.snap @@ -0,0 +1,12 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on 3..3 */\n+ ||\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/enum2.typ +--- +[ + { + "newText": "\n+ $0", + "range": "1:2:1:2" + } +] diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@list.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@list.typ.snap new file mode 100644 index 00000000..120044dd --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@list.typ.snap @@ -0,0 +1,12 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on 3..3 */\n- ||a\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/list.typ +--- +[ + { + "newText": "\n- $0", + "range": "1:2:1:2" + } +] diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@list2.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@list2.typ.snap new file mode 100644 index 00000000..1e182b07 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@list2.typ.snap @@ -0,0 +1,12 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on 3..3 */\n- ||\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/list2.typ +--- +[ + { + "newText": "\n- $0", + "range": "1:2:1:2" + } +] diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math.typ.snap new file mode 100644 index 00000000..05cd5e0d --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math.typ.snap @@ -0,0 +1,12 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on 2..2 */\n$||$\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/math.typ +--- +[ + { + "newText": "\n $0\n", + "range": "1:1:1:1" + } +] diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math2.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math2.typ.snap new file mode 100644 index 00000000..9e697d42 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math2.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on r 1..2 */\n|$|$\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/math2.typ +--- +null diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math3.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math3.typ.snap new file mode 100644 index 00000000..50b780e7 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@math3.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on 2..3 */\n$|$|\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/math3.typ +--- +null diff --git a/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@nothing.typ.snap b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@nothing.typ.snap new file mode 100644 index 00000000..2f1a9965 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/on_enter/snaps/prepare@nothing.typ.snap @@ -0,0 +1,7 @@ +--- +source: crates/tinymist-query/src/on_enter.rs +description: "On Enter on r 1..2 */\n|\n|\n\n\n1\n)" +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/on_enter/nothing.typ +--- +null diff --git a/crates/tinymist-query/src/on_enter.rs b/crates/tinymist-query/src/on_enter.rs index 090a03a8..286efe8c 100644 --- a/crates/tinymist-query/src/on_enter.rs +++ b/crates/tinymist-query/src/on_enter.rs @@ -10,6 +10,9 @@ use crate::{prelude::*, syntax::node_ancestors, SyntaxRequest}; /// - `kbd:Enter` inside triple-slash comments automatically inserts `///` /// - `kbd:Enter` in the middle or after a trailing space in `//` inserts `//` /// - `kbd:Enter` inside `//!` doc comments automatically inserts `//!` +/// - `kbd:Enter` inside block math automatically inserts a newline and indents +/// - `kbd:Enter` inside `list` or `enum` items automatically automatically +/// inserts `-` or `+` and indents /// /// [`experimental/onEnter`]: https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/lsp-extensions.md#on-enter /// @@ -34,7 +37,7 @@ impl SyntaxRequest for OnEnterRequest { ) -> Option { let root = LinkedNode::new(source.root()); let rng = to_typst_range(self.range, position_encoding, source)?; - let cursor = rng.end; + let cursor = rng.start; let leaf = root.leaf_at_compat(cursor)?; let worker = OnEnterWorker { @@ -42,17 +45,42 @@ impl SyntaxRequest for OnEnterRequest { position_encoding, }; - if matches!(leaf.kind(), SyntaxKind::LineComment) { - return worker.enter_line_doc_comment(&leaf, rng); + enum Cases<'a> { + LineComment(LinkedNode<'a>), + Equation(LinkedNode<'a>), + ListOrEnum(LinkedNode<'a>), } - let math_node = - node_ancestors(&leaf).find(|node| matches!(node.kind(), SyntaxKind::Equation)); - if let Some(mn) = math_node { - return worker.enter_block_math(mn, rng); - } + let case = node_ancestors(&leaf).find_map(|node| match node.kind() { + SyntaxKind::LineComment => Some(Cases::LineComment(node.clone())), + SyntaxKind::Equation => Some(Cases::Equation(node.clone())), + SyntaxKind::ListItem | SyntaxKind::EnumItem => Some(Cases::ListOrEnum(node.clone())), + SyntaxKind::Space | SyntaxKind::Parbreak => { + let prev_leaf = node.prev_sibling()?; - None + let inter_space = node.offset()..rng.start; + if !inter_space.is_empty() && source.text()[inter_space].contains(['\r', '\n']) { + return None; + } + + match prev_leaf.kind() { + SyntaxKind::ListItem | SyntaxKind::EnumItem => { + return Some(Cases::ListOrEnum(prev_leaf)) + } + _ => {} + } + + None + } + _ => None, + }); + + match case { + Some(Cases::LineComment(node)) => worker.enter_line_doc_comment(node, rng), + Some(Cases::Equation(node)) => worker.enter_block_math(node, rng), + Some(Cases::ListOrEnum(node)) => worker.enter_list_or_enum(node, rng), + _ => None, + } } } @@ -69,11 +97,7 @@ impl OnEnterWorker<'_> { " ".repeat(indent_size) } - fn enter_line_doc_comment( - &self, - leaf: &LinkedNode, - rng: Range, - ) -> Option> { + fn enter_line_doc_comment(&self, leaf: LinkedNode, rng: Range) -> Option> { let skipper = |n: &LinkedNode| { matches!( n.kind(), @@ -115,7 +139,7 @@ impl OnEnterWorker<'_> { fn enter_block_math( &self, - math_node: &LinkedNode<'_>, + math_node: LinkedNode<'_>, rng: Range, ) -> Option> { let o = math_node.range(); @@ -143,4 +167,56 @@ impl OnEnterWorker<'_> { Some(vec![edit]) } + + fn enter_list_or_enum(&self, node: LinkedNode<'_>, rng: Range) -> Option> { + let indent = self.indent_of(node.range().start); + + let is_list = matches!(node.kind(), SyntaxKind::ListItem); + let marker = if is_list { "-" } else { "+" }; + + let edit = TextEdit { + range: to_lsp_range(rng, self.source, self.position_encoding), + new_text: format!("\n{indent}{marker} $0"), + }; + + Some(vec![edit]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::*; + + #[test] + fn prepare() { + snapshot_testing("on_enter", &|world, path| { + let source = world.source_by_path(&path).unwrap(); + + let request = OnEnterRequest { + path: path.clone(), + range: find_test_range(&source), + }; + + let result = request.request(&source, PositionEncoding::Utf16); + + let annotated = { + let range = find_test_range_(&source); + let range_before = range.start.saturating_sub(10)..range.start; + let range_window = range.clone(); + let range_after = range.end..range.end.saturating_add(10).min(source.text().len()); + + let window_before = &source.text()[range_before]; + let window_line = &source.text()[range_window]; + let window_after = &source.text()[range_after]; + format!("{window_before}|{window_line}|{window_after}") + }; + + insta::with_settings!({ + description => format!("On Enter on {annotated})"), + }, { + assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC)); + }) + }); + } } diff --git a/crates/tinymist-query/src/tests.rs b/crates/tinymist-query/src/tests.rs index 991e92cd..0068fb03 100644 --- a/crates/tinymist-query/src/tests.rs +++ b/crates/tinymist-query/src/tests.rs @@ -11,6 +11,7 @@ use std::{ use once_cell::sync::Lazy; use serde_json::{ser::PrettyFormatter, Serializer, Value}; use tinymist_project::{CompileFontArgs, ExportTarget}; +use tinymist_std::debug_loc::LspRange; use tinymist_std::typst::TypstDocument; use tinymist_world::package::PackageSpec; use tinymist_world::vfs::WorkspaceResolver; @@ -203,7 +204,12 @@ pub fn run_with_sources(source: &str, f: impl FnOnce(&mut LspUniverse, PathBu f(&mut verse, pw) } -pub fn find_test_range(s: &Source) -> Range { +pub fn find_test_range(s: &Source) -> LspRange { + let range = find_test_range_(s); + crate::to_lsp_range(range, s, PositionEncoding::Utf16) +} + +pub fn find_test_range_(s: &Source) -> Range { // /* range -3..-1 */ fn find_prefix(s: &str, sub: &str, left: bool) -> Option<(usize, usize, bool)> { Some((s.find(sub)?, sub.len(), left))