| 1 |
// Explorer and tab decorations from each repository's latest status (spec §5.1). |
| 2 |
|
| 3 |
import * as vscode from "vscode"; |
| 4 |
import { Repository } from "./repository"; |
| 5 |
|
| 6 |
export class OxyDecorationProvider implements vscode.FileDecorationProvider, vscode.Disposable { |
| 7 |
private readonly emitter = new vscode.EventEmitter<vscode.Uri | vscode.Uri[] | undefined>(); |
| 8 |
readonly onDidChangeFileDecorations = this.emitter.event; |
| 9 |
private readonly disposables: vscode.Disposable[] = []; |
| 10 |
|
| 11 |
constructor(private readonly repositories: () => Repository[]) { |
| 12 |
this.disposables.push(vscode.window.registerFileDecorationProvider(this), this.emitter); |
| 13 |
} |
| 14 |
|
| 15 |
/** Call when a repository's status changed; every decoration is recomputed. */ |
| 16 |
refresh(): void { |
| 17 |
this.emitter.fire(undefined); |
| 18 |
} |
| 19 |
|
| 20 |
provideFileDecoration(uri: vscode.Uri): vscode.FileDecoration | undefined { |
| 21 |
if (!vscode.workspace.getConfiguration("oxygen").get<boolean>("decorations.enabled", true)) { |
| 22 |
return undefined; |
| 23 |
} |
| 24 |
for (const repo of this.repositories()) { |
| 25 |
const rel = repo.relativePath(uri); |
| 26 |
if (rel === undefined || !repo.status) { |
| 27 |
continue; |
| 28 |
} |
| 29 |
const entry = repo.status.paths.find((p) => p.path === rel); |
| 30 |
const conflict = repo.status.conflicts.find((c) => c.path === rel); |
| 31 |
if (conflict && !entry) { |
| 32 |
return new vscode.FileDecoration("‼", "conflict", new vscode.ThemeColor("oxygen.conflict")); |
| 33 |
} |
| 34 |
if (!entry) { |
| 35 |
// A directory containing changes gets a dot, like Git. |
| 36 |
if (repo.status.paths.some((p) => p.path.startsWith(rel + "/"))) { |
| 37 |
return new vscode.FileDecoration("•", "contains changes", new vscode.ThemeColor("oxygen.modified")); |
| 38 |
} |
| 39 |
continue; |
| 40 |
} |
| 41 |
const letters: Record<string, [string, string, string]> = { |
| 42 |
Added: ["A", "added", "oxygen.added"], |
| 43 |
Modified: ["M", "modified", "oxygen.modified"], |
| 44 |
Removed: ["D", "removed", "oxygen.removed"], |
| 45 |
Renamed: ["R", "renamed", "oxygen.renamed"], |
| 46 |
ModeChanged: ["X", "mode changed", "oxygen.modified"], |
| 47 |
ConflictEdited: ["‼", "conflict markers remain", "oxygen.conflict"], |
| 48 |
Resolved: ["✔", "conflict resolved", "oxygen.resolved"], |
| 49 |
}; |
| 50 |
const [badge, tooltip, color] = letters[entry.status] ?? ["?", entry.status, "oxygen.modified"]; |
| 51 |
const d = new vscode.FileDecoration(badge, tooltip, new vscode.ThemeColor(color)); |
| 52 |
d.propagate = false; |
| 53 |
return d; |
| 54 |
} |
| 55 |
return undefined; |
| 56 |
} |
| 57 |
|
| 58 |
dispose(): void { |
| 59 |
for (const d of this.disposables) { |
| 60 |
d.dispose(); |
| 61 |
} |
| 62 |
} |
| 63 |
} |