1use lsp_types::{Location, Uri};
5use nifty_syntax::ast::{File, Item};
6use nifty_syntax::{Diagnostic, FileId, Parsed, Span};
7
8use crate::document::{Document, Encoding};
9use crate::index::Index;
10
11#[derive(Debug)]
13pub(crate) struct Source {
14 pub uri: Uri,
15 pub text: String,
16}
17
18#[derive(Debug)]
19pub(crate) struct Project {
20 uris: Vec<Uri>,
22 documents: Vec<Document>,
23 pub index: Index,
24 diagnostics: Vec<Diagnostic>,
26}
27
28impl Project {
29 pub(crate) fn new(sources: Vec<Source>) -> Self {
31 let (uris, documents): (Vec<Uri>, Vec<Document>) = sources
32 .into_iter()
33 .enumerate()
34 .map(|(index, source)| (source.uri, Document::new(source.text, FileId::new(index))))
35 .unzip();
36 let parsed: Vec<&Parsed> = documents.iter().map(|document| &document.parsed).collect();
37 let files: Vec<&File> = parsed.iter().map(|parsed| &parsed.file).collect();
38 let index = Index::new(&files);
39 let diagnostics = nifty_check::compile(&parsed).err().unwrap_or_default();
40 Self {
41 uris,
42 documents,
43 index,
44 diagnostics,
45 }
46 }
47
48 pub(crate) fn file_of(&self, uri: &Uri) -> Option<FileId> {
49 self.uris
50 .iter()
51 .position(|known| known == uri)
52 .map(FileId::new)
53 }
54
55 pub(crate) fn files(&self) -> impl Iterator<Item = (FileId, &Uri)> {
57 self.uris
58 .iter()
59 .enumerate()
60 .map(|(index, uri)| (FileId::new(index), uri))
61 }
62
63 pub(crate) fn document(&self, file: FileId) -> &Document {
64 &self.documents[file.index()]
65 }
66
67 pub(crate) fn text(&self, span: Span) -> &str {
69 self.document(span.file).text(span)
70 }
71
72 pub(crate) fn location(&self, span: Span, encoding: Encoding) -> Location {
73 Location::new(
74 self.uris[span.file.index()].clone(),
75 self.document(span.file).range(span, encoding),
76 )
77 }
78
79 pub(crate) fn diagnostics_in(&self, file: FileId) -> impl Iterator<Item = &Diagnostic> {
81 self.diagnostics
82 .iter()
83 .filter(move |diagnostic| diagnostic.span.file == file)
84 }
85
86 pub(crate) fn items(&self) -> impl Iterator<Item = &Item> {
88 self.documents
89 .iter()
90 .flat_map(|document| &document.parsed.file.items)
91 }
92}
93
94#[derive(Clone, Copy, Debug)]
96pub(crate) struct Cursor<'p> {
97 pub project: &'p Project,
98 pub file: FileId,
99 pub offset: usize,
100}
101
102impl<'p> Cursor<'p> {
103 pub(crate) fn document(self) -> &'p Document {
104 self.project.document(self.file)
105 }
106}
107
108#[cfg(test)]
109impl Project {
110 pub(crate) fn single(text: &str) -> Self {
112 Self::new(vec![Source {
113 uri: test_uri("main.nf"),
114 text: text.to_owned(),
115 }])
116 }
117
118 pub(crate) fn with_cursor(source: &str) -> (Self, usize) {
120 let offset = source
121 .find('|')
122 .expect("the source marks the cursor with `|`");
123 (Self::single(&source.replacen('|', "", 1)), offset)
124 }
125
126 pub(crate) fn cursor(&self, offset: usize) -> Cursor<'_> {
127 Cursor {
128 project: self,
129 file: FileId::default(),
130 offset,
131 }
132 }
133}
134
135#[cfg(test)]
136pub(crate) fn test_uri(name: &str) -> Uri {
137 format!("file:///project/{name}")
138 .parse()
139 .expect("a valid test uri")
140}