My terminal, coding workflow - i3, Kakoune, nnn

If I’m working on code or tech related tasks, I’m doing it on Linux. That’s because I enjoy spending my time in terminal. I like playing around with new command line tools and fitting them into my workflow. Improving my process is important so I often try to update parts of my setup. The goal is to reduce the friction between my intention and execution.

Linux environment is full of various tools and utilities. I’ve always been a fan of Vim but quickly found NeoVim, which is an attempt to modernize Vim’s codebase. Vim itself is never enough so I’ve started integrating plugins, which in turn introduced me to a lot of smaller tools that further improved my process. Some of those tools have been substituted by a faster and more robust alternative (usually written in Rust).

Currently my setup revolves around several crucial parts:

  • i3, a tiling windows manager
  • Kakoune, a modal code editor similar to Vim but (imo) better
  • ctags, a tool for indexing the codebase
  • ripgrep, a tool for quickly searching through contents of files
  • nnn, a terminal file browser
  • fzf, a fuzzy finder

Recently I’ve found some fun ways of making some of those tools work with each other. It’s nothing revolutionary. It’s just a nice example of Linux’s philosophy Do one thing and do it well… working well.

Kakoune

Lets start by looking at Kakoune. It’s a code editor with a client/server architecture. Starting Kakoune means starting a server, which stores all the opened files’ data. Each instance of Kakoune can start its own server or connect to one. Each terminal window can be a client which connects to the main server. That way all the windows can access all the files that the server considers opened.

Start a Kakoune server named s1 and open file main.c:

kak -s s1 main.c

You can then open another terminal window and connect to that server by running:

kak -c s1

This window will also display contents of the main.c file.

Nothing crazy, right? The thing is that a lot of new users, me included, look for an option to vertically split Kakoune’s window. That’s usually how we use Vim - two panes, side by side. Kakoune is not a window manager. It’s a text editor - it allows the user to efficiently edit text. You want multiple windows? Open another terminal window and start Kakoune client instance.

That’s where the i3 comes in. Any tiling windows manager for that matter would do. Those work very well in situations when you open a lot of terminal windows because it handles the windows' layout.

You can call i3 commands from Kakoune which means that you can still split your view in Kakoune… except Kakoune won’t create or manage the new view. That task is, rightfully so, delegated to the windows manager.

It sounds convoluted but if you are on board with tiling windows managers, which are very fun to use if you write code, you might see the benefits of this solution.

nnn

Another feature that Kakoune skips is a file browser. Sure you can open a file from Kakoune using it’s path (with some nice auto completion) but you can’t open a proper file browser like Vim’s netrw.

Bring your own file browser!

Ok, can do.

Navigating directories in terminal isn’t fun. It’s a lot of typing. For a while I’ve used ranger to make it easier. It’s a pretty cool file browser for your terminal. I still use it from time to time, but my main driver is nnn. If you boot those two, side by side, it seems like nnn can’t compete. It looks very simple. Here is why I believe the nnn is a better file browser.

ranger is file browser written in Python and it shows. The performance seems to be ok when navigating typical filesystem but it needs a while to read dense directories. It also doesn’t start immediately. Remember: it’s a file browser. It displays files and directories. Anything less than ‘blazing fast’ is too slow. ranger’s performance isn’t atrocious but it’s not ‘blazing fast’.

nnn is written in C. It’s trivial to fetch the newest version from GitHub and build it. The resulting binary is typically around 100KB and it needs around 3.5MB of RAM memory. An app that uses so little resources can’t be sluggish and indeed nnn isn’t. It’s blazing fast.

One thing that is quite unique is how you configure nnn. Most of the apps running on Linux use configuration files which live in the ~/.config directory. nnn stores plugins and some other files there but no user configuration files. There are three ways in which user can change nnn’s behaviour: starting nnn with specific command line arguments, changing the source code and recompiling or setting up environment variables. This page describes the configuration in detail.

Using command lines arguments is pretty straight forward. If you’ve started nnn by just running nnn, you can press ’d’ key to toggle detail mode. If you want that mode be the default one, you can start it like so nnn -d. You can find all the options by running nnn -h.

If you want to, for example, edit key bindings you have to recompile the binary. Since nnn doesn’t have many dependencies this process is pretty trivial. Well structured, minimalistic projects are such a joy to work with. nnn is one of those. The sole source file consists of 7k lines of code… and I don’t think there is anything wrong with that. Much respect to the nnn’s creators and maintainers.

Lastly, one can use the environment variables. If you want to turn on nnn’s plugins (make sure to first download them - link) you have to set an environment variable like so:

export NNN_PLUG='h:hexview;v:imgview;m:-mediainf;c:chksum'

This variable maps hotkeys to specific plugins. If you now start nnn and press ‘;’ you’ll see a list of those hotkeys.

In the same way you can create a list of bookmarks for nnn:

export NNN_BMS='d:~/Desktop;'

If you want those to be persistent, I suggest appending something like so to your ~/.bashrc file:

export VISUAL=kak
export EDITOR=$VISUAL
export PAGER=less

export NNN_BMS='d:~/Desktop;'
export NNN_PLUG='h:hexview;v:imgview;m:-mediainf;c:chksum'

Everything I’ve described above is READMEs level knowledge. Let’s go a bit deeper.

i3 glue

If you’re used to tiling windows managers, you open windows left and right, move them between workspaces, resize and group them. Everyone in the office is impressed. You quickly become the coolest person around and the legends about your sick hotkeys navigation skills spread far and wide.

That’s only because they don’t see the dark side. You struggle with the fact that windows loose any meaning. You open and close them so quickly that none of them matter to you. Which one is your code editor? All of them? None? Since Kakoune allows you to connect to its server with any window, you can just keep opening new client windows. That leads to a messy environment and makes it difficult to keep a persistent session. By default i3 gives you 10 workspaces to work with, which puts the burden on the user to remember what’s where. Let’s commit to a more disciplined approach.

One workspace is for coding. It has the typical setup of two coding panes and one file browsing pane.

My i3, Kakoune, nnn workspace.
My i3, Kakoune, nnn workspace.

This setup isn’t great if the file browser isn’t interacting with the code editor’s panes. I want to be able to browse my file system and open any file in my Kakoune session. I can do that by adding my own plugin. It’s ridiculously easy since plugins can be written in any language. For a simple task like this one bash will work great:

file_path=$2/$1

echo "evaluate-commands -client L edit ${file_path}" | kak -p a

I’ve saved this script in ~/.config/nnn/plugins/kak_open file and made in executable (chmod u+x kak_open). nnn needs to be made aware of the plugin. I did that by appending k:kak_open to NNN_PLUG environment variable.

nnn calls the plugin script with filename ($1) and path ($2) as arguments. I use those to create a full file’s path. The echo outputs a command which Kakoune will understand. It specifies a command edit ${file_path} for client L and pipes it to Kakoune server named a, thanks to the -p flag.

The L and a values are hardcoded. This means that the code editor panes have to be connected to a session called a and one of those panes needs to be called L. Those names can be set with :rename-session a and :rename-client L commands, from Kakoune’s client.

After adding the plugin I can open the file in Kakoune by pressing ;k. This can be done in any terminal window.

Flexibility is the power of this setup. Each of those apps focuses on one thing but their design allows all of them to come together and provide a great experience. That’s the philosophy behind Linux - Do one thing and do it well. Flexibility comes from the fact that those apps stay in their lanes. If you want to swap the file browser you can easily do that. Kakoune’s developers didn’t waste their time implementing a subpar file browsing or windowing system. nnn developers didn’t waste their time implementing a subpar, built-in text editor.

This is just a simple example with quite hard constraints (hardcoded session’s and client’s names). Having constraints is ok. Every system has them. With this setup I feel like I can pick and choose where the system is flexible and where it’s rigid.

I’ve decided not to describe how ripgrep, fzf and ctags allows me to quickly navigate the codebase. I don’t want this post to be too dense. I encourage you to look those tools up. They are definately worth checking out.

I’m not saying this is THE way to go. We should experiment with workflows and tools if we want to provide a better user experience. I’ve never enjoyed using a computer for coding as much as I do with this setup.