1use std::path::PathBuf;
2
3use mimium_lang::interner::Symbol;
4
5pub mod semantic_token;
6
7use dashmap::DashMap;
8use log::debug;
9use mimium_lang::compiler::mirgen;
10use mimium_lang::interner::{ExprNodeId, TypeNodeId};
11use mimium_lang::plugin::Plugin;
12use mimium_lang::utils::error::ReportableError;
13use mimium_lang::{Config, ExecContext};
14use ropey::Rope;
15use semantic_token::{ImCompleteSemanticToken, LEGEND_TYPE, ParseResult, parse};
16use serde::{Deserialize, Serialize};
17use serde_json::Value;
18use tower_lsp::jsonrpc::Result;
19use tower_lsp::lsp_types::notification::Notification;
20use tower_lsp::lsp_types::*;
21use tower_lsp::{Client, LanguageServer, LspService, Server};
22type SrcUri = String;
23
24fn get_default_context(path: Option<PathBuf>, with_gui: bool, config: Config) -> ExecContext {
26 let plugins: Vec<Box<dyn Plugin>> = vec![];
27 let mut ctx = ExecContext::new(plugins.into_iter(), path, config);
28 ctx.add_system_plugin(mimium_symphonia::SamplerPlugin::default());
29 ctx.add_system_plugin(mimium_scheduler::get_default_scheduler_plugin());
30 if let Some(midi_plug) = mimium_midi::MidiPlugin::try_new() {
31 ctx.add_system_plugin(midi_plug);
32 } else {
33 log::warn!("Midi is not supported on this platform.")
34 }
35
36 if with_gui {
37 #[cfg(not(target_arch = "wasm32"))]
38 ctx.add_system_plugin(mimium_guitools::GuiToolPlugin::default());
39 }
40
41 ctx
42}
43struct MimiumCtx {
44 builtin_types: Vec<(Symbol, TypeNodeId)>,
45}
46impl MimiumCtx {
47 fn new() -> Self {
48 let mut execctx = get_default_context(None, true, Default::default());
49 execctx.prepare_compiler();
50 let builtin_types = execctx.get_compiler().unwrap().get_ext_typeinfos();
51 Self { builtin_types }
52 }
53}
54impl std::fmt::Display for MimiumCtx {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "mimium context")
57 }
58}
59impl std::fmt::Debug for MimiumCtx {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 write!(f, "mimium context")
62 }
63}
64
65#[derive(Debug)]
66struct Backend {
67 client: Client,
68 compiler_ctx: MimiumCtx,
69 ast_map: DashMap<SrcUri, ExprNodeId>,
70 document_map: DashMap<SrcUri, Rope>,
72 semantic_token_map: DashMap<SrcUri, Vec<ImCompleteSemanticToken>>,
73}
74
75#[tower_lsp::async_trait]
76impl LanguageServer for Backend {
77 async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
78 debug!("initialize: {params:#?}");
79 Ok(InitializeResult {
80 capabilities: ServerCapabilities {
81 text_document_sync: Some(TextDocumentSyncCapability::Options(
82 TextDocumentSyncOptions {
83 open_close: Some(true),
84 change: Some(TextDocumentSyncKind::FULL),
85 save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
86 include_text: Some(true),
87 })),
88 ..Default::default()
89 },
90 )),
91 semantic_tokens_provider: Some(
92 SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
93 SemanticTokensRegistrationOptions {
94 text_document_registration_options: TextDocumentRegistrationOptions {
95 document_selector: Some(vec![DocumentFilter {
96 language: Some("mimium".to_string()),
97 scheme: Some("file".to_string()),
98 pattern: None,
99 }]),
100 },
101 semantic_tokens_options: SemanticTokensOptions {
102 legend: SemanticTokensLegend {
103 token_types: LEGEND_TYPE.to_vec(),
104 token_modifiers: vec![],
105 },
106 full: Some(SemanticTokensFullOptions::Bool(true)),
107 range: Some(false),
108 work_done_progress_options: WorkDoneProgressOptions {
109 work_done_progress: None,
110 },
111 },
112
113 static_registration_options: StaticRegistrationOptions::default(),
114 },
115 ),
116 ),
117 workspace: Some(WorkspaceServerCapabilities {
118 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
119 supported: Some(true),
120 change_notifications: Some(OneOf::Left(true)),
121 }),
122 file_operations: None,
123 }),
124 ..ServerCapabilities::default()
125 },
126 server_info: None,
127 offset_encoding: None,
128 })
129 }
130
131 async fn initialized(&self, _: InitializedParams) {
132 self.client
133 .log_message(MessageType::INFO, "mimium-language-server initialized!")
134 .await;
135 }
136
137 async fn shutdown(&self) -> Result<()> {
138 Ok(())
139 }
140
141 async fn semantic_tokens_full(
142 &self,
143 params: SemanticTokensParams,
144 ) -> Result<Option<SemanticTokensResult>> {
145 let uri = params.text_document.uri.to_string();
146 let mut pre_line = 0;
147 let mut pre_start = 0;
148 let mut semantic_token = || -> Option<_> {
149 let rope = self.document_map.get(&uri)?;
150 let mut imcomplete_semantic_tokens = self.semantic_token_map.get_mut(&uri)?;
151 imcomplete_semantic_tokens.sort_by(|a, b| a.start.cmp(&b.start));
152
153 let semantic_tokens = imcomplete_semantic_tokens.iter().filter_map(|token| {
154 let line = rope.try_byte_to_line(token.start).ok()? as u32;
155 let first = rope.try_line_to_char(line as usize).ok()? as u32;
156 let start = rope.try_byte_to_char(token.start).ok()? as u32 - first;
157 let delta_line = line - pre_line;
158 let delta_start = if delta_line == 0 {
159 start - pre_start
160 } else {
161 start
162 };
163 let res = Some(SemanticToken {
164 delta_line,
165 delta_start,
166 length: token.length as u32,
167 token_type: token.token_type as u32,
168 token_modifiers_bitset: 0,
169 });
170 pre_line = line;
171 pre_start = start;
172 res
173 });
174 let res = semantic_tokens.collect::<Vec<_>>();
175 Some(SemanticTokensResult::Tokens(SemanticTokens {
177 result_id: None,
178 data: res,
179 }))
180 };
181 Ok(semantic_token())
182 }
183 async fn did_open(&self, params: DidOpenTextDocumentParams) {
184 debug!("file opened");
185 self.on_change(TextDocumentItem {
186 uri: params.text_document.uri,
187 text: params.text_document.text,
188 version: params.text_document.version,
189 language_id: "mimium".to_string(),
190 })
191 .await
192 }
193 async fn did_change(&self, params: DidChangeTextDocumentParams) {
194 self.on_change(TextDocumentItem {
195 text: params.content_changes[0].text.clone(),
196 uri: params.text_document.uri,
197 version: params.text_document.version,
198 language_id: "mimium".to_string(),
199 })
200 .await
201 }
202
203 async fn did_save(&self, params: DidSaveTextDocumentParams) {
204 dbg!(¶ms.text);
205 if let Some(text) = params.text {
206 let item = TextDocumentItem {
207 uri: params.text_document.uri,
208 text,
209 version: -1,
210 language_id: "mimium".to_string(),
211 };
212 self.on_change(item).await;
213 _ = self.client.semantic_tokens_refresh().await;
214 }
215 debug!("file saved!");
216 }
217 async fn did_close(&self, _: DidCloseTextDocumentParams) {
218 debug!("file closed!");
219 }
220}
221fn diagnostic_from_error(
222 error: Box<dyn ReportableError>,
223 url: Url,
224 rope: &Rope,
225) -> Option<Diagnostic> {
226 let severity = DiagnosticSeverity::ERROR;
227 let main_message = error.get_message();
228 let labels = error.get_labels();
229 let (mainloc, _mainmsg) = labels.first()?;
230
231 let span = &mainloc.span;
232 let start_position = offset_to_position(span.start, rope)?;
233 let end_position = offset_to_position(span.end, rope)?;
234 let related_informations = labels
235 .iter()
236 .filter_map(|(loc, msg)| {
237 let span = &loc.span;
238 let start_position = offset_to_position(span.start, rope)?;
239 let end_position = offset_to_position(span.end, rope)?;
240 let uri = if loc.path.to_string_lossy() != "" {
241 Url::from_file_path(loc.path.clone()).unwrap_or(url.clone())
242 } else {
243 url.clone()
244 };
245 Some(DiagnosticRelatedInformation {
246 location: Location {
247 uri,
248 range: Range::new(start_position, end_position),
249 },
250 message: msg.clone(),
251 })
252 })
253 .collect();
254 Some(Diagnostic::new(
255 Range::new(start_position, end_position),
256 Some(severity),
257 None,
258 None,
259 main_message.clone(),
260 Some(related_informations),
261 None,
262 ))
263}
264impl Backend {
265 fn compile(&self, src: &str, url: Url) -> Vec<Diagnostic> {
266 let rope = ropey::Rope::from_str(src);
267
268 let ParseResult {
269 ast,
270 errors,
271 semantic_tokens,
272 } = parse(src, url.as_str());
273 self.semantic_token_map
274 .insert(url.to_string(), semantic_tokens);
275 let errs = {
276 let ast = ast.wrap_to_staged_expr();
277 let (_, _, typeerrs) = mirgen::typecheck(ast, &self.compiler_ctx.builtin_types, None);
278 errors.into_iter().chain(typeerrs).collect::<Vec<_>>()
279 };
280
281 errs.into_iter()
282 .flat_map(|item| diagnostic_from_error(item, url.clone(), &rope))
283 .collect::<Vec<Diagnostic>>()
284 }
285 async fn on_change(&self, params: TextDocumentItem) {
286 debug!("{}", ¶ms.version);
287 let rope = ropey::Rope::from_str(¶ms.text);
288 self.document_map
289 .insert(params.uri.to_string(), rope.clone());
290 let diagnostics = self.compile(¶ms.text, params.uri.clone());
291 self.client
292 .publish_diagnostics(params.uri.clone(), diagnostics, Some(params.version))
293 .await;
294 }
295}
296
297pub async fn lib_main() {
298 env_logger::init();
299
300 let stdin = tokio::io::stdin();
301 let stdout = tokio::io::stdout();
302
303 let (service, socket) = LspService::build(|client| Backend {
304 client,
305 compiler_ctx: MimiumCtx::new(),
306 ast_map: DashMap::new(),
307 document_map: DashMap::new(),
308 semantic_token_map: DashMap::new(),
309 })
310 .finish();
311
312 Server::new(stdin, stdout, socket).serve(service).await;
313}
314
315fn offset_to_position(offset: usize, rope: &Rope) -> Option<Position> {
316 let line = rope.try_char_to_line(offset).ok()?;
317 let first_char_of_line = rope.try_line_to_char(line).ok()?;
318 let column = offset - first_char_of_line;
319 Some(Position::new(line as u32, column as u32))
320}
321
322fn position_to_offset(position: Position, rope: &Rope) -> Option<usize> {
323 let line_char_offset = rope.try_line_to_char(position.line as usize).ok()?;
324 let slice = rope.slice(0..line_char_offset + position.character as usize);
325 Some(slice.len_bytes())
326}