mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-14 06:15:07 +00:00
wip
This commit is contained in:
parent
20163b50f8
commit
541200cbb1
6 changed files with 77 additions and 81 deletions
|
@ -1,8 +1,7 @@
|
||||||
|
use djls_workspace::{FileId, VfsSnapshot};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_lsp_server::lsp_types::{Position, Range};
|
use tower_lsp_server::lsp_types::{Position, Range};
|
||||||
use djls_workspace::{FileId, VfsSnapshot};
|
|
||||||
|
|
||||||
/// Document metadata container - no longer a Salsa input, just plain data
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TextDocument {
|
pub struct TextDocument {
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
|
@ -20,39 +19,50 @@ impl TextDocument {
|
||||||
file_id,
|
file_id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_id(&self) -> FileId {
|
pub fn file_id(&self) -> FileId {
|
||||||
self.file_id
|
self.file_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_content(&self, vfs: &VfsSnapshot) -> Option<Arc<str>> {
|
pub fn get_content(&self, vfs: &VfsSnapshot) -> Option<Arc<str>> {
|
||||||
vfs.get_text(self.file_id)
|
vfs.get_text(self.file_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_line(&self, vfs: &VfsSnapshot, line_index: &LineIndex, line: u32) -> Option<String> {
|
pub fn get_line(&self, vfs: &VfsSnapshot, line_index: &LineIndex, line: u32) -> Option<String> {
|
||||||
let content = self.get_content(vfs)?;
|
let content = self.get_content(vfs)?;
|
||||||
|
|
||||||
let line_start = *line_index.line_starts.get(line as usize)?;
|
let line_start = *line_index.line_starts.get(line as usize)?;
|
||||||
let line_end = line_index.line_starts
|
let line_end = line_index
|
||||||
|
.line_starts
|
||||||
.get(line as usize + 1)
|
.get(line as usize + 1)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(line_index.length);
|
.unwrap_or(line_index.length);
|
||||||
|
|
||||||
Some(content[line_start as usize..line_end as usize].to_string())
|
Some(content[line_start as usize..line_end as usize].to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_text_range(&self, vfs: &VfsSnapshot, line_index: &LineIndex, range: Range) -> Option<String> {
|
pub fn get_text_range(
|
||||||
|
&self,
|
||||||
|
vfs: &VfsSnapshot,
|
||||||
|
line_index: &LineIndex,
|
||||||
|
range: Range,
|
||||||
|
) -> Option<String> {
|
||||||
let content = self.get_content(vfs)?;
|
let content = self.get_content(vfs)?;
|
||||||
|
|
||||||
let start_offset = line_index.offset(range.start)? as usize;
|
let start_offset = line_index.offset(range.start)? as usize;
|
||||||
let end_offset = line_index.offset(range.end)? as usize;
|
let end_offset = line_index.offset(range.end)? as usize;
|
||||||
|
|
||||||
Some(content[start_offset..end_offset].to_string())
|
Some(content[start_offset..end_offset].to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_template_tag_context(&self, vfs: &VfsSnapshot, line_index: &LineIndex, position: Position) -> Option<TemplateTagContext> {
|
pub fn get_template_tag_context(
|
||||||
|
&self,
|
||||||
|
vfs: &VfsSnapshot,
|
||||||
|
line_index: &LineIndex,
|
||||||
|
position: Position,
|
||||||
|
) -> Option<TemplateTagContext> {
|
||||||
let content = self.get_content(vfs)?;
|
let content = self.get_content(vfs)?;
|
||||||
|
|
||||||
let start = line_index.line_starts.get(position.line as usize)?;
|
let start = line_index.line_starts.get(position.line as usize)?;
|
||||||
let end = line_index
|
let end = line_index
|
||||||
.line_starts
|
.line_starts
|
||||||
|
@ -136,16 +146,18 @@ impl LineIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the line text
|
// Find the line text
|
||||||
let next_line_start = self.line_starts.get(position.line as usize + 1)
|
let next_line_start = self
|
||||||
|
.line_starts
|
||||||
|
.get(position.line as usize + 1)
|
||||||
.copied()
|
.copied()
|
||||||
.unwrap_or(self.length);
|
.unwrap_or(self.length);
|
||||||
|
|
||||||
let line_text = text.get(*line_start_utf8 as usize..next_line_start as usize)?;
|
let line_text = text.get(*line_start_utf8 as usize..next_line_start as usize)?;
|
||||||
|
|
||||||
// Convert UTF-16 character offset to UTF-8 byte offset within the line
|
// Convert UTF-16 character offset to UTF-8 byte offset within the line
|
||||||
let mut utf16_pos = 0;
|
let mut utf16_pos = 0;
|
||||||
let mut utf8_pos = 0;
|
let mut utf8_pos = 0;
|
||||||
|
|
||||||
for c in line_text.chars() {
|
for c in line_text.chars() {
|
||||||
if utf16_pos >= position.character {
|
if utf16_pos >= position.character {
|
||||||
break;
|
break;
|
||||||
|
@ -153,7 +165,7 @@ impl LineIndex {
|
||||||
utf16_pos += u32::try_from(c.len_utf16()).unwrap_or(0);
|
utf16_pos += u32::try_from(c.len_utf16()).unwrap_or(0);
|
||||||
utf8_pos += u32::try_from(c.len_utf8()).unwrap_or(0);
|
utf8_pos += u32::try_from(c.len_utf8()).unwrap_or(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(line_start_utf8 + utf8_pos)
|
Some(line_start_utf8 + utf8_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,4 +229,3 @@ pub struct TemplateTagContext {
|
||||||
pub closing_brace: ClosingBrace,
|
pub closing_brace: ClosingBrace,
|
||||||
pub needs_leading_space: bool,
|
pub needs_leading_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,8 @@ impl Store {
|
||||||
.ok_or_else(|| anyhow!("Line index not found for: {}", uri_str))?;
|
.ok_or_else(|| anyhow!("Line index not found for: {}", uri_str))?;
|
||||||
|
|
||||||
// Apply text changes using the new function
|
// Apply text changes using the new function
|
||||||
let new_content = apply_text_changes(¤t_content, ¶ms.content_changes, line_index)?;
|
let new_content =
|
||||||
|
apply_text_changes(¤t_content, ¶ms.content_changes, line_index)?;
|
||||||
|
|
||||||
// Update TextDocument version
|
// Update TextDocument version
|
||||||
if let Some(document) = self.documents.get_mut(&uri_str) {
|
if let Some(document) = self.documents.get_mut(&uri_str) {
|
||||||
|
@ -317,11 +318,11 @@ fn apply_text_changes(
|
||||||
(Some(range_a), Some(range_b)) => {
|
(Some(range_a), Some(range_b)) => {
|
||||||
// Primary sort: by line (reverse)
|
// Primary sort: by line (reverse)
|
||||||
let line_cmp = range_b.start.line.cmp(&range_a.start.line);
|
let line_cmp = range_b.start.line.cmp(&range_a.start.line);
|
||||||
if line_cmp != std::cmp::Ordering::Equal {
|
if line_cmp == std::cmp::Ordering::Equal {
|
||||||
line_cmp
|
|
||||||
} else {
|
|
||||||
// Secondary sort: by character (reverse)
|
// Secondary sort: by character (reverse)
|
||||||
range_b.start.character.cmp(&range_a.start.character)
|
range_b.start.character.cmp(&range_a.start.character)
|
||||||
|
} else {
|
||||||
|
line_cmp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => std::cmp::Ordering::Equal,
|
_ => std::cmp::Ordering::Equal,
|
||||||
|
@ -333,14 +334,20 @@ fn apply_text_changes(
|
||||||
for change in &sorted_changes {
|
for change in &sorted_changes {
|
||||||
if let Some(range) = change.range {
|
if let Some(range) = change.range {
|
||||||
// Convert UTF-16 positions to UTF-8 offsets
|
// Convert UTF-16 positions to UTF-8 offsets
|
||||||
let start_offset = line_index.offset_utf16(range.start, &result)
|
let start_offset = line_index
|
||||||
|
.offset_utf16(range.start, &result)
|
||||||
.ok_or_else(|| anyhow!("Invalid start position: {:?}", range.start))?;
|
.ok_or_else(|| anyhow!("Invalid start position: {:?}", range.start))?;
|
||||||
let end_offset = line_index.offset_utf16(range.end, &result)
|
let end_offset = line_index
|
||||||
|
.offset_utf16(range.end, &result)
|
||||||
.ok_or_else(|| anyhow!("Invalid end position: {:?}", range.end))?;
|
.ok_or_else(|| anyhow!("Invalid end position: {:?}", range.end))?;
|
||||||
|
|
||||||
if start_offset as usize > result.len() || end_offset as usize > result.len() {
|
if start_offset as usize > result.len() || end_offset as usize > result.len() {
|
||||||
return Err(anyhow!("Offset out of bounds: start={}, end={}, len={}",
|
return Err(anyhow!(
|
||||||
start_offset, end_offset, result.len()));
|
"Offset out of bounds: start={}, end={}, len={}",
|
||||||
|
start_offset,
|
||||||
|
end_offset,
|
||||||
|
result.len()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the change
|
// Apply the change
|
||||||
|
@ -360,7 +367,7 @@ mod tests {
|
||||||
fn test_apply_single_character_insertion() {
|
fn test_apply_single_character_insertion() {
|
||||||
let content = "Hello world";
|
let content = "Hello world";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
range: Some(Range::new(Position::new(0, 6), Position::new(0, 6))),
|
range: Some(Range::new(Position::new(0, 6), Position::new(0, 6))),
|
||||||
range_length: None,
|
range_length: None,
|
||||||
|
@ -375,11 +382,11 @@ mod tests {
|
||||||
fn test_apply_single_character_deletion() {
|
fn test_apply_single_character_deletion() {
|
||||||
let content = "Hello world";
|
let content = "Hello world";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
range: Some(Range::new(Position::new(0, 5), Position::new(0, 6))),
|
range: Some(Range::new(Position::new(0, 5), Position::new(0, 6))),
|
||||||
range_length: None,
|
range_length: None,
|
||||||
text: "".to_string(),
|
text: String::new(),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = apply_text_changes(content, &changes, &line_index).unwrap();
|
let result = apply_text_changes(content, &changes, &line_index).unwrap();
|
||||||
|
@ -390,7 +397,7 @@ mod tests {
|
||||||
fn test_apply_multiple_changes_in_reverse_order() {
|
fn test_apply_multiple_changes_in_reverse_order() {
|
||||||
let content = "line 1\nline 2\nline 3";
|
let content = "line 1\nline 2\nline 3";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
// Insert "new " at position (1, 0) and "another " at position (0, 0)
|
// Insert "new " at position (1, 0) and "another " at position (0, 0)
|
||||||
let changes = vec![
|
let changes = vec![
|
||||||
TextDocumentContentChangeEvent {
|
TextDocumentContentChangeEvent {
|
||||||
|
@ -413,7 +420,7 @@ mod tests {
|
||||||
fn test_apply_multiline_replacement() {
|
fn test_apply_multiline_replacement() {
|
||||||
let content = "line 1\nline 2\nline 3";
|
let content = "line 1\nline 2\nline 3";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
range: Some(Range::new(Position::new(0, 0), Position::new(2, 6))),
|
range: Some(Range::new(Position::new(0, 0), Position::new(2, 6))),
|
||||||
range_length: None,
|
range_length: None,
|
||||||
|
@ -428,7 +435,7 @@ mod tests {
|
||||||
fn test_apply_full_document_replacement() {
|
fn test_apply_full_document_replacement() {
|
||||||
let content = "old content";
|
let content = "old content";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
range: None,
|
range: None,
|
||||||
range_length: None,
|
range_length: None,
|
||||||
|
@ -443,7 +450,7 @@ mod tests {
|
||||||
fn test_utf16_line_index_basic() {
|
fn test_utf16_line_index_basic() {
|
||||||
let content = "hello world";
|
let content = "hello world";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
// ASCII characters should have 1:1 UTF-8:UTF-16 mapping
|
// ASCII characters should have 1:1 UTF-8:UTF-16 mapping
|
||||||
let pos = Position::new(0, 6);
|
let pos = Position::new(0, 6);
|
||||||
let offset = line_index.offset_utf16(pos, content).unwrap();
|
let offset = line_index.offset_utf16(pos, content).unwrap();
|
||||||
|
@ -455,11 +462,11 @@ mod tests {
|
||||||
fn test_utf16_line_index_with_emoji() {
|
fn test_utf16_line_index_with_emoji() {
|
||||||
let content = "hello 👋 world";
|
let content = "hello 👋 world";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
// 👋 is 2 UTF-16 code units but 4 UTF-8 bytes
|
// 👋 is 2 UTF-16 code units but 4 UTF-8 bytes
|
||||||
let pos_after_emoji = Position::new(0, 8); // UTF-16 position after "hello 👋"
|
let pos_after_emoji = Position::new(0, 8); // UTF-16 position after "hello 👋"
|
||||||
let offset = line_index.offset_utf16(pos_after_emoji, content).unwrap();
|
let offset = line_index.offset_utf16(pos_after_emoji, content).unwrap();
|
||||||
|
|
||||||
// Should point to the space before "world"
|
// Should point to the space before "world"
|
||||||
assert_eq!(offset, 10); // UTF-8 byte offset
|
assert_eq!(offset, 10); // UTF-8 byte offset
|
||||||
assert_eq!(&content[10..11], " ");
|
assert_eq!(&content[10..11], " ");
|
||||||
|
@ -469,7 +476,7 @@ mod tests {
|
||||||
fn test_utf16_line_index_multiline() {
|
fn test_utf16_line_index_multiline() {
|
||||||
let content = "first line\nsecond line";
|
let content = "first line\nsecond line";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
let pos = Position::new(1, 7); // Position at 'l' in "line" on second line
|
let pos = Position::new(1, 7); // Position at 'l' in "line" on second line
|
||||||
let offset = line_index.offset_utf16(pos, content).unwrap();
|
let offset = line_index.offset_utf16(pos, content).unwrap();
|
||||||
assert_eq!(offset, 18); // 11 (first line + \n) + 7
|
assert_eq!(offset, 18); // 11 (first line + \n) + 7
|
||||||
|
@ -480,7 +487,7 @@ mod tests {
|
||||||
fn test_apply_changes_with_emoji() {
|
fn test_apply_changes_with_emoji() {
|
||||||
let content = "hello 👋 world";
|
let content = "hello 👋 world";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
// Insert text after the space following the emoji (UTF-16 position 9)
|
// Insert text after the space following the emoji (UTF-16 position 9)
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
range: Some(Range::new(Position::new(0, 9), Position::new(0, 9))),
|
range: Some(Range::new(Position::new(0, 9), Position::new(0, 9))),
|
||||||
|
@ -496,28 +503,28 @@ mod tests {
|
||||||
fn test_line_index_utf16_tracking() {
|
fn test_line_index_utf16_tracking() {
|
||||||
let content = "a👋b";
|
let content = "a👋b";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
// Check UTF-16 line starts are tracked correctly
|
// Check UTF-16 line starts are tracked correctly
|
||||||
assert_eq!(line_index.line_starts_utf16, vec![0]);
|
assert_eq!(line_index.line_starts_utf16, vec![0]);
|
||||||
assert_eq!(line_index.length_utf16, 4); // 'a' (1) + 👋 (2) + 'b' (1) = 4 UTF-16 units
|
assert_eq!(line_index.length_utf16, 4); // 'a' (1) + 👋 (2) + 'b' (1) = 4 UTF-16 units
|
||||||
assert_eq!(line_index.length, 6); // 'a' (1) + 👋 (4) + 'b' (1) = 6 UTF-8 bytes
|
assert_eq!(line_index.length, 6); // 'a' (1) + 👋 (4) + 'b' (1) = 6 UTF-8 bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edge_case_changes_at_boundaries() {
|
fn test_edge_case_changes_at_boundaries() {
|
||||||
let content = "abc";
|
let content = "abc";
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
|
|
||||||
// Insert at beginning
|
// Insert at beginning
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
range: Some(Range::new(Position::new(0, 0), Position::new(0, 0))),
|
range: Some(Range::new(Position::new(0, 0), Position::new(0, 0))),
|
||||||
range_length: None,
|
range_length: None,
|
||||||
text: "start".to_string(),
|
text: "start".to_string(),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = apply_text_changes(content, &changes, &line_index).unwrap();
|
let result = apply_text_changes(content, &changes, &line_index).unwrap();
|
||||||
assert_eq!(result, "startabc");
|
assert_eq!(result, "startabc");
|
||||||
|
|
||||||
// Insert at end
|
// Insert at end
|
||||||
let line_index = LineIndex::new(content);
|
let line_index = LineIndex::new(content);
|
||||||
let changes = vec![TextDocumentContentChangeEvent {
|
let changes = vec![TextDocumentContentChangeEvent {
|
||||||
|
@ -525,7 +532,7 @@ mod tests {
|
||||||
range_length: None,
|
range_length: None,
|
||||||
text: "end".to_string(),
|
text: "end".to_string(),
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let result = apply_text_changes(content, &changes, &line_index).unwrap();
|
let result = apply_text_changes(content, &changes, &line_index).unwrap();
|
||||||
assert_eq!(result, "abcend");
|
assert_eq!(result, "abcend");
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
use salsa::Setter;
|
use salsa::Setter;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
db::{
|
db::{parse_template, template_errors, Database, SourceFile, TemplateAst, TemplateLoaderOrder},
|
||||||
parse_template, template_errors, Database, FileKindMini, SourceFile, TemplateAst,
|
|
||||||
TemplateLoaderOrder,
|
|
||||||
},
|
|
||||||
vfs::{FileKind, VfsSnapshot},
|
vfs::{FileKind, VfsSnapshot},
|
||||||
FileId,
|
FileId,
|
||||||
};
|
};
|
||||||
|
@ -69,16 +66,12 @@ impl FileStore {
|
||||||
pub fn apply_vfs_snapshot(&mut self, snap: &VfsSnapshot) {
|
pub fn apply_vfs_snapshot(&mut self, snap: &VfsSnapshot) {
|
||||||
for (id, rec) in &snap.files {
|
for (id, rec) in &snap.files {
|
||||||
let new_text = snap.get_text(*id).unwrap_or_else(|| Arc::<str>::from(""));
|
let new_text = snap.get_text(*id).unwrap_or_else(|| Arc::<str>::from(""));
|
||||||
let new_kind = match rec.meta.kind {
|
let new_kind = rec.meta.kind;
|
||||||
FileKind::Python => FileKindMini::Python,
|
|
||||||
FileKind::Template => FileKindMini::Template,
|
|
||||||
FileKind::Other => FileKindMini::Other,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(sf) = self.files.get(id) {
|
if let Some(sf) = self.files.get(id) {
|
||||||
// Update if changed — avoid touching Salsa when not needed
|
// Update if changed — avoid touching Salsa when not needed
|
||||||
if sf.kind(&self.db) != new_kind {
|
if sf.kind(&self.db) != new_kind {
|
||||||
sf.set_kind(&mut self.db).to(new_kind.clone());
|
sf.set_kind(&mut self.db).to(new_kind);
|
||||||
}
|
}
|
||||||
if sf.text(&self.db).as_ref() != &*new_text {
|
if sf.text(&self.db).as_ref() != &*new_text {
|
||||||
sf.set_text(&mut self.db).to(new_text.clone());
|
sf.set_text(&mut self.db).to(new_text.clone());
|
||||||
|
@ -100,7 +93,7 @@ impl FileStore {
|
||||||
/// Get the file kind classification by its [`FileId`].
|
/// Get the file kind classification by its [`FileId`].
|
||||||
///
|
///
|
||||||
/// Returns `None` if the file is not tracked in the [`FileStore`].
|
/// Returns `None` if the file is not tracked in the [`FileStore`].
|
||||||
pub fn file_kind(&self, id: FileId) -> Option<FileKindMini> {
|
pub fn file_kind(&self, id: FileId) -> Option<FileKind> {
|
||||||
self.files.get(&id).map(|sf| sf.kind(&self.db))
|
self.files.get(&id).map(|sf| sf.kind(&self.db))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ use std::sync::Arc;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::vfs::FileKind;
|
||||||
|
|
||||||
/// Salsa database root for workspace
|
/// Salsa database root for workspace
|
||||||
///
|
///
|
||||||
/// The [`Database`] provides default storage and, in tests, captures Salsa events for
|
/// The [`Database`] provides default storage and, in tests, captures Salsa events for
|
||||||
|
@ -49,21 +51,6 @@ impl Default for Database {
|
||||||
#[salsa::db]
|
#[salsa::db]
|
||||||
impl salsa::Database for Database {}
|
impl salsa::Database for Database {}
|
||||||
|
|
||||||
/// Minimal classification for analysis routing.
|
|
||||||
///
|
|
||||||
/// [`FileKindMini`] provides a lightweight categorization of files to determine which
|
|
||||||
/// analysis pipelines should process them. This is the Salsa-side representation
|
|
||||||
/// of file types, mapped from the VFS layer's `vfs::FileKind`.
|
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
|
|
||||||
pub enum FileKindMini {
|
|
||||||
/// Python source file (.py)
|
|
||||||
Python,
|
|
||||||
/// Django template file (.html, .jinja, etc.)
|
|
||||||
Template,
|
|
||||||
/// Other file types not requiring specialized analysis
|
|
||||||
Other,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a single file's classification and current content.
|
/// Represents a single file's classification and current content.
|
||||||
///
|
///
|
||||||
/// [`SourceFile`] is a Salsa input entity that tracks both the file's type (for routing
|
/// [`SourceFile`] is a Salsa input entity that tracks both the file's type (for routing
|
||||||
|
@ -72,7 +59,7 @@ pub enum FileKindMini {
|
||||||
#[salsa::input]
|
#[salsa::input]
|
||||||
pub struct SourceFile {
|
pub struct SourceFile {
|
||||||
/// The file's classification for analysis routing
|
/// The file's classification for analysis routing
|
||||||
pub kind: FileKindMini,
|
pub kind: FileKind,
|
||||||
/// The current text content of the file
|
/// The current text content of the file
|
||||||
#[returns(ref)]
|
#[returns(ref)]
|
||||||
pub text: Arc<str>,
|
pub text: Arc<str>,
|
||||||
|
@ -113,7 +100,7 @@ pub struct TemplateAst {
|
||||||
#[salsa::tracked]
|
#[salsa::tracked]
|
||||||
pub fn parse_template(db: &dyn salsa::Database, file: SourceFile) -> Option<Arc<TemplateAst>> {
|
pub fn parse_template(db: &dyn salsa::Database, file: SourceFile) -> Option<Arc<TemplateAst>> {
|
||||||
// Only parse template files
|
// Only parse template files
|
||||||
if file.kind(db) != FileKindMini::Template {
|
if file.kind(db) != FileKind::Template {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +148,7 @@ mod tests {
|
||||||
|
|
||||||
// Create a template file
|
// Create a template file
|
||||||
let template_content: Arc<str> = Arc::from("{% if user %}Hello {{ user.name }}{% endif %}");
|
let template_content: Arc<str> = Arc::from("{% if user %}Hello {{ user.name }}{% endif %}");
|
||||||
let file = SourceFile::new(&db, FileKindMini::Template, template_content.clone());
|
let file = SourceFile::new(&db, FileKind::Template, template_content.clone());
|
||||||
|
|
||||||
// First parse - should execute the parsing
|
// First parse - should execute the parsing
|
||||||
let ast1 = parse_template(&db, file);
|
let ast1 = parse_template(&db, file);
|
||||||
|
@ -181,7 +168,7 @@ mod tests {
|
||||||
|
|
||||||
// Create a template file
|
// Create a template file
|
||||||
let template_content1: Arc<str> = Arc::from("{% if user %}Hello{% endif %}");
|
let template_content1: Arc<str> = Arc::from("{% if user %}Hello{% endif %}");
|
||||||
let file = SourceFile::new(&db, FileKindMini::Template, template_content1);
|
let file = SourceFile::new(&db, FileKind::Template, template_content1);
|
||||||
|
|
||||||
// First parse
|
// First parse
|
||||||
let ast1 = parse_template(&db, file);
|
let ast1 = parse_template(&db, file);
|
||||||
|
@ -206,7 +193,7 @@ mod tests {
|
||||||
|
|
||||||
// Create a Python file
|
// Create a Python file
|
||||||
let python_content: Arc<str> = Arc::from("def hello():\n print('Hello')");
|
let python_content: Arc<str> = Arc::from("def hello():\n print('Hello')");
|
||||||
let file = SourceFile::new(&db, FileKindMini::Python, python_content);
|
let file = SourceFile::new(&db, FileKind::Python, python_content);
|
||||||
|
|
||||||
// Should return None for non-template files
|
// Should return None for non-template files
|
||||||
let ast = parse_template(&db, file);
|
let ast = parse_template(&db, file);
|
||||||
|
@ -223,7 +210,7 @@ mod tests {
|
||||||
|
|
||||||
// Create a template with an error (unclosed tag)
|
// Create a template with an error (unclosed tag)
|
||||||
let template_content: Arc<str> = Arc::from("{% if user %}Hello {{ user.name }");
|
let template_content: Arc<str> = Arc::from("{% if user %}Hello {{ user.name }");
|
||||||
let file = SourceFile::new(&db, FileKindMini::Template, template_content);
|
let file = SourceFile::new(&db, FileKind::Template, template_content);
|
||||||
|
|
||||||
// Get errors
|
// Get errors
|
||||||
let errors1 = template_errors(&db, file);
|
let errors1 = template_errors(&db, file);
|
||||||
|
|
|
@ -5,8 +5,7 @@ mod watcher;
|
||||||
|
|
||||||
pub use bridge::FileStore;
|
pub use bridge::FileStore;
|
||||||
pub use db::{
|
pub use db::{
|
||||||
parse_template, template_errors, Database, FileKindMini, SourceFile, TemplateAst,
|
parse_template, template_errors, Database, SourceFile, TemplateAst, TemplateLoaderOrder,
|
||||||
TemplateLoaderOrder,
|
|
||||||
};
|
};
|
||||||
pub use vfs::{FileKind, FileMeta, FileRecord, Revision, TextSource, Vfs, VfsSnapshot};
|
pub use vfs::{FileKind, FileMeta, FileRecord, Revision, TextSource, Vfs, VfsSnapshot};
|
||||||
pub use watcher::{VfsWatcher, WatchConfig, WatchEvent};
|
pub use watcher::{VfsWatcher, WatchConfig, WatchEvent};
|
||||||
|
|
|
@ -317,4 +317,3 @@ mod tests {
|
||||||
assert_ne!(deleted, renamed);
|
assert_ne!(deleted, renamed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue