project_panel vim counts and shortcuts
This commit is contained in:
parent
c731bb6d91
commit
d08b72b872
6 changed files with 169 additions and 17 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -17937,6 +17937,7 @@ dependencies = [
|
||||||
"language",
|
"language",
|
||||||
"log",
|
"log",
|
||||||
"lsp",
|
"lsp",
|
||||||
|
"menu",
|
||||||
"multi_buffer",
|
"multi_buffer",
|
||||||
"nvim-rs",
|
"nvim-rs",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
|
|
@ -733,6 +733,21 @@
|
||||||
"escape": "buffer_search::Dismiss"
|
"escape": "buffer_search::Dismiss"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "(GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel) && not_editing",
|
||||||
|
"bindings": {
|
||||||
|
"0": ["vim::Number", 0],
|
||||||
|
"1": ["vim::Number", 1],
|
||||||
|
"2": ["vim::Number", 2],
|
||||||
|
"3": ["vim::Number", 3],
|
||||||
|
"4": ["vim::Number", 4],
|
||||||
|
"5": ["vim::Number", 5],
|
||||||
|
"6": ["vim::Number", 6],
|
||||||
|
"7": ["vim::Number", 7],
|
||||||
|
"8": ["vim::Number", 8],
|
||||||
|
"9": ["vim::Number", 9]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "VimControl || !Editor && !Terminal",
|
"context": "VimControl || !Editor && !Terminal",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
@ -809,8 +824,8 @@
|
||||||
"enter": "project_panel::OpenPermanent",
|
"enter": "project_panel::OpenPermanent",
|
||||||
"escape": "project_panel::ToggleFocus",
|
"escape": "project_panel::ToggleFocus",
|
||||||
"h": "project_panel::CollapseSelectedEntry",
|
"h": "project_panel::CollapseSelectedEntry",
|
||||||
"j": "menu::SelectNext",
|
"j": "vim::MenuSelectNext",
|
||||||
"k": "menu::SelectPrevious",
|
"k": "vim::MenuSelectPrevious",
|
||||||
"l": "project_panel::ExpandSelectedEntry",
|
"l": "project_panel::ExpandSelectedEntry",
|
||||||
"o": "project_panel::OpenPermanent",
|
"o": "project_panel::OpenPermanent",
|
||||||
"shift-d": "project_panel::Delete",
|
"shift-d": "project_panel::Delete",
|
||||||
|
@ -829,7 +844,12 @@
|
||||||
"{": "project_panel::SelectPrevDirectory",
|
"{": "project_panel::SelectPrevDirectory",
|
||||||
"shift-g": "menu::SelectLast",
|
"shift-g": "menu::SelectLast",
|
||||||
"g g": "menu::SelectFirst",
|
"g g": "menu::SelectFirst",
|
||||||
"-": "project_panel::SelectParent"
|
"-": "project_panel::SelectParent",
|
||||||
|
"ctrl-u": "project_panel::ScrollUp",
|
||||||
|
"ctrl-d": "project_panel::ScrollDown",
|
||||||
|
"z t": "project_panel::ScrollCursorTop",
|
||||||
|
"z z": "project_panel::ScrollCursorCenter",
|
||||||
|
"z b": "project_panel::ScrollCursorBottom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -88,6 +88,10 @@ pub enum ScrollStrategy {
|
||||||
/// May not be possible if there's not enough list items above the item scrolled to:
|
/// May not be possible if there's not enough list items above the item scrolled to:
|
||||||
/// in this case, the element will be placed at the closest possible position.
|
/// in this case, the element will be placed at the closest possible position.
|
||||||
Center,
|
Center,
|
||||||
|
/// Attempt to place the element at the bottom of the list's viewport.
|
||||||
|
/// May not be possible if there's not enough list items above the item scrolled to:
|
||||||
|
/// in this case, the element will be placed at the closest possible position.
|
||||||
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -99,6 +103,7 @@ pub struct DeferredScrollToItem {
|
||||||
pub strategy: ScrollStrategy,
|
pub strategy: ScrollStrategy,
|
||||||
/// The offset in number of items
|
/// The offset in number of items
|
||||||
pub offset: usize,
|
pub offset: usize,
|
||||||
|
pub scroll_strict: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -133,12 +138,23 @@ impl UniformListScrollHandle {
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll the list to the given item index.
|
/// Scroll the list so that the given item index is onscreen.
|
||||||
pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
|
pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
|
||||||
self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem {
|
self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem {
|
||||||
item_index: ix,
|
item_index: ix,
|
||||||
strategy,
|
strategy,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
scroll_strict: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scroll the list so that the given item index is at scroll strategy position.
|
||||||
|
pub fn scroll_to_item_strict(&self, ix: usize, strategy: ScrollStrategy) {
|
||||||
|
self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem {
|
||||||
|
item_index: ix,
|
||||||
|
strategy,
|
||||||
|
offset: 0,
|
||||||
|
scroll_strict: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +168,7 @@ impl UniformListScrollHandle {
|
||||||
item_index: ix,
|
item_index: ix,
|
||||||
strategy,
|
strategy,
|
||||||
offset,
|
offset,
|
||||||
|
scroll_strict: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,24 +385,35 @@ impl Element for UniformList {
|
||||||
updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom;
|
updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
match deferred_scroll.strategy {
|
if deferred_scroll.scroll_strict
|
||||||
ScrollStrategy::Top => {}
|
|| (scrolled_to_top
|
||||||
ScrollStrategy::Center => {
|
&& (item_top < scroll_top + offset_pixels
|
||||||
if scrolled_to_top {
|
|| item_bottom > scroll_top + list_height))
|
||||||
|
{
|
||||||
|
match deferred_scroll.strategy {
|
||||||
|
ScrollStrategy::Top => {
|
||||||
|
updated_scroll_offset.y = -item_top
|
||||||
|
.max(Pixels::ZERO)
|
||||||
|
.min(content_height - list_height)
|
||||||
|
.max(Pixels::ZERO);
|
||||||
|
}
|
||||||
|
ScrollStrategy::Center => {
|
||||||
let item_center = item_top + item_height / 2.0;
|
let item_center = item_top + item_height / 2.0;
|
||||||
|
|
||||||
let viewport_height = list_height - offset_pixels;
|
let viewport_height = list_height - offset_pixels;
|
||||||
let viewport_center = offset_pixels + viewport_height / 2.0;
|
let viewport_center = offset_pixels + viewport_height / 2.0;
|
||||||
let target_scroll_top = item_center - viewport_center;
|
let target_scroll_top = item_center - viewport_center;
|
||||||
|
|
||||||
if item_top < scroll_top + offset_pixels
|
updated_scroll_offset.y = -target_scroll_top
|
||||||
|| item_bottom > scroll_top + list_height
|
.max(Pixels::ZERO)
|
||||||
{
|
.min(content_height - list_height)
|
||||||
updated_scroll_offset.y = -target_scroll_top
|
.max(Pixels::ZERO);
|
||||||
.max(Pixels::ZERO)
|
}
|
||||||
.min(content_height - list_height)
|
ScrollStrategy::Bottom => {
|
||||||
.max(Pixels::ZERO);
|
updated_scroll_offset.y = -(item_bottom - list_height)
|
||||||
}
|
.max(Pixels::ZERO)
|
||||||
|
.min(content_height - list_height)
|
||||||
|
.max(Pixels::ZERO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ pub struct ProjectPanel {
|
||||||
// An update loop that keeps incrementing/decrementing scroll offset while there is a dragged entry that's
|
// An update loop that keeps incrementing/decrementing scroll offset while there is a dragged entry that's
|
||||||
// hovered over the start/end of a list.
|
// hovered over the start/end of a list.
|
||||||
hover_scroll_task: Option<Task<()>>,
|
hover_scroll_task: Option<Task<()>>,
|
||||||
|
rendered_entries_len: usize,
|
||||||
visible_entries: Vec<VisibleEntriesForWorktree>,
|
visible_entries: Vec<VisibleEntriesForWorktree>,
|
||||||
/// Maps from leaf project entry ID to the currently selected ancestor.
|
/// Maps from leaf project entry ID to the currently selected ancestor.
|
||||||
/// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several
|
/// Relevant only for auto-fold dirs, where a single project panel entry may actually consist of several
|
||||||
|
@ -277,6 +278,11 @@ actions!(
|
||||||
UnfoldDirectory,
|
UnfoldDirectory,
|
||||||
/// Folds the selected directory.
|
/// Folds the selected directory.
|
||||||
FoldDirectory,
|
FoldDirectory,
|
||||||
|
ScrollUp,
|
||||||
|
ScrollDown,
|
||||||
|
ScrollCursorCenter,
|
||||||
|
ScrollCursorTop,
|
||||||
|
ScrollCursorBottom,
|
||||||
/// Selects the parent directory.
|
/// Selects the parent directory.
|
||||||
SelectParent,
|
SelectParent,
|
||||||
/// Selects the next entry with git changes.
|
/// Selects the next entry with git changes.
|
||||||
|
@ -603,6 +609,7 @@ impl ProjectPanel {
|
||||||
hover_scroll_task: None,
|
hover_scroll_task: None,
|
||||||
fs: workspace.app_state().fs.clone(),
|
fs: workspace.app_state().fs.clone(),
|
||||||
focus_handle,
|
focus_handle,
|
||||||
|
rendered_entries_len: 0,
|
||||||
visible_entries: Default::default(),
|
visible_entries: Default::default(),
|
||||||
ancestors: Default::default(),
|
ancestors: Default::default(),
|
||||||
folded_directory_drag_target: None,
|
folded_directory_drag_target: None,
|
||||||
|
@ -1989,6 +1996,52 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scroll_up(&mut self, _: &ScrollUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
for _ in 0..self.rendered_entries_len / 2 {
|
||||||
|
window.dispatch_action(SelectPrevious.boxed_clone(), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_down(&mut self, _: &ScrollDown, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
for _ in 0..self.rendered_entries_len / 2 {
|
||||||
|
window.dispatch_action(SelectNext.boxed_clone(), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_cursor_center(
|
||||||
|
&mut self,
|
||||||
|
_: &ScrollCursorCenter,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
|
||||||
|
self.scroll_handle
|
||||||
|
.scroll_to_item_strict(index, ScrollStrategy::Center);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_cursor_top(&mut self, _: &ScrollCursorTop, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
|
||||||
|
self.scroll_handle
|
||||||
|
.scroll_to_item_strict(index, ScrollStrategy::Top);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_cursor_bottom(
|
||||||
|
&mut self,
|
||||||
|
_: &ScrollCursorBottom,
|
||||||
|
_: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
|
||||||
|
self.scroll_handle
|
||||||
|
.scroll_to_item_strict(index, ScrollStrategy::Bottom);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if let Some(edit_state) = &self.edit_state
|
if let Some(edit_state) = &self.edit_state
|
||||||
&& edit_state.processing_filename.is_none()
|
&& edit_state.processing_filename.is_none()
|
||||||
|
@ -5233,6 +5286,11 @@ impl Render for ProjectPanel {
|
||||||
this.marked_entries.clear();
|
this.marked_entries.clear();
|
||||||
}))
|
}))
|
||||||
.key_context(self.dispatch_context(window, cx))
|
.key_context(self.dispatch_context(window, cx))
|
||||||
|
.on_action(cx.listener(Self::scroll_up))
|
||||||
|
.on_action(cx.listener(Self::scroll_down))
|
||||||
|
.on_action(cx.listener(Self::scroll_cursor_center))
|
||||||
|
.on_action(cx.listener(Self::scroll_cursor_top))
|
||||||
|
.on_action(cx.listener(Self::scroll_cursor_bottom))
|
||||||
.on_action(cx.listener(Self::select_next))
|
.on_action(cx.listener(Self::select_next))
|
||||||
.on_action(cx.listener(Self::select_previous))
|
.on_action(cx.listener(Self::select_previous))
|
||||||
.on_action(cx.listener(Self::select_first))
|
.on_action(cx.listener(Self::select_first))
|
||||||
|
@ -5313,7 +5371,8 @@ impl Render for ProjectPanel {
|
||||||
.child(
|
.child(
|
||||||
uniform_list("entries", item_count, {
|
uniform_list("entries", item_count, {
|
||||||
cx.processor(|this, range: Range<usize>, window, cx| {
|
cx.processor(|this, range: Range<usize>, window, cx| {
|
||||||
let mut items = Vec::with_capacity(range.end - range.start);
|
this.rendered_entries_len = range.end - range.start;
|
||||||
|
let mut items = Vec::with_capacity(this.rendered_entries_len);
|
||||||
this.for_each_visible_entry(
|
this.for_each_visible_entry(
|
||||||
range,
|
range,
|
||||||
window,
|
window,
|
||||||
|
|
|
@ -44,6 +44,7 @@ settings.workspace = true
|
||||||
task.workspace = true
|
task.workspace = true
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
|
menu.workspace = true
|
||||||
tokio = { version = "1.15", features = ["full"], optional = true }
|
tokio = { version = "1.15", features = ["full"], optional = true }
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
|
|
|
@ -222,6 +222,8 @@ actions!(
|
||||||
PushReplaceWithRegister,
|
PushReplaceWithRegister,
|
||||||
/// Toggles comments.
|
/// Toggles comments.
|
||||||
PushToggleComments,
|
PushToggleComments,
|
||||||
|
MenuSelectNext,
|
||||||
|
MenuSelectPrevious
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -251,6 +253,47 @@ pub fn init(cx: &mut App) {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
workspace.register_action(|_, _: &MenuSelectNext, window, cx| {
|
||||||
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
|
|
||||||
|
for _ in 0..count {
|
||||||
|
window.dispatch_action(menu::SelectNext.boxed_clone(), cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.register_action(|_, _: &MenuSelectPrevious, window, cx| {
|
||||||
|
let count = Vim::take_count(cx).unwrap_or(1);
|
||||||
|
|
||||||
|
for _ in 0..count {
|
||||||
|
window.dispatch_action(menu::SelectPrevious.boxed_clone(), cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
workspace.register_action(|workspace, n: &Number, window, cx| {
|
||||||
|
let vim = workspace
|
||||||
|
.focused_pane(window, cx)
|
||||||
|
.read(cx)
|
||||||
|
.active_item()
|
||||||
|
.and_then(|item| item.act_as::<Editor>(cx))
|
||||||
|
.and_then(|editor| editor.read(cx).addon::<VimAddon>().cloned());
|
||||||
|
if let Some(vim) = vim {
|
||||||
|
let digit = n.0;
|
||||||
|
vim.entity.update(cx, |_, cx| {
|
||||||
|
cx.defer_in(window, move |vim, window, cx| {
|
||||||
|
vim.push_count_digit(digit, window, cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let count = Vim::globals(cx).pre_count.unwrap_or(0);
|
||||||
|
Vim::globals(cx).pre_count = Some(
|
||||||
|
count
|
||||||
|
.checked_mul(10)
|
||||||
|
.and_then(|c| c.checked_add(n.0))
|
||||||
|
.unwrap_or(count),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
workspace.register_action(|_, _: &OpenDefaultKeymap, _, cx| {
|
workspace.register_action(|_, _: &OpenDefaultKeymap, _, cx| {
|
||||||
cx.emit(workspace::Event::OpenBundledFile {
|
cx.emit(workspace::Event::OpenBundledFile {
|
||||||
text: settings::vim_keymap(),
|
text: settings::vim_keymap(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue