mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-07-09 21:54:59 +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
|
@ -10,7 +10,6 @@ default = []
|
|||
[dependencies]
|
||||
pyo3 = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
tower-lsp-server = { workspace = true, features = ["proposed"] }
|
||||
|
||||
which = "7.0.1"
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ pyo3 = { workspace = true }
|
|||
salsa = { workspace = true }
|
||||
serde = { 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]
|
||||
djls-dev = { workspace = true }
|
||||
|
|
|
@ -25,13 +25,6 @@ pub struct 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) {
|
||||
let uri = params.text_document.uri.to_string();
|
||||
let version = params.text_document.version;
|
||||
|
|
|
@ -5,4 +5,25 @@ mod server;
|
|||
mod session;
|
||||
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.
|
||||
/// - `Box`: Allocates the `Future` on the heap.
|
||||
/// - `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 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.
|
||||
/// - `-> TaskFuture`: Specifies that calling the closure produces the type-erased future.
|
||||
/// - `+ 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>;
|
||||
|
||||
/// 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
|
||||
/// they were received. This ensures sequential processing of background tasks.
|
||||
///
|
||||
/// The queue is cloneable (`Arc`-based internally), allowing multiple producers
|
||||
/// to submit tasks concurrently.
|
||||
/// The queue runs within our single-threaded runtime but maintains compatibility
|
||||
/// 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.
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -19,6 +19,10 @@ pub struct Session {
|
|||
/// 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.
|
||||
///
|
||||
/// 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:
|
||||
/// ```rust,ignore
|
||||
/// // Use the StorageHandle in Session
|
||||
|
@ -41,20 +45,6 @@ pub struct 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> {
|
||||
&mut self.client_capabilities
|
||||
}
|
||||
|
@ -83,14 +73,6 @@ impl Session {
|
|||
&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
|
||||
///
|
||||
/// This creates a usable database from the handle, which can be used
|
||||
|
|
|
@ -5,7 +5,6 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
tower-lsp-server = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = "0.8"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use tower_lsp_server::lsp_types;
|
||||
|
||||
use crate::ast::AstError;
|
||||
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]
|
||||
pub fn code(&self) -> &'static str {
|
||||
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 title: String,
|
||||
pub edit: String,
|
||||
|
|
|
@ -6,7 +6,6 @@ mod tagspecs;
|
|||
mod tokens;
|
||||
|
||||
use ast::Ast;
|
||||
pub use error::to_lsp_diagnostic;
|
||||
pub use error::QuickFix;
|
||||
pub use error::TemplateError;
|
||||
use lexer::Lexer;
|
||||
|
|
|
@ -21,10 +21,7 @@ djls-server = { workspace = true }
|
|||
|
||||
anyhow = { workspace = true }
|
||||
pyo3 = { workspace = true }
|
||||
pyo3-async-runtimes = { workspace = true, features = ["tokio-runtime"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tower-lsp-server = { workspace = true }
|
||||
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use djls_server::DjangoLanguageServer;
|
||||
use tower_lsp_server::LspService;
|
||||
use tower_lsp_server::Server;
|
||||
|
||||
use crate::args::Args;
|
||||
use crate::commands::Command;
|
||||
|
@ -23,28 +20,14 @@ enum ConnectionType {
|
|||
|
||||
impl Command for Serve {
|
||||
fn execute(&self, _args: &Args) -> Result<Exit> {
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
djls_server::run()?;
|
||||
|
||||
let exit_status = 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;
|
||||
|
||||
// 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)
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue