project-switch

A multi-plat app launcher written in Rust · source · devlog

  • v0.10 - Inline math calculator

    Added an inline math calculator to the list command. Typing = followed by a math expression (like =5+3*2) evaluates it live in the autocomplete suggestions using meval. Integer results display without decimal places, and invalid expressions show an error inline. It’s a small quality-of-life feature that saves opening a separate calculator.

    Also fixed a CRLF line ending issue that was breaking the Docker build on Linux, added a .gitattributes to enforce LF endings, and deduplicated shortcuts that appeared multiple times from overlapping scan directories.

  • v0.9 - Cross-platform hotkey and macOS support

    Made the hotkey listener cross-platform — it now works on both macOS and Windows. On macOS, it uses Core Graphics event taps to listen for the ALT+SPACE combination and launches project-switch list in a new Terminal window. The platform-specific code is cleanly separated into platform/macos.rs and platform/windows.rs modules behind conditional compilation.

    Added macOS .app bundle scanning to the shortcuts feature. It scans /Applications and ~/Applications for .app bundles and surfaces them alongside Windows .lnk shortcuts in the autocomplete, using open -a to launch them.

    Fixed the hotkey to fire only on key press (not release), cleaned up the Docker build pipeline by removing bash as a dependency, and added cargo registry volume caching for the lint container.

  • v0.8.1 - Cross-compilation fixes and config auto-create

    Fixed the Docker cross-compilation pipeline to work on ARM hosts (like Apple Silicon Macs). The Dockerfile now uses conditional platform targeting so it can build Windows and Linux x86_64 binaries regardless of the host architecture. Added a build.sh script for non-Windows environments that skips the lint Docker image.

    Also added auto-creation of the config file — if ~/.project-switch.yml doesn’t exist when the program starts, it creates one with sensible defaults instead of erroring. Added cargo registry caching in the Docker build to speed up repeated builds.

  • v0.8 - File path autocomplete and fixes

    Added file path autocomplete to the list command. When you start typing a Windows path (drive letter like C:\ or a UNC path), the autocomplete switches to browsing the filesystem instead of matching commands. It auto-expands when there’s a single directory match and displays directories and files with distinct styling. This makes project-switch usable as a quick file/folder opener too.

    Fixed multi-word shortcut selection — shortcuts with spaces in their names weren’t being matched properly in the autocomplete. Also added a fallback so the list command works even without a current project set, showing only global commands and shortcuts.

    On the hotkey side, added logic to kill any prior project-switch-hotkey.exe instances on startup so you don’t end up with duplicate listeners, and the hotkey now creates a default config file if one doesn’t exist.

  • v0.7 - Config includes, system tray, and shortcut indexing

    Packed day with three big features landing.

    First, added config file includes. The main ~/.project-switch.yml can now specify an include path pointing to another YAML file, and the two get merged at load time. Project-specific commands overlay base commands by key, and projects merge by name. This lets me keep a shared config in a git repo and machine-specific overrides locally.

    Building on that, the hotkey listener now auto-syncs the included config’s git repo in the background — it does a git pull on startup and periodically, then stages, commits, and pushes any local changes. All git operations run with CREATE_NO_WINDOW so nothing flashes on screen.

    Second, migrated the hotkey listener from a plain console app to a proper Windows system tray application using tray-icon and muda. It shows a notification area icon with a right-click “Exit” menu, and runs with #![windows_subsystem = "windows"] so there’s no console window. Generated a simple icon using a Python script with Pillow.

    Third, added Windows shortcut (.lnk) indexing. The list command now scans configured shortcut directories and surfaces .lnk files alongside regular commands in the autocomplete. This means Start Menu shortcuts and other Windows shortcuts are searchable from the same prompt.

    Also added cargo fmt, clippy, and cargo audit as verification checks, committed Cargo.lock for reproducible builds, and wired the build script to restart the hotkey listener after a successful build.

  • v0.6 - ALT+SPACE hotkey listener

    Built a companion binary, project-switch-hotkey, that runs in the background on Windows and listens for a global ALT+SPACE hotkey. When triggered, it kills any running project-switch.exe instances and launches project-switch list in a new Windows Terminal tab. The idea is to make project switching feel as instant as an app launcher — press the hotkey from anywhere and you’re immediately in the selection prompt.

    The hotkey registration uses the Windows RegisterHotKey API with MOD_ALT | MOD_NOREPEAT, and the message loop polls with PeekMessageW on a 50ms sleep. It handles graceful shutdown via ctrlc and wraps the launched command in cmd /c ... & exit /b 0 so the terminal tab closes cleanly even when taskkill force-kills a prior instance.

    It’s a standalone crate under hotkey/ (not a workspace member) and gets cross-compiled through the existing Docker pipeline alongside the main binary.

  • v0.5 - Direct URL opening support

    Added the ability to type a URL directly into the list prompt or pass one to open, without needing it to be a configured command. If the input doesn’t match any command key but looks like a URL (starts with http, www., or matches a domain.tld pattern), it gets opened in the project’s configured browser. Bare domains like example.com automatically get an https:// prefix.

    Also deprecated the open command in favour of list — it’s now hidden from help output. The open command still works but includes the same URL fallback behaviour for backwards compatibility.

  • v0.4.1 - Autocomplete fixes and YAML preservation

    Fixed a couple of annoying issues with the list command’s autocomplete. Selecting a suggestion was inserting the full formatted string including ANSI colour codes and the URL arrow, so I added an ANSI stripping function and taught the completion handler to extract just the command key from highlighted suggestions. Also fixed input parsing to handle cases where the formatted suggestion text leaked into the user’s input.

    The other fix was around YAML field ordering. Previously, saving the config would re-serialise the entire struct which shuffled fields into alphabetical order — not great when you’ve carefully organised your config file. Now the config manager preserves the raw YAML on load and only patches the changed fields (currentProject, projects) back into it on save, keeping everything else untouched.

    Added skip_serializing_if annotations across the config structs so None fields and false defaults don’t clutter the output.

  • v0.4 - Command args, global commands, and browser profiles

    Big batch of enhancements to project-switch today. Added support for command arguments — you can now type extra text after a command key and it gets appended to the URL (with optional URL encoding via a url_encode config flag). This is great for search URLs where you want to pass a query string directly from the prompt.

    Introduced global commands in the config file. These are available across all projects and show up alongside project-specific commands in the list view, with project commands taking priority over globals when there’s a key collision.

    Browser profiles are now supported too. The browser string in the config can include arguments like firefox -P work, and the launcher correctly parses and passes those through on Windows, macOS, and Linux.

    Also reworked the list command to use inquire’s autocomplete instead of a select menu, so you can type to filter commands and append arguments inline. Commands that aren’t URLs are now treated as terminal commands and spawned directly in the shell.

  • v0.3 - List command and build script

    Added a list subcommand that shows an interactive menu of all openable items from the current project. It displays each command’s key alongside a truncated URL, and opens the selected item in the configured browser. This makes it much quicker to jump to project resources compared to remembering exact command keys for open.

    Extracted browser-opening logic into a shared utils::browser module since both open and list need it. Also fixed the browser launch on Windows — switched from cmd /C start to PowerShell’s Start-Process, which handles URLs with special characters more reliably.

    Created a build.ps1 script that stops any running project-switch.exe processes, cleans the bin/ folder, and runs the Docker cross-compilation build. Cleaned up docker-compose.yml to remove unused profiles and volumes.

  • v0.2 - Rewriting project-switch in Rust

    Rewrote project-switch entirely from TypeScript to Rust. The Node.js version had noticeable startup latency (~100-200ms) which felt sluggish for something that should be instant. The Rust version starts in under 5ms.

    Ported all four subcommands (switch, add, current, open) to Rust using clap for argument parsing, serde_yaml for config file handling, and inquire for interactive prompts. The config format stays the same — ~/.project-switch.yml with projects, commands, and browser overrides — so it’s a drop-in replacement.

    Set up a Docker-based cross-compilation pipeline that builds both Windows and Linux binaries from a single docker-compose up build command, using gcc-mingw-w64 for the Windows target.

  • v0.1 - Project switch genesis

    Started project-switch today — a CLI tool for quickly switching between project directories. I’ve been wanting a way to jump between repos without hunting through nested folders, and this scratches that itch.

    Built it as a TypeScript project with three subcommands: switch for fuzzy-matching a project name and changing to its directory, add for registering new projects interactively, and open for launching a project directly in VS Code. Configuration lives in a YAML file at ~/.project-switch.yml where each project has a name, path, and optional tags.

    The switch command presents an interactive list using enquirer when there are multiple matches, and jumps straight there on an exact match. The add command walks through prompts for name, path, and tags, with the path defaulting to the current directory. Spent a bit of time fixing a path resolution bug for the config file location.