Vim the Next Generation

So I figured I should modernize my Vim skills, from 1995 to 2023. A lot's changed since I last configured Vim.

Installed a modern MacVim, in my case sudo port install MacVim. It's launched with mvim, but I just change alias v=mvim in my .zshrc

In the code blocks below, ~% is my shell prompt, ## filename shows the contents of a file, cat into it or whatever. Neither of those lines belong in the file!

To start, I want to use vim9script. So my old .vimrc now starts with that mode command, then I changed all my comments from " to #. Not much else had to change. The way to detect MacVim etc is clearer now, and I can get ligatures from Fira Code!

Syntax highlighting files can just be dropped in ~/.vim/syntax/

Update 2023-04-11: added statusline highlight colors, under syntax loading

## .vimrc
vim9script
# Mark Damon Hughes vimrc file.
# Updated for Vim9, 2023-04-09
#
# To use it, copy it to ~/.vimrc
# Note: create ~/tmp, ~/.vim, see source commands below.

set nocompatible    # Use Vim defaults (much better!)
filetype plugin on
set magic
set nrformats=

set errorbells
set nomore wrapscan noignorecase noincsearch nohlsearch noshowmatch
set backspace=indent,eol,start

set nosmarttab noexpandtab shiftwidth=8 tabstop=8

set encoding=utf-8 fileencoding=utf-8
set listchars=tab:__,eol:$,nbsp:@

set backup backupdir=~/tmp dir=~/tmp
set viminfo='100,f1,<100

set popt=header:2,number:y  # 2=always

set tw=80       # I use this default, and override it in the autogroups below

# ctrl-] is used by telnet/ssh, so tags are unusable; i use ctrl-j instead.
set tags=./tags;/
map <c-j> <c-]>

# Don't use Ex mode, use Q for formatting
map Q gq

map <Tab> >>
vmap <Tab> >
map <S-Tab> <<
vmap <S-Tab> <

# Always have syntax highlighting on
syntax on

# https://github.com/mr-ubik/vim-hackerman-syntax
# changed:
# let s:colors.cyan         = { 'gui': '#cccccc', 'cterm': 45 } " mdh edit
# let s:colors.blue         = { 'gui': '#406090', 'cterm': 23 } " mdh edit
source $HOME/.vim/syntax/hackerman.vim

set laststatus=2    # 2=always
# %ESC: t=filename, m=modified, r=readonly, y=filetype, q=quickfix, ff=lineending
# =:right side, c=column, l=line, b=buffer, 1*=highlight user1..9, 0=normal
set statusline=\ %t\ %m%r%y%q\ [%{&ff}]\ %=%(c:%02c\ l:%04l\ b:%n\ %)
set termguicolors
hi statusline guibg=darkblue ctermbg=1 guifg=white ctermfg=15
hi statuslinenc guibg=blue ctermbg=9 guifg=white ctermfg=15

hi Todo term=bold guifg=red
# Use `:set guifont=*` to pick a font, then `:set guifont` to find its exact name
set guifont=FiraCode-Regular:h16
if has("gui_macvim")
    set macligatures
    set number
elseif has("gui_gtk")
    set guiligatures
    set number
endif
set guioptions=aAcdeimr
set mousemodel=popup_setpos
set numberwidth=5
set showtabline=2

augroup c
    au!
    autocmd BufRead,BufNewFile *.c set ai tw=0
augroup END

augroup html
    au!
    autocmd BufRead,BufNewFile *.html set tw=0 ai
augroup END

augroup java
    au!
    autocmd BufRead,BufNewFile *.java set tw=0 ai
augroup END

augroup objc
    au!
    autocmd BufRead,BufNewFile *.m,*.h set ai tw=0
augroup END

augroup php
    au!
    autocmd BufRead,BufNewFile *.php,*.inc set tw=0 ai et
augroup END

augroup python
    au!
    autocmd BufRead,BufNewFile *.py set ai tw=0
augroup END

augroup scheme
    au!
    autocmd BufRead,BufNewFile *.sls setf scheme
    autocmd BufRead,BufNewFile *.rkt,*.scm,*.sld,*.sls,*.ss set ai tw=0 sw=4 ts=4
augroup END

Package Managers & Snippets

Next I need a package manager. I've settled on vim-plug as complete enough to be useful, not a giant blob, and is maintained. There's at least 7 or 8 others! Complete madness out there
(I've already picked one, I don't need further advice, and will actively resent you if you give me any. I'm just pointing at the situation being awful.)
Install's easy, drop it in autoload, mkdir -p ~/.vim/plugged

The first thing I want is a snippet manager, and SnipMate's the best of those. Edit .vimrc at the end, set your "author" name, it's used by several snippets.

## .vimrc
call plug#begin()

Plug 'https://github.com/MarcWeber/vim-addon-mw-utils'
Plug 'https://github.com/tomtom/tlib_vim'
Plug 'https://github.com/garbas/vim-snipmate'
Plug 'https://github.com/honza/vim-snippets'

g:snips_author = 'Mark Damon Hughes'
g:snipMate = { 'snippet_version': 1,
        'always_choose_first': 0,
        'description_in_completion': 1,
    }

call plug#end()

Next part's super annoying. It needs a microsoft shithub account; I made a new one on a throwaway email, but I don't want rando checkouts using my real name. includeIf lets you choose between multiple config sections, so now I have:

## .gitconfig
[include]
    path = ~/.gitconfig-kami
[includeIf "gitdir:~/Code/"]
    path = ~/.gitconfig-mark

## .gitconfig-kami
[user]
    name = Kamikaze Mark
    email = foo@bar

## .gitconfig-mark
[user]
    name = Mark Damon Hughes
    email = bar@foo

~% git config user.name
Kamikaze Mark
~% cd ~/Code/CodeChez
~/Code/CodeChez% git config user.name
Mark Damon Hughes

But shithub no longer has password logins! FUCK.

~% sudo port install gh
~% gh auth login

Follow the prompts and it creates a key pair in the system keychain. I hate this, but it works (on Mac; Linux install the package however you do, it works the same; Windows you have my condolences).

Now vim, :PlugInstall, and it should read them all. I had to do it a couple times! Then :PlugStatus should show:

Finished. 0 error(s).
[====]

- vim-addon-mw-utils: OK
- vim-snipmate: OK
- vim-snippets: OK
- tlib_vim: OK

Let's create a snippet!

~% mkdir .vim/snippets

## .vim/snippets/_.snippets
snippet line
    #________________________________________

snippet header
    /* `expand('%:t')`
    * ${1:description}
    * Created `strftime("%Y-%m-%d %H:%M")`
    * Copyright © `strftime("%Y")` ${2:`g:snips_author`}. All Rights Reserved.
    */

And if I make a new file, hit i (insert), line<TAB>, it fills in the snippet! If I type c)<TAB>, it writes a copyright line with my "author" name; it's highlighted, so hit <ESC> to accept it (help says <CR> should work? But it does not). Basically like any programmer's editor from this Millennium.

Update 2023-07-24: Added header, which is my standard document header, expand is filename with extension, rest are self-explanatory. Sometimes I add a license, which SnipMate preloads as BSD3, etc.

Use :SnipMateOpenSnippetFiles to see all the defined snippet files.

File Tree

NERDTree seems useful; read the page or :help NERDTree for docs. Add another plugin in .vimrc just before call plug#end(), do a :PlugUpdate, and it's that easy. But I want to hit a key to toggle the tree, and another key to focus the file, which takes me into the exciting world of vim9 functions.

## ~/.vimrc
Plug 'https://github.com/preservim/nerdtree'

# open/close tree
def g:Nerdtog()
    :NERDTreeToggle
    wincmd p
enddef
nnoremap <F2> :call Nerdtog()<CR>
# focus current file
nnoremap <S-F2> :NERDTreeFind<CR>

Update 2023-04-11: In NERDTree, on a file, hit m for a menu, and you can quicklook, open in Finder, or reveal in Finder, and much more. Doesn't seem to be a right-click or anything functionality, so it was not immediately obvious how to make it open my image files, etc.

And I think that's got me up to a baseline modern functionality.

Scheme Parent Protocols

Here's a subtle point of using Scheme (Chez, R6RS) records with inheritance:

;; creates a record, two default fields
(define-record-type Foo
    (fields (mutable x) (mutable y))
    (protocol (λ (new) (λ ()
        (new 1 2) )))
)

;; creates a subtype, one specified field
(define-record-type Bar
    (parent Foo)
    (fields (mutable z))
    (protocol (λ (super-new) (λ (z)
        (let [ (new (super-new)) ]
            (new z) ))))
)

(define b (make-Bar 3))
(Foo-x b)
1
(Foo-y b)
2
(Bar-z b)
3

The "new" your subtype protocol (constructor, in any OOP system) gets is a wrapper function that calls the parent protocol, and returns your actual "new". If you don't have a protocol, you just do (make-Bar 1 2 3) and that works.

It's a little annoying that define-record-type doesn't copy accessors forward, leading to either that mess of Foo-x/y, Bar-z, or a long list of (define Bar-x Foo-x) etc., but if I wanted a real object system I already have one. In my use case records are faster, I just didn't understand this quirk, and the docs don't help by calling the constructors "n" and "p".

RPG Tilemap

I had a useful JavaScript utility hidden away in the source for my Stone Halls & Serpent Men game, so I extracted it into its own thing:

Especially with people doing the challenge, it may be helpful for quickly drawing a dungeon, roguelike style.

I'm pondering doing a full wilderness + dungeon adventure again sometime soon, and I'll likely use Tilemap for it, but I sure won't be doing a room a day or anything like that!

An Atari New Year!

I spent a little time this evening making some fireworks for tomorrow night!

Download, unzip, launch in your favorite Atari 800 emulator, like Atari800MacX

Pick Y from the menu, ESC to end, reboot to get back to the menu. See you in a year!

(I didn't get around to putting an emulator page on my site; I will before my next actual game)

Sing

Rather than my longer music playlists, I often just sing one song to fediverse, like this. It's a slightly manual process, but I have part of it automated:

nowplaying.applescript

#!/usr/bin/osascript
tell application "Music"
    set t to current track
    set msg to (t's artist & " " & t's name)
    msg
end tell

urlencode

#!/usr/bin/env python3

import sys
import urllib.parse

for line in sys.stdin:
    line = line.rstrip()
    print(urllib.parse.quote(line)) # unquote for the urldecode script

sing

#!/bin/zsh
q=`nowplaying.applescript|urlencode`
open -a Chromium "https://duckduckgo.com/?q=!yt+$q"
open -a Chromium "https://duckduckgo.com/?q=!genius+$q"

Then I just type sing, it pops up Chromium (my garbage media browser), I pick the best YT video and paste that, grab some lyrics and paste them, I have o/ bound as ♫ in Text replacements, and add a tag.

I suspect I could automate it a bit more, pick the first result on Genius and YT, but I trust neither to be right. Apple Music would be easier, but not everyone has it. I could grab cover art and paste that and a template into my appdot.net toot box. Anyway, it's the post-Human touch that shows I care. It's amusing that I have three scripting languages to get one thing done (urlencode/urldecode scripts date back to the '90s).

Mac Protip: Open URL in Browser

Not all browsers have an "Open in" service. I tend to use Chromium for media so it's a different crashy app than my main Safari. I've been manually copying URLs, pasting into it.

Open Automator, create a new Quick Action, pick Run Shell Script, paste in:

read -r url
open -a Chromium "$url"

Save it as "Open URL in Chromium". Quit Automator.

You can now right-click on any URL, Services menu, and send it there.

Old Man in the Woods Way of Argument Parsing

So, my premise is that only developers use command lines anymore. And after years of corporate enslavement, it's nice to run off to the woods, make a log cabin and all your tools yourself.

Therefore the best way to parse arguments in Scheme is:

(define debug #f)
(define outfile #f)
(define infiles '())

(define (main argv)
  (set! debug (if (member "--debug" argv) #t #f))  ;; boolean
  (set! outfile (if (member "--out" argv) (cadr (member "--out" argv)) #f))  ;; key-value
  (set! infiles (if (member "--" argv) (cdr (member "--" argv)) #f))  ;; all after --
  (unless outfile (error 'main "No outfile given")) ;; maybe show a whole usage & exit
)

This has some disadvantages. It's only discoverable by reading docs or even code, and you have to write the docs yourself. If you want short args, you have to duplicate lines and maybe set the arg twice.

But it's trivial to set up, you can't really get it wrong, and the amount of effort is appropriate to a developer interface.

(You might complain I'm using globals, you can just change those to let)

For the young over-engineering crowd, there are a variety of arg parsing libraries. And I'm too lazy to demonstrate each of them. But in the set of generates usage, is easy to use, and you'll be able to remember how it works in a year, they all get maybe 1, and need to be 3.

Gone from Suck to Blow

Want to move a URL or other text between your local computers, and they're not all Mac/iOS where universal pasteboard mostly works? There's smart ways, and then there's how I do it:

# note: needs Apache turned on. sudo apachectl start
mac% cd /Library/WebServer/Documents
mac% sudo ln -s $HOME/Sites
mac% cd
mac% cat bin/blow
#!/bin/zsh
pbpaste >$HOME/Sites/suckblow.txt

raspi% sudo apt-get xclip
…
raspi% cat bin/suck
#!/bin/zsh
curl -s "http://mac.local/Sites/suckblow.txt" |xclip -i -selection clipboard
xclip -o -selection clipboard

And in the reverse set, pbcopy is the Mac equivalent of xclip -i. In practice, I don't run a server on my RasPi but I rarely need to paste the other way, just sometimes scp files.

Now on the Mac, I copy some text, type "blow" in iTerm2. On the RasPi, I grab terminal and type "suck". It can take a few seconds, and then the text is in clipboard.

Without running Apache (or other web server, but I'm a caveman), you can use scp to grab the file, then cat it into xclip -i.

Happy blowing & sucking!

[Update 2022-12-03: Some update on raspi changed the default in xclip from clipboard to primary (X11). So I've added -selection clipboard to them all.]

Internet Archive Favorites

Part of my workflow with Internet Archive is to favorite things I go back to a lot. But the fav page there is nigh-unusable, it lists in order from most recent fave to oldest, including duplicates (Huh?), and even sorting by title doesn't put related things together. So I made a tool, and generated
Internet Archive Favorites which I'll update every so often.

My first attempt was simply scraping an RSS feed, but they only publish the last 50 faves! Bogus! Even if I cached them, I'd still have to check it often and reorganize things. Then I learned they have a developer interface, usable with an ia script or right from Python, which is more useful. It's slow without caching, but after first run it's very fast, mostly 1 API call.

Read the docs at the top of the script, look at the example config file (almost a Markdown outline, but I do some clever/stupid things in it). As usual license is BSD, an ye harm none, do what thou wilt shall be the whole of the law.

Now all I have to do is write a cfg file:

The stuff I've found that I like on [Internet Archive](https://archive.org), loosely sorted.

## Retrocomputing

+ Basic_Computer_Games_Microcomputer_Edition_1978_Creative_Computing
+ More_BASIC_Computer_Games
+ Basic_Computer_Adventures_1986_MS_Press
+ Best_of_Creative_Computing_Vol_1_1978_Creative_Computing_Press
+ creativecomputing
…

% archive-fav-extract.py -q mdhughes

And it makes a nice html file, tells me about any errors, and I paste the output file into a wordpress page.

A Computer is Like a Violin

LATITUDE OF EXPRESSION AND SPECIFICITY OF IDEAS

Finally we come to the question of what to do when we want to write a program but our idea of what is to be done, or how to do it, is incompletely specified. The non sequitur that put everyone off about this problem is very simple:

Major Premise: If I write a program it will do something particular, for every program does something definite.
Minor Premise: My idea is vague. I don't have any particular result in mind.
Conclusion: Ergo, the program won't do what I want.

So, everyone thinks, programs aren't expressive of vague ideas.

There are really two fallacies. First, it isn't enough to say that one doesn't have a particular result in mind. Instead, one has an (ill-defined) range of acceptable performances, and would be delighted if the machine's performance lies in the range. The wider the range, then, the wider is one's latitude in specifying the program. This isn't necessarily nullified, even when one writes down particular words or instructions, for one is still free to regard that program as an instance. In this sense, one could consider a particular written-down story as an instance of the concept that still may remain indefinite in the author's mind.
This may sound like an evasion, and in part it is. The second fallacy turns around the assertion that I have to write down a particular process. In each domain of uncertainty 1 am at liberty to specify (instead of particular procedures) procedure-generators, selection rules, courts of advice concerning choices, etc. So the behavior can have wide ranges-it need never twice follow the same lines, it can be made to cover roughly the same latitude of tolerance that lies in the author's mind.

At this point there might be a final objection: does it lie exactly over this range? Remember, I'm not saying that programming is an easy way to express poorly defined ideas! To take advantage of the unsurpassed flexibility of this medium requires tremendous skill-technical, intellectual, and esthetic. To constrain the behavior of a program precisely to a range may be very hard, just as a writer will need some skill to express just a certain degree of ambiguity. A computer is like a violin. You can imagine a novice trying first a phonograph and then a violin. The latter, he says, sounds terrible. That is the argument we have heard from our humanists and most of our computer scientists. Computer programs are good, they say, for particular purposes, but they aren't flexible. Neither is a violin, or a typewriter, until you learn how to use it.