feat: identify static path references in documents (#658)

* feat: identify static path references in documents

* test: update snapshot

* test: update snapshot
This commit is contained in:
Myriad-Dreamin 2024-10-11 12:16:37 +08:00 committed by GitHub
parent f5520691fa
commit 406b6bcb34
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 362 additions and 25 deletions

View file

@ -6,6 +6,8 @@ pub mod call;
pub use call::*;
pub mod color_exprs;
pub use color_exprs::*;
pub mod link_exprs;
pub use link_exprs::*;
pub mod def_use;
pub use def_use::*;
pub mod import;

View file

@ -0,0 +1,162 @@
//! Analyze color expressions in a source file.
use std::ops::Range;
use typst::syntax::{
ast::{self, AstNode},
LinkedNode, Source, SyntaxKind,
};
use crate::prelude::*;
/// Get link expressions from a source.
pub fn get_link_exprs(ctx: &mut AnalysisContext, src: &Source) -> Option<Vec<(Range<usize>, Url)>> {
let root = LinkedNode::new(src.root());
get_link_exprs_in(ctx, &root)
}
/// Get link expressions in a source node.
pub fn get_link_exprs_in(
ctx: &mut AnalysisContext,
node: &LinkedNode,
) -> Option<Vec<(Range<usize>, Url)>> {
let mut worker = LinkStrWorker { ctx, links: vec![] };
worker.collect_links(node)?;
Some(worker.links)
}
struct LinkStrWorker<'a, 'w> {
ctx: &'a mut AnalysisContext<'w>,
links: Vec<(Range<usize>, Url)>,
}
impl<'a, 'w> LinkStrWorker<'a, 'w> {
fn collect_links(&mut self, node: &LinkedNode) -> Option<()> {
match node.kind() {
// SyntaxKind::Link => { }
SyntaxKind::FuncCall => {
let fc = self.analyze_call(node);
if fc.is_some() {
return Some(());
}
}
// early exit
k if k.is_trivia() || k.is_keyword() || k.is_error() => return Some(()),
_ => {}
};
for child in node.children() {
self.collect_links(&child);
}
Some(())
}
fn analyze_call(&mut self, node: &LinkedNode) -> Option<()> {
let call = node.cast::<ast::FuncCall>()?;
let mut callee = call.callee();
'check_link_fn: loop {
match callee {
ast::Expr::FieldAccess(fa) => {
let target = fa.target();
let ast::Expr::Ident(ident) = target else {
return None;
};
if ident.get().as_str() != "std" {
return None;
}
callee = ast::Expr::Ident(fa.field());
continue 'check_link_fn;
}
ast::Expr::Ident(ident) => match ident.get().as_str() {
"raw" => {
self.analyze_reader(node, call, "theme", false);
self.analyze_reader(node, call, "syntaxes", false);
}
"bibliography" => {
self.analyze_reader(node, call, "cite", false);
self.analyze_reader(node, call, "style", false);
self.analyze_reader(node, call, "path", true);
}
"cbor" | "csv" | "image" | "read" | "json" | "yaml" | "xml" => {
self.analyze_reader(node, call, "path", true);
}
_ => return None,
},
_ => return None,
}
return None;
}
}
fn analyze_reader(
&mut self,
node: &LinkedNode,
call: ast::FuncCall,
key: &str,
pos: bool,
) -> Option<()> {
let arg = call.args().items().next()?;
match arg {
ast::Arg::Pos(s) if pos => {
self.analyze_path_exp(node, s);
}
_ => {}
}
for item in call.args().items() {
match item {
ast::Arg::Named(named) if named.name().get().as_str() == key => {
self.analyze_path_exp(node, named.expr());
}
_ => {}
}
}
Some(())
}
fn analyze_path_exp(&mut self, node: &LinkedNode, expr: ast::Expr) -> Option<()> {
match expr {
ast::Expr::Str(s) => self.analyze_path_str(node, s),
ast::Expr::Array(a) => {
for item in a.items() {
if let ast::ArrayItem::Pos(ast::Expr::Str(s)) = item {
self.analyze_path_str(node, s);
}
}
Some(())
}
_ => None,
}
}
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() {
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;
}
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));
Some(())
}
}

View file

@ -0,0 +1,39 @@
use crate::{analysis::get_link_exprs, prelude::*, SemanticRequest};
/// The [`textDocument/documentLink`] request is sent from the client to the
/// server to request the location of links in a document.
///
/// A document link is a range in a text document that links to an internal or
/// external resource, like another text document or a web site.
///
/// [`textDocument/documentLink`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_documentLink
///
/// # Compatibility
///
/// The [`DocumentLink::tooltip`] field was introduced in specification version
/// 3.15.0 and requires client-side support in order to be used.
#[derive(Debug, Clone)]
pub struct DocumentLinkRequest {
/// The path of the document to request color for.
pub path: PathBuf,
}
impl SemanticRequest for DocumentLinkRequest {
type Response = Vec<DocumentLink>;
fn request(self, ctx: &mut AnalysisContext) -> 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()
})
}
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/builtin.typ
---
{
"contents": "```typc\nlet table(children, align: alignment | auto | array | function, column-gutter: auto | relative | fraction | int | array, columns: auto | relative | fraction | int | array, fill: color | gradient | pattern | none | array | function, gutter: auto | relative | fraction | int | array, inset: relative | dictionary | array | function, row-gutter: auto | relative | fraction | int | array, rows: auto | relative | fraction | int | array, stroke: length | color | gradient | pattern | dictionary | stroke | none | array | function);\n```\n---\n\n\nA table of items.\n\nTables are used to arrange content in cells. Cells can contain arbitrary\ncontent, including multiple paragraphs and are specified in row-major order.\nFor a hands-on explanation of all the ways you can use and customize tables\nin Typst, check out the [table guide](https://typst.app/docs/guides/table-guide/).\n\nBecause tables are just grids with different defaults for some cell\nproperties (notably `stroke` and `inset`), refer to the [grid\ndocumentation](https://typst.app/docs/reference/layout/grid/) for more information on how to size the table tracks\nand specify the cell appearance properties.\n\nIf you are unsure whether you should be using a table or a grid, consider\nwhether the content you are arranging semantically belongs together as a set\nof related data points or similar or whether you are just want to enhance\nyour presentation by arranging unrelated content in a grid. In the former\ncase, a table is the right choice, while in the latter case, a grid is more\nappropriate. Furthermore, Typst will annotate its output in the future such\nthat screenreaders will annouce content in `table` as tabular while a grid's\ncontent will be announced no different than multiple content blocks in the\ndocument flow.\n\nNote that, to override a particular cell's properties or apply show rules on\ntable cells, you can use the [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) element. See its\ndocumentation for more information.\n\nAlthough the `table` and the `grid` share most properties, set and show\nrules on one of them do not affect the other.\n\nTo give a table a caption and make it [referenceable](https://typst.app/docs/reference/model/ref/), put it into a\n[figure].\n\n# Example\n\nThe example below demonstrates some of the most common table options.\n```typ\n#table(\n columns: (1fr, auto, auto),\n inset: 10pt,\n align: horizon,\n table.header(\n [], [*Area*], [*Parameters*],\n ),\n image(\"cylinder.svg\"),\n $ pi h (D^2 - d^2) / 4 $,\n [\n $h$: height \\\n $D$: outer radius \\\n $d$: inner radius\n ],\n image(\"tetrahedron.svg\"),\n $ sqrt(2) / 12 a^3 $,\n [$a$: edge length]\n)\n```\n\nMuch like with grids, you can use [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) to customize\nthe appearance and the position of each cell.\n\n```typ\n>>> #set page(width: auto)\n>>> #set text(font: \"IBM Plex Sans\")\n>>> #let gray = rgb(\"#565565\")\n>>>\n#set table(\n stroke: none,\n gutter: 0.2em,\n fill: (x, y) =>\n if x == 0 or y == 0 { gray },\n inset: (right: 1.5em),\n)\n\n#show table.cell: it => {\n if it.x == 0 or it.y == 0 {\n set text(white)\n strong(it)\n } else if it.body == [] {\n // Replace empty cells with 'N/A'\n pad(..it.inset)[_N/A_]\n } else {\n it\n }\n}\n\n#let a = table.cell(\n fill: green.lighten(60%),\n)[A]\n#let b = table.cell(\n fill: aqua.lighten(60%),\n)[B]\n\n#table(\n columns: 4,\n [], [Exam 1], [Exam 2], [Exam 3],\n\n [John], [], a, [],\n [Mary], [], a, a,\n [Robert], b, a, b,\n)\n```\n---\n[Open docs](https://typst.app/docs/reference/model/table/)",
"contents": "```typc\nlet table(children, align: alignment | auto | array | function, column-gutter: auto | relative | fraction | int | array, columns: auto | relative | fraction | int | array, fill: color | gradient | pattern | none | array | function, gutter: auto | relative | fraction | int | array, inset: relative | dictionary | array | function, row-gutter: auto | relative | fraction | int | array, rows: auto | relative | fraction | int | array, stroke: length | color | gradient | pattern | dictionary | stroke | none | array | function);\n```\n\n---\n\n\nA table of items.\n\nTables are used to arrange content in cells. Cells can contain arbitrary\ncontent, including multiple paragraphs and are specified in row-major order.\nFor a hands-on explanation of all the ways you can use and customize tables\nin Typst, check out the [table guide](https://typst.app/docs/guides/table-guide/).\n\nBecause tables are just grids with different defaults for some cell\nproperties (notably `stroke` and `inset`), refer to the [grid\ndocumentation](https://typst.app/docs/reference/layout/grid/) for more information on how to size the table tracks\nand specify the cell appearance properties.\n\nIf you are unsure whether you should be using a table or a grid, consider\nwhether the content you are arranging semantically belongs together as a set\nof related data points or similar or whether you are just want to enhance\nyour presentation by arranging unrelated content in a grid. In the former\ncase, a table is the right choice, while in the latter case, a grid is more\nappropriate. Furthermore, Typst will annotate its output in the future such\nthat screenreaders will annouce content in `table` as tabular while a grid's\ncontent will be announced no different than multiple content blocks in the\ndocument flow.\n\nNote that, to override a particular cell's properties or apply show rules on\ntable cells, you can use the [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) element. See its\ndocumentation for more information.\n\nAlthough the `table` and the `grid` share most properties, set and show\nrules on one of them do not affect the other.\n\nTo give a table a caption and make it [referenceable](https://typst.app/docs/reference/model/ref/), put it into a\n[figure].\n\n# Example\n\nThe example below demonstrates some of the most common table options.\n```typ\n#table(\n columns: (1fr, auto, auto),\n inset: 10pt,\n align: horizon,\n table.header(\n [], [*Area*], [*Parameters*],\n ),\n image(\"cylinder.svg\"),\n $ pi h (D^2 - d^2) / 4 $,\n [\n $h$: height \\\n $D$: outer radius \\\n $d$: inner radius\n ],\n image(\"tetrahedron.svg\"),\n $ sqrt(2) / 12 a^3 $,\n [$a$: edge length]\n)\n```\n\nMuch like with grids, you can use [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) to customize\nthe appearance and the position of each cell.\n\n```typ\n>>> #set page(width: auto)\n>>> #set text(font: \"IBM Plex Sans\")\n>>> #let gray = rgb(\"#565565\")\n>>>\n#set table(\n stroke: none,\n gutter: 0.2em,\n fill: (x, y) =>\n if x == 0 or y == 0 { gray },\n inset: (right: 1.5em),\n)\n\n#show table.cell: it => {\n if it.x == 0 or it.y == 0 {\n set text(white)\n strong(it)\n } else if it.body == [] {\n // Replace empty cells with 'N/A'\n pad(..it.inset)[_N/A_]\n } else {\n it\n }\n}\n\n#let a = table.cell(\n fill: green.lighten(60%),\n)[A]\n#let b = table.cell(\n fill: aqua.lighten(60%),\n)[B]\n\n#table(\n columns: 4,\n [], [Exam 1], [Exam 2], [Exam 3],\n\n [John], [], a, [],\n [Mary], [], a, a,\n [Robert], b, a, b,\n)\n```\n\n---\n[Open docs](https://typst.app/docs/reference/model/table/)",
"range": "0:20:0:25"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/builtin_module.typ
---
{
"contents": "```typc\n<module sys>\n```\n---\n```typc\nlet sys;\n```",
"contents": "```typc\n<module sys>\n```\n\n---\n```typc\nlet sys;\n```",
"range": "0:20:0:23"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/builtin_var2.typ
---
{
"contents": "```typc\n<module sys>\n```\n---\n```typc\nlet sys;\n```",
"contents": "```typc\n<module sys>\n```\n\n---\n```typc\nlet sys;\n```",
"range": "0:20:0:23"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/builtin_var3.typ
---
{
"contents": "```typc\n<module sys>\n```\n---\n```typc\nlet sys;\n```",
"contents": "```typc\n<module sys>\n```\n\n---\n```typc\nlet sys;\n```",
"range": "0:2:0:5"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/pagebreak.typ
---
{
"contents": "```typc\nlet pagebreak(to: \"even\" | \"odd\" | none, weak: bool);\n```\n---\n\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)",
"contents": "```typc\nlet pagebreak(to: \"even\" | \"odd\" | none, weak: bool);\n```\n\n---\n\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)",
"range": "0:20:0:29"
}

View file

@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
input_file: crates/tinymist-query/src/fixtures/hover/user.typ
---
{
"contents": "```typc\nlet f();\n```\n---\n\n\nTest",
"contents": "```typc\nlet f();\n```\n\n---\n\n\nTest",
"range": "3:20:3:21"
}

View file

@ -3,10 +3,13 @@ use core::fmt;
use typst_shim::syntax::LinkedNodeExt;
use crate::{
analysis::{analyze_dyn_signature, find_definition, DefinitionLink, Signature},
analysis::{
analyze_dyn_signature, find_definition, get_link_exprs_in, DefinitionLink, Signature,
},
jump_from_cursor,
prelude::*,
syntax::{find_docs_before, get_deref_target, LexicalKind, LexicalVarKind},
ty::PathPreference,
upstream::{expr_tooltip, plain_docs_sentence, route_of_value, truncated_repr, Tooltip},
LspHoverContents, StatefulRequest,
};
@ -42,7 +45,8 @@ impl StatefulRequest for HoverRequest {
let cursor = offset + 1;
let contents = def_tooltip(ctx, &source, doc.as_ref(), cursor)
.or_else(|| star_tooltip(ctx, &source, cursor));
.or_else(|| star_tooltip(ctx, &source, cursor))
.or_else(|| link_tooltip(ctx, &source, cursor));
let contents = contents.or_else(|| {
Some(typst_to_lsp::tooltip(
@ -64,7 +68,7 @@ impl StatefulRequest for HoverRequest {
}
MarkedString::String(e) => e,
})
.join("\n---\n"),
.join("\n\n---\n"),
LspHoverContents::Scalar(MarkedString::String(contents)) => contents,
LspHoverContents::Scalar(MarkedString::LanguageString(contents)) => {
format!("```{}\n{}\n```", contents.language, contents.value)
@ -117,6 +121,53 @@ impl StatefulRequest for HoverRequest {
}
}
fn link_tooltip(
ctx: &mut AnalysisContext<'_>,
source: &Source,
cursor: usize,
) -> Option<HoverContents> {
let mut node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?;
while !matches!(node.kind(), SyntaxKind::FuncCall) {
node = node.parent()?.clone();
}
let mut links = get_link_exprs_in(ctx, &node)?;
links.retain(|link| link.0.contains(&cursor));
if links.is_empty() {
return None;
}
let mut results = vec![];
let mut actions = vec![];
for (_, target) in links {
// open file in tab or system application
actions.push(CommandLink {
title: Some("Open in Tab".to_string()),
command_or_links: vec![CommandOrLink::Command(Command {
id: "tinymist.openInternal".to_string(),
args: vec![JsonValue::String(target.to_string())],
})],
});
actions.push(CommandLink {
title: Some("Open Externally".to_string()),
command_or_links: vec![CommandOrLink::Command(Command {
id: "tinymist.openExternal".to_string(),
args: vec![JsonValue::String(target.to_string())],
})],
});
if let Some(kind) = PathPreference::from_ext(target.path()) {
let preview = format!("A `{kind:?}` file.");
results.push(MarkedString::String(preview));
}
}
render_actions(&mut results, actions);
if results.is_empty() {
return None;
}
Some(LspHoverContents::Array(results))
}
fn star_tooltip(
ctx: &mut AnalysisContext,
source: &Source,
@ -161,8 +212,14 @@ fn star_tooltip(
Some(LspHoverContents::Array(results))
}
struct Command {
id: String,
args: Vec<JsonValue>,
}
enum CommandOrLink {
Link(String),
Command(Command),
}
struct CommandLink {
@ -281,13 +338,27 @@ fn render_actions(results: &mut Vec<MarkedString>, actions: Vec<CommandLink>) {
.into_iter()
.map(|col| match col {
CommandOrLink::Link(link) => link,
CommandOrLink::Command(command) => {
let id = command.id;
// <https://code.visualstudio.com/api/extension-guides/command#command-uris>
if command.args.is_empty() {
format!("command:{id}")
} else {
let args = serde_json::to_string(&command.args).unwrap();
let args = percent_encoding::utf8_percent_encode(
&args,
percent_encoding::NON_ALPHANUMERIC,
);
format!("command:{id}?{args}")
}
}
})
.collect::<Vec<_>>()
.join(" ");
format!("[{title}]({command_or_links})")
})
.collect::<Vec<_>>()
.join("___");
.join(" | ");
results.push(MarkedString::String(g));
}

View file

@ -35,6 +35,8 @@ mod document_highlight;
pub use document_highlight::*;
mod document_symbol;
pub use document_symbol::*;
mod document_link;
pub use document_link::*;
mod workspace_label;
pub use workspace_label::*;
mod document_metrics;
@ -246,6 +248,7 @@ mod polymorphic {
References(ReferencesRequest),
InlayHint(InlayHintRequest),
DocumentColor(DocumentColorRequest),
DocumentLink(DocumentLinkRequest),
DocumentHighlight(DocumentHighlightRequest),
ColorPresentation(ColorPresentationRequest),
CodeAction(CodeActionRequest),
@ -282,6 +285,7 @@ mod polymorphic {
Self::References(..) => PinnedFirst,
Self::InlayHint(..) => Unique,
Self::DocumentColor(..) => PinnedFirst,
Self::DocumentLink(..) => PinnedFirst,
Self::DocumentHighlight(..) => PinnedFirst,
Self::ColorPresentation(..) => ContextFreeUnique,
Self::CodeAction(..) => Unique,
@ -317,6 +321,7 @@ mod polymorphic {
Self::References(req) => &req.path,
Self::InlayHint(req) => &req.path,
Self::DocumentColor(req) => &req.path,
Self::DocumentLink(req) => &req.path,
Self::DocumentHighlight(req) => &req.path,
Self::ColorPresentation(req) => &req.path,
Self::CodeAction(req) => &req.path,
@ -353,6 +358,7 @@ mod polymorphic {
References(Option<Vec<LspLocation>>),
InlayHint(Option<Vec<InlayHint>>),
DocumentColor(Option<Vec<ColorInformation>>),
DocumentLink(Option<Vec<DocumentLink>>),
DocumentHighlight(Option<Vec<DocumentHighlight>>),
ColorPresentation(Option<Vec<ColorPresentation>>),
CodeAction(Option<Vec<CodeActionOrCommand>>),
@ -388,6 +394,7 @@ mod polymorphic {
Self::References(res) => serde_json::to_value(res),
Self::InlayHint(res) => serde_json::to_value(res),
Self::DocumentColor(res) => serde_json::to_value(res),
Self::DocumentLink(res) => serde_json::to_value(res),
Self::DocumentHighlight(res) => serde_json::to_value(res),
Self::ColorPresentation(res) => serde_json::to_value(res),
Self::CodeAction(res) => serde_json::to_value(res),

View file

@ -11,8 +11,8 @@ pub use log::{error, trace};
pub use lsp_types::{
request::GotoDeclarationResponse, CodeAction, CodeActionKind, CodeActionOrCommand, CodeLens,
ColorInformation, ColorPresentation, CompletionResponse, DiagnosticRelatedInformation,
DocumentHighlight, DocumentSymbol, DocumentSymbolResponse, Documentation, FoldingRange,
GotoDefinitionResponse, Hover, HoverContents, InlayHint, LanguageString,
DocumentHighlight, DocumentLink, DocumentSymbol, DocumentSymbolResponse, Documentation,
FoldingRange, GotoDefinitionResponse, Hover, HoverContents, InlayHint, LanguageString,
Location as LspLocation, LocationLink, MarkedString, MarkupContent, MarkupKind,
Position as LspPosition, PrepareRenameResponse, SelectionRange, SemanticTokens,
SemanticTokensDelta, SemanticTokensFullDeltaResult, SemanticTokensResult, SignatureHelp,

View file

@ -2,6 +2,7 @@ use core::fmt;
use once_cell::sync::Lazy;
use regex::RegexSet;
use strum::{EnumIter, IntoEnumIterator};
use typst::{foundations::CastInfo, syntax::Span};
use typst::{
foundations::{AutoValue, Content, Func, NoneValue, ParamInfo, Type, Value},
@ -10,10 +11,8 @@ use typst::{
use crate::{adt::interner::Interned, ty::*};
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
#[derive(Debug, Clone, Hash, PartialEq, Eq, EnumIter)]
pub enum PathPreference {
None,
Special,
Source,
Csv,
Image,
@ -25,6 +24,8 @@ pub enum PathPreference {
Bibliography,
RawTheme,
RawSyntax,
Special,
None,
}
impl PathPreference {
@ -33,7 +34,8 @@ impl PathPreference {
Lazy::new(|| RegexSet::new([r"^typ$", r"^typc$"]).unwrap());
static IMAGE_REGSET: Lazy<RegexSet> = Lazy::new(|| {
RegexSet::new([
r"^png$", r"^webp$", r"^jpg$", r"^jpeg$", r"^svg$", r"^svgz$",
r"^ico$", r"^bmp$", r"^png$", r"^webp$", r"^jpg$", r"^jpeg$", r"^jfif$", r"^tiff$",
r"^gif$", r"^svg$", r"^svgz$",
])
.unwrap()
});
@ -70,8 +72,6 @@ impl PathPreference {
});
match self {
PathPreference::None => &ALL_REGSET,
PathPreference::Special => &ALL_SPECIAL_REGSET,
PathPreference::Source => &SOURCE_REGSET,
PathPreference::Csv => &CSV_REGSET,
PathPreference::Image => &IMAGE_REGSET,
@ -83,8 +83,15 @@ impl PathPreference {
PathPreference::Bibliography => &BIB_REGSET,
PathPreference::RawTheme => &RAW_THEME_REGSET,
PathPreference::RawSyntax => &RAW_SYNTAX_REGSET,
PathPreference::Special => &ALL_SPECIAL_REGSET,
PathPreference::None => &ALL_REGSET,
}
}
pub fn from_ext(path: &str) -> Option<Self> {
let path = std::path::Path::new(path).extension()?.to_str()?;
PathPreference::iter().find(|p| p.ext_matcher().is_match(path))
}
}
impl Ty {