Array (List) functions in vimscript

Vimscript Arrays are called Lists. They have a set of rules and functions that I'll document here for my own reference:

Basics

  • They are defined with let myArray=['one', 'two', 'three']
  • They are zero-indexed: echo myArray[0] is one
  • They can be accessed from the end echo myArray[-1] is three

Functions

  • add(myArray, 'four') adds a new elements. myArray+=['four'] also works
  • get(myArray, 0, 'default value') reads a value, with a fallback
  • len(myArray) returns 3
  • index(myArray, 'three') returns 2 (or -1 if not found)
  • join(myArray, '/') returns one/two/three
  • split('one/two/three', '/') creates ['one', 'two', 'three']

Source

Parsing string as arguments in zsh

I had a commandOptions string variable that I wanted to use as arguments to fzf. But commandOptions had all kind of spaces and quoted strings in it and I had a hard time passing it as a set of distinct arguments and not one long string argument.

The best solution I found was to actually display this commandOptions in a multiline format, where each line was an option. Then, using the ${(f)commandOptions} modifier to read it.

get_options

#!/usr/bin/env zsh
local commandOptions=()
commandOptions+="--disabled"
commandOptions+="--delimiter=   "
commandOptions+="--with-nth=3"
commandOptions+="--bind=change:reload:sleep 0.1;${sourceBinary} {q} || true"

for line in $commandOptions; do
  echo $line
done

use_options

#!/usr/bin/env zsh
local myOptions="$(get_options)"
local selection="$(fzf ${(f)myOptions})"

Deduplicating array values in zsh

Given an array with a lot of values, I want to keep only one occurence of each value. I want to make those values unique.

The (u) modifier

local myArray=(a b c d a b b)
echo ${(u)myArray} # a b c d

Applied on an array, the (u) (for unique) deduplicates the array.

The typeset -aU definition

Somehow, I didn't work in my case and I'm still unsure why, so I found another way.

By defining my variable as an array with unique values from the get go, I don't need to deduplicate it manually, it will automatically refuse duplicate values.

typeset -aU myArray
local myArray=(a b c d a b b)
echo ${(u)myArray} # a b c d

Replacing new lines with spaces in zsh

Another one of those things I need to often do in zsh and never remember the syntax.

I had a multiline string (as returned by fzf) and wanted to convert it into a single line, with spaces instead of spaces.

This was the right formula: selection=("${(f)selection}")

Complex sed search and replace (multiline, regexp, non-greedy)

I had a large chunk of text (output from another command), and needed to perform a search and replace on it. I usually do that either with zsh builting ${var:gs/x/y/} syntax or sed, but this time my pattern was spread on several lines.

Multi-line with --null-data

The trick here is to use sed ---null-data to make it operate on the full text instead of on invidual lines. Technically, it now considers a "line" to end with a NUL character, and not a \n anymore.

Improve readability with --regexp-extended

To improve readability of my regexps, I also used --regexp-extended which allows me to write capturing groups without having to escape them ((.*) instead of \(.*\)).

Non-greedy

sed does not have a non-greedy mode, which means it will always capture the largest group it can.

The way to work around that involves writing a slightly more complex regexp by specifically defining the one character we don't want to capture.

For example with the string foo_bar_baz, I might want to find the part before the first _ by doing ^(.*)_. This will actually return foo_bar because it's greedy.

The way to work around that is to use ^([^_]*)_ instead. This can be read as: capture everything that is not a _ from the start until you met a _.