mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: provide label details by type, symbol, and labels (#237)
* feat: label details by type * fix: symbol's details and label details * dev: update snapshot * fix: make signature stable
This commit is contained in:
parent
3b93643091
commit
68bcc2b571
18 changed files with 493 additions and 46 deletions
|
@ -15,7 +15,7 @@ use typst::{
|
|||
util::LazyHash,
|
||||
};
|
||||
|
||||
use crate::analysis::resolve_callee;
|
||||
use crate::analysis::{resolve_callee, FlowSignature};
|
||||
use crate::syntax::{get_def_target, get_deref_target, DefTarget};
|
||||
use crate::AnalysisContext;
|
||||
|
||||
|
@ -111,6 +111,8 @@ pub struct PrimarySignature {
|
|||
pub rest: Option<Arc<ParamSpec>>,
|
||||
/// The return type.
|
||||
pub(crate) ret_ty: Option<FlowType>,
|
||||
/// The signature type.
|
||||
pub(crate) sig_ty: Option<FlowType>,
|
||||
_broken: bool,
|
||||
}
|
||||
|
||||
|
@ -367,12 +369,33 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc<PrimarySignature> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut named_vec: Vec<(EcoString, FlowType)> = named
|
||||
.iter()
|
||||
.map(|e| {
|
||||
(
|
||||
e.0.as_ref().into(),
|
||||
e.1.infer_type.clone().unwrap_or(FlowType::Any),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
named_vec.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
|
||||
let sig_ty = FlowSignature::new(
|
||||
pos.iter()
|
||||
.map(|e| e.infer_type.clone().unwrap_or(FlowType::Any)),
|
||||
named_vec.into_iter(),
|
||||
rest.as_ref()
|
||||
.map(|e| e.infer_type.clone().unwrap_or(FlowType::Any)),
|
||||
ret_ty.clone(),
|
||||
);
|
||||
Arc::new(PrimarySignature {
|
||||
pos,
|
||||
named,
|
||||
rest,
|
||||
ret_ty,
|
||||
has_fill_or_size_or_stroke: has_fill || has_stroke || has_size,
|
||||
sig_ty: Some(FlowType::Func(Box::new(sig_ty))),
|
||||
_broken: broken,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use once_cell::sync::Lazy;
|
|||
use parking_lot::{Mutex, RwLock};
|
||||
use reflexo::{hash::hash128, vector::ir::DefId};
|
||||
use typst::{
|
||||
foundations::{Func, Value},
|
||||
foundations::{Func, Repr, Value},
|
||||
syntax::{
|
||||
ast::{self, AstNode},
|
||||
LinkedNode, Source, Span, SyntaxKind,
|
||||
|
@ -86,6 +86,11 @@ impl TypeCheckInfo {
|
|||
worker.simplify(ty, principal)
|
||||
}
|
||||
|
||||
pub fn describe(&self, ty: &FlowType) -> Option<String> {
|
||||
let mut worker = TypeDescriber::default();
|
||||
worker.describe_root(ty)
|
||||
}
|
||||
|
||||
// todo: distinguish at least, at most
|
||||
pub fn witness_at_least(&mut self, site: Span, ty: FlowType) {
|
||||
Self::witness_(site, ty, &mut self.mapping);
|
||||
|
@ -1260,6 +1265,167 @@ struct TypeCanoStore {
|
|||
positives: HashSet<DefId>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TypeDescriber {
|
||||
described: HashSet<u128>,
|
||||
results: HashSet<String>,
|
||||
functions: Vec<FlowSignature>,
|
||||
}
|
||||
|
||||
impl TypeDescriber {
|
||||
fn describe_root(&mut self, ty: &FlowType) -> Option<String> {
|
||||
// recursive structure
|
||||
if self.described.contains(&hash128(ty)) {
|
||||
return Some("$self".to_string());
|
||||
}
|
||||
|
||||
let res = self.describe(ty);
|
||||
if !res.is_empty() {
|
||||
return Some(res);
|
||||
}
|
||||
self.described.insert(hash128(ty));
|
||||
|
||||
let mut results = std::mem::take(&mut self.results)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let functions = std::mem::take(&mut self.functions);
|
||||
if !functions.is_empty() {
|
||||
// todo: union signature
|
||||
// only first function is described
|
||||
let f = functions[0].clone();
|
||||
|
||||
let mut res = String::new();
|
||||
res.push('(');
|
||||
let mut not_first = false;
|
||||
for ty in f.pos.iter() {
|
||||
if not_first {
|
||||
res.push_str(", ");
|
||||
} else {
|
||||
not_first = true;
|
||||
}
|
||||
res.push_str(self.describe_root(ty).as_deref().unwrap_or("any"));
|
||||
}
|
||||
for (k, ty) in f.named.iter() {
|
||||
if not_first {
|
||||
res.push_str(", ");
|
||||
} else {
|
||||
not_first = true;
|
||||
}
|
||||
res.push_str(k);
|
||||
res.push_str(": ");
|
||||
res.push_str(self.describe_root(ty).as_deref().unwrap_or("any"));
|
||||
}
|
||||
if let Some(r) = &f.rest {
|
||||
if not_first {
|
||||
res.push_str(", ");
|
||||
}
|
||||
res.push_str("..: ");
|
||||
res.push_str(self.describe_root(r).as_deref().unwrap_or(""));
|
||||
res.push_str("[]");
|
||||
}
|
||||
res.push_str(") => ");
|
||||
res.push_str(self.describe_root(&f.ret).as_deref().unwrap_or("any"));
|
||||
results.push(res);
|
||||
}
|
||||
|
||||
if results.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
results.sort();
|
||||
results.dedup();
|
||||
Some(results.join(" | "))
|
||||
}
|
||||
|
||||
fn describe_iter(&mut self, ty: &[FlowType]) {
|
||||
for ty in ty.iter() {
|
||||
let desc = self.describe(ty);
|
||||
self.results.insert(desc);
|
||||
}
|
||||
}
|
||||
|
||||
fn describe(&mut self, ty: &FlowType) -> String {
|
||||
match ty {
|
||||
FlowType::Var(..) => {}
|
||||
FlowType::Union(tys) => {
|
||||
self.describe_iter(tys);
|
||||
}
|
||||
FlowType::Let(lb) => {
|
||||
self.describe_iter(&lb.lbs);
|
||||
self.describe_iter(&lb.ubs);
|
||||
}
|
||||
FlowType::Func(f) => {
|
||||
self.functions.push(*f.clone());
|
||||
}
|
||||
FlowType::Dict(..) => {
|
||||
return "dict".to_string();
|
||||
}
|
||||
FlowType::Tuple(..) => {
|
||||
return "array".to_string();
|
||||
}
|
||||
FlowType::Array(..) => {
|
||||
return "array".to_string();
|
||||
}
|
||||
FlowType::With(w) => {
|
||||
return self.describe(&w.0);
|
||||
}
|
||||
FlowType::Clause => {}
|
||||
FlowType::Undef => {}
|
||||
FlowType::Content => {
|
||||
return "content".to_string();
|
||||
}
|
||||
FlowType::Any => {
|
||||
return "any".to_string();
|
||||
}
|
||||
FlowType::Space => {}
|
||||
FlowType::None => {
|
||||
return "none".to_string();
|
||||
}
|
||||
FlowType::Infer => {}
|
||||
FlowType::FlowNone => {
|
||||
return "none".to_string();
|
||||
}
|
||||
FlowType::Auto => {
|
||||
return "auto".to_string();
|
||||
}
|
||||
FlowType::Boolean(None) => {
|
||||
return "boolean".to_string();
|
||||
}
|
||||
FlowType::Boolean(Some(b)) => {
|
||||
return b.to_string();
|
||||
}
|
||||
FlowType::Builtin(b) => {
|
||||
return b.describe().to_string();
|
||||
}
|
||||
FlowType::Value(v) => return v.0.repr().to_string(),
|
||||
FlowType::ValueDoc(v) => return v.0.repr().to_string(),
|
||||
FlowType::Field(..) => {
|
||||
return "field".to_string();
|
||||
}
|
||||
FlowType::Element(..) => {
|
||||
return "element".to_string();
|
||||
}
|
||||
FlowType::Args(..) => {
|
||||
return "args".to_string();
|
||||
}
|
||||
FlowType::At(..) => {
|
||||
return "any".to_string();
|
||||
}
|
||||
FlowType::Unary(..) => {
|
||||
return "any".to_string();
|
||||
}
|
||||
FlowType::Binary(..) => {
|
||||
return "any".to_string();
|
||||
}
|
||||
FlowType::If(..) => {
|
||||
return "any".to_string();
|
||||
}
|
||||
}
|
||||
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
struct TypeSimplifier<'a, 'b> {
|
||||
principal: bool,
|
||||
|
||||
|
|
|
@ -102,6 +102,40 @@ pub(crate) enum FlowBuiltinType {
|
|||
|
||||
Path(PathPreference),
|
||||
}
|
||||
impl FlowBuiltinType {
|
||||
pub(crate) fn describe(&self) -> &'static str {
|
||||
match self {
|
||||
FlowBuiltinType::Args => "args",
|
||||
FlowBuiltinType::Color => "color",
|
||||
FlowBuiltinType::TextSize => "text.size",
|
||||
FlowBuiltinType::TextFont => "text.font",
|
||||
FlowBuiltinType::TextLang => "text.lang",
|
||||
FlowBuiltinType::TextRegion => "text.region",
|
||||
FlowBuiltinType::Dir => "dir",
|
||||
FlowBuiltinType::Length => "length",
|
||||
FlowBuiltinType::Float => "float",
|
||||
FlowBuiltinType::Stroke => "stroke",
|
||||
FlowBuiltinType::Margin => "margin",
|
||||
FlowBuiltinType::Inset => "inset",
|
||||
FlowBuiltinType::Outset => "outset",
|
||||
FlowBuiltinType::Radius => "radius",
|
||||
FlowBuiltinType::Path(s) => match s {
|
||||
PathPreference::None => "[any]",
|
||||
PathPreference::Special => "[any]",
|
||||
PathPreference::Source => "[source]",
|
||||
PathPreference::Csv => "[csv]",
|
||||
PathPreference::Image => "[image]",
|
||||
PathPreference::Json => "[json]",
|
||||
PathPreference::Yaml => "[yaml]",
|
||||
PathPreference::Xml => "[xml]",
|
||||
PathPreference::Toml => "[toml]",
|
||||
PathPreference::Bibliography => "[bib]",
|
||||
PathPreference::RawTheme => "[theme]",
|
||||
PathPreference::RawSyntax => "[syntax]",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use FlowBuiltinType::*;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use typst::{
|
|||
|
||||
use crate::analysis::ty::param_mapping;
|
||||
|
||||
use super::FlowBuiltinType;
|
||||
use super::{FlowBuiltinType, TypeDescriber};
|
||||
|
||||
struct RefDebug<'a>(&'a FlowType);
|
||||
|
||||
|
@ -171,6 +171,11 @@ impl FlowType {
|
|||
Some(ty)
|
||||
}
|
||||
|
||||
pub fn describe(&self) -> Option<String> {
|
||||
let mut worker = TypeDescriber::default();
|
||||
worker.describe_root(self)
|
||||
}
|
||||
|
||||
pub(crate) fn is_dict(&self) -> bool {
|
||||
matches!(self, FlowType::Dict(..))
|
||||
}
|
||||
|
@ -412,6 +417,20 @@ impl FlowSignature {
|
|||
ret,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
pos: impl Iterator<Item = FlowType>,
|
||||
named: impl Iterator<Item = (EcoString, FlowType)>,
|
||||
rest: Option<FlowType>,
|
||||
ret_ty: Option<FlowType>,
|
||||
) -> Self {
|
||||
FlowSignature {
|
||||
pos: pos.collect(),
|
||||
named: named.collect(),
|
||||
rest,
|
||||
ret: ret_ty.unwrap_or(FlowType::Any),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FlowSignature {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aa",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aa(${1:})",
|
||||
"range": {
|
||||
|
@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aab",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aab(${1:})",
|
||||
"range": {
|
||||
|
@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc(${1:})",
|
||||
"range": {
|
||||
|
@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
@ -84,6 +96,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc(${1:})",
|
||||
"range": {
|
||||
|
@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
|
|
@ -65,6 +65,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ
|
|||
{
|
||||
"kind": 7,
|
||||
"label": "content",
|
||||
"labelDetails": {
|
||||
"description": "type"
|
||||
},
|
||||
"sortText": "050",
|
||||
"textEdit": {
|
||||
"newText": "content",
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aa",
|
||||
"labelDetails": {
|
||||
"description": "(any, any, any) => none"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aa(${1:})",
|
||||
"range": {
|
||||
|
@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aab",
|
||||
"labelDetails": {
|
||||
"description": "any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aab",
|
||||
"range": {
|
||||
|
@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc",
|
||||
"range": {
|
||||
|
@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac",
|
||||
"range": {
|
||||
|
@ -84,6 +96,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc",
|
||||
"range": {
|
||||
|
@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac",
|
||||
"range": {
|
||||
|
|
|
@ -65,6 +65,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ
|
|||
{
|
||||
"kind": 7,
|
||||
"label": "content",
|
||||
"labelDetails": {
|
||||
"description": "type"
|
||||
},
|
||||
"sortText": "050",
|
||||
"textEdit": {
|
||||
"newText": "content",
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aab",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aab(${1:})",
|
||||
"range": {
|
||||
|
@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
@ -50,6 +56,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self.typ
|
|||
{
|
||||
"kind": 9,
|
||||
"label": "base",
|
||||
"labelDetails": {
|
||||
"description": "base.typ"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "base",
|
||||
"range": {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self3.typ
|
|||
{
|
||||
"kind": 9,
|
||||
"label": "baz",
|
||||
"labelDetails": {
|
||||
"description": "base.typ"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "baz",
|
||||
"range": {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self4.typ
|
|||
{
|
||||
"kind": 9,
|
||||
"label": "baz",
|
||||
"labelDetails": {
|
||||
"description": "base.typ"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "baz",
|
||||
"range": {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aa",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aa(${1:})",
|
||||
"range": {
|
||||
|
@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aa.with",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aa.with(${1:})",
|
||||
"range": {
|
||||
|
@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aab",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aab(${1:})",
|
||||
"range": {
|
||||
|
@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc(${1:})",
|
||||
"range": {
|
||||
|
@ -79,6 +91,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc(${1:})",
|
||||
"range": {
|
||||
|
@ -118,6 +136,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => any"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aa",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aa(${1:})",
|
||||
"range": {
|
||||
|
@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
@ -50,6 +56,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
|
|
@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aa",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aa(${1:})",
|
||||
"range": {
|
||||
|
@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aab",
|
||||
"labelDetails": {
|
||||
"description": "1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aab",
|
||||
"range": {
|
||||
|
@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc",
|
||||
"range": {
|
||||
|
@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
@ -84,6 +96,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ
|
|||
{
|
||||
"kind": 6,
|
||||
"label": "aabc",
|
||||
"labelDetails": {
|
||||
"description": "1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aabc",
|
||||
"range": {
|
||||
|
@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ
|
|||
{
|
||||
"kind": 3,
|
||||
"label": "aac",
|
||||
"labelDetails": {
|
||||
"description": "() => 1"
|
||||
},
|
||||
"textEdit": {
|
||||
"newText": "aac(${1:})",
|
||||
"range": {
|
||||
|
|
|
@ -479,39 +479,6 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value, styles:
|
|||
}
|
||||
}
|
||||
|
||||
/// If is printable, return the symbol itself.
|
||||
/// Otherwise, return the symbol's unicode description.
|
||||
fn symbol_label_detail(ch: char) -> EcoString {
|
||||
if !ch.is_whitespace() && !ch.is_control() {
|
||||
return ch.into();
|
||||
}
|
||||
match ch {
|
||||
' ' => "space".into(),
|
||||
'\t' => "tab".into(),
|
||||
'\n' => "newline".into(),
|
||||
'\r' => "carriage return".into(),
|
||||
// replacer
|
||||
'\u{200D}' => "zero width joiner".into(),
|
||||
'\u{200C}' => "zero width non-joiner".into(),
|
||||
'\u{200B}' => "zero width space".into(),
|
||||
'\u{2060}' => "word joiner".into(),
|
||||
// spaces
|
||||
'\u{00A0}' => "non-breaking space".into(),
|
||||
'\u{202F}' => "narrow no-break space".into(),
|
||||
'\u{2002}' => "en space".into(),
|
||||
'\u{2003}' => "em space".into(),
|
||||
'\u{2004}' => "three-per-em space".into(),
|
||||
'\u{2005}' => "four-per-em space".into(),
|
||||
'\u{2006}' => "six-per-em space".into(),
|
||||
'\u{2007}' => "figure space".into(),
|
||||
'\u{205f}' => "medium mathematical space".into(),
|
||||
'\u{2008}' => "punctuation space".into(),
|
||||
'\u{2009}' => "thin space".into(),
|
||||
'\u{200A}' => "hair space".into(),
|
||||
_ => format!("\\u{{{:04x}}}", ch as u32).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete half-finished labels.
|
||||
fn complete_open_labels(ctx: &mut CompletionContext) -> bool {
|
||||
// A label anywhere in code: "(<la|".
|
||||
|
@ -1175,12 +1142,33 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
value: &Value,
|
||||
parens: bool,
|
||||
docs: Option<&str>,
|
||||
) {
|
||||
self.value_completion_(
|
||||
label,
|
||||
value,
|
||||
parens,
|
||||
match value {
|
||||
Value::Symbol(s) => Some(symbol_label_detail(s.get())),
|
||||
_ => None,
|
||||
},
|
||||
docs,
|
||||
);
|
||||
}
|
||||
|
||||
/// Add a completion for a specific value.
|
||||
fn value_completion_(
|
||||
&mut self,
|
||||
label: Option<EcoString>,
|
||||
value: &Value,
|
||||
parens: bool,
|
||||
label_detail: Option<EcoString>,
|
||||
docs: Option<&str>,
|
||||
) {
|
||||
let at = label.as_deref().is_some_and(|field| !is_ident(field));
|
||||
let label = label.unwrap_or_else(|| value.repr());
|
||||
|
||||
let detail = docs.map(Into::into).or_else(|| match value {
|
||||
Value::Symbol(_) => None,
|
||||
Value::Symbol(c) => Some(symbol_detail(c.get())),
|
||||
Value::Func(func) => func.docs().map(plain_docs_sentence),
|
||||
Value::Type(ty) => Some(plain_docs_sentence(ty.docs())),
|
||||
v => {
|
||||
|
@ -1216,10 +1204,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
label,
|
||||
apply,
|
||||
detail,
|
||||
label_detail: match value {
|
||||
Value::Symbol(s) => Some(symbol_label_detail(s.get())),
|
||||
_ => None,
|
||||
},
|
||||
label_detail,
|
||||
command,
|
||||
..Completion::default()
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@ use lsp_types::{CompletionItem, CompletionTextEdit, InsertTextFormat, TextEdit};
|
|||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::Mutex;
|
||||
use reflexo::path::{unix_slash, PathClean};
|
||||
use typst::foundations::{AutoValue, Func, Label, NoneValue, Type, Value};
|
||||
use typst::foundations::{AutoValue, Func, Label, NoneValue, Repr, Type, Value};
|
||||
use typst::layout::{Dir, Length};
|
||||
use typst::syntax::ast::AstNode;
|
||||
use typst::syntax::{ast, Span, SyntaxKind};
|
||||
|
@ -69,6 +69,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
let src = self.ctx.source_by_id(id).ok()?;
|
||||
self.ctx.type_check(src)
|
||||
})();
|
||||
let types = types.as_ref();
|
||||
|
||||
let mut ancestor = Some(self.leaf.clone());
|
||||
while let Some(node) = &ancestor {
|
||||
|
@ -279,10 +280,38 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
if !filter(None) || name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let _ = types;
|
||||
let span = match def_kind {
|
||||
DefKind::Syntax(span) => span,
|
||||
DefKind::Instance(span, _) => span,
|
||||
};
|
||||
// we don't check literal type here for faster completion
|
||||
let ty_detail = if let CompletionKind::Symbol(c) = &kind {
|
||||
Some(symbol_label_detail(*c))
|
||||
} else {
|
||||
types
|
||||
.and_then(|types| {
|
||||
let ty = types.mapping.get(&span)?;
|
||||
let ty = types.simplify(ty.clone(), false);
|
||||
types.describe(&ty).map(From::from)
|
||||
})
|
||||
.or_else(|| {
|
||||
if let DefKind::Instance(_, v) = &def_kind {
|
||||
Some(describe_value(self.ctx, v))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
};
|
||||
let detail = if let CompletionKind::Symbol(c) = &kind {
|
||||
Some(symbol_detail(*c))
|
||||
} else {
|
||||
ty_detail.clone()
|
||||
};
|
||||
|
||||
if kind == CompletionKind::Func {
|
||||
let base = Completion {
|
||||
kind: kind.clone(),
|
||||
label_detail: ty_detail,
|
||||
// todo: only vscode and neovim (0.9.1) support this
|
||||
command: Some("editor.action.triggerSuggest"),
|
||||
..Default::default()
|
||||
|
@ -345,12 +374,20 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
SurroundingSyntax::Selector | SurroundingSyntax::SetRule
|
||||
) && !matches!(&v, Value::Func(func) if func.element().is_some());
|
||||
if !bad_instantiate {
|
||||
self.value_completion(Some(name), &v, parens, None);
|
||||
self.value_completion_(
|
||||
Some(name),
|
||||
&v,
|
||||
parens,
|
||||
ty_detail.clone(),
|
||||
detail.as_deref(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.completions.push(Completion {
|
||||
kind,
|
||||
label: name,
|
||||
label_detail: ty_detail.clone(),
|
||||
detail,
|
||||
..Completion::default()
|
||||
});
|
||||
}
|
||||
|
@ -422,6 +459,38 @@ impl<'a, 'w> CompletionContext<'a, 'w> {
|
|||
}
|
||||
}
|
||||
|
||||
fn describe_value(ctx: &mut AnalysisContext, v: &Value) -> EcoString {
|
||||
match v {
|
||||
Value::Func(f) => {
|
||||
let mut f = f;
|
||||
while let typst::foundations::func::Repr::With(with_f) = f.inner() {
|
||||
f = &with_f.0;
|
||||
}
|
||||
|
||||
let sig = analyze_dyn_signature(ctx, f.clone());
|
||||
sig.primary()
|
||||
.sig_ty
|
||||
.as_ref()
|
||||
.and_then(|e| e.describe())
|
||||
.unwrap_or_else(|| "function".into())
|
||||
.into()
|
||||
}
|
||||
Value::Module(m) => {
|
||||
if let Some(fid) = m.file_id() {
|
||||
let package = fid.package();
|
||||
let path = unix_slash(fid.vpath().as_rootless_path());
|
||||
if let Some(package) = package {
|
||||
return eco_format!("{package}:{path}");
|
||||
}
|
||||
return path.into();
|
||||
}
|
||||
|
||||
"module".into()
|
||||
}
|
||||
_ => v.ty().repr(),
|
||||
}
|
||||
}
|
||||
|
||||
fn encolsed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
|
||||
s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
|
||||
}
|
||||
|
@ -1296,6 +1365,49 @@ pub fn complete_path(
|
|||
)
|
||||
}
|
||||
|
||||
/// If is printable, return the symbol itself.
|
||||
/// Otherwise, return the symbol's unicode detailed description.
|
||||
pub fn symbol_detail(ch: char) -> EcoString {
|
||||
let ld = symbol_label_detail(ch);
|
||||
if ld.starts_with("\\u") {
|
||||
return ld;
|
||||
}
|
||||
format!("{}, unicode: `\\u{{{:04x}}}`", ld, ch as u32).into()
|
||||
}
|
||||
|
||||
/// If is printable, return the symbol itself.
|
||||
/// Otherwise, return the symbol's unicode description.
|
||||
pub fn symbol_label_detail(ch: char) -> EcoString {
|
||||
if !ch.is_whitespace() && !ch.is_control() {
|
||||
return ch.into();
|
||||
}
|
||||
match ch {
|
||||
' ' => "space".into(),
|
||||
'\t' => "tab".into(),
|
||||
'\n' => "newline".into(),
|
||||
'\r' => "carriage return".into(),
|
||||
// replacer
|
||||
'\u{200D}' => "zero width joiner".into(),
|
||||
'\u{200C}' => "zero width non-joiner".into(),
|
||||
'\u{200B}' => "zero width space".into(),
|
||||
'\u{2060}' => "word joiner".into(),
|
||||
// spaces
|
||||
'\u{00A0}' => "non-breaking space".into(),
|
||||
'\u{202F}' => "narrow no-break space".into(),
|
||||
'\u{2002}' => "en space".into(),
|
||||
'\u{2003}' => "em space".into(),
|
||||
'\u{2004}' => "three-per-em space".into(),
|
||||
'\u{2005}' => "four-per-em space".into(),
|
||||
'\u{2006}' => "six-per-em space".into(),
|
||||
'\u{2007}' => "figure space".into(),
|
||||
'\u{205f}' => "medium mathematical space".into(),
|
||||
'\u{2008}' => "punctuation space".into(),
|
||||
'\u{2009}' => "thin space".into(),
|
||||
'\u{200A}' => "hair space".into(),
|
||||
_ => format!("\\u{{{:04x}}}", ch as u32).into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue