OXYGENHUB

0.15.0: a splash of colour b55e0d00545e

The Operations view reads as what happened (a commit's message, Pushed
to earth, Switched to feature/16-hud) with its kind and time, each with
an icon in its own colour; Bisect and Trust are coloured by meaning; the
status bar shows the bookmark in the Oxygen accent, amber when there is
uncommitted work, with coloured ahead and behind counts. Thirteen new
themeable colours from the Oxygen mark's palette.
Author
Andy McCall @andymccall <[email protected]>
When
· 2026-09-30 15:34 UTC
Signature
✔ signed by Andy McCall
Hash
b55e0d00545e2c2dbe36765341deb92f6e2ee5aa7d3e30ae1936302c243c681a
Depends on
013fefef11e3 0c26846a9a81 35d56b5803d9 bed495a96408 c8f955891224 d2c9a5843004
Depended on by
dac6876e4d08 ca2304a7851a 813df1a92fbb
In bookmarks
main
In tags
v0.16.0 v0.16.1 v0.17.0 v0.17.1 v0.18.0
Edits
25 atoms, 3 tree operations, 8 files

Browse the tree at this change

added src/looks.ts +75 −0
1 +
// How an operation of the op log reads: a friendlier label, its kind, and
2 +
// the codicon and colour the Operations view gives it (see style.ts).
3 +
4 +
export interface OperationLook {
5 +
  /** What happened, in words: a commit's subject, "Pushed to earth". */
6 +
  label: string;
7 +
  /** The kind, shown beside it: "commit", "push". */
8 +
  kind: string;
9 +
  icon: string;
10 +
  color: string;
11 +
}
12 +
13 +
/** The first line of a quoted `-m` message in an operation's command. */
14 +
function message(command: string): string | undefined {
15 +
  const m = /-m\s+"((?:[^"\\]|\\.)*)"/.exec(command);
16 +
  if (!m) return undefined;
17 +
  const text = m[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
18 +
  return text.split("\n")[0].trim() || undefined;
19 +
}
20 +
21 +
/** How an operation of the op log looks, from its command. */
22 +
export function operationLook(command: string, snapshot: boolean): OperationLook {
23 +
  const cmd = command.replace(/^oxy\s+/, "").replace(/\s*\((?:snapshot|merged with a concurrent operation)\)\s*/g, " ").trim();
24 +
  const words = cmd.split(/\s+/);
25 +
  const [verb, sub] = words;
26 +
  const arg = (i: number) => (words[i] ?? "").replace(/^"|"$/g, "");
27 +
  if (snapshot) {
28 +
    return { label: `Snapshot for ${verb}`, kind: "snapshot", icon: "circle-small", color: "descriptionForeground" };
29 +
  }
30 +
  switch (verb) {
31 +
    case "commit":
32 +
      return { label: message(cmd) ?? "Committed", kind: "commit", icon: "git-commit", color: "oxygen.commit" };
33 +
    case "amend":
34 +
    case "split":
35 +
    case "squash":
36 +
    case "describe":
37 +
      return { label: message(cmd) ?? `${verb[0].toUpperCase()}${verb.slice(1)}ed`, kind: verb, icon: "edit", color: "oxygen.commit" };
38 +
    case "push":
39 +
      return { label: sub ? `Pushed to ${sub}` : "Pushed", kind: "push", icon: "cloud-upload", color: "oxygen.push" };
40 +
    case "pull":
41 +
      return { label: sub ? `Pulled from ${sub}` : "Pulled", kind: "pull", icon: "cloud-download", color: "oxygen.pull" };
42 +
    case "fetch":
43 +
      return { label: sub ? `Fetched ${sub}` : "Fetched", kind: "fetch", icon: "sync", color: "oxygen.pull" };
44 +
    case "clone":
45 +
    case "init":
46 +
      return { label: verb === "init" ? "Repository created" : "Cloned", kind: verb, icon: "repo-clone", color: "oxygen.pull" };
47 +
    case "merge":
48 +
      return { label: sub ? `Merged ${sub}` : "Merged", kind: "merge", icon: "git-merge", color: "oxygen.merge" };
49 +
    case "rebase":
50 +
    case "resolve":
51 +
      return { label: message(cmd) ?? `${verb[0].toUpperCase()}${verb.slice(1)}d`, kind: verb, icon: "git-pull-request", color: "oxygen.merge" };
52 +
    case "checkout":
53 +
      return { label: "Checked out", kind: "checkout", icon: "arrow-swap", color: "oxygen.switch" };
54 +
    case "bookmark":
55 +
    case "tag": {
56 +
      const name = arg(2);
57 +
      if (sub === "switch") return { label: `Switched to ${name}`, kind: "switch", icon: "arrow-swap", color: "oxygen.switch" };
58 +
      const done: Record<string, string> = { create: "created", set: "moved", delete: "deleted", move: "moved" };
59 +
      return { label: `${verb === "tag" ? "Tag" : "Bookmark"} ${name} ${done[sub] ?? sub}`.trim(), kind: verb, icon: verb === "tag" ? "tag" : "bookmark", color: "oxygen.bookmark" };
60 +
    }
61 +
    case "undo":
62 +
    case "discard":
63 +
    case "abandon":
64 +
    case "restore":
65 +
      return { label: `${verb[0].toUpperCase()}${verb.slice(1)}`, kind: verb, icon: "discard", color: "oxygen.undo" };
66 +
    case "seal":
67 +
    case "sign":
68 +
    case "policy":
69 +
      return { label: cmd, kind: verb, icon: "shield", color: "oxygen.key" };
70 +
    case "submodule":
71 +
      return { label: `Subrepos ${sub ?? ""}`.trim(), kind: "subrepo", icon: "repo", color: "oxygen.subrepo" };
72 +
    default:
73 +
      return { label: cmd, kind: verb ?? "", icon: "circle-outline", color: "descriptionForeground" };
74 +
  }
75 +
}
added src/style.ts +11 −0
1 +
// A splash of colour: icons tinted with the Oxygen palette (the colours
2 +
// package.json contributes, so themes can override them).
3 +
4 +
import * as vscode from "vscode";
5 +
6 +
/** A codicon, tinted with a contributed or theme colour. */
7 +
export function icon(id: string, color?: string): vscode.ThemeIcon {
8 +
  return new vscode.ThemeIcon(id, color ? new vscode.ThemeColor(color) : undefined);
9 +
}
10 +
11 +
export { operationLook, type OperationLook } from "./looks";
added test/looks.test.mjs +19 −0
1 +
import { test } from "node:test";
2 +
import assert from "node:assert/strict";
3 +
import { operationLook } from "../out/looks.mjs";
4 +
5 +
test("operations read as what happened, with a kind and a colour", () => {
6 +
  const commit = operationLook('oxy commit -m "Backlog: rebase into the working copy overnight\\n\\nrefs #42"', false);
7 +
  assert.deepEqual(commit, { label: "Backlog: rebase into the working copy overnight", kind: "commit", icon: "git-commit", color: "oxygen.commit" });
8 +
  assert.equal(operationLook("oxy push earth", false).label, "Pushed to earth");
9 +
  assert.equal(operationLook("oxy push earth", false).color, "oxygen.push");
10 +
  assert.equal(operationLook("oxy pull origin", false).label, "Pulled from origin");
11 +
  assert.equal(operationLook("oxy bookmark switch feature/16-hud", false).label, "Switched to feature/16-hud");
12 +
  assert.equal(operationLook("oxy bookmark create logo-25", false).label, "Bookmark logo-25 created");
13 +
  assert.equal(operationLook("oxy merge develop", false).icon, "git-merge");
14 +
  assert.equal(operationLook("oxy undo", false).color, "oxygen.undo");
15 +
  assert.equal(operationLook("oxy status (snapshot)", true).kind, "snapshot");
16 +
  assert.equal(operationLook('oxy commit -m "say \\"hi\\""', false).label, 'say "hi"');
17 +
  // Anything else keeps its command, without the leading oxy.
18 +
  assert.equal(operationLook("oxy gc", false).label, "gc");
19 +
});
modified CHANGELOG.md +7 −0
1 1
# Changelog
2 2
3 +
## 0.15.0 (2026-09-30)
4 +
5 +
- **A splash of colour everywhere.** The Operations view reads as what happened: a commit shows its message, a push "Pushed to earth", a switch "Switched to feature/16-hud", each with its kind and time beside it and an icon in its own colour: green commits, blue pushes, teal pulls, violet merges, gold bookmarks, amber undos. The current operation wears the Oxygen accent, and the tooltip keeps the exact command.
6 +
- **Bisect and Trust in colour.** Passing and verified lines are green, bad and invalid red, keys and roots gold, the policy shield violet, warnings amber, with a few emoji flourishes.
7 +
- **The status bar** shows the bookmark with an icon, in the Oxygen accent when the working copy is clean and amber when it holds uncommitted work; ahead and behind counts are coloured for push and pull.
8 +
- New themeable colours: `oxygen.accent`, `oxygen.commit`, `oxygen.push`, `oxygen.pull`, `oxygen.merge`, `oxygen.bookmark`, `oxygen.switch`, `oxygen.undo`, `oxygen.good`, `oxygen.bad`, `oxygen.warning`, `oxygen.key` and `oxygen.subrepo`.
9 +
3 10
## 0.14.0 (2026-09-29)
4 11
5 12
- **Oxygen colours.** The history graph draws each lane in the Oxygen mark's palette. Heads glow, and the working copy breathes (it holds still if your system asks for reduced motion). ✔ marks a change signed on its own, ◈ one sealed on push, both coloured.
…
92 unchanged lines
modified package.json +132 −2
…
1 unchanged lines
2 2
  "name": "oxygen-vscode",
3 3
  "displayName": "Oxygen",
4 4
  "description": "Oxygen version control: source control view, diffs, decorations, bookmarks and history for oxy repositories",
5 -
  "version": "0.14.0",
5 +
  "version": "0.15.0",
6 6
  "publisher": "oxygen",
7 7
  "license": "MIT OR Apache-2.0",
8 8
  "icon": "assets/images/oxygen.png",
…
791 unchanged lines
800 800
    },
801 801
    "colors": [
802 802
      {
803 +
        "id": "oxygen.accent",
804 +
        "description": "The Oxygen accent: the current operation, repositories, the status bar.",
805 +
        "defaults": {
806 +
          "dark": "#35C7FF",
807 +
          "light": "#0A74B8",
808 +
          "highContrast": "#35C7FF",
809 +
          "highContrastLight": "#0A74B8"
810 +
        }
811 +
      },
812 +
      {
813 +
        "id": "oxygen.commit",
814 +
        "description": "Commits, amends and splits in the Operations view.",
815 +
        "defaults": {
816 +
          "dark": "#5FE3A1",
817 +
          "light": "#1A7850",
818 +
          "highContrast": "#5FE3A1",
819 +
          "highContrastLight": "#1A7850"
820 +
        }
821 +
      },
822 +
      {
823 +
        "id": "oxygen.push",
824 +
        "description": "Pushes.",
825 +
        "defaults": {
826 +
          "dark": "#3D7BFF",
827 +
          "light": "#2553C9",
828 +
          "highContrast": "#3D7BFF",
829 +
          "highContrastLight": "#2553C9"
830 +
        }
831 +
      },
832 +
      {
833 +
        "id": "oxygen.pull",
834 +
        "description": "Pulls, fetches and clones.",
835 +
        "defaults": {
836 +
          "dark": "#2BD9B3",
837 +
          "light": "#0E8A70",
838 +
          "highContrast": "#2BD9B3",
839 +
          "highContrastLight": "#0E8A70"
840 +
        }
841 +
      },
842 +
      {
843 +
        "id": "oxygen.merge",
844 +
        "description": "Merges, rebases and resolutions.",
845 +
        "defaults": {
846 +
          "dark": "#8E7CFF",
847 +
          "light": "#5B45D6",
848 +
          "highContrast": "#8E7CFF",
849 +
          "highContrastLight": "#5B45D6"
850 +
        }
851 +
      },
852 +
      {
853 +
        "id": "oxygen.bookmark",
854 +
        "description": "Bookmarks and tags.",
855 +
        "defaults": {
856 +
          "dark": "#FFD166",
857 +
          "light": "#A36A00",
858 +
          "highContrast": "#FFD166",
859 +
          "highContrastLight": "#A36A00"
860 +
        }
861 +
      },
862 +
      {
863 +
        "id": "oxygen.switch",
864 +
        "description": "Checkouts and bookmark switches.",
865 +
        "defaults": {
866 +
          "dark": "#56A8FF",
867 +
          "light": "#1F6FC2",
868 +
          "highContrast": "#56A8FF",
869 +
          "highContrastLight": "#1F6FC2"
870 +
        }
871 +
      },
872 +
      {
873 +
        "id": "oxygen.undo",
874 +
        "description": "Undo, discard and abandon.",
875 +
        "defaults": {
876 +
          "dark": "#FFB454",
877 +
          "light": "#B35900",
878 +
          "highContrast": "#FFB454",
879 +
          "highContrastLight": "#B35900"
880 +
        }
881 +
      },
882 +
      {
883 +
        "id": "oxygen.good",
884 +
        "description": "Good: passing, verified, signed.",
885 +
        "defaults": {
886 +
          "dark": "#5FE3A1",
887 +
          "light": "#1A7850",
888 +
          "highContrast": "#5FE3A1",
889 +
          "highContrastLight": "#1A7850"
890 +
        }
891 +
      },
892 +
      {
893 +
        "id": "oxygen.bad",
894 +
        "description": "Bad: failing, invalid, expired.",
895 +
        "defaults": {
896 +
          "dark": "#FF5E7A",
897 +
          "light": "#C62D47",
898 +
          "highContrast": "#FF5E7A",
899 +
          "highContrastLight": "#C62D47"
900 +
        }
901 +
      },
902 +
      {
903 +
        "id": "oxygen.warning",
904 +
        "description": "Warnings and dirty working copies.",
905 +
        "defaults": {
906 +
          "dark": "#FFB454",
907 +
          "light": "#B35900",
908 +
          "highContrast": "#FFB454",
909 +
          "highContrastLight": "#B35900"
910 +
        }
911 +
      },
912 +
      {
913 +
        "id": "oxygen.key",
914 +
        "description": "Keys and roots in the Trust view.",
915 +
        "defaults": {
916 +
          "dark": "#FFD166",
917 +
          "light": "#A36A00",
918 +
          "highContrast": "#FFD166",
919 +
          "highContrastLight": "#A36A00"
920 +
        }
921 +
      },
922 +
      {
923 +
        "id": "oxygen.subrepo",
924 +
        "description": "Subrepos.",
925 +
        "defaults": {
926 +
          "dark": "#1FB6D6",
927 +
          "light": "#0B7C93",
928 +
          "highContrast": "#1FB6D6",
929 +
          "highContrastLight": "#0B7C93"
930 +
        }
931 +
      },
932 +
      {
803 933
        "id": "oxygen.lane1",
804 934
        "description": "History graph: lane 1 (the Oxygen mark's palette).",
805 935
        "defaults": {
…
264 unchanged lines
1070 1200
    "build": "esbuild src/extension.ts --bundle --platform=node --format=cjs --external:vscode --outfile=dist/extension.js --sourcemap",
1071 1201
    "watch": "npm run build -- --watch",
1072 1202
    "typecheck": "tsc --noEmit -p .",
1073 -
    "pretest": "esbuild src/oxy.ts --bundle --platform=node --format=esm --outfile=out/oxy.mjs && esbuild src/diffutil.ts --bundle --platform=node --format=esm --outfile=out/diffutil.mjs && esbuild src/markers.ts --bundle --platform=node --format=esm --outfile=out/markers.mjs && esbuild src/api.ts --bundle --platform=node --format=esm --outfile=out/api.mjs && esbuild src/commitbox.ts --bundle --platform=node --format=esm --outfile=out/commitbox.mjs && esbuild src/handoff.ts --bundle --platform=node --format=esm --outfile=out/handoff.mjs && esbuild src/paths.ts --bundle --platform=node --format=esm --outfile=out/paths.mjs",
1203 +
    "pretest": "esbuild src/oxy.ts --bundle --platform=node --format=esm --outfile=out/oxy.mjs && esbuild src/diffutil.ts --bundle --platform=node --format=esm --outfile=out/diffutil.mjs && esbuild src/markers.ts --bundle --platform=node --format=esm --outfile=out/markers.mjs && esbuild src/api.ts --bundle --platform=node --format=esm --outfile=out/api.mjs && esbuild src/commitbox.ts --bundle --platform=node --format=esm --outfile=out/commitbox.mjs && esbuild src/handoff.ts --bundle --platform=node --format=esm --outfile=out/handoff.mjs && esbuild src/paths.ts --bundle --platform=node --format=esm --outfile=out/paths.mjs && esbuild src/looks.ts --bundle --platform=node --format=esm --outfile=out/looks.mjs",
1074 1204
    "test": "node --test \"test/**/*.test.mjs\"",
1075 1205
    "check": "npm run typecheck && npm run build && npm test",
1076 1206
    "package": "vsce package --no-dependencies"
…
9 unchanged lines
modified src/operations.ts +10 −5
…
3 unchanged lines
4 4
import * as vscode from "vscode";
5 5
import { OperationInfo } from "./oxy";
6 6
import { Repository } from "./repository";
7 +
import { icon, operationLook } from "./style";
7 8
8 9
export class OperationItem extends vscode.TreeItem {
9 10
  constructor(
…
1 unchanged lines
11 12
    readonly op: OperationInfo,
12 13
    readonly stepsBack: number,
13 14
  ) {
14 -
    super(op.command, vscode.TreeItemCollapsibleState.None);
15 +
    const look = operationLook(op.command, op.snapshot);
16 +
    super(look.label, vscode.TreeItemCollapsibleState.None);
15 17
    this.id = `${repository.root}:${op.id}`;
16 -
    this.description = timeAgo(op.when_ms);
17 -
    this.tooltip = `${op.command}\n${new Date(op.when_ms).toLocaleString()}\n${op.id.slice(0, 12)}`;
18 -
    this.iconPath = new vscode.ThemeIcon(op.current ? "circle-filled" : op.snapshot ? "circle-small" : "circle-outline");
18 +
    this.description = `${op.current ? "● now · " : ""}${look.kind} · ${timeAgo(op.when_ms)}`;
19 +
    this.tooltip = new vscode.MarkdownString(
20 +
      `$(${look.icon}) **${look.label}**\n\n\`${op.command.replace(/`/g, "'")}\`\n\n${new Date(op.when_ms).toLocaleString()} · \`${op.id.slice(0, 12)}\``,
21 +
      true,
22 +
    );
23 +
    this.iconPath = icon(op.current ? "pass-filled" : look.icon, op.current ? "oxygen.accent" : look.color);
19 24
    this.contextValue = op.current ? "current" : "operation";
20 25
  }
21 26
}
…
52 unchanged lines
74 79
export class RepositoryItem extends vscode.TreeItem {
75 80
  constructor(readonly repository: Repository) {
76 81
    super(repository.root.split("/").pop() ?? repository.root, vscode.TreeItemCollapsibleState.Expanded);
77 -
    this.iconPath = new vscode.ThemeIcon("repo");
82 +
    this.iconPath = icon("repo", "oxygen.accent");
78 83
    this.contextValue = "repository";
79 84
  }
80 85
}
…
12 unchanged lines
modified src/panels.ts +29 −6
…
3 unchanged lines
4 4
import * as vscode from "vscode";
5 5
import { BisectStatus, PolicyInfo, VerifyInfo } from "./oxy";
6 6
import { Repository } from "./repository";
7 +
import { icon as tinted } from "./style";
7 8
8 9
class Line extends vscode.TreeItem {
9 -
  constructor(label: string, opts: { description?: string; icon?: string; command?: vscode.Command; tooltip?: string; context?: string } = {}) {
10 +
  constructor(label: string, opts: { description?: string; icon?: string; color?: string; command?: vscode.Command; tooltip?: string; context?: string } = {}) {
10 11
    super(label, vscode.TreeItemCollapsibleState.None);
11 12
    this.description = opts.description;
12 13
    this.tooltip = opts.tooltip;
13 14
    this.command = opts.command;
14 15
    this.contextValue = opts.context;
15 16
    if (opts.icon) {
16 -
      this.iconPath = new vscode.ThemeIcon(opts.icon);
17 +
      this.iconPath = tinted(opts.icon, opts.color ?? ICON_COLOR[opts.icon]);
17 18
    }
18 19
  }
19 20
}
20 21
22 +
/** The colour each icon of these views wears unless a line says otherwise. */
23 +
const ICON_COLOR: Record<string, string> = {
24 +
  pass: "oxygen.good",
25 +
  verified: "oxygen.good",
26 +
  error: "oxygen.bad",
27 +
  bug: "oxygen.bad",
28 +
  warning: "oxygen.warning",
29 +
  unlock: "oxygen.warning",
30 +
  question: "oxygen.warning",
31 +
  search: "oxygen.accent",
32 +
  beaker: "oxygen.merge",
33 +
  "run-all": "oxygen.push",
34 +
  "debug-step-over": "descriptionForeground",
35 +
  discard: "oxygen.undo",
36 +
  shield: "oxygen.merge",
37 +
  key: "oxygen.key",
38 +
  law: "oxygen.key",
39 +
  "list-tree": "oxygen.accent",
40 +
  "circle-slash": "oxygen.bad",
41 +
  "circle-outline": "descriptionForeground",
42 +
};
43 +
21 44
abstract class RepoTree implements vscode.TreeDataProvider<vscode.TreeItem> {
22 45
  private readonly emitter = new vscode.EventEmitter<void>();
23 46
  readonly onDidChangeTreeData = this.emitter.event;
…
16 unchanged lines
40 63
      }
41 64
      return repos.map((r) => {
42 65
        const item = new vscode.TreeItem(r.root.split("/").pop() ?? r.root, vscode.TreeItemCollapsibleState.Expanded);
43 -
        item.iconPath = new vscode.ThemeIcon("repo");
66 +
        item.iconPath = tinted("repo", "oxygen.accent");
44 67
        (item as vscode.TreeItem & { repository?: Repository }).repository = r;
45 68
        return item;
46 69
      });
…
15 unchanged lines
62 85
    }
63 86
    const cmd = (title: string, command: string, args: unknown[] = []): vscode.Command => ({ title, command, arguments: [repo, ...args] });
64 87
    if (!status) {
65 -
      return [new Line("No bisect in progress", { icon: "search", description: "start one to find a regression", command: cmd("Start", "oxygen.bisectStart") })];
88 +
      return [new Line("No bisect in progress", { icon: "search", description: "🔍 start one to find a regression", command: cmd("Start", "oxygen.bisectStart") })];
66 89
    }
67 90
    switch (status.state) {
68 91
      case "need-marks":
…
13 unchanged lines
82 105
        ];
83 106
      case "found":
84 107
        return [
85 -
          new Line(`Found: ${status.culprit.slice(0, 12)}`, { icon: "bug", description: "the first change that makes the state bad", command: { title: "Show", command: "oxygen.showChange", arguments: [repo.root, status.culprit] } }),
108 +
          new Line(`Found: ${status.culprit.slice(0, 12)}`, { icon: "bug", description: "🎯 the first change that makes the state bad", command: { title: "Show", command: "oxygen.showChange", arguments: [repo.root, status.culprit] } }),
86 109
          new Line("Reset: back to where you were", { icon: "discard", command: cmd("Reset", "oxygen.bisectReset") }),
87 110
        ];
88 111
      case "inconclusive":
…
17 unchanged lines
106 129
    }
107 130
    const items: vscode.TreeItem[] = [];
108 131
    if (!policy) {
109 -
      items.push(new Line("No trust policy: this repository is unverified", { icon: "unlock", description: "oxy policy init creates one", tooltip: "Without a policy every change and reference update is accepted." }));
132 +
      items.push(new Line("No trust policy: this repository is unverified", { icon: "unlock", description: "🔓 oxy policy init creates one", tooltip: "Without a policy every change and reference update is accepted." }));
110 133
    } else {
111 134
      const expires = new Date(policy.expires_ms).toLocaleDateString();
112 135
      items.push(new Line(`Policy serial ${policy.serial}`, { icon: policy.expired ? "error" : "shield", description: policy.expired ? `EXPIRED ${expires}` : `expires ${expires}` }));
…
30 unchanged lines
modified src/repository.ts +7 −2
…
246 unchanged lines
247 247
      return;
248 248
    }
249 249
    const text = prompt || `${status.bookmark} ${status.working_copy.slice(0, 8)}`;
250 -
    this.statusBar.text = text;
250 +
    // The bookmark in the Oxygen accent when clean, amber with uncommitted
251 +
    // work, and on the error background with conflicts.
252 +
    const dirty = status.paths.length > 0;
253 +
    this.statusBar.text = `$(${status.conflicted ? "warning" : "bookmark"}) ${text}`;
254 +
    this.statusBar.color = status.conflicted ? undefined : new vscode.ThemeColor(dirty ? "oxygen.warning" : "oxygen.accent");
251 255
    this.statusBar.tooltip = new vscode.MarkdownString(
252 256
      `**Oxygen** ${basename(this.root)}\n\n` +
253 257
        `Bookmark \`${status.bookmark}\` with ${status.heads} head${status.heads === 1 ? "" : "s"}\n\n` +
…
7 unchanged lines
261 265
    if (current && (current.ahead !== null || current.behind !== null)) {
262 266
      const ahead = current.ahead ?? 0;
263 267
      const behind = current.behind ?? 0;
264 -
      this.syncBar.text = `$(arrow-up)${ahead} $(arrow-down)${behind}`;
268 +
      this.syncBar.text = `$(cloud-upload) ${ahead} $(cloud-download) ${behind}`;
269 +
      this.syncBar.color = new vscode.ThemeColor(behind > 0 ? "oxygen.pull" : ahead > 0 ? "oxygen.push" : "oxygen.good");
265 270
      this.syncBar.tooltip = `${ahead} change${ahead === 1 ? "" : "s"} to push, ${behind} to pull from origin. Click to sync.`;
266 271
      this.syncBar.show();
267 272
    } else {
…
27 unchanged lines