mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 05:05:00 +00:00
feat: handle enters in list or enum items (#1442)
* test: add tests about `onEnter` * feat: handle enters in list or enum items
This commit is contained in:
parent
78b80d41e4
commit
f13b632ad2
19 changed files with 200 additions and 17 deletions
|
@ -109,7 +109,7 @@ mod tests {
|
||||||
fn run(config: TestConfig) -> impl Fn(&mut LocalContext, PathBuf) {
|
fn run(config: TestConfig) -> impl Fn(&mut LocalContext, PathBuf) {
|
||||||
fn test(ctx: &mut LocalContext, id: TypstFileId) {
|
fn test(ctx: &mut LocalContext, id: TypstFileId) {
|
||||||
let source = ctx.source_by_id(id).unwrap();
|
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 text = source.text()[rng.clone()].to_string();
|
||||||
|
|
||||||
let docs = find_module_level_docs(&source).unwrap_or_default();
|
let docs = find_module_level_docs(&source).unwrap_or_default();
|
||||||
|
|
2
crates/tinymist-query/src/fixtures/on_enter/enum.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/enum.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 3..3 */
|
||||||
|
+ a
|
2
crates/tinymist-query/src/fixtures/on_enter/enum2.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/enum2.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 3..3 */
|
||||||
|
+
|
2
crates/tinymist-query/src/fixtures/on_enter/list.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/list.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 3..3 */
|
||||||
|
- a
|
2
crates/tinymist-query/src/fixtures/on_enter/list2.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/list2.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 3..3 */
|
||||||
|
-
|
2
crates/tinymist-query/src/fixtures/on_enter/math.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/math.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 2..2 */
|
||||||
|
$$
|
2
crates/tinymist-query/src/fixtures/on_enter/math2.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/math2.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 1..2 */
|
||||||
|
$$
|
2
crates/tinymist-query/src/fixtures/on_enter/math3.typ
Normal file
2
crates/tinymist-query/src/fixtures/on_enter/math3.typ
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
/* range after 2..3 */
|
||||||
|
$$
|
6
crates/tinymist-query/src/fixtures/on_enter/nothing.typ
Normal file
6
crates/tinymist-query/src/fixtures/on_enter/nothing.typ
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/* range after 1..2 */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -10,6 +10,9 @@ use crate::{prelude::*, syntax::node_ancestors, SyntaxRequest};
|
||||||
/// - `kbd:Enter` inside triple-slash comments automatically inserts `///`
|
/// - `kbd:Enter` inside triple-slash comments automatically inserts `///`
|
||||||
/// - `kbd:Enter` in the middle or after a trailing space in `//` inserts `//`
|
/// - `kbd:Enter` in the middle or after a trailing space in `//` inserts `//`
|
||||||
/// - `kbd:Enter` inside `//!` doc comments automatically 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
|
/// [`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<Self::Response> {
|
) -> Option<Self::Response> {
|
||||||
let root = LinkedNode::new(source.root());
|
let root = LinkedNode::new(source.root());
|
||||||
let rng = to_typst_range(self.range, position_encoding, source)?;
|
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 leaf = root.leaf_at_compat(cursor)?;
|
||||||
|
|
||||||
let worker = OnEnterWorker {
|
let worker = OnEnterWorker {
|
||||||
|
@ -42,17 +45,42 @@ impl SyntaxRequest for OnEnterRequest {
|
||||||
position_encoding,
|
position_encoding,
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(leaf.kind(), SyntaxKind::LineComment) {
|
enum Cases<'a> {
|
||||||
return worker.enter_line_doc_comment(&leaf, rng);
|
LineComment(LinkedNode<'a>),
|
||||||
|
Equation(LinkedNode<'a>),
|
||||||
|
ListOrEnum(LinkedNode<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
let math_node =
|
let case = node_ancestors(&leaf).find_map(|node| match node.kind() {
|
||||||
node_ancestors(&leaf).find(|node| matches!(node.kind(), SyntaxKind::Equation));
|
SyntaxKind::LineComment => Some(Cases::LineComment(node.clone())),
|
||||||
if let Some(mn) = math_node {
|
SyntaxKind::Equation => Some(Cases::Equation(node.clone())),
|
||||||
return worker.enter_block_math(mn, rng);
|
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)
|
" ".repeat(indent_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_line_doc_comment(
|
fn enter_line_doc_comment(&self, leaf: LinkedNode, rng: Range<usize>) -> Option<Vec<TextEdit>> {
|
||||||
&self,
|
|
||||||
leaf: &LinkedNode,
|
|
||||||
rng: Range<usize>,
|
|
||||||
) -> Option<Vec<TextEdit>> {
|
|
||||||
let skipper = |n: &LinkedNode| {
|
let skipper = |n: &LinkedNode| {
|
||||||
matches!(
|
matches!(
|
||||||
n.kind(),
|
n.kind(),
|
||||||
|
@ -115,7 +139,7 @@ impl OnEnterWorker<'_> {
|
||||||
|
|
||||||
fn enter_block_math(
|
fn enter_block_math(
|
||||||
&self,
|
&self,
|
||||||
math_node: &LinkedNode<'_>,
|
math_node: LinkedNode<'_>,
|
||||||
rng: Range<usize>,
|
rng: Range<usize>,
|
||||||
) -> Option<Vec<TextEdit>> {
|
) -> Option<Vec<TextEdit>> {
|
||||||
let o = math_node.range();
|
let o = math_node.range();
|
||||||
|
@ -143,4 +167,56 @@ impl OnEnterWorker<'_> {
|
||||||
|
|
||||||
Some(vec![edit])
|
Some(vec![edit])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enter_list_or_enum(&self, node: LinkedNode<'_>, rng: Range<usize>) -> Option<Vec<TextEdit>> {
|
||||||
|
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));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use std::{
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
||||||
use tinymist_project::{CompileFontArgs, ExportTarget};
|
use tinymist_project::{CompileFontArgs, ExportTarget};
|
||||||
|
use tinymist_std::debug_loc::LspRange;
|
||||||
use tinymist_std::typst::TypstDocument;
|
use tinymist_std::typst::TypstDocument;
|
||||||
use tinymist_world::package::PackageSpec;
|
use tinymist_world::package::PackageSpec;
|
||||||
use tinymist_world::vfs::WorkspaceResolver;
|
use tinymist_world::vfs::WorkspaceResolver;
|
||||||
|
@ -203,7 +204,12 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBu
|
||||||
f(&mut verse, pw)
|
f(&mut verse, pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_test_range(s: &Source) -> Range<usize> {
|
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<usize> {
|
||||||
// /* range -3..-1 */
|
// /* range -3..-1 */
|
||||||
fn find_prefix(s: &str, sub: &str, left: bool) -> Option<(usize, usize, bool)> {
|
fn find_prefix(s: &str, sub: &str, left: bool) -> Option<(usize, usize, bool)> {
|
||||||
Some((s.find(sub)?, sub.len(), left))
|
Some((s.find(sub)?, sub.len(), left))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue