Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dev: cache link expressions #866

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 78 additions & 34 deletions crates/tinymist-query/src/analysis/link_exprs.rs
Original file line number Diff line number Diff line change
@@ -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)
}

/// 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 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<'a> {
ctx: &'a mut LocalContext,
links: Vec<(Range<usize>, Url)>,
struct LinkStrWorker {
info: LinkInfo,
}

impl<'a> LinkStrWorker<'a> {
impl LinkStrWorker {
fn collect_links(&mut self, node: &LinkedNode) -> Option<()> {
match node.kind() {
// SyntaxKind::Link => { }
Expand All @@ -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(()),
_ => {}
Expand Down Expand Up @@ -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 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;
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(());
}

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));

let id = node.span().id()?;
self.info.objects.push(LinkObject {
range,
span: s.span(),
target: LinkTarget::Path(id, content),
});
Some(())
}
}
24 changes: 12 additions & 12 deletions crates/tinymist-query/src/document_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
13 changes: 10 additions & 3 deletions crates/tinymist-query/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down
Loading