- Instant popup — global hotkey brings up the launcher anywhere; appears on cursor's monitor by default, or fixed to
"primary"/ index viamonitorconfig - Fuzzy / exact / migemo / combined search — fuzzy via nucleo-matcher; migemo via rustmigemo / jsmigemo (type
hajimeto match初めて);fuzzy_migemo/exact_migemoreturn union of both - Status badges — subtle pill in the input corner shows current search mode (
≋/―/あ/≋あ/―あ) and sort order (#/⌚); click or use keybindings to cycle - Args mode — press
Tabto pass extra arguments; path / list / command completion with ghost text - Launch history — frecency sorting; previous args remembered and suggested as ghost text
- File preview panel —
Ctrl+Shift+ptoggles a side panel with Shiki syntax highlighting; scroll withCtrl+j/Ctrl+k - Slash commands —
/reload,/config,/update,/theme,/save,/resetand more - Auto-update — detects new releases on startup; one command to download, install, and restart
- Theming — built-in presets (Catppuccin, Nord, Dracula, Tokyo Night) + per-color overrides
- Movable window — drag the grip at the top of the launcher to reposition; save with
/save position - Cross-platform — Windows, macOS, Linux; portable zip with no admin rights required
Windows (PowerShell, no admin required)
irm https://yukimemi.github.io/shun/install.ps1 | iexmacOS / Linux
curl -fsSL https://yukimemi.github.io/shun/install.sh | shPackage managers & direct download
Windows — winget
winget install yukimemi.shunWindows — Scoop
scoop bucket add yukimemi https://github.com/yukimemi/scoop-bucket
scoop install yukimemi/shunmacOS — Homebrew
brew tap yukimemi/tap
brew install --cask yukimemi/tap/shunDirect download — latest installer from Releases:
| Platform | File |
|---|---|
| Windows | .msi (recommended) or shun-windows-x64.zip (portable, no admin required) |
| macOS | .dmg (universal — Apple Silicon + Intel) |
| Linux | .AppImage or .deb |
macOS note: shun's macOS builds are not Apple code-signed or notarized. After
/updatereplaces~/Applications/shun.app, shun strips thecom.apple.quarantineattribute from the new bundle before restarting itself so Gatekeeper doesn't silently block the automatic relaunch; if you ever need to do this by hand, runxattr -dr com.apple.quarantine ~/Applications/shun.app.
Works out of the box with zero config. Config file is created automatically on first launch:
| Platform | Path |
|---|---|
| Windows | %APPDATA%\shun\config.toml |
| macOS | ~/Library/Application Support/shun/config.toml |
| Linux | ~/.config/shun/config.toml |
Window corners: the launcher window is transparent and undecorated. Windows 11 rounds it natively; on macOS and Linux shun rounds the corners itself (10px). macOS uses Tauri's macos-private-api for real transparency. On Linux a compositor is required for transparency; without one the window stays square rather than showing black corners.
On Windows and macOS, if ~/.config/shun/config.toml already exists, it takes priority over the path above and is used instead (Linux already resolves to ~/.config/shun, so this has no effect there). This also determines where history.json (frecency data, see /history) is read from and written to, since it always lives alongside config.toml — switching to ~/.config/shun starts history fresh there rather than migrating entries from the old location.
A minimal config to get started (non-existent paths are silently ignored):
[keybindings]
launch = "Ctrl+Space" # global hotkey to show/hide
[[apps]]
name = "Neovide"
path = "neovide"
completion = "path"
# Windows
[[scan_dirs]]
path = "~/bin"
extensions = ["exe", "bat", "ps1", "cmd"]
# macOS / Linux
[[scan_dirs]]
path = "~/.local/bin"
extensions = ["sh", "py"]After editing config, run /reload to apply all changes without restarting.
| Command | Action |
|---|---|
/reload |
Reload config — re-registers global shortcut, rescans apps, re-applies all settings |
/config |
Open a config file (Tab to pick; delete_item key to delete; creates new config.*.toml if typed manually; via editor_command if set, else default editor) |
/theme <name> |
Switch theme for this session (Tab to pick; set in config.toml to persist) |
/update |
Install latest release (shows version if update available) |
/history |
Open history file (via editor_command if set, else default editor) |
/version |
Show current version |
/save |
Save a setting to config.local.toml (Tab to pick: monitor, position, theme, search_mode, sort_order) |
/reset |
Reset a setting in config.local.toml — falls back to config.toml or default (Tab to pick) |
/help |
Show keybindings & current status (theme, search mode, sort order) |
/exit |
Quit shun |
The tray icon's "Config" menu item opens config.toml the same way as /config (via editor_command if set, else default editor).
Full configuration reference
# Search mode: "fuzzy" (default) | "exact" | "migemo" | "fuzzy_migemo" | "exact_migemo"
search_mode = "fuzzy"
# Sort order: "count_first" (default) | "recent_first"
sort_order = "count_first"
# Auto-hide when the launcher loses focus
hide_on_blur = false
# Start automatically at login (default: true); set to false to disable
auto_start = true
# Linux only: write shun's .desktop entry + icon for AppImage (default: true).
desktop_integration = true
# Linux/niri only: opt in to shun-binds.kdl synchronization (default: false).
niri = false
# Update check interval in seconds (0 to disable)
update_check_interval = 3600
# Launcher window width in pixels (default: 620)
window_width = 620
# Max items shown in the results list (default: 8)
max_items = 8
# Max items shown in the completion dropdown (default: 6)
max_completions = 6
# Font size in pixels (default: 14)
# font_size = 14
# Window opacity 0.0–1.0 (default: 1.0)
# opacity = 1.0
# Maximum number of history entries to keep (default: 1000)
# history_max_items = 1000
# Status badge icon style: "unicode" (default) | "svg"
# icon_style = "unicode"
# Monitor to show the launcher on: "cursor" (default) | "primary" | 0 | 1 | ...
# Useful in config.local.toml for per-machine override
# monitor = "cursor"
# Saved window position (set via /save position; delete to reset to default centering)
# position_x = 960.0
# position_y = 300.0
# Preview panel: show when browsing files in args mode (default: true)
# preview_args = true
# Preview panel: show when navigating search results (default: true)
# preview_search = true
# Preview panel width in pixels (default: 400)
# preview_width = 400
# Max lines to load for file preview (default: 500)
# max_preview_lines = 500
# Note: preview panel height is fixed to max_items × item height when visible,
# so increasing max_items also increases the preview panel height.
# Editor command used by /config and /history (default: unset → OS file association)
# Space-separated command + args (no quoting support), e.g. "code -n" or "todoke"
# editor_command = "code -n"
[keybindings]
launch = "Ctrl+Space" # Global hotkey to show/hide
next = "Ctrl+n"
prev = "Ctrl+p"
confirm = "Enter"
arg_mode = "Tab"
accept_word = "Ctrl+f" # Accept next word/segment of ghost text
accept_line = "Ctrl+e" # Accept full ghost text
delete_word = "Ctrl+w" # Delete word before cursor
delete_line = "Ctrl+u" # Delete to beginning of line
run_query = "Shift+Enter" # Run typed query directly (skip history results)
close = "Escape"
delete_item = "Ctrl+d" # Delete selected history/config item
cycle_search_mode = "Ctrl+Shift+m" # Cycle search mode (fuzzy → exact → migemo)
cycle_sort_order = "Ctrl+Shift+o" # Cycle sort order (count_first ↔ recent_first)
toggle_preview = "Ctrl+Shift+p" # Toggle file preview panel
preview_scroll_down = "Ctrl+j" # Scroll preview panel down
preview_scroll_up = "Ctrl+k" # Scroll preview panel up
# Logging
[log]
level = "info" # "debug" | "info" (default) | "warn" | "error" | "off"
max_file_size_kb = 2048 # rotate when the active file passes this size (default: 2 MB)
rotation = "16" # number of rotated files to keep (default: 16) | "keep_one" | "keep_all"
retention_days = 30 # delete rotated logs older than this at startup (default: 30, 0 = never)
# Worst-case disk use with the defaults: 2 MB x (16 rotated + 1 active) = 34 MB.
# Each startup logs one `startup:` line (pid, parent process, TTY state, and a fixed allowlist of
# env vars: NO_COLOR, TERM, COLORTERM, XPC_SERVICE_NAME, SHELL, LANG) so you can tell how an
# instance was started; the update path logs `update:` lines for what it spawns.
# Theme
[theme]
preset = "catppuccin-mocha" # "catppuccin-mocha" | "catppuccin-latte" | "nord" | "dracula" | "tokyo-night" | "one-half-dark" | "solarized-dark" | "solarized-light"
# bg = "#1e1e2e"
# surface = "#313244"
# overlay = "#45475a"
# muted = "#585b70"
# text = "#cdd6f4"
# blue = "#89b4fa"
# purple = "#cba6f7"
# green = "#a6e3a1"
# red = "#f38ba8"
# Open editor with file path completion
[[apps]]
name = "Neovide"
path = "neovide"
completion = "path" # "path" | "none" | "list" | "command"
# Dedicated global hotkey: toggle Neovide's window without opening shun
[[apps]]
name = "Neovide (toggle)"
path = "neovide"
hotkey = "Ctrl+Alt+N"
hotkey_mode = "toggle" # "launch" (default) | "activate" | "toggle"
# Search Google (Tab → type query → Enter)
[[apps]]
name = "Google"
path = "https://www.google.com/search?q={{ args | urlencode }}"
# docker exec with container name completion
[[apps]]
name = "docker exec"
path = "docker"
args = ["exec", "-it"]
completion = "command"
completion_command = "docker ps --format '{{.Names}}'"
# git checkout with branch completion (exact search for this app)
[[apps]]
name = "git checkout"
path = "git"
args = ["checkout"]
completion = "command"
completion_command = "git branch --format='%(refname:short)'"
completion_search_mode = "exact" # "fuzzy" | "exact" | "migemo" (overrides global)
workdir = "~/src/myproject"
# Launch a registered custom protocol handler
[[apps]]
name = "Microsoft Teams"
path = "ms-teams:"
# Override completion for scan_dirs items
[[overrides]]
name = "scoop"
completion = "list"
completion_list = ["install", "uninstall", "update", "search", "info"]
# Open .xlsx files with LibreOffice Calc (ext-based override)
[[overrides]]
ext = "xlsx" # match by extension (no dot)
path = "C:/Program Files/LibreOffice/program/scalc.exe"
args = ["{{ file_path }}"] # pass original file path as arg
# Auto-register scripts from a directory
[[scan_dirs]]
path = "~/.local/bin"
recursive = false
extensions = ["sh", "py", "ps1", "cmd"][[overrides]] fields:
When several overrides match the same item, the last one wins (so config.local.toml entries beat config.toml). Overrides also apply to per-app global hotkeys ([[apps]].hotkey). On Windows, a bare path = "brave" that is not on PATH is resolved via the App Paths registry.
| Field | Type | Description |
|---|---|---|
name |
string | Case-insensitive stem name match (optional) |
ext |
string | Extension match without dot (e.g. "xlsx", "pdf"). ANDed with name when both are set — omit either to match on the other alone |
path |
string | Executable to launch. Supports {{ file_path }} and other override variables. On macOS, shun resolves your login shell's PATH once at first launch ($SHELL -lc 'echo $PATH') and uses it when spawning path/args — LaunchServices-launched GUI apps otherwise only get a minimal default PATH (/usr/bin:/bin:/usr/sbin:/sbin), so Homebrew/mise/asdf/cargo/npm-installed commands wouldn't resolve by bare name (applies one level down too: a command a launched terminal spawns inside itself also gets this PATH, since it's inherited). If this resolution ever fails (e.g. an unusual $SHELL), shun falls back to its own minimal inherited PATH and a bare command name would need an absolute path instead |
args |
string[] | Default arguments. Supports override template variables |
workdir |
string | Working directory |
completion |
string | "list" | "command" | "path" |
completion_list |
string[] | Candidates when completion = "list" |
completion_command |
string | Command to run for completion = "command" |
Each [[apps]] entry can register its own global hotkey, independent of keybindings.launch:
| Field | Type | Description |
|---|---|---|
hotkey |
string | Optional. A shortcut string parseable the same way as keybindings.launch (e.g. "Ctrl+Alt+N"). Omit to skip hotkey registration for this app |
hotkey_mode |
string | "launch" (default) | "activate" | "toggle" |
window_app |
string | Optional, all OSes. App/process name used to find the window for activate / toggle: executable name on Windows (without .exe), .app name on macOS (without .app), WM_CLASS on X11 / app_id on niri. Case-insensitive on Windows/Linux. Defaults to the file name (stem) of path; name is never used. Needed when path is a launcher stub, e.g. path = "wt" → window_app = "WindowsTerminal" |
window_title |
string | Optional, Windows/macOS/niri. Additionally require the window title to contain this string (case-insensitive) for activate / toggle. Distinguishes windows of the same app, e.g. a dedicated yazi window in Windows Terminal or WezTerm. On macOS this requires the Accessibility permission — shun shows the standard system prompt once (the first time a window_title hotkey fires, if not already granted); falls back to whole-app activate/toggle with a logged warning if it's denied or dismissed |
window_title_exclude |
string | Optional, Windows/macOS/niri. Skip windows whose title contains this string (case-insensitive) for activate / toggle. E.g. keep the F12 hotkey from grabbing a dedicated yazi window |
launch— starts a new process every time, same as pressing Enter on the item.activate— brings the app's window to the foreground if it's already running; launches it otherwise.toggle— minimizes the app's window if it's currently focused; otherwise same asactivate.
Conflict resolution: keybindings.launch always wins. Among [[apps]] entries, the first one
(in config file order) to claim a given key combination wins; later entries with the same
hotkey are skipped and logged as a warning (shown as a warning item in the launcher list, same
as other config warnings). Shortcut strings are compared after parsing, so "Ctrl+Alt+N" and
"ctrl+alt+n" are treated as the same key. Run /reload after editing hotkey / hotkey_mode
to re-register.
Reserved keys (Windows): some keys can't be registered via RegisterHotKey — notably a bare
F12, which Windows reserves for debuggers — or are already taken by another app. In that case
shun falls back to a low-level keyboard hook (the same technique AutoHotkey uses), so the hotkey
still works and the key press is not passed on to the focused app.
Platform support for activate / toggle:
| Platform | Behavior |
|---|---|
| Windows | Full support. Matches the app's own top-level window by executable file name (case-insensitive). If the app has multiple windows, only the first one found is operated on. Foreground-lock restrictions imposed by Windows may occasionally prevent focus-stealing (the taskbar icon flashes instead); this is logged as a warning, not an error |
| macOS | Uses NSWorkspace/NSRunningApplication (AppKit) directly for whole-app activate/toggle — no osascript/AppleScript, no Accessibility permission needed for that path. The target is window_app, or the file stem of path if unset (not name); matched against the app's localized display name or its .app file stem (locale-independent), so it works whether you write the English bundle name or it only matches your system's localized name. If the app is not running, shun launches path instead. toggle's "already frontmost" case (whole-app path) calls hide() (like ⌘H — hides the whole app's windows, but the app itself stays visible in the Dock/⌘Tab) rather than minimizing individual windows. If window_title is set, shun instead uses the Accessibility API (AXUIElement) to find and operate on that one specific window (AXRaise to bring it forward, the AXMinimized attribute to hide just it on toggle) without affecting the app's other windows. The first time such a hotkey fires without the Accessibility permission already granted, shun shows the standard system prompt (once per run — it won't nag on every press); denied or dismissed, it logs a warning and falls back to the whole-app behavior. Unlike Windows/Linux, a found-but-failed operation here does not fall back to launching (would duplicate an already-running app). Likewise, if the app is running but its window list cannot be read via Accessibility, shun retries once after ~150 ms and then does nothing (warning in the log; press the hotkey again) — it neither launches nor activates the whole app, since that would raise the wrong window while reporting success. This also applies to window_title_exclude-only entries, and an app whose Accessibility API never returns windows will do nothing for its window_title hotkey |
| Linux (niri) | Uses niri's IPC (niri msg): matches window_app (or the stem of path) as a case-insensitive substring of the window's app_id, plus window_title / window_title_exclude, and focuses the most recently focused match. toggle really toggles: if the target is already focused, focus returns to the previously focused window (niri has no minimize). Configure the keys in niri's own config — see "Command line & Linux / Wayland" |
| Linux (X11) | Requires wmctrl to be installed; matches window_app (or the stem of path) against the window's WM_CLASS via wmctrl -x -a (substring, case-insensitive, so short names can match the wrong app); falls back to launch if it's missing. toggle behaves the same as activate (no reliable way to detect focus without extra tooling), i.e. it never minimizes |
| Linux (other Wayland compositors) | Not supported: the global hotkeys can't be grabbed and windows can't be found, so activate / toggle fall back to launching. shun toggle / shun hotkey <KEY> (below) can be bound from the compositor's own config |
If a window can't be found or the platform is unsupported, shun falls back to launching a new
instance rather than doing nothing (macOS: a found app whose hide/activate operation itself
fails does not fall back to launching, to avoid spawning a duplicate of an app already running).
Known limitation (macOS, window_title): if an app is run more than once as separate OS
processes sharing the same underlying binary (e.g. wezterm-gui invoked directly as a bare
path/args command, rather than through its .app launch path, can sometimes register as a
second, independent process instead of reusing the already-running one's mux/session) — the
extra instance's window is still found and activated correctly, but macOS's own
"is this app in the foreground" signals (NSRunningApplication.isActive(),
AXFrontmostAttribute, even NSWorkspace.frontmostApplication()) can disagree with what's
actually on screen for that instance specifically. toggle then always activates rather than
minimizing on repeat presses for that one instance — a caveat, not a regression (finding and
focusing the window keeps working every time; only the "press again to hide it" half of toggle
doesn't fire in this specific case).
A second shun ... invocation is handed to the running instance (single-instance), so the hotkey
actions can be triggered from outside — from a compositor bind, a script, or a launcher:
| Command | Action |
|---|---|
shun / shun show |
Show the launcher (show also opens it when shun starts fresh) |
shun toggle |
Show / hide the launcher — what keybindings.launch does |
shun hotkey <KEY> |
Run the [[apps]] entry whose hotkey is <KEY> (as written in config.toml, e.g. shun hotkey F10); <KEY> equal to keybindings.launch toggles the launcher |
shun install-desktop |
Linux: write the .desktop entry and icon, then exit |
On Wayland, global keys belong to the compositor. shun does not initialize the X11 global-shortcut backend in a Wayland session, call the single-instance plugin's private D-Bus API directly, or reclaim focus after a blur. Compositor config is untouched by default; optional niri synchronization is described below. Windows, macOS, and native X11 continue to register shortcuts normally.
For example, add these to niri's config.kdl (replace the executable path with your installed
shun path, and the app hotkeys with those defined in config.toml):
binds {
Ctrl+Return repeat=false { spawn "/home/you/.local/bin/shun" "toggle"; }
F11 repeat=false { spawn "/home/you/.local/bin/shun" "hotkey" "F11"; }
F12 repeat=false { spawn "/home/you/.local/bin/shun" "hotkey" "F12"; }
}The normal single-instance plugin forwards these CLI commands to the resident shun.
This also starts shun if it is not running. AppImage invocations have startup overhead; this
deliberately uses the normal CLI rather than maintaining a separate fast-path transport.
For other Wayland compositors, bind the same commands using their native configuration syntax.
By default, /reload reloads shun's config, not the compositor's keys.
Optional niri synchronization (off by default)
To derive the niri keys from shun's config, set this at the top level of shun's config.toml:
niri = trueKeep the existing top-level include in niri's config.kdl, or add it once if it is absent:
include "shun-binds.kdl"No begin/end markers are needed. On startup and /reload, shun replaces only the dedicated
shun-binds.kdl file beside that config with bindings derived from keybindings.launch and
the valid [[apps]].hotkey entries. The main config.kdl and its include line are never edited.
Changing an app's hotkey in shun and running /reload therefore updates niri's key too.
The generated actions invoke the normal shun toggle / shun hotkey <KEY> CLI directly,
without a shell. niri live-reloads the updated include.
- The main config is
$NIRI_CONFIG, or the XDG config directory'sniri/config.kdl(normally~/.config/niri/config.kdl). Synchronization only runs in a niri session. - Exactly one active top-level
include "shun-binds.kdl"is required. Missing or duplicate includes, invalid config, or an unsupportedkeybindings.launchkey mapping cause a log warning and no write. An unsupported[[apps]].hotkeykey mapping is skipped with a warning; the launcher binding and other valid app bindings are still synchronized. shun never adds include lines automatically. shun-binds.kdlis entirely generated: keep hand-written bindings in the main config or another include. If the dedicated file is missing, shun creates it after validation.- Before replacing the binds file, shun stages both the new bindings and a temporary main-config
copy pointing at them, then runs
niri validate. Validation failure or detected edits to either live file leave the existing files untouched; no invalid intermediate binds are installed. - Config symlinks and existing binds-file permissions are preserved. Unchanged generated content does not rewrite the file. Cross-file binding overrides follow niri's own include precedence rather than a separate shun conflict policy.
niri = falsestops synchronization without changing existing binds. To remove synchronized shortcuts, remove the include line yourself before deleting its file.desktop_integrationis independent of this setting.
Existing generated niri binds: if an older shun already added
include "shun-binds.kdl", keep it and set niri = true to regenerate that same dedicated file
using the normal CLI path. No markers or main-config migration are required. Alternatively,
keep synchronization off and manage the included file yourself.
Desktop entry (AppImage) — the AppImage ships no .desktop file, so app launchers don't list
it. On start shun writes ~/.local/share/applications/shun.desktop (running <AppImage> show) and
an icon, and keeps its Exec path current if you move the AppImage. An entry you edited by hand
(remove the # Generated by shun line) is never touched. desktop_integration = false disables
this automatic desktop entry/icon installation; it does not control compositor shortcuts.
Any config.*.toml files in the same directory as config.toml are loaded and merged in alphabetical order after the base config. Useful for per-machine or per-context overrides with chezmoi or other dotfile managers.
Examples: config.local.toml, config.work.toml, config.home.toml
| Platform | Directory |
|---|---|
| Windows | %APPDATA%\shun\ |
| macOS | ~/Library/Application Support/shun/ |
| Linux | ~/.config/shun/ |
On Windows and macOS, this directory follows config.toml's own resolution: if ~/.config/shun/config.toml exists, override files (config.local.toml, config.*.toml) are also read from ~/.config/shun/ instead of the path above, not both.
Merge rules:
| Field | Behavior |
|---|---|
apps, scan_dirs, overrides |
Entries are appended |
search_mode, sort_order, hide_on_blur, auto_start, font_size, opacity, monitor, etc. |
Local value overrides (only when explicitly set) |
[keybindings], [theme], [log] |
Per-field override — only specified keys are overridden |
Template placeholders in path and args
You can use Tera template syntax in path, args, and most other string fields. {{ vars.* }}, {{ env.* }}, and {{ os }} are resolved once when the config loads (so they work in hotkey, name, workdir, enum fields, etc. too); {{ args }} / {{ args_list }} / {{ file_* }} are only resolved at launch time for path, args, and workdir (when args are provided via Args mode (Tab) or an override matches) — any string referencing them, including inside {% if %}, is left untouched by the load-time pass. Plain {% if %}...{% endif %} control blocks with no {{ }} inside are expanded the same way as {{ }} placeholders.
Context variables:
| Variable | Value |
|---|---|
{{ args }} |
Extra args joined by space (raw) |
{{ args_list }} |
Extra args as an array |
{{ env.VAR_NAME }} |
Environment variable |
{{ vars.my_var }} |
User-defined variable from [vars] in config |
{{ os }} |
Current OS: "windows" | "macos" | "linux" |
Override-only variables (available in [[overrides]] path, args, and completion_list when a scanned file is matched — expose the original file's path information for use in templates):
| Variable | Value |
|---|---|
{{ file_path }} |
Full path of the matched file |
{{ file_name }} |
Filename with extension (e.g. report.xlsx) |
{{ file_stem }} |
Filename without extension (e.g. report) |
{{ file_ext }} |
Extension only (e.g. xlsx) |
{{ file_dir }} |
Parent directory path |
Useful Tera expressions:
| Expression | Result |
|---|---|
{{ args | urlencode }} |
URL-encoded (spaces → %20) |
{{ now() | date(format="%Y%m%d") }} |
Today's date e.g. 20260321 |
{{ now() | date(format="%Y-%m-%d") }} |
Today's date e.g. 2026-03-21 |
{{ get_env(name="VAR", default="fallback") }} |
Env var with default |
Example — Web search:
[[apps]]
name = "Google"
path = "https://www.google.com/search?q={{ args | urlencode }}"Open shun → goo → Tab → rust borrow checker → Enter → opens the search. Query is saved to history.
Example — Date-stamped memos:
# MemoNew: Tab → type title → Enter → opens "20260321-title.md"
[[apps]]
name = "MemoNew"
path = "nvim"
args = ['~/memo/{{ now() | date(format="%Y%m%d") }}-{{ args }}.md']
completion = "none"
# MemoList: Tab → pick existing memo with migemo path completion → Enter
# (type "hajime" to find "初めて.md")
[[apps]]
name = "MemoList"
path = "nvim"
args = ["~/memo/{{ args }}"]
completion = "path"
completion_search_mode = "migemo"Example — User-defined variables:
[vars]
src_dir = "~/src/github.com/yourname"
[[apps]]
name = "Open Project"
path = "neovide"
args = ["{{ vars.src_dir }}/{{ args }}"]
completion = "path"Example — OS-conditional app (one shared config.toml across machines):
# path picked per OS; only the matching OS gets a hotkey.
# ({% if %} with no {{ }} inside still expands. On a non-matching OS
# the hotkey renders to an empty string and is simply not registered —
# silently, with no warning. Only a non-empty but unparseable shortcut
# warns, so a typo'd OS name here just means "no hotkey anywhere".)
[[apps]]
name = "Terminal"
path = "wt"
window_app = "WindowsTerminal" # wt.exe is a stub; the window belongs to WindowsTerminal.exe
hotkey = "{% if os == \"windows\" %}F12{% endif %}"
hotkey_mode = "toggle"
[[apps]]
name = "WezTerm"
path = "wezterm-gui"
window_app = "WezTerm"
window_title_exclude = "yazi" # never grab the dedicated yazi window below
hotkey = "{% if os == \"macos\" %}F12{% endif %}"
hotkey_mode = "toggle"
# A dedicated yazi window in WezTerm: window_title picks it out from the
# main terminal window above by title substring (yazi's own terminal title
# contains "yazi"), so repeated presses reuse it instead of piling up new
# windows. Uses `activate`, not `toggle` — you generally want a launcher
# shortcut like this to just bring the window forward every time, not hide
# it away on alternate presses (use `toggle` for the "everyday shell"
# hotkey above instead, where hiding it out of the way is the point).
[[apps]]
name = "yazi (WezTerm)"
path = "wezterm-gui"
args = ["start", "--", "yazi"]
window_app = "WezTerm"
window_title = "yazi"
hotkey = "{% if os == \"macos\" %}Ctrl+F10{% endif %}"
hotkey_mode = "activate"Example — args_list for multi-argument commands:
# git diff: Tab → "main feature-branch" → Enter → runs "git diff main feature-branch"
[[apps]]
name = "git diff"
path = "git"
args = ["diff", "{{ args_list.0 }}", "{{ args_list.1 | default(value='HEAD') }}"]
completion = "command"
completion_command = "git branch --format='%(refname:short)'"
# ripgrep with comma-separated globs: Tab → "*.rs *.toml" → passes as separate args
[[apps]]
name = "rg"
path = "rg"
args = ["--glob={{ args_list | join(sep=',') }}"]Theming
Switch theme for the current session with /theme <name> (Tab to pick). To persist across restarts, set it in config.toml:
[theme]
preset = "nord"
bg = "#1a1a2e" # optional per-color overrideBuilt-in presets:
| Preset | Description |
|---|---|
catppuccin-mocha |
Default — dark, muted pastel |
catppuccin-latte |
Light variant of Catppuccin |
nord |
Arctic, blue-toned dark |
dracula |
Dark with vibrant purple/pink |
tokyo-night |
Dark blue-purple, Tokyo Night style |
one-half-dark |
One Half Dark (as used in VS Code / Windows Terminal) |
solarized-dark |
Solarized Dark |
solarized-light |
Solarized Light |
Color keys (all optional — unset keys fall back to the preset):
| Key | Role |
|---|---|
bg |
Launcher background |
surface |
Selected item background / border |
overlay |
Ghost text / separator / count |
muted |
Placeholder / source labels |
text |
Main text |
blue |
Accent — dirs, URLs, args app name |
purple |
Slash commands |
green |
Path items |
red |
History items |
Keybindings reference
All keybindings are configurable via [keybindings] in config.toml. Changes take effect after /reload — no restart needed.
- In-app keys use KeyboardEvent.key values
- The global
launchshortcut uses global-hotkey key codes
Search mode (default keys):
| Key | Action |
|---|---|
Ctrl+Space |
Show / hide launcher |
Ctrl+n / ↓ |
Next item |
Ctrl+p / ↑ |
Previous item |
Enter |
Launch selected item |
Shift+Enter |
Launch typed query directly (skip history results) |
Tab |
Enter args mode |
Ctrl+f |
Accept next word of ghost text |
Ctrl+e |
Accept full ghost text |
Ctrl+w |
Delete word before cursor |
Ctrl+u |
Delete to beginning of line |
Ctrl+d |
Delete selected history item |
Ctrl+Shift+m |
Cycle search mode (fuzzy → exact → migemo) |
Ctrl+Shift+o |
Cycle sort order (count_first ↔ recent_first) |
Ctrl+Shift+p |
Toggle file preview panel |
Ctrl+j |
Scroll preview panel down |
Ctrl+k |
Scroll preview panel up |
Escape |
Hide launcher |
Args mode (default keys):
| Key | Action |
|---|---|
Enter |
Launch with args |
Shift+Enter |
Launch typed query directly |
Tab |
Apply selected completion |
Ctrl+n / Ctrl+p |
Navigate completion list |
Ctrl+f / Ctrl+e |
Accept ghost text (word / full) |
Ctrl+w |
Delete word before cursor |
Ctrl+u |
Delete to beginning of line |
Ctrl+d |
Delete selected history completion |
Ctrl+Shift+m |
Cycle search mode |
Ctrl+Shift+o |
Cycle sort order |
Ctrl+Shift+p |
Toggle file preview panel |
Ctrl+j / Ctrl+k |
Scroll preview panel down / up |
Escape |
Back to search |
Special input:
| Input | Action |
|---|---|
scheme:... (http/https/mailto/ms-excel, etc.) |
Open with the OS default protocol handler |
~/..., C:/... |
Browse filesystem, open in file manager |
URL schemes must contain at least two characters, and URL input must not contain whitespace (encode spaces as %20). Windows drive paths and shell: folders remain filesystem inputs.
Building from source
# Prerequisites: Node.js, Rust
git clone https://github.com/yukimemi/shun
cd shun
npm install
npm run tauri dev # development
npm run tauri build # production buildshun's migemo feature is powered by the following libraries and data by oguna:
| Component | Role | License |
|---|---|---|
| rustmigemo | Rust migemo engine | MIT |
| jsmigemo | JavaScript migemo engine | MIT |
| yet-another-migemo-dict | Bundled compact dictionary (Mozc + UniDic based) | BSD-3-Clause |
Full license texts are in the NOTICE file.
MIT
