2022-04-21 23:14:58 +00:00
#[ cfg(test) ]
mod vim_test_context ;
2022-03-25 02:24:36 +00:00
mod editor_events ;
mod insert ;
2022-04-15 23:00:44 +00:00
mod motion ;
2022-03-25 02:24:36 +00:00
mod normal ;
2022-04-15 23:00:44 +00:00
mod state ;
2022-05-20 00:42:30 +00:00
mod utils ;
2022-05-03 17:29:57 +00:00
mod visual ;
2022-03-25 02:24:36 +00:00
use collections ::HashMap ;
2022-06-28 20:35:43 +00:00
use command_palette ::CommandPaletteFilter ;
2022-05-24 20:35:57 +00:00
use editor ::{ Bias , CursorShape , Editor , Input } ;
2022-05-18 18:10:24 +00:00
use gpui ::{ impl_actions , MutableAppContext , Subscription , ViewContext , WeakViewHandle } ;
2022-04-08 22:32:56 +00:00
use serde ::Deserialize ;
2022-03-25 02:24:36 +00:00
2022-04-06 00:10:17 +00:00
use settings ::Settings ;
2022-04-15 23:00:44 +00:00
use state ::{ Mode , Operator , VimState } ;
2022-04-06 00:10:17 +00:00
use workspace ::{ self , Workspace } ;
2022-03-25 02:24:36 +00:00
2022-06-06 07:18:44 +00:00
#[ derive(Clone, Deserialize, PartialEq) ]
2022-04-07 23:20:49 +00:00
pub struct SwitchMode ( pub Mode ) ;
2022-06-06 07:18:44 +00:00
#[ derive(Clone, Deserialize, PartialEq) ]
2022-04-15 23:00:44 +00:00
pub struct PushOperator ( pub Operator ) ;
impl_actions! ( vim , [ SwitchMode , PushOperator ] ) ;
2022-03-25 02:24:36 +00:00
pub fn init ( cx : & mut MutableAppContext ) {
editor_events ::init ( cx ) ;
2022-04-21 23:14:58 +00:00
normal ::init ( cx ) ;
2022-05-03 17:29:57 +00:00
visual ::init ( cx ) ;
2022-03-25 02:24:36 +00:00
insert ::init ( cx ) ;
2022-04-15 23:00:44 +00:00
motion ::init ( cx ) ;
2022-03-25 02:24:36 +00:00
2022-04-15 23:00:44 +00:00
cx . add_action ( | _ : & mut Workspace , & SwitchMode ( mode ) : & SwitchMode , cx | {
Vim ::update ( cx , | vim , cx | vim . switch_mode ( mode , cx ) )
2022-03-26 20:30:55 +00:00
} ) ;
2022-04-15 23:00:44 +00:00
cx . add_action (
| _ : & mut Workspace , & PushOperator ( operator ) : & PushOperator , cx | {
Vim ::update ( cx , | vim , cx | vim . push_operator ( operator , cx ) )
} ,
) ;
2022-05-23 18:04:26 +00:00
cx . add_action ( | _ : & mut Editor , _ : & Input , cx | {
if Vim ::read ( cx ) . active_operator ( ) . is_some ( ) {
2022-05-24 20:35:57 +00:00
// Defer without updating editor
MutableAppContext ::defer ( cx , | cx | Vim ::update ( cx , | vim , cx | vim . clear_operator ( cx ) ) )
2022-05-23 18:04:26 +00:00
} else {
cx . propagate_action ( )
}
} ) ;
2022-03-25 02:24:36 +00:00
2022-06-29 18:58:12 +00:00
// Sync initial settings with the rest of the app
Vim ::update ( cx , | state , cx | state . sync_vim_settings ( cx ) ) ;
// Any time settings change, update vim mode to match
2022-05-23 16:23:25 +00:00
cx . observe_global ::< Settings , _ > ( | cx | {
Vim ::update ( cx , | state , cx | {
state . set_enabled ( cx . global ::< Settings > ( ) . vim_mode , cx )
} )
2022-03-26 20:30:55 +00:00
} )
. detach ( ) ;
2022-03-25 02:24:36 +00:00
}
#[ derive(Default) ]
2022-04-15 23:00:44 +00:00
pub struct Vim {
2022-03-25 02:24:36 +00:00
editors : HashMap < usize , WeakViewHandle < Editor > > ,
active_editor : Option < WeakViewHandle < Editor > > ,
2022-05-18 18:10:24 +00:00
selection_subscription : Option < Subscription > ,
2022-03-25 02:24:36 +00:00
enabled : bool ,
2022-04-15 23:00:44 +00:00
state : VimState ,
2022-03-25 02:24:36 +00:00
}
2022-04-15 23:00:44 +00:00
impl Vim {
fn read ( cx : & mut MutableAppContext ) -> & Self {
cx . default_global ( )
}
fn update < F , S > ( cx : & mut MutableAppContext , update : F ) -> S
2022-03-26 20:30:55 +00:00
where
F : FnOnce ( & mut Self , & mut MutableAppContext ) -> S ,
{
cx . update_default_global ( update )
}
2022-03-25 02:24:36 +00:00
fn update_active_editor < S > (
2022-03-26 20:30:55 +00:00
& self ,
2022-03-25 02:24:36 +00:00
cx : & mut MutableAppContext ,
update : impl FnOnce ( & mut Editor , & mut ViewContext < Editor > ) -> S ,
) -> Option < S > {
2022-03-26 20:30:55 +00:00
self . active_editor
2022-03-25 02:24:36 +00:00
. clone ( )
. and_then ( | ae | ae . upgrade ( cx ) )
. map ( | ae | ae . update ( cx , update ) )
}
2022-04-15 23:00:44 +00:00
fn switch_mode ( & mut self , mode : Mode , cx : & mut MutableAppContext ) {
self . state . mode = mode ;
self . state . operator_stack . clear ( ) ;
2022-06-29 18:58:12 +00:00
self . sync_vim_settings ( cx ) ;
2022-03-25 02:24:36 +00:00
}
2022-04-15 23:00:44 +00:00
fn push_operator ( & mut self , operator : Operator , cx : & mut MutableAppContext ) {
self . state . operator_stack . push ( operator ) ;
2022-06-29 18:58:12 +00:00
self . sync_vim_settings ( cx ) ;
2022-04-15 23:00:44 +00:00
}
fn pop_operator ( & mut self , cx : & mut MutableAppContext ) -> Operator {
let popped_operator = self . state . operator_stack . pop ( ) . expect ( " Operator popped when no operator was on the stack. This likely means there is an invalid keymap config " ) ;
2022-06-29 18:58:12 +00:00
self . sync_vim_settings ( cx ) ;
2022-04-15 23:00:44 +00:00
popped_operator
}
fn clear_operator ( & mut self , cx : & mut MutableAppContext ) {
self . state . operator_stack . clear ( ) ;
2022-06-29 18:58:12 +00:00
self . sync_vim_settings ( cx ) ;
2022-04-15 23:00:44 +00:00
}
2022-05-23 18:04:26 +00:00
fn active_operator ( & self ) -> Option < Operator > {
2022-04-15 23:00:44 +00:00
self . state . operator_stack . last ( ) . copied ( )
}
2022-03-26 20:30:55 +00:00
fn set_enabled ( & mut self , enabled : bool , cx : & mut MutableAppContext ) {
if self . enabled ! = enabled {
self . enabled = enabled ;
2022-04-15 23:00:44 +00:00
self . state = Default ::default ( ) ;
2022-03-27 00:38:00 +00:00
if enabled {
2022-04-15 23:00:44 +00:00
self . state . mode = Mode ::Normal ;
2022-03-27 00:38:00 +00:00
}
2022-06-29 18:58:12 +00:00
self . sync_vim_settings ( cx ) ;
2022-03-26 20:30:55 +00:00
}
2022-03-25 02:24:36 +00:00
}
2022-06-29 18:58:12 +00:00
fn sync_vim_settings ( & self , cx : & mut MutableAppContext ) {
2022-04-15 23:00:44 +00:00
let state = & self . state ;
let cursor_shape = state . cursor_shape ( ) ;
2022-05-23 18:04:26 +00:00
2022-06-29 18:58:12 +00:00
cx . update_default_global ::< CommandPaletteFilter , _ , _ > ( | filter , _ | {
if self . enabled {
filter . filtered_namespaces . remove ( " vim " ) ;
} else {
filter . filtered_namespaces . insert ( " vim " ) ;
}
} ) ;
2022-03-26 20:30:55 +00:00
for editor in self . editors . values ( ) {
if let Some ( editor ) = editor . upgrade ( cx ) {
editor . update ( cx , | editor , cx | {
if self . enabled {
editor . set_cursor_shape ( cursor_shape , cx ) ;
2022-05-23 18:04:26 +00:00
editor . set_clip_at_line_ends ( state . clip_at_line_end ( ) , cx ) ;
2022-04-15 23:00:44 +00:00
editor . set_input_enabled ( ! state . vim_controlled ( ) ) ;
2022-05-24 20:35:57 +00:00
editor . selections . line_mode =
matches! ( state . mode , Mode ::Visual { line : true } ) ;
2022-04-15 23:00:44 +00:00
let context_layer = state . keymap_context_layer ( ) ;
2022-03-26 20:30:55 +00:00
editor . set_keymap_context_layer ::< Self > ( context_layer ) ;
2022-06-06 06:14:49 +00:00
editor . change_selections ( None , cx , | s | {
s . move_with ( | map , selection | {
selection . set_head (
map . clip_point ( selection . head ( ) , Bias ::Left ) ,
selection . goal ,
) ;
if state . empty_selections_only ( ) {
selection . collapse_to ( selection . head ( ) , selection . goal )
}
} ) ;
} )
2022-03-26 20:30:55 +00:00
} else {
editor . set_cursor_shape ( CursorShape ::Bar , cx ) ;
editor . set_clip_at_line_ends ( false , cx ) ;
editor . set_input_enabled ( true ) ;
2022-05-19 00:41:26 +00:00
editor . selections . line_mode = false ;
2022-03-26 20:30:55 +00:00
editor . remove_keymap_context_layer ::< Self > ( ) ;
2022-03-25 02:24:36 +00:00
}
2022-03-26 20:30:55 +00:00
} ) ;
}
}
2022-03-25 02:24:36 +00:00
}
}
2022-03-28 00:58:28 +00:00
#[ cfg(test) ]
mod test {
2022-04-15 23:00:44 +00:00
use crate ::{ state ::Mode , vim_test_context ::VimTestContext } ;
2022-03-28 00:58:28 +00:00
#[ gpui::test ]
async fn test_initially_disabled ( cx : & mut gpui ::TestAppContext ) {
2022-04-21 23:14:58 +00:00
let mut cx = VimTestContext ::new ( cx , false ) . await ;
2022-04-15 23:00:44 +00:00
cx . simulate_keystrokes ( [ " h " , " j " , " k " , " l " ] ) ;
2022-03-28 00:58:28 +00:00
cx . assert_editor_state ( " hjkl| " ) ;
}
#[ gpui::test ]
async fn test_toggle_through_settings ( cx : & mut gpui ::TestAppContext ) {
2022-04-21 23:14:58 +00:00
let mut cx = VimTestContext ::new ( cx , true ) . await ;
2022-03-28 00:58:28 +00:00
cx . simulate_keystroke ( " i " ) ;
assert_eq! ( cx . mode ( ) , Mode ::Insert ) ;
// Editor acts as though vim is disabled
cx . disable_vim ( ) ;
2022-04-15 23:00:44 +00:00
cx . simulate_keystrokes ( [ " h " , " j " , " k " , " l " ] ) ;
2022-03-28 00:58:28 +00:00
cx . assert_editor_state ( " hjkl| " ) ;
2022-06-06 06:14:49 +00:00
// Selections aren't changed if editor is blurred but vim-mode is still disabled.
cx . set_state ( " [hjkl} " , Mode ::Normal ) ;
cx . assert_editor_state ( " [hjkl} " ) ;
cx . update_editor ( | _ , cx | cx . blur ( ) ) ;
cx . assert_editor_state ( " [hjkl} " ) ;
cx . update_editor ( | _ , cx | cx . focus_self ( ) ) ;
cx . assert_editor_state ( " [hjkl} " ) ;
2022-03-28 00:58:28 +00:00
// Enabling dynamically sets vim mode again and restores normal mode
cx . enable_vim ( ) ;
2022-04-15 23:00:44 +00:00
assert_eq! ( cx . mode ( ) , Mode ::Normal ) ;
cx . simulate_keystrokes ( [ " h " , " h " , " h " , " l " ] ) ;
2022-06-09 17:26:09 +00:00
assert_eq! ( cx . buffer_text ( ) , " hjkl " . to_owned ( ) ) ;
2022-05-24 20:35:57 +00:00
cx . assert_editor_state ( " h|jkl " ) ;
2022-04-15 23:00:44 +00:00
cx . simulate_keystrokes ( [ " i " , " T " , " e " , " s " , " t " ] ) ;
2022-05-24 20:35:57 +00:00
cx . assert_editor_state ( " hTest|jkl " ) ;
2022-03-28 00:58:28 +00:00
// Disabling and enabling resets to normal mode
assert_eq! ( cx . mode ( ) , Mode ::Insert ) ;
cx . disable_vim ( ) ;
cx . enable_vim ( ) ;
2022-04-15 23:00:44 +00:00
assert_eq! ( cx . mode ( ) , Mode ::Normal ) ;
2022-03-28 00:58:28 +00:00
}
}