feat(edit): insert an unsimplified key using CTRL-SHIFT-V

This marks the following Vim patches as ported:

vim-patch:8.1.2333: with modifyOtherKeys CTRL-^ doesn't work

Problem:    With modifyOtherKeys CTRL-^ doesn't work.
Solution:   Handle the exception.
828ffd5963

vim-patch:8.1.2350: other text for CTRL-V in Insert mode with modifyOtherKeys

Problem:    Other text for CTRL-V in Insert mode with modifyOtherKeys.
Solution:   Convert the Escape sequence back to key as if modifyOtherKeys is
            not set, and use CTRL-SHIFT-V to get the Escape sequence itself.
            (closes vim/vim#5254)
fc4ea2a72d

vim-patch:8.2.2084: CTRL-V U doesn't work to enter a Unicode character

Problem:    CTRL-V U doesn't work to enter a Unicode character when
            modifyOtherKeys is effective. (Ken Takata)
Solution:   Add a flag to get_literal() for the shift key. (closes vim/vim#7413)
0684e36a7e

Omit getcmdkeycmd() change as it depends on Vim patch 8.2.2062, which
may introduce a potential breakage.
This commit is contained in:
zeertzjq 2022-04-27 13:02:55 +08:00
parent 44269c73a3
commit 212349c100
8 changed files with 123 additions and 81 deletions

View File

@ -67,12 +67,19 @@ CTRL-V Insert next non-digit literally. Up to three digits form the
decimal value of a single byte. The non-digit and the three decimal value of a single byte. The non-digit and the three
digits are not considered for mapping. This works the same digits are not considered for mapping. This works the same
way as in Insert mode (see above, |i_CTRL-V|). way as in Insert mode (see above, |i_CTRL-V|).
For special keys, the CTRL modifier may be included into the
key to produce a control character. If there is no control
character for the key then its |key-notation| is inserted.
Note: Under Windows CTRL-V is often mapped to paste text. Note: Under Windows CTRL-V is often mapped to paste text.
Use CTRL-Q instead then. Use CTRL-Q instead then.
*c_CTRL-Q* *c_CTRL-Q*
CTRL-Q Same as CTRL-V. But with some terminals it is used for CTRL-Q Same as CTRL-V. But with some terminals it is used for
control flow, it doesn't work then. control flow, it doesn't work then.
CTRL-SHIFT-V *c_CTRL-SHIFT-V* *c_CTRL-SHIFT-Q*
CTRL-SHIFT-Q Works just like CTRL-V, but do not try to include the CTRL
modifier into the key.
*c_<Left>* *c_Left* *c_<Left>* *c_Left*
<Left> cursor left <Left> cursor left
*c_<Right>* *c_Right* *c_<Right>* *c_Right*

View File

@ -192,12 +192,14 @@ CTRL-D Delete one shiftwidth of indent at the start of the current
label. label.
*i_CTRL-V* *i_CTRL-V*
CTRL-V Insert next non-digit literally. For special keys, the CTRL-V Insert next non-digit literally. It's also possible to enter
terminal code is inserted. It's also possible to enter the the decimal, octal or hexadecimal value of a character
decimal, octal or hexadecimal value of a character
|i_CTRL-V_digit|. |i_CTRL-V_digit|.
The characters typed right after CTRL-V are not considered for The characters typed right after CTRL-V are not considered for
mapping. mapping.
For special keys, the CTRL modifier may be included into the
key to produce a control character. If there is no control
character for the key then its |key-notation| is inserted.
Note: When CTRL-V is mapped (e.g., to paste text) you can Note: When CTRL-V is mapped (e.g., to paste text) you can
often use CTRL-Q instead |i_CTRL-Q|. often use CTRL-Q instead |i_CTRL-Q|.
@ -206,6 +208,10 @@ CTRL-Q Same as CTRL-V.
Note: Some terminal connections may eat CTRL-Q, it doesn't Note: Some terminal connections may eat CTRL-Q, it doesn't
work then. It does work in the GUI. work then. It does work in the GUI.
CTRL-SHIFT-V *i_CTRL-SHIFT-V* *i_CTRL-SHIFT-Q*
CTRL-SHIFT-Q Works just like CTRL-V, but do not try to include the CTRL
modifier into the key.
CTRL-X Enter CTRL-X mode. This is a sub-mode where commands can CTRL-X Enter CTRL-X mode. This is a sub-mode where commands can
be given to complete words or scroll the window. See be given to complete words or scroll the window. See
|i_CTRL-X| and |ins-completion|. |i_CTRL-X| and |ins-completion|.

View File

@ -1587,7 +1587,8 @@ static void ins_ctrl_v(void)
add_to_showcmd_c(Ctrl_V); add_to_showcmd_c(Ctrl_V);
c = get_literal(); // Do not include modifiers into the key for CTRL-SHIFT-V.
c = get_literal(mod_mask & MOD_MASK_SHIFT);
if (did_putchar) { if (did_putchar) {
// when the line fits in 'columns' the '^' is at the start of the next // when the line fits in 'columns' the '^' is at the start of the next
// line and will not removed by the redraw // line and will not removed by the redraw
@ -5612,13 +5613,13 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
return m; return m;
} }
/* /// Next character is interpreted literally.
* Next character is interpreted literally. /// A one, two or three digit decimal number is interpreted as its byte value.
* A one, two or three digit decimal number is interpreted as its byte value. /// If one or two digits are entered, the next character is given to vungetc().
* If one or two digits are entered, the next character is given to vungetc(). /// For Unicode a character > 255 may be returned.
* For Unicode a character > 255 may be returned. ///
*/ /// @param no_simplify do not include modifiers into the key
int get_literal(void) int get_literal(bool no_simplify)
{ {
int cc; int cc;
int nc; int nc;
@ -5636,6 +5637,9 @@ int get_literal(void)
i = 0; i = 0;
for (;;) { for (;;) {
nc = plain_vgetc(); nc = plain_vgetc();
if (!no_simplify) {
nc = merge_modifiers(nc);
}
if ((mod_mask & ~MOD_MASK_SHIFT) != 0) { if ((mod_mask & ~MOD_MASK_SHIFT) != 0) {
// A character with non-Shift modifiers should not be a valid // A character with non-Shift modifiers should not be a valid
// character for i_CTRL-V_digit. // character for i_CTRL-V_digit.

View File

@ -2208,7 +2208,11 @@ static int command_line_handle_key(CommandLineState *s)
case Ctrl_Q: case Ctrl_Q:
s->ignore_drag_release = true; s->ignore_drag_release = true;
putcmdline('^', true); putcmdline('^', true);
s->c = get_literal(); // get next (two) character(s)
// Get next (two) characters.
// Do not include modifiers into the key for CTRL-SHIFT-V.
s->c = get_literal(mod_mask & MOD_MASK_SHIFT);
s->do_abbr = false; // don't do abbreviation now s->do_abbr = false; // don't do abbreviation now
ccline.special_char = NUL; ccline.special_char = NUL;
// may need to remove ^ when composing char was typed // may need to remove ^ when composing char was typed

View File

@ -1442,6 +1442,27 @@ static void updatescript(int c)
} }
} }
/// Merge "mod_mask" into "c_arg"
int merge_modifiers(int c_arg)
{
int c = c_arg;
if (mod_mask & MOD_MASK_CTRL) {
if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) {
c &= 0x1f;
mod_mask &= ~MOD_MASK_CTRL;
if (c == 0) {
c = K_ZERO;
}
} else if (c == '6') {
// CTRL-6 is equivalent to CTRL-^
c = 0x1e;
mod_mask &= ~MOD_MASK_CTRL;
}
}
return c;
}
/// Get the next input character. /// Get the next input character.
/// Can return a special key or a multi-byte character. /// Can return a special key or a multi-byte character.
/// Can return NUL when called recursively, use safe_vgetc() if that's not /// Can return NUL when called recursively, use safe_vgetc() if that's not
@ -1603,19 +1624,7 @@ int vgetc(void)
// A modifier was not used for a mapping, apply it to ASCII // A modifier was not used for a mapping, apply it to ASCII
// keys. Shift would already have been applied. // keys. Shift would already have been applied.
if (mod_mask & MOD_MASK_CTRL) { c = merge_modifiers(c);
if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) {
c &= 0x1f;
mod_mask &= ~MOD_MASK_CTRL;
if (c == 0) {
c = K_ZERO;
}
} else if (c == '6') {
// CTRL-6 is equivalent to CTRL-^
c = 0x1e;
mod_mask &= ~MOD_MASK_CTRL;
}
}
// If mappings are enabled (i.e., not Ctrl-v) and the user directly typed // If mappings are enabled (i.e., not Ctrl-v) and the user directly typed
// something with a meta- or alt- modifier that was not mapped, interpret // something with a meta- or alt- modifier that was not mapped, interpret

View File

@ -5154,7 +5154,7 @@ static void nv_replace(cmdarg_T *cap)
// get another character // get another character
if (cap->nchar == Ctrl_V) { if (cap->nchar == Ctrl_V) {
had_ctrl_v = Ctrl_V; had_ctrl_v = Ctrl_V;
cap->nchar = get_literal(); cap->nchar = get_literal(false);
// Don't redo a multibyte character with CTRL-V. // Don't redo a multibyte character with CTRL-V.
if (cap->nchar > DEL) { if (cap->nchar > DEL) {
had_ctrl_v = NUL; had_ctrl_v = NUL;
@ -5369,7 +5369,7 @@ static void nv_vreplace(cmdarg_T *cap)
emsg(_(e_modifiable)); emsg(_(e_modifiable));
} else { } else {
if (cap->extra_char == Ctrl_V) { // get another character if (cap->extra_char == Ctrl_V) { // get another character
cap->extra_char = get_literal(); cap->extra_char = get_literal(false);
} }
stuffcharReadbuff(cap->extra_char); stuffcharReadbuff(cap->extra_char);
stuffcharReadbuff(ESC); stuffcharReadbuff(ESC);

View File

@ -3,67 +3,74 @@
local helpers = require('test.functional.helpers')(after_each) local helpers = require('test.functional.helpers')(after_each)
local clear, insert, funcs, eq, feed = local clear, insert, funcs, eq, feed =
helpers.clear, helpers.insert, helpers.funcs, helpers.eq, helpers.feed helpers.clear, helpers.insert, helpers.funcs, helpers.eq, helpers.feed
local eval = helpers.eval
local meths = helpers.meths local meths = helpers.meths
describe('cmdline CTRL-R', function() describe('cmdline', function()
before_each(clear) before_each(clear)
it('pasting non-special register inserts <CR> *between* lines', function() describe('Ctrl-R', function()
insert([[ it('pasting non-special register inserts <CR> *between* lines', function()
line1abc insert([[
line2somemoretext line1abc
]]) line2somemoretext
-- Yank 2 lines linewise, then paste to cmdline. ]])
feed([[<C-\><C-N>gg0yj:<C-R>0]]) -- Yank 2 lines linewise, then paste to cmdline.
-- <CR> inserted between lines, NOT after the final line. feed([[<C-\><C-N>gg0yj:<C-R>0]])
eq('line1abc\rline2somemoretext', funcs.getcmdline()) -- <CR> inserted between lines, NOT after the final line.
eq('line1abc\rline2somemoretext', funcs.getcmdline())
-- Yank 2 lines charwise, then paste to cmdline. -- Yank 2 lines charwise, then paste to cmdline.
feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) feed([[<C-\><C-N>gg05lyvj:<C-R>0]])
-- <CR> inserted between lines, NOT after the final line. -- <CR> inserted between lines, NOT after the final line.
eq('abc\rline2', funcs.getcmdline()) eq('abc\rline2', funcs.getcmdline())
-- Yank 1 line linewise, then paste to cmdline. -- Yank 1 line linewise, then paste to cmdline.
feed([[<C-\><C-N>ggyy:<C-R>0]]) feed([[<C-\><C-N>ggyy:<C-R>0]])
-- No <CR> inserted. -- No <CR> inserted.
eq('line1abc', funcs.getcmdline()) eq('line1abc', funcs.getcmdline())
end)
it('pasting special register inserts <CR>, <NL>', function()
feed([[:<C-R>="foo\nbar\rbaz"<CR>]])
eq('foo\nbar\rbaz', funcs.getcmdline())
end)
end) end)
it('pasting special register inserts <CR>, <NL>', function() it('Ctrl-Shift-V supports entering unsimplified key notations', function()
feed([[:<C-R>="foo\nbar\rbaz"<CR>]]) feed(':"<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><CR>')
eq('foo\nbar\rbaz', funcs.getcmdline())
end) eq('"<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>', eval('@:'))
end) end)
describe('cmdline history', function() describe('history', function()
before_each(clear) it('correctly clears start of the history', function()
-- Regression test: check absence of the memory leak when clearing start of
it('correctly clears start of the history', function() -- the history using ex_getln.c/clr_history().
-- Regression test: check absence of the memory leak when clearing start of eq(1, funcs.histadd(':', 'foo'))
-- the history using ex_getln.c/clr_history(). eq(1, funcs.histdel(':'))
eq(1, funcs.histadd(':', 'foo')) eq('', funcs.histget(':', -1))
eq(1, funcs.histdel(':')) end)
eq('', funcs.histget(':', -1))
end) it('correctly clears end of the history', function()
-- Regression test: check absence of the memory leak when clearing end of
it('correctly clears end of the history', function() -- the history using ex_getln.c/clr_history().
-- Regression test: check absence of the memory leak when clearing end of meths.set_option('history', 1)
-- the history using ex_getln.c/clr_history(). eq(1, funcs.histadd(':', 'foo'))
meths.set_option('history', 1) eq(1, funcs.histdel(':'))
eq(1, funcs.histadd(':', 'foo')) eq('', funcs.histget(':', -1))
eq(1, funcs.histdel(':')) end)
eq('', funcs.histget(':', -1))
end) it('correctly removes item from history', function()
-- Regression test: check that ex_getln.c/del_history_idx() correctly clears
it('correctly removes item from history', function() -- history index after removing history entry. If it does not then deleting
-- Regression test: check that ex_getln.c/del_history_idx() correctly clears -- history will result in a double free.
-- history index after removing history entry. If it does not then deleting eq(1, funcs.histadd(':', 'foo'))
-- history will result in a double free. eq(1, funcs.histadd(':', 'bar'))
eq(1, funcs.histadd(':', 'foo')) eq(1, funcs.histadd(':', 'baz'))
eq(1, funcs.histadd(':', 'bar')) eq(1, funcs.histdel(':', -2))
eq(1, funcs.histadd(':', 'baz')) eq(1, funcs.histdel(':'))
eq(1, funcs.histdel(':', -2)) eq('', funcs.histget(':', -1))
eq(1, funcs.histdel(':')) end)
eq('', funcs.histget(':', -1))
end) end)
end) end)

View File

@ -131,6 +131,11 @@ describe('insert-mode', function()
end) end)
end) end)
it('Ctrl-Shift-V supports entering unsimplified key notations', function()
feed('i<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><Esc>')
expect('<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>')
end)
describe([[With 'insertmode', Insert mode is not re-entered immediately after <C-L>]], function() describe([[With 'insertmode', Insert mode is not re-entered immediately after <C-L>]], function()
before_each(function() before_each(function()
command('set insertmode') command('set insertmode')