Add support for resizing panes using vim motions (#21038)

Closes #8628

Release Notes:

- Added support for resizing the current pane using vim keybinds with
the intention to follow the functionality of vim
  - "ctrl-w +" to make a pane taller 
  - "ctrl-w -" to make the pane shorter
  - "ctrl-w >" to make a pane wider
  - "ctrl-w <" to make the pane narrower
- Changed vim pre_count and post_count to globals to allow for other
crates to use the vim count. In this case, it allows for resizing by
more than one unit. For example, "10 ctrl-w -" will decrease the height
of the pane 10 times more than "ctrl-w -"
- This pr does **not** add keybinds for making all panes in an axis
equal size and does **not** add support for resizing docks. This is
mentioned because these could be implied by the original issue

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
AidanV 2024-11-26 16:24:29 -08:00 committed by GitHub
parent d75d34576a
commit f702575255
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 251 additions and 68 deletions

1
Cargo.lock generated
View file

@ -13832,6 +13832,7 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"settings", "settings",
"theme",
"tokio", "tokio",
"ui", "ui",
"util", "util",

View file

@ -557,6 +557,10 @@
"ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"], "ctrl-w shift-l": ["workspace::SwapPaneInDirection", "Right"],
"ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"], "ctrl-w shift-k": ["workspace::SwapPaneInDirection", "Up"],
"ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"], "ctrl-w shift-j": ["workspace::SwapPaneInDirection", "Down"],
"ctrl-w >": ["vim::ResizePane", "Widen"],
"ctrl-w <": ["vim::ResizePane", "Narrow"],
"ctrl-w -": ["vim::ResizePane", "Shorten"],
"ctrl-w +": ["vim::ResizePane", "Lengthen"],
"ctrl-w g t": "pane::ActivateNextItem", "ctrl-w g t": "pane::ActivateNextItem",
"ctrl-w ctrl-g t": "pane::ActivateNextItem", "ctrl-w ctrl-g t": "pane::ActivateNextItem",
"ctrl-w g shift-t": "pane::ActivatePrevItem", "ctrl-w g shift-t": "pane::ActivatePrevItem",

View file

@ -36,6 +36,7 @@ serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true
theme.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

View file

@ -16,7 +16,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
impl Vim { impl Vim {
fn move_to_change(&mut self, direction: Direction, cx: &mut ViewContext<Self>) { fn move_to_change(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
if self.change_list.is_empty() { if self.change_list.is_empty() {
return; return;
} }

View file

@ -101,7 +101,7 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
let Some(workspace) = vim.workspace(cx) else { let Some(workspace) = vim.workspace(cx) else {
return; return;
}; };
let count = vim.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
workspace.update(cx, |workspace, cx| { workspace.update(cx, |workspace, cx| {
command_palette::CommandPalette::toggle( command_palette::CommandPalette::toggle(
workspace, workspace,

View file

@ -16,7 +16,7 @@ actions!(vim, [Indent, Outdent,]);
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &Indent, cx| { Vim::action(editor, cx, |vim, _: &Indent, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
vim.store_visual_marks(cx); vim.store_visual_marks(cx);
vim.update_editor(cx, |vim, editor, cx| { vim.update_editor(cx, |vim, editor, cx| {
editor.transact(cx, |editor, cx| { editor.transact(cx, |editor, cx| {
@ -34,7 +34,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &Outdent, cx| { Vim::action(editor, cx, |vim, _: &Outdent, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
vim.store_visual_marks(cx); vim.store_visual_marks(cx);
vim.update_editor(cx, |vim, editor, cx| { vim.update_editor(cx, |vim, editor, cx| {
editor.transact(cx, |editor, cx| { editor.transact(cx, |editor, cx| {

View file

@ -17,7 +17,7 @@ impl Vim {
self.sync_vim_settings(cx); self.sync_vim_settings(cx);
return; return;
} }
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
self.stop_recording_immediately(action.boxed_clone(), cx); self.stop_recording_immediately(action.boxed_clone(), cx);
if count <= 1 || Vim::globals(cx).dot_replaying { if count <= 1 || Vim::globals(cx).dot_replaying {
self.create_mark("^".into(), false, cx); self.create_mark("^".into(), false, cx);

View file

@ -2,7 +2,7 @@ use gpui::{div, Element, Render, Subscription, View, ViewContext, WeakView};
use itertools::Itertools; use itertools::Itertools;
use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView}; use workspace::{item::ItemHandle, ui::prelude::*, StatusItemView};
use crate::{Vim, VimEvent}; use crate::{Vim, VimEvent, VimGlobals};
/// The ModeIndicator displays the current mode in the status bar. /// The ModeIndicator displays the current mode in the status bar.
pub struct ModeIndicator { pub struct ModeIndicator {
@ -68,14 +68,22 @@ impl ModeIndicator {
let vim = vim.read(cx); let vim = vim.read(cx);
recording recording
.chain(vim.pre_count.map(|count| format!("{}", count))) .chain(
cx.global::<VimGlobals>()
.pre_count
.map(|count| format!("{}", count)),
)
.chain(vim.selected_register.map(|reg| format!("\"{reg}"))) .chain(vim.selected_register.map(|reg| format!("\"{reg}")))
.chain( .chain(
vim.operator_stack vim.operator_stack
.iter() .iter()
.map(|item| item.status().to_string()), .map(|item| item.status().to_string()),
) )
.chain(vim.post_count.map(|count| format!("{}", count))) .chain(
cx.global::<VimGlobals>()
.post_count
.map(|count| format!("{}", count)),
)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("") .join("")
} }

View file

@ -490,7 +490,7 @@ impl Vim {
self.pop_operator(cx); self.pop_operator(cx);
} }
let count = self.take_count(cx); let count = Vim::take_count(cx);
let active_operator = self.active_operator(); let active_operator = self.active_operator();
let mut waiting_operator: Option<Operator> = None; let mut waiting_operator: Option<Operator> = None;
match self.mode { match self.mode {
@ -510,7 +510,7 @@ impl Vim {
self.clear_operator(cx); self.clear_operator(cx);
if let Some(operator) = waiting_operator { if let Some(operator) = waiting_operator {
self.push_operator(operator, cx); self.push_operator(operator, cx);
self.pre_count = count Vim::globals(cx).pre_count = count
} }
} }
} }

View file

@ -77,17 +77,17 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &DeleteLeft, cx| { Vim::action(editor, cx, |vim, _: &DeleteLeft, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let times = vim.take_count(cx); let times = Vim::take_count(cx);
vim.delete_motion(Motion::Left, times, cx); vim.delete_motion(Motion::Left, times, cx);
}); });
Vim::action(editor, cx, |vim, _: &DeleteRight, cx| { Vim::action(editor, cx, |vim, _: &DeleteRight, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let times = vim.take_count(cx); let times = Vim::take_count(cx);
vim.delete_motion(Motion::Right, times, cx); vim.delete_motion(Motion::Right, times, cx);
}); });
Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, cx| { Vim::action(editor, cx, |vim, _: &ChangeToEndOfLine, cx| {
vim.start_recording(cx); vim.start_recording(cx);
let times = vim.take_count(cx); let times = Vim::take_count(cx);
vim.change_motion( vim.change_motion(
Motion::EndOfLine { Motion::EndOfLine {
display_lines: false, display_lines: false,
@ -98,7 +98,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
}); });
Vim::action(editor, cx, |vim, _: &DeleteToEndOfLine, cx| { Vim::action(editor, cx, |vim, _: &DeleteToEndOfLine, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let times = vim.take_count(cx); let times = Vim::take_count(cx);
vim.delete_motion( vim.delete_motion(
Motion::EndOfLine { Motion::EndOfLine {
display_lines: false, display_lines: false,
@ -109,7 +109,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
}); });
Vim::action(editor, cx, |vim, _: &JoinLines, cx| { Vim::action(editor, cx, |vim, _: &JoinLines, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let mut times = vim.take_count(cx).unwrap_or(1); let mut times = Vim::take_count(cx).unwrap_or(1);
if vim.mode.is_visual() { if vim.mode.is_visual() {
times = 1; times = 1;
} else if times > 1 { } else if times > 1 {
@ -130,7 +130,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
}); });
Vim::action(editor, cx, |vim, _: &Undo, cx| { Vim::action(editor, cx, |vim, _: &Undo, cx| {
let times = vim.take_count(cx); let times = Vim::take_count(cx);
vim.update_editor(cx, |_, editor, cx| { vim.update_editor(cx, |_, editor, cx| {
for _ in 0..times.unwrap_or(1) { for _ in 0..times.unwrap_or(1) {
editor.undo(&editor::actions::Undo, cx); editor.undo(&editor::actions::Undo, cx);
@ -138,7 +138,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
}); });
}); });
Vim::action(editor, cx, |vim, _: &Redo, cx| { Vim::action(editor, cx, |vim, _: &Redo, cx| {
let times = vim.take_count(cx); let times = Vim::take_count(cx);
vim.update_editor(cx, |_, editor, cx| { vim.update_editor(cx, |_, editor, cx| {
for _ in 0..times.unwrap_or(1) { for _ in 0..times.unwrap_or(1) {
editor.redo(&editor::actions::Redo, cx); editor.redo(&editor::actions::Redo, cx);
@ -396,7 +396,7 @@ impl Vim {
} }
fn yank_line(&mut self, _: &YankLine, cx: &mut ViewContext<Self>) { fn yank_line(&mut self, _: &YankLine, cx: &mut ViewContext<Self>) {
let count = self.take_count(cx); let count = Vim::take_count(cx);
self.yank_motion(motion::Motion::CurrentLine, count, cx) self.yank_motion(motion::Motion::CurrentLine, count, cx)
} }
@ -416,7 +416,7 @@ impl Vim {
} }
pub(crate) fn normal_replace(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) { pub(crate) fn normal_replace(&mut self, text: Arc<str>, cx: &mut ViewContext<Self>) {
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
self.stop_recording(cx); self.stop_recording(cx);
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, cx| { editor.transact(cx, |editor, cx| {

View file

@ -118,7 +118,7 @@ impl Vim {
{ {
self.record_current_action(cx); self.record_current_action(cx);
self.store_visual_marks(cx); self.store_visual_marks(cx);
let count = self.take_count(cx).unwrap_or(1) as u32; let count = Vim::take_count(cx).unwrap_or(1) as u32;
self.update_editor(cx, |vim, editor, cx| { self.update_editor(cx, |vim, editor, cx| {
let mut ranges = Vec::new(); let mut ranges = Vec::new();

View file

@ -26,13 +26,13 @@ impl_actions!(vim, [Increment, Decrement]);
pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, action: &Increment, cx| { Vim::action(editor, cx, |vim, action: &Increment, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
let step = if action.step { 1 } else { 0 }; let step = if action.step { 1 } else { 0 };
vim.increment(count as i64, step, cx) vim.increment(count as i64, step, cx)
}); });
Vim::action(editor, cx, |vim, action: &Decrement, cx| { Vim::action(editor, cx, |vim, action: &Decrement, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
let step = if action.step { -1 } else { 0 }; let step = if action.step { -1 } else { 0 };
vim.increment(-(count as i64), step, cx) vim.increment(-(count as i64), step, cx)
}); });

View file

@ -25,7 +25,7 @@ impl Vim {
pub fn paste(&mut self, action: &Paste, cx: &mut ViewContext<Self>) { pub fn paste(&mut self, action: &Paste, cx: &mut ViewContext<Self>) {
self.record_current_action(cx); self.record_current_action(cx);
self.store_visual_marks(cx); self.store_visual_marks(cx);
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
self.update_editor(cx, |vim, editor, cx| { self.update_editor(cx, |vim, editor, cx| {
let text_layout_details = editor.text_layout_details(cx); let text_layout_details = editor.text_layout_details(cx);

View file

@ -158,7 +158,7 @@ impl Vim {
} }
pub(crate) fn replay_register(&mut self, mut register: char, cx: &mut ViewContext<Self>) { pub(crate) fn replay_register(&mut self, mut register: char, cx: &mut ViewContext<Self>) {
let mut count = self.take_count(cx).unwrap_or(1); let mut count = Vim::take_count(cx).unwrap_or(1);
self.clear_operator(cx); self.clear_operator(cx);
let globals = Vim::globals(cx); let globals = Vim::globals(cx);
@ -184,7 +184,7 @@ impl Vim {
} }
pub(crate) fn repeat(&mut self, from_insert_mode: bool, cx: &mut ViewContext<Self>) { pub(crate) fn repeat(&mut self, from_insert_mode: bool, cx: &mut ViewContext<Self>) {
let count = self.take_count(cx); let count = Vim::take_count(cx);
let Some((mut actions, selection, mode)) = Vim::update_globals(cx, |globals, _| { let Some((mut actions, selection, mode)) = Vim::update_globals(cx, |globals, _| {
let actions = globals.recorded_actions.clone(); let actions = globals.recorded_actions.clone();
if actions.is_empty() { if actions.is_empty() {

View file

@ -53,7 +53,7 @@ impl Vim {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
by: fn(c: Option<f32>) -> ScrollAmount, by: fn(c: Option<f32>) -> ScrollAmount,
) { ) {
let amount = by(self.take_count(cx).map(|c| c as f32)); let amount = by(Vim::take_count(cx).map(|c| c as f32));
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
scroll_editor(editor, move_cursor, &amount, cx) scroll_editor(editor, move_cursor, &amount, cx)
}); });

View file

@ -120,7 +120,7 @@ impl Vim {
} else { } else {
Direction::Next Direction::Next
}; };
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
let prior_selections = self.editor_selections(cx); let prior_selections = self.editor_selections(cx);
pane.update(cx, |pane, cx| { pane.update(cx, |pane, cx| {
if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() { if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<BufferSearchBar>() {
@ -226,7 +226,7 @@ impl Vim {
pub fn move_to_match_internal(&mut self, direction: Direction, cx: &mut ViewContext<Self>) { pub fn move_to_match_internal(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
let Some(pane) = self.pane(cx) else { return }; let Some(pane) = self.pane(cx) else { return };
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
let prior_selections = self.editor_selections(cx); let prior_selections = self.editor_selections(cx);
let success = pane.update(cx, |pane, cx| { let success = pane.update(cx, |pane, cx| {
@ -264,7 +264,7 @@ impl Vim {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
let Some(pane) = self.pane(cx) else { return }; let Some(pane) = self.pane(cx) else { return };
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
let prior_selections = self.editor_selections(cx); let prior_selections = self.editor_selections(cx);
let vim = cx.view().clone(); let vim = cx.view().clone();

View file

@ -9,7 +9,7 @@ actions!(vim, [Substitute, SubstituteLine]);
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &Substitute, cx| { Vim::action(editor, cx, |vim, _: &Substitute, cx| {
vim.start_recording(cx); vim.start_recording(cx);
let count = vim.take_count(cx); let count = Vim::take_count(cx);
vim.substitute(count, vim.mode == Mode::VisualLine, cx); vim.substitute(count, vim.mode == Mode::VisualLine, cx);
}); });
@ -18,7 +18,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
if matches!(vim.mode, Mode::VisualBlock | Mode::Visual) { if matches!(vim.mode, Mode::VisualBlock | Mode::Visual) {
vim.switch_mode(Mode::VisualLine, false, cx) vim.switch_mode(Mode::VisualLine, false, cx)
} }
let count = vim.take_count(cx); let count = Vim::take_count(cx);
vim.substitute(count, true, cx) vim.substitute(count, true, cx)
}); });
} }

View file

@ -22,7 +22,7 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
if vim.mode != Mode::Replace { if vim.mode != Mode::Replace {
return; return;
} }
let count = vim.take_count(cx); let count = Vim::take_count(cx);
vim.undo_replace(count, cx) vim.undo_replace(count, cx)
}); });
} }

View file

@ -10,7 +10,7 @@ actions!(vim, [Rewrap]);
pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, _: &Rewrap, cx| { Vim::action(editor, cx, |vim, _: &Rewrap, cx| {
vim.record_current_action(cx); vim.record_current_action(cx);
vim.take_count(cx); Vim::take_count(cx);
vim.store_visual_marks(cx); vim.store_visual_marks(cx);
vim.update_editor(cx, |vim, editor, cx| { vim.update_editor(cx, |vim, editor, cx| {
editor.transact(cx, |editor, cx| { editor.transact(cx, |editor, cx| {

View file

@ -150,6 +150,11 @@ pub struct VimGlobals {
pub dot_recording: bool, pub dot_recording: bool,
pub dot_replaying: bool, pub dot_replaying: bool,
/// pre_count is the number before an operator is specified (3 in 3d2d)
pub pre_count: Option<usize>,
/// post_count is the number after an operator is specified (2 in 3d2d)
pub post_count: Option<usize>,
pub stop_recording_after_next_action: bool, pub stop_recording_after_next_action: bool,
pub ignore_current_insertion: bool, pub ignore_current_insertion: bool,
pub recorded_count: Option<usize>, pub recorded_count: Option<usize>,

View file

@ -35,7 +35,7 @@ impl Vim {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.stop_recording(cx); self.stop_recording(cx);
let count = self.take_count(cx); let count = Vim::take_count(cx);
let mode = self.mode; let mode = self.mode;
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
let text_layout_details = editor.text_layout_details(cx); let text_layout_details = editor.text_layout_details(cx);

View file

@ -25,8 +25,8 @@ use editor::{
Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint, Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
}; };
use gpui::{ use gpui::{
actions, impl_actions, Action, AppContext, Entity, EventEmitter, KeyContext, KeystrokeEvent, actions, impl_actions, Action, AppContext, Axis, Entity, EventEmitter, KeyContext,
Render, Subscription, View, ViewContext, WeakView, KeystrokeEvent, Render, Subscription, View, ViewContext, WeakView,
}; };
use insert::{NormalBefore, TemporaryNormal}; use insert::{NormalBefore, TemporaryNormal};
use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId}; use language::{CursorShape, Point, Selection, SelectionGoal, TransactionId};
@ -40,12 +40,17 @@ use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals}; use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use std::{mem, ops::Range, sync::Arc}; use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType; use surrounds::SurroundsType;
use theme::ThemeSettings;
use ui::{IntoElement, VisualContext}; use ui::{IntoElement, VisualContext};
use vim_mode_setting::VimModeSetting; use vim_mode_setting::VimModeSetting;
use workspace::{self, Pane, Workspace}; use workspace::{self, Pane, ResizeIntent, Workspace};
use crate::state::ReplayableAction; use crate::state::ReplayableAction;
/// Used to resize the current pane
#[derive(Clone, Deserialize, PartialEq)]
pub struct ResizePane(pub ResizeIntent);
/// An Action to Switch between modes /// An Action to Switch between modes
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq)]
pub struct SwitchMode(pub Mode); pub struct SwitchMode(pub Mode);
@ -81,7 +86,10 @@ actions!(
// in the workspace namespace so it's not filtered out when vim is disabled. // in the workspace namespace so it's not filtered out when vim is disabled.
actions!(workspace, [ToggleVimMode]); actions!(workspace, [ToggleVimMode]);
impl_actions!(vim, [SwitchMode, PushOperator, Number, SelectRegister]); impl_actions!(
vim,
[ResizePane, SwitchMode, PushOperator, Number, SelectRegister]
);
/// Initializes the `vim` crate. /// Initializes the `vim` crate.
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
@ -109,6 +117,30 @@ pub fn init(cx: &mut AppContext) {
}); });
}); });
workspace.register_action(|workspace, action: &ResizePane, cx| {
let count = Vim::take_count(cx.window_context()).unwrap_or(1) as f32;
let theme = ThemeSettings::get_global(cx);
let Ok(font_id) = cx.text_system().font_id(&theme.buffer_font) else {
return;
};
let Ok(width) = cx
.text_system()
.advance(font_id, theme.buffer_font_size(cx), 'm')
else {
return;
};
let height = theme.buffer_font_size(cx) * theme.buffer_line_height.value();
let (axis, amount) = match action.0 {
ResizeIntent::Lengthen => (Axis::Vertical, height),
ResizeIntent::Shorten => (Axis::Vertical, height * -1.),
ResizeIntent::Widen => (Axis::Horizontal, width.width),
ResizeIntent::Narrow => (Axis::Horizontal, width.width * -1.),
};
workspace.resize_pane(axis, amount * count, cx);
});
workspace.register_action(|workspace, _: &SearchSubmit, cx| { workspace.register_action(|workspace, _: &SearchSubmit, cx| {
let vim = workspace let vim = workspace
.focused_pane(cx) .focused_pane(cx)
@ -131,7 +163,7 @@ pub(crate) struct VimAddon {
impl editor::Addon for VimAddon { impl editor::Addon for VimAddon {
fn extend_key_context(&self, key_context: &mut KeyContext, cx: &AppContext) { fn extend_key_context(&self, key_context: &mut KeyContext, cx: &AppContext) {
self.view.read(cx).extend_key_context(key_context) self.view.read(cx).extend_key_context(key_context, cx)
} }
fn to_any(&self) -> &dyn std::any::Any { fn to_any(&self) -> &dyn std::any::Any {
@ -146,11 +178,6 @@ pub(crate) struct Vim {
pub temp_mode: bool, pub temp_mode: bool,
pub exit_temporary_mode: bool, pub exit_temporary_mode: bool,
/// pre_count is the number before an operator is specified (3 in 3d2d)
pre_count: Option<usize>,
/// post_count is the number after an operator is specified (2 in 3d2d)
post_count: Option<usize>,
operator_stack: Vec<Operator>, operator_stack: Vec<Operator>,
pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>, pub(crate) replacements: Vec<(Range<editor::Anchor>, String)>,
@ -197,8 +224,6 @@ impl Vim {
last_mode: Mode::Normal, last_mode: Mode::Normal,
temp_mode: false, temp_mode: false,
exit_temporary_mode: false, exit_temporary_mode: false,
pre_count: None,
post_count: None,
operator_stack: Vec::new(), operator_stack: Vec::new(),
replacements: Vec::new(), replacements: Vec::new(),
@ -471,7 +496,7 @@ impl Vim {
self.current_anchor.take(); self.current_anchor.take();
} }
if mode != Mode::Insert && mode != Mode::Replace { if mode != Mode::Insert && mode != Mode::Replace {
self.take_count(cx); Vim::take_count(cx);
} }
// Sync editor settings like clip mode // Sync editor settings like clip mode
@ -551,22 +576,24 @@ impl Vim {
}); });
} }
fn take_count(&mut self, cx: &mut ViewContext<Self>) -> Option<usize> { pub fn take_count(cx: &mut AppContext) -> Option<usize> {
let global_state = cx.global_mut::<VimGlobals>(); let global_state = cx.global_mut::<VimGlobals>();
if global_state.dot_replaying { if global_state.dot_replaying {
return global_state.recorded_count; return global_state.recorded_count;
} }
let count = if self.post_count.is_none() && self.pre_count.is_none() { let count = if global_state.post_count.is_none() && global_state.pre_count.is_none() {
return None; return None;
} else { } else {
Some(self.post_count.take().unwrap_or(1) * self.pre_count.take().unwrap_or(1)) Some(
global_state.post_count.take().unwrap_or(1)
* global_state.pre_count.take().unwrap_or(1),
)
}; };
if global_state.dot_recording { if global_state.dot_recording {
global_state.recorded_count = count; global_state.recorded_count = count;
} }
self.sync_vim_settings(cx);
count count
} }
@ -613,7 +640,7 @@ impl Vim {
} }
} }
pub fn extend_key_context(&self, context: &mut KeyContext) { pub fn extend_key_context(&self, context: &mut KeyContext, cx: &AppContext) {
let mut mode = match self.mode { let mut mode = match self.mode {
Mode::Normal => "normal", Mode::Normal => "normal",
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual", Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
@ -625,8 +652,8 @@ impl Vim {
let mut operator_id = "none"; let mut operator_id = "none";
let active_operator = self.active_operator(); let active_operator = self.active_operator();
if active_operator.is_none() && self.pre_count.is_some() if active_operator.is_none() && cx.global::<VimGlobals>().pre_count.is_some()
|| active_operator.is_some() && self.post_count.is_some() || active_operator.is_some() && cx.global::<VimGlobals>().post_count.is_some()
{ {
context.add("VimCount"); context.add("VimCount");
} }
@ -837,18 +864,18 @@ impl Vim {
fn push_count_digit(&mut self, number: usize, cx: &mut ViewContext<Self>) { fn push_count_digit(&mut self, number: usize, cx: &mut ViewContext<Self>) {
if self.active_operator().is_some() { if self.active_operator().is_some() {
let post_count = self.post_count.unwrap_or(0); let post_count = Vim::globals(cx).post_count.unwrap_or(0);
self.post_count = Some( Vim::globals(cx).post_count = Some(
post_count post_count
.checked_mul(10) .checked_mul(10)
.and_then(|post_count| post_count.checked_add(number)) .and_then(|post_count| post_count.checked_add(number))
.unwrap_or(post_count), .unwrap_or(post_count),
) )
} else { } else {
let pre_count = self.pre_count.unwrap_or(0); let pre_count = Vim::globals(cx).pre_count.unwrap_or(0);
self.pre_count = Some( Vim::globals(cx).pre_count = Some(
pre_count pre_count
.checked_mul(10) .checked_mul(10)
.and_then(|pre_count| pre_count.checked_add(number)) .and_then(|pre_count| pre_count.checked_add(number))
@ -880,7 +907,7 @@ impl Vim {
} }
fn clear_operator(&mut self, cx: &mut ViewContext<Self>) { fn clear_operator(&mut self, cx: &mut ViewContext<Self>) {
self.take_count(cx); Vim::take_count(cx);
self.selected_register.take(); self.selected_register.take();
self.operator_stack.clear(); self.operator_stack.clear();
self.sync_vim_settings(cx); self.sync_vim_settings(cx);

View file

@ -538,9 +538,8 @@ impl Vim {
} }
pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) { pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
let count = self let count =
.take_count(cx) Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
.unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
editor.set_clip_at_line_ends(false, cx); editor.set_clip_at_line_ends(false, cx);
for _ in 0..count { for _ in 0..count {
@ -556,9 +555,8 @@ impl Vim {
} }
pub fn select_previous(&mut self, _: &SelectPrevious, cx: &mut ViewContext<Self>) { pub fn select_previous(&mut self, _: &SelectPrevious, cx: &mut ViewContext<Self>) {
let count = self let count =
.take_count(cx) Vim::take_count(cx).unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
.unwrap_or_else(|| if self.mode.is_visual() { 1 } else { 2 });
self.update_editor(cx, |_, editor, cx| { self.update_editor(cx, |_, editor, cx| {
for _ in 0..count { for _ in 0..count {
if editor if editor
@ -573,7 +571,7 @@ impl Vim {
} }
pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) { pub fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
let count = self.take_count(cx).unwrap_or(1); let count = Vim::take_count(cx).unwrap_or(1);
let Some(pane) = self.pane(cx) else { let Some(pane) = self.pane(cx) else {
return; return;
}; };

View file

@ -8,8 +8,8 @@ use call::{ActiveCall, ParticipantLocation};
use client::proto::PeerId; use client::proto::PeerId;
use collections::HashMap; use collections::HashMap;
use gpui::{ use gpui::{
point, size, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton, Pixels, point, size, Along, AnyView, AnyWeakView, Axis, Bounds, IntoElement, Model, MouseButton,
Point, StyleRefinement, View, ViewContext, Pixels, Point, StyleRefinement, View, ViewContext,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use project::Project; use project::Project;
@ -90,6 +90,21 @@ impl PaneGroup {
} }
} }
pub fn resize(
&mut self,
pane: &View<Pane>,
direction: Axis,
amount: Pixels,
bounds: &Bounds<Pixels>,
) {
match &mut self.root {
Member::Pane(_) => {}
Member::Axis(axis) => {
let _ = axis.resize(pane, direction, amount, bounds);
}
};
}
pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) { pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
match &mut self.root { match &mut self.root {
Member::Pane(_) => {} Member::Pane(_) => {}
@ -445,6 +460,116 @@ impl PaneAxis {
} }
} }
fn resize(
&mut self,
pane: &View<Pane>,
axis: Axis,
amount: Pixels,
bounds: &Bounds<Pixels>,
) -> Option<bool> {
let container_size = self
.bounding_boxes
.lock()
.iter()
.filter_map(|e| *e)
.reduce(|acc, e| acc.union(&e))
.unwrap_or(*bounds)
.size;
let found_pane = self
.members
.iter()
.any(|member| matches!(member, Member::Pane(p) if p == pane));
if found_pane && self.axis != axis {
return Some(false); // pane found but this is not the correct axis direction
}
let mut found_axis_index: Option<usize> = None;
if !found_pane {
for (i, pa) in self.members.iter_mut().enumerate() {
if let Member::Axis(pa) = pa {
if let Some(done) = pa.resize(pane, axis, amount, bounds) {
if done {
return Some(true); // pane found and operations already done
} else if self.axis != axis {
return Some(false); // pane found but this is not the correct axis direction
} else {
found_axis_index = Some(i); // pane found and this is correct direction
}
}
}
}
found_axis_index?; // no pane found
}
let min_size = match axis {
Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
Axis::Vertical => px(VERTICAL_MIN_SIZE),
};
let mut flexes = self.flexes.lock();
let ix = if found_pane {
self.members.iter().position(|m| {
if let Member::Pane(p) = m {
p == pane
} else {
false
}
})
} else {
found_axis_index
};
if ix.is_none() {
return Some(true);
}
let ix = ix.unwrap_or(0);
let size = move |ix, flexes: &[f32]| {
container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
};
// Don't allow resizing to less than the minimum size, if elements are already too small
if min_size - px(1.) > size(ix, flexes.as_slice()) {
return Some(true);
}
let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
let flex_change = flexes.len() as f32 * pixel_dx / container_size.along(axis);
let current_target_flex = flexes[target_ix] + flex_change;
let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
(current_target_flex, next_target_flex)
};
let apply_changes =
|current_ix: usize, proposed_current_pixel_change: Pixels, flexes: &mut [f32]| {
let next_target_size = Pixels::max(
size(current_ix + 1, flexes) - proposed_current_pixel_change,
min_size,
);
let current_target_size = Pixels::max(
size(current_ix, flexes) + size(current_ix + 1, flexes) - next_target_size,
min_size,
);
let current_pixel_change = current_target_size - size(current_ix, flexes);
let (current_target_flex, next_target_flex) =
flex_changes(current_pixel_change, current_ix, 1, flexes);
flexes[current_ix] = current_target_flex;
flexes[current_ix + 1] = next_target_flex;
};
if ix + 1 == flexes.len() {
apply_changes(ix - 1, -1.0 * amount, flexes.as_mut_slice());
} else {
apply_changes(ix, amount, flexes.as_mut_slice());
}
Some(true)
}
fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) { fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
for member in self.members.iter_mut() { for member in self.members.iter_mut() {
match member { match member {
@ -625,6 +750,14 @@ impl SplitDirection {
} }
} }
#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
pub enum ResizeIntent {
Lengthen,
Shorten,
Widen,
Narrow,
}
mod element { mod element {
use std::mem; use std::mem;

View file

@ -2988,6 +2988,12 @@ impl Workspace {
} }
} }
pub fn resize_pane(&mut self, axis: gpui::Axis, amount: Pixels, cx: &mut ViewContext<Self>) {
self.center
.resize(&self.active_pane.clone(), axis, amount, &self.bounds);
cx.notify();
}
fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) { fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
// This is explicitly hoisted out of the following check for pane identity as // This is explicitly hoisted out of the following check for pane identity as
// terminal panel panes are not registered as a center panes. // terminal panel panes are not registered as a center panes.