mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-04 17:30:37 +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::DidOpenTextDocumentParams;
|
||||||
use tower_lsp_server::lsp_types::Position;
|
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 {
|
pub struct TextDocument {
|
||||||
#[returns(ref)]
|
#[allow(dead_code)]
|
||||||
pub uri: String,
|
pub uri: String,
|
||||||
#[returns(ref)]
|
|
||||||
pub contents: String,
|
|
||||||
#[returns(ref)]
|
|
||||||
pub index: LineIndex,
|
|
||||||
pub version: i32,
|
pub version: i32,
|
||||||
pub language_id: LanguageId,
|
pub language_id: LanguageId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextDocument {
|
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 uri = params.text_document.uri.to_string();
|
||||||
let contents = params.text_document.text.clone();
|
|
||||||
let version = params.text_document.version;
|
let version = params.text_document.version;
|
||||||
let language_id = LanguageId::from(params.text_document.language_id.as_str());
|
let language_id = LanguageId::from(params.text_document.language_id.as_str());
|
||||||
|
|
||||||
let index = LineIndex::new(&contents);
|
TextDocument::new(uri, version, language_id)
|
||||||
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,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,3 +117,4 @@ pub struct TemplateTagContext {
|
||||||
pub closing_brace: ClosingBrace,
|
pub closing_brace: ClosingBrace,
|
||||||
pub needs_leading_space: bool,
|
pub needs_leading_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl Store {
|
||||||
false
|
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
|
// Only process files within the workspace
|
||||||
if !self.is_workspace_file(¶ms.text_document.uri) {
|
if !self.is_workspace_file(¶ms.text_document.uri) {
|
||||||
// Silently ignore files outside workspace
|
// 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.add_document(document, uri.clone());
|
||||||
self.versions.insert(uri, version);
|
self.versions.insert(uri, version);
|
||||||
|
@ -93,7 +93,7 @@ impl Store {
|
||||||
|
|
||||||
pub fn handle_did_change(
|
pub fn handle_did_change(
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &dyn Database,
|
_db: &dyn Database,
|
||||||
params: &DidChangeTextDocumentParams,
|
params: &DidChangeTextDocumentParams,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Only process files within the workspace
|
// Only process files within the workspace
|
||||||
|
@ -110,21 +110,9 @@ impl Store {
|
||||||
if let Ok(relative_path) = absolute_path.strip_prefix(&self.root_path) {
|
if let Ok(relative_path) = absolute_path.strip_prefix(&self.root_path) {
|
||||||
let relative_path_str = relative_path.to_string_lossy();
|
let relative_path_str = relative_path.to_string_lossy();
|
||||||
|
|
||||||
// Read current content from VFS
|
// Read current content from VFS (single source of truth)
|
||||||
let current_content = match self.vfs.read_to_string(&relative_path_str) {
|
let current_content = self.vfs.read_to_string(&relative_path_str)
|
||||||
Ok(content) => content,
|
.map_err(|e| anyhow!("Failed to read from VFS: {}", e))?;
|
||||||
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(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply text changes to VFS content
|
// Apply text changes to VFS content
|
||||||
let updated_content = self.apply_changes_to_content(current_content, ¶ms.content_changes)?;
|
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);
|
eprintln!("Warning: Failed to write to VFS: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new TextDocument with updated content for backward compatibility
|
// Update document metadata (just version)
|
||||||
let index = LineIndex::new(&updated_content);
|
if let Some(document) = self.documents.get_mut(&uri) {
|
||||||
let language_id = self.get_document(&uri)
|
document.version = version;
|
||||||
.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);
|
|
||||||
self.versions.insert(uri, version);
|
self.versions.insert(uri, version);
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to original implementation if path conversion fails
|
// If path conversion fails, this is an error since we need VFS
|
||||||
let document = self
|
Err(anyhow!("Document not in workspace or path conversion failed: {}", uri))
|
||||||
.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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_did_close(&mut self, params: &DidCloseTextDocumentParams) {
|
pub fn handle_did_close(&mut self, params: &DidCloseTextDocumentParams) {
|
||||||
|
@ -266,12 +241,12 @@ impl Store {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_documents_by_language<'db>(
|
pub fn get_documents_by_language<'db>(
|
||||||
&'db self,
|
&'db self,
|
||||||
db: &'db dyn Database,
|
_db: &'db dyn Database,
|
||||||
language_id: LanguageId,
|
language_id: LanguageId,
|
||||||
) -> impl Iterator<Item = &'db TextDocument> + 'db {
|
) -> impl Iterator<Item = &'db TextDocument> + 'db {
|
||||||
self.documents
|
self.documents
|
||||||
.values()
|
.values()
|
||||||
.filter(move |doc| doc.language_id(db) == language_id)
|
.filter(move |doc| doc.language_id == language_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -286,14 +261,14 @@ impl Store {
|
||||||
|
|
||||||
pub fn get_completions(
|
pub fn get_completions(
|
||||||
&self,
|
&self,
|
||||||
db: &dyn Database,
|
_db: &dyn Database,
|
||||||
uri: &str,
|
uri: &str,
|
||||||
position: Position,
|
position: Position,
|
||||||
tags: &TemplateTags,
|
tags: &TemplateTags,
|
||||||
) -> Option<CompletionResponse> {
|
) -> Option<CompletionResponse> {
|
||||||
let document = self.get_document(uri)?;
|
let document = self.get_document(uri)?;
|
||||||
|
|
||||||
if document.language_id(db) != LanguageId::HtmlDjango {
|
if document.language_id != LanguageId::HtmlDjango {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,21 +282,21 @@ impl Store {
|
||||||
match self.vfs.read_to_string(&relative_path_str) {
|
match self.vfs.read_to_string(&relative_path_str) {
|
||||||
Ok(vfs_content) => vfs_content,
|
Ok(vfs_content) => vfs_content,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Fallback to document content if VFS read fails
|
// Return None if we can't read from VFS
|
||||||
document.contents(db).to_string()
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Path not within workspace, use document content
|
// Path not within workspace
|
||||||
document.contents(db).to_string()
|
return None;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// URI parsing failed, use document content
|
// URI parsing failed
|
||||||
document.contents(db).to_string()
|
return None;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// URI parsing failed, use document content
|
// URI parsing failed
|
||||||
document.contents(db).to_string()
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use standalone analyzer instead of salsa-tracked method
|
// Use standalone analyzer instead of salsa-tracked method
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue