Detailed changes
@@ -79,7 +79,6 @@ pub fn init(cx: &mut AppContext) {
workspace.toggle_panel_focus::<AssistantPanel>(cx);
})
.register_action(AssistantPanel::inline_assist)
- .register_action(AssistantPanel::cancel_last_inline_assist)
.register_action(ContextEditor::quote_selection);
},
)
@@ -421,19 +420,6 @@ impl AssistantPanel {
}
}
- fn cancel_last_inline_assist(
- _workspace: &mut Workspace,
- _: &editor::actions::Cancel,
- cx: &mut ViewContext<Workspace>,
- ) {
- let canceled = InlineAssistant::update_global(cx, |assistant, cx| {
- assistant.cancel_last_inline_assist(cx)
- });
- if !canceled {
- cx.propagate();
- }
- }
-
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
let workspace = self.workspace.upgrade()?;
@@ -16,8 +16,8 @@ use editor::{
};
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
use gpui::{
- AnyWindowHandle, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
- Global, HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
+ AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
+ HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
ViewContext, WeakView, WhiteSpace, WindowContext,
};
use language::{Buffer, Point, TransactionId};
@@ -34,6 +34,7 @@ use std::{
};
use theme::ThemeSettings;
use ui::{prelude::*, Tooltip};
+use util::RangeExt;
use workspace::{notifications::NotificationId, Toast, Workspace};
pub fn init(telemetry: Arc<Telemetry>, cx: &mut AppContext) {
@@ -45,16 +46,11 @@ const PROMPT_HISTORY_MAX_LEN: usize = 20;
pub struct InlineAssistant {
next_assist_id: InlineAssistId,
pending_assists: HashMap<InlineAssistId, PendingInlineAssist>,
- pending_assist_ids_by_editor: HashMap<WeakView<Editor>, EditorPendingAssists>,
+ pending_assist_ids_by_editor: HashMap<WeakView<Editor>, Vec<InlineAssistId>>,
prompt_history: VecDeque<String>,
telemetry: Option<Arc<Telemetry>>,
}
-struct EditorPendingAssists {
- window: AnyWindowHandle,
- assist_ids: Vec<InlineAssistId>,
-}
-
impl Global for InlineAssistant {}
impl InlineAssistant {
@@ -103,7 +99,7 @@ impl InlineAssistant {
}
};
- let inline_assist_id = self.next_assist_id.post_inc();
+ let assist_id = self.next_assist_id.post_inc();
let codegen = cx.new_model(|cx| {
Codegen::new(
editor.read(cx).buffer().clone(),
@@ -116,7 +112,7 @@ impl InlineAssistant {
let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
let prompt_editor = cx.new_view(|cx| {
InlineAssistEditor::new(
- inline_assist_id,
+ assist_id,
gutter_dimensions.clone(),
self.prompt_history.clone(),
codegen.clone(),
@@ -164,7 +160,7 @@ impl InlineAssistant {
});
self.pending_assists.insert(
- inline_assist_id,
+ assist_id,
PendingInlineAssist {
include_context,
editor: editor.downgrade(),
@@ -179,24 +175,35 @@ impl InlineAssistant {
_subscriptions: vec![
cx.subscribe(&prompt_editor, |inline_assist_editor, event, cx| {
InlineAssistant::update_global(cx, |this, cx| {
- this.handle_inline_assistant_event(inline_assist_editor, event, cx)
+ this.handle_inline_assistant_editor_event(
+ inline_assist_editor,
+ event,
+ cx,
+ )
})
}),
- cx.subscribe(editor, {
- let inline_assist_editor = prompt_editor.downgrade();
- move |editor, event, cx| {
- if let Some(inline_assist_editor) = inline_assist_editor.upgrade() {
- if let EditorEvent::SelectionsChanged { local } = event {
- if *local
- && inline_assist_editor
- .focus_handle(cx)
- .contains_focused(cx)
- {
- cx.focus_view(&editor);
- }
- }
- }
- }
+ editor.update(cx, |editor, _cx| {
+ editor.register_action(
+ move |_: &editor::actions::Newline, cx: &mut WindowContext| {
+ InlineAssistant::update_global(cx, |this, cx| {
+ this.handle_editor_action(assist_id, false, cx)
+ })
+ },
+ )
+ }),
+ editor.update(cx, |editor, _cx| {
+ editor.register_action(
+ move |_: &editor::actions::Cancel, cx: &mut WindowContext| {
+ InlineAssistant::update_global(cx, |this, cx| {
+ this.handle_editor_action(assist_id, true, cx)
+ })
+ },
+ )
+ }),
+ cx.subscribe(editor, move |editor, event, cx| {
+ InlineAssistant::update_global(cx, |this, cx| {
+ this.handle_editor_event(assist_id, editor, event, cx)
+ })
}),
cx.observe(&codegen, {
let editor = editor.downgrade();
@@ -204,19 +211,17 @@ impl InlineAssistant {
if let Some(editor) = editor.upgrade() {
InlineAssistant::update_global(cx, |this, cx| {
this.update_editor_highlights(&editor, cx);
- this.update_editor_blocks(&editor, inline_assist_id, cx);
+ this.update_editor_blocks(&editor, assist_id, cx);
})
}
}
}),
cx.subscribe(&codegen, move |codegen, event, cx| {
InlineAssistant::update_global(cx, |this, cx| match event {
- CodegenEvent::Undone => {
- this.finish_inline_assist(inline_assist_id, false, cx)
- }
+ CodegenEvent::Undone => this.finish_inline_assist(assist_id, false, cx),
CodegenEvent::Finished => {
let pending_assist = if let Some(pending_assist) =
- this.pending_assists.get(&inline_assist_id)
+ this.pending_assists.get(&assist_id)
{
pending_assist
} else {
@@ -238,7 +243,7 @@ impl InlineAssistant {
let id = NotificationId::identified::<
InlineAssistantError,
>(
- inline_assist_id.0
+ assist_id.0
);
workspace.show_toast(Toast::new(id, error), cx);
@@ -248,7 +253,7 @@ impl InlineAssistant {
}
if pending_assist.editor_decorations.is_none() {
- this.finish_inline_assist(inline_assist_id, false, cx);
+ this.finish_inline_assist(assist_id, false, cx);
}
}
})
@@ -259,16 +264,12 @@ impl InlineAssistant {
self.pending_assist_ids_by_editor
.entry(editor.downgrade())
- .or_insert_with(|| EditorPendingAssists {
- window: cx.window_handle(),
- assist_ids: Vec::new(),
- })
- .assist_ids
- .push(inline_assist_id);
+ .or_default()
+ .push(assist_id);
self.update_editor_highlights(editor, cx);
}
- fn handle_inline_assistant_event(
+ fn handle_inline_assistant_editor_event(
&mut self,
inline_assist_editor: View<InlineAssistEditor>,
event: &InlineAssistEditorEvent,
@@ -289,7 +290,7 @@ impl InlineAssistant {
self.finish_inline_assist(assist_id, true, cx);
}
InlineAssistEditorEvent::Dismissed => {
- self.hide_inline_assist_decorations(assist_id, cx);
+ self.dismiss_inline_assist(assist_id, cx);
}
InlineAssistEditorEvent::Resized { height_in_lines } => {
self.resize_inline_assist(assist_id, *height_in_lines, cx);
@@ -297,20 +298,87 @@ impl InlineAssistant {
}
}
- pub fn cancel_last_inline_assist(&mut self, cx: &mut WindowContext) -> bool {
- for (editor, pending_assists) in &self.pending_assist_ids_by_editor {
- if pending_assists.window == cx.window_handle() {
- if let Some(editor) = editor.upgrade() {
- if editor.read(cx).is_focused(cx) {
- if let Some(assist_id) = pending_assists.assist_ids.last().copied() {
- self.finish_inline_assist(assist_id, true, cx);
- return true;
- }
+ fn handle_editor_action(
+ &mut self,
+ assist_id: InlineAssistId,
+ undo: bool,
+ cx: &mut WindowContext,
+ ) {
+ let Some(assist) = self.pending_assists.get(&assist_id) else {
+ return;
+ };
+ let Some(editor) = assist.editor.upgrade() else {
+ return;
+ };
+
+ let buffer = editor.read(cx).buffer().read(cx).snapshot(cx);
+ let assist_range = assist.codegen.read(cx).range().to_offset(&buffer);
+ let editor = editor.read(cx);
+ if editor.selections.count() == 1 {
+ let selection = editor.selections.newest::<usize>(cx);
+ if assist_range.contains(&selection.start) && assist_range.contains(&selection.end) {
+ if undo {
+ self.finish_inline_assist(assist_id, true, cx);
+ } else if matches!(assist.codegen.read(cx).status, CodegenStatus::Pending) {
+ self.dismiss_inline_assist(assist_id, cx);
+ } else {
+ self.finish_inline_assist(assist_id, false, cx);
+ }
+
+ return;
+ }
+ }
+
+ cx.propagate();
+ }
+
+ fn handle_editor_event(
+ &mut self,
+ assist_id: InlineAssistId,
+ editor: View<Editor>,
+ event: &EditorEvent,
+ cx: &mut WindowContext,
+ ) {
+ let Some(assist) = self.pending_assists.get(&assist_id) else {
+ return;
+ };
+
+ match event {
+ EditorEvent::SelectionsChanged { local } if *local => {
+ if let Some(decorations) = assist.editor_decorations.as_ref() {
+ if decorations
+ .prompt_editor
+ .focus_handle(cx)
+ .contains_focused(cx)
+ {
+ cx.focus_view(&editor);
}
}
}
+ EditorEvent::Saved => {
+ if let CodegenStatus::Done = &assist.codegen.read(cx).status {
+ self.finish_inline_assist(assist_id, false, cx)
+ }
+ }
+ EditorEvent::Edited { transaction_id }
+ if matches!(
+ assist.codegen.read(cx).status,
+ CodegenStatus::Error(_) | CodegenStatus::Done
+ ) =>
+ {
+ let buffer = editor.read(cx).buffer().read(cx);
+ let edited_ranges =
+ buffer.edited_ranges_for_transaction::<usize>(*transaction_id, cx);
+ let assist_range = assist.codegen.read(cx).range().to_offset(&buffer.read(cx));
+ if edited_ranges
+ .iter()
+ .any(|range| range.overlaps(&assist_range))
+ {
+ self.finish_inline_assist(assist_id, false, cx);
+ }
+ }
+ _ => {}
}
- false
}
fn finish_inline_assist(
@@ -319,15 +387,15 @@ impl InlineAssistant {
undo: bool,
cx: &mut WindowContext,
) {
- self.hide_inline_assist_decorations(assist_id, cx);
+ self.dismiss_inline_assist(assist_id, cx);
if let Some(pending_assist) = self.pending_assists.remove(&assist_id) {
if let hash_map::Entry::Occupied(mut entry) = self
.pending_assist_ids_by_editor
.entry(pending_assist.editor.clone())
{
- entry.get_mut().assist_ids.retain(|id| *id != assist_id);
- if entry.get().assist_ids.is_empty() {
+ entry.get_mut().retain(|id| *id != assist_id);
+ if entry.get().is_empty() {
entry.remove();
}
}
@@ -344,11 +412,7 @@ impl InlineAssistant {
}
}
- fn hide_inline_assist_decorations(
- &mut self,
- assist_id: InlineAssistId,
- cx: &mut WindowContext,
- ) -> bool {
+ fn dismiss_inline_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
let Some(pending_assist) = self.pending_assists.get_mut(&assist_id) else {
return false;
};
@@ -558,16 +622,14 @@ impl InlineAssistant {
let mut gutter_transformed_ranges = Vec::new();
let mut foreground_ranges = Vec::new();
let mut inserted_row_ranges = Vec::new();
- let empty_inline_assist_ids = Vec::new();
- let inline_assist_ids = self
+ let empty_assist_ids = Vec::new();
+ let assist_ids = self
.pending_assist_ids_by_editor
.get(&editor.downgrade())
- .map_or(&empty_inline_assist_ids, |pending_assists| {
- &pending_assists.assist_ids
- });
+ .unwrap_or(&empty_assist_ids);
- for inline_assist_id in inline_assist_ids {
- if let Some(pending_assist) = self.pending_assists.get(inline_assist_id) {
+ for assist_id in assist_ids {
+ if let Some(pending_assist) = self.pending_assists.get(assist_id) {
let codegen = pending_assist.codegen.read(cx);
foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
@@ -1025,7 +1087,7 @@ impl InlineAssistEditor {
cx: &mut ViewContext<Self>,
) {
match event {
- EditorEvent::Edited => {
+ EditorEvent::Edited { .. } => {
let prompt = self.prompt_editor.read(cx).text(cx);
if self
.prompt_history_ix
@@ -592,19 +592,6 @@ impl PromptLibrary {
}
}
- fn cancel_last_inline_assist(
- &mut self,
- _: &editor::actions::Cancel,
- cx: &mut ViewContext<Self>,
- ) {
- let canceled = InlineAssistant::update_global(cx, |assistant, cx| {
- assistant.cancel_last_inline_assist(cx)
- });
- if !canceled {
- cx.propagate();
- }
- }
-
fn handle_prompt_editor_event(
&mut self,
prompt_id: PromptId,
@@ -743,7 +730,6 @@ impl PromptLibrary {
div()
.on_action(cx.listener(Self::focus_picker))
.on_action(cx.listener(Self::inline_assist))
- .on_action(cx.listener(Self::cancel_last_inline_assist))
.flex_grow()
.h_full()
.pt(Spacing::XXLarge.rems(cx))
@@ -232,7 +232,7 @@ impl ChannelView {
this.focus_position_from_link(position.clone(), false, cx);
this._reparse_subscription.take();
}
- EditorEvent::Edited | EditorEvent::SelectionsChanged { local: true } => {
+ EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {
this._reparse_subscription.take();
}
_ => {}
@@ -116,15 +116,16 @@ use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use smallvec::SmallVec;
use snippet::Snippet;
-use std::ops::Not as _;
use std::{
any::TypeId,
borrow::Cow,
+ cell::RefCell,
cmp::{self, Ordering, Reverse},
mem,
num::NonZeroU32,
- ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
+ ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
path::Path,
+ rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
@@ -377,6 +378,19 @@ impl Default for EditorStyle {
type CompletionId = usize;
+#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
+struct EditorActionId(usize);
+
+impl EditorActionId {
+ pub fn post_inc(&mut self) -> Self {
+ let answer = self.0;
+
+ *self = Self(answer + 1);
+
+ Self(answer)
+ }
+}
+
// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
@@ -512,7 +526,8 @@ pub struct Editor {
gutter_dimensions: GutterDimensions,
pub vim_replace_map: HashMap<Range<usize>, String>,
style: Option<EditorStyle>,
- editor_actions: Vec<Box<dyn Fn(&mut ViewContext<Self>)>>,
+ next_editor_action_id: EditorActionId,
+ editor_actions: Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut ViewContext<Self>)>>>>,
use_autoclose: bool,
auto_replace_emoji_shortcode: bool,
show_git_blame_gutter: bool,
@@ -1805,7 +1820,8 @@ impl Editor {
style: None,
show_cursor_names: false,
hovered_cursors: Default::default(),
- editor_actions: Default::default(),
+ next_editor_action_id: EditorActionId::default(),
+ editor_actions: Rc::default(),
vim_replace_map: Default::default(),
show_inline_completions: mode == EditorMode::Full,
custom_context_menu: None,
@@ -6448,8 +6464,10 @@ impl Editor {
return;
}
- if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
- if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() {
+ if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
+ if let Some((selections, _)) =
+ self.selection_history.transaction(transaction_id).cloned()
+ {
self.change_selections(None, cx, |s| {
s.select_anchors(selections.to_vec());
});
@@ -6457,10 +6475,8 @@ impl Editor {
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, cx);
- cx.emit(EditorEvent::Edited);
- cx.emit(EditorEvent::TransactionUndone {
- transaction_id: tx_id,
- });
+ cx.emit(EditorEvent::Edited { transaction_id });
+ cx.emit(EditorEvent::TransactionUndone { transaction_id });
}
}
@@ -6469,8 +6485,9 @@ impl Editor {
return;
}
- if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
- if let Some((_, Some(selections))) = self.selection_history.transaction(tx_id).cloned()
+ if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
+ if let Some((_, Some(selections))) =
+ self.selection_history.transaction(transaction_id).cloned()
{
self.change_selections(None, cx, |s| {
s.select_anchors(selections.to_vec());
@@ -6479,7 +6496,7 @@ impl Editor {
self.request_autoscroll(Autoscroll::fit(), cx);
self.unmark_text(cx);
self.refresh_inline_completion(true, cx);
- cx.emit(EditorEvent::Edited);
+ cx.emit(EditorEvent::Edited { transaction_id });
}
}
@@ -9590,18 +9607,20 @@ impl Editor {
now: Instant,
cx: &mut ViewContext<Self>,
) -> Option<TransactionId> {
- if let Some(tx_id) = self
+ if let Some(transaction_id) = self
.buffer
.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
{
- if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) {
+ if let Some((_, end_selections)) =
+ self.selection_history.transaction_mut(transaction_id)
+ {
*end_selections = Some(self.selections.disjoint_anchors());
} else {
log::error!("unexpectedly ended a transaction that wasn't started by this editor");
}
- cx.emit(EditorEvent::Edited);
- Some(tx_id)
+ cx.emit(EditorEvent::Edited { transaction_id });
+ Some(transaction_id)
} else {
None
}
@@ -11293,21 +11312,28 @@ impl Editor {
pub fn register_action<A: Action>(
&mut self,
listener: impl Fn(&A, &mut WindowContext) + 'static,
- ) -> &mut Self {
+ ) -> Subscription {
+ let id = self.next_editor_action_id.post_inc();
let listener = Arc::new(listener);
+ self.editor_actions.borrow_mut().insert(
+ id,
+ Box::new(move |cx| {
+ let _view = cx.view().clone();
+ let cx = cx.window_context();
+ let listener = listener.clone();
+ cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
+ let action = action.downcast_ref().unwrap();
+ if phase == DispatchPhase::Bubble {
+ listener(action, cx)
+ }
+ })
+ }),
+ );
- self.editor_actions.push(Box::new(move |cx| {
- let _view = cx.view().clone();
- let cx = cx.window_context();
- let listener = listener.clone();
- cx.on_action(TypeId::of::<A>(), move |action, phase, cx| {
- let action = action.downcast_ref().unwrap();
- if phase == DispatchPhase::Bubble {
- listener(action, cx)
- }
- })
- }));
- self
+ let editor_actions = self.editor_actions.clone();
+ Subscription::new(move || {
+ editor_actions.borrow_mut().remove(&id);
+ })
}
pub fn file_header_size(&self) -> u8 {
@@ -11764,7 +11790,9 @@ pub enum EditorEvent {
ids: Vec<ExcerptId>,
},
BufferEdited,
- Edited,
+ Edited {
+ transaction_id: clock::Lamport,
+ },
Reparsed,
Focused,
Blurred,
@@ -57,10 +57,10 @@ fn test_edit_events(cx: &mut TestAppContext) {
let events = events.clone();
|cx| {
let view = cx.view().clone();
- cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
- if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
- events.borrow_mut().push(("editor1", event.clone()));
- }
+ cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
+ EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
+ EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
+ _ => {}
})
.detach();
Editor::for_buffer(buffer.clone(), None, cx)
@@ -70,11 +70,16 @@ fn test_edit_events(cx: &mut TestAppContext) {
let editor2 = cx.add_window({
let events = events.clone();
|cx| {
- cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
- if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
- events.borrow_mut().push(("editor2", event.clone()));
- }
- })
+ cx.subscribe(
+ &cx.view().clone(),
+ move |_, _, event: &EditorEvent, _| match event {
+ EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
+ EditorEvent::BufferEdited => {
+ events.borrow_mut().push(("editor2", "buffer edited"))
+ }
+ _ => {}
+ },
+ )
.detach();
Editor::for_buffer(buffer.clone(), None, cx)
}
@@ -87,9 +92,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
- ("editor1", EditorEvent::Edited),
- ("editor1", EditorEvent::BufferEdited),
- ("editor2", EditorEvent::BufferEdited),
+ ("editor1", "edited"),
+ ("editor1", "buffer edited"),
+ ("editor2", "buffer edited"),
]
);
@@ -98,9 +103,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
- ("editor2", EditorEvent::Edited),
- ("editor1", EditorEvent::BufferEdited),
- ("editor2", EditorEvent::BufferEdited),
+ ("editor2", "edited"),
+ ("editor1", "buffer edited"),
+ ("editor2", "buffer edited"),
]
);
@@ -109,9 +114,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
- ("editor1", EditorEvent::Edited),
- ("editor1", EditorEvent::BufferEdited),
- ("editor2", EditorEvent::BufferEdited),
+ ("editor1", "edited"),
+ ("editor1", "buffer edited"),
+ ("editor2", "buffer edited"),
]
);
@@ -120,9 +125,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
- ("editor1", EditorEvent::Edited),
- ("editor1", EditorEvent::BufferEdited),
- ("editor2", EditorEvent::BufferEdited),
+ ("editor1", "edited"),
+ ("editor1", "buffer edited"),
+ ("editor2", "buffer edited"),
]
);
@@ -131,9 +136,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
- ("editor2", EditorEvent::Edited),
- ("editor1", EditorEvent::BufferEdited),
- ("editor2", EditorEvent::BufferEdited),
+ ("editor2", "edited"),
+ ("editor1", "buffer edited"),
+ ("editor2", "buffer edited"),
]
);
@@ -142,9 +147,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
assert_eq!(
mem::take(&mut *events.borrow_mut()),
[
- ("editor2", EditorEvent::Edited),
- ("editor1", EditorEvent::BufferEdited),
- ("editor2", EditorEvent::BufferEdited),
+ ("editor2", "edited"),
+ ("editor1", "buffer edited"),
+ ("editor2", "buffer edited"),
]
);
@@ -153,7 +153,7 @@ impl EditorElement {
fn register_actions(&self, cx: &mut WindowContext) {
let view = &self.editor;
view.update(cx, |editor, cx| {
- for action in editor.editor_actions.iter() {
+ for action in editor.editor_actions.borrow().values() {
(action)(cx)
}
});
@@ -615,32 +615,36 @@ fn editor_with_deleted_text(
]);
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
let diff_base_range = hunk.diff_base_byte_range.clone();
- editor.register_action::<RevertSelectedHunks>(move |_, cx| {
- parent_editor
- .update(cx, |editor, cx| {
- let Some((buffer, original_text)) = editor.buffer().update(cx, |buffer, cx| {
- let (_, buffer, _) =
- buffer.excerpt_containing(original_multi_buffer_range.start, cx)?;
- let original_text =
- buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
- Some((buffer, Arc::from(original_text.to_string())))
- }) else {
- return;
- };
- buffer.update(cx, |buffer, cx| {
- buffer.edit(
- Some((
- original_multi_buffer_range.start.text_anchor
- ..original_multi_buffer_range.end.text_anchor,
- original_text,
- )),
- None,
- cx,
- )
- });
- })
- .ok();
- });
+ editor
+ .register_action::<RevertSelectedHunks>(move |_, cx| {
+ parent_editor
+ .update(cx, |editor, cx| {
+ let Some((buffer, original_text)) =
+ editor.buffer().update(cx, |buffer, cx| {
+ let (_, buffer, _) = buffer
+ .excerpt_containing(original_multi_buffer_range.start, cx)?;
+ let original_text =
+ buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
+ Some((buffer, Arc::from(original_text.to_string())))
+ })
+ else {
+ return;
+ };
+ buffer.update(cx, |buffer, cx| {
+ buffer.edit(
+ Some((
+ original_multi_buffer_range.start.text_anchor
+ ..original_multi_buffer_range.end.text_anchor,
+ original_text,
+ )),
+ None,
+ cx,
+ )
+ });
+ })
+ .ok();
+ })
+ .detach();
editor
});
@@ -234,7 +234,7 @@ impl FollowableItem for Editor {
fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
match event {
- EditorEvent::Edited => Some(FollowEvent::Unfollow),
+ EditorEvent::Edited { .. } => Some(FollowEvent::Unfollow),
EditorEvent::SelectionsChanged { local }
| EditorEvent::ScrollPositionChanged { local, .. } => {
if *local {
@@ -773,7 +773,7 @@ impl ExtensionsPage {
event: &editor::EditorEvent,
cx: &mut ViewContext<Self>,
) {
- if let editor::EditorEvent::Edited = event {
+ if let editor::EditorEvent::Edited { .. } = event {
self.query_contains_error = false;
self.fetch_extensions_debounced(cx);
}
@@ -193,7 +193,7 @@ impl FeedbackModal {
});
cx.subscribe(&feedback_editor, |this, editor, event: &EditorEvent, cx| {
- if *event == EditorEvent::Edited {
+ if matches!(event, EditorEvent::Edited { .. }) {
this.character_count = editor
.read(cx)
.buffer()
@@ -42,17 +42,19 @@ enum GoToLineRowHighlights {}
impl GoToLine {
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
let handle = cx.view().downgrade();
- editor.register_action(move |_: &Toggle, cx| {
- let Some(editor) = handle.upgrade() else {
- return;
- };
- let Some(workspace) = editor.read(cx).workspace() else {
- return;
- };
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
+ editor
+ .register_action(move |_: &Toggle, cx| {
+ let Some(editor) = handle.upgrade() else {
+ return;
+ };
+ let Some(workspace) = editor.read(cx).workspace() else {
+ return;
+ };
+ workspace.update(cx, |workspace, cx| {
+ workspace.toggle_modal(cx, move |cx| GoToLine::new(editor, cx));
+ })
})
- });
+ .detach();
}
pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
@@ -154,6 +154,14 @@ pub struct Subscription {
}
impl Subscription {
+ /// Creates a new subscription with a callback that gets invoked when
+ /// this subscription is dropped.
+ pub fn new(unsubscribe: impl 'static + FnOnce()) -> Self {
+ Self {
+ unsubscribe: Some(Box::new(unsubscribe)),
+ }
+ }
+
/// Detaches the subscription from this handle. The callback will
/// continue to be invoked until the views or models it has been
/// subscribed to are dropped
@@ -294,7 +294,7 @@ impl MarkdownPreviewView {
let subscription = cx.subscribe(&editor, |this, editor, event: &EditorEvent, cx| {
match event {
- EditorEvent::Edited => {
+ EditorEvent::Edited { .. } => {
this.parse_markdown_from_active_editor(true, cx);
}
EditorEvent::SelectionsChanged { .. } => {
@@ -789,6 +789,68 @@ impl MultiBuffer {
}
}
+ pub fn edited_ranges_for_transaction<D>(
+ &self,
+ transaction_id: TransactionId,
+ cx: &AppContext,
+ ) -> Vec<Range<D>>
+ where
+ D: TextDimension + Ord + Sub<D, Output = D>,
+ {
+ if let Some(buffer) = self.as_singleton() {
+ return buffer
+ .read(cx)
+ .edited_ranges_for_transaction_id(transaction_id)
+ .collect::<Vec<_>>();
+ }
+
+ let Some(transaction) = self.history.transaction(transaction_id) else {
+ return Vec::new();
+ };
+
+ let mut ranges = Vec::new();
+ let snapshot = self.read(cx);
+ let buffers = self.buffers.borrow();
+ let mut cursor = snapshot.excerpts.cursor::<ExcerptSummary>();
+
+ for (buffer_id, buffer_transaction) in &transaction.buffer_transactions {
+ let Some(buffer_state) = buffers.get(&buffer_id) else {
+ continue;
+ };
+
+ let buffer = buffer_state.buffer.read(cx);
+ for range in buffer.edited_ranges_for_transaction_id::<D>(*buffer_transaction) {
+ for excerpt_id in &buffer_state.excerpts {
+ cursor.seek(excerpt_id, Bias::Left, &());
+ if let Some(excerpt) = cursor.item() {
+ if excerpt.locator == *excerpt_id {
+ let excerpt_buffer_start =
+ excerpt.range.context.start.summary::<D>(buffer);
+ let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(buffer);
+ let excerpt_range = excerpt_buffer_start.clone()..excerpt_buffer_end;
+ if excerpt_range.contains(&range.start)
+ && excerpt_range.contains(&range.end)
+ {
+ let excerpt_start = D::from_text_summary(&cursor.start().text);
+
+ let mut start = excerpt_start.clone();
+ start.add_assign(&(range.start - excerpt_buffer_start.clone()));
+ let mut end = excerpt_start;
+ end.add_assign(&(range.end - excerpt_buffer_start));
+
+ ranges.push(start..end);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ranges.sort_by_key(|range| range.start.clone());
+ ranges
+ }
+
pub fn merge_transactions(
&mut self,
transaction: TransactionId,
@@ -3968,6 +4030,17 @@ impl History {
}
}
+ fn transaction(&self, transaction_id: TransactionId) -> Option<&Transaction> {
+ self.undo_stack
+ .iter()
+ .find(|transaction| transaction.id == transaction_id)
+ .or_else(|| {
+ self.redo_stack
+ .iter()
+ .find(|transaction| transaction.id == transaction_id)
+ })
+ }
+
fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
self.undo_stack
.iter_mut()
@@ -6060,6 +6133,15 @@ mod tests {
multibuffer.end_transaction_at(now, cx);
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
+ // Verify edited ranges for transaction 1
+ assert_eq!(
+ multibuffer.edited_ranges_for_transaction(transaction_1, cx),
+ &[
+ Point::new(0, 0)..Point::new(0, 2),
+ Point::new(1, 0)..Point::new(1, 2)
+ ]
+ );
+
// Edit buffer 1 through the multibuffer
now += 2 * group_interval;
multibuffer.start_transaction_at(now, cx);
@@ -68,11 +68,13 @@ impl OutlineView {
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
if editor.mode() == EditorMode::Full {
let handle = cx.view().downgrade();
- editor.register_action(move |action, cx| {
- if let Some(editor) = handle.upgrade() {
- toggle(editor, action, cx);
- }
- });
+ editor
+ .register_action(move |action, cx| {
+ if let Some(editor) = handle.upgrade() {
+ toggle(editor, action, cx);
+ }
+ })
+ .detach();
}
}
@@ -811,7 +811,7 @@ impl BufferSearchBar {
match event {
editor::EditorEvent::Focused => self.query_editor_focused = true,
editor::EditorEvent::Blurred => self.query_editor_focused = false,
- editor::EditorEvent::Edited => {
+ editor::EditorEvent::Edited { .. } => {
self.clear_matches(cx);
let search = self.update_matches(cx);
@@ -356,6 +356,19 @@ impl History {
}
}
+ fn transaction(&self, transaction_id: TransactionId) -> Option<&Transaction> {
+ let entry = self
+ .undo_stack
+ .iter()
+ .rfind(|entry| entry.transaction.id == transaction_id)
+ .or_else(|| {
+ self.redo_stack
+ .iter()
+ .rfind(|entry| entry.transaction.id == transaction_id)
+ })?;
+ Some(&entry.transaction)
+ }
+
fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
let entry = self
.undo_stack
@@ -1389,6 +1402,19 @@ impl Buffer {
self.history.finalize_last_transaction();
}
+ pub fn edited_ranges_for_transaction_id<D>(
+ &self,
+ transaction_id: TransactionId,
+ ) -> impl '_ + Iterator<Item = Range<D>>
+ where
+ D: TextDimension,
+ {
+ self.history
+ .transaction(transaction_id)
+ .into_iter()
+ .flat_map(|transaction| self.edited_ranges_for_transaction(transaction))
+ }
+
pub fn edited_ranges_for_transaction<'a, D>(
&'a self,
transaction: &'a Transaction,
@@ -271,7 +271,9 @@ impl Vim {
EditorEvent::TransactionUndone { transaction_id } => Vim::update(cx, |vim, cx| {
vim.transaction_undone(transaction_id, cx);
}),
- EditorEvent::Edited => Vim::update(cx, |vim, cx| vim.transaction_ended(editor, cx)),
+ EditorEvent::Edited { .. } => {
+ Vim::update(cx, |vim, cx| vim.transaction_ended(editor, cx))
+ }
_ => {}
}));
@@ -74,23 +74,30 @@ fn register_backward_compatible_actions(editor: &mut Editor, cx: &mut ViewContex
editor.show_inline_completion(&Default::default(), cx);
},
))
+ .detach();
+ editor
.register_action(cx.listener(
|editor, _: &copilot::NextSuggestion, cx: &mut ViewContext<Editor>| {
editor.next_inline_completion(&Default::default(), cx);
},
))
+ .detach();
+ editor
.register_action(cx.listener(
|editor, _: &copilot::PreviousSuggestion, cx: &mut ViewContext<Editor>| {
editor.previous_inline_completion(&Default::default(), cx);
},
))
+ .detach();
+ editor
.register_action(cx.listener(
|editor,
_: &editor::actions::AcceptPartialCopilotSuggestion,
cx: &mut ViewContext<Editor>| {
editor.accept_partial_inline_completion(&Default::default(), cx);
},
- ));
+ ))
+ .detach();
}
fn assign_inline_completion_provider(