use std::{ iter::Peekable, ops::{Deref, Range}, str::Chars, sync::OnceLock,}; use anyhow::{anyhow, Result};use command_palette_hooks::CommandInterceptResult;use editor::{ actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive}, Editor, ToMultiBufferPoint,};use gpui::{actions, impl_actions, Action, AppContext, Global, ViewContext};use language::Point;use multi_buffer::MultiBufferRow;use serde::Deserialize;use ui::WindowContext;use util::ResultExt;use workspace::{notifications::NotifyResultExt, SaveIntent}; use crate::{ motion::{EndOfDocument, Motion, StartOfDocument}, normal::{ search::{FindCommand, ReplaceCommand, Replacement}, JoinLines, }, state::Mode, visual::VisualDeleteLine, Vim,}; #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct GoToLine { range: CommandRange,} #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct YankCommand { range: CommandRange,} #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct WithRange { restore_selection: bool, range: CommandRange, action: WrappedAction,} #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct WithCount { count: u32, action: WrappedAction,} #[derive(Debug)]struct WrappedAction(Box<dyn Action>); actions!(vim, [VisualCommand, CountCommand]);impl_actions!(vim, [GoToLine, YankCommand, WithRange, WithCount]); impl<'de> Deserialize<'de> for WrappedAction { fn deserialize<D>(_: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { Err(serde::de::Error::custom("Cannot deserialize WrappedAction")) }} impl PartialEq for WrappedAction { fn eq(&self, other: &Self) -> bool { self.0.partial_eq(&*other.0) }} impl Clone for WrappedAction { fn clone(&self) -> Self { Self(self.0.boxed_clone()) }} impl Deref for WrappedAction { type Target = dyn Action; fn deref(&self) -> &dyn Action { &*self.0 }} pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { Vim::action(editor, cx, |vim, _: &VisualCommand, cx| { let Some(workspace) = vim.workspace(cx) else { return; }; workspace.update(cx, |workspace, cx| { command_palette::CommandPalette::toggle(workspace, "'<,'>", cx); }) }); Vim::action(editor, cx, |vim, _: &CountCommand, cx| { let Some(workspace) = vim.workspace(cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); workspace.update(cx, |workspace, cx| { command_palette::CommandPalette::toggle( workspace, &format!(".,.+{}", count.saturating_sub(1)), cx, ); }) }); Vim::action(editor, cx, |vim, action: &GoToLine, cx| { vim.switch_mode(Mode::Normal, false, cx); let result = vim.update_editor(cx, |vim, editor, cx| { action.range.head().buffer_row(vim, editor, cx) }); let buffer_row = match result { None => return, Some(e @ Err(_)) => { let Some(workspace) = vim.workspace(cx) else { return; }; workspace.update(cx, |workspace, cx| { e.notify_err(workspace, cx); }); return; } Some(Ok(result)) => result, }; vim.move_cursor(Motion::StartOfDocument, Some(buffer_row.0 as usize + 1), cx); }); Vim::action(editor, cx, |vim, action: &YankCommand, cx| { vim.update_editor(cx, |vim, editor, cx| { let snapshot = editor.snapshot(cx); if let Ok(range) = action.range.buffer_range(vim, editor, cx) { let end = if range.end < snapshot.max_buffer_row() { Point::new(range.end.0 + 1, 0) } else { snapshot.buffer_snapshot.max_point() }; vim.copy_ranges( editor, true, true, vec![Point::new(range.start.0, 0)..end], cx, ) } }); }); Vim::action(editor, cx, |_, action: &WithCount, cx| { for _ in 0..action.count { cx.dispatch_action(action.action.boxed_clone()) } }); Vim::action(editor, cx, |vim, action: &WithRange, cx| { let result = vim.update_editor(cx, |vim, editor, cx| { action.range.buffer_range(vim, editor, cx) }); let range = match result { None => return, Some(e @ Err(_)) => { let Some(workspace) = vim.workspace(cx) else { return; }; workspace.update(cx, |workspace, cx| { e.notify_err(workspace, cx); }); return; } Some(Ok(result)) => result, }; let previous_selections = vim .update_editor(cx, |_, editor, cx| { let selections = action .restore_selection .then(|| editor.selections.disjoint_anchor_ranges()); editor.change_selections(None, cx, |s| { let end = Point::new(range.end.0, s.buffer().line_len(range.end)); s.select_ranges([end..Point::new(range.start.0, 0)]); }); selections }) .flatten(); cx.dispatch_action(action.action.boxed_clone()); cx.defer(move |vim, cx| { vim.update_editor(cx, |_, editor, cx| { editor.change_selections(None, cx, |s| { if let Some(previous_selections) = previous_selections { s.select_ranges(previous_selections); } else { s.select_ranges([ Point::new(range.start.0, 0)..Point::new(range.start.0, 0) ]); } }) }); }); });} #[derive(Default)]struct VimCommand { prefix: &'static str, suffix: &'static str, action: Option<Box<dyn Action>>, action_name: Option<&'static str>, bang_action: Option<Box<dyn Action>>, range: Option< Box< dyn Fn(Box<dyn Action>, &CommandRange) -> Option<Box<dyn Action>> + Send + Sync + 'static, >, >, has_count: bool,} impl VimCommand { fn new(pattern: (&'static str, &'static str), action: impl Action) -> Self { Self { prefix: pattern.0, suffix: pattern.1, action: Some(action.boxed_clone()), ..Default::default() } } // from_str is used for actions in other crates. fn str(pattern: (&'static str, &'static str), action_name: &'static str) -> Self { Self { prefix: pattern.0, suffix: pattern.1, action_name: Some(action_name), ..Default::default() } } fn bang(mut self, bang_action: impl Action) -> Self { self.bang_action = Some(bang_action.boxed_clone()); self } fn range( mut self, f: impl Fn(Box<dyn Action>, &CommandRange) -> Option<Box<dyn Action>> + Send + Sync + 'static, ) -> Self { self.range = Some(Box::new(f)); self } fn count(mut self) -> Self { self.has_count = true; self } fn parse( &self, mut query: &str, range: &Option<CommandRange>, cx: &AppContext, ) -> Option<Box<dyn Action>> { let has_bang = query.ends_with('!'); if has_bang { query = &query[..query.len() - 1]; } let suffix = query.strip_prefix(self.prefix)?; if !self.suffix.starts_with(suffix) { return None; } let action = if has_bang && self.bang_action.is_some() { self.bang_action.as_ref().unwrap().boxed_clone() } else if let Some(action) = self.action.as_ref() { action.boxed_clone() } else if let Some(action_name) = self.action_name { cx.build_action(action_name, None).log_err()? } else { return None; }; if let Some(range) = range { self.range.as_ref().and_then(|f| f(action, range)) } else { Some(action) } } // TODO: ranges with search queries fn parse_range(query: &str) -> (Option<CommandRange>, String) { let mut chars = query.chars().peekable(); match chars.peek() { Some('%') => { chars.next(); return ( Some(CommandRange { start: Position::Line { row: 1, offset: 0 }, end: Some(Position::LastLine { offset: 0 }), }), chars.collect(), ); } Some('*') => { chars.next(); return ( Some(CommandRange { start: Position::Mark { name: '<', offset: 0, }, end: Some(Position::Mark { name: '>', offset: 0, }), }), chars.collect(), ); } _ => {} } let start = Self::parse_position(&mut chars); match chars.peek() { Some(',' | ';') => { chars.next(); ( Some(CommandRange { start: start.unwrap_or(Position::CurrentLine { offset: 0 }), end: Self::parse_position(&mut chars), }), chars.collect(), ) } _ => ( start.map(|start| CommandRange { start, end: None }), chars.collect(), ), } } fn parse_position(chars: &mut Peekable<Chars>) -> Option<Position> { match chars.peek()? { '0'..='9' => { let row = Self::parse_u32(chars); Some(Position::Line { row, offset: Self::parse_offset(chars), }) } '\'' => { chars.next(); let name = chars.next()?; Some(Position::Mark { name, offset: Self::parse_offset(chars), }) } '.' => { chars.next(); Some(Position::CurrentLine { offset: Self::parse_offset(chars), }) } '+' | '-' => Some(Position::CurrentLine { offset: Self::parse_offset(chars), }), '$' => { chars.next(); Some(Position::LastLine { offset: Self::parse_offset(chars), }) } _ => None, } } fn parse_offset(chars: &mut Peekable<Chars>) -> i32 { let mut res: i32 = 0; while matches!(chars.peek(), Some('+' | '-')) { let sign = if chars.next().unwrap() == '+' { 1 } else { -1 }; let amount = if matches!(chars.peek(), Some('0'..='9')) { (Self::parse_u32(chars) as i32).saturating_mul(sign) } else { sign }; res = res.saturating_add(amount) } res } fn parse_u32(chars: &mut Peekable<Chars>) -> u32 { let mut res: u32 = 0; while matches!(chars.peek(), Some('0'..='9')) { res = res .saturating_mul(10) .saturating_add(chars.next().unwrap() as u32 - '0' as u32); } res }} #[derive(Debug, Clone, PartialEq, Deserialize)]enum Position { Line { row: u32, offset: i32 }, Mark { name: char, offset: i32 }, LastLine { offset: i32 }, CurrentLine { offset: i32 },} impl Position { fn buffer_row( &self, vim: &Vim, editor: &mut Editor, cx: &mut WindowContext, ) -> Result<MultiBufferRow> { let snapshot = editor.snapshot(cx); let target = match self { Position::Line { row, offset } => row.saturating_add_signed(offset.saturating_sub(1)), Position::Mark { name, offset } => { let Some(mark) = vim.marks.get(&name.to_string()).and_then(|vec| vec.last()) else { return Err(anyhow!("mark {} not set", name)); }; mark.to_point(&snapshot.buffer_snapshot) .row .saturating_add_signed(*offset) } Position::LastLine { offset } => { snapshot.max_buffer_row().0.saturating_add_signed(*offset) } Position::CurrentLine { offset } => editor .selections .newest_anchor() .head() .to_point(&snapshot.buffer_snapshot) .row .saturating_add_signed(*offset), }; Ok(MultiBufferRow(target).min(snapshot.max_buffer_row())) }} #[derive(Debug, Clone, PartialEq, Deserialize)]pub(crate) struct CommandRange { start: Position, end: Option<Position>,} impl CommandRange { fn head(&self) -> &Position { self.end.as_ref().unwrap_or(&self.start) } pub(crate) fn buffer_range( &self, vim: &Vim, editor: &mut Editor, cx: &mut WindowContext, ) -> Result<Range<MultiBufferRow>> { let start = self.start.buffer_row(vim, editor, cx)?; let end = if let Some(end) = self.end.as_ref() { end.buffer_row(vim, editor, cx)? } else { start }; if end < start { anyhow::Ok(end..start) } else { anyhow::Ok(start..end) } } pub fn as_count(&self) -> Option<u32> { if let CommandRange { start: Position::Line { row, offset: 0 }, end: None, } = &self { Some(*row) } else { None } }} fn generate_commands(_: &AppContext) -> Vec<VimCommand> { vec![ VimCommand::new(Lines 1–500 of 1110. Load more
use std::{ iter::Peekable, ops::{Deref, Range}, str::Chars, sync::OnceLock, }; use anyhow::{anyhow, Result}; use command_palette_hooks::CommandInterceptResult; use editor::{ actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive}, Editor, ToMultiBufferPoint, }; use gpui::{actions, impl_actions, Action, AppContext, Global, ViewContext}; use language::Point; use multi_buffer::MultiBufferRow; use serde::Deserialize; use ui::WindowContext; use util::ResultExt; use workspace::{notifications::NotifyResultExt, SaveIntent}; use crate::{ motion::{EndOfDocument, Motion, StartOfDocument}, normal::{ search::{FindCommand, ReplaceCommand, Replacement}, JoinLines, }, state::Mode, visual::VisualDeleteLine, Vim, }; #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct GoToLine { range: CommandRange, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct YankCommand { range: CommandRange, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct WithRange { restore_selection: bool, range: CommandRange, action: WrappedAction, } #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct WithCount { count: u32, action: WrappedAction, } #[derive(Debug)] struct WrappedAction(Box<dyn Action>); actions!(vim, [VisualCommand, CountCommand]); impl_actions!(vim, [GoToLine, YankCommand, WithRange, WithCount]); impl<'de> Deserialize<'de> for WrappedAction { fn deserialize<D>(_: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { Err(serde::de::Error::custom("Cannot deserialize WrappedAction")) } } impl PartialEq for WrappedAction { fn eq(&self, other: &Self) -> bool { self.0.partial_eq(&*other.0) } } impl Clone for WrappedAction { fn clone(&self) -> Self { Self(self.0.boxed_clone()) } } impl Deref for WrappedAction { type Target = dyn Action; fn deref(&self) -> &dyn Action { &*self.0 } } pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) { Vim::action(editor, cx, |vim, _: &VisualCommand, cx| { let Some(workspace) = vim.workspace(cx) else { return; }; workspace.update(cx, |workspace, cx| { command_palette::CommandPalette::toggle(workspace, "'<,'>", cx); }) }); Vim::action(editor, cx, |vim, _: &CountCommand, cx| { let Some(workspace) = vim.workspace(cx) else { return; }; let count = Vim::take_count(cx).unwrap_or(1); workspace.update(cx, |workspace, cx| { command_palette::CommandPalette::toggle( workspace, &format!(".,.+{}", count.saturating_sub(1)), cx, ); }) }); Vim::action(editor, cx, |vim, action: &GoToLine, cx| { vim.switch_mode(Mode::Normal, false, cx); let result = vim.update_editor(cx, |vim, editor, cx| { action.range.head().buffer_row(vim, editor, cx) }); let buffer_row = match result { None => return, Some(e @ Err(_)) => { let Some(workspace) = vim.workspace(cx) else { return; }; workspace.update(cx, |workspace, cx| { e.notify_err(workspace, cx); }); return; } Some(Ok(result)) => result, }; vim.move_cursor(Motion::StartOfDocument, Some(buffer_row.0 as usize + 1), cx); }); Vim::action(editor, cx, |vim, action: &YankCommand, cx| { vim.update_editor(cx, |vim, editor, cx| { let snapshot = editor.snapshot(cx); if let Ok(range) = action.range.buffer_range(vim, editor, cx) { let end = if range.end < snapshot.max_buffer_row() { Point::new(range.end.0 + 1, 0) } else { snapshot.buffer_snapshot.max_point() }; vim.copy_ranges( editor, true, true, vec![Point::new(range.start.0, 0)..end], cx, ) } }); }); Vim::action(editor, cx, |_, action: &WithCount, cx| { for _ in 0..action.count { cx.dispatch_action(action.action.boxed_clone()) } }); Vim::action(editor, cx, |vim, action: &WithRange, cx| { let result = vim.update_editor(cx, |vim, editor, cx| { action.range.buffer_range(vim, editor, cx) }); let range = match result { None => return, Some(e @ Err(_)) => { let Some(workspace) = vim.workspace(cx) else { return; }; workspace.update(cx, |workspace, cx| { e.notify_err(workspace, cx); }); return; } Some(Ok(result)) => result, }; let previous_selections = vim .update_editor(cx, |_, editor, cx| { let selections = action .restore_selection .then(|| editor.selections.disjoint_anchor_ranges()); editor.change_selections(None, cx, |s| { let end = Point::new(range.end.0, s.buffer().line_len(range.end)); s.select_ranges([end..Point::new(range.start.0, 0)]); }); selections }) .flatten(); cx.dispatch_action(action.action.boxed_clone()); cx.defer(move |vim, cx| { vim.update_editor(cx, |_, editor, cx| { editor.change_selections(None, cx, |s| { if let Some(previous_selections) = previous_selections { s.select_ranges(previous_selections); } else { s.select_ranges([ Point::new(range.start.0, 0)..Point::new(range.start.0, 0) ]); } }) }); }); }); } #[derive(Default)] struct VimCommand { prefix: &'static str, suffix: &'static str, action: Option<Box<dyn Action>>, action_name: Option<&'static str>, bang_action: Option<Box<dyn Action>>, range: Option< Box< dyn Fn(Box<dyn Action>, &CommandRange) -> Option<Box<dyn Action>> + Send + Sync + 'static, >, >, has_count: bool, } impl VimCommand { fn new(pattern: (&'static str, &'static str), action: impl Action) -> Self { Self { prefix: pattern.0, suffix: pattern.1, action: Some(action.boxed_clone()), ..Default::default() } } // from_str is used for actions in other crates. fn str(pattern: (&'static str, &'static str), action_name: &'static str) -> Self { Self { prefix: pattern.0, suffix: pattern.1, action_name: Some(action_name), ..Default::default() } } fn bang(mut self, bang_action: impl Action) -> Self { self.bang_action = Some(bang_action.boxed_clone()); self } fn range( mut self, f: impl Fn(Box<dyn Action>, &CommandRange) -> Option<Box<dyn Action>> + Send + Sync + 'static, ) -> Self { self.range = Some(Box::new(f)); self } fn count(mut self) -> Self { self.has_count = true; self } fn parse( &self, mut query: &str, range: &Option<CommandRange>, cx: &AppContext, ) -> Option<Box<dyn Action>> { let has_bang = query.ends_with('!'); if has_bang { query = &query[..query.len() - 1]; } let suffix = query.strip_prefix(self.prefix)?; if !self.suffix.starts_with(suffix) { return None; } let action = if has_bang && self.bang_action.is_some() { self.bang_action.as_ref().unwrap().boxed_clone() } else if let Some(action) = self.action.as_ref() { action.boxed_clone() } else if let Some(action_name) = self.action_name { cx.build_action(action_name, None).log_err()? } else { return None; }; if let Some(range) = range { self.range.as_ref().and_then(|f| f(action, range)) } else { Some(action) } } // TODO: ranges with search queries fn parse_range(query: &str) -> (Option<CommandRange>, String) { let mut chars = query.chars().peekable(); match chars.peek() { Some('%') => { chars.next(); return ( Some(CommandRange { start: Position::Line { row: 1, offset: 0 }, end: Some(Position::LastLine { offset: 0 }), }), chars.collect(), ); } Some('*') => { chars.next(); return ( Some(CommandRange { start: Position::Mark { name: '<', offset: 0, }, end: Some(Position::Mark { name: '>', offset: 0, }), }), chars.collect(), ); } _ => {} } let start = Self::parse_position(&mut chars); match chars.peek() { Some(',' | ';') => { chars.next(); ( Some(CommandRange { start: start.unwrap_or(Position::CurrentLine { offset: 0 }), end: Self::parse_position(&mut chars), }), chars.collect(), ) } _ => ( start.map(|start| CommandRange { start, end: None }), chars.collect(), ), } } fn parse_position(chars: &mut Peekable<Chars>) -> Option<Position> { match chars.peek()? { '0'..='9' => { let row = Self::parse_u32(chars); Some(Position::Line { row, offset: Self::parse_offset(chars), }) } '\'' => { chars.next(); let name = chars.next()?; Some(Position::Mark { name, offset: Self::parse_offset(chars), }) } '.' => { chars.next(); Some(Position::CurrentLine { offset: Self::parse_offset(chars), }) } '+' | '-' => Some(Position::CurrentLine { offset: Self::parse_offset(chars), }), '$' => { chars.next(); Some(Position::LastLine { offset: Self::parse_offset(chars), }) } _ => None, } } fn parse_offset(chars: &mut Peekable<Chars>) -> i32 { let mut res: i32 = 0; while matches!(chars.peek(), Some('+' | '-')) { let sign = if chars.next().unwrap() == '+' { 1 } else { -1 }; let amount = if matches!(chars.peek(), Some('0'..='9')) { (Self::parse_u32(chars) as i32).saturating_mul(sign) } else { sign }; res = res.saturating_add(amount) } res } fn parse_u32(chars: &mut Peekable<Chars>) -> u32 { let mut res: u32 = 0; while matches!(chars.peek(), Some('0'..='9')) { res = res .saturating_mul(10) .saturating_add(chars.next().unwrap() as u32 - '0' as u32); } res } } #[derive(Debug, Clone, PartialEq, Deserialize)] enum Position { Line { row: u32, offset: i32 }, Mark { name: char, offset: i32 }, LastLine { offset: i32 }, CurrentLine { offset: i32 }, } impl Position { fn buffer_row( &self, vim: &Vim, editor: &mut Editor, cx: &mut WindowContext, ) -> Result<MultiBufferRow> { let snapshot = editor.snapshot(cx); let target = match self { Position::Line { row, offset } => row.saturating_add_signed(offset.saturating_sub(1)), Position::Mark { name, offset } => { let Some(mark) = vim.marks.get(&name.to_string()).and_then(|vec| vec.last()) else { return Err(anyhow!("mark {} not set", name)); }; mark.to_point(&snapshot.buffer_snapshot) .row .saturating_add_signed(*offset) } Position::LastLine { offset } => { snapshot.max_buffer_row().0.saturating_add_signed(*offset) } Position::CurrentLine { offset } => editor .selections .newest_anchor() .head() .to_point(&snapshot.buffer_snapshot) .row .saturating_add_signed(*offset), }; Ok(MultiBufferRow(target).min(snapshot.max_buffer_row())) } } #[derive(Debug, Clone, PartialEq, Deserialize)] pub(crate) struct CommandRange { start: Position, end: Option<Position>, } impl CommandRange { fn head(&self) -> &Position { self.end.as_ref().unwrap_or(&self.start) } pub(crate) fn buffer_range( &self, vim: &Vim, editor: &mut Editor, cx: &mut WindowContext, ) -> Result<Range<MultiBufferRow>> { let start = self.start.buffer_row(vim, editor, cx)?; let end = if let Some(end) = self.end.as_ref() { end.buffer_row(vim, editor, cx)? } else { start }; if end < start { anyhow::Ok(end..start) } else { anyhow::Ok(start..end) } } pub fn as_count(&self) -> Option<u32> { if let CommandRange { start: Position::Line { row, offset: 0 }, end: None, } = &self { Some(*row) } else { None } } } fn generate_commands(_: &AppContext) -> Vec<VimCommand> { vec![ VimCommand::new(