Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 194 additions & 2 deletions bin/omarchy-default-editor
Original file line number Diff line number Diff line change
@@ -1,23 +1,208 @@
#!/bin/bash

# omarchy:summary=Set the default editor used by omarchy-launch-editor
# omarchy:summary=Set the default editor used by Omarchy and XDG handlers
# omarchy:args=[code|cursor|zed|sublime_text|helix|vim|emacs|nvim]
# omarchy:examples=omarchy default editor | omarchy default editor code | omarchy default editor helix

# Desktop entry that owns each supported editor. Kept in sync with
# test/shell.d/default-apps-test.sh.
editor_desktop_ids=(
code:code.desktop
cursor:cursor.desktop
zeditor:dev.zed.Zed.desktop
sublime_text:sublime_text.desktop
helix:helix.desktop
vim:vim.desktop
emacs:emacs.desktop
nvim:nvim.desktop
)

# MIME types the editor becomes the XDG default for. Kept in sync with the
# text handlers Omarchy's packaged system mimeapps.list ships for nvim.
editor_text_mimes=(
text/plain
text/english
text/x-makefile
text/x-c++hdr
text/x-c++src
text/x-chdr
text/x-csrc
text/x-java
text/x-moc
text/x-pascal
text/x-tcl
text/x-tex
text/x-c
text/x-c++
application/xml
text/xml
)

# MIME list as a single semicolon-separated string for the MimeType= line of
# desktop overrides.
mime_override_list=(
"text/plain;text/english;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;text/x-c;text/x-c++;application/xml;text/xml"
)

# Editors whose packaged desktop entry does not declare the text/* MIME types,
# so a per-user desktop override must add them for XDG to honour the
# association. nvim, vim, helix and emacs ship Terminal=true entries that
# already work from a file manager.
xdg_overrides=(
code.desktop
cursor.desktop
dev.zed.Zed.desktop
sublime_text.desktop
)

# Every desktop entry Omarchy can set as the default editor. Used when
# rewriting the user mimeapps defaults so an earlier Omarchy selection cannot
# shadow the new one.
all_editor_desktops=(
code.desktop
cursor.desktop
dev.zed.Zed.desktop
sublime_text.desktop
helix.desktop
vim.desktop
emacs.desktop
nvim.desktop
)

installing=false
if [[ ${1:-} == "--install" ]]; then
installing=true
shift
fi

editor_file="$HOME/.local/state/omarchy/defaults/editor"
mimeapps="$HOME/.config/mimeapps.list"

if (($# == 0)); then
default_editor() {
local editor
if [[ -f $editor_file ]]; then
read -r editor <"$editor_file"
fi

[[ -n $editor ]] && echo "$editor" || echo "nvim"
}

xdg_desktop_id() {
local selection=$1
local entry
for entry in "${editor_desktop_ids[@]}"; do
if [[ ${entry%%:*} == "$selection" ]]; then
echo "${entry#*:}"
return 0
fi
done
return 1
}

# Rewrite the user's mimeapps.list [Default Applications] section so every
# text MIME type the editor owns maps to the new desktop entry. Other keys and
# sections are preserved verbatim, including the [Added Associations] list.
sync_xdg_defaults() {
local desktop_id=$1
mkdir -p "$(dirname "$mimeapps")"
[[ -f $mimeapps ]] || touch "$mimeapps"

local text_mimes=${editor_text_mimes[*]}
local tmp="$mimeapps.tmp"
ED_DESKTOP_ID="$desktop_id" ED_MIMES="$text_mimes" python3 - "$tmp" "$mimeapps" <<'PY'
import os
import sys

tmp, path = sys.argv[1:3]
desktop_id = os.environ["ED_DESKTOP_ID"]
mimes = os.environ["ED_MIMES"].split()
mime_set = set(mimes)

lines = open(path, encoding="utf-8").read().splitlines()
sections = []
cur_name = None
cur_body = None
for line in lines:
stripped = line.strip()
if stripped.startswith("[") and stripped.endswith("]"):
cur_name = stripped[1:-1]
cur_body = []
sections.append((cur_name, cur_body))
continue
if cur_body is not None:
cur_body.append(line)

out = []
for name, body in sections:
out.append(f"[{name}]")
if name == "Default Applications":
for mime in mimes:
out.append(f"{mime}={desktop_id}")
for line in body:
key = line.split("=", 1)[0].strip()
if key not in mime_set:
out.append(line)
else:
out.extend(body)

if not sections:
# No file or no sections: create one so the file stays a valid mimeapps.list.
out.append("[Default Applications]")
for mime in mimes:
out.append(f"{mime}={desktop_id}")

with open(tmp, "w", encoding="utf-8") as fh:
fh.write("\n".join(out) + "\n")
PY
mv "$tmp" "$mimeapps"
}

# Copy a packaged desktop entry into the user's applications directory with
# the text/* MIME types added to its MimeType line, so XDG accepts the
# association even though the packaged entry does not declare them. No-op when
# the packaged entry already declares them or the source cannot be found.
install_xdg_override() {
local desktop_id=$1
local user_apps="${XDG_DATA_HOME:-$HOME/.local/share}/applications"
local target="$user_apps/$desktop_id"
local source="$target"
local system_apps="/usr/share/applications/$desktop_id"

if [[ -f $system_apps ]]; then
source=$system_apps
elif [[ ! -f $source ]]; then
return 0
fi

if grep -q "^MimeType=.*text/plain" "$source" && [[ $source == "$target" ]]; then
return 0
fi

mkdir -p "$user_apps"
ED_MIMES="${mime_override_list[0]}" python3 - "$source" "$target" <<'PY'
import os
import sys

source, target = sys.argv[1:3]
mimes = os.environ["ED_MIMES"]
out = []
found = False
with open(source, encoding="utf-8") as fh:
for line in fh:
if line.startswith("MimeType=") and not found:
line = line.rstrip("\n").rstrip(";") + ";" + mimes + "\n"
found = True
out.append(line)
if not found:
out.append("MimeType=" + mimes + "\n")

with open(target, "w", encoding="utf-8") as fh:
fh.writelines(out)
PY
}

if (($# == 0)); then
default_editor
exit 0
fi

Expand Down Expand Up @@ -56,4 +241,11 @@ fi
mkdir -p "$(dirname "$editor_file")"
printf '%s\n' "$editor" >"$editor_file"

desktop_id=$(xdg_desktop_id "$editor")
sync_xdg_defaults "$desktop_id"

if [[ " ${xdg_overrides[*]} " == *" $desktop_id "* ]]; then
install_xdg_override "$desktop_id"
fi

omarchy-notification-send -g $glyph "$name is now the default editor"
5 changes: 5 additions & 0 deletions manual/18-development-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Omarchy ships with [Neovim](https://neovim.io/) by default, but if you'd like so
Theme matching is offered for `VSCode`, `Cursor`, `VSCodium`, and `Helix`.

You can set the system-wide default editor under `Setup > Defaults > Editor`.
The selection is applied everywhere: CLI tools honor it through `$EDITOR`, and
text files opened from the file manager or launcher open in the chosen editor.
For GUI choices such as VSCode, Cursor, Zed and Sublime Text, Omarchy also
registers the editor as the XDG handler for text files so those open in the
GUI app instead of a terminal editor.

## Environment

Expand Down
36 changes: 36 additions & 0 deletions test/shell.d/default-apps-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export OMARCHY_TEST_TERMINAL_LOG="$terminal_log"
export OMARCHY_TEST_NOTIFICATION_LOG="$notification_log"
export OMARCHY_TEST_SETUP_LOG="$setup_log"
export OMARCHY_TEST_BROWSER_FILE="$browser_file"
# The editor XDG sync writes to the user applications directory. Pin it to the
# test home so the assertion is deterministic regardless of the ambient
# XDG_DATA_HOME.
unset XDG_DATA_HOME

assert_missing_opens_installer() {
local type=$1
Expand Down Expand Up @@ -297,6 +301,38 @@ for entry in "${editor_cases[@]}"; do
done
pass "editor defaults install every missing editor before selection"

test_home_mimeapps="$test_home/.config/mimeapps.list"
test_home_apps="$test_home/.local/share/applications"

omarchy-default-editor code
grep -Fxq "text/plain=code.desktop" "$test_home_mimeapps" ||
fail "code default maps text/plain to code.desktop in user mimeapps"
grep -Fxq "text/x-c=code.desktop" "$test_home_mimeapps" ||
fail "code default maps text/x-c to code.desktop in user mimeapps"
grep -Fxq "text/xml=code.desktop" "$test_home_mimeapps" ||
fail "code default maps text/xml to code.desktop in user mimeapps"
[[ -f $test_home_apps/code.desktop ]] ||
fail "code default installs a user desktop override for code.desktop"
grep -q "^MimeType=.*text/plain" "$test_home_apps/code.desktop" ||
fail "code user desktop override declares the text/plain MIME type"
pass "editor default syncs XDG text handlers to the selected GUI editor"

omarchy-default-editor nvim
grep -Fxq "text/plain=nvim.desktop" "$test_home_mimeapps" ||
fail "switching to nvim maps text/plain to nvim.desktop in user mimeapps"
grep -Fxq "text/x-c=nvim.desktop" "$test_home_mimeapps" ||
fail "switching to nvim maps text/x-c to nvim.desktop in user mimeapps"
pass "switching editor default rewrites XDG text handlers"

printf '[Default Applications]\ntext/plain=code.desktop\n\n[Added Associations]\ntext/plain=code.desktop;\n' > "$test_home_mimeapps"
omarchy-default-editor nvim
grep -Fq "[Added Associations]" "$test_home_mimeapps" ||
fail "switching editor preserves the Added Associations section"
grep -Fq "text/plain=code.desktop;" "$test_home_mimeapps" ||
fail "switching editor keeps previous editor associations in Added Associations"
pass "switching editor default preserves the Added Associations section"


: >"$install_log"
: >"$terminal_log"
omarchy-default-browser zen
Expand Down