mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-27 12:29:30 +00:00
remove extra server layer in front of tower-lsp's server (#49)
This commit is contained in:
parent
9a2c0e51c7
commit
f848798699
5 changed files with 157 additions and 331 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
||||||
- name: Install dependencies and build
|
- name: Install dependencies and build
|
||||||
run: |
|
run: |
|
||||||
uv sync --frozen
|
uv sync --frozen
|
||||||
maturin build
|
uv run maturin build
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
|
|
|
@ -1,117 +1,19 @@
|
||||||
mod documents;
|
mod documents;
|
||||||
mod notifier;
|
|
||||||
mod server;
|
mod server;
|
||||||
mod tasks;
|
mod tasks;
|
||||||
|
|
||||||
use crate::notifier::TowerLspNotifier;
|
use crate::server::DjangoLanguageServer;
|
||||||
use crate::server::{DjangoLanguageServer, LspNotification, LspRequest};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use server::LspResponse;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tower_lsp::jsonrpc::Result as LspResult;
|
|
||||||
use tower_lsp::lsp_types::*;
|
|
||||||
use tower_lsp::{LanguageServer, LspService, Server};
|
|
||||||
|
|
||||||
struct TowerLspBackend {
|
|
||||||
server: Arc<RwLock<DjangoLanguageServer>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tower_lsp::async_trait]
|
|
||||||
impl LanguageServer for TowerLspBackend {
|
|
||||||
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
|
|
||||||
match self
|
|
||||||
.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_request(LspRequest::Initialize(params))
|
|
||||||
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())?
|
|
||||||
{
|
|
||||||
LspResponse::Initialize(result) => Ok(result),
|
|
||||||
_ => Err(tower_lsp::jsonrpc::Error::internal_error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn initialized(&self, params: InitializedParams) {
|
|
||||||
if let Err(e) = self
|
|
||||||
.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_notification(LspNotification::Initialized(params))
|
|
||||||
{
|
|
||||||
eprintln!("Error handling initialized: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn shutdown(&self) -> LspResult<()> {
|
|
||||||
self.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_notification(LspNotification::Shutdown)
|
|
||||||
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn did_open(&self, params: DidOpenTextDocumentParams) {
|
|
||||||
if let Err(e) = self
|
|
||||||
.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_notification(LspNotification::DidOpenTextDocument(params))
|
|
||||||
{
|
|
||||||
eprintln!("Error handling document open: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
|
||||||
if let Err(e) = self
|
|
||||||
.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_notification(LspNotification::DidChangeTextDocument(params))
|
|
||||||
{
|
|
||||||
eprintln!("Error handling document change: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn did_close(&self, params: DidCloseTextDocumentParams) {
|
|
||||||
if let Err(e) = self
|
|
||||||
.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_notification(LspNotification::DidCloseTextDocument(params))
|
|
||||||
{
|
|
||||||
eprintln!("Error handling document close: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn completion(&self, params: CompletionParams) -> LspResult<Option<CompletionResponse>> {
|
|
||||||
match self
|
|
||||||
.server
|
|
||||||
.write()
|
|
||||||
.await
|
|
||||||
.handle_request(LspRequest::Completion(params))
|
|
||||||
.map_err(|_| tower_lsp::jsonrpc::Error::internal_error())?
|
|
||||||
{
|
|
||||||
LspResponse::Completion(result) => Ok(result),
|
|
||||||
_ => Err(tower_lsp::jsonrpc::Error::internal_error()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn serve() -> Result<()> {
|
pub async fn serve() -> Result<()> {
|
||||||
let stdin = tokio::io::stdin();
|
let stdin = tokio::io::stdin();
|
||||||
let stdout = tokio::io::stdout();
|
let stdout = tokio::io::stdout();
|
||||||
|
|
||||||
let (service, socket) = LspService::build(|client| {
|
let (service, socket) = tower_lsp::LspService::build(DjangoLanguageServer::new).finish();
|
||||||
let notifier = Box::new(TowerLspNotifier::new(client.clone()));
|
|
||||||
let server = DjangoLanguageServer::new(notifier);
|
|
||||||
TowerLspBackend {
|
|
||||||
server: Arc::new(RwLock::new(server)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.finish();
|
|
||||||
|
|
||||||
Server::new(stdin, stdout, socket).serve(service).await;
|
tower_lsp::Server::new(stdin, stdout, socket)
|
||||||
|
.serve(service)
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use tower_lsp::async_trait;
|
|
||||||
use tower_lsp::lsp_types::Diagnostic;
|
|
||||||
use tower_lsp::lsp_types::MessageActionItem;
|
|
||||||
use tower_lsp::lsp_types::MessageType;
|
|
||||||
use tower_lsp::lsp_types::Url;
|
|
||||||
use tower_lsp::Client;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Notifier: Send + Sync {
|
|
||||||
fn log_message(&self, typ: MessageType, msg: &str) -> Result<()>;
|
|
||||||
fn show_message(&self, typ: MessageType, msg: &str) -> Result<()>;
|
|
||||||
async fn show_message_request(
|
|
||||||
&self,
|
|
||||||
typ: MessageType,
|
|
||||||
msg: &str,
|
|
||||||
actions: Option<Vec<MessageActionItem>>,
|
|
||||||
) -> Result<Option<MessageActionItem>>;
|
|
||||||
fn publish_diagnostics(
|
|
||||||
&self,
|
|
||||||
uri: Url,
|
|
||||||
diagnostics: Vec<Diagnostic>,
|
|
||||||
version: Option<i32>,
|
|
||||||
) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TowerLspNotifier {
|
|
||||||
client: Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TowerLspNotifier {
|
|
||||||
pub fn new(client: Client) -> Self {
|
|
||||||
Self { client }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl Notifier for TowerLspNotifier {
|
|
||||||
fn log_message(&self, typ: MessageType, msg: &str) -> Result<()> {
|
|
||||||
let client = self.client.clone();
|
|
||||||
let msg = msg.to_string();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
client.log_message(typ, msg).await;
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_message(&self, typ: MessageType, msg: &str) -> Result<()> {
|
|
||||||
let client = self.client.clone();
|
|
||||||
let msg = msg.to_string();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
client.show_message(typ, msg).await;
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn show_message_request(
|
|
||||||
&self,
|
|
||||||
typ: MessageType,
|
|
||||||
msg: &str,
|
|
||||||
actions: Option<Vec<MessageActionItem>>,
|
|
||||||
) -> Result<Option<MessageActionItem>> {
|
|
||||||
let client = self.client.clone();
|
|
||||||
let msg = msg.to_string();
|
|
||||||
Ok(client.show_message_request(typ, msg, actions).await?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn publish_diagnostics(
|
|
||||||
&self,
|
|
||||||
uri: Url,
|
|
||||||
diagnostics: Vec<Diagnostic>,
|
|
||||||
version: Option<i32>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let client = self.client.clone();
|
|
||||||
tokio::spawn(async move {
|
|
||||||
client.publish_diagnostics(uri, diagnostics, version).await;
|
|
||||||
});
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +1,171 @@
|
||||||
use crate::documents::Store;
|
use crate::documents::Store;
|
||||||
use crate::notifier::Notifier;
|
|
||||||
use crate::tasks::DebugTask;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use djls_project::DjangoProject;
|
use djls_project::DjangoProject;
|
||||||
use djls_worker::Worker;
|
use djls_worker::Worker;
|
||||||
use pyo3::prelude::*;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use tokio::sync::RwLock;
|
||||||
|
use tower_lsp::jsonrpc::Result as LspResult;
|
||||||
use tower_lsp::lsp_types::*;
|
use tower_lsp::lsp_types::*;
|
||||||
|
use tower_lsp::{Client, LanguageServer};
|
||||||
|
|
||||||
const SERVER_NAME: &str = "Django Language Server";
|
const SERVER_NAME: &str = "Django Language Server";
|
||||||
const SERVER_VERSION: &str = "0.1.0";
|
const SERVER_VERSION: &str = "0.1.0";
|
||||||
|
|
||||||
pub enum LspRequest {
|
|
||||||
Initialize(InitializeParams),
|
|
||||||
Completion(CompletionParams),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum LspResponse {
|
|
||||||
Initialize(InitializeResult),
|
|
||||||
Completion(Option<CompletionResponse>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum LspNotification {
|
|
||||||
DidOpenTextDocument(DidOpenTextDocumentParams),
|
|
||||||
DidChangeTextDocument(DidChangeTextDocumentParams),
|
|
||||||
DidCloseTextDocument(DidCloseTextDocumentParams),
|
|
||||||
Initialized(InitializedParams),
|
|
||||||
Shutdown,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DjangoLanguageServer {
|
pub struct DjangoLanguageServer {
|
||||||
project: Option<DjangoProject>,
|
client: Client,
|
||||||
notifier: Arc<Box<dyn Notifier>>,
|
project: Arc<RwLock<Option<DjangoProject>>>,
|
||||||
documents: Store,
|
documents: Arc<RwLock<Store>>,
|
||||||
worker: Worker,
|
worker: Worker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DjangoLanguageServer {
|
impl DjangoLanguageServer {
|
||||||
pub fn new(notifier: Box<dyn Notifier>) -> Self {
|
pub fn new(client: Client) -> Self {
|
||||||
let notifier = Arc::new(notifier);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
project: None,
|
client,
|
||||||
notifier,
|
project: Arc::new(RwLock::new(None)),
|
||||||
documents: Store::new(),
|
documents: Arc::new(RwLock::new(Store::new())),
|
||||||
worker: Worker::new(),
|
worker: Worker::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_request(&mut self, request: LspRequest) -> Result<LspResponse> {
|
async fn log_message(&self, type_: MessageType, message: &str) -> Result<()> {
|
||||||
match request {
|
self.client.log_message(type_, message).await;
|
||||||
LspRequest::Initialize(params) => {
|
Ok(())
|
||||||
if let Some(mut project) = DjangoProject::from_initialize_params(¶ms) {
|
}
|
||||||
if let Err(e) = project.initialize() {
|
}
|
||||||
self.notifier.log_message(
|
|
||||||
MessageType::ERROR,
|
#[tower_lsp::async_trait]
|
||||||
&format!("Failed to initialize Django project: {}", e),
|
impl LanguageServer for DjangoLanguageServer {
|
||||||
)?;
|
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
|
||||||
} else {
|
let project = DjangoProject::from_initialize_params(¶ms);
|
||||||
self.notifier.log_message(
|
|
||||||
MessageType::INFO,
|
if let Some(mut project) = project {
|
||||||
&format!("Using project path: {}", project.path().display()),
|
match project.initialize() {
|
||||||
)?;
|
Ok(()) => {
|
||||||
self.project = Some(project);
|
self.log_message(
|
||||||
}
|
MessageType::INFO,
|
||||||
}
|
&format!("Using project path: {}", project.path().display()),
|
||||||
|
)
|
||||||
Ok(LspResponse::Initialize(InitializeResult {
|
.await
|
||||||
capabilities: ServerCapabilities {
|
.ok();
|
||||||
completion_provider: Some(CompletionOptions {
|
*self.project.write().await = Some(project);
|
||||||
resolve_provider: Some(false),
|
}
|
||||||
trigger_characters: Some(vec![
|
Err(e) => {
|
||||||
"{".to_string(),
|
self.log_message(
|
||||||
"%".to_string(),
|
MessageType::ERROR,
|
||||||
" ".to_string(),
|
&format!("Failed to initialize Django project: {}", e),
|
||||||
]),
|
)
|
||||||
..Default::default()
|
.await
|
||||||
}),
|
.ok();
|
||||||
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
}
|
||||||
TextDocumentSyncOptions {
|
}
|
||||||
open_close: Some(true),
|
}
|
||||||
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
|
||||||
will_save: Some(false),
|
Ok(InitializeResult {
|
||||||
will_save_wait_until: Some(false),
|
capabilities: ServerCapabilities {
|
||||||
save: Some(SaveOptions::default().into()),
|
completion_provider: Some(CompletionOptions {
|
||||||
},
|
resolve_provider: Some(false),
|
||||||
)),
|
trigger_characters: Some(vec![
|
||||||
..Default::default()
|
"{".to_string(),
|
||||||
},
|
"%".to_string(),
|
||||||
offset_encoding: None,
|
" ".to_string(),
|
||||||
server_info: Some(ServerInfo {
|
]),
|
||||||
name: SERVER_NAME.to_string(),
|
..Default::default()
|
||||||
version: Some(SERVER_VERSION.to_string()),
|
}),
|
||||||
}),
|
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
||||||
}))
|
TextDocumentSyncOptions {
|
||||||
}
|
open_close: Some(true),
|
||||||
LspRequest::Completion(params) => {
|
change: Some(TextDocumentSyncKind::INCREMENTAL),
|
||||||
let completions = if let Some(project) = &self.project {
|
will_save: Some(false),
|
||||||
if let Some(tags) = project.template_tags() {
|
will_save_wait_until: Some(false),
|
||||||
self.documents.get_completions(
|
save: Some(SaveOptions::default().into()),
|
||||||
params.text_document_position.text_document.uri.as_str(),
|
},
|
||||||
params.text_document_position.position,
|
)),
|
||||||
tags,
|
..Default::default()
|
||||||
)
|
},
|
||||||
} else {
|
server_info: Some(ServerInfo {
|
||||||
None
|
name: SERVER_NAME.to_string(),
|
||||||
}
|
version: Some(SERVER_VERSION.to_string()),
|
||||||
} else {
|
}),
|
||||||
None
|
offset_encoding: None,
|
||||||
};
|
})
|
||||||
|
}
|
||||||
Ok(LspResponse::Completion(completions))
|
|
||||||
}
|
async fn initialized(&self, _: InitializedParams) {
|
||||||
}
|
self.log_message(MessageType::INFO, "server initialized!")
|
||||||
}
|
.await
|
||||||
|
.ok();
|
||||||
pub fn handle_notification(&mut self, notification: LspNotification) -> Result<()> {
|
}
|
||||||
match notification {
|
|
||||||
LspNotification::DidOpenTextDocument(params) => {
|
async fn shutdown(&self) -> LspResult<()> {
|
||||||
self.documents.handle_did_open(params.clone())?;
|
Ok(())
|
||||||
self.notifier.log_message(
|
}
|
||||||
MessageType::INFO,
|
|
||||||
&format!("Opened document: {}", params.text_document.uri),
|
async fn did_open(&self, params: DidOpenTextDocumentParams) {
|
||||||
)?;
|
if let Err(e) = self.documents.write().await.handle_did_open(params.clone()) {
|
||||||
Ok(())
|
eprintln!("Error handling document open: {}", e);
|
||||||
}
|
return;
|
||||||
LspNotification::DidChangeTextDocument(params) => {
|
}
|
||||||
self.documents.handle_did_change(params.clone())?;
|
|
||||||
self.notifier.log_message(
|
self.log_message(
|
||||||
MessageType::INFO,
|
MessageType::INFO,
|
||||||
&format!("Changed document: {}", params.text_document.uri),
|
&format!("Opened document: {}", params.text_document.uri),
|
||||||
)?;
|
)
|
||||||
Ok(())
|
.await
|
||||||
}
|
.ok();
|
||||||
LspNotification::DidCloseTextDocument(params) => {
|
}
|
||||||
self.documents.handle_did_close(params.clone())?;
|
|
||||||
self.notifier.log_message(
|
async fn did_change(&self, params: DidChangeTextDocumentParams) {
|
||||||
MessageType::INFO,
|
if let Err(e) = self
|
||||||
&format!("Closed document: {}", params.text_document.uri),
|
.documents
|
||||||
)?;
|
.write()
|
||||||
Ok(())
|
.await
|
||||||
}
|
.handle_did_change(params.clone())
|
||||||
LspNotification::Initialized(_) => {
|
{
|
||||||
self.notifier
|
eprintln!("Error handling document change: {}", e);
|
||||||
.log_message(MessageType::INFO, "server initialized!")?;
|
return;
|
||||||
Ok(())
|
}
|
||||||
}
|
|
||||||
LspNotification::Shutdown => Ok(()),
|
self.log_message(
|
||||||
}
|
MessageType::INFO,
|
||||||
|
&format!("Changed document: {}", params.text_document.uri),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn did_close(&self, params: DidCloseTextDocumentParams) {
|
||||||
|
if let Err(e) = self
|
||||||
|
.documents
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.handle_did_close(params.clone())
|
||||||
|
{
|
||||||
|
eprintln!("Error handling document close: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.log_message(
|
||||||
|
MessageType::INFO,
|
||||||
|
&format!("Closed document: {}", params.text_document.uri),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn completion(&self, params: CompletionParams) -> LspResult<Option<CompletionResponse>> {
|
||||||
|
let project_guard = self.project.read().await;
|
||||||
|
let documents_guard = self.documents.read().await;
|
||||||
|
|
||||||
|
if let Some(project) = project_guard.as_ref() {
|
||||||
|
if let Some(tags) = project.template_tags() {
|
||||||
|
return Ok(documents_guard.get_completions(
|
||||||
|
params.text_document_position.text_document.uri.as_str(),
|
||||||
|
params.text_document_position.position,
|
||||||
|
tags,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
use crate::notifier::Notifier;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use djls_worker::Task;
|
use djls_worker::Task;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tower_lsp::lsp_types::MessageType;
|
|
||||||
|
|
||||||
pub struct DebugTask {
|
pub struct DebugTask {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub delay: Duration,
|
pub delay: Duration,
|
||||||
pub notifier: Arc<Box<dyn Notifier>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugTask {
|
impl DebugTask {
|
||||||
pub fn new(message: String, delay: Duration, notifier: Arc<Box<dyn Notifier>>) -> Self {
|
pub fn new(message: String, delay: Duration) -> Self {
|
||||||
Self {
|
Self { message, delay }
|
||||||
message,
|
|
||||||
delay,
|
|
||||||
notifier,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,11 +20,6 @@ impl Task for DebugTask {
|
||||||
std::thread::sleep(self.delay);
|
std::thread::sleep(self.delay);
|
||||||
let result = format!("Debug task completed: {}", self.message);
|
let result = format!("Debug task completed: {}", self.message);
|
||||||
|
|
||||||
// Log the result
|
|
||||||
self.notifier
|
|
||||||
.log_message(MessageType::INFO, &result)
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue