mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-16 17:07:14 +03:00
Refine inline transformation UX (#12939)
https://github.com/zed-industries/zed/assets/482957/1790e32e-1f59-4831-8a4c-722cf441e7e9 Release Notes: - N/A --------- Co-authored-by: Richard <richard@zed.dev> Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
9e3c5f3e12
commit
e1f4dfc068
@ -79,7 +79,6 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||||
})
|
})
|
||||||
.register_action(AssistantPanel::inline_assist)
|
.register_action(AssistantPanel::inline_assist)
|
||||||
.register_action(AssistantPanel::cancel_last_inline_assist)
|
|
||||||
.register_action(ContextEditor::quote_selection);
|
.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>> {
|
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||||
let workspace = self.workspace.upgrade()?;
|
let workspace = self.workspace.upgrade()?;
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ use editor::{
|
|||||||
};
|
};
|
||||||
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyWindowHandle, AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight,
|
AppContext, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, Global,
|
||||||
Global, HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
|
HighlightStyle, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View,
|
||||||
ViewContext, WeakView, WhiteSpace, WindowContext,
|
ViewContext, WeakView, WhiteSpace, WindowContext,
|
||||||
};
|
};
|
||||||
use language::{Buffer, Point, TransactionId};
|
use language::{Buffer, Point, TransactionId};
|
||||||
@ -34,6 +34,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, Tooltip};
|
use ui::{prelude::*, Tooltip};
|
||||||
|
use util::RangeExt;
|
||||||
use workspace::{notifications::NotificationId, Toast, Workspace};
|
use workspace::{notifications::NotificationId, Toast, Workspace};
|
||||||
|
|
||||||
pub fn init(telemetry: Arc<Telemetry>, cx: &mut AppContext) {
|
pub fn init(telemetry: Arc<Telemetry>, cx: &mut AppContext) {
|
||||||
@ -45,16 +46,11 @@ const PROMPT_HISTORY_MAX_LEN: usize = 20;
|
|||||||
pub struct InlineAssistant {
|
pub struct InlineAssistant {
|
||||||
next_assist_id: InlineAssistId,
|
next_assist_id: InlineAssistId,
|
||||||
pending_assists: HashMap<InlineAssistId, PendingInlineAssist>,
|
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>,
|
prompt_history: VecDeque<String>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EditorPendingAssists {
|
|
||||||
window: AnyWindowHandle,
|
|
||||||
assist_ids: Vec<InlineAssistId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Global for InlineAssistant {}
|
impl Global for InlineAssistant {}
|
||||||
|
|
||||||
impl 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| {
|
let codegen = cx.new_model(|cx| {
|
||||||
Codegen::new(
|
Codegen::new(
|
||||||
editor.read(cx).buffer().clone(),
|
editor.read(cx).buffer().clone(),
|
||||||
@ -116,7 +112,7 @@ impl InlineAssistant {
|
|||||||
let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
|
let gutter_dimensions = Arc::new(Mutex::new(GutterDimensions::default()));
|
||||||
let prompt_editor = cx.new_view(|cx| {
|
let prompt_editor = cx.new_view(|cx| {
|
||||||
InlineAssistEditor::new(
|
InlineAssistEditor::new(
|
||||||
inline_assist_id,
|
assist_id,
|
||||||
gutter_dimensions.clone(),
|
gutter_dimensions.clone(),
|
||||||
self.prompt_history.clone(),
|
self.prompt_history.clone(),
|
||||||
codegen.clone(),
|
codegen.clone(),
|
||||||
@ -164,7 +160,7 @@ impl InlineAssistant {
|
|||||||
});
|
});
|
||||||
|
|
||||||
self.pending_assists.insert(
|
self.pending_assists.insert(
|
||||||
inline_assist_id,
|
assist_id,
|
||||||
PendingInlineAssist {
|
PendingInlineAssist {
|
||||||
include_context,
|
include_context,
|
||||||
editor: editor.downgrade(),
|
editor: editor.downgrade(),
|
||||||
@ -179,24 +175,35 @@ impl InlineAssistant {
|
|||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
cx.subscribe(&prompt_editor, |inline_assist_editor, event, cx| {
|
cx.subscribe(&prompt_editor, |inline_assist_editor, event, cx| {
|
||||||
InlineAssistant::update_global(cx, |this, 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, {
|
editor.update(cx, |editor, _cx| {
|
||||||
let inline_assist_editor = prompt_editor.downgrade();
|
editor.register_action(
|
||||||
move |editor, event, cx| {
|
move |_: &editor::actions::Newline, cx: &mut WindowContext| {
|
||||||
if let Some(inline_assist_editor) = inline_assist_editor.upgrade() {
|
InlineAssistant::update_global(cx, |this, cx| {
|
||||||
if let EditorEvent::SelectionsChanged { local } = event {
|
this.handle_editor_action(assist_id, false, cx)
|
||||||
if *local
|
})
|
||||||
&& inline_assist_editor
|
},
|
||||||
.focus_handle(cx)
|
)
|
||||||
.contains_focused(cx)
|
}),
|
||||||
{
|
editor.update(cx, |editor, _cx| {
|
||||||
cx.focus_view(&editor);
|
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, {
|
cx.observe(&codegen, {
|
||||||
let editor = editor.downgrade();
|
let editor = editor.downgrade();
|
||||||
@ -204,19 +211,17 @@ impl InlineAssistant {
|
|||||||
if let Some(editor) = editor.upgrade() {
|
if let Some(editor) = editor.upgrade() {
|
||||||
InlineAssistant::update_global(cx, |this, cx| {
|
InlineAssistant::update_global(cx, |this, cx| {
|
||||||
this.update_editor_highlights(&editor, 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| {
|
cx.subscribe(&codegen, move |codegen, event, cx| {
|
||||||
InlineAssistant::update_global(cx, |this, cx| match event {
|
InlineAssistant::update_global(cx, |this, cx| match event {
|
||||||
CodegenEvent::Undone => {
|
CodegenEvent::Undone => this.finish_inline_assist(assist_id, false, cx),
|
||||||
this.finish_inline_assist(inline_assist_id, false, cx)
|
|
||||||
}
|
|
||||||
CodegenEvent::Finished => {
|
CodegenEvent::Finished => {
|
||||||
let pending_assist = if let Some(pending_assist) =
|
let pending_assist = if let Some(pending_assist) =
|
||||||
this.pending_assists.get(&inline_assist_id)
|
this.pending_assists.get(&assist_id)
|
||||||
{
|
{
|
||||||
pending_assist
|
pending_assist
|
||||||
} else {
|
} else {
|
||||||
@ -238,7 +243,7 @@ impl InlineAssistant {
|
|||||||
let id = NotificationId::identified::<
|
let id = NotificationId::identified::<
|
||||||
InlineAssistantError,
|
InlineAssistantError,
|
||||||
>(
|
>(
|
||||||
inline_assist_id.0
|
assist_id.0
|
||||||
);
|
);
|
||||||
|
|
||||||
workspace.show_toast(Toast::new(id, error), cx);
|
workspace.show_toast(Toast::new(id, error), cx);
|
||||||
@ -248,7 +253,7 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pending_assist.editor_decorations.is_none() {
|
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
|
self.pending_assist_ids_by_editor
|
||||||
.entry(editor.downgrade())
|
.entry(editor.downgrade())
|
||||||
.or_insert_with(|| EditorPendingAssists {
|
.or_default()
|
||||||
window: cx.window_handle(),
|
.push(assist_id);
|
||||||
assist_ids: Vec::new(),
|
|
||||||
})
|
|
||||||
.assist_ids
|
|
||||||
.push(inline_assist_id);
|
|
||||||
self.update_editor_highlights(editor, cx);
|
self.update_editor_highlights(editor, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_inline_assistant_event(
|
fn handle_inline_assistant_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
inline_assist_editor: View<InlineAssistEditor>,
|
inline_assist_editor: View<InlineAssistEditor>,
|
||||||
event: &InlineAssistEditorEvent,
|
event: &InlineAssistEditorEvent,
|
||||||
@ -289,7 +290,7 @@ impl InlineAssistant {
|
|||||||
self.finish_inline_assist(assist_id, true, cx);
|
self.finish_inline_assist(assist_id, true, cx);
|
||||||
}
|
}
|
||||||
InlineAssistEditorEvent::Dismissed => {
|
InlineAssistEditorEvent::Dismissed => {
|
||||||
self.hide_inline_assist_decorations(assist_id, cx);
|
self.dismiss_inline_assist(assist_id, cx);
|
||||||
}
|
}
|
||||||
InlineAssistEditorEvent::Resized { height_in_lines } => {
|
InlineAssistEditorEvent::Resized { height_in_lines } => {
|
||||||
self.resize_inline_assist(assist_id, *height_in_lines, cx);
|
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 {
|
fn handle_editor_action(
|
||||||
for (editor, pending_assists) in &self.pending_assist_ids_by_editor {
|
&mut self,
|
||||||
if pending_assists.window == cx.window_handle() {
|
assist_id: InlineAssistId,
|
||||||
if let Some(editor) = editor.upgrade() {
|
undo: bool,
|
||||||
if editor.read(cx).is_focused(cx) {
|
cx: &mut WindowContext,
|
||||||
if let Some(assist_id) = pending_assists.assist_ids.last().copied() {
|
) {
|
||||||
self.finish_inline_assist(assist_id, true, cx);
|
let Some(assist) = self.pending_assists.get(&assist_id) else {
|
||||||
return true;
|
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(
|
fn finish_inline_assist(
|
||||||
@ -319,15 +387,15 @@ impl InlineAssistant {
|
|||||||
undo: bool,
|
undo: bool,
|
||||||
cx: &mut WindowContext,
|
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 Some(pending_assist) = self.pending_assists.remove(&assist_id) {
|
||||||
if let hash_map::Entry::Occupied(mut entry) = self
|
if let hash_map::Entry::Occupied(mut entry) = self
|
||||||
.pending_assist_ids_by_editor
|
.pending_assist_ids_by_editor
|
||||||
.entry(pending_assist.editor.clone())
|
.entry(pending_assist.editor.clone())
|
||||||
{
|
{
|
||||||
entry.get_mut().assist_ids.retain(|id| *id != assist_id);
|
entry.get_mut().retain(|id| *id != assist_id);
|
||||||
if entry.get().assist_ids.is_empty() {
|
if entry.get().is_empty() {
|
||||||
entry.remove();
|
entry.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -344,11 +412,7 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_inline_assist_decorations(
|
fn dismiss_inline_assist(&mut self, assist_id: InlineAssistId, cx: &mut WindowContext) -> bool {
|
||||||
&mut self,
|
|
||||||
assist_id: InlineAssistId,
|
|
||||||
cx: &mut WindowContext,
|
|
||||||
) -> bool {
|
|
||||||
let Some(pending_assist) = self.pending_assists.get_mut(&assist_id) else {
|
let Some(pending_assist) = self.pending_assists.get_mut(&assist_id) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -558,16 +622,14 @@ impl InlineAssistant {
|
|||||||
let mut gutter_transformed_ranges = Vec::new();
|
let mut gutter_transformed_ranges = Vec::new();
|
||||||
let mut foreground_ranges = Vec::new();
|
let mut foreground_ranges = Vec::new();
|
||||||
let mut inserted_row_ranges = Vec::new();
|
let mut inserted_row_ranges = Vec::new();
|
||||||
let empty_inline_assist_ids = Vec::new();
|
let empty_assist_ids = Vec::new();
|
||||||
let inline_assist_ids = self
|
let assist_ids = self
|
||||||
.pending_assist_ids_by_editor
|
.pending_assist_ids_by_editor
|
||||||
.get(&editor.downgrade())
|
.get(&editor.downgrade())
|
||||||
.map_or(&empty_inline_assist_ids, |pending_assists| {
|
.unwrap_or(&empty_assist_ids);
|
||||||
&pending_assists.assist_ids
|
|
||||||
});
|
|
||||||
|
|
||||||
for inline_assist_id in inline_assist_ids {
|
for assist_id in assist_ids {
|
||||||
if let Some(pending_assist) = self.pending_assists.get(inline_assist_id) {
|
if let Some(pending_assist) = self.pending_assists.get(assist_id) {
|
||||||
let codegen = pending_assist.codegen.read(cx);
|
let codegen = pending_assist.codegen.read(cx);
|
||||||
foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
|
foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
|
||||||
|
|
||||||
@ -1025,7 +1087,7 @@ impl InlineAssistEditor {
|
|||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited => {
|
EditorEvent::Edited { .. } => {
|
||||||
let prompt = self.prompt_editor.read(cx).text(cx);
|
let prompt = self.prompt_editor.read(cx).text(cx);
|
||||||
if self
|
if self
|
||||||
.prompt_history_ix
|
.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(
|
fn handle_prompt_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
prompt_id: PromptId,
|
prompt_id: PromptId,
|
||||||
@ -743,7 +730,6 @@ impl PromptLibrary {
|
|||||||
div()
|
div()
|
||||||
.on_action(cx.listener(Self::focus_picker))
|
.on_action(cx.listener(Self::focus_picker))
|
||||||
.on_action(cx.listener(Self::inline_assist))
|
.on_action(cx.listener(Self::inline_assist))
|
||||||
.on_action(cx.listener(Self::cancel_last_inline_assist))
|
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.h_full()
|
.h_full()
|
||||||
.pt(Spacing::XXLarge.rems(cx))
|
.pt(Spacing::XXLarge.rems(cx))
|
||||||
|
@ -232,7 +232,7 @@ impl ChannelView {
|
|||||||
this.focus_position_from_link(position.clone(), false, cx);
|
this.focus_position_from_link(position.clone(), false, cx);
|
||||||
this._reparse_subscription.take();
|
this._reparse_subscription.take();
|
||||||
}
|
}
|
||||||
EditorEvent::Edited | EditorEvent::SelectionsChanged { local: true } => {
|
EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {
|
||||||
this._reparse_subscription.take();
|
this._reparse_subscription.take();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -116,15 +116,16 @@ use serde::{Deserialize, Serialize};
|
|||||||
use settings::{update_settings_file, Settings, SettingsStore};
|
use settings::{update_settings_file, Settings, SettingsStore};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use snippet::Snippet;
|
use snippet::Snippet;
|
||||||
use std::ops::Not as _;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
cell::RefCell,
|
||||||
cmp::{self, Ordering, Reverse},
|
cmp::{self, Ordering, Reverse},
|
||||||
mem,
|
mem,
|
||||||
num::NonZeroU32,
|
num::NonZeroU32,
|
||||||
ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
|
ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@ -377,6 +378,19 @@ impl Default for EditorStyle {
|
|||||||
|
|
||||||
type CompletionId = usize;
|
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 GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
|
||||||
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
||||||
|
|
||||||
@ -512,7 +526,8 @@ pub struct Editor {
|
|||||||
gutter_dimensions: GutterDimensions,
|
gutter_dimensions: GutterDimensions,
|
||||||
pub vim_replace_map: HashMap<Range<usize>, String>,
|
pub vim_replace_map: HashMap<Range<usize>, String>,
|
||||||
style: Option<EditorStyle>,
|
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,
|
use_autoclose: bool,
|
||||||
auto_replace_emoji_shortcode: bool,
|
auto_replace_emoji_shortcode: bool,
|
||||||
show_git_blame_gutter: bool,
|
show_git_blame_gutter: bool,
|
||||||
@ -1805,7 +1820,8 @@ impl Editor {
|
|||||||
style: None,
|
style: None,
|
||||||
show_cursor_names: false,
|
show_cursor_names: false,
|
||||||
hovered_cursors: Default::default(),
|
hovered_cursors: Default::default(),
|
||||||
editor_actions: Default::default(),
|
next_editor_action_id: EditorActionId::default(),
|
||||||
|
editor_actions: Rc::default(),
|
||||||
vim_replace_map: Default::default(),
|
vim_replace_map: Default::default(),
|
||||||
show_inline_completions: mode == EditorMode::Full,
|
show_inline_completions: mode == EditorMode::Full,
|
||||||
custom_context_menu: None,
|
custom_context_menu: None,
|
||||||
@ -6448,29 +6464,9 @@ impl Editor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tx_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
|
if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
|
||||||
if let Some((selections, _)) = self.selection_history.transaction(tx_id).cloned() {
|
if let Some((selections, _)) =
|
||||||
self.change_selections(None, cx, |s| {
|
self.selection_history.transaction(transaction_id).cloned()
|
||||||
s.select_anchors(selections.to_vec());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
|
|
||||||
if self.read_only(cx) {
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
self.change_selections(None, cx, |s| {
|
self.change_selections(None, cx, |s| {
|
||||||
s.select_anchors(selections.to_vec());
|
s.select_anchors(selections.to_vec());
|
||||||
@ -6479,7 +6475,28 @@ impl Editor {
|
|||||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||||
self.unmark_text(cx);
|
self.unmark_text(cx);
|
||||||
self.refresh_inline_completion(true, cx);
|
self.refresh_inline_completion(true, cx);
|
||||||
cx.emit(EditorEvent::Edited);
|
cx.emit(EditorEvent::Edited { transaction_id });
|
||||||
|
cx.emit(EditorEvent::TransactionUndone { transaction_id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
|
||||||
|
if self.read_only(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||||
|
self.unmark_text(cx);
|
||||||
|
self.refresh_inline_completion(true, cx);
|
||||||
|
cx.emit(EditorEvent::Edited { transaction_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9590,18 +9607,20 @@ impl Editor {
|
|||||||
now: Instant,
|
now: Instant,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<TransactionId> {
|
) -> Option<TransactionId> {
|
||||||
if let Some(tx_id) = self
|
if let Some(transaction_id) = self
|
||||||
.buffer
|
.buffer
|
||||||
.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
|
.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());
|
*end_selections = Some(self.selections.disjoint_anchors());
|
||||||
} else {
|
} else {
|
||||||
log::error!("unexpectedly ended a transaction that wasn't started by this editor");
|
log::error!("unexpectedly ended a transaction that wasn't started by this editor");
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.emit(EditorEvent::Edited);
|
cx.emit(EditorEvent::Edited { transaction_id });
|
||||||
Some(tx_id)
|
Some(transaction_id)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -11293,21 +11312,28 @@ impl Editor {
|
|||||||
pub fn register_action<A: Action>(
|
pub fn register_action<A: Action>(
|
||||||
&mut self,
|
&mut self,
|
||||||
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
listener: impl Fn(&A, &mut WindowContext) + 'static,
|
||||||
) -> &mut Self {
|
) -> Subscription {
|
||||||
|
let id = self.next_editor_action_id.post_inc();
|
||||||
let listener = Arc::new(listener);
|
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 editor_actions = self.editor_actions.clone();
|
||||||
let _view = cx.view().clone();
|
Subscription::new(move || {
|
||||||
let cx = cx.window_context();
|
editor_actions.borrow_mut().remove(&id);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file_header_size(&self) -> u8 {
|
pub fn file_header_size(&self) -> u8 {
|
||||||
@ -11764,7 +11790,9 @@ pub enum EditorEvent {
|
|||||||
ids: Vec<ExcerptId>,
|
ids: Vec<ExcerptId>,
|
||||||
},
|
},
|
||||||
BufferEdited,
|
BufferEdited,
|
||||||
Edited,
|
Edited {
|
||||||
|
transaction_id: clock::Lamport,
|
||||||
|
},
|
||||||
Reparsed,
|
Reparsed,
|
||||||
Focused,
|
Focused,
|
||||||
Blurred,
|
Blurred,
|
||||||
|
@ -57,10 +57,10 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
let events = events.clone();
|
let events = events.clone();
|
||||||
|cx| {
|
|cx| {
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
cx.subscribe(&view, move |_, _, event: &EditorEvent, _| {
|
cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
|
||||||
if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
|
EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
|
||||||
events.borrow_mut().push(("editor1", event.clone()));
|
EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
|
||||||
}
|
_ => {}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
Editor::for_buffer(buffer.clone(), None, cx)
|
Editor::for_buffer(buffer.clone(), None, cx)
|
||||||
@ -70,11 +70,16 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
let editor2 = cx.add_window({
|
let editor2 = cx.add_window({
|
||||||
let events = events.clone();
|
let events = events.clone();
|
||||||
|cx| {
|
|cx| {
|
||||||
cx.subscribe(&cx.view().clone(), move |_, _, event: &EditorEvent, _| {
|
cx.subscribe(
|
||||||
if matches!(event, EditorEvent::Edited | EditorEvent::BufferEdited) {
|
&cx.view().clone(),
|
||||||
events.borrow_mut().push(("editor2", event.clone()));
|
move |_, _, event: &EditorEvent, _| match event {
|
||||||
}
|
EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
|
||||||
})
|
EditorEvent::BufferEdited => {
|
||||||
|
events.borrow_mut().push(("editor2", "buffer edited"))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
)
|
||||||
.detach();
|
.detach();
|
||||||
Editor::for_buffer(buffer.clone(), None, cx)
|
Editor::for_buffer(buffer.clone(), None, cx)
|
||||||
}
|
}
|
||||||
@ -87,9 +92,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem::take(&mut *events.borrow_mut()),
|
mem::take(&mut *events.borrow_mut()),
|
||||||
[
|
[
|
||||||
("editor1", EditorEvent::Edited),
|
("editor1", "edited"),
|
||||||
("editor1", EditorEvent::BufferEdited),
|
("editor1", "buffer edited"),
|
||||||
("editor2", EditorEvent::BufferEdited),
|
("editor2", "buffer edited"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -98,9 +103,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem::take(&mut *events.borrow_mut()),
|
mem::take(&mut *events.borrow_mut()),
|
||||||
[
|
[
|
||||||
("editor2", EditorEvent::Edited),
|
("editor2", "edited"),
|
||||||
("editor1", EditorEvent::BufferEdited),
|
("editor1", "buffer edited"),
|
||||||
("editor2", EditorEvent::BufferEdited),
|
("editor2", "buffer edited"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -109,9 +114,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem::take(&mut *events.borrow_mut()),
|
mem::take(&mut *events.borrow_mut()),
|
||||||
[
|
[
|
||||||
("editor1", EditorEvent::Edited),
|
("editor1", "edited"),
|
||||||
("editor1", EditorEvent::BufferEdited),
|
("editor1", "buffer edited"),
|
||||||
("editor2", EditorEvent::BufferEdited),
|
("editor2", "buffer edited"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -120,9 +125,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem::take(&mut *events.borrow_mut()),
|
mem::take(&mut *events.borrow_mut()),
|
||||||
[
|
[
|
||||||
("editor1", EditorEvent::Edited),
|
("editor1", "edited"),
|
||||||
("editor1", EditorEvent::BufferEdited),
|
("editor1", "buffer edited"),
|
||||||
("editor2", EditorEvent::BufferEdited),
|
("editor2", "buffer edited"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -131,9 +136,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem::take(&mut *events.borrow_mut()),
|
mem::take(&mut *events.borrow_mut()),
|
||||||
[
|
[
|
||||||
("editor2", EditorEvent::Edited),
|
("editor2", "edited"),
|
||||||
("editor1", EditorEvent::BufferEdited),
|
("editor1", "buffer edited"),
|
||||||
("editor2", EditorEvent::BufferEdited),
|
("editor2", "buffer edited"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -142,9 +147,9 @@ fn test_edit_events(cx: &mut TestAppContext) {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
mem::take(&mut *events.borrow_mut()),
|
mem::take(&mut *events.borrow_mut()),
|
||||||
[
|
[
|
||||||
("editor2", EditorEvent::Edited),
|
("editor2", "edited"),
|
||||||
("editor1", EditorEvent::BufferEdited),
|
("editor1", "buffer edited"),
|
||||||
("editor2", EditorEvent::BufferEdited),
|
("editor2", "buffer edited"),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ impl EditorElement {
|
|||||||
fn register_actions(&self, cx: &mut WindowContext) {
|
fn register_actions(&self, cx: &mut WindowContext) {
|
||||||
let view = &self.editor;
|
let view = &self.editor;
|
||||||
view.update(cx, |editor, cx| {
|
view.update(cx, |editor, cx| {
|
||||||
for action in editor.editor_actions.iter() {
|
for action in editor.editor_actions.borrow().values() {
|
||||||
(action)(cx)
|
(action)(cx)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -615,32 +615,36 @@ fn editor_with_deleted_text(
|
|||||||
]);
|
]);
|
||||||
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
|
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
|
||||||
let diff_base_range = hunk.diff_base_byte_range.clone();
|
let diff_base_range = hunk.diff_base_byte_range.clone();
|
||||||
editor.register_action::<RevertSelectedHunks>(move |_, cx| {
|
editor
|
||||||
parent_editor
|
.register_action::<RevertSelectedHunks>(move |_, cx| {
|
||||||
.update(cx, |editor, cx| {
|
parent_editor
|
||||||
let Some((buffer, original_text)) = editor.buffer().update(cx, |buffer, cx| {
|
.update(cx, |editor, cx| {
|
||||||
let (_, buffer, _) =
|
let Some((buffer, original_text)) =
|
||||||
buffer.excerpt_containing(original_multi_buffer_range.start, cx)?;
|
editor.buffer().update(cx, |buffer, cx| {
|
||||||
let original_text =
|
let (_, buffer, _) = buffer
|
||||||
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
|
.excerpt_containing(original_multi_buffer_range.start, cx)?;
|
||||||
Some((buffer, Arc::from(original_text.to_string())))
|
let original_text =
|
||||||
}) else {
|
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
|
||||||
return;
|
Some((buffer, Arc::from(original_text.to_string())))
|
||||||
};
|
})
|
||||||
buffer.update(cx, |buffer, cx| {
|
else {
|
||||||
buffer.edit(
|
return;
|
||||||
Some((
|
};
|
||||||
original_multi_buffer_range.start.text_anchor
|
buffer.update(cx, |buffer, cx| {
|
||||||
..original_multi_buffer_range.end.text_anchor,
|
buffer.edit(
|
||||||
original_text,
|
Some((
|
||||||
)),
|
original_multi_buffer_range.start.text_anchor
|
||||||
None,
|
..original_multi_buffer_range.end.text_anchor,
|
||||||
cx,
|
original_text,
|
||||||
)
|
)),
|
||||||
});
|
None,
|
||||||
})
|
cx,
|
||||||
.ok();
|
)
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ impl FollowableItem for Editor {
|
|||||||
|
|
||||||
fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
|
fn to_follow_event(event: &EditorEvent) -> Option<workspace::item::FollowEvent> {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited => Some(FollowEvent::Unfollow),
|
EditorEvent::Edited { .. } => Some(FollowEvent::Unfollow),
|
||||||
EditorEvent::SelectionsChanged { local }
|
EditorEvent::SelectionsChanged { local }
|
||||||
| EditorEvent::ScrollPositionChanged { local, .. } => {
|
| EditorEvent::ScrollPositionChanged { local, .. } => {
|
||||||
if *local {
|
if *local {
|
||||||
|
@ -773,7 +773,7 @@ impl ExtensionsPage {
|
|||||||
event: &editor::EditorEvent,
|
event: &editor::EditorEvent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let editor::EditorEvent::Edited = event {
|
if let editor::EditorEvent::Edited { .. } = event {
|
||||||
self.query_contains_error = false;
|
self.query_contains_error = false;
|
||||||
self.fetch_extensions_debounced(cx);
|
self.fetch_extensions_debounced(cx);
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ impl FeedbackModal {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cx.subscribe(&feedback_editor, |this, editor, event: &EditorEvent, cx| {
|
cx.subscribe(&feedback_editor, |this, editor, event: &EditorEvent, cx| {
|
||||||
if *event == EditorEvent::Edited {
|
if matches!(event, EditorEvent::Edited { .. }) {
|
||||||
this.character_count = editor
|
this.character_count = editor
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.buffer()
|
.buffer()
|
||||||
|
@ -42,17 +42,19 @@ enum GoToLineRowHighlights {}
|
|||||||
impl GoToLine {
|
impl GoToLine {
|
||||||
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||||
let handle = cx.view().downgrade();
|
let handle = cx.view().downgrade();
|
||||||
editor.register_action(move |_: &Toggle, cx| {
|
editor
|
||||||
let Some(editor) = handle.upgrade() else {
|
.register_action(move |_: &Toggle, cx| {
|
||||||
return;
|
let Some(editor) = handle.upgrade() else {
|
||||||
};
|
return;
|
||||||
let Some(workspace) = editor.read(cx).workspace() 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));
|
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 {
|
pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
@ -154,6 +154,14 @@ pub struct Subscription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// Detaches the subscription from this handle. The callback will
|
||||||
/// continue to be invoked until the views or models it has been
|
/// continue to be invoked until the views or models it has been
|
||||||
/// subscribed to are dropped
|
/// subscribed to are dropped
|
||||||
|
@ -294,7 +294,7 @@ impl MarkdownPreviewView {
|
|||||||
|
|
||||||
let subscription = cx.subscribe(&editor, |this, editor, event: &EditorEvent, cx| {
|
let subscription = cx.subscribe(&editor, |this, editor, event: &EditorEvent, cx| {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited => {
|
EditorEvent::Edited { .. } => {
|
||||||
this.parse_markdown_from_active_editor(true, cx);
|
this.parse_markdown_from_active_editor(true, cx);
|
||||||
}
|
}
|
||||||
EditorEvent::SelectionsChanged { .. } => {
|
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(
|
pub fn merge_transactions(
|
||||||
&mut self,
|
&mut self,
|
||||||
transaction: TransactionId,
|
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> {
|
fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
|
||||||
self.undo_stack
|
self.undo_stack
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@ -6060,6 +6133,15 @@ mod tests {
|
|||||||
multibuffer.end_transaction_at(now, cx);
|
multibuffer.end_transaction_at(now, cx);
|
||||||
assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678");
|
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
|
// Edit buffer 1 through the multibuffer
|
||||||
now += 2 * group_interval;
|
now += 2 * group_interval;
|
||||||
multibuffer.start_transaction_at(now, cx);
|
multibuffer.start_transaction_at(now, cx);
|
||||||
|
@ -68,11 +68,13 @@ impl OutlineView {
|
|||||||
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
fn register(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||||
if editor.mode() == EditorMode::Full {
|
if editor.mode() == EditorMode::Full {
|
||||||
let handle = cx.view().downgrade();
|
let handle = cx.view().downgrade();
|
||||||
editor.register_action(move |action, cx| {
|
editor
|
||||||
if let Some(editor) = handle.upgrade() {
|
.register_action(move |action, cx| {
|
||||||
toggle(editor, action, cx);
|
if let Some(editor) = handle.upgrade() {
|
||||||
}
|
toggle(editor, action, cx);
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,7 +811,7 @@ impl BufferSearchBar {
|
|||||||
match event {
|
match event {
|
||||||
editor::EditorEvent::Focused => self.query_editor_focused = true,
|
editor::EditorEvent::Focused => self.query_editor_focused = true,
|
||||||
editor::EditorEvent::Blurred => self.query_editor_focused = false,
|
editor::EditorEvent::Blurred => self.query_editor_focused = false,
|
||||||
editor::EditorEvent::Edited => {
|
editor::EditorEvent::Edited { .. } => {
|
||||||
self.clear_matches(cx);
|
self.clear_matches(cx);
|
||||||
let search = self.update_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> {
|
fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> {
|
||||||
let entry = self
|
let entry = self
|
||||||
.undo_stack
|
.undo_stack
|
||||||
@ -1389,6 +1402,19 @@ impl Buffer {
|
|||||||
self.history.finalize_last_transaction();
|
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>(
|
pub fn edited_ranges_for_transaction<'a, D>(
|
||||||
&'a self,
|
&'a self,
|
||||||
transaction: &'a Transaction,
|
transaction: &'a Transaction,
|
||||||
|
@ -271,7 +271,9 @@ impl Vim {
|
|||||||
EditorEvent::TransactionUndone { transaction_id } => Vim::update(cx, |vim, cx| {
|
EditorEvent::TransactionUndone { transaction_id } => Vim::update(cx, |vim, cx| {
|
||||||
vim.transaction_undone(transaction_id, 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);
|
editor.show_inline_completion(&Default::default(), cx);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
.detach();
|
||||||
|
editor
|
||||||
.register_action(cx.listener(
|
.register_action(cx.listener(
|
||||||
|editor, _: &copilot::NextSuggestion, cx: &mut ViewContext<Editor>| {
|
|editor, _: &copilot::NextSuggestion, cx: &mut ViewContext<Editor>| {
|
||||||
editor.next_inline_completion(&Default::default(), cx);
|
editor.next_inline_completion(&Default::default(), cx);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
.detach();
|
||||||
|
editor
|
||||||
.register_action(cx.listener(
|
.register_action(cx.listener(
|
||||||
|editor, _: &copilot::PreviousSuggestion, cx: &mut ViewContext<Editor>| {
|
|editor, _: &copilot::PreviousSuggestion, cx: &mut ViewContext<Editor>| {
|
||||||
editor.previous_inline_completion(&Default::default(), cx);
|
editor.previous_inline_completion(&Default::default(), cx);
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
.detach();
|
||||||
|
editor
|
||||||
.register_action(cx.listener(
|
.register_action(cx.listener(
|
||||||
|editor,
|
|editor,
|
||||||
_: &editor::actions::AcceptPartialCopilotSuggestion,
|
_: &editor::actions::AcceptPartialCopilotSuggestion,
|
||||||
cx: &mut ViewContext<Editor>| {
|
cx: &mut ViewContext<Editor>| {
|
||||||
editor.accept_partial_inline_completion(&Default::default(), cx);
|
editor.accept_partial_inline_completion(&Default::default(), cx);
|
||||||
},
|
},
|
||||||
));
|
))
|
||||||
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assign_inline_completion_provider(
|
fn assign_inline_completion_provider(
|
||||||
|
Loading…
Reference in New Issue
Block a user