A simple project switcher for Kakoune

Source: bhoot.org
22 points by bhoot a day ago on lobsters | 5 comments

I always appreciate a project-switching interface in my editor.

I didn't expect Kakoune, a barebones terminal editor, to have a built-in project switcher. But, after executing cd $project_dir; kak for the nth time, I started looking for a solution.

How other editors implement a project switcher

VS Code provides a project switcher through Open Recent action, bound to Ctrl + R on Linux. In my experience, any file or folder opened with VS Code's Open File or Open Folder dialogs show up here. So its a bit of a stretch to call it a project switcher if you frequently open files directly.

The following video snippet demonstrates VS Code's switcher:

JetBrains IDEs offer an unbounded Open Recent Project action, which opens the switcher at the tip of the cursor. though I use a FrameSwitcher plugin which performs the job better.

The following video snippet demonstrates WebStorm's switcher:

Elements of a project switcher

I identify two essential elements of a project switcher interface:

  1. Display a list of projects already registered with the interface, through which one can fuzzy find and switch to a project. Once switched, the selected project's directory becomes the current directory within the editor's context, so that editor actions happen in the selected project directory's context. So, for instance, a file finder would now look for a file within the selected project's directory. And so on.
  2. Add a new project to the interface. Usually this is automatic. When user opens a new project in JetBrains IDEs or VS Code, the editor automatically register the project to the switcher interface.

How I manifested the elements in Kakoune

Kakoune's scripting primitives, combined with its philosophy to use shell scripting to extend itself, were adequate tools to build a simple project switcher.

I needed a place to persistently register opened project directories in Kakoune. I chose a plaintext projects file that sits besides kakoune's config file ~/.config/kak/kakrc. Each line in projects store a path to a project dir.

Registering a new project with the interface

I wrote a Kakoune command :project-add that:

  1. lets user choose a directory from their filesystem, using Kakoune's path completion interface
  2. appends the path of the chosen directory in projects file
  3. uses Kakoune's :change-directory primitive to set the chosen directory as Kakoune's working directory

Here is the implementation:

define-command project-add \
  -docstring "Add a project" \
  -params 1 %{
    nop %sh{ printf "%s\n" "$1" >> ~/.config/kak/projects }
    change-directory %arg{1}
    exec ":file-picker "
  }

complete-command project-add file

Switching to a registered project

I implemented a Kakoune command :project-pick that:

  1. lists the paths stored in projects file using Kakoune's completion interface. The completion interface also sorts and excludes duplicate entries.
  2. sets the working directory of Kakoune to the selected path using its :change-directory primitive

Here is the implementation:

define-command project-pick \
  -docstring "Pick a project" \
  -params 1 %{
    change-directory %arg{1}
    exec ":file-picker "
  }

complete-command project-pick \
  shell-script-candidates \
  %{ cat ~/.config/kak/projects | sort | uniq }

Bonus - file picker

Both :project-add and :project-pick commands lands the user on a file picker interface, which again builds on top of Kakoune's completion interface, so that they can start working right away on their project.

define-command file-picker \
  -docstring "Pick a file from current directory" \
  -params 1 %{ edit %arg{1} }

complete-command file-picker \
  shell-script-candidates \
  %{ git ls-files -c -o --exclude-standard }

Bonus - keybindings

Both the commands received their own keybindings for quick execution. Kakoune offers custom user modes which can act as namespaces to neatly tuck away related keybindings. I created a project user mode to do just that for my project switcher.

# Define project user mode
declare-user-mode project

map global project p ":project-pick " \
  -docstring "Pick a project"

map global project a ":project-add " \
  -docstring "Add a project"

# Now tie up project mode to the global user mode
# so that project mode becomes a submode to the
# global user mode
map global user p ":enter-user-mode project<ret>" \
  -docstring "Project commands"

Now, Space + p + a triggers :project-add, while Space + p + p triggers :project-pick.

In closing

The video snippet below showcases the final result. I apologise for a cropped Kakoune's modeline in the video. I don't know why that happened, and I can't be bothered with making another video since the cropped modeline is still comprehensible.

I could implement guardrails like checking for existence of the projects file, or add a :project-delete command, but I'm satisfied with the current state. Any comments are welcome.