/ de-omarchy / bin / omarchy-theme-set-vscode
swadhinbiswas Initial de-omarchy: Omarchy UI layer for Arch-based distros · 8/22/2026
9f393b2
154 lines · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#!/bin/bash

# omarchy:summary=Sync Omarchy theme to VS Code, VSCodium, and Cursor
# omarchy:hidden=true

VS_CODE_THEME_DESCRIPTOR="$HOME/.local/state/omarchy/current/theme/vscode.json"
GENERATED_THEME="$HOME/.local/state/omarchy/current/theme/vscode-theme.json"
GENERATED_EXTENSION_NAME="omarchy-theme"
GENERATED_EXTENSION_ID="local.$GENERATED_EXTENSION_NAME"
GENERATED_EXTENSION_VERSION="1.0.0"

register_generated_extension() {
  local ext_dir="$1"
  local ext_base extensions_file obsolete_file relative tmp

  ext_base=$(dirname "$ext_dir")
  extensions_file="$ext_base/extensions.json"
  obsolete_file="$ext_base/.obsolete"
  relative=$(basename "$ext_dir")

  if [[ -f $obsolete_file ]]; then
    tmp=$(mktemp)
    if jq --arg key "$GENERATED_EXTENSION_ID-$GENERATED_EXTENSION_VERSION" 'del(.[$key])' "$obsolete_file" >"$tmp"; then
      mv "$tmp" "$obsolete_file"
      [[ $(jq 'length' "$obsolete_file") == "0" ]] && rm -f "$obsolete_file"
    else
      rm -f "$tmp"
    fi
  fi

  [[ -f $extensions_file ]] || printf '[]\n' >"$extensions_file"

  tmp=$(mktemp)
  if jq \
    --arg id "$GENERATED_EXTENSION_ID" \
    --arg version "$GENERATED_EXTENSION_VERSION" \
    --arg fs_path "$ext_dir" \
    --arg external "file://$ext_dir" \
    --arg relative "$relative" \
    'map(select(.identifier.id != $id)) + [{
      identifier: { id: $id },
      version: $version,
      location: {
        "$mid": 1,
        fsPath: $fs_path,
        external: $external,
        path: $fs_path,
        scheme: "file"
      },
      relativeLocation: $relative
    }]' \
    "$extensions_file" >"$tmp"; then
    mv "$tmp" "$extensions_file"
  else
    rm -f "$tmp"
  fi
}

# Install generated theme as a local extension for a given editor
install_generated_extension() {
  local ext_dir="$1"
  local theme_type ui_theme

  theme_type=$(jq -r '.type // "dark"' "$GENERATED_THEME")
  if [[ $theme_type == "light" ]]; then
    ui_theme="vs"
  else
    ui_theme="vs-dark"
  fi

  mkdir -p "$ext_dir/themes"
  ln -sfn "$GENERATED_THEME" "$ext_dir/themes/omarchy-color-theme.json"

  cat > "$ext_dir/package.json" <<EOF
{
    "name": "$GENERATED_EXTENSION_NAME",
    "displayName": "Omarchy",
    "description": "Omarchy color theme",
    "publisher": "local",
    "version": "$GENERATED_EXTENSION_VERSION",
    "engines": { "vscode": "^1.70.0" },
    "categories": ["Themes"],
    "contributes": {
        "themes": [{
            "label": "Omarchy",
            "uiTheme": "$ui_theme",
            "path": "./themes/omarchy-color-theme.json"
        }]
    }
}
EOF

  register_generated_extension "$ext_dir"
}

set_theme() {
  local editor_cmd="$1"
  local settings_path="$2"
  local ext_base="$3"

  omarchy-cmd-present "$editor_cmd" || return 0

  local theme_name=""

  if [[ -f "$VS_CODE_THEME_DESCRIPTOR" ]]; then
    # Theme specifies a preferred 3rd-party extension/theme pair. Both fields
    # come from the theme, so neither is trusted as a shell or sed argument.
    local extension
    theme_name=$(jq -r '.name // empty' "$VS_CODE_THEME_DESCRIPTOR")
    extension=$(jq -r '.extension // empty' "$VS_CODE_THEME_DESCRIPTOR")

    # A label with quotes, backslashes, or control characters can't land in
    # settings.json as valid JSON, so drop it rather than write it out.
    [[ $theme_name =~ ^[^[:cntrl:]\"\\]+$ ]] || theme_name=""

    # Only install a well-formed publisher.name id; a malformed one skips the
    # install without skipping the colorTheme write below.
    if [[ $extension =~ ^[a-zA-Z0-9._-]+$ ]] &&
      ! "$editor_cmd" --list-extensions 2>/dev/null | grep -Fxq "$extension"; then
      "$editor_cmd" --install-extension "$extension" >/dev/null 2>&1
    fi
  elif [[ -f "$GENERATED_THEME" ]]; then
    # VS Code only discovers local color themes through an extension package.
    install_generated_extension "$ext_base/$GENERATED_EXTENSION_NAME"
    theme_name="Omarchy"
  fi

  if [[ -n "$theme_name" ]]; then
    mkdir -p "$(dirname "$settings_path")"
    [[ -f $settings_path ]] || printf '{\n}\n' >"$settings_path"

    # settings.json is JSONC: comments and trailing commas are legal there and
    # common in practice, so it's edited in place rather than round-tripped
    # through a strict JSON parser that would reject it and reformat the rest.
    local escaped=${theme_name//&/\\&}
    escaped=${escaped//|/\\|}

    if ! grep -q '"workbench.colorTheme"' "$settings_path"; then
      sed -i --follow-symlinks -E '0,/\{/{s/\{/{\ "workbench.colorTheme": "",/}' "$settings_path"
    fi

    sed -i --follow-symlinks -E \
      "s|(\"workbench.colorTheme\"[[:space:]]*:[[:space:]]*\")[^\"]*(\")|\1$escaped\2|" \
      "$settings_path"
  elif [[ -f $settings_path ]]; then
    sed -i --follow-symlinks -E 's/\"workbench\.colorTheme\"[[:space:]]*:[^,}]*,?//' "$settings_path"
  fi
}

! omarchy-toggle-enabled skip-vscode-theme-changes && set_theme "code" "$HOME/.config/Code/User/settings.json" "$HOME/.vscode/extensions"
! omarchy-toggle-enabled skip-vscode-insiders-theme-changes && set_theme "code-insiders" "$HOME/.config/Code - Insiders/User/settings.json" "$HOME/.vscode-insiders/extensions"
! omarchy-toggle-enabled skip-codium-theme-changes && set_theme "codium" "$HOME/.config/VSCodium/User/settings.json" "$HOME/.vscode-oss/extensions"
! omarchy-toggle-enabled skip-cursor-theme-changes && set_theme "/usr/bin/cursor" "$HOME/.config/Cursor/User/settings.json" "$HOME/.cursor/extensions"