mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-25 13:23:44 +00:00
dev: move field_access_completions (#1041)
This commit is contained in:
parent
a4de68a1ca
commit
a814c7a63d
3 changed files with 115 additions and 99 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue