Autocomplete for packages
This commit is contained in:
parent
65f11dc364
commit
5b4f4c164b
@ -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.
|
||||||
|
@ -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(_)) {
|
||||||
|
21
src/lib.rs
21
src/lib.rs
@ -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>)] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user