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

推荐订阅源

V
Vulnerabilities – Threatpost
月光博客
月光博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
Last Week in AI
Last Week in AI
Jina AI
Jina AI
P
Privacy International News Feed
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
T
Tenable Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
P
Privacy & Cybersecurity Law Blog
人人都是产品经理
人人都是产品经理
S
Schneier on Security
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
C
Cisco Blogs
K
Kaspersky official blog
C
Cybersecurity and Infrastructure Security Agency CISA
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
O
OpenAI News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
博客园 - 【当耐特】
腾讯CDC
V
V2EX
A
Arctic Wolf
Webroot Blog
Webroot Blog
S
Securelist
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
量子位
J
Java Code Geeks
博客园 - 叶小钗
S
SegmentFault 最新的问题
Project Zero
Project Zero
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Scott Helme
Scott Helme
Cyberwarzone
Cyberwarzone

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 5 PostgreSQL locking behaviors that trip people up okmij.org Stop advertising in your commits! | AksDev GitHub - mplsllc/macsurf: A modern web browser for Classic Mac OS 9 PowerPC. Real CSS3, ES5 JavaScript, native HTTPS — built with CodeWarrior on the Carbon API. Introducing DoomBench - Can Your Data Stack Run DOOM? What are some of your favourite developer tools? Building a Scalable Ingestion Pipeline with Temporal (Part 1) Converting shallow Git bundles into normal repositories Are you a member of any professional associations? What is a harmonic? An interactive comic about additive synthesis How Virtual Tables Work in the Itanium C++ ABI Using SwiftUI to Build a Mac-assed App in 2026 Rust (and Slint) on a jailbroken Kindle. ~jack/lambda-on-lambda - Serverless Haskell on AWS - sourcehut git Human proof for FOSS contributions Extremely simple internet radio controlled via IRC Announcing BABLR Splitting Konsole views from Helix to run tools | AksDev GitHub - yugr/rust-slides Serving files over HTTP three ways: synchronous, epoll, and io_uring update docs with information about building with build.py (#979) · astral-sh/python-build-standalone@c9c40c5 A Simple Makefile Tutorial On C extensions, portability, and alternative compilers Switching to Colemak | Pedro Alves Just How Bad Was The Intel IAPX432? Nix's Substituter List Is Not a Routing Table Accelerating copy_if using SIMD Lambda on Lambda: Serverless Haskell on AWS | Blog Announcing feed-repeat v1.0 Scaling Akvorado BMP RIB with sharding EYG news: A host of CLI improvements, new guides and new effects The social contract of writing JS Crossword C array types are weird; and related topics Flatpak will depend on systemd – OSnews Migrating from Go to Rust | corrode Rust Consulting A portentous reunion Vivado Licensing Options How my minimal, memory-safe Go rsync steers clear of vulnerabilities the entropy layer of a wavelet codec, on its own GitHub - nferhat/fht-compositor: A dynamic tiling Wayland compositor. Debian SE Linux and PinTheft Does bulk memmove speed up std::remove_if? (No.) 声明式部分更新 | Blog | Chrome for Developers Fully in-browser container builds Dianne Skoll's Web Site - Remind The Architecture of Open Source Applications (Volume 1)Berkeley DB Pardon MIE? - ironPeak Blog “Long-Term Support” doesn’t mean what you think Jira IS Turing-Complete May I recommend thinking of Emacs as your Fortress of Solitude hershey Floodgap Gopher-HTTP gateway gopher://thelambdalab.xyz/1cuneiforth/ HP QuickWeb, Singular And Pointless That one time I used Go panics for flow control A new suite of modern tools coming for editing and publishing RFCs From the Tabletop… The Digital Antiquarian Building a Host-Tuned GCC to Make GCC Compile Faster Are we self-sovereign PKI yet? Claw Patrol: an open-source security firewall for agents | Deno Revised^7 Report on Scheme, Large: Procedural Fascicle Draft is now public A Network Allow-List Won't Stop Exfiltration — André Graf From AFSK to Goertzel – µArt.cz Software For My New Home Server Introducing Neptune: Direct3D virtualization for QEMU AI Agent Bankrupted Their Operator While Trying to Scan DN42 - Lan Tian @ Blog mimalloc: A new, high-performance, scalable memory allocator for the modern era Making wl_shm fast The Soul of Maintaining a New Machine - Third Draft | Books in Progress What is Git made of?
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.