mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-04 01:10:42 +00:00
wip
This commit is contained in:
parent
943ab19790
commit
79a0c4cb4f
2 changed files with 37 additions and 168 deletions
|
@ -1,136 +1,29 @@
|
|||
use salsa::Database;
|
||||
use tower_lsp_server::lsp_types::DidOpenTextDocumentParams;
|
||||
use tower_lsp_server::lsp_types::Position;
|
||||
use tower_lsp_server::lsp_types::Range;
|
||||
use tower_lsp_server::lsp_types::TextDocumentContentChangeEvent;
|
||||
|
||||
#[salsa::input(debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextDocument {
|
||||
#[returns(ref)]
|
||||
#[allow(dead_code)]
|
||||
pub uri: String,
|
||||
#[returns(ref)]
|
||||
pub contents: String,
|
||||
#[returns(ref)]
|
||||
pub index: LineIndex,
|
||||
pub version: i32,
|
||||
pub language_id: LanguageId,
|
||||
}
|
||||
|
||||
impl TextDocument {
|
||||
pub fn from_did_open_params(db: &dyn Database, params: &DidOpenTextDocumentParams) -> Self {
|
||||
pub fn new(uri: String, version: i32, language_id: LanguageId) -> Self {
|
||||
Self {
|
||||
uri,
|
||||
version,
|
||||
language_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_did_open_params(params: &DidOpenTextDocumentParams) -> Self {
|
||||
let uri = params.text_document.uri.to_string();
|
||||
let contents = params.text_document.text.clone();
|
||||
let version = params.text_document.version;
|
||||
let language_id = LanguageId::from(params.text_document.language_id.as_str());
|
||||
|
||||
let index = LineIndex::new(&contents);
|
||||
TextDocument::new(db, uri, contents, index, version, language_id)
|
||||
}
|
||||
|
||||
pub fn with_changes(
|
||||
self,
|
||||
db: &dyn Database,
|
||||
changes: &[TextDocumentContentChangeEvent],
|
||||
new_version: i32,
|
||||
) -> Self {
|
||||
let mut new_contents = self.contents(db).to_string();
|
||||
|
||||
for change in changes {
|
||||
if let Some(range) = change.range {
|
||||
let index = LineIndex::new(&new_contents);
|
||||
|
||||
if let (Some(start_offset), Some(end_offset)) = (
|
||||
index.offset(range.start).map(|o| o as usize),
|
||||
index.offset(range.end).map(|o| o as usize),
|
||||
) {
|
||||
let mut updated_content = String::with_capacity(
|
||||
new_contents.len() - (end_offset - start_offset) + change.text.len(),
|
||||
);
|
||||
|
||||
updated_content.push_str(&new_contents[..start_offset]);
|
||||
updated_content.push_str(&change.text);
|
||||
updated_content.push_str(&new_contents[end_offset..]);
|
||||
|
||||
new_contents = updated_content;
|
||||
}
|
||||
} else {
|
||||
// Full document update
|
||||
new_contents.clone_from(&change.text);
|
||||
}
|
||||
}
|
||||
|
||||
let index = LineIndex::new(&new_contents);
|
||||
TextDocument::new(
|
||||
db,
|
||||
self.uri(db).to_string(),
|
||||
new_contents,
|
||||
index,
|
||||
new_version,
|
||||
self.language_id(db),
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_text(self, db: &dyn Database) -> String {
|
||||
self.contents(db).to_string()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_text_range(self, db: &dyn Database, range: Range) -> Option<String> {
|
||||
let index = self.index(db);
|
||||
let start = index.offset(range.start)? as usize;
|
||||
let end = index.offset(range.end)? as usize;
|
||||
let contents = self.contents(db);
|
||||
Some(contents[start..end].to_string())
|
||||
}
|
||||
|
||||
pub fn get_line(self, db: &dyn Database, line: u32) -> Option<String> {
|
||||
let index = self.index(db);
|
||||
let start = index.line_starts.get(line as usize)?;
|
||||
let end = index
|
||||
.line_starts
|
||||
.get(line as usize + 1)
|
||||
.copied()
|
||||
.unwrap_or(index.length);
|
||||
|
||||
let contents = self.contents(db);
|
||||
Some(contents[*start as usize..end as usize].to_string())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn line_count(self, db: &dyn Database) -> usize {
|
||||
self.index(db).line_starts.len()
|
||||
}
|
||||
|
||||
pub fn get_template_tag_context(
|
||||
self,
|
||||
db: &dyn Database,
|
||||
position: Position,
|
||||
) -> Option<TemplateTagContext> {
|
||||
let line = self.get_line(db, position.line)?;
|
||||
let char_pos: usize = position.character.try_into().ok()?;
|
||||
let prefix = &line[..char_pos];
|
||||
let rest_of_line = &line[char_pos..];
|
||||
let rest_trimmed = rest_of_line.trim_start();
|
||||
|
||||
prefix.rfind("{%").map(|tag_start| {
|
||||
// Check if we're immediately after {% with no space
|
||||
let needs_leading_space = prefix.ends_with("{%");
|
||||
|
||||
let closing_brace = if rest_trimmed.starts_with("%}") {
|
||||
ClosingBrace::FullClose
|
||||
} else if rest_trimmed.starts_with('}') {
|
||||
ClosingBrace::PartialClose
|
||||
} else {
|
||||
ClosingBrace::None
|
||||
};
|
||||
|
||||
TemplateTagContext {
|
||||
partial_tag: prefix[tag_start + 2..].trim().to_string(),
|
||||
closing_brace,
|
||||
needs_leading_space,
|
||||
}
|
||||
})
|
||||
TextDocument::new(uri, version, language_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,3 +117,4 @@ pub struct TemplateTagContext {
|
|||
pub closing_brace: ClosingBrace,
|
||||
pub needs_leading_space: bool,
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ impl Store {
|
|||
false
|
||||
}
|
||||
}
|
||||
pub fn handle_did_open(&mut self, db: &dyn Database, params: &DidOpenTextDocumentParams) {
|
||||
pub fn handle_did_open(&mut self, _db: &dyn Database, params: &DidOpenTextDocumentParams) {
|
||||
// Only process files within the workspace
|
||||
if !self.is_workspace_file(¶ms.text_document.uri) {
|
||||
// Silently ignore files outside workspace
|
||||
|
@ -85,7 +85,7 @@ impl Store {
|
|||
}
|
||||
}
|
||||
|
||||
let document = TextDocument::from_did_open_params(db, params);
|
||||
let document = TextDocument::from_did_open_params(params);
|
||||
|
||||
self.add_document(document, uri.clone());
|
||||
self.versions.insert(uri, version);
|
||||
|
@ -93,7 +93,7 @@ impl Store {
|
|||
|
||||
pub fn handle_did_change(
|
||||
&mut self,
|
||||
db: &dyn Database,
|
||||
_db: &dyn Database,
|
||||
params: &DidChangeTextDocumentParams,
|
||||
) -> Result<()> {
|
||||
// Only process files within the workspace
|
||||
|
@ -110,21 +110,9 @@ impl Store {
|
|||
if let Ok(relative_path) = absolute_path.strip_prefix(&self.root_path) {
|
||||
let relative_path_str = relative_path.to_string_lossy();
|
||||
|
||||
// Read current content from VFS
|
||||
let current_content = match self.vfs.read_to_string(&relative_path_str) {
|
||||
Ok(content) => content,
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to read from VFS, falling back to TextDocument: {}", e);
|
||||
// Fallback to existing TextDocument approach
|
||||
let document = self
|
||||
.get_document(&uri)
|
||||
.ok_or_else(|| anyhow!("Document not found: {}", uri))?;
|
||||
let new_document = document.with_changes(db, ¶ms.content_changes, version);
|
||||
self.documents.insert(uri.clone(), new_document);
|
||||
self.versions.insert(uri, version);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
// Read current content from VFS (single source of truth)
|
||||
let current_content = self.vfs.read_to_string(&relative_path_str)
|
||||
.map_err(|e| anyhow!("Failed to read from VFS: {}", e))?;
|
||||
|
||||
// Apply text changes to VFS content
|
||||
let updated_content = self.apply_changes_to_content(current_content, ¶ms.content_changes)?;
|
||||
|
@ -134,31 +122,18 @@ impl Store {
|
|||
eprintln!("Warning: Failed to write to VFS: {}", e);
|
||||
}
|
||||
|
||||
// Create new TextDocument with updated content for backward compatibility
|
||||
let index = LineIndex::new(&updated_content);
|
||||
let language_id = self.get_document(&uri)
|
||||
.map(|doc| doc.language_id(db))
|
||||
.unwrap_or(LanguageId::HtmlDjango);
|
||||
|
||||
let new_document = TextDocument::new(db, uri.clone(), updated_content.clone(), index, version, language_id);
|
||||
self.documents.insert(uri.clone(), new_document);
|
||||
// Update document metadata (just version)
|
||||
if let Some(document) = self.documents.get_mut(&uri) {
|
||||
document.version = version;
|
||||
}
|
||||
self.versions.insert(uri, version);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to original implementation if path conversion fails
|
||||
let document = self
|
||||
.get_document(&uri)
|
||||
.ok_or_else(|| anyhow!("Document not found: {}", uri))?;
|
||||
|
||||
let new_document = document.with_changes(db, ¶ms.content_changes, version);
|
||||
|
||||
self.documents.insert(uri.clone(), new_document);
|
||||
self.versions.insert(uri, version);
|
||||
|
||||
Ok(())
|
||||
// If path conversion fails, this is an error since we need VFS
|
||||
Err(anyhow!("Document not in workspace or path conversion failed: {}", uri))
|
||||
}
|
||||
|
||||
pub fn handle_did_close(&mut self, params: &DidCloseTextDocumentParams) {
|
||||
|
@ -266,12 +241,12 @@ impl Store {
|
|||
#[allow(dead_code)]
|
||||
pub fn get_documents_by_language<'db>(
|
||||
&'db self,
|
||||
db: &'db dyn Database,
|
||||
_db: &'db dyn Database,
|
||||
language_id: LanguageId,
|
||||
) -> impl Iterator<Item = &'db TextDocument> + 'db {
|
||||
self.documents
|
||||
.values()
|
||||
.filter(move |doc| doc.language_id(db) == language_id)
|
||||
.filter(move |doc| doc.language_id == language_id)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -286,14 +261,14 @@ impl Store {
|
|||
|
||||
pub fn get_completions(
|
||||
&self,
|
||||
db: &dyn Database,
|
||||
_db: &dyn Database,
|
||||
uri: &str,
|
||||
position: Position,
|
||||
tags: &TemplateTags,
|
||||
) -> Option<CompletionResponse> {
|
||||
let document = self.get_document(uri)?;
|
||||
|
||||
if document.language_id(db) != LanguageId::HtmlDjango {
|
||||
if document.language_id != LanguageId::HtmlDjango {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -307,21 +282,21 @@ impl Store {
|
|||
match self.vfs.read_to_string(&relative_path_str) {
|
||||
Ok(vfs_content) => vfs_content,
|
||||
Err(_) => {
|
||||
// Fallback to document content if VFS read fails
|
||||
document.contents(db).to_string()
|
||||
// Return None if we can't read from VFS
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Path not within workspace, use document content
|
||||
document.contents(db).to_string()
|
||||
// Path not within workspace
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
// URI parsing failed, use document content
|
||||
document.contents(db).to_string()
|
||||
// URI parsing failed
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
// URI parsing failed, use document content
|
||||
document.contents(db).to_string()
|
||||
// URI parsing failed
|
||||
return None;
|
||||
};
|
||||
|
||||
// Use standalone analyzer instead of salsa-tracked method
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue