Skip to content

Commit 1799355

Browse files
add document store and support for didopen, didchange, and didclose (#11)
1 parent 5971c23 commit 1799355

File tree

3 files changed

+341
-9
lines changed

3 files changed

+341
-9
lines changed

crates/djls/src/documents.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
use anyhow::{anyhow, Result};
2+
use std::collections::HashMap;
3+
use tower_lsp::lsp_types::{
4+
DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Position,
5+
Range,
6+
};
7+
8+
#[derive(Debug)]
9+
pub struct Store {
10+
documents: HashMap<String, TextDocument>,
11+
versions: HashMap<String, i32>,
12+
}
13+
14+
impl Store {
15+
pub fn new() -> Self {
16+
Self {
17+
documents: HashMap::new(),
18+
versions: HashMap::new(),
19+
}
20+
}
21+
22+
pub fn handle_did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> {
23+
let document = TextDocument::new(
24+
String::from(params.text_document.uri),
25+
params.text_document.text,
26+
params.text_document.version,
27+
params.text_document.language_id,
28+
);
29+
30+
self.add_document(document);
31+
32+
Ok(())
33+
}
34+
35+
pub fn handle_did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> {
36+
let uri = params.text_document.uri.as_str().to_string();
37+
let version = params.text_document.version;
38+
39+
let document = self
40+
.get_document_mut(&uri)
41+
.ok_or_else(|| anyhow!("Document not found: {}", uri))?;
42+
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+
}
51+
52+
document.version = version;
53+
self.versions.insert(uri, version);
54+
55+
Ok(())
56+
}
57+
58+
pub fn handle_did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> {
59+
self.remove_document(&String::from(params.text_document.uri));
60+
61+
Ok(())
62+
}
63+
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);
70+
}
71+
72+
fn remove_document(&mut self, uri: &str) {
73+
self.documents.remove(uri);
74+
self.versions.remove(uri);
75+
}
76+
77+
fn get_document(&self, uri: &str) -> Option<&TextDocument> {
78+
self.documents.get(uri)
79+
}
80+
81+
fn get_document_mut(&mut self, uri: &str) -> Option<&mut TextDocument> {
82+
self.documents.get_mut(uri)
83+
}
84+
85+
pub fn get_all_documents(&self) -> impl Iterator<Item = &TextDocument> {
86+
self.documents.values()
87+
}
88+
89+
pub fn get_documents_by_language(
90+
&self,
91+
language_id: LanguageId,
92+
) -> impl Iterator<Item = &TextDocument> {
93+
self.documents
94+
.values()
95+
.filter(move |doc| doc.language_id == language_id)
96+
}
97+
98+
pub fn get_version(&self, uri: &str) -> Option<i32> {
99+
self.versions.get(uri).copied()
100+
}
101+
102+
pub fn is_version_valid(&self, uri: &str, version: i32) -> bool {
103+
self.get_version(uri).map_or(false, |v| v == version)
104+
}
105+
}
106+
107+
#[derive(Clone, Debug)]
108+
pub struct TextDocument {
109+
uri: String,
110+
contents: String,
111+
index: LineIndex,
112+
version: i32,
113+
language_id: LanguageId,
114+
}
115+
116+
impl TextDocument {
117+
fn new(uri: String, contents: String, version: i32, language_id: String) -> Self {
118+
let index = LineIndex::new(&contents);
119+
Self {
120+
uri,
121+
contents,
122+
index,
123+
version,
124+
language_id: LanguageId::from(language_id),
125+
}
126+
}
127+
128+
pub fn apply_change(&mut self, range: Range, new_text: &str) -> Result<()> {
129+
let start_offset = self
130+
.index
131+
.offset(range.start)
132+
.ok_or_else(|| anyhow!("Invalid start position: {:?}", range.start))?
133+
as usize;
134+
let end_offset = self
135+
.index
136+
.offset(range.end)
137+
.ok_or_else(|| anyhow!("Invalid end position: {:?}", range.end))?
138+
as usize;
139+
140+
let mut new_content = String::with_capacity(
141+
self.contents.len() - (end_offset - start_offset) + new_text.len(),
142+
);
143+
144+
new_content.push_str(&self.contents[..start_offset]);
145+
new_content.push_str(new_text);
146+
new_content.push_str(&self.contents[end_offset..]);
147+
148+
self.set_content(new_content);
149+
150+
Ok(())
151+
}
152+
153+
pub fn set_content(&mut self, new_content: String) {
154+
self.contents = new_content;
155+
self.index = LineIndex::new(&self.contents);
156+
}
157+
158+
pub fn get_text(&self) -> &str {
159+
&self.contents
160+
}
161+
162+
pub fn get_text_range(&self, range: Range) -> Option<&str> {
163+
let start = self.index.offset(range.start)? as usize;
164+
let end = self.index.offset(range.end)? as usize;
165+
166+
Some(&self.contents[start..end])
167+
}
168+
169+
pub fn get_line(&self, line: u32) -> Option<&str> {
170+
let start = self.index.line_starts.get(line as usize)?;
171+
let end = self
172+
.index
173+
.line_starts
174+
.get(line as usize + 1)
175+
.copied()
176+
.unwrap_or(self.index.length);
177+
178+
Some(&self.contents[*start as usize..end as usize])
179+
}
180+
181+
pub fn line_count(&self) -> usize {
182+
self.index.line_starts.len()
183+
}
184+
}
185+
186+
#[derive(Clone, Debug)]
187+
pub struct LineIndex {
188+
line_starts: Vec<u32>,
189+
length: u32,
190+
}
191+
192+
impl LineIndex {
193+
pub fn new(text: &str) -> Self {
194+
let mut line_starts = vec![0];
195+
let mut pos = 0;
196+
197+
for c in text.chars() {
198+
pos += c.len_utf8() as u32;
199+
if c == '\n' {
200+
line_starts.push(pos);
201+
}
202+
}
203+
204+
Self {
205+
line_starts,
206+
length: pos,
207+
}
208+
}
209+
210+
pub fn offset(&self, position: Position) -> Option<u32> {
211+
let line_start = self.line_starts.get(position.line as usize)?;
212+
213+
Some(line_start + position.character)
214+
}
215+
216+
pub fn position(&self, offset: u32) -> Position {
217+
let line = match self.line_starts.binary_search(&offset) {
218+
Ok(line) => line,
219+
Err(line) => line - 1,
220+
};
221+
222+
let line_start = self.line_starts[line];
223+
let character = offset - line_start;
224+
225+
Position::new(line as u32, character)
226+
}
227+
}
228+
229+
#[derive(Clone, Debug, PartialEq)]
230+
pub enum LanguageId {
231+
HtmlDjango,
232+
Other,
233+
Python,
234+
}
235+
236+
impl From<&str> for LanguageId {
237+
fn from(language_id: &str) -> Self {
238+
match language_id {
239+
"htmldjango" => Self::HtmlDjango,
240+
"python" => Self::Python,
241+
_ => Self::Other,
242+
}
243+
}
244+
}
245+
246+
impl From<String> for LanguageId {
247+
fn from(language_id: String) -> Self {
248+
Self::from(language_id.as_str())
249+
}
250+
}

crates/djls/src/main.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,82 @@
1+
mod documents;
12
mod notifier;
23
mod server;
34

45
use crate::notifier::TowerLspNotifier;
56
use crate::server::{DjangoLanguageServer, LspNotification, LspRequest};
67
use anyhow::Result;
78
use djls_django::DjangoProject;
9+
use std::sync::Arc;
10+
use tokio::sync::RwLock;
811
use tower_lsp::jsonrpc::Result as LspResult;
912
use tower_lsp::lsp_types::*;
1013
use tower_lsp::{LanguageServer, LspService, Server};
1114

1215
struct TowerLspBackend {
13-
server: DjangoLanguageServer,
16+
server: Arc<RwLock<DjangoLanguageServer>>,
1417
}
1518

1619
#[tower_lsp::async_trait]
1720
impl LanguageServer for TowerLspBackend {
1821
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
1922
self.server
23+
.read()
24+
.await
2025
.handle_request(LspRequest::Initialize(params))
2126
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())
2227
}
2328

2429
async fn initialized(&self, params: InitializedParams) {
25-
if self
30+
if let Err(e) = self
2631
.server
32+
.write()
33+
.await
2734
.handle_notification(LspNotification::Initialized(params))
28-
.is_err()
2935
{
30-
// Handle error
36+
eprintln!("Error handling initialized: {}", e);
3137
}
3238
}
3339

3440
async fn shutdown(&self) -> LspResult<()> {
3541
self.server
42+
.write()
43+
.await
3644
.handle_notification(LspNotification::Shutdown)
3745
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())
3846
}
47+
48+
async fn did_open(&self, params: DidOpenTextDocumentParams) {
49+
if let Err(e) = self
50+
.server
51+
.write()
52+
.await
53+
.handle_notification(LspNotification::DidOpenTextDocument(params))
54+
{
55+
eprintln!("Error handling document open: {}", e);
56+
}
57+
}
58+
59+
async fn did_change(&self, params: DidChangeTextDocumentParams) {
60+
if let Err(e) = self
61+
.server
62+
.write()
63+
.await
64+
.handle_notification(LspNotification::DidChangeTextDocument(params))
65+
{
66+
eprintln!("Error handling document change: {}", e);
67+
}
68+
}
69+
70+
async fn did_close(&self, params: DidCloseTextDocumentParams) {
71+
if let Err(e) = self
72+
.server
73+
.write()
74+
.await
75+
.handle_notification(LspNotification::DidCloseTextDocument(params))
76+
{
77+
eprintln!("Error handling document close: {}", e);
78+
}
79+
}
3980
}
4081

4182
#[tokio::main]
@@ -48,7 +89,9 @@ async fn main() -> Result<()> {
4889
let (service, socket) = LspService::build(|client| {
4990
let notifier = Box::new(TowerLspNotifier::new(client.clone()));
5091
let server = DjangoLanguageServer::new(django, notifier);
51-
TowerLspBackend { server }
92+
TowerLspBackend {
93+
server: Arc::new(RwLock::new(server)),
94+
}
5295
})
5396
.finish();
5497

0 commit comments

Comments
 (0)