diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 8e0511f69d..5b09cc15f6 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -785,11 +785,13 @@ LspRequest See |LspRequest| LspTokenUpdate See |LspTokenUpdate| *MarkSet* MarkSet After a |mark| is set by |m|, |:mark|, and - |nvim_buf_set_mark()|. Supports `[a-zA-Z]` + |nvim_buf_set_mark()|, or deleted by |:delmarks| + and |nvim_buf_del_mark()|. Supports `[a-zA-Z]` marks (may support more in the future). The |autocmd-pattern| is matched against the mark name (e.g. `[ab]` matches `a` or `b`, `*` - matches all). + matches all). If line is 0, the mark has been + deleted. The |event-data| has these keys (type: `vim.event.markset.data`): - name: Mark name (e.g. "a") diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 43ff18bf58..7516c40f95 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -199,6 +199,8 @@ EVENTS • |SessionLoadPre| is triggered before loading a |Session| file. • |TabClosedPre| is triggered before closing a |tabpage|. • |TermRequest| event gained a `terminator` parameter. +• |:delmarks| now triggers the |MarkSet| autocommand with line==col==0, same + as |nvim_buf_del_mark()| HIGHLIGHTS diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 200a59b243..cd02f1223e 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -81,7 +81,9 @@ void free_xfmark(xfmark_T fm) free_fmark(fm.fmark); } -/// Free and clear fmark_T item +/// Free and clear fmark_T item. +/// +/// Does not trigger "MarkSet" event. void clear_fmark(fmark_T *const fm, const Timestamp timestamp) FUNC_ATTR_NONNULL_ALL { @@ -849,6 +851,8 @@ bool mark_check_line_bounds(buf_T *buf, fmark_T *fm, const char **errormsg) /// /// Used mainly when trashing the entire buffer during ":e" type commands. /// +/// Does not trigger "MarkSet" event. +/// /// @param[out] buf Buffer to clear marks in. void clrallmarks(buf_T *const buf, const Timestamp timestamp) FUNC_ATTR_NONNULL_ALL @@ -1010,9 +1014,30 @@ void ex_delmarks(exarg_T *eap) { int from, to; int n; + pos_T pos = { 0, 0, 0 }; if (*eap->arg == NUL && eap->forceit) { // clear all marks + for (size_t i = 0; i < NMARKS; i++) { + if (curbuf->b_namedm[i].mark.lnum != 0) { + do_markset_autocmd((char)(i + 'a'), &pos, curbuf); + } + } + if (curbuf->b_last_cursor.mark.lnum != 0) { + do_markset_autocmd('"', &pos, curbuf); + } + if (curbuf->b_last_insert.mark.lnum != 0) { + do_markset_autocmd('^', &pos, curbuf); + } + if (curbuf->b_last_change.mark.lnum != 0) { + do_markset_autocmd('.', &pos, curbuf); + } + if (curbuf->b_op_start.lnum != 0) { + do_markset_autocmd('[', &pos, curbuf); + } + if (curbuf->b_op_end.lnum != 0) { + do_markset_autocmd(']', &pos, curbuf); + } clrallmarks(curbuf, os_time()); } else if (eap->forceit) { emsg(_(e_invarg)); @@ -1044,6 +1069,9 @@ void ex_delmarks(exarg_T *eap) for (int i = from; i <= to; i++) { if (lower) { + if (curbuf->b_namedm[i - 'a'].mark.lnum != 0) { + do_markset_autocmd((char)i, &pos, curbuf); + } curbuf->b_namedm[i - 'a'].mark.lnum = 0; curbuf->b_namedm[i - 'a'].timestamp = timestamp; } else { @@ -1052,6 +1080,13 @@ void ex_delmarks(exarg_T *eap) } else { n = i - 'A'; } + if (namedfm[n].fmark.mark.lnum != 0) { + buf_T *buf = buflist_findnr(namedfm[n].fmark.fnum); + if (buf == NULL) { + buf = curbuf; + } + do_markset_autocmd((char)i, &pos, buf); + } namedfm[n].fmark.mark.lnum = 0; namedfm[n].fmark.fnum = 0; namedfm[n].fmark.timestamp = timestamp; @@ -1061,25 +1096,50 @@ void ex_delmarks(exarg_T *eap) } else { switch (*p) { case '"': + if (curbuf->b_last_cursor.mark.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } clear_fmark(&curbuf->b_last_cursor, timestamp); break; case '^': + if (curbuf->b_last_insert.mark.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } clear_fmark(&curbuf->b_last_insert, timestamp); break; case ':': // Readonly mark. No deletion allowed. break; case '.': + if (curbuf->b_last_change.mark.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } clear_fmark(&curbuf->b_last_change, timestamp); break; case '[': - curbuf->b_op_start.lnum = 0; break; + if (curbuf->b_op_start.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } + curbuf->b_op_start.lnum = 0; + break; case ']': - curbuf->b_op_end.lnum = 0; break; + if (curbuf->b_op_end.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } + curbuf->b_op_end.lnum = 0; + break; case '<': - curbuf->b_visual.vi_start.lnum = 0; break; + if (curbuf->b_visual.vi_start.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } + curbuf->b_visual.vi_start.lnum = 0; + break; case '>': - curbuf->b_visual.vi_end.lnum = 0; break; + if (curbuf->b_visual.vi_end.lnum != 0) { + do_markset_autocmd(*p, &pos, curbuf); + } + curbuf->b_visual.vi_end.lnum = 0; + break; case ' ': break; default: diff --git a/test/functional/autocmd/markset_spec.lua b/test/functional/autocmd/markset_spec.lua index d12bde304b..392481474b 100644 --- a/test/functional/autocmd/markset_spec.lua +++ b/test/functional/autocmd/markset_spec.lua @@ -151,6 +151,8 @@ describe('MarkSet', function() command('tabclose') feed('mD') + -- delmarks should signal on the buf the mark is set on, not the current buf + command('delmarks A') poke_eventloop() eq({ @@ -186,6 +188,14 @@ describe('MarkSet', function() name = 'D', }, }, + { + buf = 1, + event = { + col = 0, + line = 0, + name = 'A', + }, + }, }, eval('g:markset_events')) end) @@ -283,4 +293,57 @@ describe('MarkSet', function() eq({ 1, 1 }, api.nvim_buf_get_mark(third_bufnr, 'B')) eq({ 3, 0 }, api.nvim_buf_get_mark(first_bufnr, 'C')) end) + + it('emits when marks are deleted', function() + command([[ + let g:mark_events = [] + autocmd MarkSet * call add(g:mark_events, {'event': deepcopy(v:event)}) + ]]) + + api.nvim_buf_set_lines(0, 0, -1, true, { + 'line 1', + 'line 2', + }) + + feed('ma') + feed('mb') + command('delmarks a') + -- deleting a second time should not trigger an event + command('delmarks a') + api.nvim_buf_del_mark(0, 'b') + api.nvim_buf_del_mark(0, 'b') + + poke_eventloop() + + eq({ + { + event = { + col = 0, + line = 1, + name = 'a', + }, + }, + { + event = { + col = 0, + line = 1, + name = 'b', + }, + }, + { + event = { + col = 0, + line = 0, + name = 'a', + }, + }, + { + event = { + col = 0, + line = 0, + name = 'b', + }, + }, + }, eval('g:mark_events')) + end) end)