Skip to main content

harness_lsp/
types.rs

1use async_trait::async_trait;
2use harness_core::{PermissionPolicy, ToolError};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::watch;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub enum LspOperation {
11    Hover,
12    Definition,
13    References,
14    DocumentSymbol,
15    WorkspaceSymbol,
16    Implementation,
17}
18
19impl LspOperation {
20    pub fn as_str(&self) -> &'static str {
21        match self {
22            Self::Hover => "hover",
23            Self::Definition => "definition",
24            Self::References => "references",
25            Self::DocumentSymbol => "documentSymbol",
26            Self::WorkspaceSymbol => "workspaceSymbol",
27            Self::Implementation => "implementation",
28        }
29    }
30}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
33pub struct Position1 {
34    pub line: u32,
35    pub character: u32,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct LspServerProfile {
40    pub language: String,
41    pub extensions: Vec<String>,
42    pub command: Vec<String>,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub root_patterns: Option<Vec<String>>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub initialization_options: Option<serde_json::Value>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct LspManifest {
51    pub servers: HashMap<String, LspServerProfile>,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum ServerState {
57    Starting,
58    Ready,
59    Crashed,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ServerHandle {
64    pub language: String,
65    pub root: String,
66    pub state: ServerState,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct LspLocation {
71    pub path: String,
72    pub line: u32,
73    pub character: u32,
74    pub preview: String,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct LspSymbolInfo {
79    pub name: String,
80    pub kind: String,
81    pub path: String,
82    pub line: u32,
83    pub character: u32,
84    #[serde(default, skip_serializing_if = "Option::is_none")]
85    pub container_name: Option<String>,
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub children: Option<Vec<LspSymbolInfo>>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct LspHoverResult {
92    pub contents: String,
93    pub is_markdown: bool,
94}
95
96/// Cancel signal: a tokio watch channel shared with the caller.
97/// When `*rx.borrow()` is true, the operation should abort.
98pub type CancelSignal = watch::Receiver<bool>;
99
100#[async_trait]
101pub trait LspClient: Send + Sync {
102    async fn ensure_server(
103        &self,
104        language: &str,
105        root: &str,
106        profile: &LspServerProfile,
107    ) -> Result<ServerHandle, String>;
108
109    async fn hover(
110        &self,
111        handle: &ServerHandle,
112        path: &str,
113        pos: Position1,
114        cancel: CancelSignal,
115    ) -> Result<Option<LspHoverResult>, String>;
116
117    async fn definition(
118        &self,
119        handle: &ServerHandle,
120        path: &str,
121        pos: Position1,
122        cancel: CancelSignal,
123    ) -> Result<Vec<LspLocation>, String>;
124
125    async fn references(
126        &self,
127        handle: &ServerHandle,
128        path: &str,
129        pos: Position1,
130        cancel: CancelSignal,
131    ) -> Result<Vec<LspLocation>, String>;
132
133    async fn document_symbol(
134        &self,
135        handle: &ServerHandle,
136        path: &str,
137        cancel: CancelSignal,
138    ) -> Result<Vec<LspSymbolInfo>, String>;
139
140    async fn workspace_symbol(
141        &self,
142        handle: &ServerHandle,
143        query: &str,
144        cancel: CancelSignal,
145    ) -> Result<Vec<LspSymbolInfo>, String>;
146
147    async fn implementation(
148        &self,
149        handle: &ServerHandle,
150        path: &str,
151        pos: Position1,
152        cancel: CancelSignal,
153    ) -> Result<Vec<LspLocation>, String>;
154
155    async fn close_session(&self);
156}
157
158#[derive(Clone)]
159pub struct LspPermissionPolicy {
160    pub inner: PermissionPolicy,
161    pub unsafe_allow_lsp_without_hook: bool,
162}
163
164impl LspPermissionPolicy {
165    pub fn new(inner: PermissionPolicy) -> Self {
166        Self {
167            inner,
168            unsafe_allow_lsp_without_hook: false,
169        }
170    }
171
172    pub fn with_unsafe_bypass(mut self, v: bool) -> Self {
173        self.unsafe_allow_lsp_without_hook = v;
174        self
175    }
176}
177
178impl std::fmt::Debug for LspPermissionPolicy {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        f.debug_struct("LspPermissionPolicy")
181            .field(
182                "unsafe_allow_lsp_without_hook",
183                &self.unsafe_allow_lsp_without_hook,
184            )
185            .field("inner", &self.inner)
186            .finish()
187    }
188}
189
190#[derive(Clone)]
191pub struct LspSessionConfig {
192    pub cwd: String,
193    pub permissions: LspPermissionPolicy,
194    pub client: Arc<dyn LspClient>,
195    pub manifest: Option<LspManifest>,
196    pub manifest_path: Option<String>,
197    pub default_head_limit: Option<usize>,
198    pub default_timeout_ms: Option<u64>,
199    pub session_backstop_ms: Option<u64>,
200    pub server_startup_max_wait_ms: Option<u64>,
201    pub max_hover_markdown_bytes: Option<usize>,
202    pub max_preview_line_length: Option<usize>,
203    /// Retry counter for `server_starting` exponential backoff. Callers
204    /// can share a Map across calls by holding the Arc themselves.
205    pub retry_counter: Arc<tokio::sync::Mutex<HashMap<String, u64>>>,
206}
207
208impl LspSessionConfig {
209    pub fn new(
210        cwd: impl Into<String>,
211        permissions: LspPermissionPolicy,
212        client: Arc<dyn LspClient>,
213    ) -> Self {
214        Self {
215            cwd: cwd.into(),
216            permissions,
217            client,
218            manifest: None,
219            manifest_path: None,
220            default_head_limit: None,
221            default_timeout_ms: None,
222            session_backstop_ms: None,
223            server_startup_max_wait_ms: None,
224            max_hover_markdown_bytes: None,
225            max_preview_line_length: None,
226            retry_counter: Arc::new(tokio::sync::Mutex::new(HashMap::new())),
227        }
228    }
229}
230
231impl std::fmt::Debug for LspSessionConfig {
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        f.debug_struct("LspSessionConfig")
234            .field("cwd", &self.cwd)
235            .field("permissions", &self.permissions)
236            .finish()
237    }
238}
239
240// ---- Result union ----
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct LspHoverOk {
244    pub output: String,
245    pub path: String,
246    pub line: u32,
247    pub character: u32,
248    pub contents: String,
249    pub is_markdown: bool,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct LspDefinitionOk {
254    pub output: String,
255    pub path: String,
256    pub line: u32,
257    pub character: u32,
258    pub locations: Vec<LspLocation>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct LspReferencesOk {
263    pub output: String,
264    pub path: String,
265    pub line: u32,
266    pub character: u32,
267    pub locations: Vec<LspLocation>,
268    pub total: usize,
269    pub truncated: bool,
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct LspDocumentSymbolOk {
274    pub output: String,
275    pub path: String,
276    pub symbols: Vec<LspSymbolInfo>,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct LspWorkspaceSymbolOk {
281    pub output: String,
282    pub query: String,
283    pub symbols: Vec<LspSymbolInfo>,
284    pub total: usize,
285    pub truncated: bool,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct LspImplementationOk {
290    pub output: String,
291    pub path: String,
292    pub line: u32,
293    pub character: u32,
294    pub locations: Vec<LspLocation>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct LspNoResults {
299    pub output: String,
300    pub operation: LspOperation,
301}
302
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct LspServerStarting {
305    pub output: String,
306    pub language: String,
307    pub retry_ms: u64,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct LspError {
312    pub error: ToolError,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(tag = "kind", rename_all = "snake_case")]
317pub enum LspResult {
318    #[serde(rename = "hover")]
319    Hover(LspHoverOk),
320    #[serde(rename = "definition")]
321    Definition(LspDefinitionOk),
322    #[serde(rename = "references")]
323    References(LspReferencesOk),
324    #[serde(rename = "documentSymbol")]
325    DocumentSymbol(LspDocumentSymbolOk),
326    #[serde(rename = "workspaceSymbol")]
327    WorkspaceSymbol(LspWorkspaceSymbolOk),
328    #[serde(rename = "implementation")]
329    Implementation(LspImplementationOk),
330    #[serde(rename = "no_results")]
331    NoResults(LspNoResults),
332    #[serde(rename = "server_starting")]
333    ServerStarting(LspServerStarting),
334    #[serde(rename = "error")]
335    Error(LspError),
336}
337
338impl From<LspError> for LspResult {
339    fn from(e: LspError) -> Self {
340        LspResult::Error(e)
341    }
342}