nushell/crates/nu-lsp/src/notification.rs
zc he e4cef8a154
fix(lsp): several edge cases of inaccurate references (#15523)
# Description

Sometimes recognizing identical concepts in nushell can be difficult.
This PR fixes some cases.

# User-Facing Changes

## Before:

<img width="317" alt="image"
src="https://github.com/user-attachments/assets/40567fd2-4cf4-44bb-8845-5f39935f41bb"
/>
<img width="317" alt="image"
src="https://github.com/user-attachments/assets/0cc21aab-8c8a-4bdd-adaf-70117e46c88d"
/>
<img width="276" alt="image"
src="https://github.com/user-attachments/assets/2820f958-b1aa-4bf1-b2ec-36e3191dd1aa"
/>
<img width="311" alt="image"
src="https://github.com/user-attachments/assets/407fb20f-ca5a-42a2-b0ac-791a7ee8497a"
/>

## After:

<img width="317" alt="image"
src="https://github.com/user-attachments/assets/91ca595f-36c5-4081-ba19-4800eb89cbec"
/>
<img width="317" alt="image"
src="https://github.com/user-attachments/assets/222aa0d1-b9c6-441c-8ecd-66ae91c7d397"
/>
<img width="275" alt="image"
src="https://github.com/user-attachments/assets/7b3122d3-ed5a-4bee-8e35-5ef01abc25a1"
/>
<img width="316" alt="image"
src="https://github.com/user-attachments/assets/2c026055-5962-4d4c-97d4-c453a2fef82b"
/>

# Tests + Formatting

+3

# After Submitting
2025-04-09 21:15:35 -05:00

259 lines
9.0 KiB
Rust

use crate::LanguageServer;
use lsp_types::{
notification::{
DidChangeTextDocument, DidChangeWorkspaceFolders, DidCloseTextDocument,
DidOpenTextDocument, Notification, Progress,
},
DidChangeTextDocumentParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, ProgressParams, ProgressParamsValue, ProgressToken, Uri,
WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd, WorkDoneProgressReport,
};
use miette::{IntoDiagnostic, Result};
impl LanguageServer {
pub(crate) fn handle_lsp_notification(
&mut self,
notification: lsp_server::Notification,
) -> Option<Uri> {
let mut docs = self.docs.lock().ok()?;
docs.listen(notification.method.as_str(), &notification.params);
match notification.method.as_str() {
DidOpenTextDocument::METHOD => {
let params: DidOpenTextDocumentParams = serde_json::from_value(notification.params)
.expect("Expect receive DidOpenTextDocumentParams");
Some(params.text_document.uri)
}
DidChangeTextDocument::METHOD => {
let params: DidChangeTextDocumentParams =
serde_json::from_value(notification.params)
.expect("Expect receive DidChangeTextDocumentParams");
Some(params.text_document.uri)
}
DidCloseTextDocument::METHOD => {
let params: DidCloseTextDocumentParams =
serde_json::from_value(notification.params)
.expect("Expect receive DidCloseTextDocumentParams");
let uri = params.text_document.uri;
self.symbol_cache.drop(&uri);
self.inlay_hints.remove(&uri);
None
}
DidChangeWorkspaceFolders::METHOD => {
let params: DidChangeWorkspaceFoldersParams =
serde_json::from_value(notification.params)
.expect("Expect receive DidChangeWorkspaceFoldersParams");
for added in params.event.added {
self.workspace_folders.insert(added.name.clone(), added);
}
for removed in params.event.removed {
self.workspace_folders.remove(&removed.name);
}
None
}
_ => None,
}
}
pub(crate) fn send_progress_notification(
&self,
token: ProgressToken,
value: WorkDoneProgress,
) -> Result<()> {
let progress_params = ProgressParams {
token,
value: ProgressParamsValue::WorkDone(value),
};
let notification =
lsp_server::Notification::new(Progress::METHOD.to_string(), progress_params);
self.connection
.sender
.send(lsp_server::Message::Notification(notification))
.into_diagnostic()
}
pub(crate) fn send_progress_begin(&self, token: ProgressToken, title: String) -> Result<()> {
self.send_progress_notification(
token,
WorkDoneProgress::Begin(WorkDoneProgressBegin {
title,
percentage: Some(0),
cancellable: Some(true),
..Default::default()
}),
)
}
pub(crate) fn send_progress_report(
&self,
token: ProgressToken,
percentage: u32,
message: Option<String>,
) -> Result<()> {
self.send_progress_notification(
token,
WorkDoneProgress::Report(WorkDoneProgressReport {
message,
cancellable: Some(true),
percentage: Some(percentage),
}),
)
}
pub(crate) fn send_progress_end(
&self,
token: ProgressToken,
message: Option<String>,
) -> Result<()> {
self.send_progress_notification(
token,
WorkDoneProgress::End(WorkDoneProgressEnd { message }),
)
}
pub(crate) fn send_error_message(
&self,
id: lsp_server::RequestId,
code: i32,
message: String,
) -> Result<()> {
self.connection
.sender
.send(lsp_server::Message::Response(lsp_server::Response {
id,
result: None,
error: Some(lsp_server::ResponseError {
code,
message,
data: None,
}),
}))
.into_diagnostic()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::path_to_uri;
use crate::tests::{
initialize_language_server, open, open_unchecked, result_from_message, send_hover_request,
update,
};
use assert_json_diff::assert_json_eq;
use lsp_types::Range;
use nu_test_support::fs::fixtures;
#[test]
fn hover_correct_documentation_on_let() {
let (client_connection, _recv) = initialize_language_server(None, None);
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("var.nu");
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
let resp = send_hover_request(&client_connection, script, 0, 0);
assert_json_eq!(
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Create a variable and give it a value.\n\nThis command is a parser keyword. For details, check:\n https://www.nushell.sh/book/thinking_in_nu.html\n---\n### Usage \n```nu\n let {flags} <var_name> = <initial_value>\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n\n### Parameters\n\n `var_name`: `<vardecl>` - Variable name.\n\n `initial_value`: `<variable>` - Equals sign followed by value.\n\n\n### Input/output types\n\n```nu\n any | nothing\n\n```\n### Example(s)\n Set a variable to a value\n```nu\n let x = 10\n```\n Set a variable to the result of an expression\n```nu\n let x = 10 + 100\n```\n Set a variable based on the condition\n```nu\n let x = if false { -1 } else { 1 }\n```\n"
}
})
);
}
#[test]
fn hover_on_command_after_full_content_change() {
let (client_connection, _recv) = initialize_language_server(None, None);
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("command.nu");
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
update(
&client_connection,
script.clone(),
String::from(
r#"# Renders some updated greeting message
def hello [] {}
hello"#,
),
None,
);
let resp = send_hover_request(&client_connection, script, 3, 0);
assert_json_eq!(
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Renders some updated greeting message\n---\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
}
})
);
}
#[test]
fn hover_on_command_after_partial_content_change() {
let (client_connection, _recv) = initialize_language_server(None, None);
let mut script = fixtures();
script.push("lsp");
script.push("hover");
script.push("command.nu");
let script = path_to_uri(&script);
open_unchecked(&client_connection, script.clone());
update(
&client_connection,
script.clone(),
String::from("# Renders some updated greeting message"),
Some(Range {
start: lsp_types::Position {
line: 0,
character: 0,
},
end: lsp_types::Position {
line: 0,
character: 31,
},
}),
);
let resp = send_hover_request(&client_connection, script, 3, 0);
assert_json_eq!(
result_from_message(resp),
serde_json::json!({
"contents": {
"kind": "markdown",
"value": "Renders some updated greeting message\n---\n### Usage \n```nu\n hello {flags}\n```\n\n### Flags\n\n `-h`, `--help` - Display the help message for this command\n\n"
}
})
);
}
#[test]
fn open_document_with_utf_char() {
let (client_connection, _recv) = initialize_language_server(None, None);
let mut script = fixtures();
script.push("lsp");
script.push("notifications");
script.push("issue_11522.nu");
let script = path_to_uri(&script);
let result = open(&client_connection, script);
assert_eq!(result.map(|_| ()), Ok(()))
}
}