mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-03 17:00:33 +00:00
migrate all async tokio to server & swap to single-thread runtime (#149)
This commit is contained in:
parent
c29b268326
commit
def9fba2b6
12 changed files with 45 additions and 103 deletions
|
@ -13,15 +13,12 @@ djls-templates = { path = "crates/djls-templates" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
pyo3 = "0.24"
|
pyo3 = "0.24"
|
||||||
pyo3-async-runtimes = "0.24"
|
|
||||||
pyo3-build-config = "0.24"
|
pyo3-build-config = "0.24"
|
||||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" }
|
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "7edce6e248f35c8114b4b021cdb474a3fb2813b3" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tempfile = "3.19"
|
tempfile = "3.19"
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
tokio = { version = "1.42", features = ["full"] }
|
|
||||||
tower-lsp-server = { version = "0.21", features = ["proposed"] }
|
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
pedantic = { level = "warn", priority = -1 }
|
pedantic = { level = "warn", priority = -1 }
|
||||||
|
|
|
@ -10,7 +10,6 @@ default = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pyo3 = { workspace = true }
|
pyo3 = { workspace = true }
|
||||||
salsa = { workspace = true }
|
salsa = { workspace = true }
|
||||||
tower-lsp-server = { workspace = true, features = ["proposed"] }
|
|
||||||
|
|
||||||
which = "7.0.1"
|
which = "7.0.1"
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,10 @@ pyo3 = { workspace = true }
|
||||||
salsa = { workspace = true }
|
salsa = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true }
|
|
||||||
tower-lsp-server = { workspace = true }
|
|
||||||
|
|
||||||
percent-encoding = "2.3"
|
percent-encoding = "2.3.1"
|
||||||
|
tokio = { version = "1.45.0", features = ["full"] }
|
||||||
|
tower-lsp-server = { version = "0.21.1", features = ["proposed"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
djls-dev = { workspace = true }
|
djls-dev = { workspace = true }
|
||||||
|
|
|
@ -25,13 +25,6 @@ pub struct Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store {
|
impl Store {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
documents: HashMap::new(),
|
|
||||||
versions: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_did_open(&mut self, db: &dyn Database, params: &DidOpenTextDocumentParams) {
|
pub fn handle_did_open(&mut self, db: &dyn Database, params: &DidOpenTextDocumentParams) {
|
||||||
let uri = params.text_document.uri.to_string();
|
let uri = params.text_document.uri.to_string();
|
||||||
let version = params.text_document.version;
|
let version = params.text_document.version;
|
||||||
|
|
|
@ -5,4 +5,25 @@ mod server;
|
||||||
mod session;
|
mod session;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
||||||
pub use crate::server::DjangoLanguageServer;
|
use anyhow::Result;
|
||||||
|
use tower_lsp_server::LspService;
|
||||||
|
use tower_lsp_server::Server;
|
||||||
|
|
||||||
|
use crate::server::DjangoLanguageServer;
|
||||||
|
|
||||||
|
pub fn run() -> Result<()> {
|
||||||
|
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
runtime.block_on(async {
|
||||||
|
let stdin = tokio::io::stdin();
|
||||||
|
let stdout = tokio::io::stdout();
|
||||||
|
|
||||||
|
let (service, socket) = LspService::build(DjangoLanguageServer::new).finish();
|
||||||
|
|
||||||
|
Server::new(stdin, stdout, socket).serve(service).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -19,7 +19,9 @@ use tokio::sync::oneshot;
|
||||||
/// required for self-referential `async` blocks.
|
/// required for self-referential `async` blocks.
|
||||||
/// - `Box`: Allocates the `Future` on the heap.
|
/// - `Box`: Allocates the `Future` on the heap.
|
||||||
/// - `dyn Future`: Type erasure - hides the specific concrete `Future` type.
|
/// - `dyn Future`: Type erasure - hides the specific concrete `Future` type.
|
||||||
/// - `+ Send`: Ensures the `Future` can be safely sent across threads.
|
/// - `+ Send`: Ensures the `Future` can be safely sent across threads and required
|
||||||
|
/// by the tower-lsp-server LSP server trait bounds, even in our single-threaded
|
||||||
|
/// runtime.
|
||||||
type TaskFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
|
type TaskFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
|
||||||
|
|
||||||
/// Type alias for a type-erased, heap-allocated, Send-able closure that,
|
/// Type alias for a type-erased, heap-allocated, Send-able closure that,
|
||||||
|
@ -34,7 +36,8 @@ type TaskFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
|
||||||
/// arguments.
|
/// arguments.
|
||||||
/// - `-> TaskFuture`: Specifies that calling the closure produces the type-erased future.
|
/// - `-> TaskFuture`: Specifies that calling the closure produces the type-erased future.
|
||||||
/// - `+ Send + 'static`: Ensures the closure itself can be safely sent across
|
/// - `+ Send + 'static`: Ensures the closure itself can be safely sent across
|
||||||
/// threads and has a static lifetime (doesn't borrow short-lived data).
|
/// threads and has a static lifetime (doesn't borrow short-lived data). Required
|
||||||
|
/// for compatibility with our async runtime and LSP traits.
|
||||||
type TaskClosure = Box<dyn FnOnce() -> TaskFuture + Send + 'static>;
|
type TaskClosure = Box<dyn FnOnce() -> TaskFuture + Send + 'static>;
|
||||||
|
|
||||||
/// A simple asynchronous task queue for sequential execution.
|
/// A simple asynchronous task queue for sequential execution.
|
||||||
|
@ -43,8 +46,9 @@ type TaskClosure = Box<dyn FnOnce() -> TaskFuture + Send + 'static>;
|
||||||
/// to a dedicated worker task which executes them one at a time in the order
|
/// to a dedicated worker task which executes them one at a time in the order
|
||||||
/// they were received. This ensures sequential processing of background tasks.
|
/// they were received. This ensures sequential processing of background tasks.
|
||||||
///
|
///
|
||||||
/// The queue is cloneable (`Arc`-based internally), allowing multiple producers
|
/// The queue runs within our single-threaded runtime but maintains compatibility
|
||||||
/// to submit tasks concurrently.
|
/// with the Send+Sync requirements of the LSP. This provides the benefits of
|
||||||
|
/// simpler execution while maintaining the required trait bounds.
|
||||||
///
|
///
|
||||||
/// Shutdown is handled gracefully when the last `Queue` instance is dropped.
|
/// Shutdown is handled gracefully when the last `Queue` instance is dropped.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -19,6 +19,10 @@ pub struct Session {
|
||||||
/// where we're using the `StorageHandle` to create a thread-safe handle that can be
|
/// where we're using the `StorageHandle` to create a thread-safe handle that can be
|
||||||
/// shared between threads. When we need to use it, we clone the handle to get a new reference.
|
/// shared between threads. When we need to use it, we clone the handle to get a new reference.
|
||||||
///
|
///
|
||||||
|
/// This handle allows us to create database instances as needed.
|
||||||
|
/// Even though we're using a single-threaded runtime, we still need
|
||||||
|
/// this to be thread-safe because of LSP trait requirements.
|
||||||
|
///
|
||||||
/// Usage:
|
/// Usage:
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// // Use the StorageHandle in Session
|
/// // Use the StorageHandle in Session
|
||||||
|
@ -41,20 +45,6 @@ pub struct Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Session {
|
impl Session {
|
||||||
pub fn new(client_capabilities: ClientCapabilities) -> Self {
|
|
||||||
Self {
|
|
||||||
client_capabilities: Some(client_capabilities),
|
|
||||||
project: None,
|
|
||||||
documents: Store::new(),
|
|
||||||
settings: Settings::default(),
|
|
||||||
db_handle: StorageHandle::new(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client_capabilities(&self) -> Option<&ClientCapabilities> {
|
|
||||||
self.client_capabilities.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn client_capabilities_mut(&mut self) -> &mut Option<ClientCapabilities> {
|
pub fn client_capabilities_mut(&mut self) -> &mut Option<ClientCapabilities> {
|
||||||
&mut self.client_capabilities
|
&mut self.client_capabilities
|
||||||
}
|
}
|
||||||
|
@ -83,14 +73,6 @@ impl Session {
|
||||||
&mut self.settings
|
&mut self.settings
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the raw database handle from the session
|
|
||||||
///
|
|
||||||
/// Note: In most cases, you'll want to use `db()` instead to get a usable
|
|
||||||
/// database instance directly.
|
|
||||||
pub fn db_handle(&self) -> &StorageHandle<ServerDatabase> {
|
|
||||||
&self.db_handle
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a database instance directly from the session
|
/// Get a database instance directly from the session
|
||||||
///
|
///
|
||||||
/// This creates a usable database from the handle, which can be used
|
/// This creates a usable database from the handle, which can be used
|
||||||
|
|
|
@ -5,7 +5,6 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tower-lsp-server = { workspace = true }
|
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tower_lsp_server::lsp_types;
|
|
||||||
|
|
||||||
use crate::ast::AstError;
|
use crate::ast::AstError;
|
||||||
use crate::ast::Span;
|
use crate::ast::Span;
|
||||||
|
@ -52,17 +51,6 @@ impl TemplateError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn severity(&self) -> lsp_types::DiagnosticSeverity {
|
|
||||||
match self {
|
|
||||||
TemplateError::Lexer(_) | TemplateError::Parser(_) => {
|
|
||||||
lsp_types::DiagnosticSeverity::ERROR
|
|
||||||
}
|
|
||||||
TemplateError::Validation(_) => lsp_types::DiagnosticSeverity::WARNING,
|
|
||||||
_ => lsp_types::DiagnosticSeverity::INFORMATION,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn code(&self) -> &'static str {
|
pub fn code(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
@ -75,26 +63,6 @@ impl TemplateError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_lsp_diagnostic(error: &TemplateError, _source: &str) -> lsp_types::Diagnostic {
|
|
||||||
let range = error.span().map_or_else(lsp_types::Range::default, |span| {
|
|
||||||
let start = lsp_types::Position::new(0, span.start());
|
|
||||||
let end = lsp_types::Position::new(0, span.start() + span.length());
|
|
||||||
lsp_types::Range::new(start, end)
|
|
||||||
});
|
|
||||||
|
|
||||||
lsp_types::Diagnostic {
|
|
||||||
range,
|
|
||||||
severity: Some(error.severity()),
|
|
||||||
code: Some(lsp_types::NumberOrString::String(error.code().to_string())),
|
|
||||||
code_description: None,
|
|
||||||
source: Some("djls-template".to_string()),
|
|
||||||
message: error.to_string(),
|
|
||||||
related_information: None,
|
|
||||||
tags: None,
|
|
||||||
data: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct QuickFix {
|
pub struct QuickFix {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub edit: String,
|
pub edit: String,
|
||||||
|
|
|
@ -6,7 +6,6 @@ mod tagspecs;
|
||||||
mod tokens;
|
mod tokens;
|
||||||
|
|
||||||
use ast::Ast;
|
use ast::Ast;
|
||||||
pub use error::to_lsp_diagnostic;
|
|
||||||
pub use error::QuickFix;
|
pub use error::QuickFix;
|
||||||
pub use error::TemplateError;
|
pub use error::TemplateError;
|
||||||
use lexer::Lexer;
|
use lexer::Lexer;
|
||||||
|
|
|
@ -21,10 +21,7 @@ djls-server = { workspace = true }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
pyo3 = { workspace = true }
|
pyo3 = { workspace = true }
|
||||||
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
|
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true }
|
|
||||||
tower-lsp-server = { workspace = true }
|
|
||||||
|
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use djls_server::DjangoLanguageServer;
|
|
||||||
use tower_lsp_server::LspService;
|
|
||||||
use tower_lsp_server::Server;
|
|
||||||
|
|
||||||
use crate::args::Args;
|
use crate::args::Args;
|
||||||
use crate::commands::Command;
|
use crate::commands::Command;
|
||||||
|
@ -23,28 +20,14 @@ enum ConnectionType {
|
||||||
|
|
||||||
impl Command for Serve {
|
impl Command for Serve {
|
||||||
fn execute(&self, _args: &Args) -> Result<Exit> {
|
fn execute(&self, _args: &Args) -> Result<Exit> {
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
djls_server::run()?;
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let exit_status = runtime.block_on(async {
|
// Exit here instead of returning control to the `Cli`, for ... reasons?
|
||||||
let stdin = tokio::io::stdin();
|
// If we don't exit here, ~~~ something ~~~ goes on with PyO3 (I assume)
|
||||||
let stdout = tokio::io::stdout();
|
// or the Python entrypoint wrapper to indefinitely hang the CLI and keep
|
||||||
|
// the process running
|
||||||
let (service, socket) = LspService::build(DjangoLanguageServer::new).finish();
|
Exit::success()
|
||||||
|
.with_message("Server completed successfully")
|
||||||
Server::new(stdin, stdout, socket).serve(service).await;
|
.process_exit()
|
||||||
|
|
||||||
// Exit here instead of returning control to the `Cli`, for ... reasons?
|
|
||||||
// If we don't exit here, ~~~ something ~~~ goes on with PyO3 (I assume)
|
|
||||||
// or the Python entrypoint wrapper to indefinitely hang the CLI and keep
|
|
||||||
// the process running
|
|
||||||
Exit::success()
|
|
||||||
.with_message("Server completed successfully")
|
|
||||||
.process_exit()
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(exit_status)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue