Pablo Estrada

A workflow for writing with linters

I recently wrote about using linters to check my writing, primarily using a tool called textlint. This post describes how I tweaked my workflow to run text snippets through textlint using Visual Studio Code.

Although textlint is accessible through the command line, I’ve found its Visual Studio Code integration to be pretty smooth, so that’s how I consume textlint’s output. To process my text through Visual Studio Code, I configured an extension and configured an Alfred snippet to help me to do this easily.

Advanced New File extension

The extension Advanced New File is described as, “an easier way of creating a new file inside a project.” You can specify the name of the file on creation and if you define a path that doesn’t exist yet the folders will be created.

To trigger this extension I added this keyboard shortcut to keybindings.json:

  {
    "key": "cmd+n",
    "command": "newFile.createNewFile"
  }

I often use small text snippet files and it’s handy to sync them via Dropbox. Here’s how I configured the extension to create new files in Dropbox by default:

"newFile.defaultBaseFileName": "newFile",
"newFile.relativeTo": "root", // "root" or "project"
"newFile.defaultFileExtension": ".txt",
"newFile.rootDirectory": "~/Dropbox/snippets",
"newFile.showPathRelativeTo": "root", // "project" or "none"
"newFile.expandBraces": false,

Automating filenames using Alfred

Instead of having to manually type in a new filename, I use Alfred’s Snippets. Snippets auto-expand short keystroke combinations into longer text. The classic example is address expansion: instead of manually typing your postal address, enter a keyword like !address and the text is automatically replaced by your complete postal address.

Snippets can also use dynamic placeholders, like {date} or {clipboard}. When creating a new file, I use the {date} placeholder by typing in [date]and Alfred automatically replaces this with the current date in the format I define in Alfred’s preferences. From inside VS Code, the workflow looks like this:

New file with date as filename

Using these extensions and shortcuts, it’s super fast to create a new text file and name it accordingly. When I write in these files, textlint and other extensions for writing check my work on the fly.

How I use linters to check my writing

I installed textlint, a pluggable linter for text. This post describes how I configured it, along with initial results and observations.

At work, my team is developing Bento, a free program analysis toolkit for finding bugs that matter in your code. Our program analysis team writes code checks that find problems in projects using specific frameworks such as Flask and Requests. We’ve also added curated checks from some tools such as Flake 8 and ESLint, and characterized the performance of checks that Bento enables or disables.

As we fine-tuned Bento’s configuration, it got less noisy, had a higher signal-to-noise ratio, and became more useful. Since I spend a lot of time writing, I wanted to have similar software analyze and improve my writing, like Bento analyzes and improves source code.

Inspiration

I found helpful articles like Chris Ward’s Customizing Visual Studio Code for Writing. That and others kept pointing to textlint, so I checked it out.

textlint is described as, “the pluggable natural language linter for text and markdown.” It’s written in JavaScript and inspired by ESLint. Like ESLint, textlint’s pluggable design brings flexibility, extensibility, some initial configuration requirements, and collaboration through contribution.

Installation

textlint suggests per-project installation, but I installed it globally since I wasn’t sure how it would perform, and I didn’t want to check in textlint configs or plugin code to any repository while I tinkered with it. I’m using textlint to check my private files more so than files on which I collaborate with others, so global installation has worked well so far. I saved my configuration file to /usr/local/lib/node_modules/textlint/.textlintrc.

I’m using textlint through Visual Studio Code and the vscode-textlint extension, and added this to my settings.json:

"textlint.configPath": "/usr/local/lib/node_modules/textlint/.textlintrc",
"textlint.nodePath": "/usr/local/lib/node_modules/textlint",

Once install, textlint will do nothing straight out of the box since it has no default rules. The user must select and install rules, and this is where the configuration tasks begin. Note that if you install textlint globally then you must also install each reference rule globally.

The textlint project page lists several dozen rules, about half for English and half for Japanese. It’s not too hard to go through each rule and decide if it’s worth trying. Each rule must be installed (in my case globally) and then configured in .textlintrc. Some rules have more options, and hence need more configuration, than others.

Configuration

I decided to install and configure a bunch of rules, and then observe their behavior. Then I’ll individually turning off rules that don’t seem helpful. I chose these rules:

textlint-rule-no-todo
textlint-rule-alex
textlint-rule-abbr-within-parentheses
textlint-rule-stop-words
textlint-rule-write-good
textlint-rule-en-max-word-count
textlint-rule-en-capitalization
textlint-rule-rousseau
textlint-plugin-html
check-ends-with-period
textlint-rule-terminology
textlint-rule-max-comma
textlint-rule-no-start-duplicated-conjunction
textlint-rule-period-in-list-item
textlint-rule-en-max-word-count
textlint-rule-diacritics
textlint-rule-spellchecker

You can find links to these on the textlint collection of rules page. It took a while to install and configure all the above rules, and I’m not sure I’ve configured them optimally. Here’s a GitHub Gist with my configuration.

Consuming output

Results appear inline through squiggly underlines and in Visual Studio Code’s Problems panel, like this: Problems panel

textlint’s output is also available through the command line and looks like this: textlint output in command line

Results and observations

With these checks enabled and configured, textlint can product a lot of output. I’m still working through which textlint rules to fine-tune and which to mute. I’ve configured some redundant checks, and occasionally a useful comparison of rules appears: Check comparison

In other cases the rules are entirely redundant, such as this case in which I’ve enabled Write Good’s “weasel” and Rousseau’s “weasel” rules: Redundant check output

I also installed Michael Vernier’s SpellChecker extension for Visual Studio Code and configured it in Visual Studio Code’s settings.json:

"spellchecker.ignoreWordsList": [
        "list",
        "of",
        "ignored",
        "words",
    ],
    "spellchecker.ignoreRegExp": [
        "/\\\\(.*\\\\.(jpg|jpeg|png|md|gif|svg|pdf|PDF|SVG|JPG|JPEG|PNG|MD|GIF)\\\\)/g", //remove links to image and markdown files
        "/((http|https|ftp|git)\\\\S*)/g", //remove hyperlinks
        "/^(```\\\\s*)(\\\\w+)?(\\\\s*[\\\\w\\\\W]+?\\\\n*)(```\\\\s*)\\\\n*$/gm", //remove code blocks
        "/^>[\\\\s\\\\S]*$/gm", //remove quoted text
    ],
    "spellchecker.ignoreFileExtensions": [".textlintrc", ".yml", ".json"],
    "spellchecker.checkInterval": 1500,

and Street Side Software’s Spelling Checker for Visual Studio Code, though I haven’t spent enough time with them to form a solid opinion.

Future wishes

I wish each rule included test files, so I could run each rule and understand its behavior at installation time. This way I could better tune my config from the start. As a workaround I’ve tested rules on the command line using this structure: textlint --rule [RULE NAME] [FILENAME]. It’s rather nice to see textlint’s output in the command line instead of at the bottom of my IDE.

Some of the rules have “auto-fix” functionality, but I haven’t tested this enough yet.

Dubious decisions at 10,000 feet

Mammoth Lakes is beautiful and high. Trails and campsites often exceed 10,000 feet in elevation, testing fitness and decision-making. The original plan for a Labor Day weekend backpacking trip looked straightforward on the map, but brought some challenges.

Route Summary

Distance: 12.58 mi
Ascent: 2,954 (-2,916)

Day 1:

Day 2:

  • From Deer Lakes to Duck Lake via Mammoth Crest Trail
  • Duck Lake to Duck Lake Pass Trailhead via Duck Pass Trail
  • Download the GPX file

Day 1

We knew we’d meet holiday weekend crowds, so arriving at 8am made parking in the large lot at Horseshoe Lake Trailhead easy. Hiking began optimistically.

Clear signs at Horseshoe Lake guide to McCloud Lake. From here, the plan was to connect to the Mammoth Rim Trail by going counter-clockwise around the lake and then south and west to the trail. But we couldn’t find the trail indicated on our map, so we decided to navigate our way off-trail. This was a regrettable decision.

While under forest cover, climbing was steep, but manageable. We attributed the difficulty to carrying fully-loaded packs and to the elevation. Once out of the forest, it got hotter and the terrain looser under foot.

Approaching Mammoth Crest

Mammoth Crest loomed ahead; that should have been a warning sign. Knowing we wouldn’t be able to climb directly up and over it (having no rock climbing equipment or skills), we expected to find a path around or connecting to the trail. We continued, turning south at the base of the crest and hugging it towards the southwest.

Relief for the dog was any shade and the large patches of snow still remaining. Cooling down with snow

Fields of large boulders scattered about required departing from a straight path along the base of the crest. The steep angle and scree, combined with the heat, made progress slow. I slipped and tumbled on the slope, rolling and feeling the weight of my pack drive my momentum. Stopping after one roll, I realized how lucky I’d been to have minor scratches to my hiking poles and camera, and no injuries.

After the slip I hiked cautiously, and was also determined to get onto the Mammoth Rim Trail as efficiently as possible. What appeared to be a use trail or at least a slightly worn footpath appeared ahead, and we decided to use it to connect to the Mammoth Rim Trail. That path was steep and slippery, but it offered a clear way out of what had become an unnerving situation.

Connecting to Rim Trail Elevation: 10,331 ft.

Relief came as we joined the Mammoth Rim Trail and felt surefooted walking on a proper trail. Connecting to Rim Trail Mammoth Rim Trail

And views from the trail didn’t disappoint. Expansive views from Rim Trail Elevation: 10,494 ft.

Looking down from about 1,000 feet above Hammil Lake. Above Hammil Lake Elevation: 11,187 ft.

Our high point reached about 11,200 feet, and from descended to Deer Lakes, a group of three lakes nestled among large rock faces. The most prominent might be Blue Crag at 11,669 ft. We camped at Middle Deer Lake directly across the lake from Blue Crag. We met only one other group at the lake, a friendly trio who were on an extended trip. At night, echoes from coyotes wails bounced around the rocks, startling the dog. Arriving at Middle Deer Lake Arriving at Deer Lakes. Elevation: 10,713 ft.


Day 2

In the morning we continued southeast, passing the easternmost Deer Lake looking for the trail towards Duck Lake. But the route wasn’t clear. We knew the trail cut towards the east, but couldn’t understand how to get over the steep ridge. Passing the southern end of the lake, I scampered up a steep, rocky slope to investigate. Partway up and not finding any route, reason returned and I decided to stop climbing higher or I’d risk getting stuck. We backtracked to the edge of the lake, where we met two individual hikers searching for the same route to Duck Lake. We compared maps and one went off to search for it, shouting back to confirm the trail was usable.

The lower loop in this map view shows the route I explored and abandoned. The correct route is the one leading due east. Route-finding at Deer Lakes

In retrospect, perhaps we should have found the trail more easily. Articles about this route noted this:

Hiking Project:

Continue along the middle lake and steadily ascend again. At this point, the trail becomes less defined, but is still recognizable most of the time. Once you reach the southeastern lake, do not continue further south, but stay on an easterly direction up a fairly rocky slope. Several maps show Mammoth Crest Trail heading further south and then up an extremely steep slope. That is not the trail.

Hiking & Walking:

Part of the loop between Deer Lakes and Duck Pass travels along a use trail/route, which can be difficult to follow at times…At the upper lake, look for an obvious saddle on the ridge at the northeast end of the lake. Climb the steep talus slope to the saddle. There are a few vestiges of a use trail going up the slope.

Hiking Arizona:

To your left is a steep slope covered with boulders. You can scramble here, but there is a much easier way up. If you look to the left of the lake, you will see what appears to be a trail. To reach this trail you need to descend toward the lake and then head to the trail. This trail is well defined all the way up to the top of the steep climb. It’s also very steep.

A view from partway up the use trail looking down to Deer Lakes: View from use trail Elevation: 11,014 ft.

Even knowing this and using GPS, finding the trail proved difficult. Decision-making at 10,900+ feet seemed less than clear.

Again, we felt relief as we joined a well-worn trail, and even though the trail was difficult to follow at times, the terrain was easy to manage. Once within a few hundred feet of the Duck Pass Trail, crowds increased. Everything seemed busier and louder, noticeably different from the afternoon and evening at Deer Lakes. But the view of Duck Lake doesn’t need description:

View of Duck Lake from near Duck Pass Elevation: 10,877 ft.

Our original plan included camping at Duck Lake. We skipped that, preferring not to jostle for a campsite among many groups and instead descended 7+ miles to Duck Pass Trailhead at the edge of Cold Water Campground.

Descent from Duck Lake Pass On the descent from Duck Lake Pass

We arrived at Duck Pass Trailhead at 2:45pm. A kind couple we met on the descending trial gave me a ride back to the Horseshoe Lake parking lot, saving me walking 2+ miles. With clear weather, the drive back to San Francisco was painless.


Effects of Altitude

After the trip, I wondered if altitude affected our decisions or if something else went wrong. Insufficient planning and research? Poor map reading skills? Stubbornness?

I found this book via the National Center for Biotechnology Information, U.S. National Library of Medicine:

Marriott BM, Carlson SJ, editors, Nutritional Needs In Cold And In High-Altitude Environments; Applications for Military Personnel in Field Operations, Institute of Medicine (US) Committee on Military Nutrition Research, 1996

Some selections from Chapter 22: “The Effect of Altitude on Cognitive Performance and Mood States”

“At 4,300 m (14,110 ft), moods differed from baseline (200 m [656 ft]) on the day of arrival (day 0) and differed even more after 1 day (Figure 22-1). Subjects became less friendly, less clear thinking, and dizzier.”

“Most investigations have shown decrements in cognitive performance beginning at an altitude of 3,000 m (9,843 ft) (Bahrke and Shukitt-Hale, 1993; Tune, 1964).”

“Cognitive performance was significantly impaired on 8 out of the 10 performance measures. Even on relatively simple performance tasks—such as simple and choice reaction time—as well as complex tests of cognition—such as the addition test—impairments occurred in a graded manner.”

I’m not sure what effect altitude had during this trip. I’ve hiked at high altitude before, in the Indian Himalayas with passes exceeding 14,000 feet. While I did get some altitude sickness, I don’t recall such an impact on mental facilities. But that was years ago, and I was in better physical shape.