Rework mouse handling of git hunks diff (#14727)
Closes https://github.com/zed-industries/zed/issues/12404   Video: https://github.com/user-attachments/assets/58e62527-da75-4017-a43e-a37803bd7b49 * now shows a context menu on left click instead of expanding the hunk diff * hunk diffs can be toggled with a single cmd-click still * adds a X mark into gutter for every hunk expanded * makes `editor::ToggleDiffHunk` to work inside the deleted hunk editors Additionally, changes the way editor context menus behave when the editor is scrolled — right click and diff hunks context menu now will stick to the place it was invoked at, instead of staying onscreen at the same pixel positions. Release Notes: - Improved the way git hunks diff can be toggled with mouse ([#12404](https://github.com/zed-industries/zed/issues/12404)) --------- Co-authored-by: Nate Butler <nate@zed.dev> Co-authored-by: Conrad Irwin <conrad@zed.dev>
This commit is contained in:
parent
bf4645b1fe
commit
18c2e8f6ca
6 changed files with 628 additions and 169 deletions
|
@ -319,7 +319,7 @@ impl GroupedDiagnosticsEditor {
|
||||||
|| server_to_update.map_or(false, |to_update| *server_id != to_update)
|
|| server_to_update.map_or(false, |to_update| *server_id != to_update)
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO kb change selections as in the old panel, to the next primary diagnostics
|
// TODO change selections as in the old panel, to the next primary diagnostics
|
||||||
// TODO make [shift-]f8 to work, jump to the next block group
|
// TODO make [shift-]f8 to work, jump to the next block group
|
||||||
let _was_empty = self.path_states.is_empty();
|
let _was_empty = self.path_states.is_empty();
|
||||||
let path_ix = match self.path_states.binary_search_by(|probe| {
|
let path_ix = match self.path_states.binary_search_by(|probe| {
|
||||||
|
|
|
@ -78,7 +78,7 @@ use gpui::{
|
||||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||||
use hover_popover::{hide_hover, HoverState};
|
use hover_popover::{hide_hover, HoverState};
|
||||||
use hunk_diff::ExpandedHunks;
|
use hunk_diff::ExpandedHunks;
|
||||||
pub(crate) use hunk_diff::HunkToExpand;
|
pub(crate) use hunk_diff::HoveredHunk;
|
||||||
use indent_guides::ActiveIndentGuidesState;
|
use indent_guides::ActiveIndentGuidesState;
|
||||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||||
pub use inline_completion_provider::*;
|
pub use inline_completion_provider::*;
|
||||||
|
@ -2873,7 +2873,10 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
|
||||||
self.clear_expanded_diff_hunks(cx);
|
if self.clear_clicked_diff_hunks(cx) {
|
||||||
|
cx.notify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if self.dismiss_menus_and_popups(true, cx) {
|
if self.dismiss_menus_and_popups(true, cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2908,6 +2911,10 @@ impl Editor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.mouse_context_menu.take().is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if self.discard_inline_completion(should_report_inline_completion_event, cx) {
|
if self.discard_inline_completion(should_report_inline_completion_event, cx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -5125,6 +5132,23 @@ impl Editor {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_close_hunk_diff_button(
|
||||||
|
&self,
|
||||||
|
hunk: HoveredHunk,
|
||||||
|
row: DisplayRow,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> IconButton {
|
||||||
|
IconButton::new(
|
||||||
|
("close_hunk_diff_indicator", row.0 as usize),
|
||||||
|
ui::IconName::Close,
|
||||||
|
)
|
||||||
|
.shape(ui::IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::XSmall)
|
||||||
|
.icon_color(Color::Muted)
|
||||||
|
.tooltip(|cx| Tooltip::for_action("Close hunk diff", &ToggleHunkDiff, cx))
|
||||||
|
.on_click(cx.listener(move |editor, _e, cx| editor.toggle_hovered_hunk(&hunk, cx)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn context_menu_visible(&self) -> bool {
|
pub fn context_menu_visible(&self) -> bool {
|
||||||
self.context_menu
|
self.context_menu
|
||||||
.read()
|
.read()
|
||||||
|
@ -5879,22 +5903,7 @@ impl Editor {
|
||||||
let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
|
let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
|
||||||
if !revert_changes.is_empty() {
|
if !revert_changes.is_empty() {
|
||||||
self.transact(cx, |editor, cx| {
|
self.transact(cx, |editor, cx| {
|
||||||
editor.buffer().update(cx, |multi_buffer, cx| {
|
editor.revert(revert_changes, cx);
|
||||||
for (buffer_id, changes) in revert_changes {
|
|
||||||
if let Some(buffer) = multi_buffer.buffer(buffer_id) {
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
|
||||||
buffer.edit(
|
|
||||||
changes.into_iter().map(|(range, text)| {
|
|
||||||
(range, text.to_string().map(Arc::<str>::from))
|
|
||||||
}),
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
editor.change_selections(None, cx, |selections| selections.refresh());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5924,22 +5933,20 @@ impl Editor {
|
||||||
cx: &mut ViewContext<'_, Editor>,
|
cx: &mut ViewContext<'_, Editor>,
|
||||||
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
|
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
|
||||||
let mut revert_changes = HashMap::default();
|
let mut revert_changes = HashMap::default();
|
||||||
self.buffer.update(cx, |multi_buffer, cx| {
|
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
|
|
||||||
for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
|
for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
|
||||||
Self::prepare_revert_change(&mut revert_changes, &multi_buffer, &hunk, cx);
|
Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
revert_changes
|
revert_changes
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_revert_change(
|
pub fn prepare_revert_change(
|
||||||
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
||||||
multi_buffer: &MultiBuffer,
|
multi_buffer: &Model<MultiBuffer>,
|
||||||
hunk: &DiffHunk<MultiBufferRow>,
|
hunk: &DiffHunk<MultiBufferRow>,
|
||||||
cx: &mut AppContext,
|
cx: &AppContext,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let buffer = multi_buffer.buffer(hunk.buffer_id)?;
|
let buffer = multi_buffer.read(cx).buffer(hunk.buffer_id)?;
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
|
let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
|
||||||
let buffer_snapshot = buffer.snapshot();
|
let buffer_snapshot = buffer.snapshot();
|
||||||
|
@ -11737,15 +11744,81 @@ impl Editor {
|
||||||
pub fn file_header_size(&self) -> u8 {
|
pub fn file_header_size(&self) -> u8 {
|
||||||
self.file_header_size
|
self.file_header_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn revert(
|
||||||
|
&mut self,
|
||||||
|
revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.buffer().update(cx, |multi_buffer, cx| {
|
||||||
|
for (buffer_id, changes) in revert_changes {
|
||||||
|
if let Some(buffer) = multi_buffer.buffer(buffer_id) {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.edit(
|
||||||
|
changes.into_iter().map(|(range, text)| {
|
||||||
|
(range, text.to_string().map(Arc::<str>::from))
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.change_selections(None, cx, |selections| selections.refresh());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_pixel_point(
|
||||||
|
&mut self,
|
||||||
|
source: multi_buffer::Anchor,
|
||||||
|
editor_snapshot: &EditorSnapshot,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<gpui::Point<Pixels>> {
|
||||||
|
let text_layout_details = self.text_layout_details(cx);
|
||||||
|
let line_height = text_layout_details
|
||||||
|
.editor_style
|
||||||
|
.text
|
||||||
|
.line_height_in_pixels(cx.rem_size());
|
||||||
|
let source_point = source.to_display_point(editor_snapshot);
|
||||||
|
let first_visible_line = text_layout_details
|
||||||
|
.scroll_anchor
|
||||||
|
.anchor
|
||||||
|
.to_display_point(editor_snapshot);
|
||||||
|
if first_visible_line > source_point {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let source_x = editor_snapshot.x_for_display_point(source_point, &text_layout_details);
|
||||||
|
let source_y = line_height
|
||||||
|
* ((source_point.row() - first_visible_line.row()).0 as f32
|
||||||
|
- text_layout_details.scroll_anchor.offset.y);
|
||||||
|
Some(gpui::Point::new(source_x, source_y))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_to_pixel_point(
|
||||||
|
&mut self,
|
||||||
|
source: DisplayPoint,
|
||||||
|
editor_snapshot: &EditorSnapshot,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<gpui::Point<Pixels>> {
|
||||||
|
let line_height = self.style()?.text.line_height_in_pixels(cx.rem_size());
|
||||||
|
let text_layout_details = self.text_layout_details(cx);
|
||||||
|
let first_visible_line = text_layout_details
|
||||||
|
.scroll_anchor
|
||||||
|
.anchor
|
||||||
|
.to_display_point(editor_snapshot);
|
||||||
|
if first_visible_line > source {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
|
||||||
|
let source_y = line_height * (source.row() - first_visible_line.row()).0 as f32;
|
||||||
|
Some(gpui::Point::new(source_x, source_y))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hunks_for_selections(
|
fn hunks_for_selections(
|
||||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||||
selections: &[Selection<Anchor>],
|
selections: &[Selection<Anchor>],
|
||||||
) -> Vec<DiffHunk<MultiBufferRow>> {
|
) -> Vec<DiffHunk<MultiBufferRow>> {
|
||||||
let mut hunks = Vec::with_capacity(selections.len());
|
|
||||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
|
||||||
HashMap::default();
|
|
||||||
let buffer_rows_for_selections = selections.iter().map(|selection| {
|
let buffer_rows_for_selections = selections.iter().map(|selection| {
|
||||||
let head = selection.head();
|
let head = selection.head();
|
||||||
let tail = selection.tail();
|
let tail = selection.tail();
|
||||||
|
@ -11758,7 +11831,17 @@ fn hunks_for_selections(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for selected_multi_buffer_rows in buffer_rows_for_selections {
|
hunks_for_rows(buffer_rows_for_selections, multi_buffer_snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hunks_for_rows(
|
||||||
|
rows: impl Iterator<Item = Range<MultiBufferRow>>,
|
||||||
|
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Vec<DiffHunk<MultiBufferRow>> {
|
||||||
|
let mut hunks = Vec::new();
|
||||||
|
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||||
|
HashMap::default();
|
||||||
|
for selected_multi_buffer_rows in rows {
|
||||||
let query_rows =
|
let query_rows =
|
||||||
selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
|
selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
|
||||||
for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
|
for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
|
||||||
|
@ -12968,8 +13051,13 @@ pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> +
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait RangeToAnchorExt {
|
pub trait RangeToAnchorExt: Sized {
|
||||||
fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
|
fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
|
||||||
|
|
||||||
|
fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
|
||||||
|
let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
|
||||||
|
anchor_range.start.to_display_point(&snapshot)..anchor_range.end.to_display_point(&snapshot)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ToOffset> RangeToAnchorExt for Range<T> {
|
impl<T: ToOffset> RangeToAnchorExt for Range<T> {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::editor_settings::ScrollBeyondLastLine;
|
use crate::editor_settings::ScrollBeyondLastLine;
|
||||||
|
use crate::hunk_diff::ExpandedHunk;
|
||||||
|
use crate::mouse_context_menu::MenuPosition;
|
||||||
|
use crate::RangeToAnchorExt;
|
||||||
use crate::TransformBlockId;
|
use crate::TransformBlockId;
|
||||||
use crate::{
|
use crate::{
|
||||||
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip},
|
||||||
|
@ -21,13 +24,14 @@ use crate::{
|
||||||
scroll::scroll_amount::ScrollAmount,
|
scroll::scroll_amount::ScrollAmount,
|
||||||
CodeActionsMenu, CursorShape, DisplayPoint, DisplayRow, DocumentHighlightRead,
|
CodeActionsMenu, CursorShape, DisplayPoint, DisplayRow, DocumentHighlightRead,
|
||||||
DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
|
DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
|
||||||
ExpandExcerpts, GutterDimensions, HalfPageDown, HalfPageUp, HoveredCursor, HunkToExpand,
|
ExpandExcerpts, GutterDimensions, HalfPageDown, HalfPageUp, HoveredCursor, HoveredHunk,
|
||||||
LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
|
LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
|
||||||
Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN,
|
||||||
};
|
};
|
||||||
use client::ParticipantIndex;
|
use client::ParticipantIndex;
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid};
|
||||||
|
use gpui::Subscription;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
anchored, deferred, div, fill, outline, point, px, quad, relative, size, svg,
|
||||||
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
transparent_black, Action, AnchorCorner, AnyElement, AvailableSpace, Bounds, ClipboardItem,
|
||||||
|
@ -63,6 +67,7 @@ use sum_tree::Bias;
|
||||||
use theme::{ActiveTheme, PlayerColor};
|
use theme::{ActiveTheme, PlayerColor};
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
|
use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
|
||||||
|
use util::RangeExt;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{item::Item, Workspace};
|
use workspace::{item::Item, Workspace};
|
||||||
|
|
||||||
|
@ -442,7 +447,7 @@ impl EditorElement {
|
||||||
fn mouse_left_down(
|
fn mouse_left_down(
|
||||||
editor: &mut Editor,
|
editor: &mut Editor,
|
||||||
event: &MouseDownEvent,
|
event: &MouseDownEvent,
|
||||||
hovered_hunk: Option<&HunkToExpand>,
|
hovered_hunk: Option<HoveredHunk>,
|
||||||
position_map: &PositionMap,
|
position_map: &PositionMap,
|
||||||
text_hitbox: &Hitbox,
|
text_hitbox: &Hitbox,
|
||||||
gutter_hitbox: &Hitbox,
|
gutter_hitbox: &Hitbox,
|
||||||
|
@ -456,7 +461,28 @@ impl EditorElement {
|
||||||
let mut modifiers = event.modifiers;
|
let mut modifiers = event.modifiers;
|
||||||
|
|
||||||
if let Some(hovered_hunk) = hovered_hunk {
|
if let Some(hovered_hunk) = hovered_hunk {
|
||||||
editor.expand_diff_hunk(None, hovered_hunk, cx);
|
if modifiers.control || modifiers.platform {
|
||||||
|
editor.toggle_hovered_hunk(&hovered_hunk, cx);
|
||||||
|
} else {
|
||||||
|
let display_range = hovered_hunk
|
||||||
|
.multi_buffer_range
|
||||||
|
.clone()
|
||||||
|
.to_display_points(&position_map.snapshot);
|
||||||
|
let hunk_bounds = Self::diff_hunk_bounds(
|
||||||
|
&position_map.snapshot,
|
||||||
|
position_map.line_height,
|
||||||
|
gutter_hitbox.bounds,
|
||||||
|
&DisplayDiffHunk::Unfolded {
|
||||||
|
diff_base_byte_range: hovered_hunk.diff_base_byte_range.clone(),
|
||||||
|
display_row_range: display_range.start.row()..display_range.end.row(),
|
||||||
|
multi_buffer_range: hovered_hunk.multi_buffer_range.clone(),
|
||||||
|
status: hovered_hunk.status,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if hunk_bounds.contains(&event.position) {
|
||||||
|
editor.open_hunk_context_menu(hovered_hunk, event.position, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
cx.notify();
|
cx.notify();
|
||||||
return;
|
return;
|
||||||
} else if gutter_hitbox.is_hovered(cx) {
|
} else if gutter_hitbox.is_hovered(cx) {
|
||||||
|
@ -1245,47 +1271,18 @@ impl EditorElement {
|
||||||
.row,
|
.row,
|
||||||
);
|
);
|
||||||
|
|
||||||
let expanded_hunk_display_rows = self.editor.update(cx, |editor, _| {
|
|
||||||
editor
|
|
||||||
.expanded_hunks
|
|
||||||
.hunks(false)
|
|
||||||
.map(|expanded_hunk| {
|
|
||||||
let start_row = expanded_hunk
|
|
||||||
.hunk_range
|
|
||||||
.start
|
|
||||||
.to_display_point(snapshot)
|
|
||||||
.row();
|
|
||||||
let end_row = expanded_hunk
|
|
||||||
.hunk_range
|
|
||||||
.end
|
|
||||||
.to_display_point(snapshot)
|
|
||||||
.row();
|
|
||||||
(start_row, end_row)
|
|
||||||
})
|
|
||||||
.collect::<HashMap<_, _>>()
|
|
||||||
});
|
|
||||||
|
|
||||||
let git_gutter_setting = ProjectSettings::get_global(cx)
|
let git_gutter_setting = ProjectSettings::get_global(cx)
|
||||||
.git
|
.git
|
||||||
.git_gutter
|
.git_gutter
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
buffer_snapshot
|
let display_hunks = buffer_snapshot
|
||||||
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
|
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
|
||||||
.map(|hunk| diff_hunk_to_display(&hunk, snapshot))
|
.map(|hunk| diff_hunk_to_display(&hunk, snapshot))
|
||||||
.dedup()
|
.dedup()
|
||||||
.map(|hunk| match git_gutter_setting {
|
.map(|hunk| match git_gutter_setting {
|
||||||
GitGutterSetting::TrackedFiles => {
|
GitGutterSetting::TrackedFiles => {
|
||||||
let hitbox = if let DisplayDiffHunk::Unfolded {
|
let hitbox = match hunk {
|
||||||
display_row_range, ..
|
DisplayDiffHunk::Unfolded { .. } => {
|
||||||
} = &hunk
|
|
||||||
{
|
|
||||||
let was_expanded = expanded_hunk_display_rows
|
|
||||||
.get(&display_row_range.start)
|
|
||||||
.map(|expanded_end_row| expanded_end_row == &display_row_range.end)
|
|
||||||
.unwrap_or(false);
|
|
||||||
if was_expanded {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let hunk_bounds = Self::diff_hunk_bounds(
|
let hunk_bounds = Self::diff_hunk_bounds(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
line_height,
|
line_height,
|
||||||
|
@ -1294,14 +1291,14 @@ impl EditorElement {
|
||||||
);
|
);
|
||||||
Some(cx.insert_hitbox(hunk_bounds, true))
|
Some(cx.insert_hitbox(hunk_bounds, true))
|
||||||
}
|
}
|
||||||
} else {
|
DisplayDiffHunk::Folded { .. } => None,
|
||||||
None
|
|
||||||
};
|
};
|
||||||
(hunk, hitbox)
|
(hunk, hitbox)
|
||||||
}
|
}
|
||||||
GitGutterSetting::Hide => (hunk, None),
|
GitGutterSetting::Hide => (hunk, None),
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
|
display_hunks
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
@ -1369,9 +1366,7 @@ impl EditorElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
let absolute_offset = point(start_x, start_y);
|
let absolute_offset = point(start_x, start_y);
|
||||||
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx);
|
||||||
|
|
||||||
element.prepaint_as_root(absolute_offset, available_space, cx);
|
|
||||||
|
|
||||||
Some(element)
|
Some(element)
|
||||||
}
|
}
|
||||||
|
@ -2472,8 +2467,7 @@ impl EditorElement {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
let context_menu_size = context_menu.layout_as_root(AvailableSpace::min_size(), cx);
|
||||||
let context_menu_size = context_menu.layout_as_root(available_space, cx);
|
|
||||||
|
|
||||||
let (x, y) = match position {
|
let (x, y) = match position {
|
||||||
crate::ContextMenuOrigin::EditorPoint(point) => {
|
crate::ContextMenuOrigin::EditorPoint(point) => {
|
||||||
|
@ -2510,19 +2504,72 @@ impl EditorElement {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_mouse_context_menu(&self, cx: &mut WindowContext) -> Option<AnyElement> {
|
fn layout_mouse_context_menu(
|
||||||
let mouse_context_menu = self.editor.read(cx).mouse_context_menu.as_ref()?;
|
&self,
|
||||||
let mut element = deferred(
|
editor_snapshot: &EditorSnapshot,
|
||||||
|
visible_range: Range<DisplayRow>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Option<AnyElement> {
|
||||||
|
let position = self.editor.update(cx, |editor, cx| {
|
||||||
|
let visible_start_point = editor.display_to_pixel_point(
|
||||||
|
DisplayPoint::new(visible_range.start, 0),
|
||||||
|
editor_snapshot,
|
||||||
|
cx,
|
||||||
|
)?;
|
||||||
|
let visible_end_point = editor.display_to_pixel_point(
|
||||||
|
DisplayPoint::new(visible_range.end, 0),
|
||||||
|
editor_snapshot,
|
||||||
|
cx,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
|
||||||
|
let (source_display_point, position) = match mouse_context_menu.position {
|
||||||
|
MenuPosition::PinnedToScreen(point) => (None, point),
|
||||||
|
MenuPosition::PinnedToEditor {
|
||||||
|
source,
|
||||||
|
offset_x,
|
||||||
|
offset_y,
|
||||||
|
} => {
|
||||||
|
let source_display_point = source.to_display_point(editor_snapshot);
|
||||||
|
let mut source_point = editor.to_pixel_point(source, editor_snapshot, cx)?;
|
||||||
|
source_point.x += offset_x;
|
||||||
|
source_point.y += offset_y;
|
||||||
|
(Some(source_display_point), source_point)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let source_included = source_display_point.map_or(true, |source_display_point| {
|
||||||
|
visible_range
|
||||||
|
.to_inclusive()
|
||||||
|
.contains(&source_display_point.row())
|
||||||
|
});
|
||||||
|
let position_included =
|
||||||
|
visible_start_point.y <= position.y && position.y <= visible_end_point.y;
|
||||||
|
if !source_included && !position_included {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(position)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut element = self.editor.update(cx, |editor, _| {
|
||||||
|
let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
|
||||||
|
let context_menu = mouse_context_menu.context_menu.clone();
|
||||||
|
|
||||||
|
Some(
|
||||||
|
deferred(
|
||||||
anchored()
|
anchored()
|
||||||
.position(mouse_context_menu.position)
|
.position(position)
|
||||||
.child(mouse_context_menu.context_menu.clone())
|
.child(context_menu)
|
||||||
.anchor(AnchorCorner::TopLeft)
|
.anchor(AnchorCorner::TopLeft)
|
||||||
.snap_to_window(),
|
.snap_to_window(),
|
||||||
)
|
)
|
||||||
.with_priority(1)
|
.with_priority(1)
|
||||||
.into_any();
|
.into_any(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
element.prepaint_as_root(gpui::Point::default(), AvailableSpace::min_size(), cx);
|
element.prepaint_as_root(position, AvailableSpace::min_size(), cx);
|
||||||
Some(element)
|
Some(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2569,8 +2616,6 @@ impl EditorElement {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
|
|
||||||
|
|
||||||
// This is safe because we check on layout whether the required row is available
|
// This is safe because we check on layout whether the required row is available
|
||||||
let hovered_row_layout =
|
let hovered_row_layout =
|
||||||
&line_layouts[position.row().minus(visible_display_row_range.start) as usize];
|
&line_layouts[position.row().minus(visible_display_row_range.start) as usize];
|
||||||
|
@ -2584,7 +2629,7 @@ impl EditorElement {
|
||||||
let mut overall_height = Pixels::ZERO;
|
let mut overall_height = Pixels::ZERO;
|
||||||
let mut measured_hover_popovers = Vec::new();
|
let mut measured_hover_popovers = Vec::new();
|
||||||
for mut hover_popover in hover_popovers {
|
for mut hover_popover in hover_popovers {
|
||||||
let size = hover_popover.layout_as_root(available_space, cx);
|
let size = hover_popover.layout_as_root(AvailableSpace::min_size(), cx);
|
||||||
let horizontal_offset =
|
let horizontal_offset =
|
||||||
(text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
|
(text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
|
||||||
|
|
||||||
|
@ -2953,7 +2998,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_diff_hunks(layout: &EditorLayout, cx: &mut WindowContext) {
|
fn paint_diff_hunks(layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
if layout.display_hunks.is_empty() {
|
if layout.display_hunks.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -3018,7 +3063,7 @@ impl EditorElement {
|
||||||
fn diff_hunk_bounds(
|
fn diff_hunk_bounds(
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
line_height: Pixels,
|
line_height: Pixels,
|
||||||
bounds: Bounds<Pixels>,
|
gutter_bounds: Bounds<Pixels>,
|
||||||
hunk: &DisplayDiffHunk,
|
hunk: &DisplayDiffHunk,
|
||||||
) -> Bounds<Pixels> {
|
) -> Bounds<Pixels> {
|
||||||
let scroll_position = snapshot.scroll_position();
|
let scroll_position = snapshot.scroll_position();
|
||||||
|
@ -3030,7 +3075,7 @@ impl EditorElement {
|
||||||
let end_y = start_y + line_height;
|
let end_y = start_y + line_height;
|
||||||
|
|
||||||
let width = 0.275 * line_height;
|
let width = 0.275 * line_height;
|
||||||
let highlight_origin = bounds.origin + point(px(0.), start_y);
|
let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
|
||||||
let highlight_size = size(width, end_y - start_y);
|
let highlight_size = size(width, end_y - start_y);
|
||||||
Bounds::new(highlight_origin, highlight_size)
|
Bounds::new(highlight_origin, highlight_size)
|
||||||
}
|
}
|
||||||
|
@ -3063,7 +3108,7 @@ impl EditorElement {
|
||||||
let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
|
let end_y = end_row_in_current_excerpt.as_f32() * line_height - scroll_top;
|
||||||
|
|
||||||
let width = 0.275 * line_height;
|
let width = 0.275 * line_height;
|
||||||
let highlight_origin = bounds.origin + point(px(0.), start_y);
|
let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
|
||||||
let highlight_size = size(width, end_y - start_y);
|
let highlight_size = size(width, end_y - start_y);
|
||||||
Bounds::new(highlight_origin, highlight_size)
|
Bounds::new(highlight_origin, highlight_size)
|
||||||
}
|
}
|
||||||
|
@ -3075,7 +3120,7 @@ impl EditorElement {
|
||||||
let end_y = start_y + line_height;
|
let end_y = start_y + line_height;
|
||||||
|
|
||||||
let width = 0.35 * line_height;
|
let width = 0.35 * line_height;
|
||||||
let highlight_origin = bounds.origin + point(px(0.), start_y);
|
let highlight_origin = gutter_bounds.origin + point(px(0.), start_y);
|
||||||
let highlight_size = size(width, end_y - start_y);
|
let highlight_size = size(width, end_y - start_y);
|
||||||
Bounds::new(highlight_origin, highlight_size)
|
Bounds::new(highlight_origin, highlight_size)
|
||||||
}
|
}
|
||||||
|
@ -3091,8 +3136,11 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for test_indicators in layout.test_indicators.iter_mut() {
|
for test_indicator in layout.test_indicators.iter_mut() {
|
||||||
test_indicators.paint(cx);
|
test_indicator.paint(cx);
|
||||||
|
}
|
||||||
|
for close_indicator in layout.close_indicators.iter_mut() {
|
||||||
|
close_indicator.paint(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
if let Some(indicator) = layout.code_actions_indicator.as_mut() {
|
||||||
|
@ -3101,7 +3149,7 @@ impl EditorElement {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
|
fn paint_gutter_highlights(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
|
||||||
for (_, hunk_hitbox) in &layout.display_hunks {
|
for (_, hunk_hitbox) in &layout.display_hunks {
|
||||||
if let Some(hunk_hitbox) = hunk_hitbox {
|
if let Some(hunk_hitbox) = hunk_hitbox {
|
||||||
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
|
cx.set_cursor_style(CursorStyle::PointingHand, hunk_hitbox);
|
||||||
|
@ -3757,7 +3805,7 @@ impl EditorElement {
|
||||||
fn paint_mouse_listeners(
|
fn paint_mouse_listeners(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: &EditorLayout,
|
layout: &EditorLayout,
|
||||||
hovered_hunk: Option<HunkToExpand>,
|
hovered_hunk: Option<HoveredHunk>,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) {
|
) {
|
||||||
self.paint_scroll_wheel_listener(layout, cx);
|
self.paint_scroll_wheel_listener(layout, cx);
|
||||||
|
@ -3775,7 +3823,7 @@ impl EditorElement {
|
||||||
Self::mouse_left_down(
|
Self::mouse_left_down(
|
||||||
editor,
|
editor,
|
||||||
event,
|
event,
|
||||||
hovered_hunk.as_ref(),
|
hovered_hunk.clone(),
|
||||||
&position_map,
|
&position_map,
|
||||||
&text_hitbox,
|
&text_hitbox,
|
||||||
&gutter_hitbox,
|
&gutter_hitbox,
|
||||||
|
@ -3881,6 +3929,43 @@ impl EditorElement {
|
||||||
+ 1;
|
+ 1;
|
||||||
self.column_pixels(digit_count, cx)
|
self.column_pixels(digit_count, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout_hunk_diff_close_indicators(
|
||||||
|
&self,
|
||||||
|
expanded_hunks_by_rows: HashMap<DisplayRow, ExpandedHunk>,
|
||||||
|
line_height: Pixels,
|
||||||
|
scroll_pixel_position: gpui::Point<Pixels>,
|
||||||
|
gutter_dimensions: &GutterDimensions,
|
||||||
|
gutter_hitbox: &Hitbox,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Vec<AnyElement> {
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
expanded_hunks_by_rows
|
||||||
|
.into_iter()
|
||||||
|
.map(|(display_row, hunk)| {
|
||||||
|
let button = editor.render_close_hunk_diff_button(
|
||||||
|
HoveredHunk {
|
||||||
|
multi_buffer_range: hunk.hunk_range,
|
||||||
|
status: hunk.status,
|
||||||
|
diff_base_byte_range: hunk.diff_base_byte_range,
|
||||||
|
},
|
||||||
|
display_row,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
prepaint_gutter_button(
|
||||||
|
button,
|
||||||
|
display_row,
|
||||||
|
line_height,
|
||||||
|
gutter_dimensions,
|
||||||
|
scroll_pixel_position,
|
||||||
|
gutter_hitbox,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepaint_gutter_button(
|
fn prepaint_gutter_button(
|
||||||
|
@ -4037,9 +4122,10 @@ fn deploy_blame_entry_context_menu(
|
||||||
position: gpui::Point<Pixels>,
|
position: gpui::Point<Pixels>,
|
||||||
cx: &mut WindowContext<'_>,
|
cx: &mut WindowContext<'_>,
|
||||||
) {
|
) {
|
||||||
let context_menu = ContextMenu::build(cx, move |this, _| {
|
let context_menu = ContextMenu::build(cx, move |menu, _| {
|
||||||
let sha = format!("{}", blame_entry.sha);
|
let sha = format!("{}", blame_entry.sha);
|
||||||
this.entry("Copy commit SHA", None, move |cx| {
|
menu.on_blur_subscription(Subscription::new(|| {}))
|
||||||
|
.entry("Copy commit SHA", None, move |cx| {
|
||||||
cx.write_to_clipboard(ClipboardItem::new(sha.clone()));
|
cx.write_to_clipboard(ClipboardItem::new(sha.clone()));
|
||||||
})
|
})
|
||||||
.when_some(
|
.when_some(
|
||||||
|
@ -4049,7 +4135,11 @@ fn deploy_blame_entry_context_menu(
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.update(cx, move |editor, cx| {
|
editor.update(cx, move |editor, cx| {
|
||||||
editor.mouse_context_menu = Some(MouseContextMenu::new(position, context_menu, cx));
|
editor.mouse_context_menu = Some(MouseContextMenu::pinned_to_screen(
|
||||||
|
position,
|
||||||
|
context_menu,
|
||||||
|
cx,
|
||||||
|
));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5087,6 +5177,22 @@ impl Element for EditorElement {
|
||||||
|
|
||||||
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
let gutter_settings = EditorSettings::get_global(cx).gutter;
|
||||||
|
|
||||||
|
let expanded_add_hunks_by_rows = self.editor.update(cx, |editor, _| {
|
||||||
|
editor
|
||||||
|
.expanded_hunks
|
||||||
|
.hunks(false)
|
||||||
|
.filter(|hunk| hunk.status == DiffHunkStatus::Added)
|
||||||
|
.map(|expanded_hunk| {
|
||||||
|
let start_row = expanded_hunk
|
||||||
|
.hunk_range
|
||||||
|
.start
|
||||||
|
.to_display_point(&snapshot)
|
||||||
|
.row();
|
||||||
|
(start_row, expanded_hunk.clone())
|
||||||
|
})
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
});
|
||||||
|
|
||||||
let mut _context_menu_visible = false;
|
let mut _context_menu_visible = false;
|
||||||
let mut code_actions_indicator = None;
|
let mut code_actions_indicator = None;
|
||||||
if let Some(newest_selection_head) = newest_selection_head {
|
if let Some(newest_selection_head) = newest_selection_head {
|
||||||
|
@ -5110,14 +5216,22 @@ impl Element for EditorElement {
|
||||||
if show_code_actions {
|
if show_code_actions {
|
||||||
let newest_selection_point =
|
let newest_selection_point =
|
||||||
newest_selection_head.to_point(&snapshot.display_snapshot);
|
newest_selection_head.to_point(&snapshot.display_snapshot);
|
||||||
|
let newest_selection_display_row =
|
||||||
|
newest_selection_point.to_display_point(&snapshot).row();
|
||||||
|
if !expanded_add_hunks_by_rows
|
||||||
|
.contains_key(&newest_selection_display_row)
|
||||||
|
{
|
||||||
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
|
let buffer = snapshot.buffer_snapshot.buffer_line_for_row(
|
||||||
MultiBufferRow(newest_selection_point.row),
|
MultiBufferRow(newest_selection_point.row),
|
||||||
);
|
);
|
||||||
if let Some((buffer, range)) = buffer {
|
if let Some((buffer, range)) = buffer {
|
||||||
let buffer_id = buffer.remote_id();
|
let buffer_id = buffer.remote_id();
|
||||||
let row = range.start.row;
|
let row = range.start.row;
|
||||||
let has_test_indicator =
|
let has_test_indicator = self
|
||||||
self.editor.read(cx).tasks.contains_key(&(buffer_id, row));
|
.editor
|
||||||
|
.read(cx)
|
||||||
|
.tasks
|
||||||
|
.contains_key(&(buffer_id, row));
|
||||||
|
|
||||||
if !has_test_indicator {
|
if !has_test_indicator {
|
||||||
code_actions_indicator = self
|
code_actions_indicator = self
|
||||||
|
@ -5134,6 +5248,7 @@ impl Element for EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let test_indicators = if gutter_settings.runnables {
|
let test_indicators = if gutter_settings.runnables {
|
||||||
self.layout_run_indicators(
|
self.layout_run_indicators(
|
||||||
|
@ -5145,9 +5260,18 @@ impl Element for EditorElement {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let close_indicators = self.layout_hunk_diff_close_indicators(
|
||||||
|
expanded_add_hunks_by_rows,
|
||||||
|
line_height,
|
||||||
|
scroll_pixel_position,
|
||||||
|
&gutter_dimensions,
|
||||||
|
&gutter_hitbox,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
self.layout_signature_help(
|
self.layout_signature_help(
|
||||||
&hitbox,
|
&hitbox,
|
||||||
content_origin,
|
content_origin,
|
||||||
|
@ -5175,7 +5299,8 @@ impl Element for EditorElement {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouse_context_menu = self.layout_mouse_context_menu(cx);
|
let mouse_context_menu =
|
||||||
|
self.layout_mouse_context_menu(&snapshot, start_row..end_row, cx);
|
||||||
|
|
||||||
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
cx.with_element_namespace("gutter_fold_toggles", |cx| {
|
||||||
self.prepaint_gutter_fold_toggles(
|
self.prepaint_gutter_fold_toggles(
|
||||||
|
@ -5240,6 +5365,7 @@ impl Element for EditorElement {
|
||||||
text_hitbox,
|
text_hitbox,
|
||||||
gutter_hitbox,
|
gutter_hitbox,
|
||||||
gutter_dimensions,
|
gutter_dimensions,
|
||||||
|
display_hunks,
|
||||||
content_origin,
|
content_origin,
|
||||||
scrollbar_layout,
|
scrollbar_layout,
|
||||||
active_rows,
|
active_rows,
|
||||||
|
@ -5249,7 +5375,6 @@ impl Element for EditorElement {
|
||||||
redacted_ranges,
|
redacted_ranges,
|
||||||
line_elements,
|
line_elements,
|
||||||
line_numbers,
|
line_numbers,
|
||||||
display_hunks,
|
|
||||||
blamed_display_rows,
|
blamed_display_rows,
|
||||||
inline_blame,
|
inline_blame,
|
||||||
blocks,
|
blocks,
|
||||||
|
@ -5258,6 +5383,7 @@ impl Element for EditorElement {
|
||||||
selections,
|
selections,
|
||||||
mouse_context_menu,
|
mouse_context_menu,
|
||||||
test_indicators,
|
test_indicators,
|
||||||
|
close_indicators,
|
||||||
code_actions_indicator,
|
code_actions_indicator,
|
||||||
gutter_fold_toggles,
|
gutter_fold_toggles,
|
||||||
crease_trailers,
|
crease_trailers,
|
||||||
|
@ -5310,7 +5436,7 @@ impl Element for EditorElement {
|
||||||
.map(|hitbox| hitbox.contains(&mouse_position))
|
.map(|hitbox| hitbox.contains(&mouse_position))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
Some(HunkToExpand {
|
Some(HoveredHunk {
|
||||||
status: *status,
|
status: *status,
|
||||||
multi_buffer_range: multi_buffer_range.clone(),
|
multi_buffer_range: multi_buffer_range.clone(),
|
||||||
diff_base_byte_range: diff_base_byte_range.clone(),
|
diff_base_byte_range: diff_base_byte_range.clone(),
|
||||||
|
@ -5390,6 +5516,7 @@ pub struct EditorLayout {
|
||||||
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
selections: Vec<(PlayerColor, Vec<SelectionLayout>)>,
|
||||||
code_actions_indicator: Option<AnyElement>,
|
code_actions_indicator: Option<AnyElement>,
|
||||||
test_indicators: Vec<AnyElement>,
|
test_indicators: Vec<AnyElement>,
|
||||||
|
close_indicators: Vec<AnyElement>,
|
||||||
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
gutter_fold_toggles: Vec<Option<AnyElement>>,
|
||||||
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
|
crease_trailers: Vec<Option<CreaseTrailerLayout>>,
|
||||||
mouse_context_menu: Option<AnyElement>,
|
mouse_context_menu: Option<AnyElement>,
|
||||||
|
@ -5705,11 +5832,7 @@ impl CursorLayout {
|
||||||
.child(cursor_name.string.clone())
|
.child(cursor_name.string.clone())
|
||||||
.into_any_element();
|
.into_any_element();
|
||||||
|
|
||||||
name_element.prepaint_as_root(
|
name_element.prepaint_as_root(name_origin, AvailableSpace::min_size(), cx);
|
||||||
name_origin,
|
|
||||||
size(AvailableSpace::MinContent, AvailableSpace::MinContent),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.cursor_name = Some(name_element);
|
self.cursor_name = Some(name_element);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,28 +5,31 @@ use std::{
|
||||||
|
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use git::diff::{DiffHunk, DiffHunkStatus};
|
use git::diff::{DiffHunk, DiffHunkStatus};
|
||||||
use gpui::{AppContext, Hsla, Model, Task, View};
|
use gpui::{Action, AppContext, Hsla, Model, MouseButton, Subscription, Task, View};
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use multi_buffer::{
|
use multi_buffer::{
|
||||||
Anchor, ExcerptRange, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, ToPoint,
|
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferRow, MultiBufferSnapshot, ToPoint,
|
||||||
};
|
};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use text::{BufferId, Point};
|
use text::{BufferId, Point};
|
||||||
use ui::{
|
use ui::{
|
||||||
div, ActiveTheme, Context as _, IntoElement, ParentElement, Styled, ViewContext, VisualContext,
|
h_flex, v_flex, ActiveTheme, Context as _, ContextMenu, InteractiveElement, IntoElement,
|
||||||
|
ParentElement, Pixels, Styled, ViewContext, VisualContext,
|
||||||
};
|
};
|
||||||
use util::{debug_panic, RangeExt};
|
use util::{debug_panic, RangeExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
editor_settings::CurrentLineHighlight,
|
editor_settings::CurrentLineHighlight,
|
||||||
git::{diff_hunk_to_display, DisplayDiffHunk},
|
git::{diff_hunk_to_display, DisplayDiffHunk},
|
||||||
hunk_status, hunks_for_selections, BlockDisposition, BlockId, BlockProperties, BlockStyle,
|
hunk_status, hunks_for_selections,
|
||||||
DiffRowHighlight, Editor, EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt,
|
mouse_context_menu::MouseContextMenu,
|
||||||
RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff,
|
BlockDisposition, BlockId, BlockProperties, BlockStyle, DiffRowHighlight, Editor,
|
||||||
|
EditorSnapshot, ExpandAllHunkDiffs, RangeToAnchorExt, RevertSelectedHunks, ToDisplayPoint,
|
||||||
|
ToggleHunkDiff,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct HunkToExpand {
|
pub(super) struct HoveredHunk {
|
||||||
pub multi_buffer_range: Range<Anchor>,
|
pub multi_buffer_range: Range<Anchor>,
|
||||||
pub status: DiffHunkStatus,
|
pub status: DiffHunkStatus,
|
||||||
pub diff_base_byte_range: Range<usize>,
|
pub diff_base_byte_range: Range<usize>,
|
||||||
|
@ -63,6 +66,123 @@ pub(super) struct ExpandedHunk {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
|
pub(super) fn open_hunk_context_menu(
|
||||||
|
&mut self,
|
||||||
|
hovered_hunk: HoveredHunk,
|
||||||
|
clicked_point: gpui::Point<Pixels>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
let expanded = self
|
||||||
|
.expanded_hunks
|
||||||
|
.hunks(false)
|
||||||
|
.any(|expanded_hunk| expanded_hunk.hunk_range == hovered_hunk.multi_buffer_range);
|
||||||
|
let editor_handle = cx.view().clone();
|
||||||
|
let editor_snapshot = self.snapshot(cx);
|
||||||
|
let start_point = self
|
||||||
|
.to_pixel_point(hovered_hunk.multi_buffer_range.start, &editor_snapshot, cx)
|
||||||
|
.unwrap_or(clicked_point);
|
||||||
|
let end_point = self
|
||||||
|
.to_pixel_point(hovered_hunk.multi_buffer_range.start, &editor_snapshot, cx)
|
||||||
|
.unwrap_or(clicked_point);
|
||||||
|
let norm =
|
||||||
|
|a: gpui::Point<Pixels>, b: gpui::Point<Pixels>| (a.x - b.x).abs() + (a.y - b.y).abs();
|
||||||
|
let closest_source = if norm(start_point, clicked_point) < norm(end_point, clicked_point) {
|
||||||
|
hovered_hunk.multi_buffer_range.start
|
||||||
|
} else {
|
||||||
|
hovered_hunk.multi_buffer_range.end
|
||||||
|
};
|
||||||
|
|
||||||
|
self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
|
||||||
|
self,
|
||||||
|
closest_source,
|
||||||
|
clicked_point,
|
||||||
|
ContextMenu::build(cx, move |menu, _| {
|
||||||
|
menu.on_blur_subscription(Subscription::new(|| {}))
|
||||||
|
.context(focus_handle)
|
||||||
|
.entry(
|
||||||
|
if expanded {
|
||||||
|
"Collapse Hunk"
|
||||||
|
} else {
|
||||||
|
"Expand Hunk"
|
||||||
|
},
|
||||||
|
Some(ToggleHunkDiff.boxed_clone()),
|
||||||
|
{
|
||||||
|
let editor = editor_handle.clone();
|
||||||
|
let hunk = hovered_hunk.clone();
|
||||||
|
move |cx| {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.toggle_hovered_hunk(&hunk, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.entry("Revert Hunk", Some(RevertSelectedHunks.boxed_clone()), {
|
||||||
|
let editor = editor_handle.clone();
|
||||||
|
let hunk = hovered_hunk.clone();
|
||||||
|
move |cx| {
|
||||||
|
let multi_buffer = editor.read(cx).buffer().clone();
|
||||||
|
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||||
|
let mut revert_changes = HashMap::default();
|
||||||
|
if let Some(hunk) =
|
||||||
|
crate::hunk_diff::to_diff_hunk(&hunk, &multi_buffer_snapshot)
|
||||||
|
{
|
||||||
|
Editor::prepare_revert_change(
|
||||||
|
&mut revert_changes,
|
||||||
|
&multi_buffer,
|
||||||
|
&hunk,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !revert_changes.is_empty() {
|
||||||
|
editor.update(cx, |editor, cx| editor.revert(revert_changes, cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.entry("Revert File", None, {
|
||||||
|
let editor = editor_handle.clone();
|
||||||
|
move |cx| {
|
||||||
|
let mut revert_changes = HashMap::default();
|
||||||
|
let multi_buffer = editor.read(cx).buffer().clone();
|
||||||
|
let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
|
||||||
|
for hunk in crate::hunks_for_rows(
|
||||||
|
Some(MultiBufferRow(0)..multi_buffer_snapshot.max_buffer_row())
|
||||||
|
.into_iter(),
|
||||||
|
&multi_buffer_snapshot,
|
||||||
|
) {
|
||||||
|
Editor::prepare_revert_change(
|
||||||
|
&mut revert_changes,
|
||||||
|
&multi_buffer,
|
||||||
|
&hunk,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if !revert_changes.is_empty() {
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.transact(cx, |editor, cx| {
|
||||||
|
editor.revert(revert_changes, cx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn toggle_hovered_hunk(
|
||||||
|
&mut self,
|
||||||
|
hovered_hunk: &HoveredHunk,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let editor_snapshot = self.snapshot(cx);
|
||||||
|
if let Some(diff_hunk) = to_diff_hunk(hovered_hunk, &editor_snapshot.buffer_snapshot) {
|
||||||
|
self.toggle_hunks_expanded(vec![diff_hunk], cx);
|
||||||
|
self.change_selections(None, cx, |selections| selections.refresh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
||||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||||
let selections = self.selections.disjoint_anchors();
|
let selections = self.selections.disjoint_anchors();
|
||||||
|
@ -164,7 +284,7 @@ impl Editor {
|
||||||
retain = false;
|
retain = false;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
hunks_to_expand.push(HunkToExpand {
|
hunks_to_expand.push(HoveredHunk {
|
||||||
status,
|
status,
|
||||||
multi_buffer_range,
|
multi_buffer_range,
|
||||||
diff_base_byte_range,
|
diff_base_byte_range,
|
||||||
|
@ -182,7 +302,7 @@ impl Editor {
|
||||||
let remaining_hunk_point_range =
|
let remaining_hunk_point_range =
|
||||||
Point::new(remaining_hunk.associated_range.start.0, 0)
|
Point::new(remaining_hunk.associated_range.start.0, 0)
|
||||||
..Point::new(remaining_hunk.associated_range.end.0, 0);
|
..Point::new(remaining_hunk.associated_range.end.0, 0);
|
||||||
hunks_to_expand.push(HunkToExpand {
|
hunks_to_expand.push(HoveredHunk {
|
||||||
status: hunk_status(&remaining_hunk),
|
status: hunk_status(&remaining_hunk),
|
||||||
multi_buffer_range: remaining_hunk_point_range
|
multi_buffer_range: remaining_hunk_point_range
|
||||||
.to_anchors(&snapshot.buffer_snapshot),
|
.to_anchors(&snapshot.buffer_snapshot),
|
||||||
|
@ -215,7 +335,7 @@ impl Editor {
|
||||||
pub(super) fn expand_diff_hunk(
|
pub(super) fn expand_diff_hunk(
|
||||||
&mut self,
|
&mut self,
|
||||||
diff_base_buffer: Option<Model<Buffer>>,
|
diff_base_buffer: Option<Model<Buffer>>,
|
||||||
hunk: &HunkToExpand,
|
hunk: &HoveredHunk,
|
||||||
cx: &mut ViewContext<'_, Editor>,
|
cx: &mut ViewContext<'_, Editor>,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||||
|
@ -303,28 +423,58 @@ impl Editor {
|
||||||
&mut self,
|
&mut self,
|
||||||
diff_base_buffer: Model<Buffer>,
|
diff_base_buffer: Model<Buffer>,
|
||||||
deleted_text_height: u8,
|
deleted_text_height: u8,
|
||||||
hunk: &HunkToExpand,
|
hunk: &HoveredHunk,
|
||||||
cx: &mut ViewContext<'_, Self>,
|
cx: &mut ViewContext<'_, Self>,
|
||||||
) -> Option<BlockId> {
|
) -> Option<BlockId> {
|
||||||
let deleted_hunk_color = deleted_hunk_color(cx);
|
let deleted_hunk_color = deleted_hunk_color(cx);
|
||||||
let (editor_height, editor_with_deleted_text) =
|
let (editor_height, editor_with_deleted_text) =
|
||||||
editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx);
|
editor_with_deleted_text(diff_base_buffer, deleted_hunk_color, hunk, cx);
|
||||||
|
let editor = cx.view().clone();
|
||||||
let editor_model = cx.model().clone();
|
let editor_model = cx.model().clone();
|
||||||
|
let hunk = hunk.clone();
|
||||||
let mut new_block_ids = self.insert_blocks(
|
let mut new_block_ids = self.insert_blocks(
|
||||||
Some(BlockProperties {
|
Some(BlockProperties {
|
||||||
position: hunk.multi_buffer_range.start,
|
position: hunk.multi_buffer_range.start,
|
||||||
height: editor_height.max(deleted_text_height),
|
height: editor_height.max(deleted_text_height),
|
||||||
style: BlockStyle::Flex,
|
style: BlockStyle::Flex,
|
||||||
|
disposition: BlockDisposition::Above,
|
||||||
render: Box::new(move |cx| {
|
render: Box::new(move |cx| {
|
||||||
|
let close_button = editor.update(cx.context, |editor, cx| {
|
||||||
|
let editor_snapshot = editor.snapshot(cx);
|
||||||
|
let hunk_start_row = hunk
|
||||||
|
.multi_buffer_range
|
||||||
|
.start
|
||||||
|
.to_display_point(&editor_snapshot)
|
||||||
|
.row();
|
||||||
|
editor.render_close_hunk_diff_button(hunk.clone(), hunk_start_row, cx)
|
||||||
|
});
|
||||||
let gutter_dimensions = editor_model.read(cx).gutter_dimensions;
|
let gutter_dimensions = editor_model.read(cx).gutter_dimensions;
|
||||||
div()
|
let click_editor = editor.clone();
|
||||||
|
h_flex()
|
||||||
.bg(deleted_hunk_color)
|
.bg(deleted_hunk_color)
|
||||||
.size_full()
|
.size_full()
|
||||||
.pl(gutter_dimensions.full_width())
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.justify_center()
|
||||||
|
.max_w(gutter_dimensions.full_width())
|
||||||
|
.min_w(gutter_dimensions.full_width())
|
||||||
|
.size_full()
|
||||||
|
.on_mouse_down(MouseButton::Left, {
|
||||||
|
let click_hunk = hunk.clone();
|
||||||
|
move |e, cx| {
|
||||||
|
let modifiers = e.modifiers;
|
||||||
|
if modifiers.control || modifiers.platform {
|
||||||
|
click_editor.update(cx, |editor, cx| {
|
||||||
|
editor.toggle_hovered_hunk(&click_hunk, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(close_button),
|
||||||
|
)
|
||||||
.child(editor_with_deleted_text.clone())
|
.child(editor_with_deleted_text.clone())
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}),
|
}),
|
||||||
disposition: BlockDisposition::Above,
|
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
|
@ -339,16 +489,21 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) {
|
pub(super) fn clear_clicked_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
|
||||||
self.expanded_hunks.hunk_update_tasks.clear();
|
self.expanded_hunks.hunk_update_tasks.clear();
|
||||||
|
self.clear_row_highlights::<DiffRowHighlight>();
|
||||||
let to_remove = self
|
let to_remove = self
|
||||||
.expanded_hunks
|
.expanded_hunks
|
||||||
.hunks
|
.hunks
|
||||||
.drain(..)
|
.drain(..)
|
||||||
.filter_map(|expanded_hunk| expanded_hunk.block)
|
.filter_map(|expanded_hunk| expanded_hunk.block)
|
||||||
.collect();
|
.collect::<HashSet<_>>();
|
||||||
self.clear_row_highlights::<DiffRowHighlight>();
|
if to_remove.is_empty() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
self.remove_blocks(to_remove, None, cx);
|
self.remove_blocks(to_remove, None, cx);
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn sync_expanded_diff_hunks(
|
pub(super) fn sync_expanded_diff_hunks(
|
||||||
|
@ -457,7 +612,7 @@ impl Editor {
|
||||||
recalculated_hunks.next();
|
recalculated_hunks.next();
|
||||||
retain = true;
|
retain = true;
|
||||||
} else {
|
} else {
|
||||||
hunks_to_reexpand.push(HunkToExpand {
|
hunks_to_reexpand.push(HoveredHunk {
|
||||||
status,
|
status,
|
||||||
multi_buffer_range,
|
multi_buffer_range,
|
||||||
diff_base_byte_range,
|
diff_base_byte_range,
|
||||||
|
@ -522,6 +677,29 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_diff_hunk(
|
||||||
|
hovered_hunk: &HoveredHunk,
|
||||||
|
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||||
|
) -> Option<DiffHunk<MultiBufferRow>> {
|
||||||
|
let buffer_id = hovered_hunk
|
||||||
|
.multi_buffer_range
|
||||||
|
.start
|
||||||
|
.buffer_id
|
||||||
|
.or_else(|| hovered_hunk.multi_buffer_range.end.buffer_id)?;
|
||||||
|
let buffer_range = hovered_hunk.multi_buffer_range.start.text_anchor
|
||||||
|
..hovered_hunk.multi_buffer_range.end.text_anchor;
|
||||||
|
let point_range = hovered_hunk
|
||||||
|
.multi_buffer_range
|
||||||
|
.to_point(&multi_buffer_snapshot);
|
||||||
|
Some(DiffHunk {
|
||||||
|
associated_range: MultiBufferRow(point_range.start.row)
|
||||||
|
..MultiBufferRow(point_range.end.row),
|
||||||
|
buffer_id,
|
||||||
|
buffer_range,
|
||||||
|
diff_base_byte_range: hovered_hunk.diff_base_byte_range.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn create_diff_base_buffer(buffer: &Model<Buffer>, cx: &mut AppContext) -> Option<Model<Buffer>> {
|
fn create_diff_base_buffer(buffer: &Model<Buffer>, cx: &mut AppContext) -> Option<Model<Buffer>> {
|
||||||
buffer
|
buffer
|
||||||
.update(cx, |buffer, _| {
|
.update(cx, |buffer, _| {
|
||||||
|
@ -555,7 +733,7 @@ fn deleted_hunk_color(cx: &AppContext) -> Hsla {
|
||||||
fn editor_with_deleted_text(
|
fn editor_with_deleted_text(
|
||||||
diff_base_buffer: Model<Buffer>,
|
diff_base_buffer: Model<Buffer>,
|
||||||
deleted_color: Hsla,
|
deleted_color: Hsla,
|
||||||
hunk: &HunkToExpand,
|
hunk: &HoveredHunk,
|
||||||
cx: &mut ViewContext<'_, Editor>,
|
cx: &mut ViewContext<'_, Editor>,
|
||||||
) -> (u8, View<Editor>) {
|
) -> (u8, View<Editor>) {
|
||||||
let parent_editor = cx.view().downgrade();
|
let parent_editor = cx.view().downgrade();
|
||||||
|
@ -613,11 +791,12 @@ fn editor_with_deleted_text(
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
let parent_editor_for_reverts = parent_editor.clone();
|
||||||
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
|
editor
|
||||||
.register_action::<RevertSelectedHunks>(move |_, cx| {
|
.register_action::<RevertSelectedHunks>(move |_, cx| {
|
||||||
parent_editor
|
parent_editor_for_reverts
|
||||||
.update(cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
let Some((buffer, original_text)) =
|
let Some((buffer, original_text)) =
|
||||||
editor.buffer().update(cx, |buffer, cx| {
|
editor.buffer().update(cx, |buffer, cx| {
|
||||||
|
@ -645,6 +824,16 @@ fn editor_with_deleted_text(
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
let hunk = hunk.clone();
|
||||||
|
editor
|
||||||
|
.register_action::<ToggleHunkDiff>(move |_, cx| {
|
||||||
|
parent_editor
|
||||||
|
.update(cx, |editor, cx| {
|
||||||
|
editor.toggle_hovered_hunk(&hunk, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,62 @@ use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
|
use gpui::{DismissEvent, Pixels, Point, Subscription, View, ViewContext};
|
||||||
use workspace::OpenInTerminal;
|
use workspace::OpenInTerminal;
|
||||||
|
|
||||||
|
pub enum MenuPosition {
|
||||||
|
/// When the editor is scrolled, the context menu stays on the exact
|
||||||
|
/// same position on the screen, never disappearing.
|
||||||
|
PinnedToScreen(Point<Pixels>),
|
||||||
|
/// When the editor is scrolled, the context menu follows the position it is associated with.
|
||||||
|
/// Disappears when the position is no longer visible.
|
||||||
|
PinnedToEditor {
|
||||||
|
source: multi_buffer::Anchor,
|
||||||
|
offset_x: Pixels,
|
||||||
|
offset_y: Pixels,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MouseContextMenu {
|
pub struct MouseContextMenu {
|
||||||
pub(crate) position: Point<Pixels>,
|
pub(crate) position: MenuPosition,
|
||||||
pub(crate) context_menu: View<ui::ContextMenu>,
|
pub(crate) context_menu: View<ui::ContextMenu>,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MouseContextMenu {
|
impl MouseContextMenu {
|
||||||
pub(crate) fn new(
|
pub(crate) fn pinned_to_editor(
|
||||||
|
editor: &mut Editor,
|
||||||
|
source: multi_buffer::Anchor,
|
||||||
|
position: Point<Pixels>,
|
||||||
|
context_menu: View<ui::ContextMenu>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let context_menu_focus = context_menu.focus_handle(cx);
|
||||||
|
cx.focus(&context_menu_focus);
|
||||||
|
|
||||||
|
let _subscription = cx.subscribe(
|
||||||
|
&context_menu,
|
||||||
|
move |editor, _, _event: &DismissEvent, cx| {
|
||||||
|
editor.mouse_context_menu.take();
|
||||||
|
if context_menu_focus.contains_focused(cx) {
|
||||||
|
editor.focus(cx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let editor_snapshot = editor.snapshot(cx);
|
||||||
|
let source_point = editor.to_pixel_point(source, &editor_snapshot, cx)?;
|
||||||
|
let offset = position - source_point;
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
position: MenuPosition::PinnedToEditor {
|
||||||
|
source,
|
||||||
|
offset_x: offset.x,
|
||||||
|
offset_y: offset.y,
|
||||||
|
},
|
||||||
|
context_menu,
|
||||||
|
_subscription,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn pinned_to_screen(
|
||||||
position: Point<Pixels>,
|
position: Point<Pixels>,
|
||||||
context_menu: View<ui::ContextMenu>,
|
context_menu: View<ui::ContextMenu>,
|
||||||
cx: &mut ViewContext<Editor>,
|
cx: &mut ViewContext<Editor>,
|
||||||
|
@ -25,16 +73,18 @@ impl MouseContextMenu {
|
||||||
let context_menu_focus = context_menu.focus_handle(cx);
|
let context_menu_focus = context_menu.focus_handle(cx);
|
||||||
cx.focus(&context_menu_focus);
|
cx.focus(&context_menu_focus);
|
||||||
|
|
||||||
let _subscription =
|
let _subscription = cx.subscribe(
|
||||||
cx.subscribe(&context_menu, move |this, _, _event: &DismissEvent, cx| {
|
&context_menu,
|
||||||
this.mouse_context_menu.take();
|
move |editor, _, _event: &DismissEvent, cx| {
|
||||||
|
editor.mouse_context_menu.take();
|
||||||
if context_menu_focus.contains_focused(cx) {
|
if context_menu_focus.contains_focused(cx) {
|
||||||
this.focus(cx);
|
editor.focus(cx);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
position,
|
position: MenuPosition::PinnedToScreen(position),
|
||||||
context_menu,
|
context_menu,
|
||||||
_subscription,
|
_subscription,
|
||||||
}
|
}
|
||||||
|
@ -71,6 +121,8 @@ pub fn deploy_context_menu(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let display_map = editor.selections.display_map(cx);
|
||||||
|
let source_anchor = display_map.display_point_to_anchor(point, text::Bias::Right);
|
||||||
let context_menu = if let Some(custom) = editor.custom_context_menu.take() {
|
let context_menu = if let Some(custom) = editor.custom_context_menu.take() {
|
||||||
let menu = custom(editor, point, cx);
|
let menu = custom(editor, point, cx);
|
||||||
editor.custom_context_menu = Some(custom);
|
editor.custom_context_menu = Some(custom);
|
||||||
|
@ -98,6 +150,7 @@ pub fn deploy_context_menu(
|
||||||
let focus = cx.focused();
|
let focus = cx.focused();
|
||||||
ui::ContextMenu::build(cx, |menu, _cx| {
|
ui::ContextMenu::build(cx, |menu, _cx| {
|
||||||
let builder = menu
|
let builder = menu
|
||||||
|
.on_blur_subscription(Subscription::new(|| {}))
|
||||||
.action("Rename Symbol", Box::new(Rename))
|
.action("Rename Symbol", Box::new(Rename))
|
||||||
.action("Go to Definition", Box::new(GoToDefinition))
|
.action("Go to Definition", Box::new(GoToDefinition))
|
||||||
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
|
.action("Go to Type Definition", Box::new(GoToTypeDefinition))
|
||||||
|
@ -128,8 +181,9 @@ pub fn deploy_context_menu(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let mouse_context_menu = MouseContextMenu::new(position, context_menu, cx);
|
|
||||||
editor.mouse_context_menu = Some(mouse_context_menu);
|
editor.mouse_context_menu =
|
||||||
|
MouseContextMenu::pinned_to_editor(editor, source_anchor, position, context_menu, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -285,6 +285,11 @@ impl ContextMenu {
|
||||||
cx.propagate()
|
cx.propagate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn on_blur_subscription(mut self, new_subscription: Subscription) -> Self {
|
||||||
|
self._on_blur_subscription = new_subscription;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextMenuItem {
|
impl ContextMenuItem {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue