vim

Attempting to learn to use VSCode: some keymappings and notes.

December 12, 2025 C/C++ development and debugging. , , , , , , , , , ,

I’ve been using vim and the terminal for ~30 years, and am working now in a VSCode shop.  I am probably the only holdout, using terminal tools (tmux, vim, cscope, ctags, perl, …).  I am a keyboard guy, and am generally hopeless in a UI of any sort, and don’t find them particularly intuitive.

I keep trying to make the VSCode switch, but then get frustrated, as I want to do something that I can do trivially outside of the UI and I have no idea how to do it in the UI.  I’ll then switch to terminal for something “quick”, and end up just staying there for the rest of the day.

I know that there are good reasons to use VSCode.  In particular, the AI helper tools are near magical at filling in comments and even code.  How the hell did it read my mind this time is often the feeling that I have when I am using it.

Here are some examples of things that I can do really easily outside of the VSCode environment:

  • Switching tabs (open files in the UI) with just keystrokes.  I do this in tmux with F7, F8 keymappings.  I use tmux aliases to put names on all my shell sessions, so I can see at a glance what I am doing in any (example: I just type ‘tnd’ and my tmux window is then labelled with the last two components of my current directory.)
  • Open a file.  Clicking through a UI directory hierarchy is so slow.  I have CDPATH set so that I can get right to my src or tests directory in the terminal.
  • build the code.  Typing ninja from my tmux “build” directory is so easy (and I have scripts that rerun cmake, clean and recreate the build directory).
  • Run an ad-hoc filter on a selected range of lines in the code (either visually selected, or with a vim search expression, like “:,/^  }/!foo”.  If I install the vim extension in VSCode to use comfortable key bindings, then even a search like that doesn’t work.
  • I can’t search for /^  }/ (brace with two spaces of indentation), since the VSCode vim extension insists on ignoring multiple spaces in a search expression like that.
  • Iterate quickly over compilation errors.  In the terminal I just run ‘vim -q o’, assuming that I’ve run ‘ninja 2>&1 | tee o’
  • Launch a debugger.  I can put my breakpoints in a .gdbinit file (either ~/.gdbinit or a local directory one), and then just run.  How to do the same in the UI is not obvious, and certainly not easy.  I have done it, and when you can figure out how to do it, it’s definitely nice to have more than a peephole view of the code (especially since gdb’s TUI mode is flaky.)

It’s my goal to at least understand how to do some of these tasks in VSCode.  I’m going to come back to this blog post and gradually fill it in with the tricks that I’ve figured out, assuming I do, so that I can accomplishing the goals above and more.

My environment

I am using a PC keyboard.  It’s an ancient cheap logitech keyboard (I had two of these, both about 9 years old, both in the same sad but impressively worn state).  Those keyboards have nice pressable keys, not like the mac laptop.  The mac laptop keyboard is for well dressed people browsing the web in Starbucks, not for people in the trenches.  I use the Karabiner app to map my Alt key to Command so that the effective “command” key is always in the same place.  For that reason, some of these key mappings may not be the ones that anybody else would want.

Claude suggests that these are the meanings of the keyboard symbols in VSCode:

And suggests that for me the Alt/Option is my “physical command key” (i.e.: Alt.). I have yet to find a keybinding that I want to use with that to verify that my Karabiner settings don’t do something strange.

How to do stuff (a start):

  • Toggle to the terminal, or start a new one:ctrl-`(at least with my PC keyboard).  VSCode help shows this as:Alternative for create terminal: command-shift-p (command palette) -> open new terminal
  • Search for a file to edit:command-p(Alt-p on my PC keyboard.)VSCode help shows this as “Go to File”, but with an apparent capital P:Somewhat confusingly, the VSCode help shows all the key binding characters in upper case, even though command-p and command-P (shift p) mean different things.
  • Open keyboard shortcuts:command k, (let go), command s ; or:
    command-shift-p (command-P) -> Keyboard shortcuts(Alt-shift p on my PC keyboard)
  • Toggle between editor windows:ctrl-tab
  • Move to editor window N:ctrl-N (for example: ctrl-1, ctrl-2, …)Note that command-2 opens a split (to the right), much different than what ctrl-2 does (command-1, command-3 don’t seem to be bound)
  • Search for a pattern with multiple spaces (with vim extension installed).  Example:/^\s\s}Searching with:/^  }(start of line, two spaces, end brace), does not work, as VSCode or the vim extension seems to aggregate multiple spaces into one.
  • Maximize a terminal, or switch back to split terminal/edit view:I ended up adding a ‘command-m’ keybinding for “Toggle Maximized Panel” to do that.  With that done, I can cycle between full screen terminal and split screen editor/terminal.
  • Maximize an editor window, or switch back to split edit/terminal:ctrl-jThis might better be described as: Hide/show the panel (terminal area), giving the editor more space when the panel is hidden.
  • Close a window:command-w(Alt-w on the PC keyboard)
  • Strip trailing whitespace:command-k, let-go, command-xI see this in the ‘Keyboard Shortcuts’ mappings, but am unlikely to remember it, and will probably revert to using:%s/ *$//or an external filter (that’s how I used to do it.)
  • Build command:command-shift-b (command-B)I did have a bunch of .vscode json overrides that had different build targets, but something has removed those from my tree, so as is, it’s not clear to me what exactly this does.  cmake options come up.I’ll probably just invoke ninja from the terminal (with rm -rf build ; cmake … when I want it.)
  • Tasks shortcutctrl-shift-y (ctrl-Y)This was a recommended key binding from one of our vscode gurus, and I’ve used it.  But it’s annoying that my .vscode/tasks.json was removed by something, so this now does nothing interesting (although that’s okay, since I can now switch to the terminal with a couple keystrokes.)
  • Shell callouts.  It is my recollection that I was unable to run shell callouts.  Example::,/^}/!grep foobut after setting the shell command in the vim extension settings to /bin/bash, this now works.  It’s awkward though, and runs the shell commands locally, not on the remote environment, so I can’t run something like clang-format, which I don’t have installed (currently) on my mac, but only on the remote.  I suppose that I could have a shell command ssh to the remote, but that’s pretty awkward (and would be slow.). The work around for clang-format will probably just be to run ‘clang-format -i’ in the terminal (which can have unfortunate side effects when applied to the whole file.)
  • Debug: create a debugger launch configuration stanza in .vscode/launch.json, like so:

    {   
        "version": "0.2.0",
        "configurations": [
            {
                "name": "Debug foo",
                "type": "cppdbg",
                "request": "launch",
                "program": "${workspaceFolder}/build/foo",
                "args": [],
                "cwd": "${workspaceFolder}/build",
                "MIMode": "gdb",
                "miDebuggerPath": "/usr/bin/gdb",
                "setupCommands": [
                    { "description": "Set initial breakpoint", "text": "-break-insert debugger_test", "ignoreFailures": true }
                ],  
                "preLaunchTask": "build"
            }]} 
    

    Then set a breakpoint in the source that you want to stop in, click the bug symbol on the LHS:

    Bug icon

    select that new debug configuration, and away you go. This brings up a debugger console, but it’s a bit of a pain to use, since it’s in MI mode, so for example, instead of ‘n’, you have to type ‘-exec next’. The vscode key mappings to avoid that extra typing are (according to the Go menu) are:

    • n: F10
    • s: F11 (now cmd-F11)
    • finish: shift F11
    • c: F5That step-in F11 action didn’t work for me, as macOS intercepts it (i.e.: “Show desktop” — a function that doesn’t seem terribly useful, as I don’t have anything on my desktop.)  I’ve changed that “Debug: Step Into” keybinding to a command-F11, and changed “Debug: Step Into Target” (which used command-F11) to ctrl-F11.  I’m not sure if I’ll end up using that ctrl-F11, or just setting breakpoints when the step into candidate has multiple options.
  • MacOS required keyboard configuration!Typing spaces fast in vscode results in rogue period insertions.  Every time I would try vscode again, channelling a diet and exercise “and this time I mean it” vibe, I’d hit this rogue period issue and go back to terminal in frustration.

    Watercooler talk in the office suggested that this is apparently a MacOS feature (but doesn’t effect my usual terminal+ssh+vim workflow).  Chat recommended the following keyboard configuration setting adjustments to fix it (testing that now):

    Fixing EVIL MacOS keyboard settings that cripple vscode.

    Fixing EVIL MacOS keyboard settings that cripple vscode.

brace matching in vim, regardless of how it is formatted?

August 31, 2016 C/C++ development and debugging. ,

DB2 functions were usually formatted with the brace on the leading line like so:

size_t table_count( T * table )
{ 
   size_t count = 0 ;
   ....
} 

For such code, typing [[ in vim anywhere from somewhere in the function text would take you to the beginning of the function. It has always annoyed me that this key sequence didn’t work for functions formatted without the leading { in the first column, such as

size_t table_count( T * table ) { 
    size_t count = 0 ;
    ....
} 

Having my handy [[ command sequence take me to the first line of the file is pretty annoying, enough that I looked up the way to do what I want. A key sequence that does part of this job is:

[{

This takes you to the outermost ending position of the current scope, and you can use % to get to the beginning of that scope. You can repeat this as many times as necessary, until you get the outermost scope.

Is there a better way to go directly to the outermost scope directly, regardless of how the function happens to be formatted?

Some notes on copying and moving text in vim

April 13, 2015 perl and general scripting hackery , ,

Emad just asked me a vim question (how to use a search expression instead of a line number), and I ended up learning a new vim commmand from him as a side effect.

I’d done stuff like the following before to move text to a new file

:,/Done/-1 !cat > /tmp/newfile.txt

This assumes you’d like to delete everything from the current position to the line just before the /Done/ search expression, and write it into /tmp/newfile.txt.

The mechanism here, is that the selection is filtered through a script, where the output of the script is empty, so the lines are deleted. This particular script has the side effect of creating a file with the selected range of lines. The end effect is that the text is moved.

If you’d like to keep it and copy it to the new file, you can tee instead of cat it:

:,/Done/-1 !tee /tmp/newfile.txt

This is faster than selecting a range, switching buffers copying into the buffer, saving, and switching buffers back.

Emad taught me that this can also be done with the w command, like so:

:,/Done/-1 w /tmp/newfile.txt

It doesn’t surprise me that there’s a faster way to copy text from one file to another than using tee, but since I knew one way, I never went looking for it.

Some Unix command line one liners

July 24, 2014 perl and general scripting hackery , , , , , , , , , ,

Here’s a couple one-liner shell commands collected over the last couple months when it occurred to me to record them.  Each of these I thought were somewhat notable at the time I did so.

Nested “backquotes”

I often have to run commands where it is convenient to have the parameters of the commands in a file.  A simple example is to edit all the files in a list of files, say:

vim `cat c`
# or:
vim $(cat c)

A useful variation of this is to do the same using the output of a command that also takes its input from a file. Here’s one to edit all the “ancestor” files in the version control system, assuming a command vcsancestor that produces such filenames

vim `vcsancestor $(cat c)`
# or
vim $(vcsancestor $(cat c))

Observe how two different methods of embedding shell commands can be combined into one command. In the past I often used for loops for something like this, say:

for i in `cat c` ; do vcsancestor $i ; done > f
vim `cat f`

(because backquotes can’t be nested). It only recently occurred to me that this isn’t a limitation if $() style subshells are used.

Batching commands with xargs

When working in a version control system, it’s often useful to do a batch checkout of all the files that have compilation errors.  Suppose that you made changes that produced the following compilation error output:

$ cat compile.errors
"satauth.C", line 978.30: 1540-0274 (S) The name lookup for "sqlorest" did not find a declaration.
"scrutil.C", line 142.52: 1540-0274 (S) The name lookup for "SQLNLS_SAME_STRING" did not find a declaration.
"testdrv.C", line 1146.16: 1540-0274 (S) The name lookup for "SQLO_OK" did not find a declaration.
"testdrv.C", line 183.15: 1540-0274 (S) The name lookup for "SQLO_OK" did not find a declaration.

Here’s a one liner to checkout all the files in this list of compilation errors (this is AIX xlC error output):

cut -f2 -d'"' x | sort -u | xargs cleartool checkout -nc

The cut command selects just the (first) double-quote delimited text, then dups are removed with sort -u, and finally xargs is used to run a command on each of the files in the resulting output

Looking for a subset of information delimited by markers on separate lines

grep works nicely for matching patterns that are constrained to a single line.  If you are using gnu-grep you can use the -A and -B options to find stuff after and before the pattern of interest.  As an example, in our stacktrace files (a post mortem crash dump format), we have output that includes:

<pre>

<StackTrace>
—–FUNC-ADDR—- ——FUNCTION + OFFSET——
0x00002AAAC74EF263 ossDumpStackTraceInternal(unsigned long, OSSTrapFile&, int, siginfo*, void*, unsigned long, unsigned long) + 0x06e3
0x00002AAAC74EFE89 ossDumpStackTraceV98 + 0x007f
0x00002AAAC74E5C5F OSSTrapFile::dumpEx(unsigned long, int, siginfo*, void*, unsigned long) + 0x04db
0x00002AAABA6EB313 sqlo_trce + 0x0a6f
0x00002AAABA9C52B5 sqloDumpDiagInfoHandler + 0x047b
0x00002AAAAABD5E00 address: 0x00002AAAAABD5E00 ; dladdress: 0x00002AAAAABC8000 ; offset in lib: 0x000000000000DE00 ;
0x00002AAAAABD30A5 pthread_kill + 0x0035
0x00002AAAB5D828DF ossPthreadKill(unsigned long, unsigned int) + 0x0053
0x00002AAABA9C6CA1 sqloDumpEDU + 0x0091
0x00002AAABED7A853 sqlzerdm + 0x149b
0x00002AAAB5D7D745 sqle_remap_errors(int, sqlca*, sqeAgent*) + 0x01c9
0x00002AAAB5DE8717 sqeApplication::AppStopUsing(sqeAgent*, unsigned char, sqlca*) + 0x10b1
0x00002AAAB5D46FF5 address: 0x00002AAAB5D46FF5 ; dladdress: 0x00002AAAAACE1000 ; offset in lib: 0x000000000B065FF5 ;
0x00002AAAB5D4073F address: 0x00002AAAB5D4073F ; dladdress: 0x00002AAAAACE1000 ; offset in lib: 0x000000000B05F73F ;
0x00002AAAB5D44F35 sqleIndCoordProcessRequest(sqeAgent*) + 0x3959
0x00002AAAB5DA8E55 sqeAgent::RunEDU() + 0x061b
0x00002AAABEDAC2C7 sqzEDUObj::EDUDriver() + 0x035d
0x00002AAABEDABBD7 sqlzRunEDU(char*, unsigned int) + 0x0053
0x00002AAABA9BFC62 sqloEDUEntry + 0x1460
0x00002AAAAABCE2A3 address: 0x00002AAAAABCE2A3 ; dladdress: 0x00002AAAAABC8000 ; offset in lib: 0x00000000000062A3 ;
0x00002AAAC7F376DD __clone + 0x006d
</StackTrace>

</pre>

Here’s a one-liner to grab just the portions of these files within the delimiters (with some other filtering that isn’t of terrible interest to describe)

for i in *stack* ; do grep -A40 ‘<StackTrace’ $i | grep -v ‘(/’ | grep -B40 ‘/StackTrace’ | c++filt ; done | less

Unix to Windows path separator switching

Suppose we have some unix filenames

</pre>
$ head -5 f
/vbs/bin/AEDefines.pm
/vbs/bin/AEMacro.pm
/vbs/bin/bld_shared_lib_Darwin
/vbs/bin/chglibpaths
/vbs/bin/chglibpaths_Darwin

and want the Windows paths for the same

</pre>
$ head -5 f | tr / '\\'
bin\AEDefines.pm
bin\AEMacro.pm
bin\bld_shared_lib_Darwin
bin\chglibpaths
bin\chglibpaths_Darwin
<pre>

The tr command above looks a bit like ascii barf, and will translate forward slashes to backward slashes (perhaps for input that’s a list of files).

I didn’t understand the requirement to both single quote the backslash as well as escaping it, but Darin explained it for me:

Quotes allow the backslashes to go through the shell to tr.  And tr has its own backslash escape mechanism (so you can do things like transform \n into \r or something – where you’d then specify ‘\n’ or just \\n and ‘\r’ or \\r).

Vim: replace search results with contents from a file

Probably related to merging conflicting changes, I wanted to completely replace the implementation of a particular function:

void foo() {
 ...
}

This was an easy way one liner method to do that replacement, deleting the implementation of foo, and replacing it with the one that was found in the file ‘foo’

:,/^}/ !cat foo

file:line: delimited output for a single file

The grep -n command is very handy for producing file:line:content delimited output.  In particular, you can iterate over such output with vim -q.  When you want to do this for a single file, grep -n doesn’t include the filename, defeating a subsequent vim -q (since vim then doesn’t know what file to open).  Here’s an example

$ cat my_file_to_search
blah patternOfInterest hi
foo goo
patternOfInterest bye
blah patternOfInterest hi
blah patternOfInterest hi
foo goo
patternOfInterest bye
foo goo
patternOfInterest bye

$ grep -n patternOfInterest my_file_to_search | tee v
1:blah patternOfInterest hi
3:patternOfInterest bye
4:blah patternOfInterest hi
5:blah patternOfInterest hi
7:patternOfInterest bye
9:patternOfInterest bye

To get vim -q’able output, just include a second non-existent dummy file in the search

grep -n patternOfInterest my_file_to_search a_file_that_doesnt_exist | tee v
vim -q v

I usually use a very-short filename for the “does not exist file”, say, .u (which presumes I also don’t create little hidden files .u in my day-to-day work).

Sum of digits of small powers of nine.

July 15, 2014 math and physics play , , , ,

[Click here for a PDF of this post]

In a previous post I wondered how to prove that for integer \(d \in [1,N]\)

\begin{equation}\label{eqn:numberGame:20}
((N-1) d) \text{mod} N + ((N-1) d) \text{div} N = N-1.
\end{equation}

Here’s a proof in two steps. First for \(N = 10\), and then by search and replace for arbitrary \(N\).

\(N = 10\)

Let

\begin{equation}\label{eqn:numberGame:40}
x = 9 d = 10 a + b,
\end{equation}

where \(1 \le a, b < 9\), and let \begin{equation}\label{eqn:numberGame:180} y = a + b, \end{equation} the sum of the digits in a base \(10\) numeral system. We wish to solve the following integer system of equations \begin{equation}\label{eqn:numberGame:60} \begin{aligned} 9 d &= 10 a + b \\ y &= a + b \\ \end{aligned}. \end{equation} Scaling and subtracting we have \begin{equation}\label{eqn:numberGame:80} 10 y - 9 d = 9 b, \end{equation} or \begin{equation}\label{eqn:numberGame:100} y = \frac{9}{10} \lr{ b + d }. \end{equation} Because \(y\) is an integer, we have to conclude that \(b + d\) is a power of \(10\), and \(b + d \ge 10\). Because we have a constraint on the maximum value of this sum \begin{equation}\label{eqn:numberGame:120} b + d \le 2 ( 9 ), \end{equation} we can only conclude that \begin{equation}\label{eqn:numberGame:140} b + d = 10. \end{equation} or \begin{equation}\label{eqn:numberGame:160} \boxed{ b = 10 - d. } \end{equation} Back substitution into \ref{eqn:numberGame:40} we have \begin{equation}\label{eqn:numberGame:200} \begin{aligned} 10 a &= 9 d - b \\ &= 9 d - 10 + d \\ &= 10 d - 10 \\ &= 10 \lr{ d - 1 }, \end{aligned} \end{equation} or \begin{equation}\label{eqn:numberGame:220} \boxed{ a = d - 1. } \end{equation} Summing \ref{eqn:numberGame:220} and \ref{eqn:numberGame:160}, the sum of digits is \begin{equation}\label{eqn:numberGame:240} a + b = d - 1 + 10 - d = 9. \end{equation}

For arbitrary \(N\)

There was really nothing special about \(9, 10\) in the above proof, so generalizing requires nothing more than some search and replace. I used the following vim commands for this “proof generalization”

:,/For arb/-1 y
:+/For arb/+1
:p
:,$ s/\<9\>/(N-1)/cg
:,$ s/\<10\>/N/cg
:,$ s/numberGame:/&2:/g

Let

\begin{equation}\label{eqn:numberGame:2:40}
x = (N-1) d = N a + b,
\end{equation}

where \(1 \le a, b < N-1\), and let \begin{equation}\label{eqn:numberGame:2:180} y = a + b, \end{equation} the sum of the digits in a base \(N\) numeral system. We wish to solve the following integer system of equations \begin{equation}\label{eqn:numberGame:2:60} \begin{aligned} (N-1) d &= N a + b \\ y &= a + b \\ \end{aligned}. \end{equation} Scaling and subtracting we have \begin{equation}\label{eqn:numberGame:2:80} N y - (N-1) d = (N-1) b, \end{equation} or \begin{equation}\label{eqn:numberGame:2:100} y = \frac{N-1}{N} \lr{ b + d }. \end{equation} Because \(y\) is an integer, we have to conclude that \(b + d\) is a power of \(N\), and \(b + d \ge N\). Because we have a constraint on the maximum value of this sum \begin{equation}\label{eqn:numberGame:2:120} b + d \le 2 ( N-1 ), \end{equation} we can only conclude that \begin{equation}\label{eqn:numberGame:2:140} b + d = N. \end{equation} or \begin{equation}\label{eqn:numberGame:2:160} \boxed{ b = N - d. } \end{equation} Back substitution into \ref{eqn:numberGame:2:40} we have \begin{equation}\label{eqn:numberGame:2:200} \begin{aligned} N a &= (N-1) d - b \\ &= (N-1) d - N + d \\ &= N d - N \\ &= N \lr{ d - 1 }, \end{aligned} \end{equation} or \begin{equation}\label{eqn:numberGame:2:220} \boxed{ a = d - 1. } \end{equation} Summing \ref{eqn:numberGame:2:220} and \ref{eqn:numberGame:2:160}, the sum of digits is \begin{equation}\label{eqn:numberGame:2:260} a + b = d - 1 + N - d = N-1. \end{equation} This completes the proof of \ref{eqn:numberGame:20}.