mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-04 02:08:17 +00:00
feat: add path conversion actions for absolute and relative paths (#1696)
* feat: add path conversion actions for absolute and relative paths in special function call * feat: implement matchers * docs: edit comment * fix: path on windows * feat: add a comment * dev: edit a bit * dev: use `resolved` * refactor: simplify path rewriting logic using `diff` * feat: add absolute path import fixture * fix: update path check for absolute paths to use `starts_with` to work with windows * feat: add path expression import fixture --------- Co-authored-by: Myriad-Dreamin <camiyoru@gmail.com>
This commit is contained in:
parent
11cfb08be5
commit
6118b346d6
15 changed files with 287 additions and 1 deletions
|
@ -1,7 +1,11 @@
|
|||
//! Provides code actions for the document.
|
||||
|
||||
use regex::Regex;
|
||||
use tinymist_analysis::syntax::{adjust_expr, node_ancestors, SyntaxClass};
|
||||
use tinymist_std::path::{diff, unix_slash};
|
||||
|
||||
use super::get_link_exprs_in;
|
||||
use crate::analysis::LinkTarget;
|
||||
use crate::prelude::*;
|
||||
use crate::syntax::{interpret_mode_at, InterpretMode};
|
||||
|
||||
|
@ -55,6 +59,7 @@ impl<'a> CodeActionWorker<'a> {
|
|||
|
||||
let mut heading_resolved = false;
|
||||
let mut equation_resolved = false;
|
||||
let mut path_resolved = false;
|
||||
|
||||
self.wrap_actions(node, range);
|
||||
|
||||
|
@ -70,6 +75,10 @@ impl<'a> CodeActionWorker<'a> {
|
|||
equation_resolved = true;
|
||||
self.equation_actions(node);
|
||||
}
|
||||
SyntaxKind::Str if !path_resolved => {
|
||||
path_resolved = true;
|
||||
self.path_actions(node, cursor);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -77,6 +86,100 @@ impl<'a> CodeActionWorker<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn path_actions(&mut self, node: &LinkedNode, cursor: usize) -> Option<()> {
|
||||
// We can only process the case where the import path is a string.
|
||||
if let Some(SyntaxClass::IncludePath(path_node) | SyntaxClass::ImportPath(path_node)) =
|
||||
classify_syntax(node.clone(), cursor)
|
||||
{
|
||||
let str_node = adjust_expr(path_node)?;
|
||||
let str_ast = str_node.cast::<ast::Str>()?;
|
||||
return self.path_rewrite(self.source.id(), &str_ast.get(), &str_node);
|
||||
}
|
||||
|
||||
let link_parent = node_ancestors(node)
|
||||
.find(|node| matches!(node.kind(), SyntaxKind::FuncCall))
|
||||
.unwrap_or(node);
|
||||
|
||||
// Actually there should be only one link left
|
||||
if let Some(link_info) = get_link_exprs_in(link_parent) {
|
||||
let objects = link_info.objects.into_iter();
|
||||
let object_under_node = objects.filter(|link| link.range.contains(&cursor));
|
||||
|
||||
let mut resolved = false;
|
||||
for link in object_under_node {
|
||||
if let LinkTarget::Path(id, path) = link.target {
|
||||
// todo: is there a link that is not a path string?
|
||||
resolved = self.path_rewrite(id, &path, node).is_some() || resolved;
|
||||
}
|
||||
}
|
||||
|
||||
return resolved.then_some(());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Rewrites absolute paths from/to relative paths.
|
||||
fn path_rewrite(&mut self, id: TypstFileId, path: &str, node: &LinkedNode) -> Option<()> {
|
||||
if !matches!(node.kind(), SyntaxKind::Str) {
|
||||
log::warn!("bad path node kind on code action: {:?}", node.kind());
|
||||
return None;
|
||||
}
|
||||
|
||||
let path = Path::new(path);
|
||||
|
||||
if path.starts_with("/") {
|
||||
// Convert absolute path to relative path
|
||||
let cur_path = id.vpath().as_rooted_path().parent().unwrap();
|
||||
let new_path = diff(path, cur_path)?;
|
||||
let edit = self.edit_str(node, unix_slash(&new_path))?;
|
||||
let action = CodeActionOrCommand::CodeAction(CodeAction {
|
||||
title: "Convert to relative path".to_string(),
|
||||
kind: Some(CodeActionKind::REFACTOR_REWRITE),
|
||||
edit: Some(edit),
|
||||
..CodeAction::default()
|
||||
});
|
||||
self.actions.push(action);
|
||||
} else {
|
||||
// Convert relative path to absolute path
|
||||
let mut new_path = id.vpath().as_rooted_path().parent().unwrap().to_path_buf();
|
||||
for i in path.components() {
|
||||
match i {
|
||||
std::path::Component::ParentDir => {
|
||||
new_path.pop().then_some(())?;
|
||||
}
|
||||
std::path::Component::Normal(name) => {
|
||||
new_path.push(name);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let edit = self.edit_str(node, unix_slash(&new_path))?;
|
||||
let action = CodeActionOrCommand::CodeAction(CodeAction {
|
||||
title: "Convert to absolute path".to_string(),
|
||||
kind: Some(CodeActionKind::REFACTOR_REWRITE),
|
||||
edit: Some(edit),
|
||||
..CodeAction::default()
|
||||
});
|
||||
self.actions.push(action);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn edit_str(&mut self, node: &LinkedNode, new_content: String) -> Option<WorkspaceEdit> {
|
||||
if !matches!(node.kind(), SyntaxKind::Str) {
|
||||
log::warn!("edit_str only works on string AST nodes: {:?}", node.kind());
|
||||
return None;
|
||||
}
|
||||
|
||||
self.local_edit(TextEdit {
|
||||
range: self.ctx.to_lsp_range(node.range(), &self.source),
|
||||
// todo: this is merely ocasionally correct, abusing string escape (`fmt::Debug`)
|
||||
new_text: format!("{new_content:?}"),
|
||||
})
|
||||
}
|
||||
|
||||
fn wrap_actions(&mut self, node: &LinkedNode, range: Range<usize>) -> Option<()> {
|
||||
if range.is_empty() {
|
||||
return None;
|
||||
|
|
|
@ -80,3 +80,29 @@ impl SemanticRequest for CodeActionRequest {
|
|||
(!worker.actions.is_empty()).then_some(worker.actions)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::*;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
snapshot_testing("code_action", &|ctx, path| {
|
||||
let source = ctx.source_by_path(&path).unwrap();
|
||||
|
||||
let request = CodeActionRequest {
|
||||
path: path.clone(),
|
||||
range: find_test_range(&source),
|
||||
};
|
||||
|
||||
let result = request.request(ctx);
|
||||
|
||||
insta::with_settings!({
|
||||
description => format!("Code Action on {})", make_range_annoation(&source)),
|
||||
}, {
|
||||
assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
/// path: base.typ
|
||||
-----
|
||||
#import "/base.typ"/* range -3..-3 */;
|
|
@ -0,0 +1,3 @@
|
|||
/// path: base.typ
|
||||
-----
|
||||
$#import "base.typ"/* range -3..-3 */;$
|
|
@ -0,0 +1,3 @@
|
|||
/// path: base.typ
|
||||
-----
|
||||
#import "/" + "base.typ"/* range -3..-3 */;
|
|
@ -0,0 +1,3 @@
|
|||
/// path: base.typ
|
||||
-----
|
||||
#import "base.typ"/* range -3..-3 */;
|
|
@ -0,0 +1,3 @@
|
|||
/// path: base.typ
|
||||
-----
|
||||
#import ("base.typ")/* range -3..-3 */;
|
|
@ -0,0 +1,4 @@
|
|||
/// path: base.json
|
||||
{}
|
||||
-----
|
||||
#json("base.json" /* range -4..-4 */);
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: "Code Action on t \"/base.t||yp\"/* rang)"
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_action/absolute_path_import.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"s1.typ": [
|
||||
{
|
||||
"newText": "\"base.typ\"",
|
||||
"range": "0:8:0:19"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "refactor.rewrite",
|
||||
"title": "Convert to relative path"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: "Code Action on rt \"base.t||yp\"/* rang)"
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_action/path_and_equation.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"s1.typ": [
|
||||
{
|
||||
"newText": "\"/base.typ\"",
|
||||
"range": "0:9:0:19"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "refactor.rewrite",
|
||||
"title": "Convert to absolute path"
|
||||
},
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"s1.typ": [
|
||||
{
|
||||
"newText": " ",
|
||||
"range": "0:1:0:1"
|
||||
},
|
||||
{
|
||||
"newText": " ",
|
||||
"range": "0:38:0:38"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "refactor.rewrite",
|
||||
"title": "Convert to block equation"
|
||||
},
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"s1.typ": [
|
||||
{
|
||||
"newText": "\n",
|
||||
"range": "0:1:0:1"
|
||||
},
|
||||
{
|
||||
"newText": "\n",
|
||||
"range": "0:38:0:38"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "refactor.rewrite",
|
||||
"title": "Convert to multiple-line block equation"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: "Code Action on + \"base.t||yp\"/* rang)"
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_action/path_expression_import.typ
|
||||
---
|
||||
null
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: "Code Action on rt \"base.t||yp\"/* rang)"
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_action/path_import.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"s1.typ": [
|
||||
{
|
||||
"newText": "\"/base.typ\"",
|
||||
"range": "0:8:0:18"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "refactor.rewrite",
|
||||
"title": "Convert to absolute path"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: "Code Action on (\"base.ty||p\")/* rang)"
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_action/path_import_paren.typ
|
||||
---
|
||||
null
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/code_action.rs
|
||||
description: "Code Action on n(\"base.js||on\" /* ran)"
|
||||
expression: "JsonRepr::new_redacted(result, &REDACT_LOC)"
|
||||
input_file: crates/tinymist-query/src/fixtures/code_action/path_json.typ
|
||||
---
|
||||
[
|
||||
{
|
||||
"edit": {
|
||||
"changes": {
|
||||
"s1.typ": [
|
||||
{
|
||||
"newText": "\"/base.json\"",
|
||||
"range": "0:6:0:17"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"kind": "refactor.rewrite",
|
||||
"title": "Convert to absolute path"
|
||||
}
|
||||
]
|
Loading…
Add table
Add a link
Reference in a new issue