mirror of
https://github.com/zed-industries/zed.git
synced 2026-10-28 08:55:27 +08:00
Preserve LSP message order (#65856)
Closes https://github.com/zed-industries/zed/issues/65667 Release Notes: - Fixed LSP formatting requests bringing in incorrect changes sometimes
This commit is contained in:
1 parent
cc23f35a7d
commit
e80af52575
3 files changed
+133
-65
No files matched your search
@@ -22175,28 +22175,28 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
||||
cx.lsp
|
||||
.set_request_handler::<lsp::request::Formatting, _, _>({
|
||||
move |_, _| {
|
||||
assert_eq!(
|
||||
buffer_changes.lock().get(1..),
|
||||
Some(
|
||||
[
|
||||
(
|
||||
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
|
||||
String::new(),
|
||||
),
|
||||
(
|
||||
lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
|
||||
String::new(),
|
||||
),
|
||||
(
|
||||
lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
|
||||
language::LineEnding::default().as_str().to_owned(),
|
||||
),
|
||||
]
|
||||
.as_slice(),
|
||||
),
|
||||
);
|
||||
// Insert blank lines between each line of the buffer.
|
||||
async move {
|
||||
// TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
|
||||
// DidChangedTextDocument to the LSP before sending the formatting request.
|
||||
// assert_eq!(
|
||||
// &buffer_changes.lock()[1..],
|
||||
// &[
|
||||
// (
|
||||
// lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
|
||||
// "".into()
|
||||
// ),
|
||||
// (
|
||||
// lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
|
||||
// "".into()
|
||||
// ),
|
||||
// (
|
||||
// lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
|
||||
// "\n".into()
|
||||
// ),
|
||||
// ]
|
||||
// );
|
||||
|
||||
Ok(Some(vec
|
||||
pub fn notify<T: notification::Notification>(&self, params: T::Params) -> Result<()> {
|
||||
let outbound = self.notification_tx.clone();
|
||||
Self::notify_internal::<T>(&outbound, params)
|
||||
Self::notify_internal::<T>(&self.outbound_tx, params)
|
||||
}
|
||||
|
||||
fn notify_internal<T: notification::Notification>(
|
||||
outbound_tx: &async_channel::Sender<NotificationSerializer>,
|
||||
outbound_tx: &async_channel::Sender<OutboundMessage>,
|
||||
params: T::Params,
|
||||
) -> Result<()> {
|
||||
let serializer = NotificationSerializer(Box::new(move || {
|
||||
let message = OutboundMessage::PendingSerialization(Box::new(move || {
|
||||
serde_json::to_string(&Notification {
|
||||
jsonrpc: JSON_RPC_VERSION,
|
||||
method: T::METHOD,
|
||||
@@ -1696,7 +1696,7 @@ impl LanguageServer {
|
||||
.unwrap()
|
||||
}));
|
||||
|
||||
outbound_tx.send_blocking(serializer)?;
|
||||
outbound_tx.send_blocking(message)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2260,6 +2260,69 @@ mod tests {
|
||||
fake.receive_notification::<notification::Exit>().await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_notifications_and_requests_preserve_send_order(cx: &mut TestAppContext) {
|
||||
let (server, mut stdout, output) = shutdown_test_server(None, cx);
|
||||
let uri = Uri::from_str("file:///test.py").expect("valid document URI");
|
||||
let notify_change = |version, text: &str| {
|
||||
server
|
||||
.notify::<notification::DidChangeTextDocument>(DidChangeTextDocumentParams {
|
||||
text_document: VersionedTextDocumentIdentifier::new(uri.clone(), version),
|
||||
content_changes: vec![TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text: text.to_owned(),
|
||||
}],
|
||||
})
|
||||
.expect("document change must be queued");
|
||||
};
|
||||
|
||||
notify_change(1, "α = 1\n");
|
||||
let formatting = server.request::<request::Formatting>(
|
||||
DocumentFormattingParams {
|
||||
text_document: TextDocumentIdentifier::new(uri.clone()),
|
||||
options: FormattingOptions {
|
||||
tab_size: 4,
|
||||
insert_spaces: true,
|
||||
..FormattingOptions::default()
|
||||
},
|
||||
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||
},
|
||||
DEFAULT_LSP_REQUEST_TIMEOUT,
|
||||
);
|
||||
notify_change(2, "α = 2\n");
|
||||
cx.run_until_parked();
|
||||
|
||||
let expected = [
|
||||
r#"{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///test.py","version":1},"contentChanges":[{"text":"α = 1\n"}]}}"#,
|
||||
r#"{"jsonrpc":"2.0","id":0,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///test.py"},"options":{"tabSize":4,"insertSpaces":true}}}"#,
|
||||
r#"{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///test.py","version":2},"contentChanges":[{"text":"α = 2\n"}]}}"#,
|
||||
]
|
||||
.into_iter()
|
||||
.flat_map(framed_test_message)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
String::from_utf8(output.lock().bytes.clone()).expect("UTF-8 output"),
|
||||
String::from_utf8(expected.clone()).expect("UTF-8 expected output"),
|
||||
);
|
||||
|
||||
stdout
|
||||
.write_all(&framed_test_message(
|
||||
r#"{"jsonrpc":"2.0","id":0,"result":[]}"#,
|
||||
))
|
||||
.await
|
||||
.expect("formatting response must reach stdout");
|
||||
assert_eq!(
|
||||
formatting
|
||||
.await
|
||||
.into_response()
|
||||
.expect("formatting response"),
|
||||
Some(Vec::new()),
|
||||
);
|
||||
cx.run_until_parked();
|
||||
assert_eq!(output.lock().bytes, expected);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_shutdown_bounds_output_drain(cx: &mut TestAppContext) {
|
||||
for remaining_flushes in [Some(0), Some(1), None] {
|
||||
|
||||
Reference in new issue
Block a user