blues_lsp/project/
file.rs1use std::{fs, ops::Range, path::PathBuf, sync::Arc};
2
3use arcstr::{ArcStr, Substr};
4use tracing::{error, trace};
5
6use crate::syntax::{
7 TextChunk,
8 location::{FilePos, LineColCounter, LspCol, OriginId, Pos, ROOT_ORIGIN},
9};
10
11use super::{FileHandle, PkgHandle, Project, querry::Queryable};
12
13pub struct File {
14 pub path: Option<PathBuf>,
15}
16
17impl File {
18 pub fn new(path: Option<PathBuf>) -> Self {
19 Self { path }
20 }
21}
22
23#[derive(Debug)]
25pub struct FileContents {
26 pub contents: ArcStr,
27 line_lookup: Vec<(u32, LspCol)>,
31 end_pos: FilePos,
32}
33
34impl Queryable for FileContents {
35 type Input = FileHandle;
36 type Ctx = ();
37 type Backref = PkgHandle;
38
39 fn compute(input: Self::Input, _ctx: &Self::Ctx, proj: &Project) -> Arc<Self> {
40 if let Some(file) = proj.content_overlay.get(&input) {
41 return file.clone();
42 }
43
44 let file = proj.get_file(input);
45 let Some(path) = file.path.as_ref() else {
46 error!("FileContents::compute called on FileHandle without local path");
47 return Arc::new(FileContents::new("".into()));
48 };
49
50 trace!("reading source file '{}'", path.display());
51 let text = match fs::read_to_string(path) {
52 Ok(text) => text,
53 Err(e) => {
54 error!("could not open file {}: {e}", path.display());
55 return Arc::new(FileContents::new("".into()));
56 }
57 };
58
59 Arc::new(FileContents::new(text.into()))
60 }
61
62 fn invalidate(&self, _input: &Self::Input, _ctx: &Self::Ctx, _proj: &Project) {}
63 fn invalidate_backref(&self, backref: Self::Backref, proj: &Project) {
64 proj.syntax.invalidate(proj, &backref);
65 }
66}
67
68impl FileContents {
69 pub fn new(contents: ArcStr) -> Self {
70 let (line_lookup, end_pos) = Self::gen_line_lookup(&contents);
71
72 FileContents {
73 contents,
74 line_lookup,
75 end_pos,
76 }
77 }
78
79 fn gen_line_lookup(text: &str) -> (Vec<(u32, LspCol)>, FilePos) {
80 let mut line_ofs = 0;
81 let mut cols = LspCol::start();
82 let mut line = 0;
83 let mut counter = LineColCounter::default();
84 let mut out = Vec::new();
85
86 for (ofs, c) in text.char_indices() {
87 if counter.pos(Some(c)).line != line {
88 out.push((line_ofs, cols));
89 line_ofs = ofs as u32;
90 line += 1;
91 cols = LspCol::start();
92 }
93 cols.advance(c);
94 counter.advance(c);
95 }
96
97 out.push((line_ofs, cols));
98
99 if counter.pos(None).col == LspCol::start() {
101 out.push((text.len() as u32, LspCol::start()));
102 }
103
104 (out, counter.pos(None))
105 }
106
107 pub fn pos_add(&self, start: FilePos, ofs: LspCol) -> FilePos {
108 let mut line = start.line;
109 let mut col = start.col.to_raw() + ofs.to_raw();
110
111 while col > self.line_width(line) {
112 col -= self.line_width(line);
113 line += 1;
114 }
115
116 FilePos {
117 line,
118 col: LspCol::from_raw(col),
119 }
120 }
121
122 pub fn pos_diff(&self, range: Range<FilePos>) -> LspCol {
123 let diff = (range.start.line..range.end.line)
124 .map(|l| self.line_width(l))
125 .sum::<u32>()
126 + range.end.col.to_raw()
127 - range.start.col.to_raw();
128
129 LspCol::from_raw(diff)
130 }
131
132 pub fn line_width(&self, line: u32) -> u32 {
133 self.line_lookup[line as usize].1.to_raw()
134 }
135
136 pub fn chunks(&self, origin: OriginId) -> Vec<TextChunk> {
137 if self.contents.is_empty() {
138 Vec::new()
139 } else {
140 vec![TextChunk {
141 pos: Pos::at(origin, 0, 0),
142 text: Substr::full(self.contents.clone()),
143 }]
144 }
145 }
146
147 pub fn end_pos(&self) -> Pos {
148 Pos {
149 origin: ROOT_ORIGIN,
150 text_pos: self.end_pos,
151 }
152 }
153}