Autocomplete for packages

This commit is contained in:
Laurenz 2023-06-30 16:17:12 +02:00
parent 65f11dc364
commit 5b4f4c164b
4 changed files with 74 additions and 13 deletions

View File

@ -290,6 +290,8 @@ impl PackageManifest {
} }
/// The `package` key in the manifest. /// The `package` key in the manifest.
///
/// More fields are specified, but they are not relevant to the compiler.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct PackageInfo { pub struct PackageInfo {
/// The name of the package within its namespace. /// The name of the package within its namespace.

View File

@ -7,7 +7,7 @@ use unscanny::Scanner;
use super::analyze::analyze_labels; use super::analyze::analyze_labels;
use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family}; use super::{analyze_expr, analyze_import, plain_docs_sentence, summarize_font_family};
use crate::doc::Frame; use crate::doc::Frame;
use crate::eval::{methods_on, CastInfo, Library, Scope, Value}; use crate::eval::{format_str, methods_on, CastInfo, Library, Scope, Value};
use crate::syntax::{ use crate::syntax::{
ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind, ast, is_id_continue, is_id_start, is_ident, LinkedNode, Source, SyntaxKind,
}; };
@ -402,6 +402,22 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value) {
/// 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();
if str.get().starts_with('@');
then {
ctx.from = ctx.leaf.offset();
ctx.package_completions();
return true;
}
}
// Behind an import list: // Behind an import list:
// "#import "path.typ": |", // "#import "path.typ": |",
// "#import "path.typ": a, b, |". // "#import "path.typ": a, b, |".
@ -413,7 +429,7 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next(); if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next();
then { then {
ctx.from = ctx.cursor; ctx.from = ctx.cursor;
import_completions(ctx, &items, &value); import_item_completions(ctx, &items, &value);
return true; return true;
} }
} }
@ -431,7 +447,7 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next(); if let Some(value) = analyze_expr(ctx.world, &source).into_iter().next();
then { then {
ctx.from = ctx.leaf.offset(); ctx.from = ctx.leaf.offset();
import_completions(ctx, &items, &value); import_item_completions(ctx, &items, &value);
return true; return true;
} }
} }
@ -440,7 +456,7 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
} }
/// Add completions for all exports of a module. /// Add completions for all exports of a module.
fn import_completions( fn import_item_completions(
ctx: &mut CompletionContext, ctx: &mut CompletionContext,
existing: &[ast::Ident], existing: &[ast::Ident],
value: &Value, value: &Value,
@ -839,14 +855,26 @@ fn code_completions(ctx: &mut CompletionContext, hashtag: bool) {
); );
ctx.snippet_completion( ctx.snippet_completion(
"import", "import (file)",
"import \"${file.typ}\": ${items}", "import \"${file}.typ\": ${items}",
"Imports variables from another file.", "Imports variables from another file.",
); );
ctx.snippet_completion( ctx.snippet_completion(
"include", "import (package)",
"include \"${file.typ}\"", "import \"@${}\": ${items}",
"Imports variables from another file.",
);
ctx.snippet_completion(
"include (file)",
"include \"${file}.typ\"",
"Includes content from another file.",
);
ctx.snippet_completion(
"include (package)",
"include \"@${}\"",
"Includes content from another file.", "Includes content from another file.",
); );
@ -960,6 +988,18 @@ impl<'a> CompletionContext<'a> {
} }
} }
/// Add completions for all available packages.
fn package_completions(&mut self) {
for (package, description) in self.world.packages() {
self.value_completion(
None,
&Value::Str(format_str!("{package}")),
false,
description.as_deref(),
);
}
}
/// Add completions for raw block tags. /// Add completions for raw block tags.
fn raw_completions(&mut self) { fn raw_completions(&mut self) {
for (name, mut tags) in (self.library.items.raw_languages)() { for (name, mut tags) in (self.library.items.raw_languages)() {
@ -1014,7 +1054,10 @@ impl<'a> CompletionContext<'a> {
let detail = docs.map(Into::into).or_else(|| match value { let detail = docs.map(Into::into).or_else(|| match value {
Value::Symbol(_) => None, Value::Symbol(_) => None,
Value::Func(func) => func.info().map(|info| plain_docs_sentence(info.docs)), Value::Func(func) => func.info().map(|info| plain_docs_sentence(info.docs)),
v => Some(v.repr().into()), v => {
let repr = v.repr();
(repr.as_str() != label).then(|| repr.into())
}
}); });
if parens && matches!(value, Value::Func(_)) { if parens && matches!(value, Value::Func(_)) {

View File

@ -54,11 +54,12 @@ pub mod model;
pub mod syntax; pub mod syntax;
use comemo::{Prehashed, Track, TrackedMut}; use comemo::{Prehashed, Track, TrackedMut};
use ecow::EcoString;
use crate::diag::{FileResult, SourceResult}; use crate::diag::{FileResult, SourceResult};
use crate::doc::Document; use crate::doc::Document;
use crate::eval::{Datetime, Library, Route, Tracer}; use crate::eval::{Datetime, Library, Route, Tracer};
use crate::file::FileId; use crate::file::{FileId, PackageSpec};
use crate::font::{Font, FontBook}; use crate::font::{Font, FontBook};
use crate::syntax::Source; use crate::syntax::Source;
use crate::util::Bytes; use crate::util::Bytes;
@ -112,6 +113,11 @@ pub trait World {
fn main(&self) -> Source; fn main(&self) -> Source;
/// Try to access the specified source file. /// Try to access the specified source file.
///
/// The returned `Source` file's [id](Source::id) does not have to match the
/// given `id`. Due to symlinks, two different file id's can point to the
/// same on-disk file. Implementors can deduplicate and return the same
/// `Source` if they want to, but do not have to.
fn source(&self, id: FileId) -> FileResult<Source>; fn source(&self, id: FileId) -> FileResult<Source>;
/// Try to access the specified file. /// Try to access the specified file.
@ -124,5 +130,18 @@ pub trait World {
/// ///
/// If no offset is specified, the local date should be chosen. Otherwise, /// If no offset is specified, the local date should be chosen. Otherwise,
/// the UTC date should be chosen with the corresponding offset in hours. /// the UTC date should be chosen with the corresponding offset in hours.
///
/// If this function returns `None`, Typst's `datetime` function will
/// return an error.
fn today(&self, offset: Option<i64>) -> Option<Datetime>; fn today(&self, offset: Option<i64>) -> Option<Datetime>;
/// A list of all available packages and optionally descriptions for them.
///
/// This function is optional to implement. It enhances the user experience
/// by enabling autocompletion for packages. Details about packages from the
/// `@preview` namespace are available from
/// `https://packages.typst.org/preview/index.json`.
fn packages(&self) -> &[(PackageSpec, Option<EcoString>)] {
&[]
}
} }

View File

@ -34,9 +34,6 @@ struct Repr {
impl Source { impl Source {
/// Create a new source file. /// Create a new source file.
///
/// The path must be canonical, so that the same source file has the same
/// id even if accessed through different paths.
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn new(id: FileId, text: String) -> Self { pub fn new(id: FileId, text: String) -> Self {
let mut root = parse(&text); let mut root = parse(&text);