mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-10 04:16:30 +00:00
remove left over Database code from workspace crate (#197)
This commit is contained in:
parent
fd0a1c160f
commit
318a395d6f
4 changed files with 47 additions and 165 deletions
|
@ -7,6 +7,8 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
#[cfg(test)]
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use djls_templates::db::Db as TemplateDb;
|
use djls_templates::db::Db as TemplateDb;
|
||||||
|
@ -31,15 +33,49 @@ pub struct DjangoDatabase {
|
||||||
files: Arc<DashMap<PathBuf, SourceFile>>,
|
files: Arc<DashMap<PathBuf, SourceFile>>,
|
||||||
|
|
||||||
storage: salsa::Storage<Self>,
|
storage: salsa::Storage<Self>,
|
||||||
|
|
||||||
|
// The logs are only used for testing and demonstrating reuse:
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
logs: Arc<Mutex<Option<Vec<String>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl Default for DjangoDatabase {
|
||||||
|
fn default() -> Self {
|
||||||
|
use djls_workspace::InMemoryFileSystem;
|
||||||
|
|
||||||
|
let logs = <Arc<Mutex<Option<Vec<String>>>>>::default();
|
||||||
|
Self {
|
||||||
|
fs: Arc::new(InMemoryFileSystem::new()),
|
||||||
|
files: Arc::new(DashMap::new()),
|
||||||
|
storage: salsa::Storage::new(Some(Box::new({
|
||||||
|
let logs = logs.clone();
|
||||||
|
move |event| {
|
||||||
|
eprintln!("Event: {event:?}");
|
||||||
|
// Log interesting events, if logging is enabled
|
||||||
|
if let Some(logs) = &mut *logs.lock().unwrap() {
|
||||||
|
// only log interesting events
|
||||||
|
if let salsa::EventKind::WillExecute { .. } = event.kind {
|
||||||
|
logs.push(format!("Event: {event:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))),
|
||||||
|
logs,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DjangoDatabase {
|
impl DjangoDatabase {
|
||||||
/// Create a new [`DjangoDatabase`] with the given file system and file map.
|
/// Create a new [`DjangoDatabase`] with the given file system and file map.
|
||||||
pub fn new(file_system: Arc<dyn FileSystem>, files: Arc<DashMap<PathBuf, SourceFile>>) -> Self {
|
pub fn new(file_system: Arc<dyn FileSystem>, files: Arc<DashMap<PathBuf, SourceFile>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
storage: salsa::Storage::new(None),
|
|
||||||
fs: file_system,
|
fs: file_system,
|
||||||
files,
|
files,
|
||||||
|
storage: salsa::Storage::new(None),
|
||||||
|
#[cfg(test)]
|
||||||
|
logs: Arc::new(Mutex::new(None)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,13 +21,7 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
#[cfg(test)]
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use salsa::Setter;
|
|
||||||
|
|
||||||
use crate::FileKind;
|
use crate::FileKind;
|
||||||
use crate::FileSystem;
|
use crate::FileSystem;
|
||||||
|
@ -45,156 +39,6 @@ pub trait Db: salsa::Database {
|
||||||
fn read_file_content(&self, path: &Path) -> std::io::Result<String>;
|
fn read_file_content(&self, path: &Path) -> std::io::Result<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Temporary concrete database for workspace.
|
|
||||||
///
|
|
||||||
/// This will be moved to the server crate in the refactoring.
|
|
||||||
/// For now, it's kept here to avoid breaking existing code.
|
|
||||||
#[salsa::db]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Database {
|
|
||||||
storage: salsa::Storage<Self>,
|
|
||||||
|
|
||||||
/// File system for reading file content (checks buffers first, then disk).
|
|
||||||
fs: Arc<dyn FileSystem>,
|
|
||||||
|
|
||||||
/// Maps paths to [`SourceFile`] entities for O(1) lookup.
|
|
||||||
files: Arc<DashMap<PathBuf, SourceFile>>,
|
|
||||||
|
|
||||||
// The logs are only used for testing and demonstrating reuse:
|
|
||||||
#[cfg(test)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
logs: Arc<Mutex<Option<Vec<String>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl Default for Database {
|
|
||||||
fn default() -> Self {
|
|
||||||
use crate::fs::InMemoryFileSystem;
|
|
||||||
|
|
||||||
let logs = <Arc<Mutex<Option<Vec<String>>>>>::default();
|
|
||||||
Self {
|
|
||||||
storage: salsa::Storage::new(Some(Box::new({
|
|
||||||
let logs = logs.clone();
|
|
||||||
move |event| {
|
|
||||||
eprintln!("Event: {event:?}");
|
|
||||||
// Log interesting events, if logging is enabled
|
|
||||||
if let Some(logs) = &mut *logs.lock().unwrap() {
|
|
||||||
// only log interesting events
|
|
||||||
if let salsa::EventKind::WillExecute { .. } = event.kind {
|
|
||||||
logs.push(format!("Event: {event:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))),
|
|
||||||
fs: Arc::new(InMemoryFileSystem::new()),
|
|
||||||
files: Arc::new(DashMap::new()),
|
|
||||||
logs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Database {
|
|
||||||
pub fn new(file_system: Arc<dyn FileSystem>, files: Arc<DashMap<PathBuf, SourceFile>>) -> Self {
|
|
||||||
Self {
|
|
||||||
storage: salsa::Storage::new(None),
|
|
||||||
fs: file_system,
|
|
||||||
files,
|
|
||||||
#[cfg(test)]
|
|
||||||
logs: Arc::new(Mutex::new(None)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read file content through the file system.
|
|
||||||
pub fn read_file_content(&self, path: &Path) -> std::io::Result<String> {
|
|
||||||
self.fs.read_to_string(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get an existing [`SourceFile`] for the given path without creating it.
|
|
||||||
///
|
|
||||||
/// Returns `Some(SourceFile)` if the file is already tracked, `None` otherwise.
|
|
||||||
/// This method uses an immutable reference and doesn't modify the database.
|
|
||||||
pub fn get_file(&self, path: &Path) -> Option<SourceFile> {
|
|
||||||
self.files.get(path).map(|file_ref| *file_ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get or create a [`SourceFile`] for the given path.
|
|
||||||
///
|
|
||||||
/// Files are created with an initial revision of 0 and tracked in the [`Database`]'s
|
|
||||||
/// `DashMap`. The `Arc` ensures cheap cloning while maintaining thread safety.
|
|
||||||
///
|
|
||||||
/// ## Thread Safety
|
|
||||||
///
|
|
||||||
/// This method is inherently thread-safe despite the check-then-create pattern because
|
|
||||||
/// it requires `&mut self`, ensuring exclusive access to the Database. Only one thread
|
|
||||||
/// can call this method at a time due to Rust's ownership rules.
|
|
||||||
pub fn get_or_create_file(&mut self, path: &PathBuf) -> SourceFile {
|
|
||||||
if let Some(file_ref) = self.files.get(path) {
|
|
||||||
// Copy the value (SourceFile is Copy)
|
|
||||||
// The guard drops automatically, no need for explicit drop
|
|
||||||
return *file_ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
// File doesn't exist, so we need to create it
|
|
||||||
let kind = FileKind::from_path(path);
|
|
||||||
let file = SourceFile::new(self, kind, Arc::from(path.to_string_lossy().as_ref()), 0);
|
|
||||||
|
|
||||||
self.files.insert(path.clone(), file);
|
|
||||||
file
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if a file is being tracked without creating it.
|
|
||||||
///
|
|
||||||
/// This is primarily used for testing to verify that files have been
|
|
||||||
/// created without affecting the database state.
|
|
||||||
pub fn has_file(&self, path: &Path) -> bool {
|
|
||||||
self.files.contains_key(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Touch a file to mark it as modified, triggering re-evaluation of dependent queries.
|
|
||||||
///
|
|
||||||
/// Similar to Unix `touch`, this updates the file's revision number to signal
|
|
||||||
/// that cached query results depending on this file should be invalidated.
|
|
||||||
///
|
|
||||||
/// This is typically called when:
|
|
||||||
/// - A file is opened in the editor (if it was previously cached from disk)
|
|
||||||
/// - A file's content is modified
|
|
||||||
/// - A file's buffer is closed (reverting to disk content)
|
|
||||||
pub fn touch_file(&mut self, path: &Path) {
|
|
||||||
// Get the file if it exists
|
|
||||||
let Some(file_ref) = self.files.get(path) else {
|
|
||||||
tracing::debug!("File {} not tracked, skipping touch", path.display());
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let file = *file_ref;
|
|
||||||
drop(file_ref); // Explicitly drop to release the lock
|
|
||||||
|
|
||||||
let current_rev = file.revision(self);
|
|
||||||
let new_rev = current_rev + 1;
|
|
||||||
file.set_revision(self).to(new_rev);
|
|
||||||
|
|
||||||
tracing::debug!(
|
|
||||||
"Touched {}: revision {} -> {}",
|
|
||||||
path.display(),
|
|
||||||
current_rev,
|
|
||||||
new_rev
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[salsa::db]
|
|
||||||
impl salsa::Database for Database {}
|
|
||||||
|
|
||||||
#[salsa::db]
|
|
||||||
impl Db for Database {
|
|
||||||
fn fs(&self) -> Arc<dyn FileSystem> {
|
|
||||||
self.fs.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_file_content(&self, path: &Path) -> std::io::Result<String> {
|
|
||||||
self.fs.read_to_string(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a single file without storing its content.
|
/// Represents a single file without storing its content.
|
||||||
///
|
///
|
||||||
/// [`SourceFile`] is a Salsa input entity that tracks a file's path, revision, and
|
/// [`SourceFile`] is a Salsa input entity that tracks a file's path, revision, and
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
//! This module provides the [`FileSystem`] trait that abstracts file I/O operations.
|
//! This module provides the [`FileSystem`] trait that abstracts file I/O operations.
|
||||||
//! This allows the LSP to work with both real files and in-memory overlays.
|
//! This allows the LSP to work with both real files and in-memory overlays.
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
#[cfg(test)]
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -19,13 +17,12 @@ pub trait FileSystem: Send + Sync {
|
||||||
fn exists(&self, path: &Path) -> bool;
|
fn exists(&self, path: &Path) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub struct InMemoryFileSystem {
|
pub struct InMemoryFileSystem {
|
||||||
files: HashMap<PathBuf, String>,
|
files: HashMap<PathBuf, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
impl InMemoryFileSystem {
|
impl InMemoryFileSystem {
|
||||||
|
#[must_use]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
files: HashMap::new(),
|
files: HashMap::new(),
|
||||||
|
@ -37,7 +34,12 @@ impl InMemoryFileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl Default for InMemoryFileSystem {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FileSystem for InMemoryFileSystem {
|
impl FileSystem for InMemoryFileSystem {
|
||||||
fn read_to_string(&self, path: &Path) -> io::Result<String> {
|
fn read_to_string(&self, path: &Path) -> io::Result<String> {
|
||||||
self.files
|
self.files
|
||||||
|
@ -81,7 +83,7 @@ impl FileSystem for OsFileSystem {
|
||||||
/// This ensures consistent behavior across all filesystem operations for
|
/// This ensures consistent behavior across all filesystem operations for
|
||||||
/// buffered files that may not yet be saved to disk.
|
/// buffered files that may not yet be saved to disk.
|
||||||
///
|
///
|
||||||
/// This type is used by the [`Database`](crate::db::Database) to ensure all file reads go
|
/// This type is used by the database implementations to ensure all file reads go
|
||||||
/// through the buffer system first.
|
/// through the buffer system first.
|
||||||
pub struct WorkspaceFileSystem {
|
pub struct WorkspaceFileSystem {
|
||||||
/// In-memory buffers that take precedence over disk files
|
/// In-memory buffers that take precedence over disk files
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//! # Key Components
|
//! # Key Components
|
||||||
//!
|
//!
|
||||||
//! - [`Buffers`] - Thread-safe storage for open documents
|
//! - [`Buffers`] - Thread-safe storage for open documents
|
||||||
//! - [`Database`] - Salsa database for incremental computation
|
//! - [`Db`] - Database trait for file system access (concrete impl in server crate)
|
||||||
//! - [`TextDocument`] - LSP document representation with efficient indexing
|
//! - [`TextDocument`] - LSP document representation with efficient indexing
|
||||||
//! - [`FileSystem`] - Abstraction layer for file operations with overlay support
|
//! - [`FileSystem`] - Abstraction layer for file operations with overlay support
|
||||||
//! - [`paths`] - Consistent URL/path conversion utilities
|
//! - [`paths`] - Consistent URL/path conversion utilities
|
||||||
|
@ -24,12 +24,12 @@ mod workspace;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
pub use buffers::Buffers;
|
pub use buffers::Buffers;
|
||||||
pub use db::Database;
|
|
||||||
pub use db::Db;
|
pub use db::Db;
|
||||||
pub use db::SourceFile;
|
pub use db::SourceFile;
|
||||||
pub use document::TextDocument;
|
pub use document::TextDocument;
|
||||||
pub use encoding::PositionEncoding;
|
pub use encoding::PositionEncoding;
|
||||||
pub use fs::FileSystem;
|
pub use fs::FileSystem;
|
||||||
|
pub use fs::InMemoryFileSystem;
|
||||||
pub use fs::OsFileSystem;
|
pub use fs::OsFileSystem;
|
||||||
pub use fs::WorkspaceFileSystem;
|
pub use fs::WorkspaceFileSystem;
|
||||||
pub use language::LanguageId;
|
pub use language::LanguageId;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue