mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
fix: correct jump_from_cursor
and add tests (#1589)
* test: add tests to jump from cursor * fix: fuzzy search * fix: stupid filter * docs: add comments * docs: add some todos * fix: comment wording * dev: add a todo * dev: edit a todo * fix: comment
This commit is contained in:
parent
ee1c0ace46
commit
41f8881e8e
6 changed files with 155 additions and 21 deletions
|
@ -0,0 +1,4 @@
|
|||
/// compile: true
|
||||
|
||||
/* range after 7..10 */
|
||||
$ H e l l o W o r l d $
|
|
@ -0,0 +1,4 @@
|
|||
/// compile: true
|
||||
|
||||
/* range after 4..9 */
|
||||
Hello World
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/jump.rs
|
||||
description: "Jump cursor on */\n$ H e |l l| o W o r l)"
|
||||
expression: results
|
||||
input_file: crates/tinymist-query/src/fixtures/jump_from_cursor/markup_text.typ
|
||||
---
|
||||
nothing
|
||||
1,283.354pt,78.500pt
|
||||
nothing
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/jump.rs
|
||||
description: "Jump cursor on ..9 */\nHel|lo Wo|rld\n)"
|
||||
expression: results
|
||||
input_file: crates/tinymist-query/src/fixtures/jump_from_cursor/math_text.typ
|
||||
---
|
||||
1,70.866pt,78.104pt
|
||||
1,70.866pt,78.104pt
|
||||
1,70.866pt,78.104pt
|
||||
1,70.866pt,78.104pt
|
||||
1,70.866pt,78.104pt
|
|
@ -103,22 +103,60 @@ fn jump_from_cursor_(
|
|||
source: &Source,
|
||||
cursor: usize,
|
||||
) -> Option<Vec<Position>> {
|
||||
// todo: leaf_at_compat only matches the text before the cursor, but we could
|
||||
// also match a text if it is after the cursor
|
||||
// The case `leaf_at_compat` will match: `Hello|`
|
||||
// FIXME: The case `leaf_at_compat` will not match: `|Hello`
|
||||
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
|
||||
// todo: When we click on a label or some math operators, we seems likely also
|
||||
// be able to jump to some place.
|
||||
if !matches!(node.kind(), SyntaxKind::Text | SyntaxKind::MathText) {
|
||||
return None;
|
||||
};
|
||||
|
||||
let span = node.span();
|
||||
let offset = cursor.saturating_sub(node.offset());
|
||||
|
||||
// todo: The cursor may not exact hit at the start of some AST node. For
|
||||
// example, the cursor in the text element `Hell|o` is offset by 4 from the
|
||||
// node. It seems not pretty if we ignore the offset completely.
|
||||
let _ = offset;
|
||||
|
||||
match document {
|
||||
TypstDocument::Paged(paged_doc) => {
|
||||
let node = LinkedNode::new(source.root())
|
||||
.leaf_at_compat(cursor)
|
||||
.filter(|node| !matches!(node.kind(), SyntaxKind::Text | SyntaxKind::MathText))?;
|
||||
|
||||
let span = node.span();
|
||||
// We checks whether there are any elements exactly matching the
|
||||
// cursor position.
|
||||
let mut positions = vec![];
|
||||
|
||||
// Unluckily, we might not be able to find the exact spans, so we
|
||||
// need to find the closest one at the same time.
|
||||
let mut min_page = 0;
|
||||
let mut min_point = Point::default();
|
||||
let mut min_dis = u64::MAX;
|
||||
|
||||
for (idx, page) in paged_doc.pages.iter().enumerate() {
|
||||
let mut min_dis = u64::MAX;
|
||||
let mut point = Point::default();
|
||||
if let Some(point) = find_in_frame(&page.frame, span, &mut min_dis, &mut point) {
|
||||
// In a page, we try to find a closer span than the existing found one.
|
||||
let mut p_dis = min_dis;
|
||||
|
||||
if let Some(point) = find_in_frame(&page.frame, span, &mut p_dis, &mut min_point) {
|
||||
if let Some(page) = NonZeroUsize::new(idx + 1) {
|
||||
positions.push(Position { page, point });
|
||||
}
|
||||
}
|
||||
|
||||
// In this page, we found a closer span and update.
|
||||
if p_dis != min_dis {
|
||||
min_page = idx;
|
||||
min_dis = p_dis;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find any exact span, we add the closest one in the same page.
|
||||
if positions.is_empty() && min_dis != u64::MAX {
|
||||
positions.push(Position {
|
||||
page: NonZeroUsize::new(min_page + 1)?,
|
||||
point: min_point,
|
||||
});
|
||||
}
|
||||
|
||||
Some(positions)
|
||||
|
@ -142,13 +180,17 @@ fn find_in_frame(frame: &Frame, span: Span, min_dis: &mut u64, res: &mut Point)
|
|||
if glyph.span.0 == span {
|
||||
return Some(pos);
|
||||
}
|
||||
if glyph.span.0.id() == span.id() {
|
||||
let dis = glyph
|
||||
.span
|
||||
.0
|
||||
.into_raw()
|
||||
.get()
|
||||
.abs_diff(span.into_raw().get());
|
||||
|
||||
// We at least require that the span is in the same file.
|
||||
let is_same_file = glyph.span.0.id() == span.id();
|
||||
if is_same_file {
|
||||
// The numbers are not offsets but a unique id on the AST tree which are
|
||||
// nicely divided.
|
||||
// FIXME: since typst v0.13.0, the numbers are not only the ids, but also raw
|
||||
// ranges, See [`Span::range`].
|
||||
let glyph_num = glyph.span.0.into_raw();
|
||||
let span_num = span.into_raw().get();
|
||||
let dis = glyph_num.get().abs_diff(span_num);
|
||||
if dis < *min_dis {
|
||||
*min_dis = dis;
|
||||
*res = pos;
|
||||
|
@ -167,3 +209,50 @@ fn find_in_frame(frame: &Frame, span: Span, min_dis: &mut u64, res: &mut Point)
|
|||
fn is_in_rect(pos: Point, size: Size, click: Point) -> bool {
|
||||
pos.x <= click.x && pos.x + size.x >= click.x && pos.y <= click.y && pos.y + size.y >= click.y
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::*;
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
snapshot_testing("jump_from_cursor", &|ctx, path| {
|
||||
let source = ctx.source_by_path(&path).unwrap();
|
||||
let docs = find_module_level_docs(&source).unwrap_or_default();
|
||||
let properties = get_test_properties(&docs);
|
||||
|
||||
let graph = compile_doc_for_test(ctx, &properties);
|
||||
let document = graph.snap.success_doc.as_ref().unwrap();
|
||||
|
||||
let cursors = find_test_range_(&source);
|
||||
|
||||
let results = cursors
|
||||
.map(|cursor| {
|
||||
let points = jump_from_cursor(document, &source, cursor);
|
||||
|
||||
if points.is_empty() {
|
||||
return "nothing".to_string();
|
||||
}
|
||||
|
||||
points
|
||||
.iter()
|
||||
.map(|pos| {
|
||||
let page = pos.page.get();
|
||||
let point = pos.point;
|
||||
format!("{page},{:.3}pt,{:.3}pt", point.x.to_pt(), point.y.to_pt())
|
||||
})
|
||||
.join(";")
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
insta::with_settings!({
|
||||
description => format!("Jump cursor on {})", make_range_annoation(&source)),
|
||||
}, {
|
||||
assert_snapshot!(results);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@ use tinymist_world::{EntryManager, EntryReader, EntryState, ShadowApi, TaskInput
|
|||
use typst::foundations::Bytes;
|
||||
use typst::syntax::ast::{self, AstNode};
|
||||
use typst::syntax::{LinkedNode, Source, SyntaxKind, VirtualPath};
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
pub use crate::syntax::find_module_level_docs;
|
||||
pub use insta::assert_snapshot;
|
||||
pub use serde::Serialize;
|
||||
pub use serde_json::json;
|
||||
pub use tinymist_project::{LspUniverse, LspUniverseBuilder};
|
||||
pub use tinymist_world::WorldComputeGraph;
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use crate::syntax::find_module_level_docs;
|
||||
use crate::{analysis::Analysis, prelude::LocalContext, LspPosition, PositionEncoding};
|
||||
use crate::{to_lsp_position, CompletionFeat, LspWorldExt};
|
||||
|
||||
|
@ -234,14 +234,19 @@ pub fn find_test_range_(s: &Source) -> Range<usize> {
|
|||
}
|
||||
|
||||
pub fn find_test_position_after(s: &Source) -> LspPosition {
|
||||
find_test_position_(s, 1)
|
||||
find_test_lsp_pos(s, 1)
|
||||
}
|
||||
|
||||
pub fn find_test_position(s: &Source) -> LspPosition {
|
||||
find_test_position_(s, 0)
|
||||
find_test_lsp_pos(s, 0)
|
||||
}
|
||||
|
||||
pub fn find_test_position_(s: &Source, offset: usize) -> LspPosition {
|
||||
pub fn find_test_lsp_pos(s: &Source, offset: usize) -> LspPosition {
|
||||
let node = find_test_typst_pos(s);
|
||||
to_lsp_position(node + offset, PositionEncoding::Utf16, s)
|
||||
}
|
||||
|
||||
pub fn find_test_typst_pos(s: &Source) -> usize {
|
||||
enum AstMatcher {
|
||||
MatchAny { prev: bool },
|
||||
MatchIdent { prev: bool },
|
||||
|
@ -330,7 +335,19 @@ pub fn find_test_position_(s: &Source, offset: usize) -> LspPosition {
|
|||
break;
|
||||
}
|
||||
|
||||
to_lsp_position(n.offset() + offset, PositionEncoding::Utf16, s)
|
||||
n.offset()
|
||||
}
|
||||
|
||||
pub fn make_range_annoation(source: &Source) -> String {
|
||||
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}")
|
||||
}
|
||||
|
||||
// pub static REDACT_URI: Lazy<RedactFields> = Lazy::new(||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue