refactor: complete paths without hacking (#971)

* refactor: complete paths without hacking

* dev: move code after refactor
This commit is contained in:
Myriad-Dreamin 2024-12-10 14:29:24 +08:00 committed by GitHub
parent ab234634a9
commit 969cc6d339
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 165 additions and 181 deletions

View file

@ -333,7 +333,9 @@ impl LocalContext {
/// Get all the source files in the workspace. /// Get all the source files in the workspace.
pub fn source_files(&self) -> &Vec<TypstFileId> { pub fn source_files(&self) -> &Vec<TypstFileId> {
self.caches.root_files.get_or_init(|| { self.caches.root_files.get_or_init(|| {
self.completion_files(&PathPreference::Source) self.completion_files(&PathPreference::Source {
allow_package: false,
})
.copied() .copied()
.collect() .collect()
}) })

View file

@ -305,6 +305,11 @@ impl<'a> PostTypeChecker<'a> {
crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds); crate::log_debug_ct!("post check target iterated: {:?}", resp.bounds);
Some(resp.finalize()) Some(resp.finalize())
} }
CheckTarget::ImportPath(..) | CheckTarget::IncludePath(..) => Some(Ty::Builtin(
crate::ty::BuiltinTy::Path(crate::ty::PathPreference::Source {
allow_package: true,
}),
)),
CheckTarget::Normal(target) => { CheckTarget::Normal(target) => {
let ty = self.check_context_or(&target, context_ty)?; let ty = self.check_context_or(&target, context_ty)?;
crate::log_debug_ct!("post check target normal: {ty:?}"); crate::log_debug_ct!("post check target normal: {ty:?}");

View file

@ -7,10 +7,10 @@ use regex::{Captures, Regex};
use typst_shim::syntax::LinkedNodeExt; use typst_shim::syntax::LinkedNodeExt;
use crate::{ use crate::{
analysis::{BuiltinTy, InsTy, Ty}, analysis::{InsTy, Ty},
prelude::*, prelude::*,
syntax::{is_ident_like, DerefTarget}, syntax::{is_ident_like, DerefTarget},
upstream::{autocomplete, complete_path, CompletionContext}, upstream::{autocomplete, CompletionContext},
StatefulRequest, StatefulRequest,
}; };
@ -104,7 +104,7 @@ impl StatefulRequest for CompletionRequest {
// Skip if an error node starts with number (e.g. `1pt`) // Skip if an error node starts with number (e.g. `1pt`)
if matches!( if matches!(
deref_target, deref_target,
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..) | DerefTarget::Normal(..),) Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..) | DerefTarget::Normal(..))
) { ) {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if node.erroneous() { if node.erroneous() {
@ -122,57 +122,11 @@ impl StatefulRequest for CompletionRequest {
} }
} }
// Do some completion specific to the deref target
let mut ident_like = None;
let mut completion_result = None;
let is_callee = matches!(deref_target, Some(DerefTarget::Callee(..)));
match deref_target {
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..)) => {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if is_ident_like(&node) {
ident_like = Some(node);
}
}
Some(DerefTarget::ImportPath(v) | DerefTarget::IncludePath(v)) => {
if !v.text().starts_with(r#""@"#) {
completion_result = complete_path(
ctx,
Some(v),
&source,
cursor,
&crate::analysis::PathPreference::Source,
);
}
}
Some(DerefTarget::Normal(SyntaxKind::Str, cano_expr)) => {
let parent = cano_expr.parent()?;
if matches!(parent.kind(), SyntaxKind::Named | SyntaxKind::Args) {
let ty_chk = ctx.type_check(&source);
let ty = ty_chk.type_of_span(cano_expr.span());
crate::log_debug_ct!("check string ty: {ty:?}");
if let Some(Ty::Builtin(BuiltinTy::Path(path_filter))) = ty {
completion_result =
complete_path(ctx, Some(cano_expr), &source, cursor, &path_filter);
}
}
}
Some(DerefTarget::Label(..) | DerefTarget::Ref(..) | DerefTarget::Normal(..)) => {}
None => {}
}
let mut completion_items_rest = None; let mut completion_items_rest = None;
let is_incomplete = false; let is_incomplete = false;
let mut items = completion_result.or_else(|| { let mut cc_ctx =
let mut cc_ctx = CompletionContext::new( CompletionContext::new(ctx, doc, &source, cursor, explicit, self.trigger_character)?;
ctx,
doc,
&source,
cursor,
explicit,
self.trigger_character,
)?;
// Exclude it self from auto completion // Exclude it self from auto completion
// e.g. `#let x = (1.);` // e.g. `#let x = (1.);`
@ -194,10 +148,20 @@ impl StatefulRequest for CompletionRequest {
// is_incomplete = ic; // is_incomplete = ic;
let _ = ic; let _ = ic;
let replace_range; // Filter and determine range to replace
if ident_like.as_ref().is_some_and(|i| i.offset() == offset) { let mut from_ident = None;
let ident_like = ident_like.unwrap(); let is_callee = matches!(deref_target, Some(DerefTarget::Callee(..)));
let mut rng = ident_like.range(); if matches!(
deref_target,
Some(DerefTarget::Callee(..) | DerefTarget::VarAccess(..))
) {
let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
if is_ident_like(&node) && node.offset() == offset {
from_ident = Some(node);
}
}
let replace_range = if let Some(from_ident) = from_ident {
let mut rng = from_ident.range();
let ident_prefix = source.text()[rng.start..cursor].to_string(); let ident_prefix = source.text()[rng.start..cursor].to_string();
completions.retain(|c| { completions.retain(|c| {
@ -217,7 +181,7 @@ impl StatefulRequest for CompletionRequest {
}); });
// if modifying some arguments, we need to truncate and add a comma // if modifying some arguments, we need to truncate and add a comma
if !is_callee && cursor != rng.end && is_arg_like_context(&ident_like) { if !is_callee && cursor != rng.end && is_arg_like_context(&from_ident) {
// extend comma // extend comma
for c in completions.iter_mut() { for c in completions.iter_mut() {
let apply = match &mut c.apply { let apply = match &mut c.apply {
@ -237,10 +201,10 @@ impl StatefulRequest for CompletionRequest {
rng.end = cursor; rng.end = cursor;
} }
replace_range = ctx.to_lsp_range(rng, &source); ctx.to_lsp_range(rng, &source)
} else { } else {
replace_range = ctx.to_lsp_range(offset..cursor, &source); ctx.to_lsp_range(offset..cursor, &source)
} };
let completions = completions.iter().map(|typst_completion| { let completions = completions.iter().map(|typst_completion| {
let typst_snippet = typst_completion let typst_snippet = typst_completion
@ -276,9 +240,7 @@ impl StatefulRequest for CompletionRequest {
..Default::default() ..Default::default()
} }
}); });
let mut items = completions.collect_vec();
Some(completions.collect_vec())
})?;
if let Some(items_rest) = completion_items_rest.as_mut() { if let Some(items_rest) = completion_items_rest.as_mut() {
items.append(items_rest); items.append(items_rest);

View file

@ -519,22 +519,23 @@ pub enum CheckTarget<'a> {
container: LinkedNode<'a>, container: LinkedNode<'a>,
is_before: bool, is_before: bool,
}, },
ImportPath(LinkedNode<'a>),
IncludePath(LinkedNode<'a>),
Normal(LinkedNode<'a>), Normal(LinkedNode<'a>),
} }
impl<'a> CheckTarget<'a> { impl<'a> CheckTarget<'a> {
pub fn node(&self) -> Option<LinkedNode<'a>> { pub fn node(&self) -> Option<LinkedNode<'a>> {
Some(match self { Some(match self {
CheckTarget::Param { target, .. } => match target { CheckTarget::Param { target, .. } | CheckTarget::Element { target, .. } => match target
ParamTarget::Positional { .. } => return None, {
ParamTarget::Named(node) => node.clone(),
},
CheckTarget::Element { target, .. } => match target {
ParamTarget::Positional { .. } => return None, ParamTarget::Positional { .. } => return None,
ParamTarget::Named(node) => node.clone(), ParamTarget::Named(node) => node.clone(),
}, },
CheckTarget::Paren { container, .. } => container.clone(), CheckTarget::Paren { container, .. } => container.clone(),
CheckTarget::Normal(node) => node.clone(), CheckTarget::ImportPath(node)
| CheckTarget::IncludePath(node)
| CheckTarget::Normal(node) => node.clone(),
}) })
} }
} }
@ -606,8 +607,11 @@ pub fn get_check_target(node: LinkedNode) -> Option<CheckTarget<'_>> {
DerefTarget::Callee(callee) => { DerefTarget::Callee(callee) => {
return get_callee_target(callee, node); return get_callee_target(callee, node);
} }
DerefTarget::ImportPath(node) | DerefTarget::IncludePath(node) => { DerefTarget::ImportPath(node) => {
return Some(CheckTarget::Normal(node)); return Some(CheckTarget::ImportPath(node));
}
DerefTarget::IncludePath(node) => {
return Some(CheckTarget::IncludePath(node));
} }
deref_target => deref_target.node().clone(), deref_target => deref_target.node().clone(),
}; };
@ -882,6 +886,8 @@ mod tests {
Some(CheckTarget::Param { .. }) => 'p', Some(CheckTarget::Param { .. }) => 'p',
Some(CheckTarget::Element { .. }) => 'e', Some(CheckTarget::Element { .. }) => 'e',
Some(CheckTarget::Paren { .. }) => 'P', Some(CheckTarget::Paren { .. }) => 'P',
Some(CheckTarget::ImportPath(..)) => 'i',
Some(CheckTarget::IncludePath(..)) => 'I',
Some(CheckTarget::Normal(..)) => 'n', Some(CheckTarget::Normal(..)) => 'n',
None => ' ', None => ' ',
} }

View file

@ -15,7 +15,7 @@ use crate::ty::*;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, EnumIter)] #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
pub enum PathPreference { pub enum PathPreference {
Source, Source { allow_package: bool },
Csv, Csv,
Image, Image,
Json, Json,
@ -74,7 +74,7 @@ impl PathPreference {
}); });
match self { match self {
PathPreference::Source => &SOURCE_REGSET, PathPreference::Source { .. } => &SOURCE_REGSET,
PathPreference::Csv => &CSV_REGSET, PathPreference::Csv => &CSV_REGSET,
PathPreference::Image => &IMAGE_REGSET, PathPreference::Image => &IMAGE_REGSET,
PathPreference::Json => &JSON_REGSET, PathPreference::Json => &JSON_REGSET,
@ -363,7 +363,7 @@ impl BuiltinTy {
BuiltinTy::Path(s) => match s { BuiltinTy::Path(s) => match s {
PathPreference::None => "[any]", PathPreference::None => "[any]",
PathPreference::Special => "[any]", PathPreference::Special => "[any]",
PathPreference::Source => "[source]", PathPreference::Source { .. } => "[source]",
PathPreference::Csv => "[csv]", PathPreference::Csv => "[csv]",
PathPreference::Image => "[image]", PathPreference::Image => "[image]",
PathPreference::Json => "[json]", PathPreference::Json => "[json]",

View file

@ -21,7 +21,7 @@ use crate::analysis::{analyze_labels, DynLabel, LocalContext, Ty};
mod ext; mod ext;
use ext::*; use ext::*;
pub use ext::{complete_path, CompletionFeat, PostfixSnippet}; pub use ext::{CompletionFeat, PostfixSnippet};
/// Autocomplete a cursor position in a source file. /// Autocomplete a cursor position in a source file.
/// ///
@ -38,8 +38,8 @@ pub fn autocomplete(
mut ctx: CompletionContext, mut ctx: CompletionContext,
) -> Option<(usize, bool, Vec<Completion>, Vec<lsp_types::CompletionItem>)> { ) -> Option<(usize, bool, Vec<Completion>, Vec<lsp_types::CompletionItem>)> {
let _ = complete_comments(&mut ctx) let _ = complete_comments(&mut ctx)
|| complete_type(&mut ctx).is_none() && { || complete_type_and_syntax(&mut ctx).is_none() && {
crate::log_debug_ct!("continue after completing type"); crate::log_debug_ct!("continue after completing type and syntax");
complete_labels(&mut ctx) complete_labels(&mut ctx)
|| complete_imports(&mut ctx) || complete_imports(&mut ctx)
|| complete_field_accesses(&mut ctx) || complete_field_accesses(&mut ctx)
@ -511,24 +511,6 @@ fn complete_labels(ctx: &mut CompletionContext) -> bool {
/// Complete imports. /// Complete imports.
fn complete_imports(ctx: &mut CompletionContext) -> bool { fn complete_imports(ctx: &mut CompletionContext) -> bool {
// In an import path for a package:
// "#import "@|",
if_chain! {
if matches!(
ctx.leaf.parent_kind(),
Some(SyntaxKind::ModuleImport | SyntaxKind::ModuleInclude)
);
if let Some(ast::Expr::Str(str)) = ctx.leaf.cast();
let value = str.get();
if value.starts_with('@');
then {
let all_versions = value.contains(':');
ctx.from = ctx.leaf.offset();
ctx.package_completions(all_versions);
return true;
}
}
// On the colon marker of an import list: // On the colon marker of an import list:
// "#import "path.typ":|" // "#import "path.typ":|"
if_chain! { if_chain! {

View file

@ -1410,8 +1410,8 @@ impl TypeCompletionContext<'_, '_> {
// ctx.enrich(" ", ""); // ctx.enrich(" ", "");
// } // }
/// Complete call and set rule parameters. /// Complete code by type or syntax.
pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> { pub(crate) fn complete_type_and_syntax(ctx: &mut CompletionContext) -> Option<()> {
use crate::syntax::get_check_target; use crate::syntax::get_check_target;
use SurroundingSyntax::*; use SurroundingSyntax::*;
@ -1438,6 +1438,33 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
} }
args_node = Some(args.to_untyped().clone()); args_node = Some(args.to_untyped().clone());
} }
Some(CheckTarget::ImportPath(path) | CheckTarget::IncludePath(path)) => {
let Some(ast::Expr::Str(str)) = path.cast() else {
return None;
};
ctx.from = path.offset();
let value = str.get();
if value.starts_with('@') {
let all_versions = value.contains(':');
ctx.package_completions(all_versions);
return Some(());
} else {
let source = ctx.ctx.source_by_id(ctx.root.span().id()?).ok()?;
let paths = complete_path(
ctx.ctx,
Some(path),
&source,
ctx.cursor,
&crate::analysis::PathPreference::Source {
allow_package: true,
},
);
// todo: remove completions2
ctx.completions2.extend(paths.unwrap_or_default());
}
return Some(());
}
Some(CheckTarget::Normal(e)) Some(CheckTarget::Normal(e))
if (matches!(e.kind(), SyntaxKind::ContentBlock) if (matches!(e.kind(), SyntaxKind::ContentBlock)
&& matches!(ctx.leaf.kind(), SyntaxKind::LeftBracket)) => && matches!(ctx.leaf.kind(), SyntaxKind::LeftBracket)) =>
@ -1610,7 +1637,7 @@ pub(crate) fn complete_type(ctx: &mut CompletionContext) -> Option<()> {
Some(()) Some(())
} }
pub fn complete_path( fn complete_path(
ctx: &LocalContext, ctx: &LocalContext,
v: Option<LinkedNode>, v: Option<LinkedNode>,
source: &Source, source: &Source,