惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
博客园_首页
罗磊的独立博客
云风的 BLOG
云风的 BLOG
J
Java Code Geeks
Last Week in AI
Last Week in AI
D
DataBreaches.Net
Jina AI
Jina AI
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
Apple Machine Learning Research
Apple Machine Learning Research
V
V2EX
D
Docker
MongoDB | Blog
MongoDB | Blog
B
Blog RSS Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
Engineering at Meta
Engineering at Meta
The Cloudflare Blog
博客园 - 三生石上(FineUI控件)
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
H
Help Net Security
T
The Blog of Author Tim Ferriss

Lobsters

CIFSwitch: a non-universal Linux local root vulnerability RIPE NCC session fixation: poaching logins with an Atlas probe GNOME 2.20 but its Web Components Agentic Search for Context Engineering – Leonie Monigatti Garnix is shutting down [not OC] akashina.tngl.sh/jjc Concerning Emacs (and Jazz) Nitpicking the shell history scene in ‘Tron: Legacy’ What's cooking on SourceHut? Q2 2026 The tenth OpenPGP email summit Package managers that package package managers Clojure on Fennel part three: parsing WordPress at 23 Finding Miscompiles for Fun, Not Profit GitHub - creusot-rs/creusot: Creusot helps you prove your Rust code is correct. Announcing Rust 1.96.0 | Rust Blog A Love Letter to Neovim sqlite AGENTS.md Am I a Bad Friend? CSS vs. JavaScript • Josh W. Comeau Erlang Ecosystem Foundation - Supporting the BEAM community A brief note about slot access cost in Common Lisp Keyboard latency probe Rethinking the GNOME clipboard issues Back to the Building Blocks’ Building Blocks Tech Notes: Theseus: translating win32 to wasm Fast is better than slow Content-addressed Rust builds (or, what kache actually caches) Intent to Prototype: Embedding API Canada’s Bill C-22 and the security cost of collecting more data
A simple project switcher for Kakoune
Jayesh Bhoot's Ghost Town · 2026-05-31 · via Lobsters

Table of contents

  1. Table of contents
  2. How other editors implement a project switcher
  3. Elements of a project switcher
  4. How I manifested the elements in Kakoune
    1. Registering a new project with the interface
    2. Switching to a registered project
  5. Bonus - file picker
  6. Bonus - keybindings
  7. In closing

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.