mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-19 11:34:57 +00:00
feat: add PathAt code context query (#2232)
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
This is used for custom paste scripts
- by pattern: e.g. `$root`
- by code: e.g. `{ root }`
- on conflict callback: e.g. `{ (dir: root, on-conflict: root + "/" +
random() + ".png") }`
This commit is contained in:
parent
c63162959d
commit
c206933bf5
12 changed files with 645 additions and 13 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -4745,6 +4745,7 @@ dependencies = [
|
|||
"typlite",
|
||||
"typst",
|
||||
"typst-assets",
|
||||
"typst-library",
|
||||
"typst-macros",
|
||||
"typst-shim",
|
||||
"typst-timing 0.14.0",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ typst.workspace = true
|
|||
typst-macros.workspace = true
|
||||
typst-shim.workspace = true
|
||||
typst-timing.workspace = true
|
||||
typst-library.workspace = true
|
||||
unscanny.workspace = true
|
||||
walkdir.workspace = true
|
||||
yaml-rust2.workspace = true
|
||||
|
|
|
|||
|
|
@ -642,7 +642,7 @@ mod lint_tests {
|
|||
let result = crate::diagnostics::DiagWorker::new(ctx).convert_all(result.iter());
|
||||
let result = result
|
||||
.into_iter()
|
||||
.map(|(k, v)| (file_path_(&k), v))
|
||||
.map(|(k, v)| (file_uri_(&k), v))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,10 +1,21 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use comemo::Track;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_analysis::analyze_expr;
|
||||
use tinymist_world::ShadowApi;
|
||||
use tinymist_project::{DiagnosticFormat, PathPattern};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_world::vfs::WorkspaceResolver;
|
||||
use tinymist_world::{EntryReader, EntryState, ShadowApi, diag::print_diagnostics_to_string};
|
||||
use typst::diag::{At, SourceResult};
|
||||
use typst::foundations::{Args, Dict, NativeFunc, eco_format};
|
||||
use typst::syntax::Span;
|
||||
use typst::utils::LazyHash;
|
||||
use typst::{
|
||||
foundations::{Bytes, IntoValue, StyleChain},
|
||||
text::TextElem,
|
||||
};
|
||||
use typst_shim::eval::{Eval, Vm};
|
||||
use typst_shim::syntax::LinkedNodeExt;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -16,6 +27,38 @@ use crate::{
|
|||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||||
pub enum InteractCodeContextQuery {
|
||||
/// (Experimental) Evaluate a path expression at a specific position in a
|
||||
/// text document.
|
||||
PathAt {
|
||||
/// Code to evaluate. If the code starts with `{` and ends with `}`, it
|
||||
/// will be evaluated as a code expression, otherwise it will be
|
||||
/// evaluated as a path pattern.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// evaluate a path pattern, which could use following definitions:
|
||||
///
|
||||
/// ```plain
|
||||
/// $root/x/$dir/../$name // is evaluated as
|
||||
/// /path/to/root/x/dir/../main
|
||||
/// ```
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// evaluate a code expression, which could use following definitions:
|
||||
/// - `root`: the root of the workspace
|
||||
/// - `dir`: the directory of the current file
|
||||
/// - `name`: the name of the current file
|
||||
/// - `join(a, b, ...)`: join the arguments with the path separator
|
||||
///
|
||||
/// ```plain
|
||||
/// { join(root, "x", dir, "y", name) } // is evaluated as
|
||||
/// /path/to/root/x/dir/y/main
|
||||
/// ```
|
||||
code: String,
|
||||
/// The extra `sys.inputs` for the code expression.
|
||||
inputs: Dict,
|
||||
},
|
||||
/// Get the mode at a specific position in a text document.
|
||||
ModeAt {
|
||||
/// The position inside the text document.
|
||||
|
|
@ -34,6 +77,8 @@ pub enum InteractCodeContextQuery {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "camelCase")]
|
||||
pub enum InteractCodeContextResponse {
|
||||
/// Evaluate a path expression at a specific position in a text document.
|
||||
PathAt(QueryResult<serde_json::Value>),
|
||||
/// Get the mode at a specific position in a text document.
|
||||
ModeAt {
|
||||
/// The mode at the requested position.
|
||||
|
|
@ -66,6 +111,10 @@ impl SemanticRequest for InteractCodeContextRequest {
|
|||
|
||||
for query in self.query {
|
||||
responses.push(query.and_then(|query| match query {
|
||||
InteractCodeContextQuery::PathAt { code, inputs: base } => {
|
||||
let res = eval_path_expr(ctx, &code, base)?;
|
||||
Some(InteractCodeContextResponse::PathAt(res))
|
||||
}
|
||||
InteractCodeContextQuery::ModeAt { position } => {
|
||||
let cursor = ctx.to_typst_pos(position, &source)?;
|
||||
let mode = Self::mode_at(&source, cursor)?;
|
||||
|
|
@ -167,3 +216,248 @@ impl InteractCodeContextRequest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_path_expr(
|
||||
ctx: &mut LocalContext,
|
||||
code: &str,
|
||||
inputs: Dict,
|
||||
) -> Option<QueryResult<serde_json::Value>> {
|
||||
let entry = ctx.world().entry_state();
|
||||
let path = if code.starts_with("{") && code.ends_with("}") {
|
||||
let id = entry
|
||||
.select_in_workspace(Path::new("/__path__.typ"))
|
||||
.main()?;
|
||||
|
||||
let inputs = make_sys(&entry, ctx.world().inputs(), inputs);
|
||||
let (inputs, root, dir, name) = match inputs {
|
||||
Some(EvalSysCtx {
|
||||
inputs,
|
||||
root,
|
||||
dir,
|
||||
name,
|
||||
}) => (Some(inputs), Some(root), dir, Some(name)),
|
||||
None => (None, None, None, None),
|
||||
};
|
||||
|
||||
let mut world = ctx.world().task(tinymist_world::TaskInputs {
|
||||
entry: None,
|
||||
inputs,
|
||||
});
|
||||
// todo: bad performance
|
||||
world.take_db();
|
||||
let _ = world.map_shadow_by_id(id, Bytes::from_string(code.to_owned()));
|
||||
|
||||
tinymist_analysis::upstream::with_vm((&world as &dyn World).track(), |vm| {
|
||||
define_val(vm, "join", Value::Func(join::data().into()));
|
||||
for (key, value) in [("root", root), ("dir", dir), ("name", name)] {
|
||||
if let Some(value) = value {
|
||||
define_val(vm, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let mut expr = typst::syntax::parse_code(code);
|
||||
let span = Span::from_range(id, 0..code.len());
|
||||
expr.synthesize(span);
|
||||
|
||||
let expr = match expr.cast::<ast::Code>() {
|
||||
Some(v) => v,
|
||||
None => bail!(
|
||||
"code is not a valid code expression: kind={:?}",
|
||||
expr.kind()
|
||||
),
|
||||
};
|
||||
match expr.eval(vm) {
|
||||
Ok(value) => serde_json::to_value(value).context_ut("failed to serialize path"),
|
||||
Err(e) => {
|
||||
let res =
|
||||
print_diagnostics_to_string(&world, e.iter(), DiagnosticFormat::Human);
|
||||
let err = res.unwrap_or_else(|e| e);
|
||||
bail!("failed to evaluate path expression: {err}")
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
PathPattern::new(code)
|
||||
.substitute(&entry)
|
||||
.context_ut("failed to substitute path pattern")
|
||||
.and_then(|path| {
|
||||
serde_json::to_value(path.deref()).context_ut("failed to serialize path")
|
||||
})
|
||||
};
|
||||
Some(path.into())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
struct EvalSysCtx {
|
||||
inputs: Arc<LazyHash<Dict>>,
|
||||
root: Value,
|
||||
dir: Option<Value>,
|
||||
name: Value,
|
||||
}
|
||||
|
||||
#[comemo::memoize]
|
||||
fn make_sys(entry: &EntryState, base: Arc<LazyHash<Dict>>, inputs: Dict) -> Option<EvalSysCtx> {
|
||||
let root = entry.root();
|
||||
let main = entry.main();
|
||||
|
||||
log::debug!("Check path {main:?} and root {root:?}");
|
||||
|
||||
let (root, main) = root.zip(main)?;
|
||||
|
||||
// Files in packages are not exported
|
||||
if WorkspaceResolver::is_package_file(main) {
|
||||
return None;
|
||||
}
|
||||
// Files without a path are not exported
|
||||
let path = main.vpath().resolve(&root)?;
|
||||
|
||||
// todo: handle untitled path
|
||||
if path.strip_prefix("/untitled").is_ok() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = path.strip_prefix(&root).ok()?;
|
||||
let dir = path.parent();
|
||||
let file_name = path.file_name().unwrap_or_default();
|
||||
|
||||
let root = Value::Str(root.to_string_lossy().into());
|
||||
|
||||
let dir = dir.map(|d| Value::Str(d.to_string_lossy().into()));
|
||||
|
||||
let name = file_name.to_string_lossy();
|
||||
let name = name.as_ref().strip_suffix(".typ").unwrap_or(name.as_ref());
|
||||
let name = Value::Str(name.into());
|
||||
|
||||
let mut dict = base.as_ref().deref().clone();
|
||||
for (key, value) in inputs {
|
||||
dict.insert(key, value);
|
||||
}
|
||||
dict.insert("root".into(), root.clone());
|
||||
if let Some(dir) = &dir {
|
||||
dict.insert("dir".into(), dir.clone());
|
||||
}
|
||||
dict.insert("name".into(), name.clone());
|
||||
|
||||
Some(EvalSysCtx {
|
||||
inputs: Arc::new(LazyHash::new(dict)),
|
||||
root,
|
||||
dir,
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
fn define_val(vm: &mut Vm, name: &str, value: Value) {
|
||||
let ident = SyntaxNode::leaf(SyntaxKind::Ident, name);
|
||||
vm.define(ident.cast::<ast::Ident>().unwrap(), value);
|
||||
}
|
||||
|
||||
#[typst_macros::func(title = "Join function")]
|
||||
fn join(args: &mut Args) -> SourceResult<Value> {
|
||||
let pos = args.take().to_pos();
|
||||
let mut res = PathBuf::new();
|
||||
for arg in pos {
|
||||
match arg {
|
||||
Value::Str(s) => res.push(s.as_str()),
|
||||
_ => {
|
||||
return Err(eco_format!("join argument is not a string: {arg:?}")).at(args.span);
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(Value::Str(res.to_string_lossy().into()))
|
||||
}
|
||||
|
||||
/// A result of a query.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum QueryResult<T> {
|
||||
/// A successful result.
|
||||
Success {
|
||||
/// The value of the result.
|
||||
value: T,
|
||||
},
|
||||
/// An error result.
|
||||
Error {
|
||||
/// The error message.
|
||||
error: EcoString,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> QueryResult<T> {
|
||||
/// Creates a successful result.
|
||||
pub fn success(value: T) -> Self {
|
||||
Self::Success { value }
|
||||
}
|
||||
|
||||
/// Creates an error result.
|
||||
pub fn error(error: EcoString) -> Self {
|
||||
Self::Error { error }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error> From<Result<T, E>> for QueryResult<T> {
|
||||
fn from(value: Result<T, E>) -> Self {
|
||||
match value {
|
||||
Ok(value) => QueryResult::success(value),
|
||||
Err(error) => QueryResult::error(eco_format!("{error}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use typst::foundations::dict;
|
||||
|
||||
use super::*;
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
snapshot_testing("code_context_path_at", &|ctx, path| {
|
||||
let patterns = [
|
||||
"$root/$dir/$name",
|
||||
"$root/$name",
|
||||
"$root/assets",
|
||||
"$root/assets/$name",
|
||||
r#"{ join(root, "x", dir, "y", name) }"#,
|
||||
r#"{ join(root, 1) }"#,
|
||||
r#"{ join(roo, 1) }"#,
|
||||
];
|
||||
let inp = [
|
||||
dict! {
|
||||
"x-path-context" => "vscode-paste",
|
||||
"x-path-input-uri" => "https://huh.io/img.png",
|
||||
"x-path-input-name" => "img.png",
|
||||
},
|
||||
dict! {
|
||||
"x-path-context" => "vscode-paste",
|
||||
"x-path-input-uri" => "https://huh.io/text.md",
|
||||
"x-path-input-name" => "text.md",
|
||||
},
|
||||
];
|
||||
|
||||
let cases = patterns
|
||||
.iter()
|
||||
.map(|pat| (*pat, inp[0].clone()))
|
||||
.chain(inp.iter().map(|inp| {
|
||||
(
|
||||
r#"{ import "/resolve.typ": resolve; resolve(join, root, dir, name) }"#,
|
||||
inp.clone(),
|
||||
)
|
||||
}));
|
||||
|
||||
let result = cases
|
||||
.map(|(code, inputs)| {
|
||||
let request = InteractCodeContextRequest {
|
||||
path: path.clone(),
|
||||
query: vec![Some(InteractCodeContextQuery::PathAt {
|
||||
code: code.to_string(),
|
||||
inputs: inputs.clone(),
|
||||
})],
|
||||
};
|
||||
json!({ "code": code, "inputs": inputs, "response": request.request(ctx) })
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
/// path: resolve.typ
|
||||
#let resolve(join, root, dir, name) = {
|
||||
let asset-dir = "assets"
|
||||
if sys.inputs.x-path-input-uri.ends-with(".png") {
|
||||
return (
|
||||
file: join(root, "images", sys.inputs.x-path-input-name),
|
||||
on-conflict: ```typc
|
||||
import "/resolve.typ": on-conflict; on-conflict(join, root, dir, name)
|
||||
```.text,
|
||||
)
|
||||
}
|
||||
|
||||
join(root, "assets", name)
|
||||
};
|
||||
-----
|
||||
/// path: x_at_root.typ
|
||||
|
|
@ -0,0 +1 @@
|
|||
/// path: the_dir/x_in_dir.typ
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_context.rs
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_context_path_at/at_root.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"code": "$root/$dir/$name",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "x_at_root"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "$root/$name",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "x_at_root"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "$root/assets",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "assets"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "$root/assets/$name",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "assets/x_at_root"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ join(root, \"x\", dir, \"y\", name) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "x/y/x_at_root"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ join(root, 1) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"error": "crates/tinymist-query/src/code_context.rs:275:21: failed to evaluate path expression: error: join argument is not a string: 1\n ┌─ /__redacted_path__.typ:1:0\n │\n1 │ { join(root, 1) }\n │ ^^^^^^^^^^^^^^^^^\n\n",
|
||||
"kind": "pathAt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ join(roo, 1) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"error": "crates/tinymist-query/src/code_context.rs:275:21: failed to evaluate path expression: error: unknown variable: roo\n ┌─ /__redacted_path__.typ:1:0\n │\n1 │ { join(roo, 1) }\n │ ^^^^^^^^^^^^^^^^\n\n",
|
||||
"kind": "pathAt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ import \"/resolve.typ\": resolve; resolve(join, root, dir, name) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": {
|
||||
"file": "images/img.png",
|
||||
"on-conflict": "import \"/resolve.typ\": on-conflict; on-conflict(join, root, dir, name)"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ import \"/resolve.typ\": resolve; resolve(join, root, dir, name) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "text.md",
|
||||
"x-path-input-uri": "https://huh.io/text.md"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "assets/x_at_root"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_context.rs
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_context_path_at/in_dir.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"code": "$root/$dir/$name",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "the_dir/x_in_dir"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "$root/$name",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "x_in_dir"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "$root/assets",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "assets"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "$root/assets/$name",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "assets/x_in_dir"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ join(root, \"x\", dir, \"y\", name) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"kind": "pathAt",
|
||||
"value": "x/the_dir/y/x_in_dir"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ join(root, 1) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"error": "crates/tinymist-query/src/code_context.rs:275:21: failed to evaluate path expression: error: join argument is not a string: 1\n ┌─ /__redacted_path__.typ:1:0\n │\n1 │ { join(root, 1) }\n │ ^^^^^^^^^^^^^^^^^\n\n",
|
||||
"kind": "pathAt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ join(roo, 1) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"error": "crates/tinymist-query/src/code_context.rs:275:21: failed to evaluate path expression: error: unknown variable: roo\n ┌─ /__redacted_path__.typ:1:0\n │\n1 │ { join(roo, 1) }\n │ ^^^^^^^^^^^^^^^^\n\n",
|
||||
"kind": "pathAt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ import \"/resolve.typ\": resolve; resolve(join, root, dir, name) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "img.png",
|
||||
"x-path-input-uri": "https://huh.io/img.png"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"error": "crates/tinymist-query/src/code_context.rs:275:21: failed to evaluate path expression: error: file not found (searched at /__redacted_path__.typ)\n ┌─ /__redacted_path__.typ:1:0\n │\n1 │ { import \"/resolve.typ\": resolve; resolve(join, root, dir, name) }\n │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n",
|
||||
"kind": "pathAt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "{ import \"/resolve.typ\": resolve; resolve(join, root, dir, name) }",
|
||||
"inputs": {
|
||||
"x-path-context": "vscode-paste",
|
||||
"x-path-input-name": "text.md",
|
||||
"x-path-input-uri": "https://huh.io/text.md"
|
||||
},
|
||||
"response": [
|
||||
{
|
||||
"error": "crates/tinymist-query/src/code_context.rs:275:21: failed to evaluate path expression: error: file not found (searched at /__redacted_path__.typ)\n ┌─ /__redacted_path__.typ:1:0\n │\n1 │ { import \"/resolve.typ\": resolve; resolve(join, root, dir, name) }\n │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n",
|
||||
"kind": "pathAt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -191,7 +191,7 @@ mod tests {
|
|||
let mut result = result.map(|v| {
|
||||
v.into_iter()
|
||||
.map(|loc| {
|
||||
let fp = file_path(loc.uri.as_str());
|
||||
let fp = file_uri(loc.uri.as_str());
|
||||
format!(
|
||||
"{fp}@{}:{}:{}:{}",
|
||||
loc.range.start.line,
|
||||
|
|
|
|||
|
|
@ -329,6 +329,7 @@ pub static REDACT_LOC: LazyLock<RedactFields> = LazyLock::new(|| {
|
|||
RedactFields::from_iter([
|
||||
"location",
|
||||
"contents",
|
||||
"file",
|
||||
"uri",
|
||||
"oldUri",
|
||||
"newUri",
|
||||
|
|
@ -419,6 +420,30 @@ impl Redact for RedactFields {
|
|||
for (_, val) in map.iter_mut() {
|
||||
*val = self.redact(val.clone());
|
||||
}
|
||||
|
||||
if let Some(kind) = map.get("kind")
|
||||
&& matches!(kind.as_str(), Some("pathAt"))
|
||||
{
|
||||
if let Some(value) = map.get("value")
|
||||
&& let Value::String(s) = value
|
||||
{
|
||||
let v = file_path_(Path::new(s)).into();
|
||||
map.insert("value".to_owned(), v);
|
||||
}
|
||||
|
||||
if let Some(error) = map.get("error") {
|
||||
let error = error.as_str().unwrap();
|
||||
static REG: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r#"(/dummy-root/|C:\\dummy-root\\).*?\.typ"#).unwrap()
|
||||
});
|
||||
let error = REG.replace_all(error, "/__redacted_path__.typ").replace(
|
||||
"crates\\tinymist-query\\src\\code_context.rs",
|
||||
"crates/tinymist-query/src/code_context.rs",
|
||||
);
|
||||
map.insert("error".to_owned(), Value::String(error));
|
||||
}
|
||||
}
|
||||
|
||||
for key in self.0.iter().copied() {
|
||||
let Some(t) = map.remove(key) else {
|
||||
continue;
|
||||
|
|
@ -430,12 +455,18 @@ impl Redact for RedactFields {
|
|||
map.insert(
|
||||
key.to_owned(),
|
||||
Value::Object(
|
||||
obj.iter().map(|(k, v)| (file_path(k), v.clone())).collect(),
|
||||
obj.iter().map(|(k, v)| (file_uri(k), v.clone())).collect(),
|
||||
),
|
||||
);
|
||||
}
|
||||
"file" => {
|
||||
map.insert(
|
||||
key.to_owned(),
|
||||
file_path_(Path::new(t.as_str().unwrap())).into(),
|
||||
);
|
||||
}
|
||||
"uri" | "target" | "oldUri" | "newUri" | "targetUri" => {
|
||||
map.insert(key.to_owned(), file_path(t.as_str().unwrap()).into());
|
||||
map.insert(key.to_owned(), file_uri(t.as_str().unwrap()).into());
|
||||
}
|
||||
"range"
|
||||
| "selectionRange"
|
||||
|
|
@ -465,20 +496,24 @@ impl Redact for RedactFields {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn file_path(uri: &str) -> String {
|
||||
file_path_(&lsp_types::Url::parse(uri).unwrap())
|
||||
pub(crate) fn file_uri(uri: &str) -> String {
|
||||
file_uri_(&lsp_types::Url::parse(uri).unwrap())
|
||||
}
|
||||
|
||||
pub(crate) fn file_path_(uri: &lsp_types::Url) -> String {
|
||||
pub(crate) fn file_uri_(uri: &lsp_types::Url) -> String {
|
||||
let uri = uri.to_file_path().unwrap();
|
||||
file_path_(&uri)
|
||||
}
|
||||
|
||||
pub(crate) fn file_path_(path: &Path) -> String {
|
||||
let root = if cfg!(windows) {
|
||||
PathBuf::from("C:\\dummy-root")
|
||||
} else {
|
||||
PathBuf::from("/dummy-root")
|
||||
};
|
||||
let uri = uri.to_file_path().unwrap();
|
||||
let abs_path = Path::new(&uri).strip_prefix(root).map(|p| p.to_owned());
|
||||
let rel_path = abs_path
|
||||
.unwrap_or_else(|_| Path::new("-").join(Path::new(&uri).iter().next_back().unwrap()));
|
||||
let abs_path = path.strip_prefix(root).map(|p| p.to_owned());
|
||||
let rel_path =
|
||||
abs_path.unwrap_or_else(|_| Path::new("-").join(path.iter().next_back().unwrap()));
|
||||
|
||||
unix_slash(&rel_path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -395,6 +395,19 @@ impl<T, E: std::fmt::Display> WithContextUntyped<T> for Result<T, E> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> WithContextUntyped<T> for Option<T> {
|
||||
fn context_ut(self, loc: &'static str) -> Result<T> {
|
||||
self.ok_or_else(|| Error::new(loc, ErrKind::None, None))
|
||||
}
|
||||
|
||||
fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
|
||||
{
|
||||
self.ok_or_else(|| Error::new(loc, ErrKind::None, f()))
|
||||
}
|
||||
}
|
||||
|
||||
/// The error prelude.
|
||||
pub mod prelude {
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ impl PathPattern {
|
|||
|
||||
let w = root.to_string_lossy();
|
||||
let f = file_name.to_string_lossy();
|
||||
let f = f.as_ref().strip_suffix(".typ").unwrap_or(f.as_ref());
|
||||
|
||||
// replace all $root
|
||||
let mut path = self.0.replace("$root", &w);
|
||||
|
|
@ -184,7 +185,7 @@ impl PathPattern {
|
|||
let d = dir.to_string_lossy();
|
||||
path = path.replace("$dir", &d);
|
||||
}
|
||||
path = path.replace("$name", &f);
|
||||
path = path.replace("$name", f);
|
||||
|
||||
Some(Path::new(path.as_str()).clean().into())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue