1mod completion;
6mod definition;
7mod diagnostics;
8mod document;
9mod hover;
10
11use dashmap::DashMap;
12use tower_lsp::jsonrpc::Result;
13use tower_lsp::lsp_types::*;
14use tower_lsp::{Client, LanguageServer};
15
16pub use document::Document;
17
18pub struct SolScriptLanguageServer {
20 client: Client,
22 documents: DashMap<Url, Document>,
24}
25
26impl SolScriptLanguageServer {
27 pub fn new(client: Client) -> Self {
28 Self {
29 client,
30 documents: DashMap::new(),
31 }
32 }
33
34 fn get_document(&self, uri: &Url) -> Option<dashmap::mapref::one::Ref<'_, Url, Document>> {
36 self.documents.get(uri)
37 }
38
39 async fn analyze_document(&self, uri: &Url) {
41 if let Some(doc) = self.documents.get(uri) {
42 let diagnostics = diagnostics::get_diagnostics(&doc);
43 self.client
44 .publish_diagnostics(uri.clone(), diagnostics, Some(doc.version))
45 .await;
46 }
47 }
48}
49
50#[tower_lsp::async_trait]
51impl LanguageServer for SolScriptLanguageServer {
52 async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
53 Ok(InitializeResult {
54 capabilities: ServerCapabilities {
55 text_document_sync: Some(TextDocumentSyncCapability::Kind(
56 TextDocumentSyncKind::FULL,
57 )),
58 completion_provider: Some(CompletionOptions {
59 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
60 resolve_provider: Some(false),
61 ..Default::default()
62 }),
63 hover_provider: Some(HoverProviderCapability::Simple(true)),
64 definition_provider: Some(OneOf::Left(true)),
65 document_formatting_provider: Some(OneOf::Left(true)),
66 ..Default::default()
67 },
68 server_info: Some(ServerInfo {
69 name: "solscript-lsp".to_string(),
70 version: Some(env!("CARGO_PKG_VERSION").to_string()),
71 }),
72 })
73 }
74
75 async fn initialized(&self, _: InitializedParams) {
76 self.client
77 .log_message(MessageType::INFO, "SolScript language server initialized")
78 .await;
79 }
80
81 async fn shutdown(&self) -> Result<()> {
82 Ok(())
83 }
84
85 async fn did_open(&self, params: DidOpenTextDocumentParams) {
86 let uri = params.text_document.uri;
87 let version = params.text_document.version;
88 let text = params.text_document.text;
89
90 let doc = Document::new(text, version);
91 self.documents.insert(uri.clone(), doc);
92 self.analyze_document(&uri).await;
93 }
94
95 async fn did_change(&self, params: DidChangeTextDocumentParams) {
96 let uri = params.text_document.uri;
97 let version = params.text_document.version;
98
99 if let Some(change) = params.content_changes.into_iter().last() {
100 if let Some(mut doc) = self.documents.get_mut(&uri) {
101 doc.update(change.text, version);
102 }
103 }
104
105 self.analyze_document(&uri).await;
106 }
107
108 async fn did_close(&self, params: DidCloseTextDocumentParams) {
109 self.documents.remove(¶ms.text_document.uri);
110 }
111
112 async fn did_save(&self, params: DidSaveTextDocumentParams) {
113 self.analyze_document(¶ms.text_document.uri).await;
114 }
115
116 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
117 let uri = ¶ms.text_document_position.text_document.uri;
118 let position = params.text_document_position.position;
119
120 if let Some(doc) = self.get_document(uri) {
121 let items = completion::get_completions(&doc, position);
122 return Ok(Some(CompletionResponse::Array(items)));
123 }
124
125 Ok(None)
126 }
127
128 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
129 let uri = ¶ms.text_document_position_params.text_document.uri;
130 let position = params.text_document_position_params.position;
131
132 if let Some(doc) = self.get_document(uri) {
133 return Ok(hover::get_hover(&doc, position));
134 }
135
136 Ok(None)
137 }
138
139 async fn goto_definition(
140 &self,
141 params: GotoDefinitionParams,
142 ) -> Result<Option<GotoDefinitionResponse>> {
143 let uri = ¶ms.text_document_position_params.text_document.uri;
144 let position = params.text_document_position_params.position;
145
146 if let Some(doc) = self.get_document(uri) {
147 if let Some(location) = definition::get_definition(&doc, position, uri) {
148 return Ok(Some(GotoDefinitionResponse::Scalar(location)));
149 }
150 }
151
152 Ok(None)
153 }
154
155 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
156 let uri = ¶ms.text_document.uri;
157
158 if let Some(doc) = self.get_document(uri) {
159 if let Ok(program) = solscript_parser::parse(&doc.text) {
161 let formatted = format_program(&program);
162 let edit = TextEdit {
163 range: Range {
164 start: Position::new(0, 0),
165 end: Position::new(u32::MAX, u32::MAX),
166 },
167 new_text: formatted,
168 };
169 return Ok(Some(vec![edit]));
170 }
171 }
172
173 Ok(None)
174 }
175}
176
177fn format_program(program: &solscript_ast::Program) -> String {
179 use solscript_ast::*;
180
181 let mut output = String::new();
182
183 for item in &program.items {
184 match item {
185 Item::Contract(c) => {
186 if c.is_abstract {
187 output.push_str("abstract ");
188 }
189 output.push_str("contract ");
190 output.push_str(&c.name.name);
191
192 if !c.bases.is_empty() {
193 output.push_str(" is ");
194 let bases: Vec<_> = c.bases.iter().map(|b| b.name().to_string()).collect();
195 output.push_str(&bases.join(", "));
196 }
197
198 output.push_str(" {\n");
199
200 for member in &c.members {
201 match member {
202 ContractMember::StateVar(v) => {
203 output.push_str(" ");
204 output.push_str(&format_type(&v.ty));
205 if let Some(vis) = &v.visibility {
206 output.push_str(&format!(" {}", format_visibility(vis)));
207 }
208 output.push_str(&format!(" {};\n", v.name.name));
209 }
210 ContractMember::Function(f) => {
211 output.push_str(&format_function(f, 1));
212 }
213 ContractMember::Constructor(c) => {
214 output.push_str(" constructor(");
215 let params: Vec<_> = c
216 .params
217 .iter()
218 .map(|p| format!("{} {}", format_type(&p.ty), p.name.name))
219 .collect();
220 output.push_str(¶ms.join(", "));
221 output.push_str(") {\n");
222 output.push_str(" }\n\n");
224 }
225 _ => {}
226 }
227 }
228
229 output.push_str("}\n\n");
230 }
231 Item::Event(e) => {
232 output.push_str(&format!("event {}(", e.name.name));
233 let params: Vec<_> = e
234 .params
235 .iter()
236 .map(|p| {
237 let indexed = if p.indexed { "indexed " } else { "" };
238 format!("{} {}{}", format_type(&p.ty), indexed, p.name.name)
239 })
240 .collect();
241 output.push_str(¶ms.join(", "));
242 output.push_str(");\n\n");
243 }
244 Item::Error(e) => {
245 output.push_str(&format!("error {}(", e.name.name));
246 let params: Vec<_> = e
247 .params
248 .iter()
249 .map(|p| format!("{} {}", format_type(&p.ty), p.name.name))
250 .collect();
251 output.push_str(¶ms.join(", "));
252 output.push_str(");\n\n");
253 }
254 Item::Interface(i) => {
255 output.push_str(&format!("interface {} {{\n", i.name.name));
256 for sig in &i.members {
257 output.push_str(&format_fn_sig(sig, 1));
258 }
259 output.push_str("}\n\n");
260 }
261 _ => {}
262 }
263 }
264
265 output
266}
267
268fn format_type(ty: &solscript_ast::TypeExpr) -> String {
269 ty.name().to_string()
270}
271
272fn format_visibility(vis: &solscript_ast::Visibility) -> &'static str {
273 match vis {
274 solscript_ast::Visibility::Public => "public",
275 solscript_ast::Visibility::Private => "private",
276 solscript_ast::Visibility::Internal => "internal",
277 solscript_ast::Visibility::External => "external",
278 }
279}
280
281fn format_fn_sig(sig: &solscript_ast::FnSig, indent: usize) -> String {
282 let ind = " ".repeat(indent);
283 let mut output = String::new();
284
285 output.push_str(&format!("{}function {}(", ind, sig.name.name));
286
287 let params: Vec<_> = sig
288 .params
289 .iter()
290 .map(|p| format!("{} {}", p.ty.name(), p.name.name))
291 .collect();
292 output.push_str(¶ms.join(", "));
293 output.push(')');
294
295 if let Some(vis) = &sig.visibility {
296 output.push_str(&format!(" {}", format_visibility(vis)));
297 }
298
299 for m in &sig.state_mutability {
300 match m {
301 solscript_ast::StateMutability::View => output.push_str(" view"),
302 solscript_ast::StateMutability::Pure => output.push_str(" pure"),
303 solscript_ast::StateMutability::Payable => output.push_str(" payable"),
304 }
305 }
306
307 if !sig.return_params.is_empty() {
308 output.push_str(" returns (");
309 let returns: Vec<_> = sig.return_params.iter().map(|p| p.ty.name()).collect();
310 output.push_str(&returns.join(", "));
311 output.push(')');
312 }
313
314 output.push_str(";\n");
315 output
316}
317
318fn format_function(f: &solscript_ast::FnDef, indent: usize) -> String {
319 let ind = " ".repeat(indent);
320 let mut output = String::new();
321
322 for attr in &f.attributes {
324 output.push_str(&format!("{}#[{}]\n", ind, attr.name.name));
325 }
326
327 output.push_str(&format!("{}function {}(", ind, f.name.name));
328
329 let params: Vec<_> = f
330 .params
331 .iter()
332 .map(|p| format!("{} {}", format_type(&p.ty), p.name.name))
333 .collect();
334 output.push_str(¶ms.join(", "));
335 output.push(')');
336
337 if let Some(vis) = &f.visibility {
339 output.push_str(&format!(" {}", format_visibility(vis)));
340 }
341
342 for m in &f.state_mutability {
344 match m {
345 solscript_ast::StateMutability::View => output.push_str(" view"),
346 solscript_ast::StateMutability::Pure => output.push_str(" pure"),
347 solscript_ast::StateMutability::Payable => output.push_str(" payable"),
348 }
349 }
350
351 if !f.return_params.is_empty() {
353 output.push_str(" returns (");
354 let returns: Vec<_> = f.return_params.iter().map(|p| format_type(&p.ty)).collect();
355 output.push_str(&returns.join(", "));
356 output.push(')');
357 }
358
359 if f.body.is_some() {
360 output.push_str(" {\n");
361 output.push_str(&format!("{} // ...\n", ind));
363 output.push_str(&format!("{}}}\n\n", ind));
364 } else {
365 output.push_str(";\n\n");
366 }
367
368 output
369}