aboutsummaryrefslogtreecommitdiff
path: root/vim/vimrc
blob: 67d70a7e76002c82223313715a5a8123333e1c50 (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
"
" 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
"

" We'll start by removing unwanted addenda to 'runtimepath' from Debian
" GNU/Linux's debian.vim, so that any Vim script installed at the system level
" is ignored, since we don't want it interfering with our personal setup.
" This doesn't raise an error if the paths aren't present, so we don't need to
" check if they're already there.
"
set runtimepath-=/var/lib/vim/addons
set runtimepath-=/var/lib/vim/addons/after

" Splitting the values of a comma-separated option like 'runtimepath'
" correctly, which we'll need to do a few times throughout this file, is a bit
" more complicated than it seems.  The 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
"
" Vim, I love you, but you are weird.
"
" We don't have to deal with escaped backslashes; read the source of
" copy_option_part() in vim/src/misc2.c to see why.
"
let s:option_split_pattern
      \ = '\\'
      \ . '\@<!'
      \ . ','
      \ . '[, ]*'

" With 'runtimepath' cleaned up, 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'.
"
if !exists('$MYVIM') && &runtimepath !=# ''
  let $MYVIM = split(&runtimepath, s:option_split_pattern)[0]
endif

" 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.
"
" So, if there's a comma, we just raise an error and end the script.
"
if stridx($MYVIM, ',') != -1
  echoerr '$MYVIM contains a comma, refusing to proceed'
  finish
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 older versions of Vim.  Even with
" the magic 'p' sauce, these older 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.  We'll make all
" the directories we create have restrictive permissions, too, with a {prot}
" argument of 0700.
"
function! s:Mkdir(path) abort
  if exists('*mkdir') && !isdirectory(a:path)
    call mkdir(a:path, 'p', 0700)
  endif
endfunction

" Keep the viminfo file in a cache subdirectory of $MYVIM, creating that
" subdirectory if necessary.
"
" Using this location for viminfo has the nice benefit of preventing 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.
"
call s:Mkdir($MYVIM.'/cache')
set viminfo+=n$MYVIM/cache/viminfo

" 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, 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,$MYVIM/vimrc
      \ source $MYVIMRC

" 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.
"
" If there's stuff in any of your ftplugins 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 the SourceCmd event wasn't added until Vim 7.0.187, so we need to
" check it exists first.
"
if exists('##SourceCmd')

  " We can't wrap this in a script-local function like I normally would,
  " because without a load guard around it, Vim will get upset that we're
  " trying to redefine that function the next time this script loads.
  "
  " So, we just inline the whole thing into the hook.  It's only four
  " commands, anyway, and I'd rather most if not all of the vimrc was reloaded
  " when we source it.
  "
  autocmd vimrc SourceCmd $MYVIMRC,$MYVIM/vimrc
        \ source <afile>
        \|if exists('#filetypedetect#BufRead')
        \|  doautocmd filetypedetect BufRead
        \|endif
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

" 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

" 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 has('patch-8.1.251')
  set backupdir^=$MYVIM/cache/backup//
else
  set backupdir^=$MYVIM/cache/backup
endif
call s:Mkdir(split(&backupdir, s:option_split_pattern)[0])

" 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.
  " This might be 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

" 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.
"
" 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

" 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.
"
set directory^=$MYVIM/cache/swap//
call s:Mkdir(split(&directory, s:option_split_pattern)[0])

" 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

" 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 redunant 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 outweight
" that.
"
set incsearch

" If there's only one window, I don't need a statusline 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 linewise operations on really big data sets.
"
set lazyredraw

" Break lines at word boundaries
set linebreak

" Define extra 'list' display characters
set listchars&vi  " Neovim adds duplicates otherwise
set listchars+=tab:>-      " Tab characters, preserve width
set listchars+=trail:-     " Trailing spaces
set listchars+=extends:>   " Unwrapped text to screen right
set listchars+=precedes:<  " Unwrapped text to screen left
set listchars+=nbsp:+      " Non-breaking spaces

" I like the brief jump to the matching brackets provided by the 'showmatch'
" option; the only change I want is for it to be a little quicker, so we'll
" adjust that to 0.3 seconds.
"
set showmatch matchtime=3

" Don't let your editor's options be configured by content in arbitrary files!
" Down with modelines!  Purge them from your files!  Écrasez l'infâme!
"
" 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

" Don't show startup splash screen (I donated)
set shortmess+=I

" Prefix wrapped rows with three dots
set showbreak=...

" New window positioning
set splitbelow  " Below the current window, not above
set splitright  " Right of the current window, not left

" Don't try to syntax highlight run-on lines
set synmaxcol=500

" Add thesaurus; install with `make install-vim-thesaurus`
set thesaurus^=$MYVIM/ref/thesaurus.txt

" PuTTY is a fast terminal, but Vim doesn't know that yet
if &term =~# '^putty'
  set ttyfast
endif

" Don't use terminal mouse support, even if it would work; the manual says to
" set 't_RV' to do this, but that doesn't seem to work
if exists('+ttymouse')  " No such option in Neovim
  set ttymouse=
endif

" Keep persistent undo files in dedicated directory, named with full path
if has('persistent_undo')  " v7.2.438
  set undofile
  set undodir^=$MYVIM/cache/undo//
  call s:Mkdir(split(&undodir, s:option_split_pattern)[0])
endif

" Let me move beyond buffer text in visual block mode
set virtualedit+=block

" Never beep at me
set visualbell t_vb=

" Tab completion settings
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
if exists('+wildignorecase')  " v7.3.072
  set wildignorecase  " Case insensitive tab completion
endif
set wildmode=list:longest  " Tab press completes and lists

" Load filetype settings, plugins, and maps
filetype plugin indent on

" Use syntax highlighting
if !exists('syntax_on')
  syntax enable
endif

" Try to use sahara color scheme with 'cursorline' set; otherwise, use the
" default color scheme with a dark background
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>doautocmd filetypedetect BufRead<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