mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-16 09:25:01 +00:00
feat: complete on import item path (#949)
The import item path syntax was introduced in typst 0.12.0 and had ruin the completion on import items. * fix: grandparent is ImportItems Fix: for `import "lib.typ": tes|`, the parent syntax is import item path, then parent is import items * feat: identify syntax context in import list * feat: check the path under cursor for import completion * fix: adjust from correctly * dev: remove useless log * fix: the unknown font on windows --------- Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>
This commit is contained in:
parent
a7f203a31c
commit
b4ecb7f14b
13 changed files with 332 additions and 14 deletions
|
@ -0,0 +1,6 @@
|
|||
/// path: base.typ
|
||||
#let aa() = 1;
|
||||
|
||||
-----
|
||||
/// contains: base,aa
|
||||
#import "base.typ": /* range -1..1 */
|
|
@ -0,0 +1,9 @@
|
|||
/// path: base.typ
|
||||
#let table-prefix() = 1;
|
||||
-----
|
||||
/// path: lib.typ
|
||||
#import "base.typ"
|
||||
|
||||
-----
|
||||
/// contains: table,table-prefix
|
||||
#import "lib.typ": base./* range -1..1 */
|
|
@ -0,0 +1,9 @@
|
|||
/// path: base.typ
|
||||
#let table-prefix() = 1;
|
||||
-----
|
||||
/// path: lib.typ
|
||||
#import "base.typ"
|
||||
|
||||
-----
|
||||
/// contains: table,table-prefix
|
||||
#import "lib.typ": base.table/* range -1..1 */
|
|
@ -0,0 +1,10 @@
|
|||
/// path: base.typ
|
||||
#let table-prefix() = 1;
|
||||
#let table-prefix2() = 1;
|
||||
-----
|
||||
/// path: lib.typ
|
||||
#import "base.typ"
|
||||
|
||||
-----
|
||||
/// contains: table,table-prefix,table-prefix2
|
||||
#import "lib.typ": base.table-prefix, base.table/* range -1..1 */
|
|
@ -0,0 +1,6 @@
|
|||
/// path: base.typ
|
||||
#let table-prefix() = 1;
|
||||
|
||||
-----
|
||||
/// contains: base,table,table-prefix
|
||||
#import "base.typ": table/* range -1..1 */
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/completion.rs
|
||||
description: Completion on / (41..43)
|
||||
expression: "JsonRepr::new_pure(results)"
|
||||
input_file: crates/tinymist-query/src/fixtures/completion/import_item.typ
|
||||
snapshot_kind: text
|
||||
---
|
||||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": []
|
||||
},
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "aa",
|
||||
"textEdit": {
|
||||
"newText": "aa",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 20,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 20,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/completion.rs
|
||||
description: Completion on ./ (56..58)
|
||||
expression: "JsonRepr::new_pure(results)"
|
||||
input_file: crates/tinymist-query/src/fixtures/completion/import_item_dot.typ
|
||||
snapshot_kind: text
|
||||
---
|
||||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": []
|
||||
},
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 24,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 24,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/completion.rs
|
||||
description: Completion on e/ (61..63)
|
||||
expression: "JsonRepr::new_pure(results)"
|
||||
input_file: crates/tinymist-query/src/fixtures/completion/import_item_path.typ
|
||||
snapshot_kind: text
|
||||
---
|
||||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 29,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 24,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 29,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 24,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/completion.rs
|
||||
description: Completion on e/ (94..96)
|
||||
expression: "JsonRepr::new_pure(results)"
|
||||
input_file: crates/tinymist-query/src/fixtures/completion/import_item_path_filter.typ
|
||||
snapshot_kind: text
|
||||
---
|
||||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix2",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix2",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 48,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 43,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix2",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix2",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 48,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 43,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/completion.rs
|
||||
description: Completion on e/ (62..64)
|
||||
expression: "JsonRepr::new_pure(results)"
|
||||
input_file: crates/tinymist-query/src/fixtures/completion/import_item_shadow.typ
|
||||
snapshot_kind: text
|
||||
---
|
||||
[
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 25,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 20,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isIncomplete": false,
|
||||
"items": [
|
||||
{
|
||||
"kind": 3,
|
||||
"label": "table-prefix",
|
||||
"textEdit": {
|
||||
"newText": "table-prefix",
|
||||
"range": {
|
||||
"end": {
|
||||
"character": 25,
|
||||
"line": 1
|
||||
},
|
||||
"start": {
|
||||
"character": 20,
|
||||
"line": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -8,6 +8,7 @@ use lsp_types::TextEdit;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use typst::foundations::{fields_on, format_str, repr, Repr, StyleChain, Styles, Value};
|
||||
use typst::model::Document;
|
||||
use typst::syntax::ast::AstNode;
|
||||
use typst::syntax::{ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind};
|
||||
use typst::text::RawElem;
|
||||
use typst::World;
|
||||
|
@ -40,8 +41,8 @@ pub fn autocomplete(
|
|||
|| complete_type(&mut ctx).is_none() && {
|
||||
crate::log_debug_ct!("continue after completing type");
|
||||
complete_labels(&mut ctx)
|
||||
|| complete_field_accesses(&mut ctx)
|
||||
|| complete_imports(&mut ctx)
|
||||
|| complete_field_accesses(&mut ctx)
|
||||
|| complete_markup(&mut ctx)
|
||||
|| complete_math(&mut ctx)
|
||||
|| complete_code(&mut ctx, false)
|
||||
|
@ -538,7 +539,7 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
|||
if let Some(source) = prev.children().find(|child| child.is::<ast::Expr>());
|
||||
then {
|
||||
ctx.from = ctx.cursor;
|
||||
import_item_completions(ctx, items, &source);
|
||||
import_item_completions(ctx, items, vec![], &source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -546,16 +547,21 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
|||
// Behind a half-started identifier in an import list:
|
||||
// "#import "path.typ": thi|",
|
||||
if_chain! {
|
||||
if ctx.leaf.kind() == SyntaxKind::Ident;
|
||||
if let Some(parent) = ctx.leaf.parent();
|
||||
if matches!(ctx.leaf.kind(), SyntaxKind::Ident | SyntaxKind::Dot);
|
||||
if let Some(path_ctx) = ctx.leaf.clone().parent();
|
||||
if path_ctx.kind() == SyntaxKind::ImportItemPath;
|
||||
if let Some(parent) = path_ctx.parent();
|
||||
if parent.kind() == SyntaxKind::ImportItems;
|
||||
if let Some(grand) = parent.parent();
|
||||
if let Some(ast::Expr::Import(import)) = grand.get().cast();
|
||||
if let Some(ast::Imports::Items(items)) = import.imports();
|
||||
if let Some(source) = grand.children().find(|child| child.is::<ast::Expr>());
|
||||
then {
|
||||
ctx.from = ctx.leaf.offset();
|
||||
import_item_completions(ctx, items, &source);
|
||||
if ctx.leaf.kind() == SyntaxKind::Ident {
|
||||
ctx.from = ctx.leaf.offset();
|
||||
}
|
||||
let path = path_ctx.cast::<ast::ImportItemPath>().map(|path| path.iter().take_while(|ident| ident.span() != ctx.leaf.span()).collect());
|
||||
import_item_completions(ctx, items, path.unwrap_or_default(), &source);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -567,22 +573,43 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
|||
fn import_item_completions<'a>(
|
||||
ctx: &mut CompletionContext<'a>,
|
||||
existing: ast::ImportItems<'a>,
|
||||
comps: Vec<ast::Ident>,
|
||||
source: &LinkedNode,
|
||||
) {
|
||||
let Some(value) = ctx.ctx.analyze_import(source).1 else {
|
||||
// Select the source by `comps`
|
||||
let value = ctx.ctx.module_by_syntax(source);
|
||||
let value = comps
|
||||
.iter()
|
||||
.fold(value.as_ref(), |value, comp| value?.scope()?.get(comp));
|
||||
let Some(scope) = value.and_then(|v| v.scope()) else {
|
||||
return;
|
||||
};
|
||||
let Some(scope) = value.scope() else { return };
|
||||
|
||||
// Check imported items in the scope
|
||||
let seen = existing
|
||||
.iter()
|
||||
.flat_map(|item| {
|
||||
let item_comps = item.path().iter().collect::<Vec<_>>();
|
||||
if item_comps.len() == comps.len() + 1
|
||||
&& item_comps
|
||||
.iter()
|
||||
.zip(comps.as_slice())
|
||||
.all(|(l, r)| l.as_str() == r.as_str())
|
||||
{
|
||||
// item_comps.len() >= 1
|
||||
item_comps.last().cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if existing.iter().next().is_none() {
|
||||
ctx.snippet_completion("*", "*", "Import everything.");
|
||||
}
|
||||
|
||||
for (name, value, _) in scope.iter() {
|
||||
if existing
|
||||
.iter()
|
||||
.all(|item| item.original_name().as_str() != name)
|
||||
{
|
||||
if seen.iter().all(|item| item.as_str() != name) {
|
||||
ctx.value_completion(Some(name.clone()), value, false, None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -424,6 +424,7 @@ impl CompletionContext<'_> {
|
|||
let filter = |c: &CompletionKindChecker| {
|
||||
match surrounding_syntax {
|
||||
SurroundingSyntax::Regular => true,
|
||||
SurroundingSyntax::ImportList => false,
|
||||
SurroundingSyntax::Selector => 'selector: {
|
||||
for func in &c.functions {
|
||||
if func.element().is_some() {
|
||||
|
@ -564,6 +565,7 @@ pub(crate) enum SurroundingSyntax {
|
|||
Regular,
|
||||
Selector,
|
||||
ShowTransform,
|
||||
ImportList,
|
||||
SetRule,
|
||||
}
|
||||
|
||||
|
@ -580,6 +582,23 @@ fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax>
|
|||
SyntaxKind::CodeBlock | SyntaxKind::ContentBlock | SyntaxKind::Equation => {
|
||||
return Some(Regular);
|
||||
}
|
||||
SyntaxKind::ImportItemPath
|
||||
| SyntaxKind::ImportItems
|
||||
| SyntaxKind::RenamedImportItem => {
|
||||
return Some(ImportList);
|
||||
}
|
||||
SyntaxKind::ModuleImport => {
|
||||
let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
|
||||
let Some(colon) = colon else {
|
||||
return Some(Regular);
|
||||
};
|
||||
|
||||
if leaf.offset() >= colon.offset() {
|
||||
return Some(ImportList);
|
||||
} else {
|
||||
return Some(Regular);
|
||||
}
|
||||
}
|
||||
SyntaxKind::Named => {
|
||||
return Some(Regular);
|
||||
}
|
||||
|
@ -1332,6 +1351,7 @@ fn type_completion(
|
|||
/// Complete call and set rule parameters.
|
||||
pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
|
||||
use crate::syntax::get_check_target;
|
||||
use SurroundingSyntax::*;
|
||||
|
||||
let check_target = get_check_target(ctx.leaf.clone());
|
||||
crate::log_debug_ct!("complete_type: pos {:?} -> {check_target:#?}", ctx.leaf);
|
||||
|
@ -1379,7 +1399,7 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
|
|||
let scope = ctx.surrounding_syntax();
|
||||
|
||||
crate::log_debug_ct!("complete_type: {:?} -> ({scope:?}, {ty:#?})", ctx.leaf);
|
||||
if matches!((scope, &ty), (SurroundingSyntax::Regular, None)) {
|
||||
if matches!((scope, &ty), (Regular, None)) || matches!(scope, ImportList) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -1407,6 +1427,7 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
|
|||
|
||||
match scope {
|
||||
SurroundingSyntax::Regular => {}
|
||||
SurroundingSyntax::ImportList => {}
|
||||
SurroundingSyntax::Selector => {
|
||||
ctx.snippet_completion(
|
||||
"text selector",
|
||||
|
@ -1507,6 +1528,7 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
|
|||
|
||||
match scope {
|
||||
SurroundingSyntax::Regular => {}
|
||||
SurroundingSyntax::ImportList => {}
|
||||
SurroundingSyntax::Selector => {
|
||||
ctx.enrich("", ": ${}");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue