1use lsp_types::{Position, Range};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct LspMessage {
15 pub jsonrpc: String,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub id: Option<serde_json::Value>,
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub method: Option<String>,
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub params: Option<serde_json::Value>,
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub result: Option<serde_json::Value>,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub error: Option<serde_json::Value>,
32}
33
34impl LspMessage {
35 #[must_use]
37 pub fn request(id: impl Into<serde_json::Value>, method: &str) -> Self {
38 Self {
39 jsonrpc: "2.0".to_string(),
40 id: Some(id.into()),
41 method: Some(method.to_string()),
42 params: None,
43 result: None,
44 error: None,
45 }
46 }
47
48 #[must_use]
50 pub fn notification(method: &str) -> Self {
51 Self {
52 jsonrpc: "2.0".to_string(),
53 id: None,
54 method: Some(method.to_string()),
55 params: None,
56 result: None,
57 error: None,
58 }
59 }
60
61 #[must_use]
63 pub fn is_request(&self) -> bool {
64 self.id.is_some() && self.method.is_some()
65 }
66
67 #[must_use]
69 pub fn is_response(&self) -> bool {
70 self.id.is_some() && (self.result.is_some() || self.error.is_some())
71 }
72
73 #[must_use]
75 pub fn is_notification(&self) -> bool {
76 self.id.is_none() && self.method.is_some()
77 }
78
79 #[must_use]
81 pub fn is_error(&self) -> bool {
82 self.error.is_some()
83 }
84
85 #[must_use]
87 pub fn with_params(mut self, params: serde_json::Value) -> Self {
88 self.params = Some(params);
89 self
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95pub struct CodeContext {
96 pub uri: String,
98 pub position: Position,
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub visible_range: Option<Range>,
103}
104
105impl CodeContext {
106 #[must_use]
108 pub fn new(uri: String, line: u32, character: u32) -> Self {
109 Self {
110 uri,
111 position: Position { line, character },
112 visible_range: None,
113 }
114 }
115
116 #[must_use]
118 pub fn with_visible_range(mut self, start: Position, end: Position) -> Self {
119 self.visible_range = Some(Range { start, end });
120 self
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use similar_asserts::assert_eq;
128
129 #[test]
130 fn test_lsp_message_serialization_roundtrip() {
131 let msg = LspMessage::request(1, "textDocument/completion")
132 .with_params(serde_json::json!({"textDocument": {"uri": "file:///test.rs"}}));
133
134 let json = serde_json::to_string(&msg).expect("serialize");
135 let deserialized: LspMessage = serde_json::from_str(&json).expect("deserialize");
136 assert_eq!(msg, deserialized);
137 }
138
139 #[test]
140 fn test_lsp_message_request() {
141 let msg = LspMessage::request(42, "test/method");
142
143 assert!(msg.is_request());
144 assert!(!msg.is_response());
145 assert!(!msg.is_notification());
146 assert_eq!(msg.jsonrpc, "2.0");
147 assert_eq!(msg.method, Some("test/method".to_string()));
148 }
149
150 #[test]
151 fn test_lsp_message_notification() {
152 let msg = LspMessage::notification("window/logMessage");
153
154 assert!(msg.is_notification());
155 assert!(!msg.is_request());
156 assert!(!msg.is_response());
157 assert!(msg.id.is_none());
158 }
159
160 #[test]
161 fn test_lsp_message_response() {
162 let msg = LspMessage {
163 jsonrpc: "2.0".to_string(),
164 id: Some(serde_json::json!(1)),
165 method: None,
166 params: None,
167 result: Some(serde_json::json!({"completions": []})),
168 error: None,
169 };
170
171 assert!(msg.is_response());
172 assert!(!msg.is_request());
173 assert!(!msg.is_error());
174 }
175
176 #[test]
177 fn test_lsp_message_error_response() {
178 let msg = LspMessage {
179 jsonrpc: "2.0".to_string(),
180 id: Some(serde_json::json!(1)),
181 method: None,
182 params: None,
183 result: None,
184 error: Some(serde_json::json!({"code": -32600, "message": "Invalid Request"})),
185 };
186
187 assert!(msg.is_response());
188 assert!(msg.is_error());
189 }
190
191 #[test]
192 fn test_lsp_message_skips_none_fields() {
193 let msg = LspMessage::notification("test");
194 let json = serde_json::to_string(&msg).expect("serialize");
195
196 assert!(!json.contains("\"id\""));
198 assert!(!json.contains("\"params\""));
199 assert!(!json.contains("\"result\""));
200 assert!(!json.contains("\"error\""));
201 }
202
203 #[test]
204 fn test_code_context_serialization_roundtrip() {
205 let ctx = CodeContext::new("file:///test.rs".to_string(), 10, 5);
206
207 let json = serde_json::to_string(&ctx).expect("serialize");
208 let deserialized: CodeContext = serde_json::from_str(&json).expect("deserialize");
209 assert_eq!(ctx, deserialized);
210 }
211
212 #[test]
213 fn test_code_context_with_visible_range() {
214 let ctx = CodeContext::new("file:///test.rs".to_string(), 10, 5).with_visible_range(
215 Position {
216 line: 0,
217 character: 0,
218 },
219 Position {
220 line: 50,
221 character: 0,
222 },
223 );
224
225 assert!(ctx.visible_range.is_some());
226 let range = ctx.visible_range.unwrap();
227 assert_eq!(range.start.line, 0);
228 assert_eq!(range.end.line, 50);
229 }
230
231 #[test]
232 fn test_code_context_skips_none_visible_range() {
233 let ctx = CodeContext::new("file:///test.rs".to_string(), 10, 5);
234 let json = serde_json::to_string(&ctx).expect("serialize");
235
236 assert!(!json.contains("visible_range"));
237 }
238}