3539 lines · 121.0 KB · Rust
RawDownload
use anyhow::{Result, anyhow};use collections::{HashMap, HashSet};use command_palette_hooks::{CommandInterceptItem, CommandInterceptResult};use editor::{    Bias, Editor, EditorSettings, SelectionEffects, ToPoint,    actions::{SortLinesCaseInsensitive, SortLinesCaseSensitive},    display_map::ToDisplayPoint,};use futures::AsyncWriteExt as _;use gpui::{    Action, App, AppContext as _, Context, Global, Keystroke, Task, WeakEntity, Window, actions,};use itertools::Itertools;use language::Point;use multi_buffer::MultiBufferRow;use project::ProjectPath;use regex::Regex;use schemars::JsonSchema;use search::{BufferSearchBar, SearchOptions};use serde::Deserialize;use settings::{Settings, SettingsStore};use std::{    iter::Peekable,    ops::{Deref, Range},    path::{Path, PathBuf},    process::Stdio,    str::Chars,    sync::OnceLock,    time::Instant,};use task::{HideStrategy, RevealStrategy, SpawnInTerminal, TaskId};use ui::ActiveTheme;use util::{    ResultExt,    paths::PathStyle,    rel_path::{RelPath, RelPathBuf},};use workspace::{Item, SaveIntent, Workspace, notifications::NotifyResultExt};use workspace::{SplitDirection, notifications::DetachAndPromptErr};use zed_actions::{OpenDocs, RevealTarget}; use crate::{    ToggleMarksView, ToggleRegistersView, Vim, VimSettings,    motion::{EndOfDocument, Motion, MotionKind, StartOfDocument},    normal::{        JoinLines,        search::{FindCommand, ReplaceCommand, Replacement},    },    object::Object,    state::{Mark, Mode},    visual::VisualDeleteLine,}; /// Goes to the specified line number in the editor.#[derive(Clone, Debug, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]pub struct GoToLine {    range: CommandRange,} /// Yanks (copies) text based on the specified range.#[derive(Clone, Debug, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]pub struct YankCommand {    range: CommandRange,} /// Executes a command with the specified range.#[derive(Clone, Debug, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]pub struct WithRange {    restore_selection: bool,    range: CommandRange,    action: WrappedAction,} /// Executes a command with the specified count.#[derive(Clone, Debug, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]pub struct WithCount {    count: u32,    action: WrappedAction,} #[derive(Clone, Deserialize, JsonSchema, PartialEq)]pub enum VimOption {    Wrap(bool),    Number(bool),    RelativeNumber(bool),    IgnoreCase(bool),    GDefault(bool),} impl VimOption {    fn possible_commands(query: &str) -> Vec<CommandInterceptItem> {        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);                 CommandInterceptItem {                    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)),            (None, VimOption::IgnoreCase(true)),            (None, VimOption::IgnoreCase(false)),            (Some("ic"), VimOption::IgnoreCase(true)),            (Some("noic"), VimOption::IgnoreCase(false)),            (None, VimOption::GDefault(true)),            (Some("gd"), VimOption::GDefault(true)),            (None, VimOption::GDefault(false)),            (Some("nogd"), VimOption::GDefault(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)),             "ignorecase" => Some(Self::IgnoreCase(true)),            "ic" => Some(Self::IgnoreCase(true)),            "noignorecase" => Some(Self::IgnoreCase(false)),            "noic" => Some(Self::IgnoreCase(false)),             "gdefault" => Some(Self::GDefault(true)),            "gd" => Some(Self::GDefault(true)),            "nogdefault" => Some(Self::GDefault(false)),            "nogd" => Some(Self::GDefault(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",            VimOption::IgnoreCase(true) => "ignorecase",            VimOption::IgnoreCase(false) => "noignorecase",            VimOption::GDefault(true) => "gdefault",            VimOption::GDefault(false) => "nogdefault",        }    }} /// Sets vim options and configuration values.#[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]pub struct VimSet {    options: Vec<VimOption>,} /// Saves the current file with optional save intent.#[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]struct VimSave {    pub range: Option<CommandRange>,    pub save_intent: Option<SaveIntent>,    pub filename: String,} /// Deletes the specified marks from the editor.#[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]struct VimSplit {    pub vertical: bool,    pub filename: String,} #[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]enum DeleteMarks {    Marks(String),    AllLocal,} actions!(    vim,    [        /// Executes a command in visual mode.        VisualCommand,        /// Executes a command with a count prefix.        CountCommand,        /// Executes a shell command.        ShellCommand,        /// Indicates that an argument is required for the command.        ArgumentRequired    ]); /// Opens the specified file for editing.#[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]struct VimEdit {    pub filename: String,} /// Pastes the specified file's contents.#[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]struct VimRead {    pub range: Option<CommandRange>,    pub filename: String,} #[derive(Clone, PartialEq, Action)]#[action(namespace = vim, no_json, no_register)]struct VimNorm {    pub range: Option<CommandRange>,    pub command: String,    /// Places cursors at beginning of each given row.    /// Overrides given range and current cursor.    pub override_rows: Option<Vec<u32>>,} #[derive(Debug)]struct WrappedAction(Box<dyn Action>); 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: &VimSet, _, cx| {        for option in action.options.iter() {            vim.update_editor(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);                }                VimOption::IgnoreCase(enabled) => {                    let mut settings = EditorSettings::get_global(cx).clone();                    settings.search.case_sensitive = !*enabled;                    SettingsStore::update(cx, |store, _| {                        store.override_global(settings);                    });                }                VimOption::GDefault(enabled) => {                    let mut settings = VimSettings::get_global(cx).clone();                    settings.gdefault = *enabled;                     SettingsStore::update(cx, |store, _| {                        store.override_global(settings);                    })                }            });        }    });    Vim::action(editor, cx, |vim, _: &VisualCommand, window, cx| {        let Some(workspace) = vim.workspace(window, cx) 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, cx) else {            return;        };        workspace.update(cx, |workspace, cx| {            command_palette::CommandPalette::toggle(workspace, "'<,'>!", window, cx);        })    });     Vim::action(editor, cx, |_, _: &ArgumentRequired, window, cx| {        let _ = window.prompt(            gpui::PromptLevel::Critical,            "Argument required",            None,            &["Cancel"],            cx,        );    });     Vim::action(editor, cx, |vim, _: &ShellCommand, window, cx| {        let Some(workspace) = vim.workspace(window, cx) else {            return;        };        workspace.update(cx, |workspace, cx| {            command_palette::CommandPalette::toggle(workspace, "'<,'>!", window, cx);        })    });     Vim::action(editor, cx, |vim, action: &VimSave, window, cx| {        if let Some(range) = &action.range {            vim.update_editor(cx, |vim, editor, cx| {                let Some(range) = range.buffer_range(vim, editor, window, cx).ok() else {                    return;                };                let Some((line_ending, encoding, has_bom, text, whole_buffer)) = editor.buffer().update(cx, |multi, cx| {                    Some(multi.as_singleton()?.update(cx, |buffer, _| {                        (                            buffer.line_ending(),                            buffer.encoding(),                            buffer.has_bom(),                            buffer.as_rope().slice_rows(range.start.0..range.end.0 + 1),                            range.start.0 == 0 && range.end.0 + 1 >= buffer.row_count(),                        )                    }))                }) else {                    return;                };                 let filename = action.filename.clone();                let filename = if filename.is_empty() {                    let Some(file) = editor                        .buffer()                        .read(cx)                        .as_singleton()                        .and_then(|buffer| buffer.read(cx).file())                    else {                        let _ = window.prompt(                            gpui::PromptLevel::Warning,                            "No file name",                            Some("Partial buffer write requires file name."),                            &["Cancel"],                            cx,                        );                        return;                    };                    file.path().display(file.path_style(cx)).to_string()                } else {                    filename                };                 if action.filename.is_empty() {                    if whole_buffer {                        if let Some(workspace) = vim.workspace(window, cx) {                            workspace.update(cx, |workspace, cx| {                                workspace                                    .save_active_item(                                        action.save_intent.unwrap_or(SaveIntent::Save),                                        window,                                        cx,                                    )                                    .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);                            });                        }                        return;                    }                    if Some(SaveIntent::Overwrite) != action.save_intent {                        let _ = window.prompt(                            gpui::PromptLevel::Warning,                            "Use ! to write partial buffer",                            Some("Overwriting the current file with selected buffer content requires '!'."),                            &["Cancel"],                            cx,                        );                        return;                    }                    editor.buffer().update(cx, |multi, cx| {                        if let Some(buffer) = multi.as_singleton() {                            buffer.update(cx, |buffer, _| buffer.set_conflict());                        }                    });                };                 editor.project().unwrap().update(cx, |project, cx| {                    let worktree = project.visible_worktrees(cx).next().unwrap();                     worktree.update(cx, |worktree, cx| {                        let path_style = worktree.path_style();                        let Some(path) = RelPath::new(Path::new(&filename), path_style).ok() else {                            return;                        };                         let rx = (worktree.entry_for_path(&path).is_some() && Some(SaveIntent::Overwrite) != action.save_intent).then(|| {                            window.prompt(                                gpui::PromptLevel::Warning,                                &format!("{path:?} already exists. Do you want to replace it?"),                                Some(                                    "A file or folder with the same name already exists. Replacing it will overwrite its current contents.",                                ),                                &["Replace", "Cancel"],                                cx                            )                        });                        let filename = filename.clone();                        cx.spawn_in(window, async move |this, cx| {                            if let Some(rx) = rx                                && Ok(0) != rx.await                            {                                return;                            }                             let _ = this.update_in(cx, |worktree, window, cx| {                                let Some(path) = RelPath::new(Path::new(&filename), path_style).ok() else {                                    return;                                };                                worktree                                    .write_file(path.into_arc(), text.clone(), line_ending, encoding, has_bom, cx)                                    .detach_and_prompt_err("Failed to write lines", window, cx, |_, _, _| None);                            });                        })                        .detach();                    });                });            });            return;        }        if action.filename.is_empty() {            if let Some(workspace) = vim.workspace(window, cx) {                workspace.update(cx, |workspace, cx| {                    workspace                        .save_active_item(                            action.save_intent.unwrap_or(SaveIntent::Save),                            window,                            cx,                        )                        .detach_and_prompt_err("Failed to save", window, cx, |_, _, _| None);                });            }            return;        }        vim.update_editor(cx, |_, editor, cx| {            let Some(project) = editor.project().cloned() else {                return;            };            let Some(worktree) = project.read(cx).visible_worktrees(cx).next() else {                return;            };            let path_style = worktree.read(cx).path_style();            let Ok(project_path) =                RelPath::new(Path::new(&action.filename), path_style).map(|path| ProjectPath {                    worktree_id: worktree.read(cx).id(),                    path: path.into_arc(),                })

Lines 1–500 of 3539.