Conrad IrwinFix 0 used in a count7daed1b3 years agoHistory
528 lines · 17.0 KB · Rust
RawDownload
use crate::{    insert::NormalBefore,    motion::Motion,    state::{Mode, RecordedSelection, ReplayableAction},    visual::visual_motion,    Vim,};use gpui::{actions, Action, AppContext, WindowContext};use workspace::Workspace; actions!(vim, [Repeat, EndRepeat,]); fn should_replay(action: &Box<dyn Action>) -> bool {    // skip so that we don't leave the character palette open    if editor::ShowCharacterPalette.id() == action.id() {        return false;    }    true} fn repeatable_insert(action: &ReplayableAction) -> Option<Box<dyn Action>> {    match action {        ReplayableAction::Action(action) => {            if super::InsertBefore.id() == action.id()                || super::InsertAfter.id() == action.id()                || super::InsertFirstNonWhitespace.id() == action.id()                || super::InsertEndOfLine.id() == action.id()            {                Some(super::InsertBefore.boxed_clone())            } else if super::InsertLineAbove.id() == action.id()                || super::InsertLineBelow.id() == action.id()            {                Some(super::InsertLineBelow.boxed_clone())            } else {                None            }        }        ReplayableAction::Insertion { .. } => None,    }} pub(crate) fn init(cx: &mut AppContext) {    cx.add_action(|_: &mut Workspace, _: &EndRepeat, cx| {        Vim::update(cx, |vim, cx| {            vim.workspace_state.replaying = false;            vim.switch_mode(Mode::Normal, false, cx)        });    });     cx.add_action(|_: &mut Workspace, _: &Repeat, cx| repeat(cx, false));} pub(crate) fn repeat(cx: &mut WindowContext, from_insert_mode: bool) {    let Some((mut actions, editor, selection)) = Vim::update(cx, |vim, cx| {        let actions = vim.workspace_state.recorded_actions.clone();        if actions.is_empty() {            return None;        }         let Some(editor) = vim.active_editor.clone() else {            return None;        };        let count = vim.take_count(cx);         let selection = vim.workspace_state.recorded_selection.clone();        match selection {            RecordedSelection::SingleLine { .. } | RecordedSelection::Visual { .. } => {                vim.workspace_state.recorded_count = None;                vim.switch_mode(Mode::Visual, false, cx)            }            RecordedSelection::VisualLine { .. } => {                vim.workspace_state.recorded_count = None;                vim.switch_mode(Mode::VisualLine, false, cx)            }            RecordedSelection::VisualBlock { .. } => {                vim.workspace_state.recorded_count = None;                vim.switch_mode(Mode::VisualBlock, false, cx)            }            RecordedSelection::None => {                if let Some(count) = count {                    vim.workspace_state.recorded_count = Some(count);                }            }        }         Some((actions, editor, selection))    }) else {        return;    };     match selection {        RecordedSelection::SingleLine { cols } => {            if cols > 1 {                visual_motion(Motion::Right, Some(cols as usize - 1), cx)            }        }        RecordedSelection::Visual { rows, cols } => {            visual_motion(                Motion::Down {                    display_lines: false,                },                Some(rows as usize),                cx,            );            visual_motion(                Motion::StartOfLine {                    display_lines: false,                },                None,                cx,            );            if cols > 1 {                visual_motion(Motion::Right, Some(cols as usize - 1), cx)            }        }        RecordedSelection::VisualBlock { rows, cols } => {            visual_motion(                Motion::Down {                    display_lines: false,                },                Some(rows as usize),                cx,            );            if cols > 1 {                visual_motion(Motion::Right, Some(cols as usize - 1), cx);            }        }        RecordedSelection::VisualLine { rows } => {            visual_motion(                Motion::Down {                    display_lines: false,                },                Some(rows as usize),                cx,            );        }        RecordedSelection::None => {}    }     // insert internally uses repeat to handle counts    // vim doesn't treat 3a1 as though you literally repeated a1    // 3 times, instead it inserts the content thrice at the insert position.    if let Some(to_repeat) = repeatable_insert(&actions[0]) {        if let Some(ReplayableAction::Action(action)) = actions.last() {            if action.id() == NormalBefore.id() {                actions.pop();            }        }         let mut new_actions = actions.clone();        actions[0] = ReplayableAction::Action(to_repeat.boxed_clone());         let mut count = Vim::read(cx).workspace_state.recorded_count.unwrap_or(1);         // if we came from insert mode we're just doing repititions 2 onwards.        if from_insert_mode {            count -= 1;            new_actions[0] = actions[0].clone();        }         for _ in 1..count {            new_actions.append(actions.clone().as_mut());        }        new_actions.push(ReplayableAction::Action(NormalBefore.boxed_clone()));        actions = new_actions;    }     Vim::update(cx, |vim, _| vim.workspace_state.replaying = true);    let window = cx.window();    cx.app_context()        .spawn(move |mut cx| async move {            editor.update(&mut cx, |editor, _| {                editor.show_local_selections = false;            })?;            for action in actions {                match action {                    ReplayableAction::Action(action) => {                        if should_replay(&action) {                            window                                .dispatch_action(editor.id(), action.as_ref(), &mut cx)                                .ok_or_else(|| anyhow::anyhow!("window was closed"))                        } else {                            Ok(())                        }                    }                    ReplayableAction::Insertion {                        text,                        utf16_range_to_replace,                    } => editor.update(&mut cx, |editor, cx| {                        editor.replay_insert_event(&text, utf16_range_to_replace.clone(), cx)                    }),                }?            }            editor.update(&mut cx, |editor, _| {                editor.show_local_selections = true;            })?;            window                .dispatch_action(editor.id(), &EndRepeat, &mut cx)                .ok_or_else(|| anyhow::anyhow!("window was closed"))        })        .detach_and_log_err(cx);} #[cfg(test)]mod test {    use std::sync::Arc;     use editor::test::editor_lsp_test_context::EditorLspTestContext;    use futures::StreamExt;    use indoc::indoc;     use gpui::{executor::Deterministic, View};     use crate::{        state::Mode,        test::{NeovimBackedTestContext, VimTestContext},    };     #[gpui::test]    async fn test_dot_repeat(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {        let mut cx = NeovimBackedTestContext::new(cx).await;         // "o"        cx.set_shared_state("ˇhello").await;        cx.simulate_shared_keystrokes(["o", "w", "o", "r", "l", "d", "escape"])            .await;        cx.assert_shared_state("hello\nworlˇd").await;        cx.simulate_shared_keystrokes(["."]).await;        deterministic.run_until_parked();        cx.assert_shared_state("hello\nworld\nworlˇd").await;         // "d"        cx.simulate_shared_keystrokes(["^", "d", "f", "o"]).await;        cx.simulate_shared_keystrokes(["g", "g", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state("ˇ\nworld\nrld").await;         // "p" (note that it pastes the current clipboard)        cx.simulate_shared_keystrokes(["j", "y", "y", "p"]).await;        cx.simulate_shared_keystrokes(["shift-g", "y", "y", "."])            .await;        deterministic.run_until_parked();        cx.assert_shared_state("\nworld\nworld\nrld\nˇrld").await;         // "~" (note that counts apply to the action taken, not . itself)        cx.set_shared_state("ˇthe quick brown fox").await;        cx.simulate_shared_keystrokes(["2", "~", "."]).await;        deterministic.run_until_parked();        cx.set_shared_state("THE ˇquick brown fox").await;        cx.simulate_shared_keystrokes(["3", "."]).await;        deterministic.run_until_parked();        cx.set_shared_state("THE QUIˇck brown fox").await;        deterministic.run_until_parked();        cx.simulate_shared_keystrokes(["."]).await;        deterministic.run_until_parked();        cx.assert_shared_state("THE QUICK ˇbrown fox").await;    }     #[gpui::test]    async fn test_repeat_ime(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {        let mut cx = VimTestContext::new(cx, true).await;         cx.set_state("hˇllo", Mode::Normal);        cx.simulate_keystrokes(["i"]);         // simulate brazilian input for ä.        cx.update_editor(|editor, cx| {            editor.replace_and_mark_text_in_range(None, "\"", Some(1..1), cx);            editor.replace_text_in_range(None, "ä", cx);        });        cx.simulate_keystrokes(["escape"]);        cx.assert_state("hˇällo", Mode::Normal);        cx.simulate_keystrokes(["."]);        deterministic.run_until_parked();        cx.assert_state("hˇäällo", Mode::Normal);    }     #[gpui::test]    async fn test_repeat_completion(        deterministic: Arc<Deterministic>,        cx: &mut gpui::TestAppContext,    ) {        let cx = EditorLspTestContext::new_rust(            lsp::ServerCapabilities {                completion_provider: Some(lsp::CompletionOptions {                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),                    resolve_provider: Some(true),                    ..Default::default()                }),                ..Default::default()            },            cx,        )        .await;        let mut cx = VimTestContext::new_with_lsp(cx, true);         cx.set_state(            indoc! {"            onˇe            two            three        "},            Mode::Normal,        );         let mut request =            cx.handle_request::<lsp::request::Completion, _, _>(move |_, params, _| async move {                let position = params.text_document_position.position;                Ok(Some(lsp::CompletionResponse::Array(vec![                    lsp::CompletionItem {                        label: "first".to_string(),                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {                            range: lsp::Range::new(position.clone(), position.clone()),                            new_text: "first".to_string(),                        })),                        ..Default::default()                    },                    lsp::CompletionItem {                        label: "second".to_string(),                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {                            range: lsp::Range::new(position.clone(), position.clone()),                            new_text: "second".to_string(),                        })),                        ..Default::default()                    },                ])))            });        cx.simulate_keystrokes(["a", "."]);        request.next().await;        cx.condition(|editor, _| editor.context_menu_visible())            .await;        cx.simulate_keystrokes(["down", "enter", "!", "escape"]);         cx.assert_state(            indoc! {"                one.secondˇ!                two                three            "},            Mode::Normal,        );        cx.simulate_keystrokes(["j", "."]);        deterministic.run_until_parked();        cx.assert_state(            indoc! {"                one.second!                two.secondˇ!                three            "},            Mode::Normal,        );    }     #[gpui::test]    async fn test_repeat_visual(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {        let mut cx = NeovimBackedTestContext::new(cx).await;         // single-line (3 columns)        cx.set_shared_state(indoc! {            "ˇthe quick brown            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["v", "i", "w", "s", "o", "escape"])            .await;        cx.assert_shared_state(indoc! {            "ˇo quick brown            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["j", "w", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "o quick brown            fox ˇops over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["f", "r", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "o quick brown            fox ops oveˇothe lazy dog"        })        .await;         // visual        cx.set_shared_state(indoc! {            "the ˇquick brown            fox jumps over            fox jumps over            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["v", "j", "x"]).await;        cx.assert_shared_state(indoc! {            "the ˇumps over            fox jumps over            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "the ˇumps over            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["w", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "the umps ˇumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["j", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "the umps umps over            the ˇog"        })        .await;         // block mode (3 rows)        cx.set_shared_state(indoc! {            "ˇthe quick brown            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["ctrl-v", "j", "j", "shift-i", "o", "escape"])            .await;        cx.assert_shared_state(indoc! {            "ˇothe quick brown            ofox jumps over            othe lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["j", "4", "l", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "othe quick brown            ofoxˇo jumps over            otheo lazy dog"        })        .await;         // line mode        cx.set_shared_state(indoc! {            "ˇthe quick brown            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["shift-v", "shift-r", "o", "escape"])            .await;        cx.assert_shared_state(indoc! {            "ˇo            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["j", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {            "o            ˇo            the lazy dog"        })        .await;    }     #[gpui::test]    async fn test_repeat_motion_counts(        deterministic: Arc<Deterministic>,        cx: &mut gpui::TestAppContext,    ) {        let mut cx = NeovimBackedTestContext::new(cx).await;         cx.set_shared_state(indoc! {            "ˇthe quick brown            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["3", "d", "3", "l"]).await;        cx.assert_shared_state(indoc! {            "ˇ brown            fox jumps over            the lazy dog"        })        .await;        cx.simulate_shared_keystrokes(["j", "."]).await;        deterministic.run_until_parked();        cx.assert_shared_state(indoc! {

Lines 1–500 of 528.