mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
dev: cache link expressions (#866)
This commit is contained in:
parent
19a83bc942
commit
9885c45fb2
3 changed files with 101 additions and 50 deletions
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue