Skip to content

Commit aa55cf0

Browse files
add domain specific ClientCapabilities struct (#286)
1 parent ae1f8d3 commit aa55cf0

File tree

2 files changed

+80
-72
lines changed

2 files changed

+80
-72
lines changed

crates/djls-server/src/server.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl DjangoLanguageServer {
7373

7474
async fn publish_diagnostics(&self, document: &TextDocument) {
7575
let supports_pull = self
76-
.with_session(super::session::Session::supports_pull_diagnostics)
76+
.with_session(|session| session.client_capabilities().supports_pull_diagnostics())
7777
.await;
7878

7979
if supports_pull {
@@ -117,7 +117,7 @@ impl LanguageServer for DjangoLanguageServer {
117117
tracing::info!("Initializing server...");
118118

119119
let session = Session::new(&params);
120-
let encoding = session.position_encoding();
120+
let encoding = session.client_capabilities().position_encoding();
121121

122122
{
123123
let mut session_lock = self.session.lock().await;
@@ -179,9 +179,9 @@ impl LanguageServer for DjangoLanguageServer {
179179
let session = session_arc.lock().await;
180180

181181
if let Some(project) = session.project() {
182-
let path = project.root(session.database()).clone();
182+
let path = project.root(session.db()).clone();
183183
tracing::info!("Task: Starting initialization for project at: {}", path);
184-
project.initialize(session.database());
184+
project.initialize(session.db());
185185
tracing::info!("Task: Successfully initialized project: {}", path);
186186
} else {
187187
tracing::info!("Task: No project configured, skipping initialization.");
@@ -261,7 +261,7 @@ impl LanguageServer for DjangoLanguageServer {
261261

262262
let document = session.get_document(&path)?;
263263
let position = params.text_document_position.position;
264-
let encoding = session.position_encoding();
264+
let encoding = session.client_capabilities().position_encoding();
265265
let file_kind = FileKind::from(&path);
266266
let template_tags = session.with_db(|db| {
267267
if let Some(project) = db.project() {
@@ -279,7 +279,7 @@ impl LanguageServer for DjangoLanguageServer {
279279
}
280280
});
281281
let tag_specs = session.with_db(SemanticDb::tag_specs);
282-
let supports_snippets = session.supports_snippets();
282+
let supports_snippets = session.client_capabilities().supports_snippets();
283283

284284
let completions = djls_ide::handle_completion(
285285
&document,
@@ -352,7 +352,7 @@ impl LanguageServer for DjangoLanguageServer {
352352
) -> LspResult<Option<lsp_types::GotoDefinitionResponse>> {
353353
let response = self
354354
.with_session_mut(|session| {
355-
let encoding = session.position_encoding();
355+
let encoding = session.client_capabilities().position_encoding();
356356

357357
session.with_db_mut(|db| {
358358
let file = params
@@ -380,7 +380,7 @@ impl LanguageServer for DjangoLanguageServer {
380380
) -> LspResult<Option<Vec<lsp_types::Location>>> {
381381
let response = self
382382
.with_session_mut(|session| {
383-
let encoding = session.position_encoding();
383+
let encoding = session.client_capabilities().position_encoding();
384384

385385
session.with_db_mut(|db| {
386386
let file = params.text_document_position.text_document.to_file(db)?;
@@ -404,7 +404,7 @@ impl LanguageServer for DjangoLanguageServer {
404404

405405
self.with_session_mut(|session| {
406406
if session.project().is_some() {
407-
let project_root = session.database().project_root_or_cwd();
407+
let project_root = session.db().project_root_or_cwd();
408408

409409
match djls_conf::Settings::new(&project_root) {
410410
Ok(new_settings) => {

crates/djls-server/src/session.rs

Lines changed: 71 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,7 @@ pub struct Session {
3535
/// but not the database (which is owned directly by Session).
3636
workspace: Workspace,
3737

38-
client_capabilities: lsp_types::ClientCapabilities,
39-
40-
/// Position encoding negotiated with client
41-
position_encoding: PositionEncoding,
38+
client_capabilities: ClientCapabilities,
4239

4340
/// The Salsa database for incremental computation
4441
db: DjangoDatabase,
@@ -68,12 +65,16 @@ impl Session {
6865

6966
Self {
7067
workspace,
71-
client_capabilities: params.capabilities.clone(),
72-
position_encoding: negotiate_position_encoding(&params.capabilities),
68+
client_capabilities: ClientCapabilities::negotiate(&params.capabilities),
7369
db,
7470
}
7571
}
7672

73+
#[must_use]
74+
pub fn client_capabilities(&self) -> ClientCapabilities {
75+
self.client_capabilities
76+
}
77+
7778
#[must_use]
7879
pub fn db(&self) -> &DjangoDatabase {
7980
&self.db
@@ -83,11 +84,6 @@ impl Session {
8384
self.db.set_settings(settings);
8485
}
8586

86-
#[must_use]
87-
pub fn position_encoding(&self) -> PositionEncoding {
88-
self.position_encoding
89-
}
90-
9187
/// Execute a read-only operation with access to the database.
9288
pub fn with_db<F, R>(&self, f: F) -> R
9389
where
@@ -104,11 +100,6 @@ impl Session {
104100
f(&mut self.db)
105101
}
106102

107-
/// Get a reference to the database for project operations.
108-
pub fn database(&self) -> &DjangoDatabase {
109-
&self.db
110-
}
111-
112103
/// Get the current project for this session
113104
pub fn project(&self) -> Option<djls_project::Project> {
114105
self.db.project()
@@ -180,7 +171,7 @@ impl Session {
180171
&path,
181172
doc_changes,
182173
text_document.version,
183-
self.position_encoding,
174+
self.client_capabilities.position_encoding(),
184175
)?;
185176

186177
self.handle_file(document.file());
@@ -219,60 +210,77 @@ impl Session {
219210
}
220211
}
221212
}
213+
}
222214

223-
/// Check if the client supports pull diagnostics.
224-
///
225-
/// Returns true if the client has indicated support for textDocument/diagnostic requests.
226-
/// When true, the server should not push diagnostics and instead wait for pull requests.
227-
#[must_use]
228-
pub fn supports_pull_diagnostics(&self) -> bool {
229-
self.client_capabilities
215+
impl Default for Session {
216+
fn default() -> Self {
217+
Self::new(&lsp_types::InitializeParams::default())
218+
}
219+
}
220+
221+
#[derive(Debug, Clone, Copy)]
222+
pub struct ClientCapabilities {
223+
pull_diagnostics: bool,
224+
snippets: bool,
225+
position_encoding: PositionEncoding,
226+
}
227+
228+
impl ClientCapabilities {
229+
fn negotiate(capabilities: &lsp_types::ClientCapabilities) -> Self {
230+
let pull_diagnostics = capabilities
230231
.text_document
231232
.as_ref()
232-
.and_then(|td| td.diagnostic.as_ref())
233-
.is_some()
234-
}
233+
.and_then(|text_doc| text_doc.diagnostic.as_ref())
234+
.is_some();
235235

236-
/// Check if the client supports snippet completions
237-
#[must_use]
238-
pub fn supports_snippets(&self) -> bool {
239-
self.client_capabilities
236+
let snippets = capabilities
240237
.text_document
241238
.as_ref()
242-
.and_then(|td| td.completion.as_ref())
243-
.and_then(|c| c.completion_item.as_ref())
244-
.and_then(|ci| ci.snippet_support)
245-
.unwrap_or(false)
239+
.and_then(|text_document| text_document.completion.as_ref())
240+
.and_then(|completion| completion.completion_item.as_ref())
241+
.and_then(|completion_item| completion_item.snippet_support)
242+
.unwrap_or(false);
243+
244+
let client_encodings = capabilities
245+
.general
246+
.as_ref()
247+
.and_then(|general| general.position_encodings.as_ref())
248+
.map_or(&[][..], |kinds| kinds.as_slice());
249+
250+
let position_encoding = [
251+
PositionEncoding::Utf8,
252+
PositionEncoding::Utf32,
253+
PositionEncoding::Utf16,
254+
]
255+
.into_iter()
256+
.find(|&preferred| {
257+
client_encodings
258+
.iter()
259+
.any(|kind| kind.to_position_encoding() == Some(preferred))
260+
})
261+
.unwrap_or(PositionEncoding::Utf16);
262+
263+
Self {
264+
pull_diagnostics,
265+
snippets,
266+
position_encoding,
267+
}
246268
}
247-
}
248269

249-
impl Default for Session {
250-
fn default() -> Self {
251-
Self::new(&lsp_types::InitializeParams::default())
270+
#[must_use]
271+
pub fn supports_pull_diagnostics(&self) -> bool {
272+
self.pull_diagnostics
252273
}
253-
}
254274

255-
fn negotiate_position_encoding(capabilities: &lsp_types::ClientCapabilities) -> PositionEncoding {
256-
let client_encodings = capabilities
257-
.general
258-
.as_ref()
259-
.and_then(|general| general.position_encodings.as_ref())
260-
.map_or(&[][..], |encodings| encodings.as_slice());
261-
262-
for preferred in [
263-
PositionEncoding::Utf8,
264-
PositionEncoding::Utf32,
265-
PositionEncoding::Utf16,
266-
] {
267-
if client_encodings
268-
.iter()
269-
.any(|kind| kind.to_position_encoding() == Some(preferred))
270-
{
271-
return preferred;
272-
}
275+
#[must_use]
276+
pub fn supports_snippets(&self) -> bool {
277+
self.snippets
273278
}
274279

275-
PositionEncoding::Utf16
280+
#[must_use]
281+
pub fn position_encoding(&self) -> PositionEncoding {
282+
self.position_encoding
283+
}
276284
}
277285

278286
#[cfg(test)]
@@ -367,7 +375,7 @@ mod tests {
367375
..Default::default()
368376
};
369377
assert_eq!(
370-
negotiate_position_encoding(&capabilities),
378+
ClientCapabilities::negotiate(&capabilities).position_encoding(),
371379
PositionEncoding::Utf8
372380
);
373381
}
@@ -385,7 +393,7 @@ mod tests {
385393
..Default::default()
386394
};
387395
assert_eq!(
388-
negotiate_position_encoding(&capabilities),
396+
ClientCapabilities::negotiate(&capabilities).position_encoding(),
389397
PositionEncoding::Utf32
390398
);
391399
}
@@ -403,7 +411,7 @@ mod tests {
403411
..Default::default()
404412
};
405413
assert_eq!(
406-
negotiate_position_encoding(&capabilities),
414+
ClientCapabilities::negotiate(&capabilities).position_encoding(),
407415
PositionEncoding::Utf16
408416
);
409417
}
@@ -412,7 +420,7 @@ mod tests {
412420
fn test_negotiate_fallback_with_no_capabilities() {
413421
let capabilities = lsp_types::ClientCapabilities::default();
414422
assert_eq!(
415-
negotiate_position_encoding(&capabilities),
423+
ClientCapabilities::negotiate(&capabilities).position_encoding(),
416424
PositionEncoding::Utf16
417425
);
418426
}

0 commit comments

Comments
 (0)