mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
ep: Fix agent edits triggering edit predictions due to diagnostic refresh (#57832)
Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - N/A or Added/Fixed/Improved ... --------- Co-authored-by: zed-zippy[bot] <234243425+zed-zippy[bot]@users.noreply.github.com>
This commit is contained in:
parent
8042408df4
commit
b8c853a63d
14 changed files with 274 additions and 64 deletions
|
|
@ -16,7 +16,9 @@ use gpui::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use language::language_settings::FormatOnSave;
|
||||
use language::{Anchor, Buffer, BufferSnapshot, LanguageRegistry, Point, ToPoint, text_diff};
|
||||
use language::{
|
||||
Anchor, Buffer, BufferEditSource, BufferSnapshot, LanguageRegistry, Point, ToPoint, text_diff,
|
||||
};
|
||||
use markdown::{Markdown, MarkdownOptions};
|
||||
pub use mention::*;
|
||||
use project::lsp_store::{FormatTrigger, LspFormatTarget};
|
||||
|
|
@ -2912,7 +2914,9 @@ impl AcpThread {
|
|||
});
|
||||
|
||||
let format_on_save = buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction();
|
||||
buffer.edit(edits, None, cx);
|
||||
buffer.end_transaction_with_source(BufferEditSource::Agent, cx);
|
||||
|
||||
let settings =
|
||||
language::language_settings::LanguageSettings::for_buffer(buffer, cx);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use collections::HashSet;
|
|||
use futures::{FutureExt, channel::oneshot};
|
||||
use gpui::{App, AppContext, AsyncApp, Entity, Task, WeakEntity};
|
||||
use language::language_settings::{self, FormatOnSave};
|
||||
use language::{Buffer, BufferEvent, LanguageRegistry};
|
||||
use language::{Buffer, BufferEditSource, BufferEvent, LanguageRegistry};
|
||||
use language_model::LanguageModelToolResultContent;
|
||||
use project::lsp_store::{FormatTrigger, LspFormatTarget};
|
||||
use project::{AgentLocation, Project, ProjectPath};
|
||||
|
|
@ -975,7 +975,9 @@ fn agent_edit_buffer<I, S, T>(
|
|||
{
|
||||
cx.update(|cx| {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction();
|
||||
buffer.edit(edits, None, cx);
|
||||
buffer.end_transaction_with_source(BufferEditSource::Agent, cx);
|
||||
});
|
||||
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ use futures::{
|
|||
stream::BoxStream,
|
||||
};
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{Buffer, IndentKind, LanguageName, Point, TransactionId, line_diff};
|
||||
use language::{
|
||||
Buffer, BufferEditSource, IndentKind, LanguageName, Point, TransactionId, line_diff,
|
||||
};
|
||||
use language_model::{
|
||||
CompletionIntent, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
|
|
@ -978,7 +980,7 @@ impl CodegenAlternative {
|
|||
buffer.finalize_last_transaction(cx);
|
||||
buffer.start_transaction(cx);
|
||||
buffer.edit(edits, None, cx);
|
||||
buffer.end_transaction(cx)
|
||||
buffer.end_transaction_with_source(BufferEditSource::Agent, cx)
|
||||
});
|
||||
|
||||
if let Some(transaction) = transaction {
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ use gpui::{
|
|||
};
|
||||
use heapless::Vec as ArrayVec;
|
||||
use language::{
|
||||
Anchor, Buffer, BufferSnapshot, EditPredictionPromptFormat, EditPredictionsMode, EditPreview,
|
||||
File, OffsetRangeExt, Point, TextBufferSnapshot, ToOffset, ToPoint,
|
||||
language_settings::all_language_settings,
|
||||
Anchor, Buffer, BufferEditSource, BufferSnapshot, EditPredictionPromptFormat,
|
||||
EditPredictionsMode, EditPreview, File, OffsetRangeExt, Point, TextBufferSnapshot, ToOffset,
|
||||
ToPoint, language_settings::all_language_settings,
|
||||
};
|
||||
use project::{DisableAiSettings, Project, ProjectPath, WorktreeId};
|
||||
use release_channel::AppVersion;
|
||||
|
|
@ -324,6 +324,7 @@ struct ProjectState {
|
|||
recent_paths: VecDeque<ProjectPath>,
|
||||
registered_buffers: HashMap<gpui::EntityId, RegisteredBuffer>,
|
||||
current_prediction: Option<CurrentEditPrediction>,
|
||||
last_edit_source: Option<BufferEditSource>,
|
||||
next_pending_prediction_id: usize,
|
||||
pending_predictions: ArrayVec<PendingPrediction, 2, u8>,
|
||||
debug_tx: Option<mpsc::UnboundedSender<DebugEvent>>,
|
||||
|
|
@ -1212,6 +1213,7 @@ impl EditPredictionStore {
|
|||
debug_tx: None,
|
||||
registered_buffers: HashMap::default(),
|
||||
current_prediction: None,
|
||||
last_edit_source: None,
|
||||
cancelled_predictions: HashSet::default(),
|
||||
pending_predictions: ArrayVec::new(),
|
||||
next_pending_prediction_id: 0,
|
||||
|
|
@ -1315,6 +1317,9 @@ impl EditPredictionStore {
|
|||
}
|
||||
// TODO [zeta2] init with recent paths
|
||||
match event {
|
||||
project::Event::BufferEdited { source } => {
|
||||
self.get_or_init_project(&project, cx).last_edit_source = Some(*source);
|
||||
}
|
||||
project::Event::ActiveEntryChanged(Some(active_entry_id)) => {
|
||||
let Some(project_state) = self.projects.get_mut(&project.entity_id()) else {
|
||||
return;
|
||||
|
|
@ -1332,6 +1337,15 @@ impl EditPredictionStore {
|
|||
}
|
||||
}
|
||||
project::Event::DiagnosticsUpdated { .. } => {
|
||||
if self
|
||||
.projects
|
||||
.get(&project.entity_id())
|
||||
.and_then(|project_state| project_state.last_edit_source)
|
||||
== Some(BufferEditSource::Agent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if cx.has_flag::<EditPredictionJumpsFeatureFlag>() {
|
||||
self.refresh_prediction_from_diagnostics(
|
||||
project,
|
||||
|
|
@ -1391,11 +1405,17 @@ impl EditPredictionStore {
|
|||
cx.subscribe(buffer, {
|
||||
let project = project.downgrade();
|
||||
move |this, buffer, event, cx| {
|
||||
if let language::BufferEvent::Edited { is_local } = event
|
||||
if let language::BufferEvent::Edited { source } = event
|
||||
&& let Some(project) = project.upgrade()
|
||||
{
|
||||
let project_state = this.get_or_init_project(&project, cx);
|
||||
project_state.last_edit_source = Some(*source);
|
||||
this.report_changes_for_buffer(
|
||||
&buffer, &project, false, *is_local, cx,
|
||||
&buffer,
|
||||
&project,
|
||||
false,
|
||||
source.is_local(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ use gpui::{
|
|||
};
|
||||
use indoc::indoc;
|
||||
use language::{
|
||||
Anchor, Buffer, Capability, CursorShape, Diagnostic, DiagnosticEntry, DiagnosticSet,
|
||||
DiagnosticSeverity, Operation, Point, Selection, SelectionGoal,
|
||||
Anchor, Buffer, BufferEditSource, Capability, CursorShape, Diagnostic, DiagnosticEntry,
|
||||
DiagnosticSet, DiagnosticSeverity, Operation, Point, Selection, SelectionGoal,
|
||||
};
|
||||
|
||||
use lsp::LanguageServerId;
|
||||
|
|
@ -352,6 +352,70 @@ async fn test_diagnostics_refresh_suppressed_while_following(cx: &mut TestAppCon
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_refresh_suppressed_after_agent_edit(cx: &mut TestAppContext) {
|
||||
let (ep_store, mut requests) = init_test_with_fake_client(cx);
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_flags(
|
||||
false,
|
||||
vec![EditPredictionJumpsFeatureFlag::NAME.to_string()],
|
||||
);
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
"1.txt": "Hello!\nHow\nBye\n",
|
||||
"2.txt": "Hola!\nComo\nAdios\n"
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, vec![path!("/root").as_ref()], cx).await;
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
let path = project.find_project_path(path!("root/1.txt"), cx).unwrap();
|
||||
project.set_active_path(Some(path.clone()), cx);
|
||||
project.open_buffer(path, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
ep_store.update(cx, |ep_store, cx| {
|
||||
ep_store.register_project(&project, cx);
|
||||
ep_store.register_buffer(&buffer, &project, cx);
|
||||
});
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.start_transaction();
|
||||
buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "!")], None, cx);
|
||||
buffer.end_transaction_with_source(BufferEditSource::Agent, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
update_test_diagnostics(&project, path!("/root/2.txt"), "Sentence is incomplete", cx);
|
||||
cx.run_until_parked();
|
||||
assert_no_predict_request_ready(&mut requests.predict);
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit([(Point::new(1, 4)..Point::new(1, 4), "?")], None, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
update_test_diagnostics(
|
||||
&project,
|
||||
path!("/root/2.txt"),
|
||||
"Sentence is still incomplete",
|
||||
cx,
|
||||
);
|
||||
|
||||
let (_request, respond_tx) = requests.predict.next().await.unwrap();
|
||||
respond_tx.send(empty_response()).unwrap();
|
||||
cx.run_until_parked();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_simple_request(cx: &mut TestAppContext) {
|
||||
let (ep_store, mut requests) = init_test_with_fake_client(cx);
|
||||
|
|
@ -2498,6 +2562,39 @@ fn assert_no_predict_request_ready(
|
|||
}
|
||||
}
|
||||
|
||||
fn update_test_diagnostics(
|
||||
project: &Entity<Project>,
|
||||
path: &str,
|
||||
message: &str,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
let diagnostic = lsp::Diagnostic {
|
||||
range: lsp::Range::new(lsp::Position::new(1, 1), lsp::Position::new(1, 5)),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
message: message.to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.lsp_store().update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Uri::from_file_path(path).unwrap(),
|
||||
diagnostics: vec![diagnostic],
|
||||
version: None,
|
||||
},
|
||||
None,
|
||||
language::DiagnosticSourceKind::Pushed,
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
struct RequestChannels {
|
||||
predict: mpsc::UnboundedReceiver<(
|
||||
PredictEditsV3Request,
|
||||
|
|
|
|||
|
|
@ -9231,7 +9231,7 @@ impl Editor {
|
|||
match event {
|
||||
multi_buffer::Event::Edited {
|
||||
edited_buffer,
|
||||
is_local,
|
||||
source,
|
||||
} => {
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
self.active_indent_guides_state.dirty = true;
|
||||
|
|
@ -9242,7 +9242,7 @@ impl Editor {
|
|||
self.refresh_matching_bracket_highlights(&snapshot, cx);
|
||||
self.refresh_outline_symbols_at_cursor(cx);
|
||||
self.refresh_sticky_headers(&snapshot, cx);
|
||||
if *is_local && self.has_active_edit_prediction() {
|
||||
if source.is_local() && self.has_active_edit_prediction() {
|
||||
self.update_visible_edit_prediction(window, cx);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,6 +297,19 @@ pub enum Operation {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BufferEditSource {
|
||||
User,
|
||||
Agent,
|
||||
Remote,
|
||||
}
|
||||
|
||||
impl BufferEditSource {
|
||||
pub fn is_local(self) -> bool {
|
||||
!matches!(self, Self::Remote)
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that occurs in a buffer.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum BufferEvent {
|
||||
|
|
@ -307,7 +320,7 @@ pub enum BufferEvent {
|
|||
is_local: bool,
|
||||
},
|
||||
/// The buffer was edited.
|
||||
Edited { is_local: bool },
|
||||
Edited { source: BufferEditSource },
|
||||
/// The buffer's `dirty` bit changed.
|
||||
DirtyChanged,
|
||||
/// The buffer was saved.
|
||||
|
|
@ -2433,6 +2446,14 @@ impl Buffer {
|
|||
self.end_transaction_at(Instant::now(), cx)
|
||||
}
|
||||
|
||||
pub fn end_transaction_with_source(
|
||||
&mut self,
|
||||
source: BufferEditSource,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
self.end_transaction_at_internal(Instant::now(), source, cx)
|
||||
}
|
||||
|
||||
/// Terminates the current transaction, providing the current time. Subsequent transactions
|
||||
/// that occur within a short period of time will be grouped together. This
|
||||
/// is controlled by the buffer's undo grouping duration.
|
||||
|
|
@ -2440,6 +2461,15 @@ impl Buffer {
|
|||
&mut self,
|
||||
now: Instant,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
self.end_transaction_at_internal(now, BufferEditSource::User, cx)
|
||||
}
|
||||
|
||||
fn end_transaction_at_internal(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
source: BufferEditSource,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
assert!(self.transaction_depth > 0);
|
||||
self.transaction_depth -= 1;
|
||||
|
|
@ -2449,7 +2479,7 @@ impl Buffer {
|
|||
false
|
||||
};
|
||||
if let Some((transaction_id, start_version)) = self.text.end_transaction_at(now) {
|
||||
self.did_edit(&start_version, was_dirty, true, cx);
|
||||
self.did_edit(&start_version, was_dirty, source, cx);
|
||||
Some(transaction_id)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -2844,7 +2874,7 @@ impl Buffer {
|
|||
&mut self,
|
||||
old_version: &clock::Global,
|
||||
was_dirty: bool,
|
||||
is_local: bool,
|
||||
source: BufferEditSource,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.was_changed();
|
||||
|
|
@ -2854,7 +2884,7 @@ impl Buffer {
|
|||
}
|
||||
|
||||
self.reparse(cx, true);
|
||||
cx.emit(BufferEvent::Edited { is_local });
|
||||
cx.emit(BufferEvent::Edited { source });
|
||||
let is_dirty = self.is_dirty();
|
||||
if was_dirty != is_dirty {
|
||||
cx.emit(BufferEvent::DirtyChanged);
|
||||
|
|
@ -2976,7 +3006,7 @@ impl Buffer {
|
|||
self.text.apply_ops(buffer_ops);
|
||||
self.deferred_ops.insert(deferred_ops);
|
||||
self.flush_deferred_ops(cx);
|
||||
self.did_edit(&old_version, was_dirty, false, cx);
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::Remote, cx);
|
||||
// Notify independently of whether the buffer was edited as the operations could include a
|
||||
// selection update.
|
||||
cx.notify();
|
||||
|
|
@ -3131,7 +3161,7 @@ impl Buffer {
|
|||
|
||||
if let Some((transaction_id, operation)) = self.text.undo() {
|
||||
self.send_operation(Operation::Buffer(operation), true, cx);
|
||||
self.did_edit(&old_version, was_dirty, true, cx);
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx);
|
||||
self.restore_encoding_for_transaction(transaction_id, was_dirty);
|
||||
Some(transaction_id)
|
||||
} else {
|
||||
|
|
@ -3149,7 +3179,7 @@ impl Buffer {
|
|||
let old_version = self.version.clone();
|
||||
if let Some(operation) = self.text.undo_transaction(transaction_id) {
|
||||
self.send_operation(Operation::Buffer(operation), true, cx);
|
||||
self.did_edit(&old_version, was_dirty, true, cx);
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
@ -3171,7 +3201,7 @@ impl Buffer {
|
|||
self.send_operation(Operation::Buffer(operation), true, cx);
|
||||
}
|
||||
if undone {
|
||||
self.did_edit(&old_version, was_dirty, true, cx)
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx)
|
||||
}
|
||||
undone
|
||||
}
|
||||
|
|
@ -3181,7 +3211,7 @@ impl Buffer {
|
|||
let operation = self.text.undo_operations(counts);
|
||||
let old_version = self.version.clone();
|
||||
self.send_operation(Operation::Buffer(operation), true, cx);
|
||||
self.did_edit(&old_version, was_dirty, true, cx);
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx);
|
||||
}
|
||||
|
||||
/// Manually redoes a specific transaction in the buffer's redo history.
|
||||
|
|
@ -3191,7 +3221,7 @@ impl Buffer {
|
|||
|
||||
if let Some((transaction_id, operation)) = self.text.redo() {
|
||||
self.send_operation(Operation::Buffer(operation), true, cx);
|
||||
self.did_edit(&old_version, was_dirty, true, cx);
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx);
|
||||
self.restore_encoding_for_transaction(transaction_id, was_dirty);
|
||||
Some(transaction_id)
|
||||
} else {
|
||||
|
|
@ -3232,7 +3262,7 @@ impl Buffer {
|
|||
self.send_operation(Operation::Buffer(operation), true, cx);
|
||||
}
|
||||
if redone {
|
||||
self.did_edit(&old_version, was_dirty, true, cx)
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx)
|
||||
}
|
||||
redone
|
||||
}
|
||||
|
|
@ -3342,7 +3372,7 @@ impl Buffer {
|
|||
if !ops.is_empty() {
|
||||
for op in ops {
|
||||
self.send_operation(Operation::Buffer(op), true, cx);
|
||||
self.did_edit(&old_version, was_dirty, true, cx);
|
||||
self.did_edit(&old_version, was_dirty, BufferEditSource::User, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -460,16 +460,24 @@ fn test_edit_events(cx: &mut gpui::App) {
|
|||
assert_eq!(
|
||||
mem::take(&mut *buffer_1_events.lock()),
|
||||
vec![
|
||||
BufferEvent::Edited { is_local: true },
|
||||
BufferEvent::Edited {
|
||||
source: BufferEditSource::User
|
||||
},
|
||||
BufferEvent::DirtyChanged,
|
||||
BufferEvent::Edited { is_local: true },
|
||||
BufferEvent::Edited { is_local: true },
|
||||
BufferEvent::Edited {
|
||||
source: BufferEditSource::User
|
||||
},
|
||||
BufferEvent::Edited {
|
||||
source: BufferEditSource::User
|
||||
},
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
mem::take(&mut *buffer_2_events.lock()),
|
||||
vec![
|
||||
BufferEvent::Edited { is_local: false },
|
||||
BufferEvent::Edited {
|
||||
source: BufferEditSource::Remote
|
||||
},
|
||||
BufferEvent::DirtyChanged
|
||||
]
|
||||
);
|
||||
|
|
@ -487,14 +495,18 @@ fn test_edit_events(cx: &mut gpui::App) {
|
|||
assert_eq!(
|
||||
mem::take(&mut *buffer_1_events.lock()),
|
||||
vec![
|
||||
BufferEvent::Edited { is_local: true },
|
||||
BufferEvent::Edited {
|
||||
source: BufferEditSource::User
|
||||
},
|
||||
BufferEvent::DirtyChanged,
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
mem::take(&mut *buffer_2_events.lock()),
|
||||
vec![
|
||||
BufferEvent::Edited { is_local: false },
|
||||
BufferEvent::Edited {
|
||||
source: BufferEditSource::Remote
|
||||
},
|
||||
BufferEvent::DirtyChanged
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ use futures_lite::future::yield_now;
|
|||
use gpui::{App, Context, Entity, EventEmitter};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
AutoindentMode, Buffer, BufferChunks, BufferRow, BufferSnapshot, Capability, CharClassifier,
|
||||
CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, File, IndentGuideSettings,
|
||||
IndentSize, Language, LanguageAwareStyling, LanguageScope, OffsetRangeExt, OffsetUtf16,
|
||||
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _,
|
||||
ToPoint as _, TransactionId, TreeSitterOptions, Unclipped,
|
||||
AutoindentMode, Buffer, BufferChunks, BufferEditSource, BufferRow, BufferSnapshot, Capability,
|
||||
CharClassifier, CharKind, CharScopeContext, Chunk, CursorShape, DiagnosticEntryRef, File,
|
||||
IndentGuideSettings, IndentSize, Language, LanguageAwareStyling, LanguageScope, OffsetRangeExt,
|
||||
OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject,
|
||||
ToOffset as _, ToPoint as _, TransactionId, TreeSitterOptions, Unclipped,
|
||||
language_settings::{AllLanguageSettings, LanguageSettings},
|
||||
};
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ pub enum Event {
|
|||
DiffHunksToggled,
|
||||
Edited {
|
||||
edited_buffer: Option<Entity<Buffer>>,
|
||||
is_local: bool,
|
||||
source: BufferEditSource,
|
||||
},
|
||||
TransactionUndone {
|
||||
transaction_id: TransactionId,
|
||||
|
|
@ -1828,7 +1828,7 @@ impl MultiBuffer {
|
|||
}
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
cx.emit(Event::BuffersRemoved { removed_buffer_ids });
|
||||
cx.notify();
|
||||
|
|
@ -1952,9 +1952,9 @@ impl MultiBuffer {
|
|||
use language::BufferEvent;
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
cx.emit(match event {
|
||||
&BufferEvent::Edited { is_local } => Event::Edited {
|
||||
&BufferEvent::Edited { source } => Event::Edited {
|
||||
edited_buffer: Some(buffer),
|
||||
is_local,
|
||||
source,
|
||||
},
|
||||
BufferEvent::DirtyChanged => Event::DirtyChanged,
|
||||
BufferEvent::Saved => Event::Saved,
|
||||
|
|
@ -2044,7 +2044,7 @@ impl MultiBuffer {
|
|||
}
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2090,7 +2090,7 @@ impl MultiBuffer {
|
|||
}
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2313,7 +2313,7 @@ impl MultiBuffer {
|
|||
cx.emit(Event::DiffHunksToggled);
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -2449,7 +2449,7 @@ impl MultiBuffer {
|
|||
cx.emit(Event::DiffHunksToggled);
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -3102,7 +3102,7 @@ impl MultiBuffer {
|
|||
cx.emit(Event::DiffHunksToggled);
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,15 +192,15 @@ fn test_excerpt_boundaries_and_clipping(cx: &mut App) {
|
|||
&[
|
||||
Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: language::BufferEditSource::User,
|
||||
},
|
||||
Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: language::BufferEditSource::User,
|
||||
},
|
||||
Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: language::BufferEditSource::User,
|
||||
}
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{ops::Range, rc::Rc, sync::Arc};
|
|||
|
||||
use gpui::{App, AppContext, Context, Entity};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use language::{Buffer, BufferEditSource, BufferSnapshot};
|
||||
use rope::Point;
|
||||
use sum_tree::{Dimensions, SumTree};
|
||||
use text::{Bias, BufferId, Edit, OffsetRangeExt, Patch};
|
||||
|
|
@ -603,7 +603,7 @@ impl MultiBuffer {
|
|||
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
cx.emit(Event::BufferRangesUpdated {
|
||||
buffer,
|
||||
|
|
@ -687,7 +687,7 @@ impl MultiBuffer {
|
|||
|
||||
cx.emit(Event::Edited {
|
||||
edited_buffer: None,
|
||||
is_local: true,
|
||||
source: BufferEditSource::User,
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use gpui::{App, Context, Entity};
|
||||
use language::{self, Buffer, TransactionId};
|
||||
use language::{self, Buffer, BufferEditSource, TransactionId};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::Range,
|
||||
|
|
@ -288,6 +288,35 @@ impl MultiBuffer {
|
|||
self.end_transaction_at(Instant::now(), cx)
|
||||
}
|
||||
|
||||
pub fn end_transaction_with_source(
|
||||
&mut self,
|
||||
source: BufferEditSource,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<TransactionId> {
|
||||
let now = Instant::now();
|
||||
if let Some(buffer) = self.as_singleton() {
|
||||
return buffer.update(cx, |buffer, cx| {
|
||||
buffer.end_transaction_with_source(source, cx)
|
||||
});
|
||||
}
|
||||
|
||||
let mut buffer_transactions = HashMap::default();
|
||||
for BufferState { buffer, .. } in self.buffers.values() {
|
||||
if let Some(transaction_id) = buffer.update(cx, |buffer, cx| {
|
||||
buffer.end_transaction_with_source(source, cx)
|
||||
}) {
|
||||
buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id);
|
||||
}
|
||||
}
|
||||
|
||||
if self.history.end_transaction(now, buffer_transactions) {
|
||||
let transaction_id = self.history.group().unwrap();
|
||||
Some(transaction_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_transaction_at(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
|
|
|
|||
|
|
@ -89,9 +89,9 @@ use gpui::{
|
|||
Task, TaskExt, WeakEntity, Window,
|
||||
};
|
||||
use language::{
|
||||
Buffer, BufferEvent, Capability, CodeLabel, CursorShape, DiskState, Language, LanguageName,
|
||||
LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain, ToolchainMetadata,
|
||||
ToolchainScope, Transaction, Unclipped, language_settings::InlayHintKind,
|
||||
Buffer, BufferEditSource, BufferEvent, Capability, CodeLabel, CursorShape, DiskState, Language,
|
||||
LanguageName, LanguageRegistry, PointUtf16, ToOffset, ToPointUtf16, Toolchain,
|
||||
ToolchainMetadata, ToolchainScope, Transaction, Unclipped, language_settings::InlayHintKind,
|
||||
proto::split_operations,
|
||||
};
|
||||
use lsp::{
|
||||
|
|
@ -410,7 +410,9 @@ pub enum Event {
|
|||
EntryRenamed(ProjectTransaction, ProjectPath, PathBuf),
|
||||
WorkspaceEditApplied(ProjectTransaction),
|
||||
AgentLocationChanged,
|
||||
BufferEdited,
|
||||
BufferEdited {
|
||||
source: BufferEditSource,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct AgentLocationChanged;
|
||||
|
|
@ -3810,8 +3812,8 @@ impl Project {
|
|||
self.request_buffer_diff_recalculation(&buffer, cx);
|
||||
}
|
||||
|
||||
if matches!(event, BufferEvent::Edited { .. }) {
|
||||
cx.emit(Event::BufferEdited);
|
||||
if let BufferEvent::Edited { source } = event {
|
||||
cx.emit(Event::BufferEdited { source: *source });
|
||||
}
|
||||
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
|
|
|
|||
|
|
@ -6073,7 +6073,9 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(
|
||||
*events.lock(),
|
||||
&[
|
||||
language::BufferEvent::Edited { is_local: true },
|
||||
language::BufferEvent::Edited {
|
||||
source: language::BufferEditSource::User
|
||||
},
|
||||
language::BufferEvent::DirtyChanged
|
||||
]
|
||||
);
|
||||
|
|
@ -6102,9 +6104,13 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(
|
||||
*events.lock(),
|
||||
&[
|
||||
language::BufferEvent::Edited { is_local: true },
|
||||
language::BufferEvent::Edited {
|
||||
source: language::BufferEditSource::User
|
||||
},
|
||||
language::BufferEvent::DirtyChanged,
|
||||
language::BufferEvent::Edited { is_local: true },
|
||||
language::BufferEvent::Edited {
|
||||
source: language::BufferEditSource::User
|
||||
},
|
||||
],
|
||||
);
|
||||
events.lock().clear();
|
||||
|
|
@ -6119,7 +6125,9 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(
|
||||
*events.lock(),
|
||||
&[
|
||||
language::BufferEvent::Edited { is_local: true },
|
||||
language::BufferEvent::Edited {
|
||||
source: language::BufferEditSource::User
|
||||
},
|
||||
language::BufferEvent::DirtyChanged
|
||||
]
|
||||
);
|
||||
|
|
@ -6159,7 +6167,9 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(
|
||||
mem::take(&mut *events.lock()),
|
||||
&[
|
||||
language::BufferEvent::Edited { is_local: true },
|
||||
language::BufferEvent::Edited {
|
||||
source: language::BufferEditSource::User
|
||||
},
|
||||
language::BufferEvent::DirtyChanged
|
||||
]
|
||||
);
|
||||
|
|
@ -6174,7 +6184,9 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
|
|||
assert_eq!(
|
||||
*events.lock(),
|
||||
&[
|
||||
language::BufferEvent::Edited { is_local: true },
|
||||
language::BufferEvent::Edited {
|
||||
source: language::BufferEditSource::User
|
||||
},
|
||||
language::BufferEvent::DirtyChanged
|
||||
]
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue