dev: move field_access_completions (#1041)

This commit is contained in:
Myriad-Dreamin 2024-12-21 13:51:09 +08:00 committed by GitHub
parent a4de68a1ca
commit a814c7a63d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 115 additions and 99 deletions

View file

@ -242,9 +242,9 @@ impl Ty {
self.satisfy(ctx, |ty: &Ty, _pol| { self.satisfy(ctx, |ty: &Ty, _pol| {
res = res || { res = res || {
match ty { match ty {
Ty::Value(v) => matches!(v.val, Value::Content(..)), Ty::Value(v) => is_content_builtin_type(&v.val.ty()),
Ty::Builtin(BuiltinTy::Content | BuiltinTy::Element(..)) => true, Ty::Builtin(BuiltinTy::Content | BuiltinTy::Element(..)) => true,
Ty::Builtin(BuiltinTy::Type(v)) => *v == Type::of::<Content>(), Ty::Builtin(BuiltinTy::Type(v)) => is_content_builtin_type(v),
_ => false, _ => false,
} }
} }
@ -253,6 +253,10 @@ impl Ty {
} }
} }
fn is_content_builtin_type(ty: &Type) -> bool {
*ty == Type::of::<Content>() || *ty == Type::of::<typst::symbols::Symbol>()
}
/// A function parameter type /// A function parameter type
pub enum TypeSigParam<'a> { pub enum TypeSigParam<'a> {
/// A positional parameter /// A positional parameter

View file

@ -6,7 +6,7 @@ use ecow::{eco_format, EcoString};
use if_chain::if_chain; use if_chain::if_chain;
use lsp_types::TextEdit; use lsp_types::TextEdit;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typst::foundations::{fields_on, format_str, repr, Repr, StyleChain, Styles, Value}; use typst::foundations::{format_str, repr, Repr, Value};
use typst::model::Document; use typst::model::Document;
use typst::syntax::ast::{AstNode, Param}; use typst::syntax::ast::{AstNode, Param};
use typst::syntax::{ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind}; use typst::syntax::{ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind};
@ -297,92 +297,6 @@ fn complete_math(ctx: &mut CompletionContext) -> bool {
false false
} }
/// Add completions for all fields on a value.
fn field_access_completions(
ctx: &mut CompletionContext,
node: &LinkedNode,
value: &Value,
styles: &Option<Styles>,
) {
for (name, value, _) in value.ty().scope().iter() {
ctx.value_completion(Some(name.clone()), value, true, None);
}
if let Some(scope) = value.scope() {
for (name, value, _) in scope.iter() {
ctx.value_completion(Some(name.clone()), value, true, None);
}
}
for &field in fields_on(value.ty()) {
// Complete the field name along with its value. Notes:
// 1. No parentheses since function fields cannot currently be called
// with method syntax;
// 2. We can unwrap the field's value since it's a field belonging to
// this value's type, so accessing it should not fail.
ctx.value_completion(
Some(field.into()),
&value.field(field).unwrap(),
false,
None,
);
}
ctx.postfix_completions(node, value);
match value {
Value::Symbol(symbol) => {
for modifier in symbol.modifiers() {
if let Ok(modified) = symbol.clone().modified(modifier) {
ctx.completions.push(Completion {
kind: CompletionKind::Symbol(modified.get()),
label: modifier.into(),
label_detail: Some(symbol_label_detail(modified.get())),
..Completion::default()
});
}
}
ctx.ufcs_completions(node, value);
}
Value::Content(content) => {
for (name, value) in content.fields() {
ctx.value_completion(Some(name.into()), &value, false, None);
}
ctx.ufcs_completions(node, value);
}
Value::Dict(dict) => {
for (name, value) in dict.iter() {
ctx.value_completion(Some(name.clone().into()), value, false, None);
}
}
Value::Func(func) => {
// Autocomplete get rules.
if let Some((elem, styles)) = func.element().zip(styles.as_ref()) {
for param in elem.params().iter().filter(|param| !param.required) {
if let Some(value) = elem
.field_id(param.name)
.map(|id| elem.field_from_styles(id, StyleChain::new(styles)))
{
ctx.value_completion(Some(param.name.into()), &value.unwrap(), false, None);
}
}
}
}
Value::Plugin(plugin) => {
for name in plugin.iter() {
ctx.completions.push(Completion {
kind: CompletionKind::Func,
label: name.clone(),
..Completion::default()
})
}
}
_ => {}
}
}
/// Complete imports. /// Complete imports.
fn complete_imports(ctx: &mut CompletionContext) -> bool { fn complete_imports(ctx: &mut CompletionContext) -> bool {
// On the colon marker of an import list: // On the colon marker of an import list:

View file

@ -9,7 +9,9 @@ use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tinymist_derive::BindTyCtx; use tinymist_derive::BindTyCtx;
use tinymist_world::LspWorld; use tinymist_world::LspWorld;
use typst::foundations::{AutoValue, Content, Func, Label, NoneValue, Scope, Type, Value}; use typst::foundations::{
fields_on, AutoValue, Func, Label, NoneValue, Scope, StyleChain, Styles, Type, Value,
};
use typst::syntax::ast::AstNode; use typst::syntax::ast::AstNode;
use typst::syntax::{ast, SyntaxKind, SyntaxNode}; use typst::syntax::{ast, SyntaxKind, SyntaxNode};
use typst::visualize::Color; use typst::visualize::Color;
@ -23,8 +25,7 @@ use crate::syntax::{
PreviousDecl, SurroundingSyntax, SyntaxContext, VarClass, PreviousDecl, SurroundingSyntax, SyntaxContext, VarClass,
}; };
use crate::ty::{DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeInfo, TypeVar}; use crate::ty::{DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeInfo, TypeVar};
use crate::upstream::complete::{complete_code, field_access_completions}; use crate::upstream::complete::complete_code;
use crate::{completion_kind, prelude::*, LspCompletion}; use crate::{completion_kind, prelude::*, LspCompletion};
/// Tinymist's completion features. /// Tinymist's completion features.
@ -139,7 +140,7 @@ impl CompletionContext<'_> {
Some((src, defines)) Some((src, defines))
} }
pub fn postfix_completions(&mut self, node: &LinkedNode, value: &Value) -> Option<()> { pub fn postfix_completions(&mut self, node: &LinkedNode, ty: Ty) -> Option<()> {
if !self.ctx.analysis.completion_feat.postfix() { if !self.ctx.analysis.completion_feat.postfix() {
return None; return None;
} }
@ -153,8 +154,7 @@ impl CompletionContext<'_> {
} }
let cursor_mode = interpret_mode_at(Some(node)); let cursor_mode = interpret_mode_at(Some(node));
let is_content = value.ty() == Type::of::<Content>() let is_content = ty.is_content(&());
|| value.ty() == Type::of::<typst::symbols::Symbol>();
crate::log_debug_ct!("post snippet is_content: {is_content}"); crate::log_debug_ct!("post snippet is_content: {is_content}");
let rng = node.range(); let rng = node.range();
@ -254,12 +254,13 @@ impl CompletionContext<'_> {
Some(()) Some(())
} }
pub fn ufcs_completions(&mut self, node: &LinkedNode, value: &Value) { /// Make ufcs-style completions. Note: you must check that node is a content
/// before calling this. Todo: ufcs completions for other types.
pub fn ufcs_completions(&mut self, node: &LinkedNode) {
if !self.ctx.analysis.completion_feat.any_ufcs() { if !self.ctx.analysis.completion_feat.any_ufcs() {
return; return;
} }
let _ = value;
let surrounding_syntax = self.surrounding_syntax(); let surrounding_syntax = self.surrounding_syntax();
if !matches!(surrounding_syntax, SurroundingSyntax::Regular) { if !matches!(surrounding_syntax, SurroundingSyntax::Regular) {
return; return;
@ -523,6 +524,104 @@ impl CompletionContext<'_> {
}); });
} }
} }
/// Add completions for all fields on a node.
fn field_access_completions(&mut self, target: &LinkedNode) -> Option<()> {
let (value, styles) = self.ctx.analyze_expr(target).into_iter().next()?;
self.value_field_access_completions(target, &value, &styles);
Some(())
}
/// Add completions for all fields on a value.
fn value_field_access_completions(
&mut self,
node: &LinkedNode,
value: &Value,
styles: &Option<Styles>,
) {
for (name, value, _) in value.ty().scope().iter() {
self.value_completion(Some(name.clone()), value, true, None);
}
if let Some(scope) = value.scope() {
for (name, value, _) in scope.iter() {
self.value_completion(Some(name.clone()), value, true, None);
}
}
for &field in fields_on(value.ty()) {
// Complete the field name along with its value. Notes:
// 1. No parentheses since function fields cannot currently be called
// with method syntax;
// 2. We can unwrap the field's value since it's a field belonging to
// this value's type, so accessing it should not fail.
self.value_completion(
Some(field.into()),
&value.field(field).unwrap(),
false,
None,
);
}
self.postfix_completions(node, Ty::Value(InsTy::new(value.clone())));
match value {
Value::Symbol(symbol) => {
for modifier in symbol.modifiers() {
if let Ok(modified) = symbol.clone().modified(modifier) {
self.completions.push(Completion {
kind: CompletionKind::Symbol(modified.get()),
label: modifier.into(),
label_detail: Some(symbol_label_detail(modified.get())),
..Completion::default()
});
}
}
self.ufcs_completions(node);
}
Value::Content(content) => {
for (name, value) in content.fields() {
self.value_completion(Some(name.into()), &value, false, None);
}
self.ufcs_completions(node);
}
Value::Dict(dict) => {
for (name, value) in dict.iter() {
self.value_completion(Some(name.clone().into()), value, false, None);
}
}
Value::Func(func) => {
// Autocomplete get rules.
if let Some((elem, styles)) = func.element().zip(styles.as_ref()) {
for param in elem.params().iter().filter(|param| !param.required) {
if let Some(value) = elem
.field_id(param.name)
.map(|id| elem.field_from_styles(id, StyleChain::new(styles)))
{
self.value_completion(
Some(param.name.into()),
&value.unwrap(),
false,
None,
);
}
}
}
}
Value::Plugin(plugin) => {
for name in plugin.iter() {
self.completions.push(Completion {
kind: CompletionKind::Func,
label: name.clone(),
..Completion::default()
})
}
}
_ => {}
}
}
} }
#[derive(BindTyCtx)] #[derive(BindTyCtx)]
@ -1301,8 +1400,7 @@ pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()
let offset = field.offset(&ctx.ctx.source_by_id(target.span().id()?).ok()?)?; let offset = field.offset(&ctx.ctx.source_by_id(target.span().id()?).ok()?)?;
ctx.from = offset; ctx.from = offset;
let (value, styles) = ctx.ctx.analyze_expr(&target).into_iter().next()?; ctx.field_access_completions(&target);
field_access_completions(ctx, &target, &value, &styles);
return Some(()); return Some(());
} }
Some(SyntaxContext::ImportPath(path) | SyntaxContext::IncludePath(path)) => { Some(SyntaxContext::ImportPath(path) | SyntaxContext::IncludePath(path)) => {