Skip to main content

wdl_analysis/handlers/
code_lens.rs

1//! Handlers for code lens requests.
2//!
3//! This module implements the LSP `textDocument/codeLens` functionality for
4//! WDL files.
5//!
6//! See: [LSP Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens)
7
8use anyhow::Result;
9use anyhow::anyhow;
10use anyhow::bail;
11use line_index::TextSize;
12use lsp_types::CodeLens;
13use lsp_types::Command;
14use lsp_types::Range;
15use url::Url;
16use wdl_grammar::SyntaxNode;
17
18use crate::graph::DocumentGraph;
19use crate::graph::ParseState;
20use crate::handlers::common::position;
21
22/// A `sprocket run` command, to be run on an LSP client.
23#[derive(Debug)]
24pub struct RunCommand {
25    /// The source document URI.
26    pub source: String,
27    /// The target to run.
28    pub target: String,
29}
30
31impl From<RunCommand> for Command {
32    fn from(command: RunCommand) -> Self {
33        Command {
34            title: format!("Run '{}'", command.target),
35            command: "sprocket.run".to_string(),
36            arguments: Some(vec![command.source.into(), command.target.into()]),
37        }
38    }
39}
40
41/// Computes the [`CodeLens`]es for the given document, if applicable.
42///
43/// Implementation of [`textDocument/codeLens`]
44///
45/// [`textDocument/codeLens`]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeLens
46pub fn code_lens(graph: &DocumentGraph, document_uri: &Url) -> Result<Option<Vec<CodeLens>>> {
47    let index = graph
48        .get_index(document_uri)
49        .ok_or_else(|| anyhow!("document `{uri}` not found in graph", uri = document_uri))?;
50
51    let node = graph.get(index);
52    let (_, lines) = match node.parse_state() {
53        ParseState::Parsed { lines, root, .. } => {
54            (SyntaxNode::new_root(root.clone()), lines.clone())
55        }
56        _ => bail!("document `{uri}` has not been parsed", uri = document_uri),
57    };
58
59    let Some(analysis_doc) = node.document() else {
60        bail!("document analysis data not available for `{document_uri}`");
61    };
62
63    let mut lenses = Vec::new();
64    for target in analysis_doc.callables() {
65        if target.inputs().values().any(|i| i.required()) {
66            continue;
67        }
68
69        let start_offset = TextSize::from(target.name_span().start() as u32);
70        let end_offset = TextSize::from(target.name_span().end() as u32);
71
72        lenses.push(CodeLens {
73            range: Range {
74                start: position(&lines, start_offset)?,
75                end: position(&lines, end_offset)?,
76            },
77            command: Some(
78                RunCommand {
79                    source: document_uri.to_string(),
80                    target: target.name().to_string(),
81                }
82                .into(),
83            ),
84            data: None,
85        });
86    }
87
88    if lenses.is_empty() {
89        Ok(None)
90    } else {
91        Ok(Some(lenses))
92    }
93}