mirror of
https://github.com/coder3101/protols.git
synced 2025-12-23 05:36:51 +00:00
feat: Use builder pattern and log unhandled notifications (#84)
Some checks are pending
Build and Test / build (push) Waiting to run
Some checks are pending
Build and Test / build (push) Waiting to run
Potentially fixes #83 Prior to this, any unhandled notification not starting with `$` would have caused the main loop to exit with error resulting in server to crash. Moreover, this unhandled_notification handler only works with builder pattern which imo is cleaner and offers ability to separate out implementation of various requests into its own files (i'll maybe do that someday).
This commit is contained in:
parent
a15cfb7c00
commit
dd71d54555
5 changed files with 106 additions and 72 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -792,7 +792,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "protols"
|
||||
version = "0.12.5"
|
||||
version = "0.12.6"
|
||||
dependencies = [
|
||||
"async-lsp",
|
||||
"basic-toml",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "protols"
|
||||
description = "Language server for proto3 files"
|
||||
version = "0.12.5"
|
||||
version = "0.12.6"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/coder3101/protols"
|
||||
|
|
@ -12,8 +12,8 @@ keywords = ["lsp", "proto"]
|
|||
exclude = ["assets/*", "sample/*"]
|
||||
|
||||
[dependencies]
|
||||
async-lsp = { version = "0.2.0", features = ["tokio"] }
|
||||
futures = "0.3.30"
|
||||
async-lsp = { version = "0.2.2", features = ["tokio"] }
|
||||
futures = "0.3.31"
|
||||
tokio = { version = "1.38.0", features = ["time", "full"] }
|
||||
tokio-util = { version = "0.7.11", features = ["compat"] }
|
||||
tower = "0.5.2"
|
||||
|
|
|
|||
111
src/lsp.rs
111
src/lsp.rs
|
|
@ -4,34 +4,30 @@ use tracing::{error, info};
|
|||
|
||||
use async_lsp::lsp_types::{
|
||||
CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
|
||||
CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams,
|
||||
DidChangeTextDocumentParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
|
||||
DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentFormattingParams,
|
||||
DocumentRangeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, Documentation,
|
||||
FileOperationFilter, FileOperationPattern, FileOperationPatternKind,
|
||||
FileOperationRegistrationOptions, GotoDefinitionParams, GotoDefinitionResponse, Hover,
|
||||
HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
|
||||
Location, MarkupContent, MarkupKind, OneOf, PrepareRenameResponse, ReferenceParams,
|
||||
RenameFilesParams, RenameOptions, RenameParams, ServerCapabilities, ServerInfo,
|
||||
TextDocumentPositionParams, TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url,
|
||||
WorkspaceEdit, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
|
||||
CreateFilesParams, DeleteFilesParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
||||
DidSaveTextDocumentParams, DocumentFormattingParams, DocumentRangeFormattingParams,
|
||||
DocumentSymbolParams, DocumentSymbolResponse, Documentation, FileOperationFilter,
|
||||
FileOperationPattern, FileOperationPatternKind, FileOperationRegistrationOptions,
|
||||
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
|
||||
HoverProviderCapability, InitializeParams, InitializeResult, Location, MarkupContent,
|
||||
MarkupKind, OneOf, PrepareRenameResponse, ReferenceParams, RenameFilesParams, RenameOptions,
|
||||
RenameParams, ServerCapabilities, ServerInfo, TextDocumentPositionParams,
|
||||
TextDocumentSyncCapability, TextDocumentSyncKind, TextEdit, Url, WorkspaceEdit,
|
||||
WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities,
|
||||
WorkspaceServerCapabilities,
|
||||
};
|
||||
use async_lsp::{LanguageClient, LanguageServer, ResponseError};
|
||||
use async_lsp::{LanguageClient, ResponseError};
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
use crate::docs;
|
||||
use crate::formatter::ProtoFormatter;
|
||||
use crate::server::ProtoLanguageServer;
|
||||
|
||||
impl LanguageServer for ProtoLanguageServer {
|
||||
type Error = ResponseError;
|
||||
type NotifyResult = ControlFlow<async_lsp::Result<()>>;
|
||||
|
||||
fn initialize(
|
||||
impl ProtoLanguageServer {
|
||||
pub(super) fn initialize(
|
||||
&mut self,
|
||||
params: InitializeParams,
|
||||
) -> BoxFuture<'static, Result<InitializeResult, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<InitializeResult, ResponseError>> {
|
||||
let (cname, version) = params
|
||||
.client_info
|
||||
.as_ref()
|
||||
|
|
@ -122,10 +118,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(response) })
|
||||
}
|
||||
|
||||
fn hover(
|
||||
pub(super) fn hover(
|
||||
&mut self,
|
||||
param: HoverParams,
|
||||
) -> BoxFuture<'static, Result<Option<Hover>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<Hover>, ResponseError>> {
|
||||
let uri = param.text_document_position_params.text_document.uri;
|
||||
let pos = param.text_document_position_params.position;
|
||||
|
||||
|
|
@ -154,10 +150,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
})
|
||||
}
|
||||
|
||||
fn completion(
|
||||
pub(super) fn completion(
|
||||
&mut self,
|
||||
params: CompletionParams,
|
||||
) -> BoxFuture<'static, Result<Option<CompletionResponse>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<CompletionResponse>, ResponseError>> {
|
||||
let uri = params.text_document_position.text_document.uri;
|
||||
|
||||
// All keywords in the language
|
||||
|
|
@ -203,10 +199,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(Some(CompletionResponse::Array(completions))) })
|
||||
}
|
||||
|
||||
fn prepare_rename(
|
||||
pub(super) fn prepare_rename(
|
||||
&mut self,
|
||||
params: TextDocumentPositionParams,
|
||||
) -> BoxFuture<'static, Result<Option<PrepareRenameResponse>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<PrepareRenameResponse>, ResponseError>> {
|
||||
let uri = params.text_document.uri;
|
||||
let pos = params.position;
|
||||
|
||||
|
|
@ -220,10 +216,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(response) })
|
||||
}
|
||||
|
||||
fn rename(
|
||||
pub(super) fn rename(
|
||||
&mut self,
|
||||
params: RenameParams,
|
||||
) -> BoxFuture<'static, Result<Option<WorkspaceEdit>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<WorkspaceEdit>, ResponseError>> {
|
||||
let uri = params.text_document_position.text_document.uri;
|
||||
let pos = params.text_document_position.position;
|
||||
|
||||
|
|
@ -271,7 +267,7 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(response) })
|
||||
}
|
||||
|
||||
fn references(
|
||||
pub(super) fn references(
|
||||
&mut self,
|
||||
param: ReferenceParams,
|
||||
) -> BoxFuture<'static, Result<Option<Vec<Location>>, ResponseError>> {
|
||||
|
|
@ -318,7 +314,7 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
})
|
||||
}
|
||||
|
||||
fn definition(
|
||||
pub(super) fn definition(
|
||||
&mut self,
|
||||
param: GotoDefinitionParams,
|
||||
) -> BoxFuture<'static, Result<Option<GotoDefinitionResponse>, ResponseError>> {
|
||||
|
|
@ -353,10 +349,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(response) })
|
||||
}
|
||||
|
||||
fn document_symbol(
|
||||
pub(super) fn document_symbol(
|
||||
&mut self,
|
||||
params: DocumentSymbolParams,
|
||||
) -> BoxFuture<'static, Result<Option<DocumentSymbolResponse>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<DocumentSymbolResponse>, ResponseError>> {
|
||||
let uri = params.text_document.uri;
|
||||
|
||||
let Some(tree) = self.state.get_tree(&uri) else {
|
||||
|
|
@ -371,10 +367,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(Some(response)) })
|
||||
}
|
||||
|
||||
fn formatting(
|
||||
pub(super) fn formatting(
|
||||
&mut self,
|
||||
params: DocumentFormattingParams,
|
||||
) -> BoxFuture<'static, Result<Option<Vec<TextEdit>>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<Vec<TextEdit>>, ResponseError>> {
|
||||
let uri = params.text_document.uri;
|
||||
let content = self.state.get_content(&uri);
|
||||
|
||||
|
|
@ -386,10 +382,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(response) })
|
||||
}
|
||||
|
||||
fn range_formatting(
|
||||
pub(super) fn range_formatting(
|
||||
&mut self,
|
||||
params: DocumentRangeFormattingParams,
|
||||
) -> BoxFuture<'static, Result<Option<Vec<TextEdit>>, Self::Error>> {
|
||||
) -> BoxFuture<'static, Result<Option<Vec<TextEdit>>, ResponseError>> {
|
||||
let uri = params.text_document.uri;
|
||||
let content = self.state.get_content(&uri);
|
||||
|
||||
|
|
@ -401,7 +397,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
Box::pin(async move { Ok(response) })
|
||||
}
|
||||
|
||||
fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Self::NotifyResult {
|
||||
pub(super) fn did_save(
|
||||
&mut self,
|
||||
params: DidSaveTextDocumentParams,
|
||||
) -> ControlFlow<async_lsp::Result<()>> {
|
||||
let uri = params.text_document.uri;
|
||||
let content = self.state.get_content(&uri);
|
||||
|
||||
|
|
@ -424,11 +423,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn did_close(&mut self, _params: DidCloseTextDocumentParams) -> Self::NotifyResult {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Self::NotifyResult {
|
||||
pub(super) fn did_open(
|
||||
&mut self,
|
||||
params: DidOpenTextDocumentParams,
|
||||
) -> ControlFlow<async_lsp::Result<()>> {
|
||||
let uri = params.text_document.uri;
|
||||
let content = params.text_document.text;
|
||||
|
||||
|
|
@ -451,7 +449,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Self::NotifyResult {
|
||||
pub(super) fn did_change(
|
||||
&mut self,
|
||||
params: DidChangeTextDocumentParams,
|
||||
) -> ControlFlow<async_lsp::Result<()>> {
|
||||
let uri = params.text_document.uri;
|
||||
let content = params.content_changes[0].text.clone();
|
||||
|
||||
|
|
@ -474,7 +475,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn did_create_files(&mut self, params: CreateFilesParams) -> Self::NotifyResult {
|
||||
pub(super) fn did_create_files(
|
||||
&mut self,
|
||||
params: CreateFilesParams,
|
||||
) -> ControlFlow<async_lsp::Result<()>> {
|
||||
for file in params.files {
|
||||
if let Ok(uri) = Url::from_file_path(&file.uri) {
|
||||
// Safety: The uri is always a file type
|
||||
|
|
@ -488,7 +492,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn did_rename_files(&mut self, params: RenameFilesParams) -> Self::NotifyResult {
|
||||
pub(super) fn did_rename_files(
|
||||
&mut self,
|
||||
params: RenameFilesParams,
|
||||
) -> ControlFlow<async_lsp::Result<()>> {
|
||||
for file in params.files {
|
||||
let Ok(new_uri) = Url::from_file_path(&file.new_uri) else {
|
||||
error!(uri = file.new_uri, "failed to parse uri");
|
||||
|
|
@ -505,7 +512,10 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
fn did_delete_files(&mut self, params: DeleteFilesParams) -> Self::NotifyResult {
|
||||
pub(super) fn did_delete_files(
|
||||
&mut self,
|
||||
params: DeleteFilesParams,
|
||||
) -> ControlFlow<async_lsp::Result<()>> {
|
||||
for file in params.files {
|
||||
if let Ok(uri) = Url::from_file_path(&file.uri) {
|
||||
self.state.delete_file(&uri);
|
||||
|
|
@ -515,17 +525,4 @@ impl LanguageServer for ProtoLanguageServer {
|
|||
}
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
// Required because of: https://github.com/coder3101/protols/issues/32
|
||||
fn did_change_configuration(&mut self, _: DidChangeConfigurationParams) -> Self::NotifyResult {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
// Required because when jumping to outside the workspace; this is triggered
|
||||
fn did_change_workspace_folders(
|
||||
&mut self,
|
||||
_: DidChangeWorkspaceFoldersParams,
|
||||
) -> Self::NotifyResult {
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ async fn main() {
|
|||
let cli = Cli::parse();
|
||||
|
||||
let dir = std::env::temp_dir();
|
||||
eprintln!("Rolling file based logging at directory: {dir:?}");
|
||||
eprintln!("file logging at directory: {dir:?}");
|
||||
|
||||
let file_appender = tracing_appender::rolling::daily(dir.clone(), "protols.log");
|
||||
let file_appender = tracing_appender::non_blocking(file_appender);
|
||||
|
|
@ -48,9 +48,10 @@ async fn main() {
|
|||
.with_writer(file_appender.0)
|
||||
.init();
|
||||
|
||||
tracing::info!("server version: {}", env!("CARGO_PKG_VERSION"));
|
||||
let (server, _) = async_lsp::MainLoop::new_server(|client| {
|
||||
tracing::info!("Using CLI options: {:?}", cli);
|
||||
let server = ProtoLanguageServer::new_router(
|
||||
let router = ProtoLanguageServer::new_router(
|
||||
client.clone(),
|
||||
cli.include_paths
|
||||
.map(|ic| ic.into_iter().map(std::path::PathBuf::from).collect())
|
||||
|
|
@ -76,7 +77,7 @@ async fn main() {
|
|||
.layer(CatchUnwindLayer::default())
|
||||
.layer(ConcurrencyLayer::default())
|
||||
.layer(ClientProcessMonitorLayer::new(client.clone()))
|
||||
.service(server)
|
||||
.service(router)
|
||||
});
|
||||
|
||||
// Prefer truly asynchronous piped stdin/stdout without blocking tasks.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
use async_lsp::{
|
||||
ClientSocket, LanguageClient,
|
||||
lsp_types::{NumberOrString, ProgressParams, ProgressParamsValue},
|
||||
lsp_types::{
|
||||
NumberOrString, ProgressParams, ProgressParamsValue,
|
||||
notification::{
|
||||
DidChangeTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument,
|
||||
DidRenameFiles, DidSaveTextDocument,
|
||||
},
|
||||
request::{
|
||||
Completion, DocumentSymbolRequest, Formatting, GotoDefinition, HoverRequest,
|
||||
Initialize, PrepareRenameRequest, RangeFormatting, References, Rename,
|
||||
},
|
||||
},
|
||||
router::Router,
|
||||
};
|
||||
use std::{
|
||||
|
|
@ -22,19 +32,45 @@ pub struct ProtoLanguageServer {
|
|||
|
||||
impl ProtoLanguageServer {
|
||||
pub fn new_router(client: ClientSocket, cli_include_paths: Vec<PathBuf>) -> Router<Self> {
|
||||
let mut router = Router::from_language_server(Self {
|
||||
let mut router = Router::new(Self {
|
||||
client,
|
||||
counter: 0,
|
||||
state: ProtoLanguageState::new(),
|
||||
configs: WorkspaceProtoConfigs::new(cli_include_paths),
|
||||
});
|
||||
router.event(Self::on_tick);
|
||||
router
|
||||
}
|
||||
|
||||
fn on_tick(&mut self, _: TickEvent) -> ControlFlow<async_lsp::Result<()>> {
|
||||
self.counter += 1;
|
||||
ControlFlow::Continue(())
|
||||
router.event::<TickEvent>(|st, _| {
|
||||
st.counter += 1;
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
||||
// Ignore any unknown notification.
|
||||
router.unhandled_notification(|_, notif| {
|
||||
tracing::info!(notif.method, "ignored unknown notification");
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
||||
// Handling request
|
||||
router.request::<Initialize, _>(|st, params| st.initialize(params));
|
||||
router.request::<HoverRequest, _>(|st, params| st.hover(params));
|
||||
router.request::<Completion, _>(|st, params| st.completion(params));
|
||||
router.request::<PrepareRenameRequest, _>(|st, params| st.prepare_rename(params));
|
||||
router.request::<Rename, _>(|st, params| st.rename(params));
|
||||
router.request::<References, _>(|st, params| st.references(params));
|
||||
router.request::<GotoDefinition, _>(|st, params| st.definition(params));
|
||||
router.request::<DocumentSymbolRequest, _>(|st, params| st.document_symbol(params));
|
||||
router.request::<Formatting, _>(|st, params| st.formatting(params));
|
||||
router.request::<RangeFormatting, _>(|st, params| st.range_formatting(params));
|
||||
|
||||
// Handling notification
|
||||
router.notification::<DidSaveTextDocument>(|st, params| st.did_save(params));
|
||||
router.notification::<DidOpenTextDocument>(|st, params| st.did_open(params));
|
||||
router.notification::<DidChangeTextDocument>(|st, params| st.did_change(params));
|
||||
router.notification::<DidCreateFiles>(|st, params| st.did_create_files(params));
|
||||
router.notification::<DidRenameFiles>(|st, params| st.did_rename_files(params));
|
||||
router.notification::<DidDeleteFiles>(|st, params| st.did_delete_files(params));
|
||||
|
||||
router
|
||||
}
|
||||
|
||||
pub fn with_report_progress(&self, token: NumberOrString) -> Sender<ProgressParamsValue> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue