Skip to content

Commit 38bab4b

Browse files
Copilotcoder3101
andauthored
Implement shutdown and exit RPCs per LSP specification (#102)
VS Code LSP client was failing with `No such method shutdown` when stopping the server. The server lacked implementation of the required lifecycle RPCs. ## Changes - **Added shutdown request handler**: Returns null response, sets `shutdown_received` flag - **Added exit notification handler**: Exits with code 0 after proper shutdown, code 1 otherwise per LSP spec - **Added state tracking**: `shutdown_received: bool` field in `ProtoLanguageServer` to determine exit behavior ## Implementation ```rust pub(super) fn shutdown(&mut self, _params: ()) -> BoxFuture<'static, Result<(), ResponseError>> { info!("Received shutdown request"); self.shutdown_received = true; Box::pin(async move { Ok(()) }) } pub(super) fn exit(&mut self, _params: ()) -> ControlFlow<async_lsp::Result<()>> { if self.shutdown_received { info!("Received exit notification after shutdown, exiting with code 0"); std::process::exit(0); } else { warn!("Received exit notification without shutdown, exiting with code 1"); std::process::exit(1); } } ``` The `LifecycleLayer` middleware handles state transitions; these handlers provide the required lifecycle endpoints and exit code semantics. > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `docs.rs` > - Triggering command: `/home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js` (dns block) > - `microsoft.github.io` > - Triggering command: `/home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js` (dns block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/coder3101/protols/settings/copilot/coding_agent) (admins only) > > </details> <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Implement shutdown and exit RPCs for clean exit</issue_title> > <issue_description>For a clean exit, [Shutdown](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#shutdown) and [Exit](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#exit) notifications are sent by the client. Currently protols does not implement these two rpcs on the server side. > > VS Code built-in LSP client is failing with the following error when a `stop()` command is given: > > ``` > Error checking for protols updates: _ResponseError: No such method shutdown {code: -32601, data: null, stack: 'Error: No such method shutdown > at handleResp…Trampoline (node:internal/async_hooks:130:17)', message: 'No such method shutdown'} > ``` > > </issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes #101 <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/coder3101/protols/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: coder3101 <22212259+coder3101@users.noreply.github.com>
1 parent d089ea9 commit 38bab4b

3 files changed

Lines changed: 31 additions & 8 deletions

File tree

src/lsp.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,15 @@ impl ProtoLanguageServer {
131131
Box::pin(async move { Ok(response) })
132132
}
133133

134+
pub(super) fn shutdown(
135+
&mut self,
136+
_params: (),
137+
) -> BoxFuture<'static, Result<(), ResponseError>> {
138+
info!("Received shutdown request");
139+
self.shutdown_received = true;
140+
Box::pin(async move { Ok(()) })
141+
}
142+
134143
pub(super) fn hover(
135144
&mut self,
136145
param: HoverParams,
@@ -564,6 +573,16 @@ impl ProtoLanguageServer {
564573
}
565574
ControlFlow::Continue(())
566575
}
576+
577+
pub(super) fn exit(&mut self, _params: ()) -> ControlFlow<async_lsp::Result<()>> {
578+
if self.shutdown_received {
579+
info!("Received exit notification after shutdown, exiting with code 0");
580+
std::process::exit(0);
581+
} else {
582+
warn!("Received exit notification without shutdown, exiting with code 1");
583+
std::process::exit(1);
584+
}
585+
}
567586
}
568587

569588
/// Parse include_paths from initialization options

src/server.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use async_lsp::{
44
NumberOrString, ProgressParams, ProgressParamsValue,
55
notification::{
66
DidChangeTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument,
7-
DidRenameFiles, DidSaveTextDocument,
7+
DidRenameFiles, DidSaveTextDocument, Exit,
88
},
99
request::{
1010
Completion, DocumentSymbolRequest, Formatting, GotoDefinition, HoverRequest,
11-
Initialize, PrepareRenameRequest, RangeFormatting, References, Rename,
11+
Initialize, PrepareRenameRequest, RangeFormatting, References, Rename, Shutdown,
1212
WorkspaceSymbolRequest,
1313
},
1414
},
@@ -29,6 +29,7 @@ pub struct ProtoLanguageServer {
2929
pub counter: i32,
3030
pub state: ProtoLanguageState,
3131
pub configs: WorkspaceProtoConfigs,
32+
pub shutdown_received: bool,
3233
}
3334

3435
impl ProtoLanguageServer {
@@ -42,6 +43,7 @@ impl ProtoLanguageServer {
4243
counter: 0,
4344
state: ProtoLanguageState::new(),
4445
configs: WorkspaceProtoConfigs::new(cli_include_paths, fallback_include_path),
46+
shutdown_received: false,
4547
});
4648

4749
router.event::<TickEvent>(|st, _| {
@@ -57,6 +59,7 @@ impl ProtoLanguageServer {
5759

5860
// Handling request
5961
router.request::<Initialize, _>(|st, params| st.initialize(params));
62+
router.request::<Shutdown, _>(|st, params| st.shutdown(params));
6063
router.request::<HoverRequest, _>(|st, params| st.hover(params));
6164
router.request::<Completion, _>(|st, params| st.completion(params));
6265
router.request::<PrepareRenameRequest, _>(|st, params| st.prepare_rename(params));
@@ -75,6 +78,7 @@ impl ProtoLanguageServer {
7578
router.notification::<DidCreateFiles>(|st, params| st.did_create_files(params));
7679
router.notification::<DidRenameFiles>(|st, params| st.did_rename_files(params));
7780
router.notification::<DidDeleteFiles>(|st, params| st.did_delete_files(params));
81+
router.notification::<Exit>(|st, params| st.exit(params));
7882

7983
router
8084
}

src/workspace/workspace_symbol.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ mod test {
1313
"file://{}/src/workspace/input/a.proto",
1414
current_dir.to_str().unwrap()
1515
)
16-
.parse()
17-
.unwrap();
16+
.parse()
17+
.unwrap();
1818
let b_uri = format!(
1919
"file://{}/src/workspace/input/b.proto",
2020
current_dir.to_str().unwrap()
2121
)
22-
.parse()
23-
.unwrap();
22+
.parse()
23+
.unwrap();
2424
let c_uri = format!(
2525
"file://{}/src/workspace/input/c.proto",
2626
current_dir.to_str().unwrap()
2727
)
28-
.parse()
29-
.unwrap();
28+
.parse()
29+
.unwrap();
3030

3131
let a = include_str!("input/a.proto");
3232
let b = include_str!("input/b.proto");

0 commit comments

Comments
 (0)