dev: cache link expressions (#866)

This commit is contained in:
Myriad-Dreamin 2024-11-20 16:24:12 +08:00 committed by GitHub
parent 19a83bc942
commit 9885c45fb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 101 additions and 50 deletions

View file

@ -1,32 +1,75 @@
//! Analyze color expressions in a source file.
use std::str::FromStr;
use lsp_types::Url;
use reflexo_typst::package::PackageSpec;
use super::prelude::*;
use crate::path_to_url;
/// Get link expressions from a source.
pub fn get_link_exprs(ctx: &mut LocalContext, src: &Source) -> Option<Vec<(Range<usize>, Url)>> {
#[comemo::memoize]
pub fn get_link_exprs(src: &Source) -> Arc<LinkInfo> {
let root = LinkedNode::new(src.root());
get_link_exprs_in(ctx, &root)
Arc::new(get_link_exprs_in(&root).unwrap_or_default())
}
/// Get link expressions in a source node.
pub fn get_link_exprs_in(
ctx: &mut LocalContext,
node: &LinkedNode,
) -> Option<Vec<(Range<usize>, Url)>> {
let mut worker = LinkStrWorker { ctx, links: vec![] };
pub fn get_link_exprs_in(node: &LinkedNode) -> Option<LinkInfo> {
let mut worker = LinkStrWorker {
info: LinkInfo::default(),
};
worker.collect_links(node)?;
Some(worker.links)
Some(worker.info)
}
struct LinkStrWorker<'a> {
ctx: &'a mut LocalContext,
links: Vec<(Range<usize>, Url)>,
/// A valid link target.
pub enum LinkTarget {
/// A package specification.
Package(Box<PackageSpec>),
/// A URL.
Url(Box<Url>),
/// A file path.
Path(TypstFileId, EcoString),
}
impl<'a> LinkStrWorker<'a> {
impl LinkTarget {
pub(crate) fn resolve(&self, ctx: &mut LocalContext) -> Option<Url> {
match self {
LinkTarget::Package(..) => None,
LinkTarget::Url(url) => Some(url.as_ref().clone()),
LinkTarget::Path(id, path) => {
// Avoid creating new ids here.
let base = id.vpath().join(path.as_str());
let root = ctx.path_for_id(id.join("/")).ok()?;
crate::path_to_url(&base.resolve(&root)?).ok()
}
}
}
}
/// A link object in a source file.
pub struct LinkObject {
/// The range of the link expression.
pub range: Range<usize>,
/// The span of the link expression.
pub span: Span,
/// The target of the link.
pub target: LinkTarget,
}
/// Link information in a source file.
#[derive(Default)]
pub struct LinkInfo {
/// The link objects in a source file.
pub objects: Vec<LinkObject>,
}
struct LinkStrWorker {
info: LinkInfo,
}
impl LinkStrWorker {
fn collect_links(&mut self, node: &LinkedNode) -> Option<()> {
match node.kind() {
// SyntaxKind::Link => { }
@ -36,6 +79,11 @@ impl<'a> LinkStrWorker<'a> {
return Some(());
}
}
SyntaxKind::Include => {
let inc = node.cast::<ast::ModuleInclude>()?;
let path = inc.source();
self.analyze_path_exp(node, path);
}
// early exit
k if k.is_trivia() || k.is_keyword() || k.is_error() => return Some(()),
_ => {}
@ -128,32 +176,28 @@ impl<'a> LinkStrWorker<'a> {
fn analyze_path_str(&mut self, node: &LinkedNode, s: ast::Str<'_>) -> Option<()> {
let str_node = node.find(s.span())?;
let str_range = str_node.range();
let content_range = str_range.start + 1..str_range.end - 1;
if content_range.is_empty() {
let range = str_range.start + 1..str_range.end - 1;
if range.is_empty() {
return None;
}
// Avoid creating new ids here.
let content = s.get();
if content.starts_with('@') {
let pkg_spec = PackageSpec::from_str(&content).ok()?;
self.info.objects.push(LinkObject {
range,
span: s.span(),
target: LinkTarget::Package(Box::new(pkg_spec)),
});
return Some(());
}
let id = node.span().id()?;
let base = id.vpath().join(s.get().as_str());
let root = self.ctx.path_for_id(id.join("/")).ok()?;
let path = base.resolve(&root)?;
if !path.exists() {
return None;
}
self.push_path(content_range, path.as_path())
}
fn push_path(&mut self, range: Range<usize>, path: &Path) -> Option<()> {
self.push_link(range, path_to_url(path).ok()?)
}
fn push_link(&mut self, range: Range<usize>, target: Url) -> Option<()> {
// let rng = self.ctx.to_lsp_range(range, &self.source);
self.links.push((range, target));
self.info.objects.push(LinkObject {
range,
span: s.span(),
target: LinkTarget::Path(id, content),
});
Some(())
}
}

View file

@ -23,17 +23,17 @@ impl SemanticRequest for DocumentLinkRequest {
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
let source = ctx.source_by_path(&self.path).ok()?;
let links = get_link_exprs(ctx, &source);
links.map(|links| {
links
.into_iter()
.map(|(range, target)| DocumentLink {
range: ctx.to_lsp_range(range, &source),
target: Some(target),
tooltip: None,
data: None,
})
.collect()
})
let links = get_link_exprs(&source);
if links.objects.is_empty() {
return None;
}
let links = links.objects.iter().map(|obj| DocumentLink {
range: ctx.to_lsp_range(obj.range.clone(), &source),
target: obj.target.resolve(ctx),
tooltip: None,
data: None,
});
Some(links.collect())
}
}

View file

@ -235,15 +235,22 @@ fn link_tooltip(
node = node.parent()?;
}
let mut links = get_link_exprs_in(ctx, node)?;
links.retain(|link| link.0.contains(&cursor));
let links = get_link_exprs_in(node)?;
let links = links
.objects
.iter()
.filter(|link| link.range.contains(&cursor))
.collect::<Vec<_>>();
if links.is_empty() {
return None;
}
let mut results = vec![];
let mut actions = vec![];
for (_, target) in links {
for obj in links {
let Some(target) = obj.target.resolve(ctx) else {
continue;
};
// open file in tab or system application
actions.push(CommandLink {
title: Some("Open in Tab".to_string()),