Refactor editor scrolling and implement scroll commands from vim mode
This commit is contained in:
parent
1920de81d9
commit
cffb064c16
29 changed files with 1244 additions and 683 deletions
|
@ -38,22 +38,6 @@
|
||||||
],
|
],
|
||||||
"%": "vim::Matching",
|
"%": "vim::Matching",
|
||||||
"escape": "editor::Cancel",
|
"escape": "editor::Cancel",
|
||||||
"i": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"Object": {
|
|
||||||
"around": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"a": [
|
|
||||||
"vim::PushOperator",
|
|
||||||
{
|
|
||||||
"Object": {
|
|
||||||
"around": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
"0": "vim::StartOfLine", // When no number operator present, use start of line motion
|
||||||
"1": [
|
"1": [
|
||||||
"vim::Number",
|
"vim::Number",
|
||||||
|
@ -93,6 +77,28 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
//Operators
|
||||||
|
"context": "Editor && VimControl && vim_operator == none",
|
||||||
|
"bindings": {
|
||||||
|
"i": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
{
|
||||||
|
"Object": {
|
||||||
|
"around": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"a": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
{
|
||||||
|
"Object": {
|
||||||
|
"around": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == normal && vim_operator == none",
|
"context": "Editor && vim_mode == normal && vim_operator == none",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
@ -110,6 +116,12 @@
|
||||||
"vim::PushOperator",
|
"vim::PushOperator",
|
||||||
"Yank"
|
"Yank"
|
||||||
],
|
],
|
||||||
|
"z": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
{
|
||||||
|
"Namespace": "Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
"i": [
|
"i": [
|
||||||
"vim::SwitchMode",
|
"vim::SwitchMode",
|
||||||
"Insert"
|
"Insert"
|
||||||
|
@ -147,6 +159,30 @@
|
||||||
{
|
{
|
||||||
"focus": true
|
"focus": true
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"ctrl-f": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"PageDown"
|
||||||
|
],
|
||||||
|
"ctrl-b": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"PageUp"
|
||||||
|
],
|
||||||
|
"ctrl-d": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"HalfPageDown"
|
||||||
|
],
|
||||||
|
"ctrl-u": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"HalfPageUp"
|
||||||
|
],
|
||||||
|
"ctrl-e": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"LineDown"
|
||||||
|
],
|
||||||
|
"ctrl-y": [
|
||||||
|
"vim::Scroll",
|
||||||
|
"LineUp"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -188,6 +224,18 @@
|
||||||
"y": "vim::CurrentLine"
|
"y": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == z",
|
||||||
|
"bindings": {
|
||||||
|
"t": "editor::ScrollCursorTop",
|
||||||
|
"z": "editor::ScrollCursorCenter",
|
||||||
|
"b": "editor::ScrollCursorBottom",
|
||||||
|
"escape": [
|
||||||
|
"vim::SwitchMode",
|
||||||
|
"Normal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && VimObject",
|
"context": "Editor && VimObject",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -5,8 +5,9 @@ use collections::{BTreeMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
diagnostic_block_renderer,
|
diagnostic_block_renderer,
|
||||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
|
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock},
|
||||||
highlight_diagnostic_message, Autoscroll, Editor, ExcerptId, ExcerptRange, MultiBuffer,
|
highlight_diagnostic_message,
|
||||||
ToOffset,
|
scroll::autoscroll::Autoscroll,
|
||||||
|
Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
|
actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
|
||||||
|
|
|
@ -10,6 +10,7 @@ mod mouse_context_menu;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
mod multi_buffer;
|
mod multi_buffer;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
|
pub mod scroll;
|
||||||
pub mod selections_collection;
|
pub mod selections_collection;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -33,13 +34,13 @@ use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
executor,
|
executor,
|
||||||
fonts::{self, HighlightStyle, TextStyle},
|
fonts::{self, HighlightStyle, TextStyle},
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::Vector2F,
|
||||||
impl_actions, impl_internal_actions,
|
impl_actions, impl_internal_actions,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
serde_json::json,
|
serde_json::json,
|
||||||
text_layout, AnyViewHandle, AppContext, AsyncAppContext, Axis, ClipboardItem, Element,
|
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
|
||||||
ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription,
|
ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
|
||||||
Task, View, ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
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};
|
||||||
|
@ -61,11 +62,13 @@ pub use multi_buffer::{
|
||||||
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
|
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction};
|
use project::{FormatTrigger, LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||||
|
use scroll::{
|
||||||
|
autoscroll::Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide,
|
||||||
|
};
|
||||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use smol::Timer;
|
|
||||||
use snippet::Snippet;
|
use snippet::Snippet;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
|
@ -86,11 +89,9 @@ use workspace::{ItemNavHistory, Workspace, WorkspaceId};
|
||||||
use crate::git::diff_hunk_to_display;
|
use crate::git::diff_hunk_to_display;
|
||||||
|
|
||||||
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
|
||||||
const MAX_LINE_LEN: usize = 1024;
|
const MAX_LINE_LEN: usize = 1024;
|
||||||
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
|
||||||
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
const MAX_SELECTION_HISTORY_LEN: usize = 1024;
|
||||||
pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
|
|
||||||
|
|
||||||
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
pub const FORMAT_TIMEOUT: Duration = Duration::from_secs(2);
|
||||||
|
|
||||||
|
@ -100,12 +101,6 @@ pub struct SelectNext {
|
||||||
pub replace_newest: bool,
|
pub replace_newest: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub struct Scroll {
|
|
||||||
pub scroll_position: Vector2F,
|
|
||||||
pub axis: Option<Axis>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Select(pub SelectPhase);
|
pub struct Select(pub SelectPhase);
|
||||||
|
|
||||||
|
@ -258,7 +253,7 @@ impl_actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
impl_internal_actions!(editor, [Scroll, Select, Jump]);
|
impl_internal_actions!(editor, [Select, Jump]);
|
||||||
|
|
||||||
enum DocumentHighlightRead {}
|
enum DocumentHighlightRead {}
|
||||||
enum DocumentHighlightWrite {}
|
enum DocumentHighlightWrite {}
|
||||||
|
@ -270,12 +265,8 @@ pub enum Direction {
|
||||||
Next,
|
Next,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct ScrollbarAutoHide(bool);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(Editor::new_file);
|
cx.add_action(Editor::new_file);
|
||||||
cx.add_action(Editor::scroll);
|
|
||||||
cx.add_action(Editor::select);
|
cx.add_action(Editor::select);
|
||||||
cx.add_action(Editor::cancel);
|
cx.add_action(Editor::cancel);
|
||||||
cx.add_action(Editor::newline);
|
cx.add_action(Editor::newline);
|
||||||
|
@ -305,12 +296,9 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(Editor::redo);
|
cx.add_action(Editor::redo);
|
||||||
cx.add_action(Editor::move_up);
|
cx.add_action(Editor::move_up);
|
||||||
cx.add_action(Editor::move_page_up);
|
cx.add_action(Editor::move_page_up);
|
||||||
cx.add_action(Editor::page_up);
|
|
||||||
cx.add_action(Editor::move_down);
|
cx.add_action(Editor::move_down);
|
||||||
cx.add_action(Editor::move_page_down);
|
cx.add_action(Editor::move_page_down);
|
||||||
cx.add_action(Editor::page_down);
|
|
||||||
cx.add_action(Editor::next_screen);
|
cx.add_action(Editor::next_screen);
|
||||||
|
|
||||||
cx.add_action(Editor::move_left);
|
cx.add_action(Editor::move_left);
|
||||||
cx.add_action(Editor::move_right);
|
cx.add_action(Editor::move_right);
|
||||||
cx.add_action(Editor::move_to_previous_word_start);
|
cx.add_action(Editor::move_to_previous_word_start);
|
||||||
|
@ -370,6 +358,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
hover_popover::init(cx);
|
hover_popover::init(cx);
|
||||||
link_go_to_definition::init(cx);
|
link_go_to_definition::init(cx);
|
||||||
mouse_context_menu::init(cx);
|
mouse_context_menu::init(cx);
|
||||||
|
scroll::actions::init(cx);
|
||||||
|
|
||||||
workspace::register_project_item::<Editor>(cx);
|
workspace::register_project_item::<Editor>(cx);
|
||||||
workspace::register_followable_item::<Editor>(cx);
|
workspace::register_followable_item::<Editor>(cx);
|
||||||
|
@ -411,46 +400,6 @@ pub enum SelectMode {
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
|
||||||
pub enum Autoscroll {
|
|
||||||
Next,
|
|
||||||
Strategy(AutoscrollStrategy),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Autoscroll {
|
|
||||||
pub fn fit() -> Self {
|
|
||||||
Self::Strategy(AutoscrollStrategy::Fit)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn newest() -> Self {
|
|
||||||
Self::Strategy(AutoscrollStrategy::Newest)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn center() -> Self {
|
|
||||||
Self::Strategy(AutoscrollStrategy::Center)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Default)]
|
|
||||||
pub enum AutoscrollStrategy {
|
|
||||||
Fit,
|
|
||||||
Newest,
|
|
||||||
#[default]
|
|
||||||
Center,
|
|
||||||
Top,
|
|
||||||
Bottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AutoscrollStrategy {
|
|
||||||
fn next(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
AutoscrollStrategy::Center => AutoscrollStrategy::Top,
|
|
||||||
AutoscrollStrategy::Top => AutoscrollStrategy::Bottom,
|
|
||||||
_ => AutoscrollStrategy::Center,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum EditorMode {
|
pub enum EditorMode {
|
||||||
SingleLine,
|
SingleLine,
|
||||||
|
@ -477,74 +426,12 @@ type CompletionId = usize;
|
||||||
type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
|
type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
|
||||||
type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct OngoingScroll {
|
|
||||||
last_timestamp: Instant,
|
|
||||||
axis: Option<Axis>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OngoingScroll {
|
|
||||||
fn initial() -> OngoingScroll {
|
|
||||||
OngoingScroll {
|
|
||||||
last_timestamp: Instant::now() - SCROLL_EVENT_SEPARATION,
|
|
||||||
axis: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, axis: Option<Axis>) {
|
|
||||||
self.last_timestamp = Instant::now();
|
|
||||||
self.axis = axis;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
|
|
||||||
const UNLOCK_PERCENT: f32 = 1.9;
|
|
||||||
const UNLOCK_LOWER_BOUND: f32 = 6.;
|
|
||||||
let mut axis = self.axis;
|
|
||||||
|
|
||||||
let x = delta.x().abs();
|
|
||||||
let y = delta.y().abs();
|
|
||||||
let duration = Instant::now().duration_since(self.last_timestamp);
|
|
||||||
if duration > SCROLL_EVENT_SEPARATION {
|
|
||||||
//New ongoing scroll will start, determine axis
|
|
||||||
axis = if x <= y {
|
|
||||||
Some(Axis::Vertical)
|
|
||||||
} else {
|
|
||||||
Some(Axis::Horizontal)
|
|
||||||
};
|
|
||||||
} else if x.max(y) >= UNLOCK_LOWER_BOUND {
|
|
||||||
//Check if the current ongoing will need to unlock
|
|
||||||
match axis {
|
|
||||||
Some(Axis::Vertical) => {
|
|
||||||
if x > y && x >= y * UNLOCK_PERCENT {
|
|
||||||
axis = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(Axis::Horizontal) => {
|
|
||||||
if y > x && y >= x * UNLOCK_PERCENT {
|
|
||||||
axis = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match axis {
|
|
||||||
Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
|
|
||||||
Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
axis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
handle: WeakViewHandle<Self>,
|
handle: WeakViewHandle<Self>,
|
||||||
buffer: ModelHandle<MultiBuffer>,
|
buffer: ModelHandle<MultiBuffer>,
|
||||||
display_map: ModelHandle<DisplayMap>,
|
display_map: ModelHandle<DisplayMap>,
|
||||||
pub selections: SelectionsCollection,
|
pub selections: SelectionsCollection,
|
||||||
|
pub scroll_manager: ScrollManager,
|
||||||
columnar_selection_tail: Option<Anchor>,
|
columnar_selection_tail: Option<Anchor>,
|
||||||
add_selections_state: Option<AddSelectionsState>,
|
add_selections_state: Option<AddSelectionsState>,
|
||||||
select_next_state: Option<SelectNextState>,
|
select_next_state: Option<SelectNextState>,
|
||||||
|
@ -554,10 +441,6 @@ pub struct Editor {
|
||||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||||
ime_transaction: Option<TransactionId>,
|
ime_transaction: Option<TransactionId>,
|
||||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||||
ongoing_scroll: OngoingScroll,
|
|
||||||
scroll_position: Vector2F,
|
|
||||||
scroll_top_anchor: Anchor,
|
|
||||||
autoscroll_request: Option<(Autoscroll, bool)>,
|
|
||||||
soft_wrap_mode_override: Option<settings::SoftWrap>,
|
soft_wrap_mode_override: Option<settings::SoftWrap>,
|
||||||
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
|
get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
|
||||||
override_text_style: Option<Box<OverrideTextStyle>>,
|
override_text_style: Option<Box<OverrideTextStyle>>,
|
||||||
|
@ -565,10 +448,7 @@ pub struct Editor {
|
||||||
focused: bool,
|
focused: bool,
|
||||||
blink_manager: ModelHandle<BlinkManager>,
|
blink_manager: ModelHandle<BlinkManager>,
|
||||||
show_local_selections: bool,
|
show_local_selections: bool,
|
||||||
show_scrollbars: bool,
|
|
||||||
hide_scrollbar_task: Option<Task<()>>,
|
|
||||||
mode: EditorMode,
|
mode: EditorMode,
|
||||||
vertical_scroll_margin: f32,
|
|
||||||
placeholder_text: Option<Arc<str>>,
|
placeholder_text: Option<Arc<str>>,
|
||||||
highlighted_rows: Option<Range<u32>>,
|
highlighted_rows: Option<Range<u32>>,
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
|
@ -590,8 +470,6 @@ pub struct Editor {
|
||||||
leader_replica_id: Option<u16>,
|
leader_replica_id: Option<u16>,
|
||||||
hover_state: HoverState,
|
hover_state: HoverState,
|
||||||
link_go_to_definition_state: LinkGoToDefinitionState,
|
link_go_to_definition_state: LinkGoToDefinitionState,
|
||||||
visible_line_count: Option<f32>,
|
|
||||||
last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
|
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,9 +478,8 @@ pub struct EditorSnapshot {
|
||||||
pub display_snapshot: DisplaySnapshot,
|
pub display_snapshot: DisplaySnapshot,
|
||||||
pub placeholder_text: Option<Arc<str>>,
|
pub placeholder_text: Option<Arc<str>>,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
|
scroll_anchor: ScrollAnchor,
|
||||||
ongoing_scroll: OngoingScroll,
|
ongoing_scroll: OngoingScroll,
|
||||||
scroll_position: Vector2F,
|
|
||||||
scroll_top_anchor: Anchor,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -1090,12 +967,9 @@ pub struct ClipboardSelection {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NavigationData {
|
pub struct NavigationData {
|
||||||
// Matching offsets for anchor and scroll_top_anchor allows us to recreate the anchor if the buffer
|
|
||||||
// has since been closed
|
|
||||||
cursor_anchor: Anchor,
|
cursor_anchor: Anchor,
|
||||||
cursor_position: Point,
|
cursor_position: Point,
|
||||||
scroll_position: Vector2F,
|
scroll_anchor: ScrollAnchor,
|
||||||
scroll_top_anchor: Anchor,
|
|
||||||
scroll_top_row: u32,
|
scroll_top_row: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1163,9 +1037,8 @@ impl Editor {
|
||||||
display_map.set_state(&snapshot, cx);
|
display_map.set_state(&snapshot, cx);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
clone.selections.set_state(&self.selections);
|
clone.selections.clone_state(&self.selections);
|
||||||
clone.scroll_position = self.scroll_position;
|
clone.scroll_manager.clone_state(&self.scroll_manager);
|
||||||
clone.scroll_top_anchor = self.scroll_top_anchor;
|
|
||||||
clone.searchable = self.searchable;
|
clone.searchable = self.searchable;
|
||||||
clone
|
clone
|
||||||
}
|
}
|
||||||
|
@ -1200,6 +1073,7 @@ impl Editor {
|
||||||
buffer: buffer.clone(),
|
buffer: buffer.clone(),
|
||||||
display_map: display_map.clone(),
|
display_map: display_map.clone(),
|
||||||
selections,
|
selections,
|
||||||
|
scroll_manager: ScrollManager::new(),
|
||||||
columnar_selection_tail: None,
|
columnar_selection_tail: None,
|
||||||
add_selections_state: None,
|
add_selections_state: None,
|
||||||
select_next_state: None,
|
select_next_state: None,
|
||||||
|
@ -1212,17 +1086,10 @@ impl Editor {
|
||||||
soft_wrap_mode_override: None,
|
soft_wrap_mode_override: None,
|
||||||
get_field_editor_theme,
|
get_field_editor_theme,
|
||||||
project,
|
project,
|
||||||
ongoing_scroll: OngoingScroll::initial(),
|
|
||||||
scroll_position: Vector2F::zero(),
|
|
||||||
scroll_top_anchor: Anchor::min(),
|
|
||||||
autoscroll_request: None,
|
|
||||||
focused: false,
|
focused: false,
|
||||||
blink_manager: blink_manager.clone(),
|
blink_manager: blink_manager.clone(),
|
||||||
show_local_selections: true,
|
show_local_selections: true,
|
||||||
show_scrollbars: true,
|
|
||||||
hide_scrollbar_task: None,
|
|
||||||
mode,
|
mode,
|
||||||
vertical_scroll_margin: 3.0,
|
|
||||||
placeholder_text: None,
|
placeholder_text: None,
|
||||||
highlighted_rows: None,
|
highlighted_rows: None,
|
||||||
background_highlights: Default::default(),
|
background_highlights: Default::default(),
|
||||||
|
@ -1244,8 +1111,6 @@ impl Editor {
|
||||||
leader_replica_id: None,
|
leader_replica_id: None,
|
||||||
hover_state: Default::default(),
|
hover_state: Default::default(),
|
||||||
link_go_to_definition_state: Default::default(),
|
link_go_to_definition_state: Default::default(),
|
||||||
visible_line_count: None,
|
|
||||||
last_autoscroll: None,
|
|
||||||
_subscriptions: vec![
|
_subscriptions: vec![
|
||||||
cx.observe(&buffer, Self::on_buffer_changed),
|
cx.observe(&buffer, Self::on_buffer_changed),
|
||||||
cx.subscribe(&buffer, Self::on_buffer_event),
|
cx.subscribe(&buffer, Self::on_buffer_event),
|
||||||
|
@ -1254,7 +1119,7 @@ impl Editor {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
this.end_selection(cx);
|
this.end_selection(cx);
|
||||||
this.make_scrollbar_visible(cx);
|
this.scroll_manager.show_scrollbar(cx);
|
||||||
|
|
||||||
let editor_created_event = EditorCreated(cx.handle());
|
let editor_created_event = EditorCreated(cx.handle());
|
||||||
cx.emit_global(editor_created_event);
|
cx.emit_global(editor_created_event);
|
||||||
|
@ -1307,9 +1172,8 @@ impl Editor {
|
||||||
EditorSnapshot {
|
EditorSnapshot {
|
||||||
mode: self.mode,
|
mode: self.mode,
|
||||||
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
|
||||||
ongoing_scroll: self.ongoing_scroll,
|
scroll_anchor: self.scroll_manager.anchor(),
|
||||||
scroll_position: self.scroll_position,
|
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
|
||||||
scroll_top_anchor: self.scroll_top_anchor,
|
|
||||||
placeholder_text: self.placeholder_text.clone(),
|
placeholder_text: self.placeholder_text.clone(),
|
||||||
is_focused: self
|
is_focused: self
|
||||||
.handle
|
.handle
|
||||||
|
@ -1348,64 +1212,6 @@ impl Editor {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
|
|
||||||
self.vertical_scroll_margin = margin_rows as f32;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
|
|
||||||
self.set_scroll_position_internal(scroll_position, true, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_scroll_position_internal(
|
|
||||||
&mut self,
|
|
||||||
scroll_position: Vector2F,
|
|
||||||
local: bool,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
|
||||||
|
|
||||||
if scroll_position.y() <= 0. {
|
|
||||||
self.scroll_top_anchor = Anchor::min();
|
|
||||||
self.scroll_position = scroll_position.max(vec2f(0., 0.));
|
|
||||||
} else {
|
|
||||||
let scroll_top_buffer_offset =
|
|
||||||
DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right);
|
|
||||||
let anchor = map
|
|
||||||
.buffer_snapshot
|
|
||||||
.anchor_at(scroll_top_buffer_offset, Bias::Right);
|
|
||||||
self.scroll_position = vec2f(
|
|
||||||
scroll_position.x(),
|
|
||||||
scroll_position.y() - anchor.to_display_point(&map).row() as f32,
|
|
||||||
);
|
|
||||||
self.scroll_top_anchor = anchor;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.make_scrollbar_visible(cx);
|
|
||||||
self.autoscroll_request.take();
|
|
||||||
hide_hover(self, cx);
|
|
||||||
|
|
||||||
cx.emit(Event::ScrollPositionChanged { local });
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_visible_line_count(&mut self, lines: f32) {
|
|
||||||
self.visible_line_count = Some(lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_scroll_top_anchor(
|
|
||||||
&mut self,
|
|
||||||
anchor: Anchor,
|
|
||||||
position: Vector2F,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) {
|
|
||||||
self.scroll_top_anchor = anchor;
|
|
||||||
self.scroll_position = position;
|
|
||||||
self.make_scrollbar_visible(cx);
|
|
||||||
cx.emit(Event::ScrollPositionChanged { local: false });
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
|
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
|
||||||
self.cursor_shape = cursor_shape;
|
self.cursor_shape = cursor_shape;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -1431,199 +1237,6 @@ impl Editor {
|
||||||
self.input_enabled = input_enabled;
|
self.input_enabled = input_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
|
||||||
compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
|
||||||
if max < self.scroll_position.x() {
|
|
||||||
self.scroll_position.set_x(max);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn autoscroll_vertically(
|
|
||||||
&mut self,
|
|
||||||
viewport_height: f32,
|
|
||||||
line_height: f32,
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> bool {
|
|
||||||
let visible_lines = viewport_height / line_height;
|
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
|
||||||
let mut scroll_position =
|
|
||||||
compute_scroll_position(&display_map, self.scroll_position, &self.scroll_top_anchor);
|
|
||||||
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
|
||||||
(display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
|
|
||||||
} else {
|
|
||||||
display_map.max_point().row() as f32
|
|
||||||
};
|
|
||||||
if scroll_position.y() > max_scroll_top {
|
|
||||||
scroll_position.set_y(max_scroll_top);
|
|
||||||
self.set_scroll_position(scroll_position, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (autoscroll, local) = if let Some(autoscroll) = self.autoscroll_request.take() {
|
|
||||||
autoscroll
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let first_cursor_top;
|
|
||||||
let last_cursor_bottom;
|
|
||||||
if let Some(highlighted_rows) = &self.highlighted_rows {
|
|
||||||
first_cursor_top = highlighted_rows.start as f32;
|
|
||||||
last_cursor_bottom = first_cursor_top + 1.;
|
|
||||||
} else if autoscroll == Autoscroll::newest() {
|
|
||||||
let newest_selection = self.selections.newest::<Point>(cx);
|
|
||||||
first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32;
|
|
||||||
last_cursor_bottom = first_cursor_top + 1.;
|
|
||||||
} else {
|
|
||||||
let selections = self.selections.all::<Point>(cx);
|
|
||||||
first_cursor_top = selections
|
|
||||||
.first()
|
|
||||||
.unwrap()
|
|
||||||
.head()
|
|
||||||
.to_display_point(&display_map)
|
|
||||||
.row() as f32;
|
|
||||||
last_cursor_bottom = selections
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.head()
|
|
||||||
.to_display_point(&display_map)
|
|
||||||
.row() as f32
|
|
||||||
+ 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
|
||||||
0.
|
|
||||||
} else {
|
|
||||||
((visible_lines - (last_cursor_bottom - first_cursor_top)) / 2.0).floor()
|
|
||||||
};
|
|
||||||
if margin < 0.0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let strategy = match autoscroll {
|
|
||||||
Autoscroll::Strategy(strategy) => strategy,
|
|
||||||
Autoscroll::Next => {
|
|
||||||
let last_autoscroll = &self.last_autoscroll;
|
|
||||||
if let Some(last_autoscroll) = last_autoscroll {
|
|
||||||
if self.scroll_position == last_autoscroll.0
|
|
||||||
&& first_cursor_top == last_autoscroll.1
|
|
||||||
&& last_cursor_bottom == last_autoscroll.2
|
|
||||||
{
|
|
||||||
last_autoscroll.3.next()
|
|
||||||
} else {
|
|
||||||
AutoscrollStrategy::default()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AutoscrollStrategy::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match strategy {
|
|
||||||
AutoscrollStrategy::Fit | AutoscrollStrategy::Newest => {
|
|
||||||
let margin = margin.min(self.vertical_scroll_margin);
|
|
||||||
let target_top = (first_cursor_top - margin).max(0.0);
|
|
||||||
let target_bottom = last_cursor_bottom + margin;
|
|
||||||
let start_row = scroll_position.y();
|
|
||||||
let end_row = start_row + visible_lines;
|
|
||||||
|
|
||||||
if target_top < start_row {
|
|
||||||
scroll_position.set_y(target_top);
|
|
||||||
self.set_scroll_position_internal(scroll_position, local, cx);
|
|
||||||
} else if target_bottom >= end_row {
|
|
||||||
scroll_position.set_y(target_bottom - visible_lines);
|
|
||||||
self.set_scroll_position_internal(scroll_position, local, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AutoscrollStrategy::Center => {
|
|
||||||
scroll_position.set_y((first_cursor_top - margin).max(0.0));
|
|
||||||
self.set_scroll_position_internal(scroll_position, local, cx);
|
|
||||||
}
|
|
||||||
AutoscrollStrategy::Top => {
|
|
||||||
scroll_position.set_y((first_cursor_top).max(0.0));
|
|
||||||
self.set_scroll_position_internal(scroll_position, local, cx);
|
|
||||||
}
|
|
||||||
AutoscrollStrategy::Bottom => {
|
|
||||||
scroll_position.set_y((last_cursor_bottom - visible_lines).max(0.0));
|
|
||||||
self.set_scroll_position_internal(scroll_position, local, cx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.last_autoscroll = Some((
|
|
||||||
self.scroll_position,
|
|
||||||
first_cursor_top,
|
|
||||||
last_cursor_bottom,
|
|
||||||
strategy,
|
|
||||||
));
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn autoscroll_horizontally(
|
|
||||||
&mut self,
|
|
||||||
start_row: u32,
|
|
||||||
viewport_width: f32,
|
|
||||||
scroll_width: f32,
|
|
||||||
max_glyph_width: f32,
|
|
||||||
layouts: &[text_layout::Line],
|
|
||||||
cx: &mut ViewContext<Self>,
|
|
||||||
) -> bool {
|
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
|
||||||
let selections = self.selections.all::<Point>(cx);
|
|
||||||
|
|
||||||
let mut target_left;
|
|
||||||
let mut target_right;
|
|
||||||
|
|
||||||
if self.highlighted_rows.is_some() {
|
|
||||||
target_left = 0.0_f32;
|
|
||||||
target_right = 0.0_f32;
|
|
||||||
} else {
|
|
||||||
target_left = std::f32::INFINITY;
|
|
||||||
target_right = 0.0_f32;
|
|
||||||
for selection in selections {
|
|
||||||
let head = selection.head().to_display_point(&display_map);
|
|
||||||
if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 {
|
|
||||||
let start_column = head.column().saturating_sub(3);
|
|
||||||
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
|
|
||||||
target_left = target_left.min(
|
|
||||||
layouts[(head.row() - start_row) as usize]
|
|
||||||
.x_for_index(start_column as usize),
|
|
||||||
);
|
|
||||||
target_right = target_right.max(
|
|
||||||
layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize)
|
|
||||||
+ max_glyph_width,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target_right = target_right.min(scroll_width);
|
|
||||||
|
|
||||||
if target_right - target_left > viewport_width {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scroll_left = self.scroll_position.x() * max_glyph_width;
|
|
||||||
let scroll_right = scroll_left + viewport_width;
|
|
||||||
|
|
||||||
if target_left < scroll_left {
|
|
||||||
self.scroll_position.set_x(target_left / max_glyph_width);
|
|
||||||
true
|
|
||||||
} else if target_right > scroll_right {
|
|
||||||
self.scroll_position
|
|
||||||
.set_x((target_right - viewport_width) / max_glyph_width);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selections_did_change(
|
fn selections_did_change(
|
||||||
&mut self,
|
&mut self,
|
||||||
local: bool,
|
local: bool,
|
||||||
|
@ -1746,11 +1359,6 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll(&mut self, action: &Scroll, cx: &mut ViewContext<Self>) {
|
|
||||||
self.ongoing_scroll.update(action.axis);
|
|
||||||
self.set_scroll_position(action.scroll_position, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
|
fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
|
||||||
self.hide_context_menu(cx);
|
self.hide_context_menu(cx);
|
||||||
|
|
||||||
|
@ -4073,23 +3681,6 @@ impl Editor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) {
|
|
||||||
if self.take_rename(true, cx).is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = self.context_menu.as_mut() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(self.mode, EditorMode::SingleLine) {
|
|
||||||
cx.propagate_action();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.request_autoscroll(Autoscroll::Next, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
|
||||||
if self.take_rename(true, cx).is_some() {
|
if self.take_rename(true, cx).is_some() {
|
||||||
return;
|
return;
|
||||||
|
@ -4118,26 +3709,18 @@ impl Editor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
|
pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||||
if self.take_rename(true, cx).is_some() {
|
self.take_rename(true, cx)?;
|
||||||
return;
|
if self.context_menu.as_mut()?.select_first(cx) {
|
||||||
}
|
return None;
|
||||||
|
|
||||||
if let Some(context_menu) = self.context_menu.as_mut() {
|
|
||||||
if context_menu.select_first(cx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(self.mode, EditorMode::SingleLine) {
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
cx.propagate_action();
|
cx.propagate_action();
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row_count = match self.visible_line_count {
|
let row_count = self.visible_line_count()? as u32 - 1;
|
||||||
Some(row_count) => row_count as u32 - 1,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let autoscroll = if action.center_cursor {
|
let autoscroll = if action.center_cursor {
|
||||||
Autoscroll::center()
|
Autoscroll::center()
|
||||||
|
@ -4156,32 +3739,8 @@ impl Editor {
|
||||||
selection.collapse_to(cursor, goal);
|
selection.collapse_to(cursor, goal);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page_up(&mut self, _: &PageUp, cx: &mut ViewContext<Self>) {
|
Some(())
|
||||||
if self.take_rename(true, cx).is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(context_menu) = self.context_menu.as_mut() {
|
|
||||||
if context_menu.select_first(cx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(self.mode, EditorMode::SingleLine) {
|
|
||||||
cx.propagate_action();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lines = match self.visible_line_count {
|
|
||||||
Some(lines) => lines,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cur_position = self.scroll_position(cx);
|
|
||||||
let new_pos = cur_position - vec2f(0., lines + 1.);
|
|
||||||
self.set_scroll_position(new_pos, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
|
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -4216,26 +3775,25 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
|
pub fn move_page_down(
|
||||||
|
&mut self,
|
||||||
|
action: &MovePageDown,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<()> {
|
||||||
if self.take_rename(true, cx).is_some() {
|
if self.take_rename(true, cx).is_some() {
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(context_menu) = self.context_menu.as_mut() {
|
if self.context_menu.as_mut()?.select_last(cx) {
|
||||||
if context_menu.select_last(cx) {
|
return None;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(self.mode, EditorMode::SingleLine) {
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
cx.propagate_action();
|
cx.propagate_action();
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let row_count = match self.visible_line_count {
|
let row_count = self.visible_line_count()? as u32 - 1;
|
||||||
Some(row_count) => row_count as u32 - 1,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let autoscroll = if action.center_cursor {
|
let autoscroll = if action.center_cursor {
|
||||||
Autoscroll::center()
|
Autoscroll::center()
|
||||||
|
@ -4254,32 +3812,8 @@ impl Editor {
|
||||||
selection.collapse_to(cursor, goal);
|
selection.collapse_to(cursor, goal);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
pub fn page_down(&mut self, _: &PageDown, cx: &mut ViewContext<Self>) {
|
Some(())
|
||||||
if self.take_rename(true, cx).is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(context_menu) = self.context_menu.as_mut() {
|
|
||||||
if context_menu.select_last(cx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(self.mode, EditorMode::SingleLine) {
|
|
||||||
cx.propagate_action();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let lines = match self.visible_line_count {
|
|
||||||
Some(lines) => lines,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let cur_position = self.scroll_position(cx);
|
|
||||||
let new_pos = cur_position + vec2f(0., lines - 1.);
|
|
||||||
self.set_scroll_position(new_pos, cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
|
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -4602,18 +4136,19 @@ impl Editor {
|
||||||
|
|
||||||
fn push_to_nav_history(
|
fn push_to_nav_history(
|
||||||
&self,
|
&self,
|
||||||
position: Anchor,
|
cursor_anchor: Anchor,
|
||||||
new_position: Option<Point>,
|
new_position: Option<Point>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(nav_history) = &self.nav_history {
|
if let Some(nav_history) = &self.nav_history {
|
||||||
let buffer = self.buffer.read(cx).read(cx);
|
let buffer = self.buffer.read(cx).read(cx);
|
||||||
let point = position.to_point(&buffer);
|
let cursor_position = cursor_anchor.to_point(&buffer);
|
||||||
let scroll_top_row = self.scroll_top_anchor.to_point(&buffer).row;
|
let scroll_state = self.scroll_manager.anchor();
|
||||||
|
let scroll_top_row = scroll_state.top_row(&buffer);
|
||||||
drop(buffer);
|
drop(buffer);
|
||||||
|
|
||||||
if let Some(new_position) = new_position {
|
if let Some(new_position) = new_position {
|
||||||
let row_delta = (new_position.row as i64 - point.row as i64).abs();
|
let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
|
||||||
if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
|
if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4621,10 +4156,9 @@ impl Editor {
|
||||||
|
|
||||||
nav_history.push(
|
nav_history.push(
|
||||||
Some(NavigationData {
|
Some(NavigationData {
|
||||||
cursor_anchor: position,
|
cursor_anchor,
|
||||||
cursor_position: point,
|
cursor_position,
|
||||||
scroll_position: self.scroll_position,
|
scroll_anchor: scroll_state,
|
||||||
scroll_top_anchor: self.scroll_top_anchor,
|
|
||||||
scroll_top_row,
|
scroll_top_row,
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
|
@ -5922,16 +5456,6 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
|
|
||||||
self.autoscroll_request = Some((autoscroll, true));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_autoscroll_remotely(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
|
|
||||||
self.autoscroll_request = Some((autoscroll, false));
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transact(
|
pub fn transact(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -6340,31 +5864,6 @@ impl Editor {
|
||||||
self.blink_manager.read(cx).visible() && self.focused
|
self.blink_manager.read(cx).visible() && self.focused
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_scrollbars(&self) -> bool {
|
|
||||||
self.show_scrollbars
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_scrollbar_visible(&mut self, cx: &mut ViewContext<Self>) {
|
|
||||||
if !self.show_scrollbars {
|
|
||||||
self.show_scrollbars = true;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
if cx.default_global::<ScrollbarAutoHide>().0 {
|
|
||||||
self.hide_scrollbar_task = Some(cx.spawn_weak(|this, mut cx| async move {
|
|
||||||
Timer::after(SCROLLBAR_SHOW_INTERVAL).await;
|
|
||||||
if let Some(this) = this.upgrade(&cx) {
|
|
||||||
this.update(&mut cx, |this, cx| {
|
|
||||||
this.show_scrollbars = false;
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
self.hide_scrollbar_task = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_buffer_changed(&mut self, _: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Self>) {
|
fn on_buffer_changed(&mut self, _: ModelHandle<MultiBuffer>, cx: &mut ViewContext<Self>) {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -6561,11 +6060,7 @@ impl EditorSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn scroll_position(&self) -> Vector2F {
|
pub fn scroll_position(&self) -> Vector2F {
|
||||||
compute_scroll_position(
|
self.scroll_anchor.scroll_position(&self.display_snapshot)
|
||||||
&self.display_snapshot,
|
|
||||||
self.scroll_position,
|
|
||||||
&self.scroll_top_anchor,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6577,20 +6072,6 @@ impl Deref for EditorSnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_scroll_position(
|
|
||||||
snapshot: &DisplaySnapshot,
|
|
||||||
mut scroll_position: Vector2F,
|
|
||||||
scroll_top_anchor: &Anchor,
|
|
||||||
) -> Vector2F {
|
|
||||||
if *scroll_top_anchor != Anchor::min() {
|
|
||||||
let scroll_top = scroll_top_anchor.to_display_point(snapshot).row() as f32;
|
|
||||||
scroll_position.set_y(scroll_top + scroll_position.y());
|
|
||||||
} else {
|
|
||||||
scroll_position.set_y(0.);
|
|
||||||
}
|
|
||||||
scroll_position
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
BufferEdited,
|
BufferEdited,
|
||||||
|
@ -6603,7 +6084,6 @@ pub enum Event {
|
||||||
SelectionsChanged { local: bool },
|
SelectionsChanged { local: bool },
|
||||||
ScrollPositionChanged { local: bool },
|
ScrollPositionChanged { local: bool },
|
||||||
Closed,
|
Closed,
|
||||||
IgnoredInput,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditorFocused(pub ViewHandle<Editor>);
|
pub struct EditorFocused(pub ViewHandle<Editor>);
|
||||||
|
@ -6789,7 +6269,6 @@ impl View for Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if !self.input_enabled {
|
if !self.input_enabled {
|
||||||
cx.emit(Event::IgnoredInput);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6826,7 +6305,6 @@ impl View for Editor {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
if !self.input_enabled {
|
if !self.input_enabled {
|
||||||
cx.emit(Event::IgnoredInput);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::test::{
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
executor::Deterministic,
|
executor::Deterministic,
|
||||||
geometry::rect::RectF,
|
geometry::{rect::RectF, vector::vec2f},
|
||||||
platform::{WindowBounds, WindowOptions},
|
platform::{WindowBounds, WindowOptions},
|
||||||
};
|
};
|
||||||
use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
|
use language::{FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
|
||||||
|
@ -544,31 +544,30 @@ fn test_navigation_history(cx: &mut gpui::MutableAppContext) {
|
||||||
|
|
||||||
// Set scroll position to check later
|
// Set scroll position to check later
|
||||||
editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
|
editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
|
||||||
let original_scroll_position = editor.scroll_position;
|
let original_scroll_position = editor.scroll_manager.anchor();
|
||||||
let original_scroll_top_anchor = editor.scroll_top_anchor;
|
|
||||||
|
|
||||||
// Jump to the end of the document and adjust scroll
|
// Jump to the end of the document and adjust scroll
|
||||||
editor.move_to_end(&MoveToEnd, cx);
|
editor.move_to_end(&MoveToEnd, cx);
|
||||||
editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
|
editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
|
||||||
assert_ne!(editor.scroll_position, original_scroll_position);
|
assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
|
||||||
assert_ne!(editor.scroll_top_anchor, original_scroll_top_anchor);
|
|
||||||
|
|
||||||
let nav_entry = pop_history(&mut editor, cx).unwrap();
|
let nav_entry = pop_history(&mut editor, cx).unwrap();
|
||||||
editor.navigate(nav_entry.data.unwrap(), cx);
|
editor.navigate(nav_entry.data.unwrap(), cx);
|
||||||
assert_eq!(editor.scroll_position, original_scroll_position);
|
assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
|
||||||
assert_eq!(editor.scroll_top_anchor, original_scroll_top_anchor);
|
|
||||||
|
|
||||||
// Ensure we don't panic when navigation data contains invalid anchors *and* points.
|
// Ensure we don't panic when navigation data contains invalid anchors *and* points.
|
||||||
let mut invalid_anchor = editor.scroll_top_anchor;
|
let mut invalid_anchor = editor.scroll_manager.anchor().top_anchor;
|
||||||
invalid_anchor.text_anchor.buffer_id = Some(999);
|
invalid_anchor.text_anchor.buffer_id = Some(999);
|
||||||
let invalid_point = Point::new(9999, 0);
|
let invalid_point = Point::new(9999, 0);
|
||||||
editor.navigate(
|
editor.navigate(
|
||||||
Box::new(NavigationData {
|
Box::new(NavigationData {
|
||||||
cursor_anchor: invalid_anchor,
|
cursor_anchor: invalid_anchor,
|
||||||
cursor_position: invalid_point,
|
cursor_position: invalid_point,
|
||||||
scroll_top_anchor: invalid_anchor,
|
scroll_anchor: ScrollAnchor {
|
||||||
|
top_anchor: invalid_anchor,
|
||||||
|
offset: Default::default(),
|
||||||
|
},
|
||||||
scroll_top_row: invalid_point.row,
|
scroll_top_row: invalid_point.row,
|
||||||
scroll_position: Default::default(),
|
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -5034,7 +5033,7 @@ fn test_following(cx: &mut gpui::MutableAppContext) {
|
||||||
.apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
|
.apply_update_proto(pending_update.borrow_mut().take().unwrap(), cx)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(follower.scroll_position(cx), initial_scroll_position);
|
assert_eq!(follower.scroll_position(cx), initial_scroll_position);
|
||||||
assert!(follower.autoscroll_request.is_some());
|
assert!(follower.scroll_manager.has_autoscroll_request());
|
||||||
});
|
});
|
||||||
assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
|
assert_eq!(follower.read(cx).selections.ranges(cx), vec![0..0]);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{
|
use super::{
|
||||||
display_map::{BlockContext, ToDisplayPoint},
|
display_map::{BlockContext, ToDisplayPoint},
|
||||||
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Scroll, Select, SelectPhase,
|
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Select, SelectPhase, SoftWrap,
|
||||||
SoftWrap, ToPoint, MAX_LINE_LEN,
|
ToPoint, MAX_LINE_LEN,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
|
display_map::{BlockStyle, DisplaySnapshot, TransformBlock},
|
||||||
|
@ -13,6 +13,7 @@ use crate::{
|
||||||
GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
|
GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
|
||||||
},
|
},
|
||||||
mouse_context_menu::DeployMouseContextMenu,
|
mouse_context_menu::DeployMouseContextMenu,
|
||||||
|
scroll::actions::Scroll,
|
||||||
EditorStyle,
|
EditorStyle,
|
||||||
};
|
};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
|
@ -955,7 +956,7 @@ impl EditorElement {
|
||||||
move |_, cx| {
|
move |_, cx| {
|
||||||
if let Some(view) = view.upgrade(cx.deref_mut()) {
|
if let Some(view) = view.upgrade(cx.deref_mut()) {
|
||||||
view.update(cx.deref_mut(), |view, cx| {
|
view.update(cx.deref_mut(), |view, cx| {
|
||||||
view.make_scrollbar_visible(cx);
|
view.scroll_manager.show_scrollbar(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -977,7 +978,7 @@ impl EditorElement {
|
||||||
position.set_y(top_row as f32);
|
position.set_y(top_row as f32);
|
||||||
view.set_scroll_position(position, cx);
|
view.set_scroll_position(position, cx);
|
||||||
} else {
|
} else {
|
||||||
view.make_scrollbar_visible(cx);
|
view.scroll_manager.show_scrollbar(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1298,7 +1299,7 @@ impl EditorElement {
|
||||||
};
|
};
|
||||||
|
|
||||||
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
|
||||||
let scroll_x = snapshot.scroll_position.x();
|
let scroll_x = snapshot.scroll_anchor.offset.x();
|
||||||
let (fixed_blocks, non_fixed_blocks) = snapshot
|
let (fixed_blocks, non_fixed_blocks) = snapshot
|
||||||
.blocks_in_range(rows.clone())
|
.blocks_in_range(rows.clone())
|
||||||
.partition::<Vec<_>, _>(|(_, block)| match block {
|
.partition::<Vec<_>, _>(|(_, block)| match block {
|
||||||
|
@ -1670,7 +1671,7 @@ impl Element for EditorElement {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
show_scrollbars = view.show_scrollbars();
|
show_scrollbars = view.scroll_manager.scrollbars_visible();
|
||||||
include_root = view
|
include_root = view
|
||||||
.project
|
.project
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -1725,7 +1726,7 @@ impl Element for EditorElement {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.update_view(cx.app, |view, cx| {
|
self.update_view(cx.app, |view, cx| {
|
||||||
let clamped = view.clamp_scroll_left(scroll_max.x());
|
let clamped = view.scroll_manager.clamp_scroll_left(scroll_max.x());
|
||||||
|
|
||||||
let autoscrolled = if autoscroll_horizontally {
|
let autoscrolled = if autoscroll_horizontally {
|
||||||
view.autoscroll_horizontally(
|
view.autoscroll_horizontally(
|
||||||
|
|
|
@ -26,8 +26,9 @@ use workspace::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
|
display_map::ToDisplayPoint, link_go_to_definition::hide_link_definition,
|
||||||
movement::surrounding_word, persistence::DB, Anchor, Autoscroll, Editor, Event, ExcerptId,
|
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
|
||||||
MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _, FORMAT_TIMEOUT,
|
Event, ExcerptId, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
|
||||||
|
FORMAT_TIMEOUT,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const MAX_TAB_TITLE_LEN: usize = 24;
|
pub const MAX_TAB_TITLE_LEN: usize = 24;
|
||||||
|
@ -87,14 +88,16 @@ impl FollowableItem for Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(anchor) = state.scroll_top_anchor {
|
if let Some(anchor) = state.scroll_top_anchor {
|
||||||
editor.set_scroll_top_anchor(
|
editor.set_scroll_anchor(
|
||||||
Anchor {
|
ScrollAnchor {
|
||||||
buffer_id: Some(state.buffer_id as usize),
|
top_anchor: Anchor {
|
||||||
excerpt_id,
|
buffer_id: Some(state.buffer_id as usize),
|
||||||
text_anchor: language::proto::deserialize_anchor(anchor)
|
excerpt_id,
|
||||||
.ok_or_else(|| anyhow!("invalid scroll top"))?,
|
text_anchor: language::proto::deserialize_anchor(anchor)
|
||||||
|
.ok_or_else(|| anyhow!("invalid scroll top"))?,
|
||||||
|
},
|
||||||
|
offset: vec2f(state.scroll_x, state.scroll_y),
|
||||||
},
|
},
|
||||||
vec2f(state.scroll_x, state.scroll_y),
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -132,13 +135,14 @@ impl FollowableItem for Editor {
|
||||||
|
|
||||||
fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
|
fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
|
||||||
let buffer_id = self.buffer.read(cx).as_singleton()?.read(cx).remote_id();
|
let buffer_id = self.buffer.read(cx).as_singleton()?.read(cx).remote_id();
|
||||||
|
let scroll_anchor = self.scroll_manager.anchor();
|
||||||
Some(proto::view::Variant::Editor(proto::view::Editor {
|
Some(proto::view::Variant::Editor(proto::view::Editor {
|
||||||
buffer_id,
|
buffer_id,
|
||||||
scroll_top_anchor: Some(language::proto::serialize_anchor(
|
scroll_top_anchor: Some(language::proto::serialize_anchor(
|
||||||
&self.scroll_top_anchor.text_anchor,
|
&scroll_anchor.top_anchor.text_anchor,
|
||||||
)),
|
)),
|
||||||
scroll_x: self.scroll_position.x(),
|
scroll_x: scroll_anchor.offset.x(),
|
||||||
scroll_y: self.scroll_position.y(),
|
scroll_y: scroll_anchor.offset.y(),
|
||||||
selections: self
|
selections: self
|
||||||
.selections
|
.selections
|
||||||
.disjoint_anchors()
|
.disjoint_anchors()
|
||||||
|
@ -160,11 +164,12 @@ impl FollowableItem for Editor {
|
||||||
match update {
|
match update {
|
||||||
proto::update_view::Variant::Editor(update) => match event {
|
proto::update_view::Variant::Editor(update) => match event {
|
||||||
Event::ScrollPositionChanged { .. } => {
|
Event::ScrollPositionChanged { .. } => {
|
||||||
|
let scroll_anchor = self.scroll_manager.anchor();
|
||||||
update.scroll_top_anchor = Some(language::proto::serialize_anchor(
|
update.scroll_top_anchor = Some(language::proto::serialize_anchor(
|
||||||
&self.scroll_top_anchor.text_anchor,
|
&scroll_anchor.top_anchor.text_anchor,
|
||||||
));
|
));
|
||||||
update.scroll_x = self.scroll_position.x();
|
update.scroll_x = scroll_anchor.offset.x();
|
||||||
update.scroll_y = self.scroll_position.y();
|
update.scroll_y = scroll_anchor.offset.y();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
Event::SelectionsChanged { .. } => {
|
Event::SelectionsChanged { .. } => {
|
||||||
|
@ -207,14 +212,16 @@ impl FollowableItem for Editor {
|
||||||
self.set_selections_from_remote(selections, cx);
|
self.set_selections_from_remote(selections, cx);
|
||||||
self.request_autoscroll_remotely(Autoscroll::newest(), cx);
|
self.request_autoscroll_remotely(Autoscroll::newest(), cx);
|
||||||
} else if let Some(anchor) = message.scroll_top_anchor {
|
} else if let Some(anchor) = message.scroll_top_anchor {
|
||||||
self.set_scroll_top_anchor(
|
self.set_scroll_anchor(
|
||||||
Anchor {
|
ScrollAnchor {
|
||||||
buffer_id: Some(buffer_id),
|
top_anchor: Anchor {
|
||||||
excerpt_id,
|
buffer_id: Some(buffer_id),
|
||||||
text_anchor: language::proto::deserialize_anchor(anchor)
|
excerpt_id,
|
||||||
.ok_or_else(|| anyhow!("invalid scroll top"))?,
|
text_anchor: language::proto::deserialize_anchor(anchor)
|
||||||
|
.ok_or_else(|| anyhow!("invalid scroll top"))?,
|
||||||
|
},
|
||||||
|
offset: vec2f(message.scroll_x, message.scroll_y),
|
||||||
},
|
},
|
||||||
vec2f(message.scroll_x, message.scroll_y),
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -279,13 +286,12 @@ impl Item for Editor {
|
||||||
buffer.clip_point(data.cursor_position, Bias::Left)
|
buffer.clip_point(data.cursor_position, Bias::Left)
|
||||||
};
|
};
|
||||||
|
|
||||||
let scroll_top_anchor = if buffer.can_resolve(&data.scroll_top_anchor) {
|
let mut scroll_anchor = data.scroll_anchor;
|
||||||
data.scroll_top_anchor
|
if !buffer.can_resolve(&scroll_anchor.top_anchor) {
|
||||||
} else {
|
scroll_anchor.top_anchor = buffer.anchor_before(
|
||||||
buffer.anchor_before(
|
|
||||||
buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
|
buffer.clip_point(Point::new(data.scroll_top_row, 0), Bias::Left),
|
||||||
)
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
drop(buffer);
|
drop(buffer);
|
||||||
|
|
||||||
|
@ -293,8 +299,7 @@ impl Item for Editor {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
let nav_history = self.nav_history.take();
|
let nav_history = self.nav_history.take();
|
||||||
self.scroll_position = data.scroll_position;
|
self.set_scroll_anchor(data.scroll_anchor, cx);
|
||||||
self.scroll_top_anchor = scroll_top_anchor;
|
|
||||||
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.select_ranges([offset..offset])
|
s.select_ranges([offset..offset])
|
||||||
});
|
});
|
||||||
|
|
339
crates/editor/src/scroll.rs
Normal file
339
crates/editor/src/scroll.rs
Normal file
|
@ -0,0 +1,339 @@
|
||||||
|
pub mod actions;
|
||||||
|
pub mod autoscroll;
|
||||||
|
pub mod scroll_amount;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
geometry::vector::{vec2f, Vector2F},
|
||||||
|
Axis, MutableAppContext, Task, ViewContext,
|
||||||
|
};
|
||||||
|
use language::Bias;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
|
hover_popover::hide_hover,
|
||||||
|
Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::{
|
||||||
|
autoscroll::{Autoscroll, AutoscrollStrategy},
|
||||||
|
scroll_amount::ScrollAmount,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);
|
||||||
|
const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ScrollbarAutoHide(pub bool);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct ScrollAnchor {
|
||||||
|
pub offset: Vector2F,
|
||||||
|
pub top_anchor: Anchor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollAnchor {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
offset: Vector2F::zero(),
|
||||||
|
top_anchor: Anchor::min(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
|
||||||
|
let mut scroll_position = self.offset;
|
||||||
|
if self.top_anchor != Anchor::min() {
|
||||||
|
let scroll_top = self.top_anchor.to_display_point(snapshot).row() as f32;
|
||||||
|
scroll_position.set_y(scroll_top + scroll_position.y());
|
||||||
|
} else {
|
||||||
|
scroll_position.set_y(0.);
|
||||||
|
}
|
||||||
|
scroll_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 {
|
||||||
|
self.top_anchor.to_point(buffer).row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct OngoingScroll {
|
||||||
|
last_event: Instant,
|
||||||
|
axis: Option<Axis>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OngoingScroll {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
last_event: Instant::now() - SCROLL_EVENT_SEPARATION,
|
||||||
|
axis: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filter(&self, delta: &mut Vector2F) -> Option<Axis> {
|
||||||
|
const UNLOCK_PERCENT: f32 = 1.9;
|
||||||
|
const UNLOCK_LOWER_BOUND: f32 = 6.;
|
||||||
|
let mut axis = self.axis;
|
||||||
|
|
||||||
|
let x = delta.x().abs();
|
||||||
|
let y = delta.y().abs();
|
||||||
|
let duration = Instant::now().duration_since(self.last_event);
|
||||||
|
if duration > SCROLL_EVENT_SEPARATION {
|
||||||
|
//New ongoing scroll will start, determine axis
|
||||||
|
axis = if x <= y {
|
||||||
|
Some(Axis::Vertical)
|
||||||
|
} else {
|
||||||
|
Some(Axis::Horizontal)
|
||||||
|
};
|
||||||
|
} else if x.max(y) >= UNLOCK_LOWER_BOUND {
|
||||||
|
//Check if the current ongoing will need to unlock
|
||||||
|
match axis {
|
||||||
|
Some(Axis::Vertical) => {
|
||||||
|
if x > y && x >= y * UNLOCK_PERCENT {
|
||||||
|
axis = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Axis::Horizontal) => {
|
||||||
|
if y > x && y >= x * UNLOCK_PERCENT {
|
||||||
|
axis = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match axis {
|
||||||
|
Some(Axis::Vertical) => *delta = vec2f(0., delta.y()),
|
||||||
|
Some(Axis::Horizontal) => *delta = vec2f(delta.x(), 0.),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
axis
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScrollManager {
|
||||||
|
vertical_scroll_margin: f32,
|
||||||
|
anchor: ScrollAnchor,
|
||||||
|
ongoing: OngoingScroll,
|
||||||
|
autoscroll_request: Option<(Autoscroll, bool)>,
|
||||||
|
last_autoscroll: Option<(Vector2F, f32, f32, AutoscrollStrategy)>,
|
||||||
|
show_scrollbars: bool,
|
||||||
|
hide_scrollbar_task: Option<Task<()>>,
|
||||||
|
visible_line_count: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ScrollManager {
|
||||||
|
vertical_scroll_margin: 3.0,
|
||||||
|
anchor: ScrollAnchor::new(),
|
||||||
|
ongoing: OngoingScroll::new(),
|
||||||
|
autoscroll_request: None,
|
||||||
|
show_scrollbars: true,
|
||||||
|
hide_scrollbar_task: None,
|
||||||
|
last_autoscroll: None,
|
||||||
|
visible_line_count: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_state(&mut self, other: &Self) {
|
||||||
|
self.anchor = other.anchor;
|
||||||
|
self.ongoing = other.ongoing;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anchor(&self) -> ScrollAnchor {
|
||||||
|
self.anchor
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ongoing_scroll(&self) -> OngoingScroll {
|
||||||
|
self.ongoing
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) {
|
||||||
|
self.ongoing.last_event = Instant::now();
|
||||||
|
self.ongoing.axis = axis;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> Vector2F {
|
||||||
|
self.anchor.scroll_position(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_scroll_position(
|
||||||
|
&mut self,
|
||||||
|
scroll_position: Vector2F,
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
local: bool,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let new_anchor = if scroll_position.y() <= 0. {
|
||||||
|
ScrollAnchor {
|
||||||
|
top_anchor: Anchor::min(),
|
||||||
|
offset: scroll_position.max(vec2f(0., 0.)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let scroll_top_buffer_offset =
|
||||||
|
DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right);
|
||||||
|
let top_anchor = map
|
||||||
|
.buffer_snapshot
|
||||||
|
.anchor_at(scroll_top_buffer_offset, Bias::Right);
|
||||||
|
|
||||||
|
ScrollAnchor {
|
||||||
|
top_anchor,
|
||||||
|
offset: vec2f(
|
||||||
|
scroll_position.x(),
|
||||||
|
scroll_position.y() - top_anchor.to_display_point(&map).row() as f32,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.set_anchor(new_anchor, local, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_anchor(&mut self, anchor: ScrollAnchor, local: bool, cx: &mut ViewContext<Editor>) {
|
||||||
|
self.anchor = anchor;
|
||||||
|
cx.emit(Event::ScrollPositionChanged { local });
|
||||||
|
self.show_scrollbar(cx);
|
||||||
|
self.autoscroll_request.take();
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_scrollbar(&mut self, cx: &mut ViewContext<Editor>) {
|
||||||
|
if !self.show_scrollbars {
|
||||||
|
self.show_scrollbars = true;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
if cx.default_global::<ScrollbarAutoHide>().0 {
|
||||||
|
self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
|
||||||
|
cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
|
||||||
|
if let Some(editor) = editor.upgrade(&cx) {
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
editor.scroll_manager.show_scrollbars = false;
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
self.hide_scrollbar_task = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scrollbars_visible(&self) -> bool {
|
||||||
|
self.show_scrollbars
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_autoscroll_request(&self) -> bool {
|
||||||
|
self.autoscroll_request.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp_scroll_left(&mut self, max: f32) -> bool {
|
||||||
|
if max < self.anchor.offset.x() {
|
||||||
|
self.anchor.offset.set_x(max);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor {
|
||||||
|
pub fn vertical_scroll_margin(&mut self) -> usize {
|
||||||
|
self.scroll_manager.vertical_scroll_margin as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut ViewContext<Self>) {
|
||||||
|
self.scroll_manager.vertical_scroll_margin = margin_rows as f32;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn visible_line_count(&self) -> Option<f32> {
|
||||||
|
self.scroll_manager.visible_line_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_visible_line_count(&mut self, lines: f32) {
|
||||||
|
self.scroll_manager.visible_line_count = Some(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext<Self>) {
|
||||||
|
self.set_scroll_position_internal(scroll_position, true, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_scroll_position_internal(
|
||||||
|
&mut self,
|
||||||
|
scroll_position: Vector2F,
|
||||||
|
local: bool,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
|
||||||
|
hide_hover(self, cx);
|
||||||
|
self.scroll_manager
|
||||||
|
.set_scroll_position(scroll_position, &map, local, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
|
||||||
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
self.scroll_manager.anchor.scroll_position(&display_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
|
||||||
|
hide_hover(self, cx);
|
||||||
|
self.scroll_manager.set_anchor(scroll_anchor, true, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
|
||||||
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
|
cx.propagate_action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.take_rename(true, cx).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if amount.move_context_menu_selection(self, cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cur_position = self.scroll_position(cx);
|
||||||
|
let new_pos = cur_position + vec2f(0., amount.lines(self) - 1.);
|
||||||
|
self.set_scroll_position(new_pos, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an ordering. The newest selection is:
|
||||||
|
/// Ordering::Equal => on screen
|
||||||
|
/// Ordering::Less => above the screen
|
||||||
|
/// Ordering::Greater => below the screen
|
||||||
|
pub fn newest_selection_on_screen(&self, cx: &mut MutableAppContext) -> Ordering {
|
||||||
|
let snapshot = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
let newest_head = self
|
||||||
|
.selections
|
||||||
|
.newest_anchor()
|
||||||
|
.head()
|
||||||
|
.to_display_point(&snapshot);
|
||||||
|
let screen_top = self
|
||||||
|
.scroll_manager
|
||||||
|
.anchor
|
||||||
|
.top_anchor
|
||||||
|
.to_display_point(&snapshot);
|
||||||
|
|
||||||
|
if screen_top > newest_head {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(visible_lines) = self.visible_line_count() {
|
||||||
|
if newest_head.row() < screen_top.row() + visible_lines as u32 {
|
||||||
|
return Ordering::Equal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ordering::Greater
|
||||||
|
}
|
||||||
|
}
|
159
crates/editor/src/scroll/actions.rs
Normal file
159
crates/editor/src/scroll/actions.rs
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
use gpui::{
|
||||||
|
actions, geometry::vector::Vector2F, impl_internal_actions, Axis, MutableAppContext,
|
||||||
|
ViewContext,
|
||||||
|
};
|
||||||
|
use language::Bias;
|
||||||
|
|
||||||
|
use crate::{Editor, EditorMode};
|
||||||
|
|
||||||
|
use super::{autoscroll::Autoscroll, scroll_amount::ScrollAmount, ScrollAnchor};
|
||||||
|
|
||||||
|
actions!(
|
||||||
|
editor,
|
||||||
|
[
|
||||||
|
LineDown,
|
||||||
|
LineUp,
|
||||||
|
HalfPageDown,
|
||||||
|
HalfPageUp,
|
||||||
|
PageDown,
|
||||||
|
PageUp,
|
||||||
|
NextScreen,
|
||||||
|
ScrollCursorTop,
|
||||||
|
ScrollCursorCenter,
|
||||||
|
ScrollCursorBottom,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Scroll {
|
||||||
|
pub scroll_position: Vector2F,
|
||||||
|
pub axis: Option<Axis>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_internal_actions!(editor, [Scroll]);
|
||||||
|
|
||||||
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
|
cx.add_action(Editor::next_screen);
|
||||||
|
cx.add_action(Editor::scroll);
|
||||||
|
cx.add_action(Editor::scroll_cursor_top);
|
||||||
|
cx.add_action(Editor::scroll_cursor_center);
|
||||||
|
cx.add_action(Editor::scroll_cursor_bottom);
|
||||||
|
cx.add_action(|this: &mut Editor, _: &LineDown, cx| {
|
||||||
|
this.scroll_screen(&ScrollAmount::LineDown, cx)
|
||||||
|
});
|
||||||
|
cx.add_action(|this: &mut Editor, _: &LineUp, cx| {
|
||||||
|
this.scroll_screen(&ScrollAmount::LineUp, cx)
|
||||||
|
});
|
||||||
|
cx.add_action(|this: &mut Editor, _: &HalfPageDown, cx| {
|
||||||
|
this.scroll_screen(&ScrollAmount::HalfPageDown, cx)
|
||||||
|
});
|
||||||
|
cx.add_action(|this: &mut Editor, _: &HalfPageUp, cx| {
|
||||||
|
this.scroll_screen(&ScrollAmount::HalfPageUp, cx)
|
||||||
|
});
|
||||||
|
cx.add_action(|this: &mut Editor, _: &PageDown, cx| {
|
||||||
|
this.scroll_screen(&ScrollAmount::PageDown, cx)
|
||||||
|
});
|
||||||
|
cx.add_action(|this: &mut Editor, _: &PageUp, cx| {
|
||||||
|
this.scroll_screen(&ScrollAmount::PageUp, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor {
|
||||||
|
pub fn next_screen(&mut self, _: &NextScreen, cx: &mut ViewContext<Editor>) -> Option<()> {
|
||||||
|
if self.take_rename(true, cx).is_some() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.context_menu.as_mut()?;
|
||||||
|
|
||||||
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
|
cx.propagate_action();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.request_autoscroll(Autoscroll::Next, cx);
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll(&mut self, action: &Scroll, cx: &mut ViewContext<Self>) {
|
||||||
|
self.scroll_manager.update_ongoing_scroll(action.axis);
|
||||||
|
self.set_scroll_position(action.scroll_position, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
|
||||||
|
let snapshot = editor.snapshot(cx).display_snapshot;
|
||||||
|
let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
|
||||||
|
|
||||||
|
let mut new_screen_top = editor.selections.newest_display(cx).head();
|
||||||
|
*new_screen_top.row_mut() = new_screen_top.row().saturating_sub(scroll_margin_rows);
|
||||||
|
*new_screen_top.column_mut() = 0;
|
||||||
|
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
|
||||||
|
let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
|
||||||
|
|
||||||
|
editor.set_scroll_anchor(
|
||||||
|
ScrollAnchor {
|
||||||
|
top_anchor: new_anchor,
|
||||||
|
offset: Default::default(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_cursor_center(
|
||||||
|
editor: &mut Editor,
|
||||||
|
_: &ScrollCursorCenter,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let snapshot = editor.snapshot(cx).display_snapshot;
|
||||||
|
let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
|
||||||
|
visible_rows as u32
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_screen_top = editor.selections.newest_display(cx).head();
|
||||||
|
*new_screen_top.row_mut() = new_screen_top.row().saturating_sub(visible_rows / 2);
|
||||||
|
*new_screen_top.column_mut() = 0;
|
||||||
|
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
|
||||||
|
let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
|
||||||
|
|
||||||
|
editor.set_scroll_anchor(
|
||||||
|
ScrollAnchor {
|
||||||
|
top_anchor: new_anchor,
|
||||||
|
offset: Default::default(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_cursor_bottom(
|
||||||
|
editor: &mut Editor,
|
||||||
|
_: &ScrollCursorBottom,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
let snapshot = editor.snapshot(cx).display_snapshot;
|
||||||
|
let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
|
||||||
|
let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
|
||||||
|
visible_rows as u32
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_screen_top = editor.selections.newest_display(cx).head();
|
||||||
|
*new_screen_top.row_mut() = new_screen_top
|
||||||
|
.row()
|
||||||
|
.saturating_sub(visible_rows.saturating_sub(scroll_margin_rows));
|
||||||
|
*new_screen_top.column_mut() = 0;
|
||||||
|
let new_screen_top = new_screen_top.to_offset(&snapshot, Bias::Left);
|
||||||
|
let new_anchor = snapshot.buffer_snapshot.anchor_before(new_screen_top);
|
||||||
|
|
||||||
|
editor.set_scroll_anchor(
|
||||||
|
ScrollAnchor {
|
||||||
|
top_anchor: new_anchor,
|
||||||
|
offset: Default::default(),
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
246
crates/editor/src/scroll/autoscroll.rs
Normal file
246
crates/editor/src/scroll/autoscroll.rs
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
use gpui::{text_layout, ViewContext};
|
||||||
|
use language::Point;
|
||||||
|
|
||||||
|
use crate::{display_map::ToDisplayPoint, Editor, EditorMode};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub enum Autoscroll {
|
||||||
|
Next,
|
||||||
|
Strategy(AutoscrollStrategy),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Autoscroll {
|
||||||
|
pub fn fit() -> Self {
|
||||||
|
Self::Strategy(AutoscrollStrategy::Fit)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newest() -> Self {
|
||||||
|
Self::Strategy(AutoscrollStrategy::Newest)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center() -> Self {
|
||||||
|
Self::Strategy(AutoscrollStrategy::Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Default)]
|
||||||
|
pub enum AutoscrollStrategy {
|
||||||
|
Fit,
|
||||||
|
Newest,
|
||||||
|
#[default]
|
||||||
|
Center,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoscrollStrategy {
|
||||||
|
fn next(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
AutoscrollStrategy::Center => AutoscrollStrategy::Top,
|
||||||
|
AutoscrollStrategy::Top => AutoscrollStrategy::Bottom,
|
||||||
|
_ => AutoscrollStrategy::Center,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Editor {
|
||||||
|
pub fn autoscroll_vertically(
|
||||||
|
&mut self,
|
||||||
|
viewport_height: f32,
|
||||||
|
line_height: f32,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) -> bool {
|
||||||
|
let visible_lines = viewport_height / line_height;
|
||||||
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
let mut scroll_position = self.scroll_manager.scroll_position(&display_map);
|
||||||
|
let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||||
|
(display_map.max_point().row() as f32 - visible_lines + 1.).max(0.)
|
||||||
|
} else {
|
||||||
|
display_map.max_point().row() as f32
|
||||||
|
};
|
||||||
|
if scroll_position.y() > max_scroll_top {
|
||||||
|
scroll_position.set_y(max_scroll_top);
|
||||||
|
self.set_scroll_position(scroll_position, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (autoscroll, local) =
|
||||||
|
if let Some(autoscroll) = self.scroll_manager.autoscroll_request.take() {
|
||||||
|
autoscroll
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let first_cursor_top;
|
||||||
|
let last_cursor_bottom;
|
||||||
|
if let Some(highlighted_rows) = &self.highlighted_rows {
|
||||||
|
first_cursor_top = highlighted_rows.start as f32;
|
||||||
|
last_cursor_bottom = first_cursor_top + 1.;
|
||||||
|
} else if autoscroll == Autoscroll::newest() {
|
||||||
|
let newest_selection = self.selections.newest::<Point>(cx);
|
||||||
|
first_cursor_top = newest_selection.head().to_display_point(&display_map).row() as f32;
|
||||||
|
last_cursor_bottom = first_cursor_top + 1.;
|
||||||
|
} else {
|
||||||
|
let selections = self.selections.all::<Point>(cx);
|
||||||
|
first_cursor_top = selections
|
||||||
|
.first()
|
||||||
|
.unwrap()
|
||||||
|
.head()
|
||||||
|
.to_display_point(&display_map)
|
||||||
|
.row() as f32;
|
||||||
|
last_cursor_bottom = selections
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.head()
|
||||||
|
.to_display_point(&display_map)
|
||||||
|
.row() as f32
|
||||||
|
+ 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||||
|
0.
|
||||||
|
} else {
|
||||||
|
((visible_lines - (last_cursor_bottom - first_cursor_top)) / 2.0).floor()
|
||||||
|
};
|
||||||
|
if margin < 0.0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let strategy = match autoscroll {
|
||||||
|
Autoscroll::Strategy(strategy) => strategy,
|
||||||
|
Autoscroll::Next => {
|
||||||
|
let last_autoscroll = &self.scroll_manager.last_autoscroll;
|
||||||
|
if let Some(last_autoscroll) = last_autoscroll {
|
||||||
|
if self.scroll_manager.anchor.offset == last_autoscroll.0
|
||||||
|
&& first_cursor_top == last_autoscroll.1
|
||||||
|
&& last_cursor_bottom == last_autoscroll.2
|
||||||
|
{
|
||||||
|
last_autoscroll.3.next()
|
||||||
|
} else {
|
||||||
|
AutoscrollStrategy::default()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AutoscrollStrategy::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match strategy {
|
||||||
|
AutoscrollStrategy::Fit | AutoscrollStrategy::Newest => {
|
||||||
|
let margin = margin.min(self.scroll_manager.vertical_scroll_margin);
|
||||||
|
let target_top = (first_cursor_top - margin).max(0.0);
|
||||||
|
let target_bottom = last_cursor_bottom + margin;
|
||||||
|
let start_row = scroll_position.y();
|
||||||
|
let end_row = start_row + visible_lines;
|
||||||
|
|
||||||
|
if target_top < start_row {
|
||||||
|
scroll_position.set_y(target_top);
|
||||||
|
self.set_scroll_position_internal(scroll_position, local, cx);
|
||||||
|
} else if target_bottom >= end_row {
|
||||||
|
scroll_position.set_y(target_bottom - visible_lines);
|
||||||
|
self.set_scroll_position_internal(scroll_position, local, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AutoscrollStrategy::Center => {
|
||||||
|
scroll_position.set_y((first_cursor_top - margin).max(0.0));
|
||||||
|
self.set_scroll_position_internal(scroll_position, local, cx);
|
||||||
|
}
|
||||||
|
AutoscrollStrategy::Top => {
|
||||||
|
scroll_position.set_y((first_cursor_top).max(0.0));
|
||||||
|
self.set_scroll_position_internal(scroll_position, local, cx);
|
||||||
|
}
|
||||||
|
AutoscrollStrategy::Bottom => {
|
||||||
|
scroll_position.set_y((last_cursor_bottom - visible_lines).max(0.0));
|
||||||
|
self.set_scroll_position_internal(scroll_position, local, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scroll_manager.last_autoscroll = Some((
|
||||||
|
self.scroll_manager.anchor.offset,
|
||||||
|
first_cursor_top,
|
||||||
|
last_cursor_bottom,
|
||||||
|
strategy,
|
||||||
|
));
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn autoscroll_horizontally(
|
||||||
|
&mut self,
|
||||||
|
start_row: u32,
|
||||||
|
viewport_width: f32,
|
||||||
|
scroll_width: f32,
|
||||||
|
max_glyph_width: f32,
|
||||||
|
layouts: &[text_layout::Line],
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
|
let selections = self.selections.all::<Point>(cx);
|
||||||
|
|
||||||
|
let mut target_left;
|
||||||
|
let mut target_right;
|
||||||
|
|
||||||
|
if self.highlighted_rows.is_some() {
|
||||||
|
target_left = 0.0_f32;
|
||||||
|
target_right = 0.0_f32;
|
||||||
|
} else {
|
||||||
|
target_left = std::f32::INFINITY;
|
||||||
|
target_right = 0.0_f32;
|
||||||
|
for selection in selections {
|
||||||
|
let head = selection.head().to_display_point(&display_map);
|
||||||
|
if head.row() >= start_row && head.row() < start_row + layouts.len() as u32 {
|
||||||
|
let start_column = head.column().saturating_sub(3);
|
||||||
|
let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3);
|
||||||
|
target_left = target_left.min(
|
||||||
|
layouts[(head.row() - start_row) as usize]
|
||||||
|
.x_for_index(start_column as usize),
|
||||||
|
);
|
||||||
|
target_right = target_right.max(
|
||||||
|
layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize)
|
||||||
|
+ max_glyph_width,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
target_right = target_right.min(scroll_width);
|
||||||
|
|
||||||
|
if target_right - target_left > viewport_width {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scroll_left = self.scroll_manager.anchor.offset.x() * max_glyph_width;
|
||||||
|
let scroll_right = scroll_left + viewport_width;
|
||||||
|
|
||||||
|
if target_left < scroll_left {
|
||||||
|
self.scroll_manager
|
||||||
|
.anchor
|
||||||
|
.offset
|
||||||
|
.set_x(target_left / max_glyph_width);
|
||||||
|
true
|
||||||
|
} else if target_right > scroll_right {
|
||||||
|
self.scroll_manager
|
||||||
|
.anchor
|
||||||
|
.offset
|
||||||
|
.set_x((target_right - viewport_width) / max_glyph_width);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut ViewContext<Self>) {
|
||||||
|
self.scroll_manager.autoscroll_request = Some((autoscroll, true));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn request_autoscroll_remotely(
|
||||||
|
&mut self,
|
||||||
|
autoscroll: Autoscroll,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.scroll_manager.autoscroll_request = Some((autoscroll, false));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
48
crates/editor/src/scroll/scroll_amount.rs
Normal file
48
crates/editor/src/scroll/scroll_amount.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use gpui::ViewContext;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use util::iife;
|
||||||
|
|
||||||
|
use crate::Editor;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Deserialize)]
|
||||||
|
pub enum ScrollAmount {
|
||||||
|
LineUp,
|
||||||
|
LineDown,
|
||||||
|
HalfPageUp,
|
||||||
|
HalfPageDown,
|
||||||
|
PageUp,
|
||||||
|
PageDown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScrollAmount {
|
||||||
|
pub fn move_context_menu_selection(
|
||||||
|
&self,
|
||||||
|
editor: &mut Editor,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) -> bool {
|
||||||
|
iife!({
|
||||||
|
let context_menu = editor.context_menu.as_mut()?;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::LineDown | Self::HalfPageDown => context_menu.select_next(cx),
|
||||||
|
Self::LineUp | Self::HalfPageUp => context_menu.select_prev(cx),
|
||||||
|
Self::PageDown => context_menu.select_last(cx),
|
||||||
|
Self::PageUp => context_menu.select_first(cx),
|
||||||
|
}
|
||||||
|
.then_some(())
|
||||||
|
})
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lines(&self, editor: &mut Editor) -> f32 {
|
||||||
|
match self {
|
||||||
|
Self::LineDown => 1.,
|
||||||
|
Self::LineUp => -1.,
|
||||||
|
Self::HalfPageDown => editor.visible_line_count().map(|l| l / 2.).unwrap_or(1.),
|
||||||
|
Self::HalfPageUp => -editor.visible_line_count().map(|l| l / 2.).unwrap_or(1.),
|
||||||
|
// Minus 1. here so that there is a pivot line that stays on the screen
|
||||||
|
Self::PageDown => editor.visible_line_count().unwrap_or(1.) - 1.,
|
||||||
|
Self::PageUp => -editor.visible_line_count().unwrap_or(1.) - 1.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -61,7 +61,7 @@ impl SelectionsCollection {
|
||||||
self.buffer.read(cx).read(cx)
|
self.buffer.read(cx).read(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_state(&mut self, other: &SelectionsCollection) {
|
pub fn clone_state(&mut self, other: &SelectionsCollection) {
|
||||||
self.next_selection_id = other.next_selection_id;
|
self.next_selection_id = other.next_selection_id;
|
||||||
self.line_mode = other.line_mode;
|
self.line_mode = other.line_mode;
|
||||||
self.disjoint = other.disjoint.clone();
|
self.disjoint = other.disjoint.clone();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use editor::{display_map::ToDisplayPoint, Autoscroll, DisplayPoint, Editor};
|
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, Axis, Entity,
|
actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, Axis, Entity,
|
||||||
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
MutableAppContext, RenderContext, View, ViewContext, ViewHandle,
|
||||||
|
|
|
@ -594,6 +594,9 @@ type ReleaseObservationCallback = Box<dyn FnOnce(&dyn Any, &mut MutableAppContex
|
||||||
type ActionObservationCallback = Box<dyn FnMut(TypeId, &mut MutableAppContext)>;
|
type ActionObservationCallback = Box<dyn FnMut(TypeId, &mut MutableAppContext)>;
|
||||||
type WindowActivationCallback = Box<dyn FnMut(bool, &mut MutableAppContext) -> bool>;
|
type WindowActivationCallback = Box<dyn FnMut(bool, &mut MutableAppContext) -> bool>;
|
||||||
type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut MutableAppContext) -> bool>;
|
type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut MutableAppContext) -> bool>;
|
||||||
|
type KeystrokeCallback = Box<
|
||||||
|
dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut MutableAppContext) -> bool,
|
||||||
|
>;
|
||||||
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
|
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
|
||||||
type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
||||||
|
|
||||||
|
@ -619,6 +622,7 @@ pub struct MutableAppContext {
|
||||||
observations: CallbackCollection<usize, ObservationCallback>,
|
observations: CallbackCollection<usize, ObservationCallback>,
|
||||||
window_activation_observations: CallbackCollection<usize, WindowActivationCallback>,
|
window_activation_observations: CallbackCollection<usize, WindowActivationCallback>,
|
||||||
window_fullscreen_observations: CallbackCollection<usize, WindowFullscreenCallback>,
|
window_fullscreen_observations: CallbackCollection<usize, WindowFullscreenCallback>,
|
||||||
|
keystroke_observations: CallbackCollection<usize, KeystrokeCallback>,
|
||||||
|
|
||||||
release_observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ReleaseObservationCallback>>>>,
|
release_observations: Arc<Mutex<HashMap<usize, BTreeMap<usize, ReleaseObservationCallback>>>>,
|
||||||
action_dispatch_observations: Arc<Mutex<BTreeMap<usize, ActionObservationCallback>>>,
|
action_dispatch_observations: Arc<Mutex<BTreeMap<usize, ActionObservationCallback>>>,
|
||||||
|
@ -678,6 +682,7 @@ impl MutableAppContext {
|
||||||
global_observations: Default::default(),
|
global_observations: Default::default(),
|
||||||
window_activation_observations: Default::default(),
|
window_activation_observations: Default::default(),
|
||||||
window_fullscreen_observations: Default::default(),
|
window_fullscreen_observations: Default::default(),
|
||||||
|
keystroke_observations: Default::default(),
|
||||||
action_dispatch_observations: Default::default(),
|
action_dispatch_observations: Default::default(),
|
||||||
presenters_and_platform_windows: Default::default(),
|
presenters_and_platform_windows: Default::default(),
|
||||||
foreground,
|
foreground,
|
||||||
|
@ -763,11 +768,11 @@ impl MutableAppContext {
|
||||||
.with_context(|| format!("invalid data for action {}", name))
|
.with_context(|| format!("invalid data for action {}", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_action<A, V, F>(&mut self, handler: F)
|
pub fn add_action<A, V, F, R>(&mut self, handler: F)
|
||||||
where
|
where
|
||||||
A: Action,
|
A: Action,
|
||||||
V: View,
|
V: View,
|
||||||
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>),
|
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
|
||||||
{
|
{
|
||||||
self.add_action_internal(handler, false)
|
self.add_action_internal(handler, false)
|
||||||
}
|
}
|
||||||
|
@ -781,11 +786,11 @@ impl MutableAppContext {
|
||||||
self.add_action_internal(handler, true)
|
self.add_action_internal(handler, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_action_internal<A, V, F>(&mut self, mut handler: F, capture: bool)
|
fn add_action_internal<A, V, F, R>(&mut self, mut handler: F, capture: bool)
|
||||||
where
|
where
|
||||||
A: Action,
|
A: Action,
|
||||||
V: View,
|
V: View,
|
||||||
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>),
|
F: 'static + FnMut(&mut V, &A, &mut ViewContext<V>) -> R,
|
||||||
{
|
{
|
||||||
let handler = Box::new(
|
let handler = Box::new(
|
||||||
move |view: &mut dyn AnyView,
|
move |view: &mut dyn AnyView,
|
||||||
|
@ -1255,6 +1260,27 @@ impl MutableAppContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn observe_keystrokes<F>(&mut self, window_id: usize, callback: F) -> Subscription
|
||||||
|
where
|
||||||
|
F: 'static
|
||||||
|
+ FnMut(
|
||||||
|
&Keystroke,
|
||||||
|
&MatchResult,
|
||||||
|
Option<&Box<dyn Action>>,
|
||||||
|
&mut MutableAppContext,
|
||||||
|
) -> bool,
|
||||||
|
{
|
||||||
|
let subscription_id = post_inc(&mut self.next_subscription_id);
|
||||||
|
self.keystroke_observations
|
||||||
|
.add_callback(window_id, subscription_id, Box::new(callback));
|
||||||
|
|
||||||
|
Subscription::KeystrokeObservation {
|
||||||
|
id: subscription_id,
|
||||||
|
window_id,
|
||||||
|
observations: Some(self.keystroke_observations.downgrade()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) {
|
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) {
|
||||||
self.pending_effects.push_back(Effect::Deferred {
|
self.pending_effects.push_back(Effect::Deferred {
|
||||||
callback: Box::new(callback),
|
callback: Box::new(callback),
|
||||||
|
@ -1538,27 +1564,39 @@ impl MutableAppContext {
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
match self
|
let match_result = self
|
||||||
.keystroke_matcher
|
.keystroke_matcher
|
||||||
.push_keystroke(keystroke.clone(), dispatch_path)
|
.push_keystroke(keystroke.clone(), dispatch_path);
|
||||||
{
|
let mut handled_by = None;
|
||||||
|
|
||||||
|
let keystroke_handled = match &match_result {
|
||||||
MatchResult::None => false,
|
MatchResult::None => false,
|
||||||
MatchResult::Pending => true,
|
MatchResult::Pending => true,
|
||||||
MatchResult::Matches(matches) => {
|
MatchResult::Matches(matches) => {
|
||||||
for (view_id, action) in matches {
|
for (view_id, action) in matches {
|
||||||
if self.handle_dispatch_action_from_effect(
|
if self.handle_dispatch_action_from_effect(
|
||||||
window_id,
|
window_id,
|
||||||
Some(view_id),
|
Some(*view_id),
|
||||||
action.as_ref(),
|
action.as_ref(),
|
||||||
) {
|
) {
|
||||||
self.keystroke_matcher.clear_pending();
|
self.keystroke_matcher.clear_pending();
|
||||||
return true;
|
handled_by = Some(action.boxed_clone());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
handled_by.is_some()
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
self.keystroke(
|
||||||
|
window_id,
|
||||||
|
keystroke.clone(),
|
||||||
|
handled_by,
|
||||||
|
match_result.clone(),
|
||||||
|
);
|
||||||
|
keystroke_handled
|
||||||
} else {
|
} else {
|
||||||
|
self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2110,6 +2148,12 @@ impl MutableAppContext {
|
||||||
} => {
|
} => {
|
||||||
self.handle_window_should_close_subscription_effect(window_id, callback)
|
self.handle_window_should_close_subscription_effect(window_id, callback)
|
||||||
}
|
}
|
||||||
|
Effect::Keystroke {
|
||||||
|
window_id,
|
||||||
|
keystroke,
|
||||||
|
handled_by,
|
||||||
|
result,
|
||||||
|
} => self.handle_keystroke_effect(window_id, keystroke, handled_by, result),
|
||||||
}
|
}
|
||||||
self.pending_notifications.clear();
|
self.pending_notifications.clear();
|
||||||
self.remove_dropped_entities();
|
self.remove_dropped_entities();
|
||||||
|
@ -2188,6 +2232,21 @@ impl MutableAppContext {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn keystroke(
|
||||||
|
&mut self,
|
||||||
|
window_id: usize,
|
||||||
|
keystroke: Keystroke,
|
||||||
|
handled_by: Option<Box<dyn Action>>,
|
||||||
|
result: MatchResult,
|
||||||
|
) {
|
||||||
|
self.pending_effects.push_back(Effect::Keystroke {
|
||||||
|
window_id,
|
||||||
|
keystroke,
|
||||||
|
handled_by,
|
||||||
|
result,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn refresh_windows(&mut self) {
|
pub fn refresh_windows(&mut self) {
|
||||||
self.pending_effects.push_back(Effect::RefreshWindows);
|
self.pending_effects.push_back(Effect::RefreshWindows);
|
||||||
}
|
}
|
||||||
|
@ -2299,6 +2358,21 @@ impl MutableAppContext {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_keystroke_effect(
|
||||||
|
&mut self,
|
||||||
|
window_id: usize,
|
||||||
|
keystroke: Keystroke,
|
||||||
|
handled_by: Option<Box<dyn Action>>,
|
||||||
|
result: MatchResult,
|
||||||
|
) {
|
||||||
|
self.update(|this| {
|
||||||
|
let mut observations = this.keystroke_observations.clone();
|
||||||
|
observations.emit_and_cleanup(window_id, this, {
|
||||||
|
move |callback, this| callback(&keystroke, &result, handled_by.as_ref(), this)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) {
|
fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) {
|
||||||
//Short circuit evaluation if we're already g2g
|
//Short circuit evaluation if we're already g2g
|
||||||
if self
|
if self
|
||||||
|
@ -2852,6 +2926,12 @@ pub enum Effect {
|
||||||
subscription_id: usize,
|
subscription_id: usize,
|
||||||
callback: WindowFullscreenCallback,
|
callback: WindowFullscreenCallback,
|
||||||
},
|
},
|
||||||
|
Keystroke {
|
||||||
|
window_id: usize,
|
||||||
|
keystroke: Keystroke,
|
||||||
|
handled_by: Option<Box<dyn Action>>,
|
||||||
|
result: MatchResult,
|
||||||
|
},
|
||||||
RefreshWindows,
|
RefreshWindows,
|
||||||
DispatchActionFrom {
|
DispatchActionFrom {
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
|
@ -2995,6 +3075,21 @@ impl Debug for Effect {
|
||||||
.debug_struct("Effect::WindowShouldCloseSubscription")
|
.debug_struct("Effect::WindowShouldCloseSubscription")
|
||||||
.field("window_id", window_id)
|
.field("window_id", window_id)
|
||||||
.finish(),
|
.finish(),
|
||||||
|
Effect::Keystroke {
|
||||||
|
window_id,
|
||||||
|
keystroke,
|
||||||
|
handled_by,
|
||||||
|
result,
|
||||||
|
} => f
|
||||||
|
.debug_struct("Effect::Keystroke")
|
||||||
|
.field("window_id", window_id)
|
||||||
|
.field("keystroke", keystroke)
|
||||||
|
.field(
|
||||||
|
"keystroke",
|
||||||
|
&handled_by.as_ref().map(|handled_by| handled_by.name()),
|
||||||
|
)
|
||||||
|
.field("result", result)
|
||||||
|
.finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3826,6 +3921,33 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn observe_keystroke<F>(&mut self, mut callback: F) -> Subscription
|
||||||
|
where
|
||||||
|
F: 'static
|
||||||
|
+ FnMut(
|
||||||
|
&mut T,
|
||||||
|
&Keystroke,
|
||||||
|
Option<&Box<dyn Action>>,
|
||||||
|
&MatchResult,
|
||||||
|
&mut ViewContext<T>,
|
||||||
|
) -> bool,
|
||||||
|
{
|
||||||
|
let observer = self.weak_handle();
|
||||||
|
self.app.observe_keystrokes(
|
||||||
|
self.window_id(),
|
||||||
|
move |keystroke, result, handled_by, cx| {
|
||||||
|
if let Some(observer) = observer.upgrade(cx) {
|
||||||
|
observer.update(cx, |observer, cx| {
|
||||||
|
callback(observer, keystroke, handled_by, result, cx);
|
||||||
|
});
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn emit(&mut self, payload: T::Event) {
|
pub fn emit(&mut self, payload: T::Event) {
|
||||||
self.app.pending_effects.push_back(Effect::Event {
|
self.app.pending_effects.push_back(Effect::Event {
|
||||||
entity_id: self.view_id,
|
entity_id: self.view_id,
|
||||||
|
@ -5018,6 +5140,11 @@ pub enum Subscription {
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
observations: Option<Weak<Mapping<usize, WindowFullscreenCallback>>>,
|
observations: Option<Weak<Mapping<usize, WindowFullscreenCallback>>>,
|
||||||
},
|
},
|
||||||
|
KeystrokeObservation {
|
||||||
|
id: usize,
|
||||||
|
window_id: usize,
|
||||||
|
observations: Option<Weak<Mapping<usize, KeystrokeCallback>>>,
|
||||||
|
},
|
||||||
|
|
||||||
ReleaseObservation {
|
ReleaseObservation {
|
||||||
id: usize,
|
id: usize,
|
||||||
|
@ -5056,6 +5183,9 @@ impl Subscription {
|
||||||
Subscription::ActionObservation { observations, .. } => {
|
Subscription::ActionObservation { observations, .. } => {
|
||||||
observations.take();
|
observations.take();
|
||||||
}
|
}
|
||||||
|
Subscription::KeystrokeObservation { observations, .. } => {
|
||||||
|
observations.take();
|
||||||
|
}
|
||||||
Subscription::WindowActivationObservation { observations, .. } => {
|
Subscription::WindowActivationObservation { observations, .. } => {
|
||||||
observations.take();
|
observations.take();
|
||||||
}
|
}
|
||||||
|
@ -5175,6 +5305,27 @@ impl Drop for Subscription {
|
||||||
observations.lock().remove(id);
|
observations.lock().remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Subscription::KeystrokeObservation {
|
||||||
|
id,
|
||||||
|
window_id,
|
||||||
|
observations,
|
||||||
|
} => {
|
||||||
|
if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) {
|
||||||
|
match observations
|
||||||
|
.lock()
|
||||||
|
.entry(*window_id)
|
||||||
|
.or_default()
|
||||||
|
.entry(*id)
|
||||||
|
{
|
||||||
|
btree_map::Entry::Vacant(entry) => {
|
||||||
|
entry.insert(None);
|
||||||
|
}
|
||||||
|
btree_map::Entry::Occupied(entry) => {
|
||||||
|
entry.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Subscription::WindowActivationObservation {
|
Subscription::WindowActivationObservation {
|
||||||
id,
|
id,
|
||||||
window_id,
|
window_id,
|
||||||
|
|
|
@ -112,6 +112,21 @@ impl PartialEq for MatchResult {
|
||||||
|
|
||||||
impl Eq for MatchResult {}
|
impl Eq for MatchResult {}
|
||||||
|
|
||||||
|
impl Clone for MatchResult {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
MatchResult::None => MatchResult::None,
|
||||||
|
MatchResult::Pending => MatchResult::Pending,
|
||||||
|
MatchResult::Matches(matches) => MatchResult::Matches(
|
||||||
|
matches
|
||||||
|
.iter()
|
||||||
|
.map(|(view_id, action)| (*view_id, Action::boxed_clone(action.as_ref())))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Matcher {
|
impl Matcher {
|
||||||
pub fn new(keymap: Keymap) -> Self {
|
pub fn new(keymap: Keymap) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use chrono::{Datelike, Local, NaiveTime, Timelike};
|
use chrono::{Datelike, Local, NaiveTime, Timelike};
|
||||||
use editor::{Autoscroll, Editor};
|
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||||
use gpui::{actions, MutableAppContext};
|
use gpui::{actions, MutableAppContext};
|
||||||
use settings::{HourFormat, Settings};
|
use settings::{HourFormat, Settings};
|
||||||
use std::{
|
use std::{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use editor::{
|
use editor::{
|
||||||
combine_syntax_and_fuzzy_match_highlights, display_map::ToDisplayPoint, Anchor, AnchorRangeExt,
|
combine_syntax_and_fuzzy_match_highlights, display_map::ToDisplayPoint,
|
||||||
Autoscroll, DisplayPoint, Editor, ToPoint,
|
scroll::autoscroll::Autoscroll, Anchor, AnchorRangeExt, DisplayPoint, Editor, ToPoint,
|
||||||
};
|
};
|
||||||
use fuzzy::StringMatch;
|
use fuzzy::StringMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use editor::{
|
use editor::{
|
||||||
combine_syntax_and_fuzzy_match_highlights, styled_runs_for_code_label, Autoscroll, Bias, Editor,
|
combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
|
||||||
|
styled_runs_for_code_label, Bias, Editor,
|
||||||
};
|
};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
items::active_match_index, Anchor, Autoscroll, Editor, MultiBuffer, SelectAll,
|
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
|
||||||
MAX_TAB_TITLE_LEN,
|
SelectAll, MAX_TAB_TITLE_LEN,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox,
|
actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox,
|
||||||
|
|
|
@ -216,6 +216,8 @@ pub fn unzip_option<T, U>(option: Option<(T, U)>) -> (Option<T>, Option<U>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Immediately invoked function expression. Good for using the ? operator
|
||||||
|
/// in functions which do not return an Option or Result
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! iife {
|
macro_rules! iife {
|
||||||
($block:block) => {
|
($block:block) => {
|
||||||
|
@ -223,6 +225,8 @@ macro_rules! iife {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Async lImmediately invoked function expression. Good for using the ? operator
|
||||||
|
/// in functions which do not return an Option or Result. Async version of above
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! async_iife {
|
macro_rules! async_iife {
|
||||||
($block:block) => {
|
($block:block) => {
|
||||||
|
|
|
@ -22,20 +22,9 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont
|
||||||
vim.active_editor = Some(editor.downgrade());
|
vim.active_editor = Some(editor.downgrade());
|
||||||
vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| {
|
vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| {
|
||||||
if editor.read(cx).leader_replica_id().is_none() {
|
if editor.read(cx).leader_replica_id().is_none() {
|
||||||
match event {
|
if let editor::Event::SelectionsChanged { local: true } = event {
|
||||||
editor::Event::SelectionsChanged { local: true } => {
|
let newest_empty = editor.read(cx).selections.newest::<usize>(cx).is_empty();
|
||||||
let newest_empty =
|
editor_local_selections_changed(newest_empty, cx);
|
||||||
editor.read(cx).selections.newest::<usize>(cx).is_empty();
|
|
||||||
editor_local_selections_changed(newest_empty, cx);
|
|
||||||
}
|
|
||||||
editor::Event::IgnoredInput => {
|
|
||||||
Vim::update(cx, |vim, cx| {
|
|
||||||
if vim.active_operator().is_some() {
|
|
||||||
vim.clear_operator(cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{state::Mode, Vim};
|
use crate::{state::Mode, Vim};
|
||||||
use editor::{Autoscroll, Bias};
|
use editor::{scroll::autoscroll::Autoscroll, Bias};
|
||||||
use gpui::{actions, MutableAppContext, ViewContext};
|
use gpui::{actions, MutableAppContext, ViewContext};
|
||||||
use language::SelectionGoal;
|
use language::SelectionGoal;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod change;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod yank;
|
mod yank;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, cmp::Ordering};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
motion::Motion,
|
motion::Motion,
|
||||||
|
@ -12,10 +12,13 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
display_map::ToDisplayPoint, Anchor, Autoscroll, Bias, ClipboardSelection, DisplayPoint,
|
display_map::ToDisplayPoint,
|
||||||
|
scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
|
||||||
|
Anchor, Bias, ClipboardSelection, DisplayPoint, Editor,
|
||||||
};
|
};
|
||||||
use gpui::{actions, MutableAppContext, ViewContext};
|
use gpui::{actions, impl_actions, MutableAppContext, ViewContext};
|
||||||
use language::{AutoindentMode, Point, SelectionGoal};
|
use language::{AutoindentMode, Point, SelectionGoal};
|
||||||
|
use serde::Deserialize;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
|
@ -24,6 +27,9 @@ use self::{
|
||||||
yank::{yank_motion, yank_object},
|
yank::{yank_motion, yank_object},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Deserialize)]
|
||||||
|
struct Scroll(ScrollAmount);
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
vim,
|
vim,
|
||||||
[
|
[
|
||||||
|
@ -41,6 +47,8 @@ actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
impl_actions!(vim, [Scroll]);
|
||||||
|
|
||||||
pub fn init(cx: &mut MutableAppContext) {
|
pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(insert_after);
|
cx.add_action(insert_after);
|
||||||
cx.add_action(insert_first_non_whitespace);
|
cx.add_action(insert_first_non_whitespace);
|
||||||
|
@ -72,6 +80,13 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
cx.add_action(paste);
|
cx.add_action(paste);
|
||||||
|
cx.add_action(|_: &mut Workspace, Scroll(amount): &Scroll, cx| {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
|
scroll(editor, amount, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normal_motion(
|
pub fn normal_motion(
|
||||||
|
@ -367,6 +382,46 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Editor>) {
|
||||||
|
let should_move_cursor = editor.newest_selection_on_screen(cx).is_eq();
|
||||||
|
editor.scroll_screen(amount, cx);
|
||||||
|
if should_move_cursor {
|
||||||
|
let selection_ordering = editor.newest_selection_on_screen(cx);
|
||||||
|
if selection_ordering.is_eq() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let visible_rows = if let Some(visible_rows) = editor.visible_line_count() {
|
||||||
|
visible_rows as u32
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let scroll_margin_rows = editor.vertical_scroll_margin() as u32;
|
||||||
|
let top_anchor = editor.scroll_manager.anchor().top_anchor;
|
||||||
|
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.replace_cursors_with(|snapshot| {
|
||||||
|
let mut new_point = top_anchor.to_display_point(&snapshot);
|
||||||
|
|
||||||
|
match selection_ordering {
|
||||||
|
Ordering::Less => {
|
||||||
|
*new_point.row_mut() += scroll_margin_rows;
|
||||||
|
new_point = snapshot.clip_point(new_point, Bias::Right);
|
||||||
|
}
|
||||||
|
Ordering::Greater => {
|
||||||
|
*new_point.row_mut() += visible_rows - scroll_margin_rows as u32;
|
||||||
|
new_point = snapshot.clip_point(new_point, Bias::Left);
|
||||||
|
}
|
||||||
|
Ordering::Equal => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![new_point]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
|
use crate::{motion::Motion, object::Object, state::Mode, utils::copy_selections_content, Vim};
|
||||||
use editor::{
|
use editor::{
|
||||||
char_kind, display_map::DisplaySnapshot, movement, Autoscroll, CharKind, DisplayPoint,
|
char_kind, display_map::DisplaySnapshot, movement, scroll::autoscroll::Autoscroll, CharKind,
|
||||||
|
DisplayPoint,
|
||||||
};
|
};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
use language::Selection;
|
use language::Selection;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use editor::{display_map::ToDisplayPoint, Autoscroll, Bias};
|
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
|
|
||||||
pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut MutableAppContext) {
|
pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut MutableAppContext) {
|
||||||
|
|
|
@ -18,6 +18,7 @@ impl Default for Mode {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||||
pub enum Namespace {
|
pub enum Namespace {
|
||||||
G,
|
G,
|
||||||
|
Z,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
|
||||||
|
@ -95,6 +96,7 @@ impl Operator {
|
||||||
let operator_context = match operator {
|
let operator_context = match operator {
|
||||||
Some(Operator::Number(_)) => "n",
|
Some(Operator::Number(_)) => "n",
|
||||||
Some(Operator::Namespace(Namespace::G)) => "g",
|
Some(Operator::Namespace(Namespace::G)) => "g",
|
||||||
|
Some(Operator::Namespace(Namespace::Z)) => "z",
|
||||||
Some(Operator::Object { around: false }) => "i",
|
Some(Operator::Object { around: false }) => "i",
|
||||||
Some(Operator::Object { around: true }) => "a",
|
Some(Operator::Object { around: true }) => "a",
|
||||||
Some(Operator::Change) => "c",
|
Some(Operator::Change) => "c",
|
||||||
|
|
|
@ -81,6 +81,28 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any keystrokes not mapped to vim should clar the active operator
|
||||||
|
pub fn observe_keypresses(window_id: usize, cx: &mut MutableAppContext) {
|
||||||
|
cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| {
|
||||||
|
dbg!(_keystroke);
|
||||||
|
dbg!(_result);
|
||||||
|
if let Some(handled_by) = handled_by {
|
||||||
|
dbg!(handled_by.name());
|
||||||
|
if handled_by.namespace() == "vim" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
if vim.active_operator().is_some() {
|
||||||
|
vim.clear_operator(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.detach()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Vim {
|
pub struct Vim {
|
||||||
editors: HashMap<usize, WeakViewHandle<Editor>>,
|
editors: HashMap<usize, WeakViewHandle<Editor>>,
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{display_map::ToDisplayPoint, Autoscroll, Bias, ClipboardSelection};
|
use editor::{
|
||||||
|
display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection,
|
||||||
|
};
|
||||||
use gpui::{actions, MutableAppContext, ViewContext};
|
use gpui::{actions, MutableAppContext, ViewContext};
|
||||||
use language::{AutoindentMode, SelectionGoal};
|
use language::{AutoindentMode, SelectionGoal};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
|
@ -175,21 +175,16 @@ impl Dock {
|
||||||
new_position: DockPosition,
|
new_position: DockPosition,
|
||||||
cx: &mut ViewContext<Workspace>,
|
cx: &mut ViewContext<Workspace>,
|
||||||
) {
|
) {
|
||||||
dbg!("starting", &new_position);
|
|
||||||
workspace.dock.position = new_position;
|
workspace.dock.position = new_position;
|
||||||
// Tell the pane about the new anchor position
|
// Tell the pane about the new anchor position
|
||||||
workspace.dock.pane.update(cx, |pane, cx| {
|
workspace.dock.pane.update(cx, |pane, cx| {
|
||||||
dbg!("setting docked");
|
|
||||||
pane.set_docked(Some(new_position.anchor()), cx)
|
pane.set_docked(Some(new_position.anchor()), cx)
|
||||||
});
|
});
|
||||||
|
|
||||||
if workspace.dock.position.is_visible() {
|
if workspace.dock.position.is_visible() {
|
||||||
dbg!("dock is visible");
|
|
||||||
// Close the right sidebar if the dock is on the right side and the right sidebar is open
|
// Close the right sidebar if the dock is on the right side and the right sidebar is open
|
||||||
if workspace.dock.position.anchor() == DockAnchor::Right {
|
if workspace.dock.position.anchor() == DockAnchor::Right {
|
||||||
dbg!("dock anchor is right");
|
|
||||||
if workspace.right_sidebar().read(cx).is_open() {
|
if workspace.right_sidebar().read(cx).is_open() {
|
||||||
dbg!("Toggling right sidebar");
|
|
||||||
workspace.toggle_sidebar(SidebarSide::Right, cx);
|
workspace.toggle_sidebar(SidebarSide::Right, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,10 +194,8 @@ impl Dock {
|
||||||
if pane.read(cx).items().next().is_none() {
|
if pane.read(cx).items().next().is_none() {
|
||||||
let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
|
let item_to_add = (workspace.dock.default_item_factory)(workspace, cx);
|
||||||
// Adding the item focuses the pane by default
|
// Adding the item focuses the pane by default
|
||||||
dbg!("Adding item to dock");
|
|
||||||
Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
|
Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx);
|
||||||
} else {
|
} else {
|
||||||
dbg!("just focusing dock");
|
|
||||||
cx.focus(pane);
|
cx.focus(pane);
|
||||||
}
|
}
|
||||||
} else if let Some(last_active_center_pane) = workspace
|
} else if let Some(last_active_center_pane) = workspace
|
||||||
|
@ -214,7 +207,6 @@ impl Dock {
|
||||||
}
|
}
|
||||||
cx.emit(crate::Event::DockAnchorChanged);
|
cx.emit(crate::Event::DockAnchorChanged);
|
||||||
workspace.serialize_workspace(cx);
|
workspace.serialize_workspace(cx);
|
||||||
dbg!("Serializing workspace after dock position changed");
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -324,6 +324,9 @@ pub fn initialize_workspace(
|
||||||
|
|
||||||
auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
|
auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
|
||||||
|
|
||||||
|
let window_id = cx.window_id();
|
||||||
|
vim::observe_keypresses(window_id, cx);
|
||||||
|
|
||||||
cx.on_window_should_close(|workspace, cx| {
|
cx.on_window_should_close(|workspace, cx| {
|
||||||
if let Some(task) = workspace.close(&Default::default(), cx) {
|
if let Some(task) = workspace.close(&Default::default(), cx) {
|
||||||
task.detach_and_log_err(cx);
|
task.detach_and_log_err(cx);
|
||||||
|
@ -613,7 +616,7 @@ fn schema_file_match(path: &Path) -> &Path {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use editor::{Autoscroll, DisplayPoint, Editor};
|
use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle,
|
executor::Deterministic, AssetSource, MutableAppContext, TestAppContext, ViewHandle,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue