Showing posts with label vim. Show all posts
Showing posts with label vim. Show all posts

Monday, July 12, 2021

Vim (for non-programmers) Volume Four Hundred and a Score: stone by stone, brick by brick

Swopped out work laptops earlier this year, and for reasons involving ignorance ("back up your dotfiles to GitHub" means as much to me as it does to Emily Fusselman's rabbit) and laziness I have not had the advantage of my fully functioning battle station set of tweaks for some months now. Over the past few days, though, I've had a felt need for some new functionality, and, having achieved this, I took on an old challenge I had never quite cracked.

I shall treat of these in turn.

I've been doing a lot more in Markdown lately, and while taking notes during a training on returning to office work in a COVID-19 environment1 I realized that, for note-taking purposes and later reference, Markdown's reference links make lots more sense for a readable document than in-line HTML-style links.

Per the documentation, inline links look like:
This is an [example in-line link](https://examplelink.org) in a sentence people are trying to read

Reference links look like:
This is the [first example reference link][1] and this is the [second example reference link][2].
[1]: https://examplelink.org
[2]: https://secondexamplelink.org

It's more readable! I like that. I like to read. :) So I wanted to figure out how to make it faster and easier. You may remember that my old link-adding techniques looked like:
Add Link to Word the Cursor Is On
" make the word under the cursor a hyperlink to URL from system clipboard
" 18jan2016
" mapping <leader>a to:
" select the current word with viw
" wrap the current word in an <a href> tag
" by moving to the end of the word and adding <a> to close the tag
" then moving to the beginning of the WORD and adding <a href="">
" moving back to the beginning of the WORD to move to the quotes
" and populate the quotes with the contents of the + register
" mostly inspired by Steve Losh
" edited 27dec2016
" :nnoremap <leader>a viw<esc>a</a><esc>Bi<a href=""><esc>Bci"<esc>a<c-r>+<esc>
:nnoremap <leader>a viw<esc>a</a>Bi<a href="<c-r>+<esc>a"><esc>


Add Link to Last Thing That Was Visually Selected
" make the last visually selected text a hyperlink to URL from system clipboard
" 18jan2016
" mapping <leader>v to:
" select the last visual selection with `< and `>
" wrap that selection in an <a href> tag
" by moving to the end of the visual selection and adding </a> to close the tag
" then moving to the beginning of the visual selection and adding <a href="">
" moving back to the beginning of the visual selection to move to the quotes
" and populate the quotes with the contents of the + register
" mostly inspired by Steve Losh
:nnoremap <leader>v `>a<a><esc>`<i<a href=""><esc>Bci"<esc>a<c-r>+<esc>

The needs of the Markdown style of reference link are slightly different, however, so the approach I landed on was the following:
Add Reference Link to Last Thing That Was Visually Selected
" add a reference link in Markdown
:nnoremap <leader>rl <esc>gvI[<esc>A][]<esc>

This will need at some point some editing to append to the bottom of the file and add the actual links, etc., but I'm not quite there yet.

I've also long struggled with automating / streamlining making things bullets in Markdown, as well. The most common use case for making things bullets that weren't bullets as I was originally typing things in involves marking up previously typed things that I've visually selected. Luckily, the modal editor paradigm allows this to work with a subtle change in the mapping: it turns out a person can remap visual mode key combinations just as she can remap normal mode key combinations. Fiddling around with this this week, I landed on:
" add bullet format to visual selection
:vnoremap <leader>- <esc>gv:s/^/ - /<enter>
:vnoremap <leader>* <esc>gv:s/^/ * /<enter><esc>V>

Then, when I started dinking around with consolidating all this, I realized I had this already floating around:
:nnoremap <leader>li I- <esc>
:nnoremap <leader>ld 0V><esc>f-r*

This latter solves the problem that, often, what I need to do automatically is indent an existing bullet...but, obviously I didn't use it enough to remember that I'd created it.

But! As a bonus, I'd always had the problem—particularly when working on my .vimrc file—of wanting, even needing, to comment out a bunch of lines that I'd visually selected. For some reason, this never clicked until recently, when I realized this operation is more or less trivial:
:vnoremap <leader>c <esc>gv:s/^/" /<enter>

I have in the works a way to abstract that key combination to insert comments by filetype (HTML, Python, VimScript) but it's not super pressing, and the mechanics are basically transparent, so I can leave the details as an exercise for the (likely purely hypothetical, at this point) reader.

—Fat, trying to remember to sharpen his saw

1 Shut up, I'm a union steward, taking notes on things bosses say is kind of my thing.

Saturday, November 14, 2020

Vim (for non-programmers) Section DCLXVI: Automating Making a To-List and Crossing Off Make a To-Do List on Your To-Do List Automatically

Vim (for non-programmers) Section DCLXVI: Automating Making a To-List and Crossing Off Make a To-Do List on Your To-Do List Automatically OR So...I wrote a thing...the thing is a plugin, sort of

Where notebooks and calendars and computers meet is ... well, lots of different places. But one place they meet is at work. Thus it did come to pass that I felt the need to take a daily notebook calendaring exercise and turn it into something that could be replicated on my computer. Which brings us to a thing I wrote, entitled "todotxt.vim", a "plugin" for the Vim text editor, which promises little and delivers less.

As it says in the help file I wrote (lol):

When I start work each day, it makes sense for me to:
  1. Start a new page in my work notebook for that day
  2. List my meetings for the day (at the top of the page, in order)
  3. List my tasks for the day (to use the page efficiently, I populate the tasks starting at the bottom of the page, but I don't generally order them or worry about grouping or managing them)
This plugin creates a digital version of that page of my notebook, with a new file for each day.

The premise of the plugin is that a list of the things you have to do is a good thing to have. The list might as well be in plain text, so you can look at it on pretty much any device, and if it's in plain text, I might as well be able to use the fancy special features of my favorite text editing program, Vim to edit it. I find it helpful to be able to look back and see what I did on a given day, so a big piece of the "functionality" involves knowing what day it is and making a special file for that day. Since I don't always finish everything I want to do on a given day (lol), another big piece of the "functionality" involves looking at a specified previous day and pulling the un-done tasks from that day to the current day. There's also a tiny bit of attention paid to making it look quasi-nice (using Vim's syntax formatting).

Anyway, if you want to try this, there's a link to the files here:

And while the file itself is more or less definitive, and can be read in a text editor by clicking here, the help file may be more illustrative, so I have pasted it in below. Sorry about the formatting below. If you hate it, click the link, I guess. NOTE: where it says something like \tadd you should replace \ with leader in angle brackets.

*todotxt.txt* for Vim version 7.4 and greater Last change: 2020 Nov 4

Adds limited functionality and syntax highlighting for plain-text to-do files
accessed in Vim.

================================================================================
CONTENTS *todotxtcontents*

1. Introduction ................ |todotxt-introduction|
2. Installation ................ |todotxt-installation|
3. Usage ....................... |todotxt-usage|
4. Mappings .................... |todotxt-mappings|
5. Configuration ............... |todotxt-config|
6. License ..................... |todotxt-license|
7. Bugs, Version, and To-Dos ... |todotxt-bugs|
8. Contributing ................ |todotxt-contributing|
9. Changelog ................... |todotxt-changelog|
10. Credits .................... |todotxt-credits|


================================================================================
Section 1: Introduction *todotxt-introduction*

This is a very simple plugin for creating, using, updating, and (very very
lightly) tracking to-do files in plain text. Syntax highlighting and a few
mappings make the to-do list easier to look at and easier to use.

The basic idea is to start your workday with a file that has the following in
it:


By default, when the empty file is loaded at the beginning of the day, the
plugin will ask if there is a previous file to check for tasks that are not
marked as done. This will usually be the previous workday's file. If the
specified file has tasks not marked as done, those tasks will be added to the
current day's file, along the lines of the following.

+--------------------------------------------------------------------+
| # Today's Date |
| # Meetings |
| |
| --- |
| |
| # Tasks |
| |
| - [ ] McDonalds 2020-11-04 |
+--------------------------------------------------------------------+

To add a task, use \tadd to enter a date-stamped task on the line under
the cursor along the following lines.

+--------------------------------------------------------------------+
| # Today's Date |
| # Meetings |
| |
| --- |
| |
| # Tasks |
| |
| - [ ] McDonalds 2020-11-04 |
| - [ ] Eat Hot Chip 2020-11-05 |
+--------------------------------------------------------------------+

More tasks can be added to the line under the cursor.

+--------------------------------------------------------------------+
| # Today's Date |
| # Meetings |
| |
| --- |
| |
| # Tasks |
| |
| - [ ] McDonalds 2020-11-04 |
| - [ ] Eat Hot Chip 2020-11-05 |
| - [ ] Charge Phone 2020-11-05 |
| - [ ] Lie 2020-11-05 |
+--------------------------------------------------------------------+

Meetings work similarly, but instead of a datestamp that is added
automatically, meetings ask you to enter a time. Once added, it looks
something like the following. The command for this is \madd.

+--------------------------------------------------------------------+
| # Today's Date |
| # Meetings |
| - [ ] Twerk Noonish? |
| |
| --- |
| |
| # Tasks |
| |
| - [ ] McDonalds 2020-11-05 |
| - [ ] Eat Hot Chip 2020-11-05 |
| - [ ] Charge Phone 2020-11-05 |
| - [ ] Lie 2020-11-05 |
+--------------------------------------------------------------------+

Adding tasks and meetings manually can be done throughout the day.

To make a file for the day, \logit is used. This will create a file
with a datestamp in the filename for easy sorting, and to make it simple to
pull the undone tasks from earlier files.

To mark a meeting or task as done, put the cursor on the line of the meeting
or task and enter \done. This will have an effect like the following.

+--------------------------------------------------------------------+
| # Today's Date |
| # Meetings |
| - [ ] Twerk Noonish? |
| |
| --- |
| |
| # Tasks |
| |
| - [X] McDonalds 2020-11-05 |
| - [ ] Eat Hot Chip 2020-11-05 |
| - [X] Charge Phone 2020-11-05 |
| - [X] Lie 2020-11-05 |
+--------------------------------------------------------------------+

Sometimes a meeting will be cancelled or you will decide to not do a task in a
way you want to document (and not carry the task forward to future days). To
do this, enter the command \skip, which will have an effect like the
following.

+--------------------------------------------------------------------+
| # Today's Date |
| # Meetings |
| |
| |
| --- |
| |
| # Tasks |
| |
| - [X] McDonalds 2020-11-05 |
| - [ ] Eat Hot Chip 2020-11-05 |
| - [X] Charge Phone 2020-11-05 |
| - [X] Lie 2020-11-05 |
+--------------------------------------------------------------------+

This doesn't look very interesting in a help file, but the plugin uses
Markdown formatting to make list items and HTML comments -- like the skipped
meeting above -- visually distinctive. Since the meetings and tasks are added
on the line under the cursor, the line the cursor is on is also highlighted.

To-do-specific functions are mostly handled with mappings. Everything else
about the file is meant to be handled using Vim's built-in capabilities. For
more details, see the credits section.


================================================================================
Section 2: Installation *todotxt-installation*

I think this will work with Pathogen? I'm not sure: I don't use Pathogen.
What I would do is:
1. Put the todotxt.vim file somewhere in your plugins folder
2. Make a todo directory somewhere that makes sense to you
3. Add the following to your .vimrc file. >
augroup filetype_todo
autocmd!
autocmd BufNewFile */todotxt/*todo*.txt :source path/to/todotxt.vim
autocmd BufNewFile */todotxt/*todo*.txt :Todotxtstartup
augroup END
<

Or you could just dump the whole todotxt.vim file into your .vimrc...


================================================================================
Section 3: Usage *todotxt-usage*

This is an opinionated little plugin. It is based on a small set of (my)
practices and makes certain assumptions. If those practices and assumptions
don't work for you, this plugin is almost certainly not going to work for you
either (whether or not I've actually made it functional).

Practices: When I start work each day, it makes sense for me to:
1. Start a new page in my work notebook for that day
2. List my meetings for the day (at the top of the page, in order)
3. List my tasks for the day (to use the page efficiently, I populate the
tasks starting at the bottom of the page, but I don't generally order them or
worry about grouping or managing them)

This plugin creates a digital version of that page of my notebook, with a new
file for each day.

Assumptions:
1. You want to do something (very) like the practices above
2. You want to use Vim to edit to-do files
3. You're okay managing your own files

The way todotxt.txt expects you to use it is:
1. Each day, use the command line to tell Vim to create a file with "todo"
(without the quotes) in its filename and the extension .txt in a folder with
"todo" (without the quotes) in its path
2. Tell todotxt.txt to import undone tasks from a specified previous day (or,
in a very special situation, a lot of days)
3. Add your meetings and tasks over the course of the day
4. Mark meetings and tasks done over the course of the day
5. Save a copy of the list for reference later
6. Repeat 1-5 the next day
7. Every so often (once a month would make a lot of sense) move all the old
daily files into a sub-folder

To make 2, 3, 4, and 5 easier, there are mappings. To make the file easier to
look at, it uses syntax highlighting. That's it. That's all it does. It
should save you some typing of items from day-to-day if on Day 2, you have
some tasks you didn't complete on Day 1, and it should look better than a raw
text file. Otherwise, you're on your own.


================================================================================
Section 4: Mappings *todotxt-mappings*

This is kind of the meat of the plugin, in a weird way. Of course, if you're
a Vim user looking for plugins, you probably have a ton of your own mappings,
which is a problem, because I cannot for the life of me figure out all the
things I'm supposed to do to make sure this plugin's mappings aren't going to
clobber yours. Sorry. To make things easier, I've listed them all below. To
make things less dangerous, I've made them all pretty long.

Type: To: ~
\tadd Add a new task
\madd Add a new meeting
\done Mark a task or meeting accomplished
\skip Mark a meeting or task skipped
\logit Write a file with the current date in the filename
\grabit Pull in undone things from a specified file
\graball Pull in undone things from all files in the folder

graball is meant for big clean-up jobs and should rarely be used. All others
are meant for use daily (logit) or more often (tadd, madd, done, skip). grabit
fires automatically when you open the file, but can be used to add days one at
a time, if needed.


================================================================================
Section 5: Configuration *todotxt-config*

There aren't configuration options at this time. If you want a different path
or a different filename or a different type of syntax highlighting, you can
edit the todotxt.vim file to suit your preferences (but don't blame me if
something goes wrong).


================================================================================
Section 6: License *todotxt-license*

This file is placed in the public domain. But you shouldn't use it if you
aren't literally me.


================================================================================
Section 7: Bugs *todotxt-bugs*

God, there are probably a million bugs. If you find one, shoot me an email at
cfcollision@gmail.com, please! If you fix it, that's even better.

I am having enormous trouble with the apparently important "avoid loading the
file multiple times per buffer" issue, so frequently you may need to manually
set filetype to markdown, and I have no idea why. It loads other shit, but
not the filetype setting and I cannot figure out why that might be. For now,
I just have the entire "1. Filetype Protections from usr_41.txt" section
commented out because I can't make it work properly.

Tasks and meetings are added under the cursor. This is a little clunky, but
since Vim makes it easy to put the cursor where we want it, we can accept this mild inconvenience.

Not a bug, but a behavior: "logit" will (a) overwrite any previous version of
the file you made that day and (b) silently enter the current day if you start
up todotxt.vim on one day but use "logitlogit" on a different day.

"skip" marks things done, so they do not carry over into following days. This
is deliberate, but may not be what you want. My usage is to use "skipskip" only
for meetings that were cancelled, not for tasks I have chosen to leave undone;
those, I would usually allow to carry forward to the next day (and the next
day's file) and simply delete if need be, or use "skipskip" and add a comment
about why I had left the task undone.

This is tested on my home laptop, running Ubuntu 16.4, and my work MacBook.
It has NOT been tested on any Windows machine and probably won't work on one.

This works on Vim version 7.4 on Ubuntu 16.4 and should work on newer
versions. I think it works on Vim version 8.0 on my work MacBook.

Todo:
1. Sorting tasks by datestamp would be pretty cool
2. Auto-sorting meetings by time would also be pretty cool
3. Getting gVim to work would also be sweet (setting pwd on BufNew or
something?)
4. Add menus?
5. Sweet README.md file?
6. Smart enough to check more than one file?
7. Sweep done tasks (but not meetings) to the bottom of the file and hide them
(comment them out or fold them?)
8. Cleverly fold the todotxt.vim file using blank lines?
9. Shortening everything so I could have a cal in there
10. Making long lines work better and not just let them scroll off
endlessly?

================================================================================
Section 8: Contributing *todotxt-contributing*

It is unlikely that this will be useful enough to any other human to merit any
contributions. If you tweak it to your liking, feel free to shoot me an
email about your tweak. If you are okay with me adopting your tweak, please
say so.


================================================================================
Section 9: Changelog *todotxt-changelog*

This document is for plugin version 0.2.1, first even potentially shareable
version, with broken stuff commented out.


================================================================================
Section 10: Credits *todotxt-credits*

This is inspired by three things:
1. Bullet journal practice
2. Hipster PDA practice (documentation here:
http://www.43folders.com/2004/09/03/introducing-the-hipster-pda_ )
3. Steve Losh's program t: "a command-line todo list manager for people who
want to FINISH tasks, not organize them" ... "it does the simplest thing that
could possibly work" ... "hacked together in a couple nights to fit my needs"
with more documentation available here:
https://stevelosh.com/projects/t/

The idea is to mirror in plain text files my particular implementation of
ideas adopted from bullet journaling and the hipster PDA. In particular:
1. Treating a day as a container for the stuff I have to do that day, not
distinguishing between tasks and meetings, because they are both things I have
to do on a given day
2. Devoting a page in a notebook to that day, so everything is available at a
glance
3. Marking things done when done
4. Carrying over things to the next day if they are not done

Losh's program did a lot of things I found interesting, especially with very
short commands to do useful things, but had certain features that didn't work
well for my needs, including a dependency on Python and a need to make
command-line aliases for things. His repeated comment "need to do something?
open it in a text editor" inspired me to harness Vim's capacities and make Vim
the home for the whole thing. Also I have one need his program does not allow
for, which is an occasional but very important need to document what I did on
a given day or set of days: this implies a daily log of some kind. Finally,
hell, sometimes I do want to organize my tasks, not finish them. (And a lot
of his functionality is built around preventing multiple users or computers
clobbering one file, which is not a need I have.)

So, I hacked something together over the course of a couple days, test-drove
it and amended it.

His way -- t -- is almost certainly better, and I encourage you to try it out
rather than muck around with this.

vim:tw=78:ts=8:ft=help:norl:

Previous entries in Vim (for non-programmers):

  1. Vim (for non-programmers) Chapter O (NOT 0), recipes which are quick and dirty, example six: Let's Make a Time-Stamped Log of Stuff We Read Online and Want to Have a List Of; Hey, Guess What? I Got a Lot of This from Chris Toomey (heart-eyes emoji)
  2. Vim (for non-programmers) Part Three: Refactoring my _vimrc File; Chapter Five: Correct Easy Link Addition (Correcting My Misreading of Steve Losh)
  3. Vim (for non-programmers) Part Three: Refactoring my .vimrc File, Chapter Five; Correct Easy Markup of Markdown Headlines (Building on Chris Toomey's "Your First Vim Plugin")
  4. Vim (for non-programmers) Chapter O (NOT 0), recipes which are quick and dirty, example two: Dumping Out the Recommendations from IDEOTVPod into One File

Tuesday, January 22, 2019

Vim (for non-programmers) Chapter O (NOT 0), recipes which are quick and dirty, example six: Let's Make a Time-Stamped Log of Stuff We Read Online and Want to Have a List Of; Hey, Guess What? I Got a Lot of This from Chris Toomey (heart-eyes emoji)

(Or, more generally: having Vim do some stuff automagically and plus yet still even lots more fun with functions both built-in and homebrewed, goals probably often shared by many.)

So's anyways, I was thinking I might could benefit from a list of stuff I read online, and since sometimes I read online on a computer, I thought I could try to make the computer help me out with making that list. It only took about a billion hours to work out how. And, of course, what's a list of stuff you did, unless it's also a list of when you did it? Also that's a thing I would like the computer help me out with. And what good is a list if it's not, you know, nicely formatted and fun to read? Not much fucking good at all if you ask me, bud.

Full Disclosure: a big part of this project was that I had a potent itch to just ... learn some stuff, and do something new. The problem is that I am pretty terrible at learning in a vacuum*, so I needed to figure out a need that learning something could address. This was pushing up at the end of the year, so I was, amangst other things, going thru my lists, so I figured, "Hey, let's make MORE lists, that sounds fun". This collided with my occasional dicking around on over to YouTube, which led me to another Chris Toomey video that was as good as the one I saw that drove me to madness helped me make some easier ways to do stuff in Markup, which led me to a now-ancient blog post, which contained a magnificent little skeleton for making, testing, and building up / out a Vim function. So that's where I started.

*Can't breathe, for one.

Chris Toomey's function skeleton looks like so: you put it in a file someplace, and save the file:
function! DoSomething()
    echo 'Hi, Fat.'
endfunction

nnoremap <leader>tf :source %<cr>:call DoSomething()

So. The first line says "define a function named DoSomething()", which is maybe a little embarrassingly basic, but, hey, it's my little prototyping section inside my own private Vim configuration file, so YOU CAN'T JUDGE ME. The second line defines the, uh, functionality of the, uh, function: it prints 'Hi, Fat.' (without the quotes) to the screen, and that's it. What's nice about this is that it's fast and low-impact. Third line, and this will shock you, says "this is the end of the function." The nnoremap we've seen before, it just means "in normal mode**, make the leader key, followed by tf, do two things: first, reload this file (that's the :source % bit), which will update the function we're playing with, and then, second, make the function named DoSomething() happen". The mnemonic is "test function".

**We all of course remember that "normal mode" means "not typing into the file, but navigating around the file". I.e., we can type stuff, and it won't (necessarily) add / subtract any text from the file, it'll just move around and whatnot.

So, obvs, the trick is to start adding lines. Bonus points for adding lines that do something you might want your computer to do.

So...what do we want it to do? The goal: automatic timestamps in a log of links I read and found interesting, which log looks halfway decent, preferably in Markdown formatting. (That's a plain text format that's pretty friendly on the eye and lends itself well to conversion to other formats, like HTML or whatever. We've seen this before.)

Vim, oddly, has a built-in function called strftime(), which asks the computer what time it is, and spits it out in the format of a "string" (whatever the he*l that is). If I understand it correctly, it is pretty much just a standard Unix function. Anyway, it has one million flags and a syntax I don't really understand, but the version I use is: strftime('%c'), which, if, say, called by :put =strftime('%c'), dumps something along the following lines into your file: "Sat 19 Jan 2019 12:52:29 AM PST" (without quotes). Now, that has a lot of extraneous bullshit in it, but the flags are difficult to understand (and platform-dependent), so fuck it, I'll stick with '%c'.

Now that we know Vim can tell us what the current time / date are, and even put it into a file, we can ... ask it to do so.

function! IncrementLog()
    let a:timestamp = strftime('%c')
    call(append(line('$'), [' ', join(['- ', 'a:timestamp']), join(['    * ', @+])]))
endfunction

(Hello append() my old friend; I've come to exploit you again...)

So, yeah. First line, say we have a function, with a name. Second line, define a variable, named timestamp and make the content of the variable the "get the time / date" bit we saw before. (The a: bit we can ignore for now: it's a (Ed. Note: the) way to limit the scope of the variable, which seems like a good idea with a variable name as generic and likely to occur widesly as timestamp.) Then: append(line('$') starts us off by saying "add this stuff AFTER the LAST line". This is good for a log, because it enforces a nice chronological order, like you want, in a log.

The next bit is a little bit of a lot. Stuff in [' ', ' '] brackets with commas separating stuff enclosed in quotes is, to Vim, a List. The join() function takes a List as an argument, and spits out a "string". You'll note that there's two of these join() functions, and that each is preceded by some stuff, and each is enclosed in those neat [' ', ' '] brackets with commas separating stuff enclosed in quotes. What append() is calling are two Lists. What this does is put each List on its own line. That's handy af.

The first List that append() takes as an argument is a blank line, then, as the second item, a formatting character, -, followed by a space, followed by our timestamp variable. The second List has as its first element a shit-ton of spaces, for indenting, followed by a formatting character, *, followed by @+, which is, of course, Vim-speak for "whatever is in the computer's shared clipboard". All together, that should result in, schematically:
[blank_line]
- [timestamp]
    * [contents_of_clipboard]

Or, more specifically:
- Sat 19 Jan 2019 01:21:38 AM PST
       *  https://prospect.org/article/return-strike

So that works, but it works in the file we're working in. That seems wrong. Seems like a log file should just be one file that lives somewhere stable. Also, this should be as automatic as possible. What I need is an easy way to call the IncrementLog() function, and a way to specify where to do so. That specified way should be easy to get to from wherever.

I fiddled for a long time with options that would automatically add to the log whenever I opened the log file, but then I realized that a file that adds to itself every time I open it ... is kind of a catastrophically dumb idea. I then fiddled for a short time with an option that would allow me to, from the command line, call a program and feed it an argument, which would be the clipboard. That seemed hard***, so instead I just fell back to the fact that I basically always have Vim open somewhere, and if I don't, it's as close as opening Terminal window and typing vim in there.

***And also more like a Python tutorial than a Vim way of doing things.

That meant I needed a way to find / open / write the log file, and, somewhere in there, at a semi-logical place, make the previous function happen. After quite a huge amount of fiddling, I discovered the following old VimScript adage: Just Put It in a Function, Sparklehorse, Then Call It (the Function).

function! s:UpdateReadingLog()
    :split ~/path/to/file.txt
    :call s:DoSomething()
    :execute ":wq"
endfunction

command! UpdateReadingLog :call s:UpdateReadingLog()

:nnoremap log :UpdateReadingLog<cr>

Fun! So: anywhere in Vim, mash the ol' leader key, followed by log, and the UpdateReadingLog() function opens a new window inside whatever window I'm working in, opens the file I want, runs the function we made earlier (which name I have changed to LogIt() for no good reason), then writes (saves) the file and quits it.

I'm on the fence about whether this really needs two separate functions. I may redo this at some point. But for now, it does almost exactly what I want, and seems to work on my work Mac and my home Zareason Linux laptop****, which is pretty neat. And what have we learned along the way?

  • Quick and easy prototyping of functions!
  • A relatively easy way to spit out the contents of a Vim register into a file—this is relevant because, earlier, we found a way to concatenate a whole bunch of files (or parts of those files) into one register
  • IDK, fun with variables??
  • Another possible use of this: imagine you want to research a new-to-you topic online and have a way to set up some breadcrumbs for yourself; you could bang in a new location for your log file and change your leader situation to like leadernote and Bob's your uncle
  • Anyway, I had a desire to learn something, and a desire to be able to log stuff I was reading, and I (a) learned how to (b) log stuff I was reading
  • So that's cool

****Interestingly, the auto-reload :source % command doesn't seem to work in MacVim. Deeply annoying, and cost me some time. Some possibility, of course, of user error.

Previous entries in Vim (for non-programmers):

Monday, January 07, 2019

Vim (for non-programmers) Chapter O (NOT 0), recipes which are quick and dirty, example two: Dumping Out the Recommendations from IDEOTVPod into One File

(Or, more generally: pulling specified chunks of text from one million semi-well-structured text files, a goal probably often shared by many.)

I had been asked to help a listener pull all the recommendations from my podcast, I Don't Even Own a Television, along with what episode they came from. Since I write the show notes that go on the website, I figured I might have a quicker way to do this than manually going through every post on the site to do the ol' copy-paste. Below is what I came up with. Note: I use Ubuntu at home. The described process works because my laptop is where all the files are, and because, as we'll see, all the relevant files are set up and named the same way.

  1. Navigate to the correct directory
  2. Load all specified files into Vim's argument list—this is a pretentious way of saying "open all the files in Vim at the same time":
    From the command line, run: vim ep_*
    (This works for me because I am a martinet about file naming conventions)
  3. Type :argdo :execute "normal @m"

But what does that even mean? As usual, we have to look at this from right to left (or from inside to out).

To begin with, @m is a macro. Typing @m just means "do a sequence of key-presses that has been specified and then saved to the register m". Or at least that's what typing @m means when you're in Normal mode, which is why we specify :execute ":normal" before the macro. So there's a lot going on with :execute ":normal", but most of it I don't actually understand, so let's skip it for now, because it's complicated, and go to the macro.

The macro is a recording of key-presses. This recording can be of pretty much anything. If you have a repetitive task, a macro can make it much less repetitive. If you have a repetitive task that involves a lot of tricky typing, that's an even more enticing opportunity to use a macro. (In general, macros don't seem to get a lot of use in the Skilled Vim User community, but I think they're often a good way to do a task you have to do a whole bunch of times one day, and maybe not ever again on any other day.) What we have here is:

  • @m = /\d<cr>"By<cr>/Reco<cr>V/\/ul<cr>"By
  • / = search*
  • \d = for the first digit**
  • <cr> execute the search and put the cursor at the result of the search
  • "By = append to the b register the entire line that first digit was on—the functioning of this is a little obscure to me, because it copies the entire line when I don't think it should, but it works, so whatever.
    y means "yank (Vim slang for 'copy')", " means "look for a named register", and B means "use the b register, but I'm capitalized, so append what you're yanking to the end of whatever's in this register, instead of overwriting what's in it"
  • /Reco<cr> = search for the string "Reco" without the quotes and go there
  • V = visually select the entire line
  • /ul<cr> = search for the closing tag of the unordered list that appears in the html file under the heading "Recommendations"—doing this after entering Visual mode will extend the selecting over that whole span: the prefixed \ before the /ul is needed otherwise you can't search for the /, and hitting return, as usual, fixes the selection, and sets us up for the culminating
  • "By, which is another "append this selection to the register we've been working with"

* (for whatever is typed after the slash (in normal mode, which, remember, we specified we'd be in already; otherwise one could preface this with <esc>, which I would normally do just out of muscle memory, honestly))
** \d is of course a regular expression for "digit"

NOTE: for the sake of hygiene, this should almost certainly start off with gg to go to the top of the file, as Vim can be asked to save the last location of the cursor for a given file. So let's pretend I did that.

Finally, the :argdo bit we started with just means "do the following for every argument Vim has right now". In our case, since we opened Vim with all the files we wanted, in Step Two above, this is all the files.

Basically, then, we have one trick, done twice. That trick is "make the computer do the same tedious thing over and over again". We ask the computer to do this first, in one file, as a macro: basically, this automates the process of looking at an open file, searching for and copying the episode name / number into a new place, then searching for and copying the Recommendations section underneath it. (The macro is a dense but plausible reconstruction of the searches/copying one might reasonably do here. The only weird part is dumping everything into a named register instead of an external file.) Second, we then ask the computer to run that macro once per file for a whole pile of files: that's the :argo :execute "normal @m" bit, once the files are loaded into Vim. As for loading them into Vim, that's an operating system task. The cool part is having two separate ways to repeat: the macro and the :argdo command.

Once everything was done, I just pasted the contents of the b register into a text file and emailed it off into the world. If I needed to do this again, I would probably (look up how to) redirect the text I was selecting into a log file somewhere, and skip the register step.

It's debatable whether this was actually quicker than going through a couple year's worth of files. But at least I learned some stuff, and the work I was doing in doing so was much more interesting and engaging than just manually cutting and pasting a whole bunch of stuff a whole bunch of times. Learning :argdo alone will probably make my life a lot easier in future...

Previous entries in Vim (for non-programmers):

Tuesday, March 27, 2018

Vim (for non-programmers) Part Three: Refactoring my .vimrc File, Chapter Five; Correct Easy Markup of Markdown Headlines (Building on Chris Toomey's "Your First Vim Plugin")

At some point in the quest to have The World's Greatest .vimrc File, a person will naturally begin to explore the wider surround, see what the community is up to on the weird-ass-solutions-to-incredibly-narrow-problems front. Since, sometimes, work, life, and other responsibilities exert their gravitationals* upon a person, one helpful way to perform this exploration is to throw on a YouTube video in the background while waiting for the requiring hordes to get bored waiting for me to do the needful and go bother somebody else. I.e., it can be kind of fun to half-listen to somebody, and sometimes you get interesting hints while you're ostensibly, or even actually, working on something else...

*Read: suck.

A great place to start is with Chris Toomey's "Your First Vim Plugin" talk.

Toomey comes across like a delightful fellow, smart and approachable, and his basic approach to creating a plugin seems exactly right to me:

  • Find a problem (something that's difficult, or time-consuming, or repetitive to do)
  • Figure out a working solution (that is easier, or faster, or that eliminates repetition)
  • Drop it in your .vimrc
  • Tinker with it as you find that it needs improvement
  • Eventually abstract it and pull it out of your .vimrc, if you feel like it or if you feel like sharing it

Since I tend to take a lot of my notes in Markdown**, his Markdown underlining approach really made sense to me: a quick and dirty mapping to make something an H1 headline or H2 headline fits my workflow nicely—particularly after abstracting it a tiny bit so that one mapping, <leader>h1, adds the appropriate headlining markup whether I'm writing HTML or Markdown (which Vim knows because it's smart about filetypes!).

**What's Markdown? For most people, it's a way to write very lightly formatted text that can be submitted to another program, and out is spit nicely formatted HTML or something that looks pretty pasted into a Word doc or something. For me, it's a way to tell Vim "Please treat a raw .txt file like it's something else, including pretty syntax highlighting that makes it easier to see what's what." It looks like so:

Here's Chris Toomey's approach:

:nnoremap <leader>h1 :normal! "yypVr=<esc>"
:nnoremap
– in normal mode, ignoring all other mappings
<leader>h1 – when I type the leader key, then h then 1, act like I typed the next line (without quotes)
:normal! "yypVr=<esc>"
yypVr=yy means "yank (copy) the entire line";
p means "put (paste), in this case, the entire line"
V means "visually select (highlight) the entire line"
r= uses the r command followed by another character to mean "replace the character under the cursor with the character typed after r"; in this case, since the entire line has been visually selected, in effect, every character in the line is under the cursor, so every character in the line gets replaced with an equals sign character, which is the Markdown character that makes a headline

It works, it's lightning-fast, and it's completely transparent! I dumped it into my .vimrc immediately, and changed two characters so it would also work for h2 tags (by adding a line of hyphens, not equals signs):

:nnoremap <leader>h2 :normal! "yypVr-<esc>"

This overall approach, in which you come up with a solution that can then be extended, is super congenial to me, and I'm beyond stoked that Toomey put it in front of me. After a while, though, the specific nature of the solution started to bug me a little bit.

The problem was that, at an abstract level, I didn't want to:

Copy a line
Paste a copy of the line beneath the first line
Replace all the characters in the second line with a Markdown markup character

What I wanted to do was:

Given a certain line
Create a line underneath it
With as many markdown markup characters as the given line had characters (including tabs or whatever) so it looks pretty

So I set about trying to make something that would do those things.

Oddly, I succeeded. Here's how it went down.

Vim's built-in programming language, VimScript, has a big library of built-in functions, some of which interact with text. You can check them out at :help functions and :help function-list. After poking around in them and fiddling around getting frustrated for a while, I was able to write the following one-liner, which did exactly what I wanted:

:call append(".", repeat("=", strdisplaywidth(getline("."))))

Let's read that from the inside out:

getline(".") is a function that fetches a line in the current buffer (read buffer for our purposes as "the file we're working on"); what goes in the parentheses is the line number, and "." is the wildcard that means "the line the cursor is on"
strdisplaywidth() is a function that looks at a string and tells you how wide it is, in characters displayed on the screen – including tabs and what have you. The trick with this function is that it can take as its argument another function; put the two functions together as we have, and it means "get the line under the cursor, then tell me how long it is"
repeat("=", 68) is trickier: the way the help describes it as a generic function is repeat({expression}, {count}), which means "do the {expression}, which is the thing inside quotes and before the comma, a number of times specified in {count}". Here I've told it that the expression is the equals sign, which is the character we use in Markdown to underline something that's a headline, and I've yet again fed it, instead of a number, the result of a function, our earlier "get the line under the cursor, then tell me how long it is"
append(".", {text}) is a bit of an easier one by now. append()'s arguments say "after a specified line, insert {text}". Here, we re-use the "." wildcard to specify that the line we want to start with is the line under the cursor, and the text we want to be inserted is the result of the repeat("=", strdisplaywidth(getline(".))) series of functions. Simple!

I can do this any time just by putting the cursor on the line I want and typing:
:call append(".", repeat("=", strdisplaywidth(getline("."))))

That's a lot of relatively persnickety typing to have to do, though. The approach I took was what I take to be a relatively common one, but one that I may very well be misunderstanding! What I did was:

  1. Wrapped the 'sucker in a (script-specific) function of my own
  2. Defined a special command to call the function
  3. Made a mapping that will call that command when I'm writing Markdown, but will call a different command when I'm writing something else

What I may be misunderstanding is why exactly I needed my own function. It seems to simplify using the same mapping to call a variety of different commands, but it may be an unneeded step. Anyway, as it lives in my .vimrc file, it looks like the following:

function! s:MarkdownHeadline1()
:call append(".", repeat("=", strdisplaywidth(getline("."))))
endfunction
command! MarkdownHeadline1 call s:MarkdownHeadline1()

function! s:MarkdownHeadline2()
:call append(".", repeat("-", strdisplaywidth(getline("."))))
endfunction
command! MarkdownHeadline2 call s:MarkdownHeadline2()


function! s:MakeHeadlines()
if &filetype == 'markdown'
echo "Markdown!"
" :nnoremap h1 yypVr=
:nnoremap h1 :MarkdownHeadline1
" :nnoremap h2 yypVr-
:nnoremap h2 :MarkdownHeadline2
:nnoremap li I-
" other stuff elided for clarity
endif
endfunction
command! MakeHeadlines call s:MakeHeadlines()

The MakeHeadlines() function is growing, slowly, and as I get it to do more stuff that I want it to do, it's slowly outgrowing its little name. By the time I'll abstract it into something more broadly useful and extract it into a real plugin, not just a chunk of code sitting in my .vimrc file, I will rename it "Corduroy."

Previous entries in Vim (for non-programmers):

Image yoinked from: http://zedisred.blogspot.com/2011/05/let-me-win-your-heart-and-mind-or-ill.html.

Wednesday, March 14, 2018

Vim (for non-programmers) Part Three: Refactoring my _vimrc File; Chapter Five: Correct Easy Link Addition (Correcting My Misreading of Steve Losh)

As we've seen in previous entries, I've decided to refactor my somewhat creaky, bloated _vimrc file. (For one thing, it was more or less organized chronologically, which makes a certain kind of sense—it certainly reflects an increasing sophistication (or anyway complexity) to my efforts—but also is obviously an insane way to organize configuration settings for anything. For another, I have a tendency to want to hold on to everything I ever tried, so I have, for example, an entire section called "Experiments That Failed Too Many Times".)

Part of the refactoring process, though, involves more than just reordering elements in the file: it involves examining those elements and determining if they could stand some improving.

One of the most vital resources for customizing your _vimrc is Steve Losh's Learn Vimscript the Hard Way. While I was working my way through (the early parts of) this book, I fell in love with a mapping he suggested for surrounding a word with double quotes. (As we know, mappings are a way to tell Vim, "When I type X, please act as though I typed Y". To keep from typing X accidentally, it's common to use a "leader" key followed by a short mnemonic. Because Vim is incredibly complicated, you have to specify what mode the mapping operates in, and you have to specify whether or not the mapping is recursive. This means a mapping is almost impossible to parse visually, because it has the form:
MODE_MAPPING_IS_FOR I_TYPE_THIS YOU_ACT_AS_THOUGH_I_TYPED_THIS
Which, given Vim's predilection for being hella terse, and the obvious utility to tell it "When I type something very short and easy to type, act as though I typed something very long and difficult to type", makes reading mappings difficult.)

viw<ESC>a"<ESC>bi"<ESC>lel
viw
– visually select the current word (the word the cursor is on)
<ESC> – exit visual mode / return to normal mode
a – enter insert mode after the character the cursor is on
" – insert a double quote <ESC> – exit insert mode / return to normal mode
b – move to beginning of word the cursor is on
i – enter insert mode before the character the cursor is on
" – insert a double quote
<ESC> – exit insert mode / return to normal mode
l – move cursor right one character (back onto the word)
e – move to end of word
l – move cursor right one character (onto the closing double quote)

As an exercise, he suggested a more advanced mapping that added double quotes around the last thing that had been visually suggested. I was quite taken with this approach, and, reasoning that HTML tags came in pairs just like quotation marks, I adapted his mappings so that I could quickly add hyperlinks to the word the cursor was on, or to the last thing I had visually suggested. The chunk of my _vimrc containing these mappings read like so (please be impressed with my incredible commitment to code documentation via comment):

" make the word under the cursor a hyperlink to URL from system clipboard
" 18jan2016
" mapping <leader>a to:
" select the current word with viw
" wrap the current word in an <a href> tag
" by moving to the end of the word and adding </a> to close the tag
" then moving to the beginning of the WORD and adding <a href="">
" moving back to the beginning of the WORD to move to the quotes
" and populate the quotes with the contents of the + register
" mostly inspired by Steve Losh
" edited 27dec2016
" :nnoremap <leader>a viw<esc>a</a><esc>Bi<a href=""><esc>Bci"<esc>a<c-r>+<esc>
:nnoremap <leader>a viw<esc>a</a><esc>Bi<a href="<c-r>+<esc>a"><esc>


" make the last visually selected text a hyperlink to URL from system clipboard
" 18jan2016
" mapping <leader>v to:
" select the last visual selection with `< and `>
" wrap that selection in an <a href> tag
" by moving to the end of the visual selection and adding </a> to close the tag
" then moving to the beginning of the visual selection and adding <a href="">
" moving back to the beginning of the visual selection to move to the quotes
" and populate the quotes with the contents of the + register
" mostly inspired by Steve Losh
:nnoremap <leader>v `><esc>a</a><esc>`<i<a href=""><esc>Bci"<esc>a<c-r>+<esc>

This allowed me to use two keystrokes—<leader>v or <leader>a—to add links to my text. This is an extremely handy shortcut—especially when working here in Reviewiera, where it's often the case that to make the case that some stuff is better than some other stuff, it is helpful to link to stuff, and then again to other stuff.

The one big problem I had with this mapping is that it didn't work if the line had quotes in it, because my clever ci" motion "selects the text from the previous quote until the next quote" (see :help a"). This meant that when I wanted to add my links to, say, a paragraph where I had mentioned the title of something, I had to:

  1. Enter some line breaks before and after the text I wanted to add links to
  2. Then add the links
  3. Then remove the line breaks

This pretty severely compromised the whole point of having a quick-keystroke method for adding links. And because I didn't look up the behavior of ci" until I was typing this up, I wasn't entirely certain what the glitch was! All I knew was that I had had to create a workaround when the line before whatever I wanted to add a link to included double quotes.

Today, in a meeting, I decided this was no longer an acceptable way to go about my business, so I put the matter to some thought, and decided:

  • Steve Losh was wrong and his approach to selecting a word then moving the cursor around manually wasn't the best
  • The way to craft this mapping properly would leverage Vim's multiple registers (registers are basically clipboards with names: the ones you can assign are a, b, c ... z, and this was something I abused a lot in my early days using Vim)

I sketched it out on paper before trying anything on the laptop, and, oddly, it turned out to work pretty much exactly as designed. Here's what it looks like:

:nnoremap <leader>a "zdiwi<a href="<CTRL-r>+"<CTRL-z</a><ESC>

As I was working on it and testing it, I realized that the problem wasn't Steve Losh at all, the problem was all me! But now I have a couple extremely sexy mappings that work when I personally need them to work. Just for grins, let's break them down and see what they do.

:nnoremap <leader>a "zdiwi<a href="<CTRL-r>+"<CTRL-z</a><ESC> "z – into the z buffer ... diw – delete the word the cursor is on (not including any surrounding whitespace) i – enter insert mode <a href=" – type <a href=" <CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers) + – the + register is the system clipboard, where we assume the URL is <CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers) z – paste the z register in (this is the word we deleted at first) </a> – close the html tag <ESC> – exit insert mode / return to normal mode

What I like about this is that it works left-to-right, in a more or less sensible way. It basically does exactly what I would do if I were typing: the bulk of the mapping is just banging away in insert mode; typing in the <a href=", then using <CTRL-r>+ to drop in the URL from the clipboard is precisely how I tended to add links before I started fiddling around with mappings in the first place. Also, since I rarely use named registers, I think it's okay to have this mapping clobber the z register. However, as a best practice slash approximation to idiomatically written Vimscript, I think the following mapping is probably better:

:nnoremap <leader>a diwi<a href="<c-r>+"><c-r>"</a><esc>

This just deletes the word, which by default places it into the unnamed register (see :help quotequote), which can be accessed by calling it by the name '"'. (By this point in the series, we should be far beyond being surprised or upset by trivialities like the fact that you can name an unnamed register...)

The visual mapping is a little bit trickier, but only a little bit. It uses exactly the same left-to-right approach, but leverages a fancy little command: gv, which in normal mode re-selects the last visual selection. NOTE: the g prefix in normal mode does some seriously under-known shit. In February of 2015, Tim Chase, one of the heroes on the Vim list, dropped this little gem:

Also, just in case you need it, "g&" is an obscure "across all lines in the file, repeat the last substitution with the same flags" command, even if it's several items back in your command-line history.

Like, seriously, what the hell, Vim? That's a LOT of power to pack into two keystrokes. Anyway, let's check out visual-mode link-adding.

:nnoremap <leader>v gvdi<a href="<c-r>+"><c-r>"</a><esc>
gv – re-select the last visual selection
d – delete what's selected
i – enter insert mode
<a href=" – type <a href="
<CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers)
+ – the + register is the system clipboard, where we assume the URL is
<CTRL-r> – hold down Control and r at the same time (in insert mode, this allows pasting from registers)
" – paste the so-called unnamed register in (this is the word we deleted at first)
</a> – close the html tag
<ESC> – exit insert mode / return to normal mode

Again, this is a simpler approach than Losh's, which goes right-to-left, then has to jump back to the right when it's done. Literally everything else he says and does makes a hell of a lot more sense than anything else I say or do, of course.

Anyway, that's two mappings refactored! I only have the entire rest of my _vimrc to go.