Vim developers maillist,
Patch: The patch "vim_option_protectncwcleol.patch" (~177.143 KB)
referenced throughout this message is posted at
=====
[
https://gist.github.com/alexkulungowski/4cadb5450c9c4bdf03309155fd30611c][Vim
option "protectncwcleol"/"pncwcleol" patch · GitHub]
=====
. I recommend reading all of this message before viewing the patch.
Preamble: I suspect proposals similar to the present one have been
made before. If so, please consider this message and the posted patch
my vote for the addition of equivalent functionality, with the patch
in particular standing as a measure of the earnestness of my vote.
The vague formulation "equivalent functionality" in the previous
sentence refers to something that somehow in most of the likely cases
circumvents the irritating problem I describe below. I view this
patch as entirely provisional: it is merely one of the less painful of
the many possible solutions I've considered over the years. I have
been using variations of this patch in production contexts for weeks
without any obvious problems. Before I started developing this patch,
I read the entirety of
=====
[
https://github.com/vim/vim/blob/master/runtime/doc/todo.txt][vim/todo.txt
at master · vim/vim · GitHub]
=====
and did not see anything analogous to what I need. I did not find
much of use through online searches, either, though that might be
because I searched for the wrong thing. If there are other solutions
to the problem I describe below, please inform me of them. I haven't
yet used Neovim, but if it provides a solution please let me know.
Problem: For years now I have been using large (and steadily
increasing) numbers of windows and tabs in conjunction with
":mksession" and "vim -S". I cannot remember precisely when I began
developing these habits, but for as long as I have been relying on the
relevant features, I have kept the following selection from ":help
tab-page-intro" well in mind:
=====
:help tab-page-intro
... ... ...
Tabs are also a nice way to edit a buffer temporarily without changing the
current window layout. Open a new tab page, do whatever you want to do and
close the tab page.
... ... ...
=====
. This technique is quite useful, but I have frequently been irked
when an unintentional, mistyped, or otherwise malformed command --- in
short, a mistake --- disrupts multiple windows in a manner that is
often not conveniently recoverable. The type of accidental
multi-window disruption I wish to avoid is best demonstrated by an
executable example. Starting with a single window open on an empty
unnamed buffer, execute the following commands:
=====
:call append(0, split("Line 1.\nLine 2.\nLine 3.\nLine 4.\nLine
5.\nLine 6.\nLine 7.\nLine 8.\nLine 9.\nLine 10.\n", '\n'))
:execute "normal! Gdd" | redraw!
:silent execute "1 | split | resize 1 | wincmd j | 4 | split | resize
1 | wincmd j | 7 | split | resize 1 | wincmd j | 10 | 2wincmd k |
resize"
=====
. Note the line number of each of the four windows, which, to be
explicit, is the buffer line number of the associated window's cursor.
I will refer to the buffer line number of a window's cursor as that
window's "wcl_lnum", which is an abbreviation of "window current line
line number". (One might wonder why I don't reference a/the window's
cursor or cursor position in this identifier or throughout the rest of
this message, for that matter. I promise I have reasons.) Now
execute the following commands:
=====
:tab split
:normal! 7G0dG
=====
. Assume that at this point you realize that you've made a mistake
and should have deleted only the eighth line. Execute the following
commands:
=====
:undo
:quit
=====
. The screen position, height, and width of each window, i.e., layout
without regard to contents, in the first tab are unchanged, as are the
"wcl_lnum"s of the first and second windows from the top of the
screen; the "wcl_lnum"s of the third and fourth windows, however, are
now both "6", their ordering lost. This example is simple and
contrived, but imagine a much more complex arrangement of windows and
tabs viewing a single large file. Then consider making a single hasty
mistake --- deleting the wrong section, for example --- that silently
obliterates the "wcl_lnum" ordering of tens or even hundreds of
windows distributed among the non-current (and therefore non-visible
at the time the mistake is made) tabs. Note that there are critical
cases where I _expect_ "wcl_lnum" ordering to be invalidated and/or
lost, particularly those involving a file that is altered outside of
the current Vim process (say, by Git) and subsequently reloaded (after
a helpful warning) via ":edit". I have developed ways of recovering
from the "wcl_lnum" disruptions produced in such situations, but these
techniques, which can still be somewhat time consuming, depend on
preparation, and the only way I know of to prepare for a silent
multiple-"wcl_lnum"-flattening mistake is to regularly back up the
file produced by ":mksession".
Proposal: I propose adding the global/buffer-local boolean option
"protectncwcleol"/"pncwcleol", where "ncwcl" stands for "non-current
window current line" and "eol", as you may have guessed, "end of
line". According to ":help 'protectncwcleol'" (from
"vim_option_protectncwcleol.patch:runtime/doc/options.txt"):
=====
:help 'protectncwcleol'
... ... ...
'protectncwcleol' 'pncwcleol' boolean (default off)
global or local to buffer |global-local|
{not in Vi}
{only available when compiled with the
|+protectncwcleol| feature}
When on, prevent deletion of non-current window current line EOLs. A
non-current window current line is a line in the current buffer that is
the current line of at least one window other than the current window.
If this option has a local value, use this command to switch back to
using the global value: >
:setlocal protectncwcleol<
<
... ... ...
=====
. In the example provided in the problem statement above, executing
":set protectncwcleol" before the ":normal! 7G0dG" command would
result in the latter deleting the entire seventh line up to but not
including the E.O.L. and producing the "semsg()" message "EXXX:
is_ncwcl_lnum(): 7".
The posted patch referenced at the beginning of this message contains
the "protectncwcleol"(/"pncwcleol")-related modifications I have made
to
=====
[
https://github.com/vim/vim/commit/de19b745eee06a8a204988ae9989d97143caece9][patch
8.2.1093: Python: double free when adding item to dict fails ·
vim/vim@de19b74 · GitHub]
=====
(this is the most recent "master" branch commit according to "git
merge-base", anyway, and is dated "Mon Jun 29 23:07:44 2020 +0200").
In addition to changes to existing files, this patch includes the new
file "vim_option_protectncwcleol.patch:src/testdir/test_option_protectncwcleol.vim"
containing a testing framework developed in parallel with the
"protectncwcleol" option. The "protectncwcleol" testing framework is
currently the most comprehensive documentation of the
"protectncwcleol" option's expected behaviour. Describing this
expected behaviour in prose would take some effort, and before I do
that I'd like to see if anyone on this maillist has any suggestions.
To give you a rough idea of the command coverage involved in
implementing the current version of the "protectncwcleol" option, the
"protectncwcleol" testing framework currently includes over fifty
tests.
As stated in the preamble, I have been using (variations of) this
patch in production contexts (almost) every day for weeks now without
any issues. If you decide to try out this patch, please be aware of
the following:
--- More testing can always be done. There could be gaping holes
in coverage, paths to mass "wcl_lnum" collapse that I've overlooked.
There could be catastrophic bugs that have yet to emerge. Some
"protectncwcleol" command restrictions might be too severe.
--- To be clear, the line number of a "ncwcl" is not protected
when "protectncwcleol" is on and neither is the line number delta
between any two "ncwcl"s (unless the "ncwcl"s are the same line).
--- The emphasis is on protecting a "ncwcl"'s E.O.L., not the
pre-E.O.L. contents of a "ncwcl". When "protectncwcleol" is on,
altering the pre-E.O.L. contents of a "ncwcl" is allowed except for
the insertion of another E.O.L. (e.g., by pressing [ENTER] in insert
mode) or the joining of a "ncwcl" with the preceding line (e.g., by
issuing [BACKSPACE] in insert mode at the start of a "ncwcl" with
"lnum > 1").
--- All undo and redo command variants (should...) respect the
relevant "protectncwcleol" setting. I find this _extremely_ useful.
--- Because of the implementation details of certain commands, the
protection of "ncwcleol"s from these commands produces somewhat
counterintuitive results. Provided that the current window does not
become a non-current window and no further edits are made, all such
changes are (read "should be") immediately (and safely with respect to
any "ncwcleol"s) undoable.
--- I don't use the mouse terminal features or the GUI (in fact, I
usually execute "vim" with the "-X" option). I don't foresee any
conflicts between those features and this patch, but I haven't done
anything to confirm this hunch, either.
--- I use a grand total of one Vim plugin that isn't prepackaged:
"linediff.vim" (
=====
[
https://www.vim.org/scripts/script.php?script_id=3745][linediff.vim
- Perform an interactive diff on two blocks of text : vim online]
=====
). I rely on "linediff.vim" rather heavily (it's wonderful... but
if there's anything else out there that's better, please let me know)
and am reasonably certain that it functions correctly with the
(effectively buffer-local) option setting "noprotectncwcleol".
--- The "protectncwcleol" testing framework is integrated into
Vim's "make test" framework but its source code does not conform to
any of the coding styles of any of the other tests. From the
perspective of Vim's "make test" framework, the fifty-plus tests
contained within the "protectncwcleol" testing framework function as a
single pass/fail test. One way to execute the "protectncwcleol"
testing framework directly is to start a new Vim instance with a
single window open on an empty unnamed buffer and then issue the
(equivalent of the) following command:
=====
:source src/testdir/test_option_protectncwcleol.vim | call
Test_ExecuteVimOptionProtectncwcleolPncwcleolTester()
=====
.
.
Alex