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 |