diff options
-rw-r--r-- | runtime/autoload/shada.vim | 3 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 2 | ||||
-rw-r--r-- | runtime/doc/starting.txt | 49 | ||||
-rw-r--r-- | src/nvim/eval.c | 13 | ||||
-rw-r--r-- | src/nvim/ops.c | 29 | ||||
-rw-r--r-- | src/nvim/shada.c | 88 | ||||
-rw-r--r-- | test/functional/legacy/eval_spec.lua | 9 | ||||
-rw-r--r-- | test/functional/plugin/shada_spec.lua | 37 | ||||
-rw-r--r-- | test/functional/shada/registers_spec.lua | 36 |
9 files changed, 207 insertions, 59 deletions
diff --git a/runtime/autoload/shada.vim b/runtime/autoload/shada.vim index cf27ee608a..87acc515ee 100644 --- a/runtime/autoload/shada.vim +++ b/runtime/autoload/shada.vim @@ -45,7 +45,7 @@ call map(copy(s:SHADA_ENTRY_NAMES), let s:SHADA_MAP_ENTRIES = { \'search_pattern': ['sp', 'sh', 'ss', 'sb', 'sm', 'sc', 'sl', 'se', 'so', \ 'su'], - \'register': ['n', 'rc', 'rw', 'rt'], + \'register': ['n', 'rc', 'rw', 'rt', 'ru'], \'global_mark': ['n', 'f', 'l', 'c'], \'local_mark': ['f', 'n', 'l', 'c'], \'jump': ['f', 'l', 'c'], @@ -139,6 +139,7 @@ let s:SHADA_STANDARD_KEYS = { \'rt': ['type', 'regtype', s:SHADA_ENUMS.regtype.CHARACTERWISE], \'rw': ['block width', 'uint', 0], \'rc': ['contents', 'binarray', s:SHADA_REQUIRED], + \'ru': ['is_unnamed', 'boolean', g:msgpack#false], \'n': ['name', 'intchar', char2nr('"')], \'l': ['line number', 'uint', 1], \'c': ['column', 'uint', 0], diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index b692b28418..11b42dd2e5 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6719,6 +6719,8 @@ setreg({regname}, {value} [, {options}]) used as the width of the selection - if it is not specified then the width of the block is set to the number of characters in the longest line (counting a <Tab> as 1 character). + If {options} contains "u" or '"', then the unnamed register is + set to point to register {regname}. If {options} contains no register settings, then the default is to use character mode unless {value} ends in a <NL> for diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 477d927a12..4b3f894cf7 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -1281,29 +1281,32 @@ exactly four MessagePack objects: 5 (Register) Map describing one register (|registers|). If key value is equal to default then it is normally not present. Keys: - Key Type Def Description ~ - rt UInteger 0 Register type: - No Description ~ - 0 |characterwise-register| - 1 |linewise-register| - 2 |blockwise-register| - rw UInteger 0 Register width. Only valid - for |blockwise-register|s. - rc Array of binary N/A Register contents. Each - entry in the array - represents its own line. - NUL characters inside the - line should be represented - as NL according to - |NL-used-for-Nul|. - n UInteger N/A Register name: character - code in range [1, 255]. - Example: |quote0| register - has name 48 (ASCII code for - zero character). - * any none Other keys are allowed - for compatibility reasons, - see |shada-compatibility|. + Key Type Def Description ~ + rt UInteger 0 Register type: + No Description ~ + 0 |characterwise-register| + 1 |linewise-register| + 2 |blockwise-register| + rw UInteger 0 Register width. Only valid + for |blockwise-register|s. + rc Array of binary N/A Register contents. Each + entry in the array + represents its own line. + NUL characters inside the + line should be represented + as NL according to + |NL-used-for-Nul|. + ru Boolean false Unnamed register. Whether + the unnamed register had + pointed to this register. + n UInteger N/A Register name: character + code in range [1, 255]. + Example: |quote0| register + has name 48 (ASCII code for + zero character). + * any none Other keys are allowed + for compatibility reasons, + see |shada-compatibility|. 6 (Variable) Array containing two items: variable name (binary) and variable value (any object). Values are converted using the same code |msgpackparse()| uses when reading, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 4e303414a3..7187386ec7 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5100,7 +5100,8 @@ bool garbage_collect(bool testing) do { yankreg_T reg; char name = NUL; - reg_iter = op_register_iter(reg_iter, &name, ®); + bool is_unnamed = false; + reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed); if (name != NUL) { ABORTING(set_ref_dict)(reg.additional_data, copyID); } @@ -14834,6 +14835,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) regname = '"'; } + bool set_unnamed = false; if (argvars[2].v_type != VAR_UNKNOWN) { const char *stropt = tv_get_string_chk(&argvars[2]); if (stropt == NULL) { @@ -14862,6 +14864,10 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; } + case 'u': case '"': { // unnamed register + set_unnamed = true; + break; + } } } } @@ -14914,6 +14920,11 @@ free_lstval: append, yank_type, block_len); } rettv->vval.v_number = 0; + + if (set_unnamed) { + // Discard the result. We already handle the error case. + if (op_register_set_previous(regname)) { } + } } /* diff --git a/src/nvim/ops.c b/src/nvim/ops.c index c77781b1d1..e374686286 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5780,7 +5780,7 @@ static inline bool reg_empty(const yankreg_T *const reg) /// @return Pointer that needs to be passed to next `op_register_iter` call or /// NULL if iteration is over. const void *op_register_iter(const void *const iter, char *const name, - yankreg_T *const reg) + yankreg_T *const reg, bool *is_unnamed) FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { *name = NUL; @@ -5796,6 +5796,7 @@ const void *op_register_iter(const void *const iter, char *const name, int iter_off = (int)(iter_reg - &(y_regs[0])); *name = (char)get_register_name(iter_off); *reg = *iter_reg; + *is_unnamed = (iter_reg == y_previous); while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) { if (!reg_empty(iter_reg)) { return (void *) iter_reg; @@ -5820,10 +5821,11 @@ size_t op_register_amount(void) /// Set register to a given value /// /// @param[in] name Register name. -/// @param[in] reg Register value. +/// @param[in] reg Register value. +/// @param[in] is_unnamed Whether to set the unnamed regiseter to reg /// /// @return true on success, false on failure. -bool op_register_set(const char name, const yankreg_T reg) +bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed) { int i = op_reg_index(name); if (i == -1) { @@ -5831,6 +5833,10 @@ bool op_register_set(const char name, const yankreg_T reg) } free_register(&y_regs[i]); y_regs[i] = reg; + + if (is_unnamed) { + y_previous = &y_regs[i]; + } return true; } @@ -5847,3 +5853,20 @@ const yankreg_T *op_register_get(const char name) } return &y_regs[i]; } + +/// Set the previous yank register +/// +/// @param[in] name Register name. +/// +/// @return true on success, false on failure. +bool op_register_set_previous(const char name) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + int i = op_reg_index(name); + if (i == -1) { + return false; + } + + y_previous = &y_regs[i]; + return true; +} diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 87b4617099..4788b1e7d0 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -98,6 +98,7 @@ KHASH_SET_INIT_STR(strset) #define REG_KEY_TYPE "rt" #define REG_KEY_WIDTH "rw" #define REG_KEY_CONTENTS "rc" +#define REG_KEY_UNNAMED "ru" #define KEY_LNUM "l" #define KEY_COL "c" @@ -284,6 +285,7 @@ typedef struct { char name; MotionType type; char **contents; + bool is_unnamed; size_t contents_size; size_t width; dict_T *additional_data; @@ -473,6 +475,7 @@ static const ShadaEntry sd_default_values[] = { .type = kMTCharWise, .contents = NULL, .contents_size = 0, + .is_unnamed = false, .width = 0, .additional_data = NULL), DEF_SDE(Variable, global_var, @@ -1335,7 +1338,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .y_width = (colnr_T) cur_entry.data.reg.width, .timestamp = cur_entry.timestamp, .additional_data = cur_entry.data.reg.additional_data, - })) { + }, cur_entry.data.reg.is_unnamed)) { shada_free_shada_entry(&cur_entry); } // Do not free shada entry: its allocated memory was saved above. @@ -1780,6 +1783,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, 2 // Register contents and name + ONE_IF_NOT_DEFAULT(entry, reg.type) + ONE_IF_NOT_DEFAULT(entry, reg.width) + + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed) // Additional entries, if any: + (size_t) (entry.data.reg.additional_data == NULL ? 0 @@ -1800,6 +1804,14 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, PACK_STATIC_STR(REG_KEY_WIDTH); msgpack_pack_uint64(spacker, (uint64_t) entry.data.reg.width); } + if (!CHECK_DEFAULT(entry, reg.is_unnamed)) { + PACK_STATIC_STR(REG_KEY_UNNAMED); + if (entry.data.reg.is_unnamed) { + msgpack_pack_true(spacker); + } else { + msgpack_pack_false(spacker); + } + } DUMP_ADDITIONAL_DATA(entry.data.reg.additional_data, "register item"); break; } @@ -2318,6 +2330,48 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse, } } +/// Initialize registers for writing to the ShaDa file +/// +/// @param[in] wms The WriteMergerState used when writing. +/// @param[in] max_reg_lines The maximum number of register lines. +static inline void shada_initialize_registers(WriteMergerState *const wms, + int max_reg_lines) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + const void *reg_iter = NULL; + const bool limit_reg_lines = max_reg_lines >= 0; + do { + yankreg_T reg; + char name = NUL; + bool is_unnamed = false; + reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed); + if (name == NUL) { + break; + } + if (limit_reg_lines && reg.y_size > (size_t)max_reg_lines) { + continue; + } + wms->registers[op_reg_index(name)] = (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemRegister, + .timestamp = reg.timestamp, + .data = { + .reg = { + .contents = (char **)reg.y_array, + .contents_size = (size_t)reg.y_size, + .type = reg.y_type, + .width = (size_t)(reg.y_type == kMTBlockWise ? reg.y_width : 0), + .additional_data = reg.additional_data, + .name = name, + .is_unnamed = is_unnamed, + } + } + } + }; + } while (reg_iter != NULL); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2344,7 +2398,6 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, if (max_reg_lines < 0) { max_reg_lines = get_shada_parameter('"'); } - const bool limit_reg_lines = max_reg_lines >= 0; const bool dump_registers = (max_reg_lines != 0); khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset); const size_t max_kbyte = (size_t) max_kbyte_i; @@ -2587,35 +2640,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize registers if (dump_registers) { - const void *reg_iter = NULL; - do { - yankreg_T reg; - char name = NUL; - reg_iter = op_register_iter(reg_iter, &name, ®); - if (name == NUL) { - break; - } - if (limit_reg_lines && reg.y_size > (size_t)max_reg_lines) { - continue; - } - wms->registers[op_reg_index(name)] = (PossiblyFreedShadaEntry) { - .can_free_entry = false, - .data = { - .type = kSDItemRegister, - .timestamp = reg.timestamp, - .data = { - .reg = { - .contents = (char **) reg.y_array, - .contents_size = (size_t) reg.y_size, - .type = reg.y_type, - .width = (size_t) (reg.y_type == kMTBlockWise ? reg.y_width : 0), - .additional_data = reg.additional_data, - .name = name, - } - } - } - }; - } while (reg_iter != NULL); + shada_initialize_registers(wms, max_reg_lines); } // Initialize buffers @@ -3594,6 +3619,7 @@ shada_read_next_item_start: entry->data.reg.contents[i] = BIN_CONVERTED(arr.ptr[i].via.bin); } } + BOOLEAN_KEY("register", REG_KEY_UNNAMED, entry->data.reg.is_unnamed) TYPED_KEY("register", REG_KEY_TYPE, "an unsigned integer", entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8) TYPED_KEY("register", KEY_NAME_CHAR, "an unsigned integer", diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index d7ef508194..c5d38d6d05 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -516,6 +516,15 @@ describe('eval', function() eq({'item'}, eval("y")) end) + it('sets the unnamed register when the "u" option is passed to setreg', function() + command("call setreg('a','a reg', 'cu')") + eq("a reg", eval('@"')) + command("call setreg('b','b reg', 'cu')") + eq("b reg", eval('@"')) + command("call setreg('c','c reg', 'c')") + eq("b reg", eval('@"')) + end) + it('search and expressions', function() command('so test_eval_setup.vim') command([=[call SetReg('/', ['abc/'])]=]) diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index b543037ae2..639833071b 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -179,6 +179,7 @@ describe('In autoload/shada.vim', function() ' + n name \'@\'', ' + rc contents ["abc", "def"]', ' + rt type CHARACTERWISE', + ' + ru is_unnamed FALSE', ' + rw block width 10', ' + sb search backward TRUE', ' + sc smartcase value FALSE', @@ -204,6 +205,7 @@ describe('In autoload/shada.vim', function() 'rt': 0, 'rw': 10, 'rc': ['abc', 'def'], + 'ru': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0}, 'n': 0x40, 'l': 10, 'c': 0, @@ -226,6 +228,8 @@ describe('In autoload/shada.vim', function() .. '0 (CHARACTERWISE), 1 (LINEWISE), 2 (BLOCKWISE)', ' + rt type 10', ' # Expected boolean', + ' + ru is_unnamed 10', + ' # Expected boolean', ' + sc smartcase value NIL', ' # Expected boolean', ' + sm magic value "TRUE"', @@ -240,6 +244,7 @@ describe('In autoload/shada.vim', function() 'sp': {'_TYPE': v:msgpack_types.string, '_VAL': ["abc"]}, 'rt': 10, 'rc': '10', + 'ru': 10, 'n': -0x40, 'l': -10, 'c': 'abc', @@ -636,6 +641,7 @@ describe('In autoload/shada.vim', function() ' # Required key missing: rc', ' + rw block width 0', ' + rt type CHARACTERWISE', + ' + ru is_unnamed FALSE', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { }}] ]]):gsub('\n', '')) sd2strings_eq({ @@ -645,6 +651,7 @@ describe('In autoload/shada.vim', function() ' # Required key missing: rc', ' + rw block width 0', ' + rt type CHARACTERWISE', + ' + ru is_unnamed FALSE', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, }}] ]]):gsub('\n', '')) @@ -655,9 +662,11 @@ describe('In autoload/shada.vim', function() ' + rc contents ["abc", "def"]', ' + rw block width 0', ' + rt type CHARACTERWISE', + ' + ru is_unnamed FALSE', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, 'rc': ["abc", "def"], + 'ru': {'_TYPE': v:msgpack_types.boolean, '_VAL': 0}, }}] ]]):gsub('\n', '')) sd2strings_eq({ 'Register with timestamp ' .. epoch .. ':', @@ -668,9 +677,11 @@ describe('In autoload/shada.vim', function() ' | - "abcdefghijklmnopqrstuvwxyz"', ' + rw block width 0', ' + rt type CHARACTERWISE', + ' + ru is_unnamed TRUE', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'], + 'ru': {'_TYPE': v:msgpack_types.boolean, '_VAL': 1}, }}] ]]):gsub('\n', '')) sd2strings_eq({ 'Register with timestamp ' .. epoch .. ':', @@ -681,6 +692,7 @@ describe('In autoload/shada.vim', function() ' | - "abcdefghijklmnopqrstuvwxyz"', ' + rw block width 0', ' + rt type CHARACTERWISE', + ' + ru is_unnamed FALSE', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'], @@ -696,6 +708,7 @@ describe('In autoload/shada.vim', function() ' | - "abcdefghijklmnopqrstuvwxyz"', ' + rw block width 5', ' + rt type LINEWISE', + ' + ru is_unnamed FALSE', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'], @@ -712,11 +725,14 @@ describe('In autoload/shada.vim', function() ' # Expected integer', ' + rw block width ""', ' + rt type BLOCKWISE', + ' # Expected boolean', + ' + ru is_unnamed ""', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'], 'rw': "", 'rt': 2, + 'ru': "" }}] ]]):gsub('\n', '')) sd2strings_eq({ 'Register with timestamp ' .. epoch .. ':', @@ -729,11 +745,32 @@ describe('In autoload/shada.vim', function() ' # Unexpected enum value: expected one of 0 (CHARACTERWISE), ' .. '1 (LINEWISE), 2 (BLOCKWISE)', ' + rt type 10', + ' # Expected boolean', + ' + ru is_unnamed ["abc", "def"]', }, ([[ [{'type': 5, 'timestamp': 0, 'data': { 'n': 0x20, 'rc': 0, 'rw': -1, 'rt': 10, + 'ru': ['abc', 'def'], + }}] ]]):gsub('\n', '')) + sd2strings_eq({ + 'Register with timestamp ' .. epoch .. ':', + ' % Key Description Value', + ' + n name \' \'', + ' + rc contents @', + ' | - "abcdefghijklmnopqrstuvwxyz"', + ' | - "abcdefghijklmnopqrstuvwxyz"', + ' + rw block width 5', + ' + rt type LINEWISE', + ' # Expected boolean', + ' + ru is_unnamed 0', + }, ([[ [{'type': 5, 'timestamp': 0, 'data': { + 'n': 0x20, + 'rc': ['abcdefghijklmnopqrstuvwxyz', 'abcdefghijklmnopqrstuvwxyz'], + 'rw': 5, + 'rt': 1, + 'ru': 0, }}] ]]):gsub('\n', '')) end) diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index fc812f799c..71af14aba8 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -148,4 +148,40 @@ describe('ShaDa support code', function() eq({{'\171«'}, 'v'}, getreg('e')) end) + it('has a blank unnamed register if it wasn\'t set and register 0 is empty', + function() + setreg('1', {'one'}, 'c') + setreg('2', {'two'}, 'c') + setreg('a', {'a'}, 'c') + nvim_command('qall') + reset() + eq({{}, ''}, getreg('0')) + eq({{'one'}, 'v'}, getreg('1')) + eq({{}, ''}, getreg('"')) + eq({{'a'}, 'v'}, getreg('a')) + end) + + it('defaults the unnamed register to register 0 if it wasn\'t set', + function() + setreg('0', {'zero'}, 'c') + setreg('1', {'one'}, 'c') + setreg('2', {'two'}, 'c') + nvim_command('qall') + reset() + eq({{'zero'}, 'v'}, getreg('0')) + eq({{'one'}, 'v'}, getreg('1')) + eq({{'zero'}, 'v'}, getreg('"')) + end) + + it('remembers which register was the unnamed register when loading', + function() + setreg('0', {'zero'}, 'c') + setreg('1', {'one'}, 'cu') + setreg('2', {'two'}, 'c') + nvim_command('qall') + reset() + eq({{'zero'}, 'v'}, getreg('0')) + eq({{'one'}, 'v'}, getreg('1')) + eq({{'one'}, 'v'}, getreg('"')) + end) end) |