Skip to content

Commit c29b268

Browse files
Integrate Salsa into document tracking for server (#148)
1 parent d677aac commit c29b268

File tree

8 files changed

+256
-137
lines changed

8 files changed

+256
-137
lines changed

Justfile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,18 @@ nox SESSION *ARGS:
2020
bumpver *ARGS:
2121
uv run --with bumpver bumpver {{ ARGS }}
2222

23+
check *ARGS:
24+
cargo check {{ ARGS }}
25+
2326
clean:
2427
cargo clean
2528

29+
clippy:
30+
cargo clippy --all-targets --all-features --fix -- -D warnings
31+
32+
fmt *ARGS:
33+
cargo +nightly fmt {{ ARGS }}
34+
2635
# run pre-commit on all files
2736
lint:
2837
@just --fmt

crates/djls-server/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ djls-templates = { workspace = true }
1414

1515
anyhow = { workspace = true }
1616
pyo3 = { workspace = true }
17+
salsa = { workspace = true }
1718
serde = { workspace = true }
1819
serde_json = { workspace = true }
1920
tokio = { workspace = true }
@@ -23,3 +24,6 @@ percent-encoding = "2.3"
2324

2425
[build-dependencies]
2526
djls-dev = { workspace = true }
27+
28+
[lints]
29+
workspace = true

crates/djls-server/src/db.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use salsa::Database;
2+
3+
#[salsa::db]
4+
#[derive(Clone, Default)]
5+
pub struct ServerDatabase {
6+
storage: salsa::Storage<Self>,
7+
}
8+
9+
impl ServerDatabase {
10+
/// Create a new database from storage
11+
pub fn new(storage: salsa::Storage<Self>) -> Self {
12+
Self { storage }
13+
}
14+
}
15+
16+
impl std::fmt::Debug for ServerDatabase {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
f.debug_struct("ServerDatabase").finish_non_exhaustive()
19+
}
20+
}
21+
22+
impl Database for ServerDatabase {}

crates/djls-server/src/documents.rs

Lines changed: 114 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,20 @@ use std::collections::HashMap;
33
use anyhow::anyhow;
44
use anyhow::Result;
55
use djls_project::TemplateTags;
6-
use tower_lsp_server::lsp_types::*;
6+
use salsa::Database;
7+
use tower_lsp_server::lsp_types::CompletionItem;
8+
use tower_lsp_server::lsp_types::CompletionItemKind;
9+
use tower_lsp_server::lsp_types::CompletionResponse;
10+
use tower_lsp_server::lsp_types::DidChangeTextDocumentParams;
11+
use tower_lsp_server::lsp_types::DidCloseTextDocumentParams;
12+
use tower_lsp_server::lsp_types::DidOpenTextDocumentParams;
13+
use tower_lsp_server::lsp_types::Documentation;
14+
use tower_lsp_server::lsp_types::InsertTextFormat;
15+
use tower_lsp_server::lsp_types::MarkupContent;
16+
use tower_lsp_server::lsp_types::MarkupKind;
17+
use tower_lsp_server::lsp_types::Position;
18+
use tower_lsp_server::lsp_types::Range;
19+
use tower_lsp_server::lsp_types::TextDocumentContentChangeEvent;
720

821
#[derive(Debug, Default)]
922
pub struct Store {
@@ -19,54 +32,42 @@ impl Store {
1932
}
2033
}
2134

22-
pub fn handle_did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> {
23-
let document = TextDocument::new(
24-
params.text_document.uri.to_string(),
25-
params.text_document.text,
26-
params.text_document.version,
27-
params.text_document.language_id,
28-
);
35+
pub fn handle_did_open(&mut self, db: &dyn Database, params: &DidOpenTextDocumentParams) {
36+
let uri = params.text_document.uri.to_string();
37+
let version = params.text_document.version;
2938

30-
self.add_document(document);
39+
let document = TextDocument::from_did_open_params(db, params);
3140

32-
Ok(())
41+
self.add_document(document, uri.clone());
42+
self.versions.insert(uri, version);
3343
}
3444

35-
pub fn handle_did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> {
45+
pub fn handle_did_change(
46+
&mut self,
47+
db: &dyn Database,
48+
params: &DidChangeTextDocumentParams,
49+
) -> Result<()> {
3650
let uri = params.text_document.uri.as_str().to_string();
3751
let version = params.text_document.version;
3852

3953
let document = self
40-
.get_document_mut(&uri)
54+
.get_document(&uri)
4155
.ok_or_else(|| anyhow!("Document not found: {}", uri))?;
4256

43-
for change in params.content_changes {
44-
if let Some(range) = change.range {
45-
document.apply_change(range, &change.text)?;
46-
} else {
47-
// Full document update
48-
document.set_content(change.text);
49-
}
50-
}
57+
let new_document = document.with_changes(db, &params.content_changes, version);
5158

52-
document.version = version;
59+
self.documents.insert(uri.clone(), new_document);
5360
self.versions.insert(uri, version);
5461

5562
Ok(())
5663
}
5764

58-
pub fn handle_did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> {
65+
pub fn handle_did_close(&mut self, params: &DidCloseTextDocumentParams) {
5966
self.remove_document(params.text_document.uri.as_str());
60-
61-
Ok(())
6267
}
6368

64-
fn add_document(&mut self, document: TextDocument) {
65-
let uri = document.uri.clone();
66-
let version = document.version;
67-
68-
self.documents.insert(uri.clone(), document);
69-
self.versions.insert(uri, version);
69+
fn add_document(&mut self, document: TextDocument, uri: String) {
70+
self.documents.insert(uri, document);
7071
}
7172

7273
fn remove_document(&mut self, uri: &str) {
@@ -78,6 +79,7 @@ impl Store {
7879
self.documents.get(uri)
7980
}
8081

82+
#[allow(dead_code)]
8183
fn get_document_mut(&mut self, uri: &str) -> Option<&mut TextDocument> {
8284
self.documents.get_mut(uri)
8385
}
@@ -88,13 +90,14 @@ impl Store {
8890
}
8991

9092
#[allow(dead_code)]
91-
pub fn get_documents_by_language(
92-
&self,
93+
pub fn get_documents_by_language<'db>(
94+
&'db self,
95+
db: &'db dyn Database,
9396
language_id: LanguageId,
94-
) -> impl Iterator<Item = &TextDocument> {
97+
) -> impl Iterator<Item = &'db TextDocument> + 'db {
9598
self.documents
9699
.values()
97-
.filter(move |doc| doc.language_id == language_id)
100+
.filter(move |doc| doc.language_id(db) == language_id)
98101
}
99102

100103
#[allow(dead_code)]
@@ -109,17 +112,18 @@ impl Store {
109112

110113
pub fn get_completions(
111114
&self,
115+
db: &dyn Database,
112116
uri: &str,
113117
position: Position,
114118
tags: &TemplateTags,
115119
) -> Option<CompletionResponse> {
116120
let document = self.get_document(uri)?;
117121

118-
if document.language_id != LanguageId::HtmlDjango {
122+
if document.language_id(db) != LanguageId::HtmlDjango {
119123
return None;
120124
}
121125

122-
let context = document.get_template_tag_context(position)?;
126+
let context = document.get_template_tag_context(db, position)?;
123127

124128
let mut completions: Vec<CompletionItem> = tags
125129
.iter()
@@ -135,7 +139,7 @@ impl Store {
135139
documentation: tag.doc().as_ref().map(|doc| {
136140
Documentation::MarkupContent(MarkupContent {
137141
kind: MarkupKind::Markdown,
138-
value: doc.to_string(),
142+
value: (*doc).to_string(),
139143
})
140144
}),
141145
insert_text: Some(match context.closing_brace {
@@ -158,89 +162,110 @@ impl Store {
158162
}
159163
}
160164

161-
#[derive(Clone, Debug)]
165+
#[salsa::input(debug)]
162166
pub struct TextDocument {
167+
#[return_ref]
163168
uri: String,
169+
#[return_ref]
164170
contents: String,
171+
#[return_ref]
165172
index: LineIndex,
166173
version: i32,
167174
language_id: LanguageId,
168175
}
169176

170177
impl TextDocument {
171-
fn new(uri: String, contents: String, version: i32, language_id: String) -> Self {
178+
pub fn from_did_open_params(db: &dyn Database, params: &DidOpenTextDocumentParams) -> Self {
179+
let uri = params.text_document.uri.to_string();
180+
let contents = params.text_document.text.clone();
181+
let version = params.text_document.version;
182+
let language_id = LanguageId::from(params.text_document.language_id.as_str());
183+
172184
let index = LineIndex::new(&contents);
173-
Self {
174-
uri,
175-
contents,
176-
index,
177-
version,
178-
language_id: LanguageId::from(language_id),
179-
}
185+
TextDocument::new(db, uri, contents, index, version, language_id)
180186
}
181187

182-
pub fn apply_change(&mut self, range: Range, new_text: &str) -> Result<()> {
183-
let start_offset = self
184-
.index
185-
.offset(range.start)
186-
.ok_or_else(|| anyhow!("Invalid start position: {:?}", range.start))?
187-
as usize;
188-
let end_offset = self
189-
.index
190-
.offset(range.end)
191-
.ok_or_else(|| anyhow!("Invalid end position: {:?}", range.end))?
192-
as usize;
188+
pub fn with_changes(
189+
self,
190+
db: &dyn Database,
191+
changes: &[TextDocumentContentChangeEvent],
192+
new_version: i32,
193+
) -> Self {
194+
let mut new_contents = self.contents(db).to_string();
193195

194-
let mut new_content = String::with_capacity(
195-
self.contents.len() - (end_offset - start_offset) + new_text.len(),
196-
);
196+
for change in changes {
197+
if let Some(range) = change.range {
198+
let index = LineIndex::new(&new_contents);
197199

198-
new_content.push_str(&self.contents[..start_offset]);
199-
new_content.push_str(new_text);
200-
new_content.push_str(&self.contents[end_offset..]);
200+
if let (Some(start_offset), Some(end_offset)) = (
201+
index.offset(range.start).map(|o| o as usize),
202+
index.offset(range.end).map(|o| o as usize),
203+
) {
204+
let mut updated_content = String::with_capacity(
205+
new_contents.len() - (end_offset - start_offset) + change.text.len(),
206+
);
201207

202-
self.set_content(new_content);
208+
updated_content.push_str(&new_contents[..start_offset]);
209+
updated_content.push_str(&change.text);
210+
updated_content.push_str(&new_contents[end_offset..]);
203211

204-
Ok(())
205-
}
212+
new_contents = updated_content;
213+
}
214+
} else {
215+
// Full document update
216+
new_contents.clone_from(&change.text);
217+
}
218+
}
206219

207-
pub fn set_content(&mut self, new_content: String) {
208-
self.contents = new_content;
209-
self.index = LineIndex::new(&self.contents);
220+
let index = LineIndex::new(&new_contents);
221+
TextDocument::new(
222+
db,
223+
self.uri(db).to_string(),
224+
new_contents,
225+
index,
226+
new_version,
227+
self.language_id(db),
228+
)
210229
}
211230

212231
#[allow(dead_code)]
213-
pub fn get_text(&self) -> &str {
214-
&self.contents
232+
pub fn get_text(self, db: &dyn Database) -> String {
233+
self.contents(db).to_string()
215234
}
216235

217236
#[allow(dead_code)]
218-
pub fn get_text_range(&self, range: Range) -> Option<&str> {
219-
let start = self.index.offset(range.start)? as usize;
220-
let end = self.index.offset(range.end)? as usize;
221-
222-
Some(&self.contents[start..end])
237+
pub fn get_text_range(self, db: &dyn Database, range: Range) -> Option<String> {
238+
let index = self.index(db);
239+
let start = index.offset(range.start)? as usize;
240+
let end = index.offset(range.end)? as usize;
241+
let contents = self.contents(db);
242+
Some(contents[start..end].to_string())
223243
}
224244

225-
pub fn get_line(&self, line: u32) -> Option<&str> {
226-
let start = self.index.line_starts.get(line as usize)?;
227-
let end = self
228-
.index
245+
pub fn get_line(self, db: &dyn Database, line: u32) -> Option<String> {
246+
let index = self.index(db);
247+
let start = index.line_starts.get(line as usize)?;
248+
let end = index
229249
.line_starts
230250
.get(line as usize + 1)
231251
.copied()
232-
.unwrap_or(self.index.length);
252+
.unwrap_or(index.length);
233253

234-
Some(&self.contents[*start as usize..end as usize])
254+
let contents = self.contents(db);
255+
Some(contents[*start as usize..end as usize].to_string())
235256
}
236257

237258
#[allow(dead_code)]
238-
pub fn line_count(&self) -> usize {
239-
self.index.line_starts.len()
259+
pub fn line_count(self, db: &dyn Database) -> usize {
260+
self.index(db).line_starts.len()
240261
}
241262

242-
pub fn get_template_tag_context(&self, position: Position) -> Option<TemplateTagContext> {
243-
let line = self.get_line(position.line)?;
263+
pub fn get_template_tag_context(
264+
self,
265+
db: &dyn Database,
266+
position: Position,
267+
) -> Option<TemplateTagContext> {
268+
let line = self.get_line(db, position.line)?;
244269
let char_pos: usize = position.character.try_into().ok()?;
245270
let prefix = &line[..char_pos];
246271
let rest_of_line = &line[char_pos..];
@@ -252,7 +277,7 @@ impl TextDocument {
252277

253278
let closing_brace = if rest_trimmed.starts_with("%}") {
254279
ClosingBrace::FullClose
255-
} else if rest_trimmed.starts_with("}") {
280+
} else if rest_trimmed.starts_with('}') {
256281
ClosingBrace::PartialClose
257282
} else {
258283
ClosingBrace::None
@@ -279,7 +304,7 @@ impl LineIndex {
279304
let mut pos = 0;
280305

281306
for c in text.chars() {
282-
pos += c.len_utf8() as u32;
307+
pos += u32::try_from(c.len_utf8()).unwrap_or(0);
283308
if c == '\n' {
284309
line_starts.push(pos);
285310
}
@@ -307,7 +332,7 @@ impl LineIndex {
307332
let line_start = self.line_starts[line];
308333
let character = offset - line_start;
309334

310-
Position::new(line as u32, character)
335+
Position::new(u32::try_from(line).unwrap_or(0), character)
311336
}
312337
}
313338

crates/djls-server/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod db;
12
mod documents;
23
mod queue;
34
mod server;

0 commit comments

Comments
 (0)