ts_bridge/protocol/text_document/
document_highlight.rs1use anyhow::{Context, Result};
11use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams};
12use serde::Deserialize;
13use serde_json::{Value, json};
14
15use crate::protocol::{AdapterResult, RequestSpec};
16use crate::rpc::{Priority, Route};
17use crate::utils::{tsserver_range_from_value_lsp, uri_to_file_path};
18
19const CMD_DOCUMENT_HIGHLIGHTS: &str = "documentHighlights";
20
21#[derive(Debug, Deserialize)]
22struct HighlightContext {
23 file: String,
24}
25
26pub fn handle(params: DocumentHighlightParams) -> RequestSpec {
27 let text_document = params.text_document_position_params.text_document;
28 let position = params.text_document_position_params.position;
29 let uri_string = text_document.uri.to_string();
30 let file = uri_to_file_path(text_document.uri.as_str()).unwrap_or(uri_string);
31
32 let request = json!({
33 "command": CMD_DOCUMENT_HIGHLIGHTS,
34 "arguments": {
35 "file": file,
36 "line": position.line + 1,
37 "offset": position.character + 1,
38 }
39 });
40
41 let context = json!({ "file": file });
42
43 RequestSpec {
44 route: Route::Syntax,
45 payload: request,
46 priority: Priority::Normal,
47 on_response: Some(adapt_document_highlights),
48 response_context: Some(context),
49 }
50}
51
52fn adapt_document_highlights(payload: &Value, context: Option<&Value>) -> Result<AdapterResult> {
53 let ctx: HighlightContext = serde_json::from_value(
54 context
55 .cloned()
56 .context("missing documentHighlight context")?,
57 )?;
58 let items = payload
59 .get("body")
60 .context("tsserver documentHighlights missing body")?
61 .as_array()
62 .cloned()
63 .unwrap_or_default();
64
65 let mut highlights = Vec::new();
66 for item in items {
67 let file = item
68 .get("file")
69 .or_else(|| item.get("fileName"))
70 .and_then(|v| v.as_str())
71 .unwrap_or("");
72 if !file.is_empty() && file != ctx.file {
73 continue;
74 }
75 let spans = item
76 .get("highlightSpans")
77 .and_then(|value| value.as_array())
78 .cloned()
79 .unwrap_or_default();
80 for span in spans {
81 if let Some(range) = tsserver_range_from_value_lsp(&span) {
82 let kind = span
83 .get("kind")
84 .and_then(|value| value.as_str())
85 .and_then(highlight_kind_from_ts_kind);
86 highlights.push(DocumentHighlight { range, kind });
87 }
88 }
89 }
90
91 Ok(AdapterResult::ready(serde_json::to_value(highlights)?))
92}
93
94fn highlight_kind_from_ts_kind(kind: &str) -> Option<DocumentHighlightKind> {
95 match kind {
96 "writtenReference" => Some(DocumentHighlightKind::WRITE),
97 "definition" => Some(DocumentHighlightKind::WRITE),
98 "reference" => Some(DocumentHighlightKind::READ),
99 "none" => Some(DocumentHighlightKind::TEXT),
100 _ => None,
101 }
102}