mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
editor: Extract git and input out of editor.rs (#56155)
cc @SomeoneToIgnore ## Summary Follow-up to https://github.com/zed-industries/zed/discussions/55352, where the conclusion was to split `editor.rs` incrementally by topic instead of all at once. This mechanically extracts two editor topics into focused sibling modules: - `crates/editor/src/input.rs` - `crates/editor/src/git.rs` The git extraction is intentionally partial for now. I left a lot of related parts because otherwise the diff was super huge (over 9K lines) in the Github, so we can move those parts later in the follow-up PRs Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Release Notes: - N/A
This commit is contained in:
parent
e78ddcac8d
commit
6aca6364fe
3 changed files with 2936 additions and 2912 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1 +1,710 @@
|
|||
pub mod blame;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Editor {
|
||||
pub fn diff_hunks_in_ranges<'a>(
|
||||
&'a self,
|
||||
ranges: &'a [Range<Anchor>],
|
||||
buffer: &'a MultiBufferSnapshot,
|
||||
) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
|
||||
ranges.iter().flat_map(move |range| {
|
||||
let end_excerpt = buffer.excerpt_containing(range.end..range.end);
|
||||
let range = range.to_point(buffer);
|
||||
let mut peek_end = range.end;
|
||||
if range.end.row < buffer.max_row().0 {
|
||||
peek_end = Point::new(range.end.row + 1, 0);
|
||||
}
|
||||
buffer
|
||||
.diff_hunks_in_range(range.start..peek_end)
|
||||
.filter(move |hunk| {
|
||||
if let Some((_, excerpt_range)) = &end_excerpt
|
||||
&& let Some(end_anchor) =
|
||||
buffer.anchor_in_excerpt(excerpt_range.context.end)
|
||||
&& let Some(hunk_end_anchor) =
|
||||
buffer.anchor_in_excerpt(hunk.excerpt_range.context.end)
|
||||
&& hunk_end_anchor.cmp(&end_anchor, buffer).is_gt()
|
||||
{
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_render_diff_hunk_controls(
|
||||
&mut self,
|
||||
render_diff_hunk_controls: RenderDiffHunkControlsFn,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.render_diff_hunk_controls = render_diff_hunk_controls;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
|
||||
&& let Some(dir) = file.abs_path(cx).parent()
|
||||
{
|
||||
return Some(dir.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
|
||||
self.active_buffer(cx).and_then(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
if let Some(project_path) = buffer.project_path(cx) {
|
||||
let project = self.project()?.read(cx);
|
||||
project.absolute_path(&project_path, cx)
|
||||
} else {
|
||||
buffer
|
||||
.file()
|
||||
.and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the project path for the editor's buffer, if any buffer is
|
||||
/// opened in the editor.
|
||||
pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
|
||||
if let Some(buffer) = self.buffer.read(cx).as_singleton() {
|
||||
buffer.read(cx).project_path(cx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn git_blame_inline_enabled(&self) -> bool {
|
||||
self.git_blame_inline_enabled
|
||||
}
|
||||
|
||||
pub fn selection_menu_enabled(&self, cx: &App) -> bool {
|
||||
self.show_selection_menu
|
||||
.unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
|
||||
}
|
||||
|
||||
pub fn toggle_selection_menu(
|
||||
&mut self,
|
||||
_: &ToggleSelectionMenu,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.show_selection_menu = self
|
||||
.show_selection_menu
|
||||
.map(|show_selections_menu| !show_selections_menu)
|
||||
.or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn blame(&self) -> Option<&Entity<GitBlame>> {
|
||||
self.blame.as_ref()
|
||||
}
|
||||
|
||||
pub fn show_git_blame_gutter(&self) -> bool {
|
||||
self.show_git_blame_gutter
|
||||
}
|
||||
|
||||
pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
|
||||
let ranges: Vec<_> = self
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
.map(|s| s.range())
|
||||
.collect();
|
||||
self.buffer
|
||||
.update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
|
||||
}
|
||||
|
||||
pub fn copy_file_name_without_extension(
|
||||
&mut self,
|
||||
_: &CopyFileNameWithoutExtension,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(file_stem) = self.active_buffer(cx).and_then(|buffer| {
|
||||
let file = buffer.read(cx).file()?;
|
||||
file.path().file_stem()
|
||||
}) {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(file_name) = self.active_buffer(cx).and_then(|buffer| {
|
||||
let file = buffer.read(cx).file()?;
|
||||
Some(file.file_name(cx))
|
||||
}) {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_git_blame(
|
||||
&mut self,
|
||||
_: &::git::Blame,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.show_git_blame_gutter = !self.show_git_blame_gutter;
|
||||
|
||||
if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
|
||||
self.start_git_blame(true, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn toggle_git_blame_inline(
|
||||
&mut self,
|
||||
_: &ToggleGitBlameInline,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.toggle_git_blame_inline_internal(true, window, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(super) fn toggle_staged_selected_diff_hunks(
|
||||
&mut self,
|
||||
_: &::git::ToggleStaged,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let ranges: Vec<_> = self
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
.iter()
|
||||
.map(|s| s.range())
|
||||
.collect();
|
||||
let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
|
||||
self.stage_or_unstage_diff_hunks(stage, ranges, cx);
|
||||
}
|
||||
|
||||
pub(super) fn stage_and_next(
|
||||
&mut self,
|
||||
_: &::git::StageAndNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.do_stage_or_unstage_and_next(true, window, cx);
|
||||
}
|
||||
|
||||
pub(super) fn unstage_and_next(
|
||||
&mut self,
|
||||
_: &::git::UnstageAndNext,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.do_stage_or_unstage_and_next(false, window, cx);
|
||||
}
|
||||
|
||||
pub(super) fn stage_or_unstage_diff_hunks(
|
||||
&mut self,
|
||||
stage: bool,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.delegate_stage_and_restore {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let hunks: Vec<_> = self.diff_hunks_in_ranges(&ranges, &snapshot).collect();
|
||||
if !hunks.is_empty() {
|
||||
cx.emit(EditorEvent::StageOrUnstageRequested { stage, hunks });
|
||||
}
|
||||
return;
|
||||
}
|
||||
let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
|
||||
cx.spawn(async move |this, cx| {
|
||||
task.await?;
|
||||
this.update(cx, |this, cx| {
|
||||
let snapshot = this.buffer.read(cx).snapshot(cx);
|
||||
let chunk_by = this
|
||||
.diff_hunks_in_ranges(&ranges, &snapshot)
|
||||
.chunk_by(|hunk| hunk.buffer_id);
|
||||
for (buffer_id, hunks) in &chunk_by {
|
||||
this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
|
||||
}
|
||||
})
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
pub(super) fn do_stage_or_unstage(
|
||||
&self,
|
||||
stage: bool,
|
||||
buffer_id: BufferId,
|
||||
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
|
||||
cx: &mut App,
|
||||
) -> Option<()> {
|
||||
let project = self.project()?;
|
||||
let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
|
||||
let diff = self.buffer.read(cx).diff_for(buffer_id)?;
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let file_exists = buffer_snapshot
|
||||
.file()
|
||||
.is_some_and(|file| file.disk_state().exists());
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.stage_or_unstage_hunks(
|
||||
stage,
|
||||
&hunks
|
||||
.map(|hunk| buffer_diff::DiffHunk {
|
||||
buffer_range: hunk.buffer_range,
|
||||
// We don't need to pass in word diffs here because they're only used for rendering and
|
||||
// this function changes internal state
|
||||
base_word_diffs: Vec::default(),
|
||||
buffer_word_diffs: Vec::default(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.start.0
|
||||
..hunk.diff_base_byte_range.end.0,
|
||||
secondary_status: hunk.status.secondary,
|
||||
range: Point::zero()..Point::zero(), // unused
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
&buffer_snapshot,
|
||||
file_exists,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let ranges = vec![Anchor::Min..Anchor::Max];
|
||||
if !buffer.all_diff_hunks_expanded()
|
||||
&& buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
|
||||
{
|
||||
buffer.collapse_diff_hunks(ranges, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
|
||||
if self.buffer.read(cx).all_diff_hunks_expanded() {
|
||||
return true;
|
||||
}
|
||||
let ranges = vec![Anchor::Min..Anchor::Max];
|
||||
self.buffer
|
||||
.read(cx)
|
||||
.has_expanded_diff_hunks_in_ranges(&ranges, cx)
|
||||
}
|
||||
|
||||
pub(super) fn toggle_diff_hunks_in_ranges(
|
||||
&mut self,
|
||||
ranges: Vec<Range<Anchor>>,
|
||||
cx: &mut Context<Editor>,
|
||||
) {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
|
||||
buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
buffer.toggle_single_diff_hunk(range, cx);
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn apply_all_diff_hunks(
|
||||
&mut self,
|
||||
_: &ApplyAllDiffHunks,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
|
||||
let buffers = self.buffer.read(cx).all_buffers();
|
||||
for branch_buffer in buffers {
|
||||
branch_buffer.update(cx, |branch_buffer, cx| {
|
||||
branch_buffer.merge_into_base(Vec::new(), cx);
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(project) = self.project.clone() {
|
||||
self.save(
|
||||
SaveOptions {
|
||||
format: true,
|
||||
force_format: false,
|
||||
autosave: false,
|
||||
},
|
||||
project,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn apply_selected_diff_hunks(
|
||||
&mut self,
|
||||
_: &ApplyDiffHunk,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.read_only(cx) {
|
||||
return;
|
||||
}
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let hunks = snapshot.hunks_for_ranges(
|
||||
self.selections
|
||||
.all(&snapshot.display_snapshot)
|
||||
.into_iter()
|
||||
.map(|selection| selection.range()),
|
||||
);
|
||||
let mut ranges_by_buffer = HashMap::default();
|
||||
self.transact(window, cx, |editor, _window, cx| {
|
||||
for hunk in hunks {
|
||||
if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
|
||||
ranges_by_buffer
|
||||
.entry(buffer.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(hunk.buffer_range.to_offset(buffer.read(cx)));
|
||||
}
|
||||
}
|
||||
|
||||
for (buffer, ranges) in ranges_by_buffer {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.merge_into_base(ranges, cx);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(project) = self.project.clone() {
|
||||
self.save(
|
||||
SaveOptions {
|
||||
format: true,
|
||||
force_format: false,
|
||||
autosave: false,
|
||||
},
|
||||
project,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
|
||||
self.active_buffer(cx)?
|
||||
.read(cx)
|
||||
.file()
|
||||
.and_then(|f| f.as_local())
|
||||
}
|
||||
|
||||
pub(super) fn reveal_in_finder(
|
||||
&mut self,
|
||||
_: &RevealInFileManager,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(path) = self.target_file_abs_path(cx) {
|
||||
if let Some(project) = self.project() {
|
||||
project.update(cx, |project, cx| project.reveal_path(&path, cx));
|
||||
} else {
|
||||
cx.reveal_path(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn copy_path(
|
||||
&mut self,
|
||||
_: &zed_actions::workspace::CopyPath,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(path) = self.target_file_abs_path(cx)
|
||||
&& let Some(path) = path.to_str()
|
||||
{
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn copy_relative_path(
|
||||
&mut self,
|
||||
_: &zed_actions::workspace::CopyRelativePath,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(path) = self.active_buffer(cx).and_then(|buffer| {
|
||||
let project = self.project()?.read(cx);
|
||||
let path = buffer.read(cx).file()?.path();
|
||||
let path = path.display(project.path_style(cx));
|
||||
Some(path)
|
||||
}) {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn go_to_active_debug_line(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
maybe!({
|
||||
let breakpoint_store = self.breakpoint_store.as_ref()?;
|
||||
|
||||
let (active_stack_frame, debug_line_pane_id) = {
|
||||
let store = breakpoint_store.read(cx);
|
||||
let active_stack_frame = store.active_position().cloned();
|
||||
let debug_line_pane_id = store.active_debug_line_pane_id();
|
||||
(active_stack_frame, debug_line_pane_id)
|
||||
};
|
||||
|
||||
let Some(active_stack_frame) = active_stack_frame else {
|
||||
self.clear_row_highlights::<ActiveDebugLine>();
|
||||
return None;
|
||||
};
|
||||
|
||||
if let Some(debug_line_pane_id) = debug_line_pane_id {
|
||||
if let Some(workspace) = self
|
||||
.workspace
|
||||
.as_ref()
|
||||
.and_then(|(workspace, _)| workspace.upgrade())
|
||||
{
|
||||
let editor_pane_id = workspace
|
||||
.read(cx)
|
||||
.pane_for_item_id(cx.entity_id())
|
||||
.map(|pane| pane.entity_id());
|
||||
|
||||
if editor_pane_id.is_some_and(|id| id != debug_line_pane_id) {
|
||||
self.clear_row_highlights::<ActiveDebugLine>();
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let position = active_stack_frame.position;
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let multibuffer_anchor = snapshot.anchor_in_excerpt(position)?;
|
||||
|
||||
self.clear_row_highlights::<ActiveDebugLine>();
|
||||
|
||||
self.go_to_line::<ActiveDebugLine>(
|
||||
multibuffer_anchor,
|
||||
Some(cx.theme().colors().editor_debugger_active_line_background),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.notify();
|
||||
|
||||
Some(())
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub(super) fn open_git_blame_commit(
|
||||
&mut self,
|
||||
_: &OpenGitBlameCommit,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.open_git_blame_commit_internal(window, cx);
|
||||
}
|
||||
|
||||
pub(super) fn start_git_blame(
|
||||
&mut self,
|
||||
user_triggered: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(project) = self.project() {
|
||||
if let Some(buffer) = self.buffer().read(cx).as_singleton()
|
||||
&& buffer.read(cx).file().is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let focused = self.focus_handle(cx).contains_focused(window, cx);
|
||||
|
||||
let project = project.clone();
|
||||
let blame = cx
|
||||
.new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
|
||||
self.blame_subscription =
|
||||
Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
|
||||
self.blame = Some(blame);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn toggle_git_blame_inline_internal(
|
||||
&mut self,
|
||||
user_triggered: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.git_blame_inline_enabled {
|
||||
self.git_blame_inline_enabled = false;
|
||||
self.show_git_blame_inline = false;
|
||||
self.show_git_blame_inline_delay_task.take();
|
||||
} else {
|
||||
self.git_blame_inline_enabled = true;
|
||||
self.start_git_blame_inline(user_triggered, window, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(super) fn start_git_blame_inline(
|
||||
&mut self,
|
||||
user_triggered: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.start_git_blame(user_triggered, window, cx);
|
||||
|
||||
if ProjectSettings::get_global(cx)
|
||||
.git
|
||||
.inline_blame_delay()
|
||||
.is_some()
|
||||
{
|
||||
self.start_inline_blame_timer(window, cx);
|
||||
} else {
|
||||
self.show_git_blame_inline = true
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn render_git_blame_gutter(&self, cx: &App) -> bool {
|
||||
!self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
|
||||
}
|
||||
|
||||
pub(super) fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
|
||||
self.show_git_blame_inline
|
||||
&& (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
|
||||
&& !self.newest_selection_head_on_empty_line(cx)
|
||||
&& self.has_blame_entries(cx)
|
||||
}
|
||||
|
||||
fn has_stageable_diff_hunks_in_ranges(
|
||||
&self,
|
||||
ranges: &[Range<Anchor>],
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
) -> bool {
|
||||
let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
|
||||
hunks.any(|hunk| hunk.status().has_secondary_hunk())
|
||||
}
|
||||
|
||||
fn save_buffers_for_ranges_if_needed(
|
||||
&mut self,
|
||||
ranges: &[Range<Anchor>],
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Task<Result<()>> {
|
||||
let multibuffer = self.buffer.read(cx);
|
||||
let snapshot = multibuffer.read(cx);
|
||||
let buffer_ids: HashSet<_> = ranges
|
||||
.iter()
|
||||
.flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
|
||||
.collect();
|
||||
drop(snapshot);
|
||||
|
||||
let mut buffers = HashSet::default();
|
||||
for buffer_id in buffer_ids {
|
||||
if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
|
||||
let buffer = buffer_entity.read(cx);
|
||||
if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
|
||||
{
|
||||
buffers.insert(buffer_entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(project) = &self.project {
|
||||
project.update(cx, |project, cx| project.save_buffers(buffers, cx))
|
||||
} else {
|
||||
Task::ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
fn do_stage_or_unstage_and_next(
|
||||
&mut self,
|
||||
stage: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
|
||||
if ranges.iter().any(|range| range.start != range.end) {
|
||||
self.stage_or_unstage_diff_hunks(stage, ranges, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
self.stage_or_unstage_diff_hunks(stage, ranges, cx);
|
||||
|
||||
let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
|
||||
let wrap_around = !all_diff_hunks_expanded;
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let position = self
|
||||
.selections
|
||||
.newest::<Point>(&snapshot.display_snapshot)
|
||||
.head();
|
||||
|
||||
self.go_to_hunk_before_or_after_position(
|
||||
&snapshot,
|
||||
position,
|
||||
Direction::Next,
|
||||
wrap_around,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn open_git_blame_commit_internal(
|
||||
&mut self,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<()> {
|
||||
let blame = self.blame.as_ref()?;
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
let cursor = self
|
||||
.selections
|
||||
.newest::<Point>(&snapshot.display_snapshot)
|
||||
.head();
|
||||
let (buffer, point) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
|
||||
let (_, blame_entry) = blame
|
||||
.update(cx, |blame, cx| {
|
||||
blame
|
||||
.blame_for_rows(
|
||||
&[RowInfo {
|
||||
buffer_id: Some(buffer.remote_id()),
|
||||
buffer_row: Some(point.row),
|
||||
..Default::default()
|
||||
}],
|
||||
cx,
|
||||
)
|
||||
.next()
|
||||
})
|
||||
.flatten()?;
|
||||
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
|
||||
let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
|
||||
let workspace = self.workspace()?.downgrade();
|
||||
renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
|
||||
None
|
||||
}
|
||||
|
||||
fn has_blame_entries(&self, cx: &App) -> bool {
|
||||
self.blame()
|
||||
.is_some_and(|blame| blame.read(cx).has_generated_entries())
|
||||
}
|
||||
|
||||
fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
|
||||
let cursor_anchor = self.selections.newest_anchor().head();
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
|
||||
|
||||
snapshot.line_len(buffer_row) == 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2221
crates/editor/src/input.rs
Normal file
2221
crates/editor/src/input.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue