Compare commits

...

1 Commits

Author SHA1 Message Date
smit
6a9fa435f0 initial idea 2025-03-17 15:29:58 +05:30
3 changed files with 307 additions and 5 deletions

View File

@@ -38,6 +38,7 @@ mod proposed_changes_editor;
mod rust_analyzer_ext;
pub mod scroll;
mod selections_collection;
mod smooth_cursor_manager;
pub mod tasks;
#[cfg(test)]
@@ -152,6 +153,7 @@ use selections_collection::{
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsLocation, SettingsStore};
use smallvec::SmallVec;
use smooth_cursor_manager::SmoothCursorManager;
use snippet::Snippet;
use std::{
any::TypeId,
@@ -760,6 +762,7 @@ pub struct Editor {
toggle_fold_multiple_buffers: Task<()>,
_scroll_cursor_center_top_bottom_task: Task<()>,
serialize_selections: Task<()>,
smooth_cursor_manager: SmoothCursorManager,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
@@ -1467,6 +1470,7 @@ impl Editor {
serialize_selections: Task::ready(()),
text_style_refinement: None,
load_diff_task: load_uncommitted_diff,
smooth_cursor_manager: SmoothCursorManager::Inactive,
};
this.tasks_update_task = Some(this.refresh_runnables(window, cx));
this._subscriptions.extend(project_subscriptions);
@@ -2030,6 +2034,7 @@ impl Editor {
local: bool,
old_cursor_position: &Anchor,
show_completions: bool,
pre_edit_pixel_points: HashMap<usize, Option<gpui::Point<Pixels>>>,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -2162,6 +2167,23 @@ impl Editor {
hide_hover(self, cx);
let mut post_edit_pixel_points = HashMap::default();
for selection in self.selections.disjoint_anchors().iter() {
let head_point =
self.to_pixel_point(selection.head(), &self.snapshot(window, cx), window);
post_edit_pixel_points.insert(selection.id, head_point);
}
if let Some(pending) = self.selections.pending_anchor() {
let head_point =
self.to_pixel_point(pending.head(), &self.snapshot(window, cx), window);
post_edit_pixel_points.insert(pending.id, head_point);
}
self.smooth_cursor_manager
.update(pre_edit_pixel_points, post_edit_pixel_points);
if old_cursor_position.to_display_point(&display_map).row()
!= new_cursor_position.to_display_point(&display_map).row()
{
@@ -2279,6 +2301,21 @@ impl Editor {
change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
) -> R {
let old_cursor_position = self.selections.newest_anchor().head();
let mut pre_edit_pixel_points = HashMap::default();
for selection in self.selections.disjoint_anchors().iter() {
let head_point =
self.to_pixel_point(selection.head(), &self.snapshot(window, cx), window);
pre_edit_pixel_points.insert(selection.id, head_point);
}
if let Some(pending) = self.selections.pending_anchor() {
let head_point =
self.to_pixel_point(pending.head(), &self.snapshot(window, cx), window);
pre_edit_pixel_points.insert(pending.id, head_point);
}
self.push_to_selection_history();
let (changed, result) = self.selections.change_with(cx, change);
@@ -2287,7 +2324,14 @@ impl Editor {
if let Some(autoscroll) = autoscroll {
self.request_autoscroll(autoscroll, cx);
}
self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
self.selections_did_change(
true,
&old_cursor_position,
request_completions,
pre_edit_pixel_points,
window,
cx,
);
if self.should_open_signature_help_automatically(
&old_cursor_position,
@@ -3100,6 +3144,20 @@ impl Editor {
let initial_buffer_versions =
jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
let mut pre_edit_pixel_points = HashMap::default();
for selection in this.selections.disjoint_anchors().iter() {
let head_point =
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
pre_edit_pixel_points.insert(selection.id, head_point);
}
if let Some(pending) = this.selections.pending_anchor() {
let head_point =
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
pre_edit_pixel_points.insert(pending.id, head_point);
}
this.buffer.update(cx, |buffer, cx| {
buffer.edit(edits, this.autoindent_mode.clone(), cx);
});
@@ -3201,6 +3259,22 @@ impl Editor {
linked_editing_ranges::refresh_linked_ranges(this, window, cx);
this.refresh_inline_completion(true, false, window, cx);
jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
let mut post_edit_pixel_points = HashMap::default();
for selection in this.selections.disjoint_anchors().iter() {
let head_point =
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
post_edit_pixel_points.insert(selection.id, head_point);
}
if let Some(pending) = this.selections.pending_anchor() {
let head_point =
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
post_edit_pixel_points.insert(pending.id, head_point);
}
this.smooth_cursor_manager
.update(pre_edit_pixel_points, post_edit_pixel_points);
});
}
@@ -3253,6 +3327,20 @@ impl Editor {
pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
self.transact(window, cx, |this, window, cx| {
let mut pre_edit_pixel_points = HashMap::default();
for selection in this.selections.disjoint_anchors().iter() {
let head_point =
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
pre_edit_pixel_points.insert(selection.id, head_point);
}
if let Some(pending) = this.selections.pending_anchor() {
let head_point =
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
pre_edit_pixel_points.insert(pending.id, head_point);
}
let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
let selections = this.selections.all::<usize>(cx);
let multi_buffer = this.buffer.read(cx);
@@ -3363,6 +3451,23 @@ impl Editor {
s.select(new_selections)
});
this.refresh_inline_completion(true, false, window, cx);
let mut post_edit_pixel_points = HashMap::default();
for selection in this.selections.disjoint_anchors().iter() {
let head_point =
this.to_pixel_point(selection.head(), &this.snapshot(window, cx), window);
post_edit_pixel_points.insert(selection.id, head_point);
}
if let Some(pending) = this.selections.pending_anchor() {
let head_point =
this.to_pixel_point(pending.head(), &this.snapshot(window, cx), window);
post_edit_pixel_points.insert(pending.id, head_point);
}
this.smooth_cursor_manager
.update(pre_edit_pixel_points, post_edit_pixel_points);
});
}
@@ -13185,7 +13290,14 @@ impl Editor {
s.clear_pending();
}
});
self.selections_did_change(false, &old_cursor_position, true, window, cx);
self.selections_did_change(
false,
&old_cursor_position,
true,
HashMap::default(),
window,
cx,
);
}
fn push_to_selection_history(&mut self) {

View File

@@ -21,9 +21,9 @@ use crate::{
EditorSettings, EditorSnapshot, EditorStyle, FocusedBlock, GoToHunk, GoToPreviousHunk,
GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, InlayHintRefreshReason,
InlineCompletion, JumpData, LineDown, LineHighlight, LineUp, OpenExcerpts, PageDown, PageUp,
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SmoothCursorManager,
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS,
CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
@@ -83,6 +83,7 @@ const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
const MIN_SCROLL_THUMB_SIZE: f32 = 25.;
struct SelectionLayout {
id: usize,
head: DisplayPoint,
cursor_shape: CursorShape,
is_newest: bool,
@@ -140,6 +141,7 @@ impl SelectionLayout {
}
Self {
id: selection.id,
head,
cursor_shape,
is_newest,
@@ -1151,12 +1153,29 @@ impl EditorElement {
let cursor_layouts = self.editor.update(cx, |editor, cx| {
let mut cursors = Vec::new();
let is_animating =
!matches!(editor.smooth_cursor_manager, SmoothCursorManager::Inactive);
let animated_selection_ids = if is_animating {
match &editor.smooth_cursor_manager {
SmoothCursorManager::Active { cursors } => {
cursors.keys().copied().collect::<HashSet<_>>()
}
_ => HashSet::default(),
}
} else {
HashSet::default()
};
let show_local_cursors = editor.show_local_cursors(window, cx);
for (player_color, selections) in selections {
for selection in selections {
let cursor_position = selection.head;
if animated_selection_ids.contains(&selection.id) {
continue;
}
let in_range = visible_display_row_range.contains(&cursor_position.row());
if (selection.is_local && !show_local_cursors)
|| !in_range
@@ -1283,6 +1302,19 @@ impl EditorElement {
}
}
if is_animating {
let animated_cursors = self.layout_animated_cursors(
editor,
content_origin,
line_height,
em_advance,
selections,
window,
cx,
);
cursors.extend(animated_cursors);
}
cursors
});
@@ -1293,6 +1325,47 @@ impl EditorElement {
cursor_layouts
}
fn layout_animated_cursors(
&self,
editor: &mut Editor,
content_origin: gpui::Point<Pixels>,
line_height: Pixels,
em_advance: Pixels,
selections: &[(PlayerColor, Vec<SelectionLayout>)],
window: &mut Window,
cx: &mut App,
) -> Vec<CursorLayout> {
let new_positions = editor.smooth_cursor_manager.animate();
if !new_positions.is_empty() {
window.request_animation_frame();
}
new_positions
.into_iter()
.map(|(id, position)| {
// todo smit: worst way to get cursor shape and player color
let (cursor_shape, player_color) = selections
.iter()
.find_map(|(player_color, sels)| {
sels.iter()
.find(|sel| sel.id == id)
.map(|sel| (sel.cursor_shape, *player_color))
})
.unwrap_or((CursorShape::Bar, editor.current_user_player_color(cx)));
let mut cursor = CursorLayout {
color: player_color.cursor,
block_width: em_advance,
origin: position,
line_height,
shape: cursor_shape,
block_text: None,
cursor_name: None,
};
cursor.layout(content_origin, None, window, cx);
cursor
})
.collect()
}
fn layout_scrollbars(
&self,
snapshot: &EditorSnapshot,

View File

@@ -0,0 +1,117 @@
use collections::HashMap;
use gpui::Pixels;
const DELTA_PERCENT_PER_FRAME: f32 = 0.01;
pub struct Cursor {
current_position: gpui::Point<Pixels>,
target_position: gpui::Point<Pixels>,
}
pub enum SmoothCursorManager {
Inactive,
Active { cursors: HashMap<usize, Cursor> },
}
impl SmoothCursorManager {
pub fn update(
&mut self,
source_positions: HashMap<usize, Option<gpui::Point<Pixels>>>,
target_positions: HashMap<usize, Option<gpui::Point<Pixels>>>,
) {
if source_positions.len() == 1 && target_positions.len() == 1 {
let old_id = source_positions.keys().next().unwrap();
let new_id = target_positions.keys().next().unwrap();
if old_id != new_id {
if let (Some(Some(old_pos)), Some(Some(new_pos))) = (
source_positions.values().next(),
target_positions.values().next(),
) {
*self = Self::Active {
cursors: HashMap::from_iter([(
*new_id,
Cursor {
current_position: *old_pos,
target_position: *new_pos,
},
)]),
};
return;
}
}
}
match self {
Self::Inactive => {
let mut cursors = HashMap::default();
for (id, target_position) in target_positions.iter() {
let Some(target_position) = target_position else {
continue;
};
let Some(Some(source_position)) = source_positions.get(id) else {
continue;
};
if source_position == target_position {
continue;
}
cursors.insert(
*id,
Cursor {
current_position: *source_position,
target_position: *target_position,
},
);
}
if !cursors.is_empty() {
*self = Self::Active { cursors };
}
}
Self::Active { cursors } => {
for (id, target_position) in target_positions.iter() {
let Some(target_position) = target_position else {
continue;
};
if let Some(cursor) = cursors.get_mut(id) {
cursor.target_position = *target_position;
}
}
}
}
}
pub fn animate(&mut self) -> HashMap<usize, gpui::Point<Pixels>> {
match self {
Self::Inactive => HashMap::default(),
Self::Active { cursors } => {
let mut new_positions = HashMap::default();
let mut completed = Vec::new();
for (id, cursor) in cursors.iter_mut() {
let dx = cursor.target_position.x - cursor.current_position.x;
let dy = cursor.target_position.y - cursor.current_position.y;
let distance = (dx.0.powi(2) + dy.0.powi(2)).sqrt();
if distance < 0.2 {
new_positions.insert(*id, cursor.target_position);
completed.push(*id);
} else {
cursor.current_position.x =
Pixels(cursor.current_position.x.0 + dx.0 * DELTA_PERCENT_PER_FRAME);
cursor.current_position.y =
Pixels(cursor.current_position.y.0 + dy.0 * DELTA_PERCENT_PER_FRAME);
new_positions.insert(*id, cursor.current_position);
}
}
for id in completed {
cursors.remove(&id);
}
if cursors.is_empty() {
*self = Self::Inactive;
}
new_positions
}
}
}
}