Joseph T. LyonsFix typo (#18345)9300dbc2 years agoHistory
1004 lines · 31.9 KB · Rust
RawDownload
use std::{iter::Peekable, ops::Range, str::Chars, sync::OnceLock}; use anyhow::{anyhow, Result};use command_palette_hooks::CommandInterceptResult;use editor::{    actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive},    Editor, ToPoint,};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, VisualYankLine},    Vim,}; #[derive(Debug, Clone, PartialEq, Deserialize)]pub struct GoToLine {    range: CommandRange,} #[derive(Debug)]pub struct WithRange {    is_count: bool,    range: CommandRange,    action: Box<dyn Action>,} actions!(vim, [VisualCommand, CountCommand]);impl_actions!(vim, [GoToLine, WithRange]); impl<'de> Deserialize<'de> for WithRange {    fn deserialize<D>(_: D) -> Result<Self, D::Error>    where        D: serde::Deserializer<'de>,    {        Err(serde::de::Error::custom("Cannot deserialize WithRange"))    }} impl PartialEq for WithRange {    fn eq(&self, other: &Self) -> bool {        self.range == other.range && self.action.partial_eq(&*other.action)    }} impl Clone for WithRange {    fn clone(&self) -> Self {        Self {            is_count: self.is_count,            range: self.range.clone(),            action: self.action.boxed_clone(),        }    }} 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: &WithRange, cx| {        if action.is_count {            for _ in 0..action.range.as_count() {                cx.dispatch_action(action.action.boxed_clone())            }            return;        }        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,        };        vim.update_editor(cx, |_, editor, cx| {            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)]);            })        });        cx.dispatch_action(action.action.boxed_clone());        cx.defer(move |vim, cx| {            vim.update_editor(cx, |_, editor, cx| {                editor.change_selections(None, cx, |s| {                    s.select_ranges([Point::new(range.start.0, 0)..Point::new(range.start.0, 0)]);                })            });        });    });} #[derive(Debug, 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>>,    has_range: bool,    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) -> Self {        self.has_range = true;        self    }    fn count(mut self) -> Self {        self.has_count = true;        self    }     fn parse(&self, mut query: &str, 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;        }         if has_bang && self.bang_action.is_some() {            Some(self.bang_action.as_ref().unwrap().boxed_clone())        } else if let Some(action) = self.action.as_ref() {            Some(action.boxed_clone())        } else if let Some(action_name) = self.action_name {            cx.build_action(action_name, None).log_err()        } else {            None        }    }     // 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) -> u32 {        if let CommandRange {            start: Position::Line { row, offset: 0 },            end: None,        } = &self        {            *row        } else {            0        }    }     pub fn is_count(&self) -> bool {        matches!(            &self,            CommandRange {                start: Position::Line { row: _, offset: 0 },                end: None            }        )    }} fn generate_commands(_: &AppContext) -> Vec<VimCommand> {    vec![        VimCommand::new(            ("w", "rite"),            workspace::Save {                save_intent: Some(SaveIntent::Save),            },        )        .bang(workspace::Save {            save_intent: Some(SaveIntent::Overwrite),        }),        VimCommand::new(            ("q", "uit"),            workspace::CloseActiveItem {                save_intent: Some(SaveIntent::Close),            },        )        .bang(workspace::CloseActiveItem {            save_intent: Some(SaveIntent::Skip),        }),        VimCommand::new(            ("wq", ""),            workspace::CloseActiveItem {                save_intent: Some(SaveIntent::Save),            },        )        .bang(workspace::CloseActiveItem {            save_intent: Some(SaveIntent::Overwrite),        }),        VimCommand::new(            ("x", "it"),            workspace::CloseActiveItem {                save_intent: Some(SaveIntent::SaveAll),            },        )        .bang(workspace::CloseActiveItem {            save_intent: Some(SaveIntent::Overwrite),        }),        VimCommand::new(            ("ex", "it"),            workspace::CloseActiveItem {                save_intent: Some(SaveIntent::SaveAll),            },        )        .bang(workspace::CloseActiveItem {            save_intent: Some(SaveIntent::Overwrite),        }),        VimCommand::new(            ("up", "date"),            workspace::Save {                save_intent: Some(SaveIntent::SaveAll),            },        ),        VimCommand::new(            ("wa", "ll"),            workspace::SaveAll {                save_intent: Some(SaveIntent::SaveAll),            },        )        .bang(workspace::SaveAll {            save_intent: Some(SaveIntent::Overwrite),        }),        VimCommand::new(            ("qa", "ll"),            workspace::CloseAllItemsAndPanes {                save_intent: Some(SaveIntent::Close),            },        )        .bang(workspace::CloseAllItemsAndPanes {            save_intent: Some(SaveIntent::Skip),

Lines 1–500 of 1004.