Skip to content

feat(ui): add vim yank/paste (y/p/P) with unnamed register#22026

Merged
jacob314 merged 7 commits intogoogle-gemini:mainfrom
aanari:feat/vim-yank-paste
Mar 11, 2026
Merged

feat(ui): add vim yank/paste (y/p/P) with unnamed register#22026
jacob314 merged 7 commits intogoogle-gemini:mainfrom
aanari:feat/vim-yank-paste

Conversation

@aanari
Copy link
Copy Markdown
Contributor

@aanari aanari commented Mar 11, 2026

Follow-up to #21932 and #21973.

Adds the vim unnamed register and yank/paste support to the input buffer:

  • yy / Y — yank current line (linewise)
  • yw / yW / ye / yE / y$ — yank by motion (charwise)
  • p / P — paste after/before cursor; linewise yanks insert a new line, charwise inserts inline
  • All delete operations (x, X, dd, dw, dW, de, dE, D, df/dt, dF/dT) now populate the register too, so dwp to move a word works as expected

Tested manually: yy+p to duplicate a line, yw+p to paste a word, dw+p to move a word.

GCLI-vim-yank

@aanari aanari requested a review from a team as a code owner March 11, 2026 14:48
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the text editing experience within the UI's input buffer by introducing core Vim-like yank and paste functionalities. Users can now efficiently copy and move text using familiar y and p/P commands, with delete operations also contributing to the unnamed register. This change aims to provide a more powerful and intuitive text manipulation interface for Vim users.

Highlights

  • Vim Yank Commands Added: Implemented a comprehensive set of Vim-style yank commands including yy (yank line), Y (yank line), yw (yank word forward), yW (yank big word forward), ye (yank to end of word), yE (yank to end of big word), and y$ (yank to end of line).
  • Vim Paste Commands Implemented: Added Vim-style paste functionality with p (paste after cursor) and P (paste before cursor), supporting both linewise and charwise content from the yank register.
  • Delete Operations Populate Yank Register: All Vim-style delete operations (e.g., x, X, dd, dw, D, df/dt, dF/dT) now automatically populate the unnamed yank register, enabling seamless cut-and-paste workflows.
  • Extensive Test Coverage: New unit and integration tests were added to ensure the correct behavior of all new yank and paste commands, as well as the interaction between delete operations and the yank register.
Changelog
  • packages/cli/src/ui/components/shared/text-buffer.test.ts
    • Added yankRegister property to the initialState for testing purposes.
    • Introduced new test suites for vim yank and paste operations, covering various scenarios for vim_yank_line, vim_yank_word_forward, vim_yank_big_word_forward, vim_yank_word_end, vim_yank_big_word_end, and vim_yank_to_end_of_line.
    • Added tests to verify that delete operations (x, X, dd, dw, dW, de, dE, D, df/dt, dF/dT) correctly populate the yankRegister.
    • Included tests for vim_paste_after and vim_paste_before for both charwise and linewise content, including count repetition.
  • packages/cli/src/ui/components/shared/text-buffer.ts
    • Added yankRegister property to the TextBufferState interface to store yanked text and its type (linewise/charwise).
    • Extended TextBufferAction type with new actions for various yank and paste operations (vim_yank_line, vim_yank_word_forward, vim_yank_big_word_forward, vim_yank_word_end, vim_yank_big_word_end, vim_yank_to_end_of_line, vim_paste_after, vim_paste_before).
    • Updated textBufferReducerLogic to dispatch new yank and paste actions to the handleVimAction function.
    • Initialized yankRegister to null in the useTextBuffer hook's initial state.
    • Exposed new vimYankLine, vimYankWordForward, vimYankBigWordForward, vimYankWordEnd, vimYankBigWordEnd, vimYankToEndOfLine, vimPasteAfter, and vimPasteBefore functions from the useTextBuffer hook.
    • Updated the TextBuffer interface to include documentation for the new yank and paste methods.
  • packages/cli/src/ui/components/shared/vim-buffer-actions.test.ts
    • Added yankRegister to the createTestState function for consistent testing.
    • Introduced a new test suite for vim yank and paste actions, covering vim_yank_line, vim_yank_word_forward, vim_yank_big_word_forward, vim_yank_word_end, vim_yank_big_word_end, vim_yank_to_end_of_line, vim_paste_after, and vim_paste_before.
    • Verified that delete operations (vim_delete_char, vim_delete_char_before, vim_delete_line, vim_delete_word_forward, vim_delete_big_word_forward, vim_delete_word_end, vim_delete_big_word_end, vim_delete_to_end_of_line, vim_delete_to_char_forward, vim_delete_to_char_backward) correctly populate the yankRegister.
    • Added tests for vim_paste_after and vim_paste_before with various content types and counts.
  • packages/cli/src/ui/components/shared/vim-buffer-actions.ts
    • Extended VimAction type to include new yank and paste actions.
    • Added a new helper function extractRange to efficiently extract text for yank/delete operations across lines.
    • Modified existing delete actions (vim_delete_word_forward, vim_delete_big_word_forward, vim_delete_word_end, vim_delete_big_word_end, vim_delete_line, vim_delete_to_end_of_line, vim_delete_char, vim_delete_char_before, vim_delete_to_char_forward, vim_delete_to_char_backward) to populate the yankRegister with the deleted text.
    • Implemented new action handlers for vim_yank_line, vim_yank_word_forward, vim_yank_big_word_forward, vim_yank_word_end, vim_yank_big_word_end, and vim_yank_to_end_of_line to store text in the yankRegister.
    • Implemented new action handlers for vim_paste_after and vim_paste_before to insert content from the yankRegister, handling both linewise and charwise pastes and count repetitions.
  • packages/cli/src/ui/hooks/vim.test.tsx
    • Added yankRegister to the createMockTextBufferState function.
    • Mocked the new vimYankLine, vimYankWordForward, vimYankBigWordForward, vimYankWordEnd, vimYankBigWordEnd, vimYankToEndOfLine, vimPasteAfter, and vimPasteBefore functions in the mockBuffer.
    • Added new test suites for Yank and paste (y/p/P) commands, verifying correct dispatch of actions for yy, Y, yw, yW, ye, yE, y$, p, and P with various counts.
    • Included integration tests using textBufferReducer to simulate full yy+p, yw+p, and dw+p workflows and verify state changes.
  • packages/cli/src/ui/hooks/vim.ts
    • Defined new CMD_TYPES constants for yank and paste operations (YANK_LINE, YANK_WORD_FORWARD, YANK_BIG_WORD_FORWARD, YANK_WORD_END, YANK_BIG_WORD_END, YANK_TO_EOL, PASTE_AFTER, PASTE_BEFORE).
    • Updated the VimState and VimAction types to include 'y' as a possible pendingOperator.
    • Added new cases to the executeCommand function to handle the new CMD_TYPES for yank and paste, dispatching corresponding buffer actions.
    • Implemented logic within the handleInput function to manage the 'y' pending operator for motions like yw, yW, ye, yE, and y$, and to handle the yy command.
    • Added direct handlers for Y, p, and P commands to trigger the respective buffer actions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@aanari
Copy link
Copy Markdown
Contributor Author

aanari commented Mar 11, 2026

Fixed Y to behave like y$ (yank to end of line) rather than yy. Neovim aligned Y with y$ in 0.7 for consistency, so matching that behavior here makes more sense.

For the other points:

Change ops and register population (cw, C, etc.) are intentionally out of scope for this PR. Happy to follow up in a separate one if there's interest.

Register locality: there's only ever one TextBuffer instance in the app, so per-buffer state is fine. Moving it to VimModeContext would be a bigger refactor and can be revisited separately.

extractRange consistency: vim_delete_char was already doing manual slicing before this PR, not introducing that pattern here.

@gemini-cli gemini-cli bot added area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item. labels Mar 11, 2026
@jacob314
Copy link
Copy Markdown
Contributor

/review-frontend manually reviewed by Jacob

I have reviewed the frontend code for PR 22026, which introduces Vim yank and paste functionalities (y, p, P, and yankRegister handling on deletions).

What I Found

  1. Overall Implementation:
    The logic added for yanking (yy, yw, yW, ye, yE, y$) and tracking deleted text on mutations (d, c, x, etc.) is solid. The behavior closely aligns with Vim, the usage of the unnamed register (yankRegister) works correctly, and the integration into vim.ts correctly handles both pending operators (y) and repeat counts. The design correctly leverages the existing code point lengths, extraction tools, and word-boundary finders.

  2. The "make Y behave like y$" feature:
    This correctly maps the Y command directly to y$ to align with modern conventions (like Neovim), as intended in the PR's latest commit.

  3. Subtle Bug in Multiline Paste:
    I discovered a bug involving how the cursor lands after pasting a charwise (non-linewise) text containing multiple lines (e.g., when you use yW across a line boundary and then paste it with p or P).

    The original logic in packages/cli/src/ui/components/shared/vim-buffer-actions.ts was:

    // Cursor lands on last pasted char
    const pasteCodePoints = toCodePoints(pasteText);
    const newCol = insertCol + pasteCodePoints.length - 1;
    return clampNormalCursor({
      ...newState,
      cursorCol: Math.max(insertCol, newCol),
      preferredCol: null,
    });

    Because pasteCodePoints.length measures the length of the entire multiline string (including characters after newlines), newCol results in a massive column index. clampNormalCursor then clamps it to the end of the last pasted line, instead of correctly placing it on the last pasted character. If the string is multiline, insertCol shouldn't apply to the last line because the final line inherently starts at column 0.

The Fix

Since replaceRangeInternal already calculates the cursor correctly mathematically and safely returns newState.cursorCol positioned exactly one character after the inserted text (even for multi-line replacements), we can just step it back by 1 (unless the paste text is empty).

The fixed code simplifies both vim_paste_after and vim_paste_before to:

const pasteLength = pasteText.length;
return clampNormalCursor({
  ...newState,
  cursorCol: Math.max(0, newState.cursorCol - (pasteLength > 0 ? 1 : 0)),
  preferredCol: null,
});

I recommend applying this fix and adding a test covering multiline charwise p and P pasting into vim-buffer-actions.test.ts to prevent regressions.

@aanari
Copy link
Copy Markdown
Contributor Author

aanari commented Mar 11, 2026

Good catch. Applied the fix to both vim_paste_after and vim_paste_before, using newState.cursorCol - 1 instead of recomputing from insertCol. Added tests for multiline charwise paste on both p and P to cover the regression.

Copy link
Copy Markdown
Contributor

@jacob314 jacob314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@jacob314 jacob314 enabled auto-merge March 11, 2026 18:32
@jacob314 jacob314 added this pull request to the merge queue Mar 11, 2026
Merged via the queue into google-gemini:main with commit 08e174a Mar 11, 2026
27 checks passed
liamhelmer pushed a commit to badal-io/gemini-cli that referenced this pull request Mar 12, 2026
ruomengz pushed a commit that referenced this pull request Mar 13, 2026
Co-authored-by: Jacob Richman <jacob314@gmail.com>
SUNDRAM07 pushed a commit to SUNDRAM07/gemini-cli that referenced this pull request Mar 30, 2026
warrenzhu25 pushed a commit to warrenzhu25/gemini-cli that referenced this pull request Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality 🔒 maintainer only ⛔ Do not contribute. Internal roadmap item.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants