mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-25 05:22:52 +00:00
feat: add code action to wrap content blocks (#881)
This commit is contained in:
parent
3207f57a78
commit
94ea230645
1 changed files with 46 additions and 5 deletions
|
|
@ -3,7 +3,11 @@ use once_cell::sync::{Lazy, OnceCell};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use typst_shim::syntax::LinkedNodeExt;
|
use typst_shim::syntax::LinkedNodeExt;
|
||||||
|
|
||||||
use crate::{prelude::*, SemanticRequest};
|
use crate::{
|
||||||
|
prelude::*,
|
||||||
|
syntax::{interpret_mode_at, InterpretMode},
|
||||||
|
SemanticRequest,
|
||||||
|
};
|
||||||
|
|
||||||
/// The [`textDocument/codeAction`] request is sent from the client to the
|
/// The [`textDocument/codeAction`] request is sent from the client to the
|
||||||
/// server to compute commands for a given text document and range. These
|
/// server to compute commands for a given text document and range. These
|
||||||
|
|
@ -77,12 +81,10 @@ impl SemanticRequest for CodeActionRequest {
|
||||||
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
|
fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
|
||||||
let source = ctx.source_by_path(&self.path).ok()?;
|
let source = ctx.source_by_path(&self.path).ok()?;
|
||||||
let range = ctx.to_typst_range(self.range, &source)?;
|
let range = ctx.to_typst_range(self.range, &source)?;
|
||||||
let cursor = (range.start + 1).min(source.text().len());
|
|
||||||
// todo: don't ignore the range end
|
|
||||||
|
|
||||||
let root = LinkedNode::new(source.root());
|
let root = LinkedNode::new(source.root());
|
||||||
let mut worker = CodeActionWorker::new(ctx, source.clone());
|
let mut worker = CodeActionWorker::new(ctx, source.clone());
|
||||||
worker.work(root, cursor);
|
worker.work(root, range);
|
||||||
|
|
||||||
let res = worker.actions;
|
let res = worker.actions;
|
||||||
(!res.is_empty()).then_some(res)
|
(!res.is_empty()).then_some(res)
|
||||||
|
|
@ -112,6 +114,7 @@ impl<'a> CodeActionWorker<'a> {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
fn local_edits(&self, edits: Vec<TextEdit>) -> Option<WorkspaceEdit> {
|
fn local_edits(&self, edits: Vec<TextEdit>) -> Option<WorkspaceEdit> {
|
||||||
Some(WorkspaceEdit {
|
Some(WorkspaceEdit {
|
||||||
changes: Some(HashMap::from_iter([(self.local_url()?.clone(), edits)])),
|
changes: Some(HashMap::from_iter([(self.local_url()?.clone(), edits)])),
|
||||||
|
|
@ -119,10 +122,45 @@ impl<'a> CodeActionWorker<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
fn local_edit(&self, edit: TextEdit) -> Option<WorkspaceEdit> {
|
fn local_edit(&self, edit: TextEdit) -> Option<WorkspaceEdit> {
|
||||||
self.local_edits(vec![edit])
|
self.local_edits(vec![edit])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wrap_actions(&mut self, node: &LinkedNode, range: Range<usize>) -> Option<()> {
|
||||||
|
if range.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_mode = interpret_mode_at(Some(node));
|
||||||
|
if !matches!(start_mode, InterpretMode::Markup | InterpretMode::Math) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let edit = self.local_edits(vec![
|
||||||
|
TextEdit {
|
||||||
|
range: self
|
||||||
|
.ctx
|
||||||
|
.to_lsp_range(range.start..range.start, &self.current),
|
||||||
|
new_text: "#[".into(),
|
||||||
|
},
|
||||||
|
TextEdit {
|
||||||
|
range: self.ctx.to_lsp_range(range.end..range.end, &self.current),
|
||||||
|
new_text: "]".into(),
|
||||||
|
},
|
||||||
|
])?;
|
||||||
|
|
||||||
|
let action = CodeActionOrCommand::CodeAction(CodeAction {
|
||||||
|
title: "Wrap with content block".to_string(),
|
||||||
|
kind: Some(CodeActionKind::REFACTOR_REWRITE),
|
||||||
|
edit: Some(edit),
|
||||||
|
..CodeAction::default()
|
||||||
|
});
|
||||||
|
self.actions.push(action);
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
fn heading_actions(&mut self, node: &LinkedNode) -> Option<()> {
|
fn heading_actions(&mut self, node: &LinkedNode) -> Option<()> {
|
||||||
let h = node.cast::<ast::Heading>()?;
|
let h = node.cast::<ast::Heading>()?;
|
||||||
let depth = h.depth().get();
|
let depth = h.depth().get();
|
||||||
|
|
@ -269,13 +307,16 @@ impl<'a> CodeActionWorker<'a> {
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn work(&mut self, root: LinkedNode, cursor: usize) -> Option<()> {
|
fn work(&mut self, root: LinkedNode, range: Range<usize>) -> Option<()> {
|
||||||
|
let cursor = (range.start + 1).min(self.current.text().len());
|
||||||
let node = root.leaf_at_compat(cursor)?;
|
let node = root.leaf_at_compat(cursor)?;
|
||||||
let mut node = &node;
|
let mut node = &node;
|
||||||
|
|
||||||
let mut heading_resolved = false;
|
let mut heading_resolved = false;
|
||||||
let mut equation_resolved = false;
|
let mut equation_resolved = false;
|
||||||
|
|
||||||
|
self.wrap_actions(node, range);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match node.kind() {
|
match node.kind() {
|
||||||
// Only the deepest heading is considered
|
// Only the deepest heading is considered
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue