Skip to content

Commit 143536d

Browse files
Extract IDE features from various crates to new dedicated crate (#230)
1 parent 17d7e1a commit 143536d

File tree

25 files changed

+374
-403
lines changed

25 files changed

+374
-403
lines changed

Cargo.lock

Lines changed: 15 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ resolver = "2"
55
[workspace.dependencies]
66
djls = { path = "crates/djls" }
77
djls-conf = { path = "crates/djls-conf" }
8-
djls-semantic = { path = "crates/djls-semantic" }
8+
djls-ide = { path = "crates/djls-ide" }
99
djls-project = { path = "crates/djls-project" }
10+
djls-semantic = { path = "crates/djls-semantic" }
1011
djls-server = { path = "crates/djls-server" }
1112
djls-templates = { path = "crates/djls-templates" }
1213
djls-workspace = { path = "crates/djls-workspace" }

crates/djls-ide/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "djls-ide"
3+
version = "0.0.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
djls-project = { workspace = true }
8+
djls-semantic = { workspace = true }
9+
djls-templates = { workspace = true }
10+
djls-workspace = { workspace = true }
11+
12+
salsa = { workspace = true }
13+
tower-lsp-server = { workspace = true }
14+
15+
[lints]
16+
workspace = true

crates/djls-server/src/completions.rs renamed to crates/djls-ide/src/completions.rs

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,16 @@
44
//! and generating appropriate completion items for Django templates.
55
66
use djls_project::TemplateTags;
7-
use djls_semantic::generate_partial_snippet;
8-
use djls_semantic::generate_snippet_for_tag_with_end;
97
use djls_semantic::ArgType;
108
use djls_semantic::SimpleArgType;
119
use djls_semantic::TagSpecs;
1210
use djls_workspace::FileKind;
1311
use djls_workspace::PositionEncoding;
1412
use djls_workspace::TextDocument;
15-
use tower_lsp_server::lsp_types::CompletionItem;
16-
use tower_lsp_server::lsp_types::CompletionItemKind;
17-
use tower_lsp_server::lsp_types::Documentation;
18-
use tower_lsp_server::lsp_types::InsertTextFormat;
19-
use tower_lsp_server::lsp_types::Position;
20-
use tower_lsp_server::lsp_types::Range;
21-
use tower_lsp_server::lsp_types::TextEdit;
13+
use tower_lsp_server::lsp_types;
14+
15+
use crate::snippets::generate_partial_snippet;
16+
use crate::snippets::generate_snippet_for_tag_with_end;
2217

2318
/// Tracks what closing characters are needed to complete a template tag.
2419
///
@@ -96,15 +91,16 @@ pub struct LineInfo {
9691
}
9792

9893
/// Main entry point for handling completion requests
94+
#[must_use]
9995
pub fn handle_completion(
10096
document: &TextDocument,
101-
position: Position,
97+
position: lsp_types::Position,
10298
encoding: PositionEncoding,
10399
file_kind: FileKind,
104100
template_tags: Option<&TemplateTags>,
105101
tag_specs: Option<&TagSpecs>,
106102
supports_snippets: bool,
107-
) -> Vec<CompletionItem> {
103+
) -> Vec<lsp_types::CompletionItem> {
108104
// Only handle template files
109105
if file_kind != FileKind::Template {
110106
return Vec::new();
@@ -135,7 +131,7 @@ pub fn handle_completion(
135131
/// Extract line information from document at given position
136132
fn get_line_info(
137133
document: &TextDocument,
138-
position: Position,
134+
position: lsp_types::Position,
139135
encoding: PositionEncoding,
140136
) -> Option<LineInfo> {
141137
let content = document.content();
@@ -290,10 +286,10 @@ fn generate_template_completions(
290286
template_tags: Option<&TemplateTags>,
291287
tag_specs: Option<&TagSpecs>,
292288
supports_snippets: bool,
293-
position: Position,
289+
position: lsp_types::Position,
294290
line_text: &str,
295291
cursor_offset: usize,
296-
) -> Vec<CompletionItem> {
292+
) -> Vec<lsp_types::CompletionItem> {
297293
match context {
298294
TemplateCompletionContext::TagName {
299295
partial,
@@ -340,17 +336,17 @@ fn generate_template_completions(
340336

341337
/// Calculate the range to replace for a completion
342338
fn calculate_replacement_range(
343-
position: Position,
339+
position: lsp_types::Position,
344340
line_text: &str,
345341
cursor_offset: usize,
346342
partial_len: usize,
347343
closing: &ClosingBrace,
348-
) -> Range {
344+
) -> lsp_types::Range {
349345
// Start position: move back by the length of the partial text
350346
let start_col = position
351347
.character
352348
.saturating_sub(u32::try_from(partial_len).unwrap_or(0));
353-
let start = Position::new(position.line, start_col);
349+
let start = lsp_types::Position::new(position.line, start_col);
354350

355351
// End position: include auto-paired } if present
356352
let mut end_col = position.character;
@@ -361,9 +357,9 @@ fn calculate_replacement_range(
361357
end_col += 1;
362358
}
363359
}
364-
let end = Position::new(position.line, end_col);
360+
let end = lsp_types::Position::new(position.line, end_col);
365361

366-
Range::new(start, end)
362+
lsp_types::Range::new(start, end)
367363
}
368364

369365
/// Generate completions for tag names
@@ -375,10 +371,10 @@ fn generate_tag_name_completions(
375371
template_tags: Option<&TemplateTags>,
376372
tag_specs: Option<&TagSpecs>,
377373
supports_snippets: bool,
378-
position: Position,
374+
position: lsp_types::Position,
379375
line_text: &str,
380376
cursor_offset: usize,
381-
) -> Vec<CompletionItem> {
377+
) -> Vec<lsp_types::CompletionItem> {
382378
let Some(tags) = template_tags else {
383379
return Vec::new();
384380
};
@@ -413,14 +409,14 @@ fn generate_tag_name_completions(
413409
ClosingBrace::FullClose => {} // No closing needed
414410
}
415411

416-
completions.push(CompletionItem {
412+
completions.push(lsp_types::CompletionItem {
417413
label: end_tag.name.clone(),
418-
kind: Some(CompletionItemKind::KEYWORD),
414+
kind: Some(lsp_types::CompletionItemKind::KEYWORD),
419415
detail: Some(format!("End tag for {opener_name}")),
420416
text_edit: Some(tower_lsp_server::lsp_types::CompletionTextEdit::Edit(
421-
TextEdit::new(replacement_range, insert_text.clone()),
417+
lsp_types::TextEdit::new(replacement_range, insert_text.clone()),
422418
)),
423-
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
419+
insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
424420
filter_text: Some(end_tag.name.clone()),
425421
sort_text: Some(format!("0_{}", end_tag.name)), // Priority sort
426422
..Default::default()
@@ -464,7 +460,7 @@ fn generate_tag_name_completions(
464460
}
465461
}
466462

467-
(text, InsertTextFormat::SNIPPET)
463+
(text, lsp_types::InsertTextFormat::SNIPPET)
468464
}
469465
} else {
470466
// No spec found, use plain text
@@ -481,19 +477,21 @@ fn generate_tag_name_completions(
481477

482478
// Create completion item
483479
// Use SNIPPET kind when we're inserting a snippet, KEYWORD otherwise
484-
let kind = if matches!(insert_format, InsertTextFormat::SNIPPET) {
485-
CompletionItemKind::SNIPPET
480+
let kind = if matches!(insert_format, lsp_types::InsertTextFormat::SNIPPET) {
481+
lsp_types::CompletionItemKind::SNIPPET
486482
} else {
487-
CompletionItemKind::KEYWORD
483+
lsp_types::CompletionItemKind::KEYWORD
488484
};
489485

490-
let completion_item = CompletionItem {
486+
let completion_item = lsp_types::CompletionItem {
491487
label: tag.name().clone(),
492488
kind: Some(kind),
493489
detail: Some(format!("from {}", tag.library())),
494-
documentation: tag.doc().map(|doc| Documentation::String(doc.clone())),
490+
documentation: tag
491+
.doc()
492+
.map(|doc| lsp_types::Documentation::String(doc.clone())),
495493
text_edit: Some(tower_lsp_server::lsp_types::CompletionTextEdit::Edit(
496-
TextEdit::new(replacement_range, insert_text.clone()),
494+
lsp_types::TextEdit::new(replacement_range, insert_text.clone()),
497495
)),
498496
insert_text_format: Some(insert_format),
499497
filter_text: Some(tag.name().clone()),
@@ -519,7 +517,7 @@ fn generate_argument_completions(
519517
_template_tags: Option<&TemplateTags>,
520518
tag_specs: Option<&TagSpecs>,
521519
supports_snippets: bool,
522-
) -> Vec<CompletionItem> {
520+
) -> Vec<lsp_types::CompletionItem> {
523521
let Some(specs) = tag_specs else {
524522
return Vec::new();
525523
};
@@ -548,12 +546,12 @@ fn generate_argument_completions(
548546
ClosingBrace::FullClose => {} // No closing needed
549547
}
550548

551-
completions.push(CompletionItem {
549+
completions.push(lsp_types::CompletionItem {
552550
label: arg.name.clone(),
553-
kind: Some(CompletionItemKind::KEYWORD),
551+
kind: Some(lsp_types::CompletionItemKind::KEYWORD),
554552
detail: Some("literal argument".to_string()),
555553
insert_text: Some(insert_text),
556-
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
554+
insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
557555
..Default::default()
558556
});
559557
}
@@ -571,12 +569,12 @@ fn generate_argument_completions(
571569
ClosingBrace::FullClose => {} // No closing needed
572570
}
573571

574-
completions.push(CompletionItem {
572+
completions.push(lsp_types::CompletionItem {
575573
label: option.clone(),
576-
kind: Some(CompletionItemKind::ENUM_MEMBER),
574+
kind: Some(lsp_types::CompletionItemKind::ENUM_MEMBER),
577575
detail: Some(format!("choice for {}", arg.name)),
578576
insert_text: Some(insert_text),
579-
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
577+
insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
580578
..Default::default()
581579
});
582580
}
@@ -586,12 +584,12 @@ fn generate_argument_completions(
586584
// For variables, we could offer variable completions from context
587585
// For now, just provide a hint
588586
if partial.is_empty() {
589-
completions.push(CompletionItem {
587+
completions.push(lsp_types::CompletionItem {
590588
label: format!("<{}>", arg.name),
591-
kind: Some(CompletionItemKind::VARIABLE),
589+
kind: Some(lsp_types::CompletionItemKind::VARIABLE),
592590
detail: Some("variable argument".to_string()),
593591
insert_text: None, // Don't insert placeholder
594-
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
592+
insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
595593
..Default::default()
596594
});
597595
}
@@ -600,12 +598,12 @@ fn generate_argument_completions(
600598
// For strings, could offer template name completions
601599
// For now, just provide a hint
602600
if partial.is_empty() {
603-
completions.push(CompletionItem {
601+
completions.push(lsp_types::CompletionItem {
604602
label: format!("\"{}\"", arg.name),
605-
kind: Some(CompletionItemKind::TEXT),
603+
kind: Some(lsp_types::CompletionItemKind::TEXT),
606604
detail: Some("string argument".to_string()),
607605
insert_text: None, // Don't insert placeholder
608-
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
606+
insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
609607
..Default::default()
610608
});
611609
}
@@ -636,12 +634,12 @@ fn generate_argument_completions(
636634
"remaining arguments".to_string()
637635
};
638636

639-
completions.push(CompletionItem {
637+
completions.push(lsp_types::CompletionItem {
640638
label,
641-
kind: Some(CompletionItemKind::SNIPPET),
639+
kind: Some(lsp_types::CompletionItemKind::SNIPPET),
642640
detail: Some("Complete remaining arguments".to_string()),
643641
insert_text: Some(insert_text),
644-
insert_text_format: Some(InsertTextFormat::SNIPPET),
642+
insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
645643
sort_text: Some("zzz".to_string()), // Sort at the end
646644
..Default::default()
647645
});
@@ -656,7 +654,7 @@ fn generate_library_completions(
656654
partial: &str,
657655
closing: &ClosingBrace,
658656
template_tags: Option<&TemplateTags>,
659-
) -> Vec<CompletionItem> {
657+
) -> Vec<lsp_types::CompletionItem> {
660658
let Some(tags) = template_tags else {
661659
return Vec::new();
662660
};
@@ -680,12 +678,12 @@ fn generate_library_completions(
680678
ClosingBrace::FullClose => {} // No closing needed
681679
}
682680

683-
completions.push(CompletionItem {
681+
completions.push(lsp_types::CompletionItem {
684682
label: library.clone(),
685-
kind: Some(CompletionItemKind::MODULE),
683+
kind: Some(lsp_types::CompletionItemKind::MODULE),
686684
detail: Some("Django template library".to_string()),
687685
insert_text: Some(insert_text),
688-
insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
686+
insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
689687
filter_text: Some(library.clone()),
690688
..Default::default()
691689
});
@@ -700,7 +698,7 @@ fn build_plain_insert_for_tag(
700698
tag_name: &str,
701699
needs_space: bool,
702700
closing: &ClosingBrace,
703-
) -> (String, InsertTextFormat) {
701+
) -> (String, lsp_types::InsertTextFormat) {
704702
let mut insert_text = String::new();
705703

706704
// Add leading space if needed (cursor right after {%)
@@ -717,7 +715,7 @@ fn build_plain_insert_for_tag(
717715
ClosingBrace::FullClose => {} // No closing needed
718716
}
719717

720-
(insert_text, InsertTextFormat::PLAIN_TEXT)
718+
(insert_text, lsp_types::InsertTextFormat::PLAIN_TEXT)
721719
}
722720

723721
#[cfg(test)]
@@ -847,8 +845,15 @@ mod tests {
847845
closing: ClosingBrace::None,
848846
};
849847

850-
let completions =
851-
generate_template_completions(&context, None, None, false, Position::new(0, 0), "", 0);
848+
let completions = generate_template_completions(
849+
&context,
850+
None,
851+
None,
852+
false,
853+
lsp_types::Position::new(0, 0),
854+
"",
855+
0,
856+
);
852857

853858
assert!(completions.is_empty());
854859
}

0 commit comments

Comments
 (0)