1899 lines · 62.7 KB · Rust
RawDownload
use anyhow::{anyhow, Result};use collections::HashMap;use command_palette_hooks::CommandInterceptResult;use editor::{    actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive},    display_map::ToDisplayPoint,    scroll::Autoscroll,    Bias, Editor, ToPoint,};use gpui::{actions, impl_internal_actions, Action, App, AppContext as _, Context, Global, Window};use itertools::Itertools;use language::Point;use multi_buffer::MultiBufferRow;use regex::Regex;use schemars::JsonSchema;use search::{BufferSearchBar, SearchOptions};use serde::Deserialize;use std::{    io::Write,    iter::Peekable,    ops::{Deref, Range},    process::Stdio,    str::Chars,    sync::OnceLock,    time::Instant,};use task::{HideStrategy, RevealStrategy, SpawnInTerminal, TaskId};use ui::ActiveTheme;use util::ResultExt;use workspace::{notifications::NotifyResultExt, SaveIntent};use zed_actions::RevealTarget; use crate::{    motion::{EndOfDocument, Motion, StartOfDocument},    normal::{        search::{FindCommand, ReplaceCommand, Replacement},        JoinLines,    },    object::Object,    state::Mode,    visual::VisualDeleteLine,    ToggleRegistersView, Vim,}; #[derive(Clone, Debug, PartialEq)]pub struct GoToLine {    range: CommandRange,} #[derive(Clone, Debug, PartialEq)]pub struct YankCommand {    range: CommandRange,} #[derive(Clone, Debug, PartialEq)]pub struct WithRange {    restore_selection: bool,    range: CommandRange,    action: WrappedAction,} #[derive(Clone, Debug, PartialEq)]pub struct WithCount {    count: u32,    action: WrappedAction,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]pub enum VimOption {    Wrap(bool),    Number(bool),    RelativeNumber(bool),} impl VimOption {    fn possible_commands(query: &str) -> Vec<CommandInterceptResult> {        let mut prefix_of_options = Vec::new();        let mut options = query.split(" ").collect::<Vec<_>>();        let prefix = options.pop().unwrap_or_default();        for option in options {            if let Some(opt) = Self::from(option) {                prefix_of_options.push(opt)            } else {                return vec![];            }        }         Self::possibilities(&prefix)            .map(|possible| {                let mut options = prefix_of_options.clone();                options.push(possible);                 CommandInterceptResult {                    string: format!(                        "set {}",                        options.iter().map(|opt| opt.to_string()).join(" ")                    ),                    action: VimSet { options }.boxed_clone(),                    positions: vec![],                }            })            .collect()    }     fn possibilities(query: &str) -> impl Iterator<Item = Self> + '_ {        [            (None, VimOption::Wrap(true)),            (None, VimOption::Wrap(false)),            (None, VimOption::Number(true)),            (None, VimOption::Number(false)),            (None, VimOption::RelativeNumber(true)),            (None, VimOption::RelativeNumber(false)),            (Some("rnu"), VimOption::RelativeNumber(true)),            (Some("nornu"), VimOption::RelativeNumber(false)),        ]        .into_iter()        .filter(move |(prefix, option)| prefix.unwrap_or(option.to_string()).starts_with(query))        .map(|(_, option)| option)    }     fn from(option: &str) -> Option<Self> {        match option {            "wrap" => Some(Self::Wrap(true)),            "nowrap" => Some(Self::Wrap(false)),             "number" => Some(Self::Number(true)),            "nu" => Some(Self::Number(true)),            "nonumber" => Some(Self::Number(false)),            "nonu" => Some(Self::Number(false)),             "relativenumber" => Some(Self::RelativeNumber(true)),            "rnu" => Some(Self::RelativeNumber(true)),            "norelativenumber" => Some(Self::RelativeNumber(false)),            "nornu" => Some(Self::RelativeNumber(false)),             _ => None,        }    }     fn to_string(&self) -> &'static str {        match self {            VimOption::Wrap(true) => "wrap",            VimOption::Wrap(false) => "nowrap",            VimOption::Number(true) => "number",            VimOption::Number(false) => "nonumber",            VimOption::RelativeNumber(true) => "relativenumber",            VimOption::RelativeNumber(false) => "norelativenumber",        }    }} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]pub struct VimSet {    options: Vec<VimOption>,} #[derive(Debug)]struct WrappedAction(Box<dyn Action>); actions!(vim, [VisualCommand, CountCommand, ShellCommand]);impl_internal_actions!(    vim,    [        GoToLine,        YankCommand,        WithRange,        WithCount,        OnMatchingLines,        ShellExec,        VimSet,    ]); 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 Context<Vim>) {    // Vim::action(editor, cx, |vim, action: &StartOfLine, window, cx| {    Vim::action(editor, cx, |vim, action: &VimSet, window, cx| {        for option in action.options.iter() {            vim.update_editor(window, cx, |_, editor, _, cx| match option {                VimOption::Wrap(true) => {                    editor                        .set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);                }                VimOption::Wrap(false) => {                    editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx);                }                VimOption::Number(enabled) => {                    editor.set_show_line_numbers(*enabled, cx);                }                VimOption::RelativeNumber(enabled) => {                    editor.set_relative_line_number(Some(*enabled), cx);                }            });        }    });    Vim::action(editor, cx, |vim, _: &VisualCommand, window, cx| {        let Some(workspace) = vim.workspace(window) else {            return;        };        workspace.update(cx, |workspace, cx| {            command_palette::CommandPalette::toggle(workspace, "'<,'>", window, cx);        })    });     Vim::action(editor, cx, |vim, _: &ShellCommand, window, cx| {        let Some(workspace) = vim.workspace(window) else {            return;        };        workspace.update(cx, |workspace, cx| {            command_palette::CommandPalette::toggle(workspace, "'<,'>!", window, cx);        })    });     Vim::action(editor, cx, |vim, _: &CountCommand, window, cx| {        let Some(workspace) = vim.workspace(window) else {            return;        };        let count = Vim::take_count(cx).unwrap_or(1);        let n = if count > 1 {            format!(".,.+{}", count.saturating_sub(1))        } else {            ".".to_string()        };        workspace.update(cx, |workspace, cx| {            command_palette::CommandPalette::toggle(workspace, &n, window, cx);        })    });     Vim::action(editor, cx, |vim, action: &GoToLine, window, cx| {        vim.switch_mode(Mode::Normal, false, window, cx);        let result = vim.update_editor(window, cx, |vim, editor, window, cx| {            let snapshot = editor.snapshot(window, cx);            let buffer_row = action.range.head().buffer_row(vim, editor, window, cx)?;            let current = editor.selections.newest::<Point>(cx);            let target = snapshot                .buffer_snapshot                .clip_point(Point::new(buffer_row.0, current.head().column), Bias::Left);            editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {                s.select_ranges([target..target]);            });             anyhow::Ok(())        });        if let Some(e @ Err(_)) = result {            let Some(workspace) = vim.workspace(window) else {                return;            };            workspace.update(cx, |workspace, cx| {                e.notify_err(workspace, cx);            });            return;        }    });     Vim::action(editor, cx, |vim, action: &YankCommand, window, cx| {        vim.update_editor(window, cx, |vim, editor, window, cx| {            let snapshot = editor.snapshot(window, cx);            if let Ok(range) = action.range.buffer_range(vim, editor, window, cx) {                let end = if range.end < snapshot.buffer_snapshot.max_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, window, cx| {        for _ in 0..action.count {            window.dispatch_action(action.action.boxed_clone(), cx)        }    });     Vim::action(editor, cx, |vim, action: &WithRange, window, cx| {        let result = vim.update_editor(window, cx, |vim, editor, window, cx| {            action.range.buffer_range(vim, editor, window, cx)        });         let range = match result {            None => return,            Some(e @ Err(_)) => {                let Some(workspace) = vim.workspace(window) else {                    return;                };                workspace.update(cx, |workspace, cx| {                    e.notify_err(workspace, cx);                });                return;            }            Some(Ok(result)) => result,        };         let previous_selections = vim            .update_editor(window, cx, |_, editor, window, cx| {                let selections = action.restore_selection.then(|| {                    editor                        .selections                        .disjoint_anchor_ranges()                        .collect::<Vec<_>>()                });                editor.change_selections(None, window, 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();        window.dispatch_action(action.action.boxed_clone(), cx);        cx.defer_in(window, move |vim, window, cx| {            vim.update_editor(window, cx, |_, editor, window, cx| {                editor.change_selections(None, window, 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)                        ]);                    }                })            });        });    });     Vim::action(editor, cx, |vim, action: &OnMatchingLines, window, cx| {        action.run(vim, window, cx)    });     Vim::action(editor, cx, |vim, action: &ShellExec, window, cx| {        action.run(vim, window, cx)    })} #[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: &App,    ) -> 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(),            ),        }    } 

Lines 1–500 of 1899.