remove extra server layer in front of tower-lsp's server (#49)

This commit is contained in:
Josh Thomas 2024-12-24 13:40:47 -06:00 committed by GitHub
parent 9a2c0e51c7
commit f848798699
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 157 additions and 331 deletions

View file

@ -38,7 +38,7 @@ jobs:
- name: Install dependencies and build
run: |
uv sync --frozen
maturin build
uv run maturin build
- name: Run tests
run: cargo test --verbose

View file

@ -1,117 +1,19 @@
mod documents;
mod notifier;
mod server;
mod tasks;
use crate::notifier::TowerLspNotifier;
use crate::server::{DjangoLanguageServer, LspNotification, LspRequest};
use crate::server::DjangoLanguageServer;
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<()> {
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let (service, socket) = LspService::build(|client| {
let notifier = Box::new(TowerLspNotifier::new(client.clone()));
let server = DjangoLanguageServer::new(notifier);
TowerLspBackend {
server: Arc::new(RwLock::new(server)),
}
})
.finish();
let (service, socket) = tower_lsp::LspService::build(DjangoLanguageServer::new).finish();
Server::new(stdin, stdout, socket).serve(service).await;
tower_lsp::Server::new(stdin, stdout, socket)
.serve(service)
.await;
Ok(())
}

View file

@ -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(())
}
}

View file

@ -1,154 +1,171 @@
use crate::documents::Store;
use crate::notifier::Notifier;
use crate::tasks::DebugTask;
use anyhow::Result;
use djls_project::DjangoProject;
use djls_worker::Worker;
use pyo3::prelude::*;
use std::collections::BTreeMap;
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::{Client, LanguageServer};
const SERVER_NAME: &str = "Django Language Server";
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 {
project: Option<DjangoProject>,
notifier: Arc<Box<dyn Notifier>>,
documents: Store,
client: Client,
project: Arc<RwLock<Option<DjangoProject>>>,
documents: Arc<RwLock<Store>>,
worker: Worker,
}
impl DjangoLanguageServer {
pub fn new(notifier: Box<dyn Notifier>) -> Self {
let notifier = Arc::new(notifier);
pub fn new(client: Client) -> Self {
Self {
project: None,
notifier,
documents: Store::new(),
client,
project: Arc::new(RwLock::new(None)),
documents: Arc::new(RwLock::new(Store::new())),
worker: Worker::new(),
}
}
pub fn handle_request(&mut self, request: LspRequest) -> Result<LspResponse> {
match request {
LspRequest::Initialize(params) => {
if let Some(mut project) = DjangoProject::from_initialize_params(&params) {
if let Err(e) = project.initialize() {
self.notifier.log_message(
MessageType::ERROR,
&format!("Failed to initialize Django project: {}", e),
)?;
} else {
self.notifier.log_message(
MessageType::INFO,
&format!("Using project path: {}", project.path().display()),
)?;
self.project = Some(project);
}
}
Ok(LspResponse::Initialize(InitializeResult {
capabilities: ServerCapabilities {
completion_provider: Some(CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec![
"{".to_string(),
"%".to_string(),
" ".to_string(),
]),
..Default::default()
}),
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::INCREMENTAL),
will_save: Some(false),
will_save_wait_until: Some(false),
save: Some(SaveOptions::default().into()),
},
)),
..Default::default()
},
offset_encoding: None,
server_info: Some(ServerInfo {
name: SERVER_NAME.to_string(),
version: Some(SERVER_VERSION.to_string()),
}),
}))
}
LspRequest::Completion(params) => {
let completions = if let Some(project) = &self.project {
if let Some(tags) = project.template_tags() {
self.documents.get_completions(
params.text_document_position.text_document.uri.as_str(),
params.text_document_position.position,
tags,
)
} else {
None
}
} else {
None
};
Ok(LspResponse::Completion(completions))
}
}
}
pub fn handle_notification(&mut self, notification: LspNotification) -> Result<()> {
match notification {
LspNotification::DidOpenTextDocument(params) => {
self.documents.handle_did_open(params.clone())?;
self.notifier.log_message(
MessageType::INFO,
&format!("Opened document: {}", params.text_document.uri),
)?;
Ok(())
}
LspNotification::DidChangeTextDocument(params) => {
self.documents.handle_did_change(params.clone())?;
self.notifier.log_message(
MessageType::INFO,
&format!("Changed document: {}", params.text_document.uri),
)?;
Ok(())
}
LspNotification::DidCloseTextDocument(params) => {
self.documents.handle_did_close(params.clone())?;
self.notifier.log_message(
MessageType::INFO,
&format!("Closed document: {}", params.text_document.uri),
)?;
Ok(())
}
LspNotification::Initialized(_) => {
self.notifier
.log_message(MessageType::INFO, "server initialized!")?;
Ok(())
}
LspNotification::Shutdown => Ok(()),
}
async fn log_message(&self, type_: MessageType, message: &str) -> Result<()> {
self.client.log_message(type_, message).await;
Ok(())
}
}
#[tower_lsp::async_trait]
impl LanguageServer for DjangoLanguageServer {
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
let project = DjangoProject::from_initialize_params(&params);
if let Some(mut project) = project {
match project.initialize() {
Ok(()) => {
self.log_message(
MessageType::INFO,
&format!("Using project path: {}", project.path().display()),
)
.await
.ok();
*self.project.write().await = Some(project);
}
Err(e) => {
self.log_message(
MessageType::ERROR,
&format!("Failed to initialize Django project: {}", e),
)
.await
.ok();
}
}
}
Ok(InitializeResult {
capabilities: ServerCapabilities {
completion_provider: Some(CompletionOptions {
resolve_provider: Some(false),
trigger_characters: Some(vec![
"{".to_string(),
"%".to_string(),
" ".to_string(),
]),
..Default::default()
}),
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::INCREMENTAL),
will_save: Some(false),
will_save_wait_until: Some(false),
save: Some(SaveOptions::default().into()),
},
)),
..Default::default()
},
server_info: Some(ServerInfo {
name: SERVER_NAME.to_string(),
version: Some(SERVER_VERSION.to_string()),
}),
offset_encoding: None,
})
}
async fn initialized(&self, _: InitializedParams) {
self.log_message(MessageType::INFO, "server initialized!")
.await
.ok();
}
async fn shutdown(&self) -> LspResult<()> {
Ok(())
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
if let Err(e) = self.documents.write().await.handle_did_open(params.clone()) {
eprintln!("Error handling document open: {}", e);
return;
}
self.log_message(
MessageType::INFO,
&format!("Opened document: {}", params.text_document.uri),
)
.await
.ok();
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
if let Err(e) = self
.documents
.write()
.await
.handle_did_change(params.clone())
{
eprintln!("Error handling document change: {}", e);
return;
}
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)
}
}

View file

@ -1,23 +1,15 @@
use crate::notifier::Notifier;
use anyhow::Result;
use djls_worker::Task;
use std::sync::Arc;
use std::time::Duration;
use tower_lsp::lsp_types::MessageType;
pub struct DebugTask {
pub message: String,
pub delay: Duration,
pub notifier: Arc<Box<dyn Notifier>>,
}
impl DebugTask {
pub fn new(message: String, delay: Duration, notifier: Arc<Box<dyn Notifier>>) -> Self {
Self {
message,
delay,
notifier,
}
pub fn new(message: String, delay: Duration) -> Self {
Self { message, delay }
}
}
@ -28,11 +20,6 @@ impl Task for DebugTask {
std::thread::sleep(self.delay);
let result = format!("Debug task completed: {}", self.message);
// Log the result
self.notifier
.log_message(MessageType::INFO, &result)
.unwrap_or_default();
Ok(result)
}
}