tinymist_query/
code_action.rs

1use lsp_types::CodeActionContext;
2
3use crate::{SemanticRequest, analysis::CodeActionWorker, prelude::*};
4
5pub(crate) mod proto;
6pub use proto::*;
7
8/// The [`textDocument/codeAction`] request is sent from the client to the
9/// server to compute commands for a given text document and range. These
10/// commands are typically code fixes to either fix problems or to
11/// beautify/refactor code.
12///
13/// The result of a [`textDocument/codeAction`] request is an array of `Command`
14/// literals which are typically presented in the user interface.
15///
16/// [`textDocument/codeAction`]: https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction
17///
18/// To ensure that a server is useful in many clients, the commands specified in
19/// a code actions should be handled by the server and not by the client (see
20/// [`workspace/executeCommand`] and
21/// `ServerCapabilities::execute_command_provider`). If the client supports
22/// providing edits with a code action, then the mode should be used.
23///
24/// When the command is selected the server should be contacted again (via the
25/// [`workspace/executeCommand`] request) to execute the command.
26///
27/// [`workspace/executeCommand`]: https://microsoft.github.io/language-server-protocol/specification#workspace_executeCommand
28///
29/// # Compatibility
30///
31/// ## Since version 3.16.0
32///
33/// A client can offer a server to delay the computation of code action
34/// properties during a `textDocument/codeAction` request. This is useful for
35/// cases where it is expensive to compute the value of a property (for example,
36/// the `edit` property).
37///
38/// Clients signal this through the `code_action.resolve_support` client
39/// capability which lists all properties a client can resolve lazily. The
40/// server capability `code_action_provider.resolve_provider` signals that a
41/// server will offer a `codeAction/resolve` route.
42///
43/// To help servers uniquely identify a code action in the resolve request, a
44/// code action literal may optionally carry a `data` property. This is also
45/// guarded by an additional client capability `code_action.data_support`. In
46/// general, a client should offer data support if it offers resolve support.
47///
48/// It should also be noted that servers shouldn’t alter existing attributes of
49/// a code action in a `codeAction/resolve` request.
50///
51/// ## Since version 3.8.0
52///
53/// Support for [`CodeAction`] literals to enable the following scenarios:
54///
55/// * The ability to directly return a workspace edit from the code action
56///   request. This avoids having another server roundtrip to execute an actual
57///   code action. However server providers should be aware that if the code
58///   action is expensive to compute or the edits are huge it might still be
59///   beneficial if the result is simply a command and the actual edit is only
60///   computed when needed.
61///
62/// * The ability to group code actions using a kind. Clients are allowed to
63///   ignore that information. However it allows them to better group code
64///   action, for example, into corresponding menus (e.g. all refactor code
65///   actions into a refactor menu).
66#[derive(Debug, Clone)]
67pub struct CodeActionRequest {
68    /// The path of the document to request for.
69    pub path: PathBuf,
70    /// The range of the document to get code actions for.
71    pub range: LspRange,
72    /// The context of the code action request.
73    pub context: CodeActionContext,
74}
75
76impl SemanticRequest for CodeActionRequest {
77    type Response = Vec<CodeAction>;
78
79    fn request(self, ctx: &mut LocalContext) -> Option<Self::Response> {
80        log::trace!("requested code action: {self:?}");
81
82        let source = ctx.source_by_path(&self.path).ok()?;
83        let range = ctx.to_typst_range(self.range, &source)?;
84
85        let root = LinkedNode::new(source.root());
86        let mut worker = CodeActionWorker::new(ctx, source.clone());
87        worker.autofix(&root, &range, &self.context);
88        worker.scoped(&root, &range);
89
90        (!worker.actions.is_empty()).then_some(worker.actions)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use typst::{diag::Warned, layout::PagedDocument};
97
98    use super::*;
99    use crate::{DiagWorker, tests::*};
100
101    #[test]
102    fn test() {
103        snapshot_testing("code_action", &|ctx, path| {
104            let source = ctx.source_by_path(&path).unwrap();
105
106            let request_range = find_test_range(&source);
107            let code_action_ctx = compute_code_action_context(ctx, &source, &request_range);
108            let request = CodeActionRequest {
109                path: path.clone(),
110                range: request_range,
111                context: code_action_ctx,
112            };
113
114            let result = request.request(ctx);
115
116            with_settings!({
117                description => format!("Code Action on {}", make_range_annotation(&source)),
118            }, {
119                assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC));
120            })
121        });
122    }
123
124    fn compute_code_action_context(
125        ctx: &mut LocalContext,
126        source: &Source,
127        request_range: &LspRange,
128    ) -> CodeActionContext {
129        let Warned {
130            output,
131            warnings: compiler_warnings,
132        } = typst::compile::<PagedDocument>(&ctx.world);
133        let compiler_errors = output.err().unwrap_or_default();
134
135        let lint_warnings = ctx.lint(source);
136        let diagnostics = DiagWorker::new(ctx)
137            .convert_all(
138                compiler_errors
139                    .iter()
140                    .chain(compiler_warnings.iter())
141                    .chain(lint_warnings.iter()),
142            )
143            .into_values()
144            .flatten();
145
146        CodeActionContext {
147            // The filtering here matches the LSP specification and VS Code behavior;
148            // see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext:
149            //   `diagnostics`: An array of diagnostics known on the client side overlapping the range
150            //   provided to the textDocument/codeAction request [...]
151            diagnostics: diagnostics
152                .filter(|diag| ranges_overlap(&diag.range, request_range))
153                .collect(),
154            only: None,
155            trigger_kind: None,
156        }
157    }
158
159    fn ranges_overlap(r1: &LspRange, r2: &LspRange) -> bool {
160        !(r1.end <= r2.start || r2.end <= r1.start)
161    }
162}