Editable dot register by Shougo · Pull Request #19342 · vim/vim · GitHub
Skip to content

Editable dot register#19342

Closed
Shougo wants to merge 23 commits into
vim:masterfrom
Shougo:setreg_dot
Closed

Editable dot register#19342
Shougo wants to merge 23 commits into
vim:masterfrom
Shougo:setreg_dot

Conversation

@Shougo

@Shougo Shougo commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

neovim/neovim#33030

Current dot register is not editable.

https://github.com/tpope/vim-repeat plugin already exists. But when using this plugin, users must install repeat.vim and there are side effects such as changing the mappings to custom ones. Therefore, I adopted an approach of modifying the core functionality to make the dot register changeable from scripts.

Design Decisions

This PR makes the dot register writable, enabling customization of the repeat
command behavior without requiring external plugins like vim-repeat.

Command Interpretation (Finalized)

The dot register represents a command sequence, not just text. This is
consistent with how :normal works and the fundamental nature of the dot
command (.), which repeats the last change (an operation), not just text.

Implementation:

  • If the first character is [iaoOIAcsSC], it's treated as an insert mode command
  • The remaining characters (if any) are treated as the text to insert
  • If the first character is NOT an insert command, the string is executed as a normal mode command sequence
  • This is consistent with :normal behavior - no implicit commands are added

Examples:

" Insert mode commands
let @. = 'iHello'   " → i command + 'Hello'
let @. = 'i'        " → i command (no text)
let @. = 'oLine'    " → o command + 'Line'
let @. = 'ai'       " → a command + 'i' (to insert literal 'i')

" Normal mode commands
let @. = 'dd'       " → delete line
let @. = '3x'       " → delete 3 characters
let @. = 'yy'       " → yank line

Note: The string is executed as-is, similar to :normal. Users are
responsible for providing valid command sequences.

@Shougo

Shougo commented Feb 6, 2026

Copy link
Copy Markdown
Contributor Author

@Shougo

Shougo commented Feb 6, 2026

Copy link
Copy Markdown
Contributor Author

The above bug has been fixed, but there are the following issues:

Issue 1: Even when the completion plugin overwrites the . register, it seems to be restored somehow. The timing is unclear.

Issue 2: The overwritten dot register value is not written to the buffer. Only the internal display changes. As a countermeasure, I'm manipulating the Redo buffer, but it doesn't seem to be taking effect.

@Shougo Shougo marked this pull request as draft February 6, 2026 07:47
@Shougo

Shougo commented Feb 6, 2026

Copy link
Copy Markdown
Contributor Author

I have changed it to the draft. Because it does not work well. The help is welcome.

Comment thread src/edit.c Outdated
size_t len = str ? STRLEN(str) : 0;

vim_free(last_insert.string);
last_insert.string = alloc(len * MB_MAXBYTES + 5);

@zeertzjq zeertzjq Feb 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

len is already the number of bytes in the string. You shouldn't multiply it with the number of bytes in a single character.

Suggested change
last_insert.string = alloc(len * MB_MAXBYTES + 5);
last_insert.string = alloc(len * 3 + 5);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks.

@Shougo

Shougo commented Feb 6, 2026

Copy link
Copy Markdown
Contributor Author

Dot repeat works by stuffReadbuff(). But it executes keys instantly...

@chrisbra

chrisbra commented Feb 6, 2026

Copy link
Copy Markdown
Member

related: #6299 , #6346 https://groups.google.com/g/vim_dev/c/TIH6wsD4Qo4/m/msUMWHnWt3oJ

@mattn

mattn commented Feb 6, 2026

Copy link
Copy Markdown
Member

Probably this works well.

https://gist.github.com/mattn/5989cc3cf2edf47a25fa977a9cabc8a9

@Shougo

Shougo commented Feb 7, 2026

Copy link
Copy Markdown
Contributor Author

Usage: call setreg('.', "ahoge\<ESC>") from command line.

Thanks. I have included the patch. It works from command line.

But one problem exists. If plugin changes dot register, dot register changes seems restored.
As it stands, it's difficult to handle the dot register from plugins, so I'll keep this as WIP for now.

@Shougo

Shougo commented Feb 7, 2026

Copy link
Copy Markdown
Contributor Author

related: #6299 , #6346 https://groups.google.com/g/vim_dev/c/TIH6wsD4Qo4/m/msUMWHnWt3oJ

Thank you for the information. I have not found them.

@Shougo

Shougo commented Feb 7, 2026

Copy link
Copy Markdown
Contributor Author

I get it.

/*
 * Restore redobuff and old_redobuff from save_redobuff and save_old_redobuff.
 * Used after executing autocommands and user functions.
 */
    void
restoreRedobuff(save_redo_T *save_redo)
{
    free_buff(&redobuff);
    redobuff = save_redo->sr_redobuff;
    free_buff(&old_redobuff);
    old_redobuff = save_redo->sr_old_redobuff;
}

It restores dot register changes.

@Shougo

Shougo commented Feb 7, 2026

Copy link
Copy Markdown
Contributor Author

I have added skipRestoreRedobuff(). But it does not work well. The dot register is restored.
It is hard. The help is welcome.

@Shougo Shougo marked this pull request as ready for review February 9, 2026 10:41
@Shougo

Shougo commented Feb 9, 2026

Copy link
Copy Markdown
Contributor Author

I have added skipRestoreRedobuff(). But it does not work well. The dot register is restored.
It is hard. The help is welcome.

Fixed.

@h-east

h-east commented Feb 10, 2026

Copy link
Copy Markdown
Member
  • There's a CI error caused by you. (You're mixing C and C++ specifications.)
  • Is that the only documentation change you need?
    Don't you need to at least change :h quote_.?
  • And don't you need tests that actually use the updated . register?

You seem to be doing things haphazardly every time.
As I've said many times before, you should take the time to do a self-review phase.

@h-east

h-east commented Feb 11, 2026

Copy link
Copy Markdown
Member

The following comments were generated by AI (Claude Opus 4.5).


Here are the specification concerns when making the . register writable:

1. Ambiguity of Input Format

What should users set?

let @. = "hello"      " Text only?
let @. = "ihello"     " Command character + text?
let @. = "ihello\<ESC>"  " Command + text + ESC?

The current patch tries to auto-detect the command character, but this is ambiguous. For example, what if you want to insert a word starting with i?

let @. = "ice cream"   " Is 'i' a command? Or text?

2. Consistency with the . Command

Should the . register and . command be synchronized?

let @. = "hello"
.   " Should this insert 'hello'?

The patch updates the redo buffer with ResetRedobuff() + AppendCharToRedobuff(), which suggests the intention is to synchronize them, but there are no tests for this.

3. Non-insert Operations

The normal . command records operations other than insertions:

dw      " Delete word
.       " Repeat

In this case, what should @. contain? If writable, should deletion operations also be settable?

4. Relationship with Count

3ihello<ESC>   " Does @. contain "hello" 3 times? Or just once?
let @. = "hello"
3.              " How should this behave?

5. Asymmetry Between Read and Write

" After some input
echo @.         " => "hello" (text only)
let @. = @.     " Intending to set the same value...
echo @.         " => Same result?

If the internal format differs from the user-facing format, round-tripping may break things.

6. Handling of Special Keys

let @. = "hello\<CR>world"   " Newline
let @. = "hello\<BS>world"   " Backspace
let @. = "\<C-R>a"           " Register insertion

Should these work correctly? What should be supported?


Design Suggestions

Points that should be clarified:

  1. Input format: Don't include command character (always treat as i) - simpler
  2. ESC: Auto-append (users don't need to include it)
  3. Synchronization with . command: Clarify explicitly (if synchronized, tests are required)
  4. Scope: Insert operations only (deletions etc. are out of scope)

These should be documented clearly.

@Shougo Shougo changed the title Editable dot register WIP: Editable dot register Feb 11, 2026
@Shougo

Shougo commented Feb 11, 2026

Copy link
Copy Markdown
Contributor Author

Currently, the CI is being fixed. I'm sorry, please wait.

Since the specification was not finalized, I had postponed the documentation. I am currently documenting the current specification.

We have finally reached the stage where the code is working, and there is still room for consideration regarding the specification.

I didn't think of having AI review the pull request code. Thank you.

@Shougo

Shougo commented Feb 12, 2026

Copy link
Copy Markdown
Contributor Author

I have updated the implementation and the documentation.
I think it is better.

@dkearns

dkearns commented Feb 12, 2026

Copy link
Copy Markdown
Contributor

I'm quite confused by this PR so I'm probably missing something very obvious as others seem happy with it.

The . register is currently only loosely associated with the . repeat last change command and simply stores the last inserted text. One of the primary uses of the register is access to the value without concern for the insert command that generated it, to disassociate it from the repeat command.

Making this register writeable might be reasonable but I don't understand using it as an interface to the repeat command, at all. Why not offer a proper builtin function interface? Having the register perform double duty seems like a bad idea.

@Shougo Shougo changed the title WIP: Editable dot register Editable dot register Feb 13, 2026
@Shougo

Shougo commented Feb 13, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for this thoughtful feedback and for pointing out these concerns.
You're absolutely right to question the design, and I appreciate the
opportunity to discuss this.

Context from Previous Discussions

This feature has been requested multiple times with specific use cases:

Your Valid Concerns

You're correct that:

  1. Current usage: The . register stores "last inserted text" only (read-only, no command info)
  2. Your use case: Access the text without caring about the command
  3. Double duty problem: My PR changes getreg('.') behavior, breaking backward compatibility
  4. Interface longevity: Once merged, interfaces become very hard to change

The Core Problem

Vim's redo buffer uses a special internal format that's not accessible to scripts. This prevents:

  • ❌ Saving/restoring the repeat command
  • ❌ Building repeat history
  • ❌ Plugins like telescope from preserving user's dot command
  • ❌ Custom commands from integrating with . repeat

Revised Proposal: Dictionary-Based Functions

After considering your feedback, I believe we should use dedicated functions
with a dictionary interface
that enables save/restore and history management.

API Design

" NEW: Repeat command control
call setrepeat({'cmd': 'dd'})                    " Set repeat command
call setrepeat({'cmd': 'i', 'text': 'Hello'})    " Set insert operation

echo getrepeat()
" → {'cmd': 'dd'}
" → {'cmd': 'i', 'text': 'Hello'}

" EXISTING: Text-only access (unchanged)
echo getreg('.')
" → 'Hello' (last inserted text, unchanged behavior)

Key Use Cases Enabled

1. Save/Restore (telescope, neovim#33030)

" Save before temporary edits
let saved = getrepeat()

" ... prompt buffer edits that would overwrite . ...

" Restore original repeat
call setrepeat(saved)

2. Repeat History (#6299)

" Save current repeat before making other changes
let important_repeat = getrepeat()

" ... make other edits ...

" Restore and use the saved repeat
call setrepeat(important_repeat)
normal! .

3. Custom Plugin Integration (vim-repeat use case)

function! MyComplexOperation()
  " ... complex logic ...

  " Make this operation repeatable with .
  call setrepeat({'cmd': ':call MyComplexOperation()'})
endfunction

4. Repeat History Management (#6346 goal)

" Plugin can maintain history
let g:repeat_history = []

autocmd TextChanged * call add(g:repeat_history, getrepeat())

" Access previous repeats
call setrepeat(g:repeat_history[1])
normal! .

Dictionary Fields

Phase 1 (implement now):

{
  'cmd': 'string'   " Required - the command
  'text': 'string'  " Optional - text to insert (for insert mode)
}

Phase 2 (future extensibility):

{
  'cmd': 'string'
  'text': 'string'
  'type': 'string'      " 'normal', 'insert', 'visual'
  'mode': 'string'      " 'v', 'V', CTRL-V
  'count': number       " Repeat count
  'register': 'string'  " Target register
}

Benefits

Clean Separation:

  • setrepeat() / getrepeat() - for repeat command control (new)
  • getreg('.') - for last inserted text (unchanged)
  • ✅ No double duty, no confusion

Backward Compatibility:

  • getreg('.') behavior completely unchanged
  • ✅ Existing scripts continue to work
  • ✅ No breaking changes

Enables All Requested Use Cases:

Future-Proof:

  • ✅ Dictionary allows adding fields without breaking changes
  • ✅ Can support visual mode, counts, registers later
  • ✅ Doesn't paint us into a corner

Current Limitations (Acceptable for Phase 1)

  • ⚠️ Visual mode operations not fully supported yet (will be added via type and mode fields)
  • ⚠️ Some complex operations may not be representable initially (documented)

Implementation Plan

Phase 1 (this PR, if acceptable):

setrepeat({dict})
  Fields: 'cmd' (required), 'text' (optional)

getrepeat()
  Returns: {dict} with same structure

" Existing behavior unchanged
getreg('.') → string (last inserted text)

" Comprehensive tests including save/restore scenarios
" Documentation with use case examples

Phase 2 (future):

" Enhanced dictionary support for complex operations
setrepeat({'type': 'visual', 'mode': 'v', 'count': 3, 'cmd': 'd'})

" Backward compatibility maintained

Questions

  1. Does this approach address all the use cases from the related issues?
  2. Is the save/restore capability via getrepeat()/setrepeat() the right solution for telescope and history management?
  3. Should I proceed with this complete redesign, or do you see any remaining issues?

I'm willing to completely rework the PR to use this dictionary-based approach.
It solves all the requested use cases (save/restore, history, plugin
integration) while maintaining backward compatibility and future extensibility.

@Shougo

Shougo commented Feb 15, 2026

Copy link
Copy Markdown
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants