Merge pull request #1692 from zed-industries/avoid-duplicate-autoformat-edits
Avoid duplicate autoformat edits
This commit is contained in:
commit
ec76146a23
3 changed files with 738 additions and 659 deletions
|
@ -1,9 +1,8 @@
|
||||||
|
use super::*;
|
||||||
use crate::test::{
|
use crate::test::{
|
||||||
assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext,
|
assert_text_with_selections, build_editor, select_ranges, EditorLspTestContext,
|
||||||
EditorTestContext,
|
EditorTestContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
geometry::rect::RectF,
|
geometry::rect::RectF,
|
||||||
|
@ -3846,6 +3845,63 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
|
||||||
|
cx.foreground().forbid_parking();
|
||||||
|
|
||||||
|
let mut cx = EditorLspTestContext::new_rust(
|
||||||
|
lsp::ServerCapabilities {
|
||||||
|
document_formatting_provider: Some(lsp::OneOf::Left(true)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.set_state(indoc! {"
|
||||||
|
one.twoˇ
|
||||||
|
"});
|
||||||
|
|
||||||
|
// The format request takes a long time. When it completes, it inserts
|
||||||
|
// a newline and an indent before the `.`
|
||||||
|
cx.lsp
|
||||||
|
.handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
|
||||||
|
let executor = cx.background();
|
||||||
|
async move {
|
||||||
|
executor.timer(Duration::from_millis(100)).await;
|
||||||
|
Ok(Some(vec![lsp::TextEdit {
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
|
||||||
|
new_text: "\n ".into(),
|
||||||
|
}]))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit a format request.
|
||||||
|
let format_1 = cx
|
||||||
|
.update_editor(|editor, cx| editor.format(&Format, cx))
|
||||||
|
.unwrap();
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// Submit a second format request.
|
||||||
|
let format_2 = cx
|
||||||
|
.update_editor(|editor, cx| editor.format(&Format, cx))
|
||||||
|
.unwrap();
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
// Wait for both format requests to complete
|
||||||
|
cx.foreground().advance_clock(Duration::from_millis(200));
|
||||||
|
cx.foreground().start_waiting();
|
||||||
|
format_1.await.unwrap();
|
||||||
|
cx.foreground().start_waiting();
|
||||||
|
format_2.await.unwrap();
|
||||||
|
|
||||||
|
// The formatting edits only happens once.
|
||||||
|
cx.assert_editor_state(indoc! {"
|
||||||
|
one
|
||||||
|
.twoˇ
|
||||||
|
"});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||||
let mut cx = EditorLspTestContext::new_rust(
|
let mut cx = EditorLspTestContext::new_rust(
|
||||||
|
@ -3927,7 +3983,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
|
||||||
three sˇ
|
three sˇ
|
||||||
additional edit
|
additional edit
|
||||||
"});
|
"});
|
||||||
//
|
|
||||||
handle_completion_request(
|
handle_completion_request(
|
||||||
&mut cx,
|
&mut cx,
|
||||||
indoc! {"
|
indoc! {"
|
||||||
|
@ -4580,7 +4635,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
|
||||||
);
|
);
|
||||||
|
|
||||||
let text = concat!(
|
let text = concat!(
|
||||||
"{ }\n", // Suppress rustfmt
|
"{ }\n", //
|
||||||
" x\n", //
|
" x\n", //
|
||||||
" /* */\n", //
|
" /* */\n", //
|
||||||
"x\n", //
|
"x\n", //
|
||||||
|
|
|
@ -325,7 +325,12 @@ impl Deterministic {
|
||||||
let mut state = self.state.lock();
|
let mut state = self.state.lock();
|
||||||
let wakeup_at = state.now + duration;
|
let wakeup_at = state.now + duration;
|
||||||
let id = util::post_inc(&mut state.next_timer_id);
|
let id = util::post_inc(&mut state.next_timer_id);
|
||||||
state.pending_timers.push((id, wakeup_at, tx));
|
match state
|
||||||
|
.pending_timers
|
||||||
|
.binary_search_by_key(&wakeup_at, |e| e.1)
|
||||||
|
{
|
||||||
|
Ok(ix) | Err(ix) => state.pending_timers.insert(ix, (id, wakeup_at, tx)),
|
||||||
|
}
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
Timer::Deterministic(DeterministicTimer { rx, id, state })
|
Timer::Deterministic(DeterministicTimer { rx, id, state })
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,7 @@ pub mod worktree;
|
||||||
mod project_tests;
|
mod project_tests;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use client::{
|
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
|
||||||
proto::{self},
|
|
||||||
Client, PeerId, TypedEnvelope, User, UserStore,
|
|
||||||
};
|
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
use collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||||
use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt};
|
use futures::{future::Shared, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt};
|
||||||
|
@ -66,7 +63,7 @@ use std::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use util::{post_inc, ResultExt, TryFutureExt as _};
|
use util::{defer, post_inc, ResultExt, TryFutureExt as _};
|
||||||
|
|
||||||
pub use db::Db;
|
pub use db::Db;
|
||||||
pub use fs::*;
|
pub use fs::*;
|
||||||
|
@ -128,6 +125,7 @@ pub struct Project {
|
||||||
opened_buffers: HashMap<u64, OpenBuffer>,
|
opened_buffers: HashMap<u64, OpenBuffer>,
|
||||||
incomplete_buffers: HashMap<u64, ModelHandle<Buffer>>,
|
incomplete_buffers: HashMap<u64, ModelHandle<Buffer>>,
|
||||||
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
|
buffer_snapshots: HashMap<u64, Vec<(i32, TextBufferSnapshot)>>,
|
||||||
|
buffers_being_formatted: HashSet<usize>,
|
||||||
nonce: u128,
|
nonce: u128,
|
||||||
initialized_persistent_state: bool,
|
initialized_persistent_state: bool,
|
||||||
_maintain_buffer_languages: Task<()>,
|
_maintain_buffer_languages: Task<()>,
|
||||||
|
@ -512,6 +510,7 @@ impl Project {
|
||||||
language_server_statuses: Default::default(),
|
language_server_statuses: Default::default(),
|
||||||
last_workspace_edits_by_language_server: Default::default(),
|
last_workspace_edits_by_language_server: Default::default(),
|
||||||
language_server_settings: Default::default(),
|
language_server_settings: Default::default(),
|
||||||
|
buffers_being_formatted: Default::default(),
|
||||||
next_language_server_id: 0,
|
next_language_server_id: 0,
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
initialized_persistent_state: false,
|
initialized_persistent_state: false,
|
||||||
|
@ -627,6 +626,7 @@ impl Project {
|
||||||
last_workspace_edits_by_language_server: Default::default(),
|
last_workspace_edits_by_language_server: Default::default(),
|
||||||
next_language_server_id: 0,
|
next_language_server_id: 0,
|
||||||
opened_buffers: Default::default(),
|
opened_buffers: Default::default(),
|
||||||
|
buffers_being_formatted: Default::default(),
|
||||||
buffer_snapshots: Default::default(),
|
buffer_snapshots: Default::default(),
|
||||||
nonce: StdRng::from_entropy().gen(),
|
nonce: StdRng::from_entropy().gen(),
|
||||||
initialized_persistent_state: false,
|
initialized_persistent_state: false,
|
||||||
|
@ -3113,7 +3113,26 @@ impl Project {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (buffer, buffer_abs_path, language_server) in local_buffers {
|
// Do not allow multiple concurrent formatting requests for the
|
||||||
|
// same buffer.
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
local_buffers
|
||||||
|
.retain(|(buffer, _, _)| this.buffers_being_formatted.insert(buffer.id()));
|
||||||
|
});
|
||||||
|
let _cleanup = defer({
|
||||||
|
let this = this.clone();
|
||||||
|
let mut cx = cx.clone();
|
||||||
|
let local_buffers = &local_buffers;
|
||||||
|
move || {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
for (buffer, _, _) in local_buffers {
|
||||||
|
this.buffers_being_formatted.remove(&buffer.id());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (buffer, buffer_abs_path, language_server) in &local_buffers {
|
||||||
let (format_on_save, formatter, tab_size) = buffer.read_with(&cx, |buffer, cx| {
|
let (format_on_save, formatter, tab_size) = buffer.read_with(&cx, |buffer, cx| {
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
let language_name = buffer.language().map(|language| language.name());
|
let language_name = buffer.language().map(|language| language.name());
|
||||||
|
@ -3165,7 +3184,7 @@ impl Project {
|
||||||
buffer.forget_transaction(transaction.id)
|
buffer.forget_transaction(transaction.id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
project_transaction.0.insert(buffer, transaction);
|
project_transaction.0.insert(buffer.clone(), transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue