aboutsummaryrefslogtreecommitdiff
path: root/vim/vimrc
blob: bbcc670786de6b70cc3425d03155d40541e7198b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
"
" Tom Ryder (tejr)'s vimrc
" ========================
"
" <https://sanctum.geek.nz/cgit/dotfiles.git>
"
" This is a 'literate vimrc', in the Donald Knuth tradition.  It's long, and
" comments abound.
"
" This file should be saved as "vimrc" in the user runtime directory. On
" Unix-like operating systems, this is ~/.vim; on Windows, it's ~/vimfiles.
" It requires Vim 7.0 or newer with +eval, not running in &compatible mode.
" The vimrc stub at ~/.vimrc on Unix or ~/_vimrc on Windows checks that these
" conditions are met before loading this file.
"
" > And I was lifted up in heart, and thought
" > Of all my late-shown prowess in the lists,
" > How my strong lance had beaten down the knights,
" > So many and famous names; and never yet
" > Had heaven appeared so blue, nor earth so green,
" > For all my blood danced in me, and I knew
" > That I should light upon the Holy Grail.
" > --Tennyson
"

" This file contains a few Unicode characters, and the Vint Vim script linter
" wants me to declare that, so I'll do so.  The :help for :scriptencoding says
" that I should do that after 'encoding' is set, so we'll do that now.
"
" On Unix, I keep LANG defined in my environment, and it's almost always set
" to a multibyte (UTF-8) locale.  This informs Vim's choice of internal
" character encoding, but the default for the 'encoding' option is latin1,
" which is seldom what I want, and if I do want it, I'll specify it with LANG
" or possibly a manual :set command.  UTF-8 makes much more sense as a default
" encoding if Vim can't glean what I want from LANG.
"
if !exists('$LANG')
  set encoding=utf-8
endif
scriptencoding utf-8

" With encoding handled, the next thing we'll do is set an environment
" variable MYVIM for the user runtime directory, if such a variable does not
" already exist in the environment, and there's a value in 'runtimepath' from
" which to glean a useable path.  We'll use the path nominated in the MYVIM
" variable as the root of our 'backupdir', 'directory', 'undodir', and
" 'viminfofile' caches, and anywhere else we need a sensible writeable
" location for Vim-related files.
"
" I think the absence of a variable like this is a glaring omission from Vim.
" We have $VIM, $VIMRUNTIME, and $MYVIMRC, so why is there not an environment
" variable for the user's Vim runtime directory?  It is a mystery.
"
" We'll use the first path specified in 'runtimepath' as a default value.
" This is similar to what Vim does internally for the location of the spelling
" database files in the absence of a setting for 'spellfile'.
"
" Splitting the values of a comma-separated option like 'runtimepath'
" correctly, is a bit more complicated than it seems.  Its list separator is
" more accurately defined as a comma that is not preceded by a backslash, and
" which is followed by any number of spaces and/or further commas.
"
" The pattern required for the split breaks down like this:
"
"   \\     Literal backslash
"   \@<!   Negative lookbehind assertion; means that whatever occurred before
"          this pattern, i.e. a backslash, cannot precede what follows, but is
"          not included as part of the split delimiter itself
"   ,      Literal comma
"   [, ]*  Any number of commas and spaces
"
" We don't have to deal with escaped backslashes; read the source of
" copy_option_part() in vim/src/misc2.c to see why.
"
" Vim, I love you, but you are so weird.
"
let runtimepath = split(&runtimepath, '\\\@<!,[, ]*')
if !exists('$MYVIM') && len(runtimepath) > 0
  let $MYVIM = runtimepath[0]
endif

" We need to check the MYVIM environment variable's value to ensure it's not
" going to cause problems for the rest of this file.
"
" Firstly, if the path specified in the MYVIM environment variable contains
" a comma, its use in comma-separated option values will confuse Vim into
" thinking more than one directory is being specified, per normal :set
" semantics.  It's possible to work around this with some careful escaping,
" either at :set time with an :execute abstraction or with a separate
" environment variable for that particular context, but it's not really worth
" the extra complexity for such a niche situation.
"
" Secondly, some versions of Vim prior to v7.2.0 exhibit bizarre behaviour
" with escaping with the backslash character on the command line, so on these
" older versions of Vim, forbid that character.  I haven't found the exact
" patch level that this was fixed yet, nor the true reason for the bug.
"
if $MYVIM =~# ','
  echoerr 'Illegal comma in user runtime path'
  let $MYVIM = ''
elseif v:version < 702 && $MYVIM =~# '\\'
  echoerr 'Illegal backslash in user runtime path on Vim < v7.2'
  let $MYVIM = ''
endif

" If the MYVIM environment variable was set outside Vim, it may not correspond
" to the first element of the default &runtimepath.  If this is the case,
" we'll slot it in, having already checked it for troublesome characters.
"
if $MYVIM !=# '' && len(runtimepath) > 0 && $MYVIM !=# runtimepath[0]
  set runtimepath^=$MYVIM
endif

" We're going to be creating a few directories, and the code to do so in
" a compatible way is surprisingly verbose, because we need to check the
" mkdir() function is actually available, and also whether the directory
" concerned already exists, even if we specify the special 'p' value for its
" optional {path} argument.
"
" This is because the meaning of mkdir(..., 'p') is not the same as `mkdir -p`
" in shell script, or at least, it isn't in versions of Vim  before v8.0.1708.
" Even with the magic 'p' sauce, these versions throw errors if the directory
" already exists, despite what someone familiar with `mkdir -p`'s behaviour in
" shell script might expect.
"
" So, let's wrap all that nonsense in a script-local function, and then
" abstract that away too with a user command, to keep the semantics of the
" :set operations nice and clean.  We'll make all the directories we create
" have restrictive permissions, too, with a {prot} argument of 0700 for the
" final one, since every directory we want to create in this file should be
" locked down in this way.
"
function s:EnsureDir(path) abort
  let path = expand(a:path)
  return isdirectory(path)
        \ || exists('*mkdir') && mkdir(path, 'p', 0700)
endfunction
command! -complete=dir -nargs=+ EnsureDir
      \ call s:EnsureDir(<q-args>)

" Now that we have a clean means to create directories if they don't already
" exist, let's apply it for the first time, in making sure that the MYVIM
" directory exists, if it's been set.
"
if $MYVIM !=# ''
  EnsureDir $MYVIM
endif

" Create a 'vimrc' automatic command hook group, if it already exists, and
" clear away any automatic command hooks already defined within it if it does,
" so that we don't end up collecting multiple copies of the hooks configured
" in the rest of this file if it's reloaded.
"
augroup vimrc
  autocmd!
augroup END

" If this file or the vimrc stub that calls it is written to by Vim, we'd like
" to reload the stub vimrc and thereby the main vimrc, so that our changes
" apply immediately in the current editing session.  This often makes broken
" changes immediately apparent.
"
autocmd vimrc BufWritePost $MYVIMRC
      \ source $MYVIMRC
if $MYVIMRC !=# ''
  autocmd vimrc BufWritePost $MYVIM/vimrc
        \ doautocmd vimrc BufWritePost $MYVIMRC
endif

" Similarly, if this file or the vimrc stub that calls it is sourced, whether
" because of the above hook, or the <Leader>R mapping prescribed later in this
" file, add a hook that re-runs filetype detection and thereby ftplugin
" loading.  This is chiefly so that any global options set in this file don't
" trample over needed buffer-local settings.
"
" We'll abstract this away a bit behind a new user command named
" FileTypeReload, which just re-runs BufRead events for filetype detection if
" they've been loaded.
"
command! FileTypeReload
      \ if exists('did_load_filetypes')
      \|  doautocmd filetypedetect BufRead
      \|endif

" Note that the SourceCmd event wasn't added until Vim 7.0.187, so we need to
" check it exists first.
"
" If there's stuff in any of your filetype plugins that doesn't cope well with
" being reloaded, and just assumes a single BufRead event, it might be
" necessary to rewrite those parts to be idempotent, or to add load guards
" around them so that they only run once.
"
" Note that we reload the stub ~/.vimrc or ~/_vimrc file when either it or
" this main file is saved, using :doautocmd abstraction.
"
if exists('##SourceCmd')
  autocmd vimrc SourceCmd $MYVIMRC
        \ source <afile> | FileTypeReload
  if $MYVIM !=# ''
    autocmd vimrc SourceCmd $MYVIM/vimrc
          \ doautocmd vimrc SourceCmd $MYVIMRC
  endif
endif

" Keep the viminfo file in a cache subdirectory of $MYVIM, creating that
" subdirectory first if necessary.
"
" Using this non-default location for viminfo has the nice benefit of
" preventing command and search history from getting clobbered when something
" runs Vim without using this vimrc, because it writes its history to the
" default viminfo path instead.  It also means that everything Vim-related in
" the user's home directory should be encapsulated in the one ~/.vim or
" ~/vimfiles directory.
"
" The normal method of specifying the path to the viminfo file used here is an
" addendum to the 'viminfo' option, which works OK.  Vim v8.1.716 introduced
" a nicer way to set it with a 'viminfofile' option, but there's no particular
" reason to use it until it's in a few more stable versions.
"
if $MYVIM !=# ''
  EnsureDir $MYVIM/cache
  set viminfo+=n$MYVIM/cache/viminfo
endif

" We'll start our options by modernising a little in adjusting some options
" with language-specific defaults.
"
" Traditional vi was often used for development in the C programming language.
" The default values for a lot of Vim's options still reflect this common use
" pattern.  In this case, the 'comments' and 'commentstring' options reflect
" the C syntax for comments:
"
"     /*
"      * This is an ANSI C comment.
"      */
"
" Similarly, the 'define' and 'include' options default to C preprocessor
" directives:
"
"     #define FOO "bar"
"
"     #include "baz.h"
"
" Times change, however, and I don't get to work with C nearly as much as I'd
" like.  The defaults for these options no longer make sense, and so we blank
" them, compelling filetype plugins to set them as they need instead.
"
set comments= commentstring= define= include=

" The default value for the 'path' option is similar in that it has an aged
" default; this option specifies directories in which project files and
" includes can be unearthed by navigation commands like 'gf'.  Specifically,
" its default value comprises /usr/include, which is another C default.  Let's
" get rid of that, too.
"
set path-=/usr/include

" Next, we'll adjust the global indentation settings.  In general and as
" a default, I prefer spaces to tabs, and I like to use four of them, for
" a more distinct visual structure.  Should you happen to disagree with this,
" I cordially invite you to fite me irl.
"
" <https://sanctum.geek.nz/blinkenlights/spaces.webm>
"
" Filetype indent plugins will often refine these settings for individual
" buffers.  For example, 'expandtab' is not appropriate for Makefiles, nor for
" the Go programming language.  For another, two-space indents are more
" traditional for Vim script.
"
set autoindent    " Use indent of previous line on new lines
set expandtab     " Insert spaces when tab key is pressed in insert mode
set shiftwidth=4  " Indent command like < and > use four-space indents

" Apply 'softtabstop' option to make a tab key press in insert mode insert the
" same number of spaces as defined by the indent depth in 'shiftwidth'.  If
" Vim is new enough to support it (v7.3.693), apply a negative value to do
" this dynamically if 'shiftwidth' changes.
"
if v:version > 730 || v:version == 730 && has('patch693')
  set softtabstop=-1
else
  let &softtabstop = &shiftwidth
endif

" Enable automatic backups of most file buffers.  In practice, I don't need
" these backups very much if I'm using version control sensibly, but they have
" still saved my bacon a few times.
"
set backup

" Try to keep the aforementioned backup files in a dedicated cache directory,
" to stop them proliferating next to their prime locations and getting
" committed to version control repositories.  Create that directory if needed,
" too, with restrictive permissions.
"
" If Vim is new enough (v8.1.251), add two trailing slashes to the path we're
" inserting, which prompts Vim to incorporate the full escaped path in the
" backup filename, avoiding collisions.
"
" As a historical note, other similar directory path list options supported
" this trailing slashes hint for a long time before 'backupdir' caught up to
" them.  The 'directory' option for swapfiles has supported it at least as far
" back as v5.8.0 (2001), and 'undodir' appears to have supported it since its
" creation in v7.2.438.  Even though the :help for 'backupdir' didn't say so,
" people assumed it would work the same way, when in fact Vim simply ignored
" it until v8.1.251.
"
" I don't want to add the slashes to the option value in older versions of Vim
" where they don't do anything, so I check the version to see if there's any
" point adding them.
"
" It's all so awkward.  Surely options named something like 'backupfullpath',
" 'swapfilefullpath', and 'undofullpath' would have been clearer.
"
if $MYVIM !=# ''
  EnsureDir $MYVIM/cache/backup
  if has('patch-8.1.251')
    set backupdir^=$MYVIM/cache/backup//
  else
    set backupdir^=$MYVIM/cache/backup
  endif
endif

" Files in certain directories on Unix-compatible filesystems should not be
" backed up for reasons of privacy, or an intentional ephemerality, or both.
" This is particularly important if editing temporary files created by
" sudoedit(8).  We add a few paths to the default value of 'backupskip' here
" in order to prevent the creation of such undesired backup files.
"
if has('unix')

  " Vim doesn't seem to check patterns added to 'backupskip' for uniqueness,
  " so adding them repeatedly if this file is reloaded results in duplicates,
  " due to the absence of the P_NODUP flag for its definition in src/option.c.
  " This is likely a bug in Vim.  To work around this, we reset the path back
  " to its default first.
  "
  set backupskip&

  " * /dev/shm: RAM disk, default path for password-store's temporary files
  " * /usr/tmp: Hard-coded path for sudoedit(8) [1/2]
  " * /var/tmp: Hard-coded path for sudoedit(8) [2/2]
  "
  set backupskip^=/dev/shm/*,/usr/tmp/*,/var/tmp/*

endif

" Relax traditional vi's harsh standards over what regions of the buffer can
" be removed with backspace in insert mode.  While this admittedly allows bad
" habits to continue, since insert mode by definition is not really intended
" for deleting text, I feel the convenience outweighs that in this case.
"
set backspace+=eol     " Line breaks
set backspace+=indent  " Leading whitespace characters created by 'autoindent'
set backspace+=start   " Text before the start of the current insertion

" When soft-wrapping text with the 'wrap' option on, which is off by default,
" break the lines between words, rather than within them; it's much easier to
" read.
"
set linebreak

" Similarly, show that the screen line is a trailing part of a wrapped line by
" prefixing it with an ellipsis.  If we have a multi-byte encoding, use U+2026
" HORIZONTAL ELLIPSIS to save a couple of columns, but otherwise three periods
" will do just fine.
"
if has('multi_byte_encoding')
  set showbreak=else
  set showbreak=...
endif

" The visual structure of code provided by indents breaks down if a lot of the
" lines wrap.  Ideally, most if not all lines would be kept below 80
" characters, but in cases where this isn't possible, soft-wrapping longer
" lines when 'wrap' is on so that the indent is preserved in the following
" line mitigates this breakdown somewhat.
"
" With this set, it's particularly important to have 'showbreak' set to
" something, above, otherwise without line numbers it's hard to tell what's
" a logical line and what's not.
"
" This option wasn't added until v7.4.338, so we need to check it exists
" before we set it.
"
if exists('+breakindent')
  set breakindent
endif

" Rather than rejecting operations like :write or :saveas when 'readonly' is
" set, and other situations in which data might be lost or I'm acting against
" an option, Vim should give me a prompt to allow me to confirm that I know
" what I'm doing.
"
set confirm

" After staunchly opposing it for years, I have converted to two-spacing.  You
" can blame Steve Losh:
"
" <http://stevelosh.com/blog/2012/10/why-i-two-space/>
"
" Consequently, we specify that sentence objects for the purposes of the 's'
" text object, the '(' and ')' sentence motions, and formatting with the 'gq'
" command must be separated by *two* spaces.  One space does not suffice.
"
" My defection to the two-spacers is the reason I now also leave 'joinspaces'
" set, per its default, so that two spaces are inserted when consecutive
" sentences separated by a line break are joined onto one line by the 'J'
" command.
"
set cpoptions+=J

" For word completion in insert mode with CTRL-X CTRL-K, or if 'complete'
" includes the 'k' flag, this specifies the path to the system dictionary to
" find words.  This makes the dictionary completion work consistently, even if
" 'spell' isn't set at that moment.
"
" At some point, I may end up having to set this option along with 'spellfile'
" a bit more intelligently to ensure that spell checking and dictionary
" function consistently with reference to the same resources.
"
" It's not an error if this file doesn't exist; indeed, on some systems I use,
" it doesn't.
"
set dictionary^=/usr/share/dict/words
if $MYVIM !=# ''
  set dictionary^=$MYVIM/ref/dictionary.txt
endif

" Keep swap files for file buffers in a dedicated directory, rather than the
" default of writing them to the same directory as the buffer file.  Add two
" trailing slashes to the path to prompt Vim to use the full escaped path in
" its name, in order to avoid filename collisions.  Create that path if
" needed, too.
"
if $MYVIM !=# ''
  EnsureDir $MYVIM/cache/swap
  set directory^=$MYVIM/cache/swap//
endif

" If Vim receives an Escape key code in insert mode, it shouldn't wait to see
" if it's going to be followed by another key code, despite this being how the
" function keys and Meta/Alt modifier are implemented for many terminal types.
" Otherwise, if I press Escape, there's an annoying delay before 'showmode'
" stops showing "--INSERT--".
"
" This breaks the function keys and the Meta/Alt modifier in insert mode in
" most or maybe all of the terminals I use, but I don't want those keys in
" insert mode anyway.  It all works fine in the GUI, of course.
"
" There's no such option as 'esckeys' in Neovim, which I gather has completely
" overhauled its method of keyboard event handling, so we need to check
" whether the option exists before we try to set it.
"
if exists('+esckeys')
  set noesckeys
endif

" By default, I prefer that figuring out where a region of text to fold away
" should be done by the indent level of its lines, since I tend to be careful
" about my indentation even in languages where it has no structure
" significance.
"
set foldmethod=indent

" That said, I don't want any folding to actually take place unless
" I specifically ask for it.
"
" I think of a Vim window with a file buffer loaded as a two-dimensional
" planar view of the file, so that moving down one screen line means moving
" down one buffer line, at least when 'wrap' is unset.  Folds break that
" mental model, and so I usually enable them explicitly only when I'm
" struggling to grasp some in-depth code with very long functions or loops.
"
" Therefore, we set the depth level at which folds should automatically start
" as closed to a rather high number, per the documentation's recommendations.
"
set foldlevelstart=99

" Automatic text wrapping options using flags in the 'formatoptions' option
" begin here.  I allow filetypes to set 't' and 'c' to configure whether text
" or comments should be wrapped, and so I don't mess with either of those
" flags here.

" If a line is already longer than 'textwidth' would otherwise limit when
" editing of that line begins in insert mode, don't suddenly automatically
" wrap it; I'll break it apart myself with a command like 'gq'.
"
set formatoptions+=l

" Don't wrap a line in such a way that a single-letter word like "I" or "a" is
" at the end of it.  Typographically, as far as I can tell, this seems to be
" a stylistic preference rather than a rule like avoiding "widow" and "orphan"
" lines in typesetting.  I think it generally looks better to have the short
" word start the line.
"
set formatoptions+=1

" If the filetype plugins have correctly described what the comment syntax for
" the buffer's language looks like, it makes sense to use that to figure out
" how to join lines within comments without redundant comment leaders cropping
" up.  For example, with this set, in Vim, joining lines in this very comment
" with 'J' would remove the leading '"' characters that denote a comment.
"
" This option flag wasn't added until v7.3.541.  Because we can't test for the
" availability of option flags directly, we resort to a version number check
" before attempting to add the flag.
"
if v:version > 730 || v:version == 730 && has('patch541')
  set formatoptions+=j
endif

" Separating sentences with two spaces has an advantage in distinguishing
" between two different types of periods: periods that abbreviate longer
" words, as in "Mr. Moolenaar", and periods that terminate sentences, like
" this one.
"
" If we're using two-period spacing for sentences, Vim can interpret the
" different spacing to distinguish between the two types, and can avoid
" breaking a line just after an abbreviating period.  That means the two words
" in "Mr. Moolenaar" should never be split apart, preventing confusion on the
" reader's part, and also preserving the semantics of the period for
" subsequent reformats.
"
" This is what the 'p' flag does.  I wrote the patch that added it, after
" becoming envious of an analogous feature during an ill-fated foray into GNU
" Emacs usage.
"
" <https://github.com/vim/vim/commit/c3c3158>
"
if has('patch-8.1.728')
  set formatoptions+=p
endif

" In an effort to avoid loading unnecessary files, we add a flag to the
" 'guioptions' option to prevent the menu.vim runtime file from being loaded.
" It doesn't do any harm, but I never use it, and it's easy to turn it off.
"
" The documentation for this flag in `:help 'go-M'` includes a note saying the
" flag should be set here, rather that in the GUI-specific gvimrc file, as one
" might otherwise think.
"
if has('gui_running')
  set guioptions+=M
endif

" By default, Vim doesn't allow a file buffer to have unsaved changes if it's
" not displayed in a window.  Setting this option removes that restriction so
" that buffers can be modified and not displayed.
"
" Despite this option being in almost every vimrc I read, I didn't personally
" need it for years into my Vim career, because I instinctively only closed
" windows onto buffers after the buffers within them were saved anyway.
"
" However, the option really is required for batch operations performed with
" commands like :argdo or :bufdo.  After I started using those a bit more
" often, I realised I finally had a reason to turn this on, and so on it shall
" stay.
"
set hidden

" I don't think I'm ever likely to be in a situation where remembering several
" thousand Vim commands and search patterns is going to severely tax memory,
" let alone hard disk space.  The maximum value for this option is documented
" as 10000, so let's just use that.
"
set history=10000

" Do highlight completed searches, but clear them away on vimrc reload.  Later
" on in this file, CTRL-L in normal mode is remapped to stack on a :nohlsearch
" as well.
"
set hlsearch
nohlsearch

" Show search matches as I type my pattern, including scrolling the screen if
" necessary.  This is somewhat jarring sometimes, particularly when the cursor
" runs so far away from home, but I think the benefits of being able to see
" instances of what I'm trying to match as I try to match it do outweigh that.
"
set incsearch

" If there's only one window, I don't need a status line to appear beneath it.
" I very often edit only one file, and just open a :help buffer or two.  This
" is the Vim default, but Neovim changed it, so we'll explicitly set it to the
" default here in case we're using Neovim.
"
set laststatus=1

" Don't waste cycles and bandwidth redrawing the screen during batch execution
" of macros.  I think this does amount to the occasional :redraw needing to be
" in a script, but it's not too bad, and last I checked it really does speed
" things up, especially for operations on really big data sets.
"
set lazyredraw

" Define meta-characters to show in place of otherwise difficult to detect
" characters, or line wrapping attributes when the 'list' option is enabled.
"
" We reset the option to its default value first, because at the time of
" writing at least, Neovim v0.3.5 doesn't check these for uniqueness,
" resulting in duplicates if this file is reloaded.
"
set listchars&vi

" These ones all correspond to otherwise invisible or indistinguishable
" characters.  We leave the default eol:$ in place to show newlines, too.
"
set listchars+=tab:>-   " Tab characters, preserve width with hyphens
set listchars+=trail:-  " Trailing spaces
set listchars+=nbsp:+   " Non-breaking spaces

" These two are actually somewhat misplaced, in that they don't represent
" characters, but the line wrap state.  They're useful, though.
"
set listchars+=extends:>   " Unwrapped text to screen right
set listchars+=precedes:<  " Unwrapped text to screen left

" Don't let your editor's options be configured by content in arbitrary files!
" Down with modelines!  Purge them from your files!
"
" I think that modelines are Vim's worst misfeature, and that 'nomodeline'
" should be the default.  It's enabled pretty bad security vulnerabilities
" over the years, and it's a lot more effective to use filetype detection,
" other automatic command hooks, or systems like .editorconfig to set
" variables specifically for a buffer or project.
"
set nomodeline

" The only octal numbers I can think of that I ever even encounter are Unix
" permissions masks, and I'd never use CTRL-A or CTRL-X to increment them.
" Numbers with leading zeroes are far more likely to be decimals.
"
set nrformats-=octal

" I like to leave the last line of the screen blank unless something is
" actually happening in it, so I have grown to like the Vim default of
" 'noruler'.
"
" System vimrc files tend to turn this back on, though, and Neovim has it on
" by default, so we will often need to put it back to normal.
"
set noruler

" Sessions are great, but they don't play together well with plugins and
" filetype plugins.  Restoring the same settings from both reloaded plugins
" and from the session causes screeds of errors.  Adjusting session behaviour
" to stop it trying to restore quite so much makes them useable.
"
set sessionoptions-=localoptions  " No buffer options or mappings
set sessionoptions-=options       " No global options or mappings

" This flag prevents the display of the Vim startup screen with version
" information, :help hints, and donation suggestion.  After I registered Vim
" and donated to Uganda per the screen's plea, I didn't feel bad about turning
" this off anymore.
"
" Even with this setting in place, I wouldn't normally see it too often, as
" I seldom start Vim with no file arguments.
"
" I haven't felt the need to mess with the other flags in this option.
" I don't have any problems with spurious Enter prompts, which seems to be
" one of the main reasons people abuse this option.
"
set shortmess+=I

" Limit the number of characters per line that syntax highlighting will
" attempt to highlight.  This is as much an effort to encourage me to break
" long lines and do hard wrapping correctly as it is for efficiency.
"
set synmaxcol=500

" Add the expected path to the thesaurus, for completion with CTRL-X CTRL-T in
" insert mode, or with 't' added to 'completeopt.  This isn't installed as
" part of the default `install-vim` target in tejr's dotfiles; it can be
" installed with `install-vim-thesaurus`.
"
" I got the thesaurus itself from the link in the :help for 'thesaurus' in
" v8.1.  It's from WordNet and MyThes-1.  I maintain a mirror on my own
" website that the Makefile recipe attempts to retrieve.  I had to remove the
" first two metadata lines from thesaurus.txt, as Vim appeared to interpet
" them as part of the body data.
"
if $MYVIM !=# ''
  set thesaurus^=$MYVIM/ref/thesaurus.txt
endif

" Vim has an internal list of terminal types that support using smoother
" terminal redrawing.  It includes most of the terminals I use, but there are
" a couple more for which the 'ttyfast' option should apply: the windows
" terminal emulator PuTTY, and the terminal multiplexer tmux, both of which
" I use heavily.
"
if &term =~# '^putty\|^tmux'
  set ttyfast
endif

" We really don't want a mouse; it just gets in the way.  Mouse events should
" be exclusively handled by the terminal emulator application, so Vim
" shouldn't try to give me terminal mouse support, even if it would work.
"
" The manual suggests that disabling this should be done by clearing 't_RV',
" but that didn't actually seem to work when I tried it.
"
" We have to check for the existence of the option first, as it doesn't exist
" in Neovim.
"
if exists('+ttymouse')
  set ttymouse=
endif

" Keep tracked undo history for files permanently, in a dedicated cache
" directory.
"
" Support for persistent undo file caches was not added until v7.2.438, so we
" need to check for the feature's presence before we enable it.
"
if has('persistent_undo')

  " This has the same structure as 'backupdir' and 'directory'; if we have
  " a user runtime directory, create a sub-subdirectory within it dedicated to
  " the undo files cache.
  "
  if $MYVIM !=# ''
    EnsureDir $MYVIM/cache/undo
    set undodir^=$MYVIM/cache/undo//
  endif

  " Turn the persistent undo features on, regardless of whether we have
  " a cache directory for them.  It's better than losing the history
  " completely.
  "
  set undofile

endif

" While using virtual block mode, allow me to navigate to any column of the
" buffer window; don't confine the boundaries of the block to actually present
" characters.
"
set virtualedit+=block

" I can't recall a time that Vim's error beeping or flashing was actually
" useful to me, so turn it off in the manner that the manual suggests in
" `:help 'visualbell'`, enabling visual rather than audio error bells, but
" blanking the terminal attribute that would be used for the screen blinking
" at the same time, effectively silencing it.
"
" I thought at first that the newer 'belloff' and/or 'errorbells' options
" would be a nicer way to keep Vim quiet, but they don't actually appear to
" work as comprehensively as this older method, last time I checked that.
"
" Interestingly, the :help says that this setting has to be reinstated in the
" gvimrc file for GUI Vim.
"
set visualbell t_vb=

" Define a list of wildignore patterns for into the 'wildignore' option.
" Files and directories with names matching any of these patterns won't be
" presented as options for tab completion on the command line.
"
" To make this list, I went right through my home directory with
" a `find`-toothed comb, counted the lowercased occurrences of every
" extension, and then manually selected the ones that I was confident would
" seldom contain plain text.  This does the trick, giving you patterns for the
" top 50 extensions:
"
"     $ find ~ -type f -name '*.*' |
"           awk -F. '{exts[tolower($NF)]++}
"           END {for(ext in exts)print exts[ext], "*." ext}' |
"           sort -k1,1nr |
"           sed 50q
"
" Turns out I have a lot of .html files.
"
" It's tempting to put the list of patterns here into a separate file, or at
" least into a more readily editable intermediate list variable, rather than
" the minor maintenance hassle it presently constitutes in this compact form.
" I'm not sure whether I'll do that just yet.
"
set wildignore=*~,#*#,*.7z,.DS_Store,.git,.hg,.svn,*.a,*.adf,*.asc,*.au,*.aup
      \,*.avi,*.bin,*.bmp,*.bz2,*.class,*.db,*.dbm,*.djvu,*.docx,*.exe
      \,*.filepart,*.flac,*.gd2,*.gif,*.gifv,*.gmo,*.gpg,*.gz,*.hdf,*.ico
      \,*.iso,*.jar,*.jpeg,*.jpg,*.m4a,*.mid,*.mp3,*.mp4,*.o,*.odp,*.ods,*.odt
      \,*.ogg,*.ogv,*.opus,*.pbm,*.pdf,*.png,*.ppt,*.psd,*.pyc,*.rar,*.rm
      \,*.s3m,*.sdbm,*.sqlite,*.swf,*.swp,*.tar,*.tga,*.ttf,*.wav,*.webm,*.xbm
      \,*.xcf,*.xls,*.xlsx,*.xpm,*.xz,*.zip

" Allow me to be lazy and type a path to complete on the Ex command line in
" all-lowercase, and transform the consequent completion to match the
" appropriate case, like the Readline setting completion-ignore-case can be
" used for GNU Bash.
"
" As far as I can tell, this option doesn't have anything to do with the
" 'wildignore' settings, and so files that would match any of those patterns
" only with case insensitivity implied will still be candidates for
" completion.  It also wasn't added until v7.3.72, so we need to check it
" exists before we try to set it.
"
if exists('+wildignorecase')
  set wildignorecase
endif

" When Ex command line completion is started with Tab, list valid completions
" and complete the command line to the longest common substring, just as Bash
" does, with just the one keypress.  The default value of 'full' puts the full
" completion onto the line immediately, which I tolerate for insert mode
" completion but don't really like on the Ex command line.
"
set wildmode=list:longest

" You might be wondering why we got through the options with 'wild...' as
" a prefix to their names without setting 'wildmenu'.  The answer is that
" I don't actually want the popup navigable completion menu.  I just want
" tab-completion to work in a shell-like fashion.  I've never used the former,
" nor have I messed with 'wildchar' or 'wildcharm'; I've just never needed to,
" and so 'wildmenu' stays as the default.
"
" I used to have 'wildmenu' set, and I didn't realise for years that this did
" nothing at all, because the 'full' flag on which its appearance hinges
" didn't appear in my 'wildmode' setting.  I hadn't read the documentation
" properly.
"

" Use all of the filetype detection, plugin, and indent support available.
" I define my own filetype.vim and scripts.vim files for filetype detection,
" in a similar but not identical form to the stock runtime files.  I also
" define my own ftplugin and indent files for some types, sometimes replacing
" and sometimes supplenting the runtime files.
"
filetype plugin indent on

" Enable syntax highlighting, but only if it's not already on, to save
" reloading the syntax files unnecessarily.
"
" <https://sanctum.geek.nz/blinkenlights/syntax-on.jpg>
"
" For several months in 2018, as an experiment, I tried using terminals with
" no colour at all, imitating a phenomenally productive BSD purist co-worker
" who abhorred colour in any form on his terminals.  He only drank black
" coffee, too.  If you're reading this: Hello, bdh!
"
" That experiment was instructive and interesting, and I found I had been
" leaning on colour information in some surprising ways.  However, some months
" later, I found I still missed my colours, and so I went back to my
" Kodachrome roots, and didn't pine for my old monochrome world.
"
" The thing I most like about syntax highlighting is detecting unterminated
" strings, which generally works in even the most threadbare language syntax
" highlighting definitions.  I kept missing such errors when I didn't have the
" colours.
"
if !exists('syntax_on')
  syntax enable
endif

" Try to use my 'sahara' fork of the 'desert256' colour scheme, and if
" successful, turn on the 'cursorline' feature, since the scheme configures it
" and 'cursorcolumn' to be a very dark grey that doesn't stand out too much
" against a black background.  Aside from the aforementioned experiment with
" monochrome terminals, I exclusively use dark backgrounds.
"
" If we fail to load the colour scheme, for whatever reason, suppress the
" error, and reset the syntax highlighting, 'background', and 'cursorline' for
" dark-background default syntax highlighting.  I used it for years; it looks
" and works just fine.
"
" There's also a very simple grayscale colorscheme I occasionally use instead
" called 'juvenile', which is included as a submodule with this dotfiles
" distribution.
"
try
  colorscheme sahara
  set cursorline
catch
  syntax reset
  set background=dark
  set nocursorline
endtry

" Space bar scrolls down a page, :next at buffer's end if plugin available
if globpath(&runtimepath, 'plugin/scroll_next.vim') !=# ''
      \ && &loadplugins
  nmap <Space> <Plug>(ScrollNext)
else
  nnoremap <Space> <PageDown>
endif

" Remap insert Ctrl-C to undo the escaped insert operation, but don't break
" the key if the plugin isn't there
if globpath(&runtimepath, 'plugin/insert_cancel.vim') !=# ''
      \ && &loadplugins
  imap <C-C> <Plug>(InsertCancel)
endif

" Map double Ctrl-K in insert mode to search digraph names
imap <C-K><C-K> <Plug>(DigraphSearch)

" Stack Ctrl-L to clear search highlight, make it work in insert mode too
nnoremap <C-L> :<C-U>nohlsearch<CR><C-L>
vnoremap <C-L> :<C-U>nohlsearch<CR>gv<C-L>
inoremap <C-L> <C-O>:<C-U>nohlsearch<CR><C-O><C-L>

" Remap normal/visual & and g& to preserve substitution flags
nnoremap & :&&<CR>
xnoremap & :&&<CR>
nnoremap g& :<C-U>%&&<CR>

" Map g: as a 'colon operator'
nmap g: <Plug>(ColonOperator)

" Cycle through argument list
nnoremap [a :previous<CR>
nnoremap ]a :next<CR>
" Cycle through buffers
nnoremap [b :bprevious<CR>
nnoremap ]b :bnext<CR>
" Cycle through quickfix list items
nnoremap [c :cprevious<CR>
nnoremap ]c :cnext<CR>
" Cycle through location list items
nnoremap [l :lprevious<CR>
nnoremap ]l :lnext<CR>

" Insert blank lines around current line
nmap [<Space> <Plug>(PutBlankLinesAbove)
nmap ]<Space> <Plug>(PutBlankLinesBelow)

" Set leader keys
let mapleader = '\'
let maplocalleader = ','

" \a toggles 'formatoptions' 'a' flag using a plugin
nnoremap <Leader>a :<C-U>ToggleFlagLocal formatoptions a<CR>

" \b toggles settings friendly to copying and pasting
nmap <Leader>b <Plug>(CopyLinebreakToggle)

" \c toggles 'cursorline'; no visual mode map as it doesn't work
nnoremap <Leader>c :<C-U>setlocal cursorline! cursorline?<CR>
" \C toggles 'cursorcolumn'; works in visual mode
nnoremap <Leader>C :<C-U>setlocal cursorcolumn! cursorcolumn?<CR>
xnoremap <Leader>C :<C-U>setlocal cursorcolumn! cursorcolumn?<CR>gv

" \d inserts the local date (POSIX date)
nnoremap <Leader>d :read !date<CR>
" \D inserts the UTC date (POSIX date)
nnoremap <Leader>D :read !date -u<CR>

" \e forces a buffer to be editable
nnoremap <Leader>e :<C-U>setlocal modifiable noreadonly<CR>

" \f shows the current 'formatoptions' at a glance
nnoremap <Leader>f :<C-U>setlocal formatoptions?<CR>

" \F reloads filetype plugins
nnoremap <Leader>F :<C-U>FileTypeReload<CR>

" \g shows the current file's fully expanded path
nnoremap <Leader>g :<C-U>echo expand('%:p')<CR>
" \G changes directory to the current file's location
nnoremap <Leader>G :<C-U>cd %:h<Bar>pwd<CR>

" \h toggles highlighting search results
nnoremap <Leader>h :<C-U>set hlsearch! hlsearch?<CR>

" \H shows command history
nnoremap <Leader>H :<C-U>history :<CR>

" \i toggles showing matches as I enter my pattern
nnoremap <Leader>i :<C-U>set incsearch! incsearch?<CR>

" \j jumps to buffers ("jetpack")
nnoremap <Leader>j :<C-U>buffers<CR>:buffer<Space>

" \k shows my marks
nnoremap <Leader>k :<C-U>marks<CR>

" \l toggles showing tab, end-of-line, and trailing white space
nnoremap <Leader>l :<C-U>setlocal list! list?<CR>
xnoremap <Leader>l :<C-U>setlocal list! list?<CR>gv

" \L toggles 'colorcolumn' showing 'textwidth'
nnoremap <Leader>L :<C-U>ToggleFlagLocal colorcolumn +1<CR>
xnoremap <Leader>L :<C-U>ToggleFlagLocal colorcolumn +1<CR>gv

" \m shows normal maps
nnoremap <Leader>m :<C-U>map<CR>
" \M shows buffer-local normal maps
nnoremap <Leader>M :<C-U>map <buffer><CR>

" \n toggles line number display
nnoremap <Leader>n :<C-U>setlocal number! number?<CR>
xnoremap <Leader>n :<C-U>setlocal number! number?<CR>gv
" \N toggles position display in bottom right
nnoremap <Leader>N :<C-U>set ruler! ruler?<CR>
xnoremap <Leader>N :<C-U>set ruler! ruler?<CR>gv

" \o opens a line below in paste mode
nmap <Leader>o <Plug>(PasteOpenBelow)
" \O opens a line above in paste mode
nmap <Leader>O <Plug>(PasteOpenAbove)

" \p toggles paste mode
nnoremap <Leader>p :<C-U>set paste! paste?<CR>

" \P creates the path to the current file
nnoremap <Leader>P :<C-U>call mkdir(expand('%:h'), 'p')<CR>

" \q formats the current paragraph
nnoremap <Leader>q gqap

" \r acts as a replacement operator
nmap <Leader>r <Plug>(ReplaceOperator)
xmap <Leader>r <Plug>(ReplaceOperator)

" \R reloads ~/.vimrc
nnoremap <Leader>R :<C-U>source $MYVIMRC<CR>

" \s toggles spell checking
nnoremap <Leader>s :<C-U>setlocal spell! spell?<CR>

" \S shows loaded scripts
nnoremap <Leader>S :<C-U>scriptnames<CR>

" \t shows current filetype
nnoremap <Leader>t :<C-U>setlocal filetype?<CR>
" \T clears filetype
nnoremap <Leader>T :<C-U>setlocal filetype=<CR>

" \u sets US English spelling (compare \z)
nnoremap <Leader>u :<C-U>setlocal spelllang=en_us<CR>

" \v shows all global variables
nnoremap <Leader>v :<C-U>let g: v:<CR>
" \V shows all local variables
nnoremap <Leader>V :<C-U>let b: t: w:<CR>

" \w toggles wrapping
nnoremap <Leader>w :<C-U>setlocal wrap! wrap?<CR>
xnoremap <Leader>w :<C-U>setlocal wrap! wrap?<CR>gv

" \x strips trailing whitespace via a custom plugin
nnoremap <Leader>x :StripTrailingWhitespace<CR>
xnoremap <Leader>x :StripTrailingWhitespace<CR>

" \X squeezes repeated blank lines via a custom plugin
nnoremap <Leader>X :SqueezeRepeatBlanks<CR>
xnoremap <Leader>X :SqueezeRepeatBlanks<CR>

" \y shows all registers
nnoremap <Leader>y :<C-U>registers<CR>

" \z sets NZ English spelling (compare \u)
nnoremap <Leader>z :<C-U>setlocal spelllang=en_nz<CR>

" \= runs the whole buffer through =, preserving position
nnoremap <Leader>= :<C-U>KeepPosition normal! 1G=G<CR>
" \+ runs the whole buffer through gq, preserving position
nnoremap <Leader>+ :<C-U>KeepPosition normal! 1GgqG<CR>

" \. runs the configured make program into the location list
nnoremap <Leader>. :<C-U>lmake!<CR>

" \< and \> adjust indent of last edit; good for pasting
nnoremap <Leader><lt> :<C-U>'[,']<lt><CR>
nnoremap <Leader>> :<C-U>'[,']><CR>

" \_ uses last changed or yanked text as an object
onoremap <Leader>_ :<C-U>normal! `[v`]<CR>

" \% uses entire buffer as an object
onoremap <Leader>% :<C-U>normal! 1GVG<CR>

" \{ and \} move to lines with non-space chars before current column
map <Leader>{ <Plug>(VerticalRegionUp)
sunmap <Leader>{
map <Leader>} <Plug>(VerticalRegionDown)
sunmap <Leader>}

" \/ types :vimgrep for me ready to enter a search pattern
nnoremap <Leader>/ :<C-U>vimgrep /\c/j **<S-Left><S-Left><Right>
" \? types :lhelpgrep for me ready to enter a search pattern
nnoremap <Leader>? :<C-U>lhelpgrep \c<S-Left>

" \* escapes regex metacharacters
nmap <Leader>* <Plug>(RegexEscape)
xmap <Leader>* <Plug>(RegexEscape)

" \\ jumps to the last edit position mark, like g;, but works as a motion
" "Now, where was I?" (tap-tap)
nnoremap <Leader>\ `"
xnoremap <Leader>\ `"

" \DEL deletes the current buffer
nnoremap <Leader><Delete> :bdelete<CR>
" \INS edits a new buffer
nnoremap <Leader><Insert> :<C-U>enew<CR>

" \TAB toggles 'autoindent'
nnoremap <Leader><Tab> :<C-U>setlocal autoindent! autoindent?<CR>

" Some useful abbreviations
inoreabbrev tr@ tom@sanctum.geek.nz
inoreabbrev tr/ <https://sanctum.geek.nz/>

" Things I almsot always type wrnog
inoreabbrev almsot almost
inoreabbrev wrnog wrong
inoreabbrev Fielding Feilding
inoreabbrev THe The
inoreabbrev THere There