dev: correctly handle module import completion (#234)

This commit is contained in:
Myriad-Dreamin 2024-05-05 17:59:26 +08:00 committed by GitHub
parent abb89ed3e8
commit c133d81d36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 535 additions and 22 deletions

View file

@ -0,0 +1,10 @@
// path: base.typ
#let aa() = 1;
#let aab() = 1;
#let aac() = 1;
#let aabc() = 1;
-----
// contains: base,aa,aab,aac,aabc,aa.with,aa.where
#import "base.typ": aab, aac
#aac(/* range -2..0 */);

View file

@ -0,0 +1,10 @@
// path: base.typ
#let aa() = 1;
#let aab() = 1;
#let aac() = 1;
#let aabc() = 1;
-----
// contains: base,aa,aab,aac,aabc,aa.with,aa.where
#import "base.typ"
#bac(/* range -2..0 */);

View file

@ -0,0 +1,10 @@
// path: base.typ
#let aa() = 1;
#let aab() = 1;
#let aac() = 1;
#let aabc() = 1;
-----
// contains: base,aa,aab,aac,aabc,aa.with,aa.where
#import "base.typ": *
#bac(/* range -2..0 */);

View file

@ -0,0 +1,10 @@
// path: base.typ
#let aa() = 1;
#let aab() = 1;
#let aac() = 1;
#let aabc() = 1;
-----
// contains: base,baz,aa,aab,aac,aabc,aa.with,aa.where
#import "base.typ" as baz
#bac(/* range -2..0 */);

View file

@ -0,0 +1,10 @@
// path: base.typ
#let aa() = 1;
#let aab() = 1;
#let aac() = 1;
#let aabc() = 1;
-----
// contains: base,baz,aa,aab,aac,aabc,aa.with,aa.where
#import "base.typ" as baz: *
#bac(/* range -2..0 */);

View file

@ -0,0 +1,10 @@
// path: base.typ
#let aa() = 1;
#let aab() = 1;
#let aac() = 1;
#let aabc() = 1;
-----
// contains: base,aa,aab,aac,aabc,aa.with,aa.where
#import "base.typ": *
#aac(/* range -2..0 */);

View file

@ -0,0 +1,69 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (83..85)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "aab",
"textEdit": {
"newText": "aab(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aac",
"textEdit": {
"newText": "aac(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
},
{
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "aac",
"textEdit": {
"newText": "aac(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (73..75)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_self.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 9,
"label": "base",
"textEdit": {
"newText": "base",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
},
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,16 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (76..78)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_self2.typ
---
[
{
"isIncomplete": false,
"items": []
},
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (84..86)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_self3.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 9,
"label": "baz",
"textEdit": {
"newText": "baz",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
},
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (87..89)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_self4.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 9,
"label": "baz",
"textEdit": {
"newText": "baz",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
},
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,154 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on c( (76..78)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "aa",
"textEdit": {
"newText": "aa(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aa.where",
"textEdit": {
"newText": "aa.where(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aa.with",
"textEdit": {
"newText": "aa.with(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aab",
"textEdit": {
"newText": "aab(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aabc",
"textEdit": {
"newText": "aabc(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aac",
"textEdit": {
"newText": "aac(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
},
{
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "aabc",
"textEdit": {
"newText": "aabc(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
},
{
"kind": 3,
"label": "aac",
"textEdit": {
"newText": "aac(${1:})",
"range": {
"end": {
"character": 4,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
}
]

View file

@ -47,7 +47,13 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
/// Filters the global/math scope with the given filter.
pub fn scope_completions_(&mut self, parens: bool, filter: impl Fn(Option<&Value>) -> bool) {
let mut defined = BTreeMap::new();
let mut try_insert = |name: EcoString, kind: CompletionKind| {
enum DefKind {
Syntax(Span),
Instance(Spanned<Value>),
}
let mut try_insert = |name: EcoString, kind: (CompletionKind, DefKind)| {
if name.is_empty() {
return;
}
@ -57,6 +63,12 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
}
};
let types = (|| {
let id = self.root.span().id()?;
let src = self.ctx.source_by_id(id).ok()?;
self.ctx.type_check(src)
})();
let mut ancestor = Some(self.leaf.clone());
while let Some(node) = &ancestor {
let mut sibling = Some(node.clone());
@ -67,7 +79,10 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
ast::LetBindingKind::Normal(..) => CompletionKind::Variable,
};
for ident in v.kind().bindings() {
try_insert(ident.get().clone(), kind.clone());
try_insert(
ident.get().clone(),
(kind.clone(), DefKind::Syntax(ident.span())),
);
}
}
@ -81,22 +96,107 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
if analyzed.is_none() {
log::debug!("failed to analyze import: {:?}", anaylyze);
}
if let Some(value) = analyzed {
if imports.is_none() {
if let Some(name) = value.name() {
try_insert(name.into(), CompletionKind::Module);
// import it self
if imports.is_none() || v.new_name().is_some() {
// todo: name of import syntactically
let name = (|| {
if let Some(new_name) = v.new_name() {
return Some(new_name.get().clone());
}
} else if let Some(scope) = value.scope() {
for (name, v) in scope.iter() {
let kind = match v {
Value::Func(..) => CompletionKind::Func,
Value::Module(..) => CompletionKind::Module,
Value::Type(..) => CompletionKind::Type,
_ => CompletionKind::Constant,
};
try_insert(name.clone(), kind);
if let Some(module_ins) = &analyzed {
return module_ins.name().map(From::from);
}
// todo: name of import syntactically
None
})();
let def_kind = analyzed.clone().map(|module_ins| {
DefKind::Instance(Spanned::new(
module_ins,
v.new_name()
.map(|n| n.span())
.unwrap_or_else(Span::detached),
))
});
if let Some((name, def_kind)) = name.zip(def_kind) {
try_insert(name, (CompletionKind::Module, def_kind));
}
}
// import items
match (imports, analyzed) {
(Some(..), None) => {
// todo: name of import syntactically
}
(Some(e), Some(module_ins)) => {
let import_filter = match e {
ast::Imports::Wildcard => None,
ast::Imports::Items(e) => {
let mut filter = HashMap::new();
for item in e.iter() {
match item {
ast::ImportItem::Simple(n) => {
filter.insert(
n.get().clone(),
DefKind::Syntax(n.span()),
);
}
ast::ImportItem::Renamed(n) => {
filter.insert(
n.new_name().get().clone(),
DefKind::Syntax(n.span()),
);
}
}
}
Some(filter)
}
};
if let Some(scope) = module_ins.scope() {
for (name, v) in scope.iter() {
let kind = match v {
Value::Func(..) => CompletionKind::Func,
Value::Module(..) => CompletionKind::Module,
Value::Type(..) => CompletionKind::Type,
_ => CompletionKind::Constant,
};
let def_kind = match &import_filter {
Some(import_filter) => {
let w = import_filter.get(name);
match w {
Some(DefKind::Syntax(span)) => {
Some(DefKind::Instance(Spanned::new(
v.clone(),
*span,
)))
}
Some(DefKind::Instance(v)) => {
Some(DefKind::Instance(v.clone()))
}
None => None,
}
}
None => Some(DefKind::Instance(Spanned::new(
v.clone(),
Span::detached(),
))),
};
if let Some(def_kind) = def_kind {
try_insert(name.clone(), (kind, def_kind));
}
}
} else if let Some(filter) = import_filter {
for (name, def_kind) in filter {
try_insert(name, (CompletionKind::Variable, def_kind));
}
}
}
_ => {}
}
}
@ -108,7 +208,10 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
if node.prev_sibling_kind() != Some(SyntaxKind::In) {
let pattern = v.pattern();
for ident in pattern.bindings() {
try_insert(ident.get().clone(), CompletionKind::Variable);
try_insert(
ident.get().clone(),
(CompletionKind::Variable, DefKind::Syntax(ident.span())),
);
}
}
}
@ -117,15 +220,22 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
match param {
ast::Param::Pos(pattern) => {
for ident in pattern.bindings() {
try_insert(ident.get().clone(), CompletionKind::Variable);
try_insert(
ident.get().clone(),
(CompletionKind::Variable, DefKind::Syntax(ident.span())),
);
}
}
ast::Param::Named(n) => {
try_insert(n.name().get().clone(), CompletionKind::Variable)
}
ast::Param::Named(n) => try_insert(
n.name().get().clone(),
(CompletionKind::Variable, DefKind::Syntax(n.name().span())),
),
ast::Param::Spread(s) => {
if let Some(sink_ident) = s.sink_ident() {
try_insert(sink_ident.get().clone(), CompletionKind::Variable)
try_insert(
sink_ident.get().clone(),
(CompletionKind::Variable, DefKind::Syntax(s.span())),
)
}
}
}
@ -157,8 +267,10 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
}
}
for (name, kind) in defined {
for (name, (kind, def_kind)) in defined {
if filter(None) && !name.is_empty() {
let _ = types;
let _ = def_kind;
if kind == CompletionKind::Func {
let apply = eco_format!("{}.with(${{}})", name);
self.completions.push(Completion {