1use std::collections::{BTreeMap, HashSet};
2use std::fmt;
3use std::path::{Path, PathBuf};
4use std::sync::Arc;
5
6use anyhow::{Context as _, Result};
7use lsp::Url;
8use ropey::Rope;
9use tokio::sync::Notify;
10
11use crate::alloc::prelude::*;
12use crate::alloc::{self, HashMap, String, Vec};
13use crate::ast::{Span, Spanned};
14use crate::compile::meta;
15use crate::compile::{
16 self, CompileVisitor, LinkerError, Located, Location, MetaError, MetaRef, SourceMeta, WithSpan,
17};
18use crate::diagnostics::{Diagnostic, FatalDiagnosticKind};
19use crate::doc::VisitorData;
20use crate::item::ComponentRef;
21use crate::languageserver::connection::Output;
22use crate::languageserver::Language;
23use crate::workspace::{self, WorkspaceError};
24use crate::{self as rune, Diagnostics};
25use crate::{BuildError, Context, Hash, Item, Options, Source, SourceId, Sources, Unit};
26
27#[derive(Default)]
28struct Reporter {
29 by_url: BTreeMap<Url, Vec<lsp::Diagnostic>>,
30}
31
32impl Reporter {
33 fn ensure(&mut self, url: &Url) {
35 if !self.by_url.contains_key(url) {
36 self.by_url.insert(url.clone(), Vec::new());
37 }
38 }
39
40 fn entry(&mut self, url: &Url) -> &mut Vec<lsp::Diagnostic> {
42 self.by_url.entry(url.clone()).or_default()
43 }
44}
45
46struct Build {
47 id_to_url: HashMap<SourceId, Url>,
48 sources: Sources,
49 workspace: bool,
51}
52
53impl Build {
54 pub(super) fn from_workspace() -> Self {
55 Self {
56 id_to_url: HashMap::new(),
57 sources: Sources::default(),
58 workspace: true,
59 }
60 }
61
62 pub(super) fn from_file() -> Self {
63 Self {
64 id_to_url: HashMap::new(),
65 sources: Sources::default(),
66 workspace: false,
67 }
68 }
69
70 pub(super) fn populate(&mut self, reporter: &mut Reporter) -> Result<()> {
71 for id in self.sources.source_ids() {
72 let Some(source) = self.sources.get(id) else {
73 continue;
74 };
75
76 let Some(path) = source.path() else {
77 continue;
78 };
79
80 let Ok(url) = crate::languageserver::url::from_file_path(path) else {
81 continue;
82 };
83
84 reporter.ensure(&url);
85 self.id_to_url.try_insert(id, url)?;
86 }
87
88 Ok(())
89 }
90
91 pub(super) fn visit(&mut self, visited: &mut HashSet<Url>) {
92 for id in self.sources.source_ids() {
93 let Some(source) = self.sources.get(id) else {
94 continue;
95 };
96
97 let Some(path) = source.path() else {
98 continue;
99 };
100
101 let Ok(url) = crate::languageserver::url::from_file_path(path) else {
102 continue;
103 };
104
105 visited.insert(url.clone());
106 }
107 }
108}
109
110pub(super) enum StateEncoding {
111 Utf8,
112 Utf16,
113}
114
115impl StateEncoding {
116 pub(super) fn source_range(&self, source: &Source, span: Span) -> Result<lsp::Range> {
118 let start = self.source_position(source, span.start.into_usize())?;
119 let end = self.source_position(source, span.end.into_usize())?;
120 Ok(lsp::Range { start, end })
121 }
122
123 pub(super) fn source_position(&self, source: &Source, at: usize) -> Result<lsp::Position> {
125 let (l, c) = match self {
126 StateEncoding::Utf16 => source.pos_to_utf16cu_linecol(at),
127 StateEncoding::Utf8 => source.pos_to_utf8_linecol(at),
128 };
129
130 Ok(lsp::Position {
131 line: u32::try_from(l)?,
132 character: u32::try_from(c)?,
133 })
134 }
135
136 pub(super) fn rope_position(&self, rope: &Rope, pos: lsp::Position) -> Result<usize> {
137 fn rope_position_utf16(rope: &Rope, pos: lsp::Position) -> Result<usize> {
142 let line = usize::try_from(pos.line)?;
143 let character = usize::try_from(pos.character)?;
144 let line = rope.try_line_to_char(line)?;
145 Ok(rope.try_utf16_cu_to_char(line + character)?)
146 }
147
148 fn rope_position_utf8(rope: &Rope, pos: lsp::Position) -> Result<usize> {
149 let line = usize::try_from(pos.line)?;
150 let character = usize::try_from(pos.character)?;
151 Ok(rope.try_line_to_char(line)? + character)
152 }
153
154 match self {
155 StateEncoding::Utf16 => rope_position_utf16(rope, pos),
156 StateEncoding::Utf8 => rope_position_utf8(rope, pos),
157 }
158 }
159}
160
161impl fmt::Display for StateEncoding {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 StateEncoding::Utf8 => "utf-8".fmt(f),
165 StateEncoding::Utf16 => "utf-16".fmt(f),
166 }
167 }
168}
169
170pub(super) struct State<'a> {
172 pub(super) encoding: StateEncoding,
173 pub(super) output: Output,
175 rebuild_notify: &'a Notify,
178 context: crate::Context,
180 options: Options,
182 initialized: bool,
184 stopped: bool,
186 pub(super) workspace: Workspace,
188}
189
190impl<'a> State<'a> {
191 pub(super) fn new(
193 output: Output,
194 rebuild_notify: &'a Notify,
195 context: Context,
196 options: Options,
197 ) -> Self {
198 Self {
199 encoding: StateEncoding::Utf16,
200 output,
201 rebuild_notify,
202 context,
203 options,
204 initialized: bool::default(),
205 stopped: bool::default(),
206 workspace: Workspace::default(),
207 }
208 }
209
210 pub(super) fn initialize(&mut self) {
212 self.initialized = true;
213 }
214
215 pub(super) fn is_initialized(&self) -> bool {
217 self.initialized
218 }
219
220 pub(super) fn stop(&mut self) {
222 self.stopped = true;
223 }
224
225 pub(super) fn is_stopped(&self) -> bool {
227 self.stopped
228 }
229
230 pub(super) fn rebuild_interest(&self) {
234 self.rebuild_notify.notify_one();
235 }
236
237 pub(super) async fn goto_definition(
239 &self,
240 uri: &Url,
241 position: lsp::Position,
242 ) -> Result<Option<lsp::Location>> {
243 let Some(source) = self.workspace.get(uri) else {
244 return Ok(None);
245 };
246
247 let offset = self.encoding.rope_position(&source.content, position)?;
248
249 let Some(def) = source.find_definition_at(Span::point(offset)) else {
250 return Ok(None);
251 };
252
253 let url = match def.source.path() {
254 Some(path) => crate::languageserver::url::from_file_path(path)?,
255 None => uri.clone(),
256 };
257
258 let Some(source) = source
259 .build_sources
260 .as_ref()
261 .and_then(|s| s.get(def.source.source_id()))
262 else {
263 return Ok(None);
264 };
265
266 let range = self.encoding.source_range(source, def.source.span())?;
267
268 let location = lsp::Location { uri: url, range };
269
270 tracing::trace!("go to location: {:?}", location);
271 Ok(Some(location))
272 }
273
274 #[tracing::instrument(skip_all)]
276 pub(super) fn complete(
277 &self,
278 uri: &Url,
279 position: lsp::Position,
280 ) -> Result<Option<Vec<lsp::CompletionItem>>> {
281 let sources = &self.workspace.sources;
282 tracing::trace!(uri = ?uri, uri_exists = sources.get(uri).is_some());
283
284 let Some(workspace_source) = sources.get(uri) else {
285 return Ok(None);
286 };
287
288 let offset = self
289 .encoding
290 .rope_position(&workspace_source.content, position)?;
291
292 let Some((mut symbol, _start)) = workspace_source.looking_back(offset)? else {
293 return Ok(None);
294 };
295
296 tracing::trace!(?symbol, start = ?_start);
297
298 if symbol.is_empty() {
299 return Ok(None);
300 }
301
302 let mut results = Vec::new();
303
304 let can_use_instance_fn: &[_] = &['.'];
305 let first_char = symbol.remove(0);
306 let symbol = symbol.trim();
307
308 if let Some(unit) = workspace_source.unit.as_ref() {
309 super::completion::complete_for_unit(
310 workspace_source,
311 unit,
312 symbol,
313 position,
314 &mut results,
315 )?;
316 }
317
318 if first_char.is_ascii_alphabetic() || can_use_instance_fn.contains(&first_char) {
319 super::completion::complete_native_instance_data(
320 &self.context,
321 symbol,
322 position,
323 &mut results,
324 )?;
325 } else {
326 super::completion::complete_native_loose_data(
327 &self.context,
328 symbol,
329 position,
330 &mut results,
331 )?;
332 }
333
334 Ok(Some(results))
335 }
336
337 pub(super) fn format(&mut self, uri: &Url) -> Result<Option<lsp::TextEdit>> {
338 let sources = &mut self.workspace.sources;
339 tracing::trace!(uri = ?uri.try_to_string()?, uri_exists = sources.get(uri).is_some());
340
341 let Some(s) = sources.get_mut(uri) else {
342 return Ok(None);
343 };
344
345 let source = s.content.try_to_string()?;
346
347 let mut diagnostics = Diagnostics::new();
348
349 let Ok(formatted) = crate::fmt::layout_source_with(
350 &source,
351 SourceId::EMPTY,
352 &self.options,
353 &mut diagnostics,
354 ) else {
355 return Ok(None);
356 };
357
358 if source == formatted {
360 return Ok(None);
361 }
362
363 let edit = lsp::TextEdit::new(
364 lsp::Range::new(
366 lsp::Position::new(0, 0),
367 lsp::Position::new(u32::MAX, u32::MAX),
368 ),
369 formatted.into_std(),
370 );
371
372 Ok(Some(edit))
373 }
374
375 pub(super) fn range_format(
376 &mut self,
377 uri: &Url,
378 range: &lsp::Range,
379 ) -> Result<Option<lsp::TextEdit>> {
380 let sources = &mut self.workspace.sources;
381 tracing::trace!(uri = ?uri.try_to_string()?, uri_exists = sources.get(uri).is_some());
382
383 let Some(s) = sources.get_mut(uri) else {
384 return Ok(None);
385 };
386
387 let start = self.encoding.rope_position(&s.content, range.start)?;
388 let end = self.encoding.rope_position(&s.content, range.end)?;
389
390 let Some(source) = s.content.get_slice(start..end) else {
391 return Ok(None);
392 };
393
394 let source = source.try_to_string()?;
395
396 let mut options = self.options.clone();
397 options.fmt.force_newline = false;
398
399 let mut diagnostics = Diagnostics::new();
400
401 let Ok(formatted) =
402 crate::fmt::layout_source_with(&source, SourceId::EMPTY, &options, &mut diagnostics)
403 else {
404 return Ok(None);
405 };
406
407 if source == formatted {
409 return Ok(None);
410 }
411
412 let edit = lsp::TextEdit::new(*range, formatted.into_std());
413 Ok(Some(edit))
414 }
415
416 pub(super) async fn rebuild(&mut self) -> Result<()> {
418 let mut visited = HashSet::new();
420 let mut workspace_results = Vec::new();
422 let mut script_results = Vec::new();
424 let mut reporter = Reporter::default();
426
427 if let Some((workspace_url, workspace_path)) = &self.workspace.manifest_path {
428 let mut diagnostics = workspace::Diagnostics::default();
429 let mut build = Build::from_workspace();
430
431 let result = self.load_workspace(
432 workspace_url,
433 workspace_path,
434 &mut build,
435 &mut diagnostics,
436 &self.workspace,
437 );
438
439 match result {
440 Err(error) => {
441 tracing::error!("error loading workspace: {error}");
442
443 for _error in error.chain().skip(1) {
444 tracing::error!("caused by: {_error}");
445 }
446 }
447 Ok(script_builds) => {
448 for script_build in script_builds {
449 script_results
450 .try_push(self.build_scripts(script_build, Some(&mut visited))?)?;
451 }
452 }
453 };
454
455 workspace_results.try_push((diagnostics, build))?;
456 }
457
458 for (url, source) in &self.workspace.sources {
459 if visited.contains(url) {
460 tracing::trace!(url = ?url.try_to_string()?, "already populated by workspace");
461 continue;
462 }
463
464 if !matches!(source.language, Language::Rune) {
465 continue;
466 }
467
468 tracing::trace!(url = ?url.try_to_string()?, "build plain source");
469
470 let mut build = Build::from_file();
471
472 let input = match url.to_file_path() {
473 Ok(path) => Source::with_path(url, source.try_to_string()?, path)?,
474 Err(..) => Source::new(url, source.try_to_string()?)?,
475 };
476
477 build.sources.insert(input)?;
478 script_results.try_push(self.build_scripts(build, None)?)?;
479 }
480
481 for url in self.workspace.removed.drain(..) {
484 reporter.ensure(&url);
485 }
486
487 for (diagnostics, mut build) in workspace_results {
488 build.populate(&mut reporter)?;
489 self.emit_workspace(diagnostics, &build, &mut reporter)?;
490 }
491
492 for (diagnostics, mut build, source_visitor, doc_visitor, unit) in script_results {
493 build.populate(&mut reporter)?;
494 self.emit_scripts(diagnostics, &build, &mut reporter)?;
495
496 let sources = Arc::new(build.sources);
497 let doc_visitor = Arc::new(doc_visitor);
498
499 for (source_id, value) in source_visitor.into_indexes() {
500 let Some(url) = build.id_to_url.get(&source_id) else {
501 continue;
502 };
503
504 let Some(source) = self.workspace.sources.get_mut(url) else {
505 continue;
506 };
507
508 source.index = value;
509 source.build_sources = Some(sources.clone());
510
511 if let Ok(unit) = &unit {
512 source.unit = Some(unit.try_clone()?);
513 }
514
515 source.docs = Some(doc_visitor.clone());
516 }
517 }
518
519 for (url, diagnostics) in reporter.by_url {
520 tracing::info!(
521 url = ?url.try_to_string()?,
522 diagnostics = diagnostics.len(),
523 "publishing diagnostics"
524 );
525
526 let diagnostics = lsp::PublishDiagnosticsParams {
527 uri: url.clone(),
528 diagnostics: diagnostics.into_std(),
529 version: None,
530 };
531
532 self.output
533 .notification::<lsp::notification::PublishDiagnostics>(diagnostics)
534 .await?;
535 }
536
537 Ok(())
538 }
539
540 fn load_workspace(
542 &self,
543 url: &Url,
544 path: &Path,
545 manifest_build: &mut Build,
546 diagnostics: &mut workspace::Diagnostics,
547 workspace: &Workspace,
548 ) -> Result<Vec<Build>, anyhow::Error> {
549 tracing::info!(url = ?url.try_to_string(), "building workspace");
550
551 let source = match workspace.sources.get(url) {
552 Some(source) => source.chunks().try_collect::<String>()?,
553 None => match std::fs::read_to_string(path) {
554 Ok(source) => String::try_from(source)?,
555 Err(error) => {
556 return Err(error).context(url.try_to_string()?);
557 }
558 },
559 };
560
561 manifest_build
562 .sources
563 .insert(Source::with_path(url, source, path)?)?;
564
565 let mut source_loader = WorkspaceSourceLoader::new(&self.workspace.sources);
566
567 let manifest = workspace::prepare(&mut manifest_build.sources)
568 .with_diagnostics(diagnostics)
569 .with_source_loader(&mut source_loader)
570 .build()?;
571
572 let mut script_builds = Vec::new();
573
574 for p in manifest.find_all(workspace::WorkspaceFilter::All)? {
575 let Ok(url) = crate::languageserver::url::from_file_path(&p.found.path) else {
576 continue;
577 };
578
579 tracing::trace!("Found manifest source: {}", url);
580
581 let source = match workspace.sources.get(&url) {
582 Some(source) => source.chunks().try_collect::<String>()?,
583 None => match std::fs::read_to_string(&p.found.path) {
584 Ok(string) => String::try_from(string)?,
585 Err(err) => return Err(err).context(p.found.path.display().try_to_string()?),
586 },
587 };
588
589 let mut build = Build::from_workspace();
590
591 build
592 .sources
593 .insert(Source::with_path(&url, source, p.found.path)?)?;
594
595 script_builds.try_push(build)?;
596 }
597
598 Ok(script_builds)
599 }
600
601 fn build_scripts(
602 &self,
603 mut build: Build,
604 built: Option<&mut HashSet<Url>>,
605 ) -> Result<(
606 crate::Diagnostics,
607 Build,
608 Visitor,
609 crate::doc::Visitor,
610 Result<Unit, BuildError>,
611 )> {
612 let mut diagnostics = crate::Diagnostics::new();
613 let mut source_visitor = Visitor::default();
614 let mut doc_visitor = crate::doc::Visitor::new(Item::new())?;
615
616 let mut source_loader = ScriptSourceLoader::new(&self.workspace.sources);
617
618 let mut options = self.options.clone();
619
620 if !build.workspace {
621 options.function_body = true;
622 }
623
624 let unit = crate::prepare(&mut build.sources)
625 .with_context(&self.context)
626 .with_diagnostics(&mut diagnostics)
627 .with_options(&options)
628 .with_visitor(&mut doc_visitor)?
629 .with_visitor(&mut source_visitor)?
630 .with_source_loader(&mut source_loader)
631 .build();
632
633 if let Some(built) = built {
634 build.visit(built);
635 }
636
637 Ok((diagnostics, build, source_visitor, doc_visitor, unit))
638 }
639
640 fn emit_workspace(
642 &self,
643 diagnostics: workspace::Diagnostics,
644 build: &Build,
645 reporter: &mut Reporter,
646 ) -> Result<()> {
647 if tracing::enabled!(tracing::Level::TRACE) {
648 let _id_to_url = build
649 .id_to_url
650 .iter()
651 .map(|(k, v)| Ok::<_, alloc::Error>((*k, v.try_to_string()?)))
652 .try_collect::<alloc::Result<HashMap<_, _>, _>>()??;
653
654 tracing::trace!(id_to_url = ?_id_to_url, "emitting manifest diagnostics");
655 }
656
657 for diagnostic in diagnostics.diagnostics() {
658 tracing::trace!(?diagnostic, "workspace diagnostic");
659
660 let workspace::Diagnostic::Fatal(f) = diagnostic;
661 self.report(build, reporter, f.source_id(), f.error(), to_error)?;
662 }
663
664 Ok(())
665 }
666
667 fn emit_scripts(
669 &self,
670 diagnostics: crate::Diagnostics,
671 build: &Build,
672 reporter: &mut Reporter,
673 ) -> Result<()> {
674 if tracing::enabled!(tracing::Level::TRACE) {
675 let _id_to_url = build
676 .id_to_url
677 .iter()
678 .map(|(k, v)| Ok::<_, alloc::Error>((*k, v.try_to_string()?)))
679 .try_collect::<alloc::Result<HashMap<_, _>, _>>()??;
680
681 tracing::trace!(id_to_url = ?_id_to_url, "emitting script diagnostics");
682 }
683
684 for diagnostic in diagnostics.diagnostics() {
685 tracing::trace!(?diagnostic, id_to_url = ?build.id_to_url, "script diagnostic");
686
687 match diagnostic {
688 Diagnostic::Fatal(f) => match f.kind() {
689 FatalDiagnosticKind::CompileError(e) => {
690 self.report(build, reporter, f.source_id(), e, to_error)?;
691 }
692 FatalDiagnosticKind::LinkError(e) => match e {
693 LinkerError::MissingFunction { hash, spans } => {
694 for (span, source_id) in spans {
695 let (Some(url), Some(source)) = (
696 build.id_to_url.get(source_id),
697 build.sources.get(*source_id),
698 ) else {
699 continue;
700 };
701
702 let range = self.encoding.source_range(source, *span)?;
703
704 let diagnostics = reporter.entry(url);
705
706 diagnostics.try_push(to_error(
707 range,
708 format_args!("Missing function with hash `{}`", hash),
709 )?)?;
710 }
711 }
712 },
713 FatalDiagnosticKind::Internal(e) => {
714 report_without_span(build, reporter, f.source_id(), e, to_error)?;
715 }
716 },
717 Diagnostic::Warning(e) => {
718 self.report(build, reporter, e.source_id(), e, to_warning)?;
719 }
720 Diagnostic::RuntimeWarning(_) => {}
721 }
722 }
723
724 Ok(())
725 }
726
727 fn report<E, R>(
729 &self,
730 build: &Build,
731 reporter: &mut Reporter,
732 source_id: SourceId,
733 error: E,
734 report: R,
735 ) -> Result<()>
736 where
737 E: fmt::Display,
738 E: Spanned,
739 R: Fn(lsp::Range, E) -> alloc::Result<lsp::Diagnostic>,
740 {
741 let span = error.span();
742
743 let (Some(source), Some(url)) = (
744 build.sources.get(source_id),
745 build.id_to_url.get(&source_id),
746 ) else {
747 return Ok(());
748 };
749
750 let range = self.encoding.source_range(source, span)?;
751
752 reporter.entry(url).try_push(report(range, error)?)?;
753 Ok(())
754 }
755}
756
757#[derive(Default)]
759pub(super) struct Workspace {
760 pub(super) manifest_path: Option<(Url, PathBuf)>,
762 sources: HashMap<Url, ServerSource>,
764 removed: Vec<Url>,
766}
767
768impl Workspace {
769 pub(super) fn insert_source(
771 &mut self,
772 url: Url,
773 text: String,
774 language: Language,
775 ) -> alloc::Result<Option<ServerSource>> {
776 let source = ServerSource {
777 content: Rope::from_str(text.as_str()),
778 index: Default::default(),
779 build_sources: None,
780 language,
781 unit: None,
782 docs: None,
783 };
784
785 self.sources.try_insert(url, source)
786 }
787
788 pub(super) fn get(&self, url: &Url) -> Option<&ServerSource> {
790 self.sources.get(url)
791 }
792
793 pub(super) fn get_mut(&mut self, url: &Url) -> Option<&mut ServerSource> {
795 self.sources.get_mut(url)
796 }
797
798 pub(super) fn remove(&mut self, url: &Url) -> Result<()> {
800 if self.sources.remove(url).is_some() {
801 self.removed.try_push(url.clone())?;
802 }
803
804 Ok(())
805 }
806}
807
808pub(super) struct ServerSource {
810 content: Rope,
812 index: Index,
814 build_sources: Option<Arc<Sources>>,
817 language: Language,
819 unit: Option<Unit>,
821 docs: Option<Arc<crate::doc::Visitor>>,
823}
824
825impl ServerSource {
826 pub(super) fn find_definition_at(&self, span: Span) -> Option<&Definition> {
828 let (found_span, definition) = self.index.definitions.range(..=span).next_back()?;
829
830 if span.start >= found_span.start && span.end <= found_span.end {
831 tracing::trace!("found {:?}", definition);
832 return Some(definition);
833 }
834
835 None
836 }
837
838 pub(super) fn modify_lsp_range(
840 &mut self,
841 encoding: &StateEncoding,
842 range: lsp::Range,
843 content: &str,
844 ) -> Result<()> {
845 let start = encoding.rope_position(&self.content, range.start)?;
846 let end = encoding.rope_position(&self.content, range.end)?;
847 self.modify_range(start, end, content)
848 }
849
850 fn modify_range(&mut self, start: usize, end: usize, content: &str) -> Result<()> {
851 self.content.try_remove(start..end)?;
852
853 if !content.is_empty() {
854 self.content.try_insert(start, content)?;
855 }
856
857 Ok(())
858 }
859
860 pub(super) fn chunks(&self) -> impl Iterator<Item = &str> {
862 self.content.chunks()
863 }
864
865 pub(crate) fn looking_back(&self, offset: usize) -> alloc::Result<Option<(String, usize)>> {
867 let (chunk, start_byte, _, _) = self.content.chunk_at_byte(offset);
868
869 let x: &[_] = &[
871 ',', ';', '(', '.', '=', '+', '-', '*', '/', '}', '{', ']', '[', ')',
872 ];
873
874 let end_search = (offset - start_byte + 1).min(chunk.len());
875
876 let Some(looking_back) = chunk[..end_search].rfind(x) else {
877 return Ok(None);
878 };
879
880 Ok(Some((
881 chunk[looking_back..end_search].trim().try_to_owned()?,
882 start_byte + looking_back,
883 )))
884 }
885
886 pub(super) fn get_docs_by_hash(&self, hash: crate::Hash) -> Option<&VisitorData> {
887 self.docs.as_ref().and_then(|docs| docs.get_by_hash(hash))
888 }
889}
890
891impl fmt::Display for ServerSource {
892 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
893 write!(f, "{}", self.content)
894 }
895}
896
897fn report_without_span<E, R>(
899 build: &Build,
900 reporter: &mut Reporter,
901 source_id: SourceId,
902 error: E,
903 report: R,
904) -> Result<()>
905where
906 E: fmt::Display,
907 R: Fn(lsp::Range, E) -> alloc::Result<lsp::Diagnostic>,
908{
909 let Some(url) = build.id_to_url.get(&source_id) else {
910 return Ok(());
911 };
912
913 let range = lsp::Range::default();
914 let diagnostics = reporter.entry(url);
915 diagnostics.try_push(report(range, error)?)?;
916 Ok(())
917}
918
919fn to_error<E>(range: lsp::Range, error: E) -> alloc::Result<lsp::Diagnostic>
921where
922 E: fmt::Display,
923{
924 display_to_diagnostic(range, error, lsp::DiagnosticSeverity::ERROR)
925}
926
927fn to_warning<E>(range: lsp::Range, error: E) -> alloc::Result<lsp::Diagnostic>
929where
930 E: fmt::Display,
931{
932 display_to_diagnostic(range, error, lsp::DiagnosticSeverity::WARNING)
933}
934
935fn display_to_diagnostic<E>(
937 range: lsp::Range,
938 error: E,
939 severity: lsp::DiagnosticSeverity,
940) -> alloc::Result<lsp::Diagnostic>
941where
942 E: fmt::Display,
943{
944 Ok(lsp::Diagnostic {
945 range,
946 severity: Some(severity),
947 code: None,
948 code_description: None,
949 source: None,
950 message: error.try_to_string()?.into_std(),
951 related_information: None,
952 tags: None,
953 data: None,
954 })
955}
956
957#[derive(Default)]
958pub(super) struct Index {
959 definitions: BTreeMap<Span, Definition>,
961}
962
963#[derive(Debug, TryClone)]
965pub(super) enum DefinitionSource {
966 Source(SourceId),
968 Location(Location),
970 SourceMeta(SourceMeta),
972}
973
974impl DefinitionSource {
975 fn span(&self) -> Span {
976 match self {
977 Self::Source(..) => Span::empty(),
978 Self::Location(location) => location.span,
979 Self::SourceMeta(compile_source) => compile_source.location.span,
980 }
981 }
982
983 fn source_id(&self) -> SourceId {
984 match self {
985 Self::Source(source_id) => *source_id,
986 Self::Location(location) => location.source_id,
987 Self::SourceMeta(compile_source) => compile_source.location.source_id,
988 }
989 }
990
991 fn path(&self) -> Option<&Path> {
992 match self {
993 Self::SourceMeta(compile_source) => compile_source.path.as_deref(),
994 _ => None,
995 }
996 }
997}
998
999#[derive(Debug, TryClone)]
1000pub(super) struct Definition {
1001 pub(super) kind: DefinitionKind,
1003 pub(super) source: DefinitionSource,
1005}
1006
1007#[derive(Debug, TryClone, Clone, Copy)]
1008#[try_clone(copy)]
1009pub(super) enum DefinitionKind {
1010 EmptyStruct,
1012 TupleStruct,
1014 Struct,
1016 UnitVariant,
1018 TupleVariant,
1020 StructVariant,
1022 Enum,
1024 Function,
1026 AssociatedFunction,
1028 Local,
1030 Module,
1032}
1033
1034#[derive(Default)]
1035struct Visitor {
1036 indexes: HashMap<SourceId, Index>,
1037}
1038
1039impl Visitor {
1040 pub(super) fn into_indexes(self) -> HashMap<SourceId, Index> {
1042 self.indexes
1043 }
1044}
1045
1046impl CompileVisitor for Visitor {
1047 fn visit_meta(&mut self, location: &dyn Located, meta: MetaRef<'_>) -> Result<(), MetaError> {
1048 let Some(source) = meta.source else {
1049 return Ok(());
1050 };
1051
1052 let kind = match &meta.kind {
1053 meta::Kind::Struct {
1054 fields: meta::Fields::Empty,
1055 enum_hash: Hash::EMPTY,
1056 ..
1057 } => DefinitionKind::EmptyStruct,
1058 meta::Kind::Struct {
1059 fields: meta::Fields::Unnamed(..),
1060 enum_hash: Hash::EMPTY,
1061 ..
1062 } => DefinitionKind::TupleStruct,
1063 meta::Kind::Struct {
1064 fields: meta::Fields::Named(..),
1065 enum_hash: Hash::EMPTY,
1066 ..
1067 } => DefinitionKind::Struct,
1068 meta::Kind::Struct {
1069 fields: meta::Fields::Empty,
1070 ..
1071 } => DefinitionKind::UnitVariant,
1072 meta::Kind::Struct {
1073 fields: meta::Fields::Unnamed(..),
1074 ..
1075 } => DefinitionKind::TupleVariant,
1076 meta::Kind::Struct {
1077 fields: meta::Fields::Named(..),
1078 ..
1079 } => DefinitionKind::StructVariant,
1080 meta::Kind::Enum { .. } => DefinitionKind::Enum,
1081 meta::Kind::Function {
1082 associated: None, ..
1083 } => DefinitionKind::Function,
1084 meta::Kind::Function {
1085 associated: Some(..),
1086 ..
1087 } => DefinitionKind::AssociatedFunction,
1088 _ => return Ok(()),
1089 };
1090
1091 let definition = Definition {
1092 kind,
1093 source: DefinitionSource::SourceMeta(source.try_clone()?),
1094 };
1095
1096 let location = location.location();
1097
1098 let index = self.indexes.entry(location.source_id).or_try_default()?;
1099
1100 if let Some(_def) = index.definitions.insert(location.span, definition) {
1101 tracing::warn!("Replaced definition: {:?}", _def.kind);
1102 }
1103
1104 Ok(())
1105 }
1106
1107 fn visit_variable_use(
1108 &mut self,
1109 source_id: SourceId,
1110 var_span: &dyn Spanned,
1111 span: &dyn Spanned,
1112 ) -> Result<(), MetaError> {
1113 let definition = Definition {
1114 kind: DefinitionKind::Local,
1115 source: DefinitionSource::Location(Location::new(source_id, var_span.span())),
1116 };
1117
1118 let index = self.indexes.entry(source_id).or_try_default()?;
1119
1120 if let Some(_def) = index.definitions.insert(span.span(), definition) {
1121 tracing::warn!("replaced definition: {:?}", _def.kind);
1122 }
1123
1124 Ok(())
1125 }
1126
1127 fn visit_mod(&mut self, location: &dyn Located) -> Result<(), MetaError> {
1128 let location = location.location();
1129
1130 let definition = Definition {
1131 kind: DefinitionKind::Module,
1132 source: DefinitionSource::Source(location.source_id),
1133 };
1134
1135 let index = self.indexes.entry(location.source_id).or_try_default()?;
1136
1137 if let Some(_def) = index.definitions.insert(location.span, definition) {
1138 tracing::warn!("replaced definition: {:?}", _def.kind);
1139 }
1140
1141 Ok(())
1142 }
1143}
1144
1145struct ScriptSourceLoader<'a> {
1146 sources: &'a HashMap<Url, ServerSource>,
1147 base: compile::FileSourceLoader,
1148}
1149
1150impl<'a> ScriptSourceLoader<'a> {
1151 pub(super) fn new(sources: &'a HashMap<Url, ServerSource>) -> Self {
1153 Self {
1154 sources,
1155 base: compile::FileSourceLoader::new(),
1156 }
1157 }
1158
1159 fn candidates(
1161 root: &Path,
1162 item: &Item,
1163 span: &dyn Spanned,
1164 ) -> compile::Result<Option<[(Url, PathBuf); 2]>> {
1165 let mut base = root.try_to_owned()?;
1166
1167 let mut it = item.iter().peekable();
1168 let mut last = None;
1169
1170 while let Some(c) = it.next() {
1171 if it.peek().is_none() {
1172 let ComponentRef::Str(string) = c else {
1173 return Ok(None);
1174 };
1175
1176 last = Some(string);
1177 break;
1178 }
1179
1180 let ComponentRef::Str(string) = c else {
1181 return Ok(None);
1182 };
1183
1184 base.push(string);
1185 }
1186
1187 let Some(last) = last else {
1188 return Ok(None);
1189 };
1190
1191 let mut a = base.clone();
1192 a.push(format!("{last}.rn"));
1193
1194 let mut b = base;
1195 b.push(last);
1196 b.push("mod.rn");
1197
1198 let a_url = crate::languageserver::url::from_file_path(&a).with_span(span)?;
1199 let b_url = crate::languageserver::url::from_file_path(&b).with_span(span)?;
1200
1201 Ok(Some([(a_url, a), (b_url, b)]))
1202 }
1203}
1204
1205impl crate::compile::SourceLoader for ScriptSourceLoader<'_> {
1206 fn load(&mut self, root: &Path, item: &Item, span: &dyn Spanned) -> compile::Result<Source> {
1207 tracing::trace!("load {} (root: {})", item, root.display());
1208
1209 if let Some(candidates) = Self::candidates(root, item, span)? {
1210 for (url, path) in candidates {
1211 if let Some(s) = self.sources.get(&url) {
1212 return Ok(Source::with_path(url, s.try_to_string()?, path)?);
1213 }
1214 }
1215 }
1216
1217 self.base.load(root, item, span)
1218 }
1219}
1220
1221struct WorkspaceSourceLoader<'a> {
1222 sources: &'a HashMap<Url, ServerSource>,
1223 base: workspace::FileSourceLoader,
1224}
1225
1226impl<'a> WorkspaceSourceLoader<'a> {
1227 pub(super) fn new(sources: &'a HashMap<Url, ServerSource>) -> Self {
1229 Self {
1230 sources,
1231 base: workspace::FileSourceLoader::new(),
1232 }
1233 }
1234}
1235
1236impl workspace::SourceLoader for WorkspaceSourceLoader<'_> {
1237 fn load(&mut self, span: Span, path: &Path) -> Result<Source, WorkspaceError> {
1238 if let Ok(url) = crate::languageserver::url::from_file_path(path) {
1239 if let Some(s) = self.sources.get(&url) {
1240 let source = s.try_to_string().with_span(span)?;
1241 return Ok(Source::with_path(url, source, path).with_span(span)?);
1242 }
1243 }
1244
1245 self.base.load(span, path)
1246 }
1247}