| 1 | import * as vscode from "vscode"; |
| 2 | import { AUTH_ID, DiffineryAuth, SCOPES } from "./auth"; |
| 3 | import { GitCredentials, Publisher, syncCredentialStore } from "./git"; |
| 4 | import { RemoteSources } from "./remoteSources"; |
| 5 | import type { GitAPI, GitBaseAPI, GitBaseExtension, GitExtension } from "./typings/git"; |
| 6 | import { checkForUpdates } from "./updates"; |
| 7 | |
| 8 | export async function activate(ctx: vscode.ExtensionContext): Promise<void> { |
| 9 | const version = String(ctx.extension.packageJSON.version ?? "0.0.0"); |
| 10 | const log = vscode.window.createOutputChannel("Diffinery", { log: true }); |
| 11 | const auth = new DiffineryAuth(ctx, log, version); |
| 12 | ctx.subscriptions.push( |
| 13 | log, |
| 14 | auth, |
| 15 | vscode.authentication.registerAuthenticationProvider(AUTH_ID, "Diffinery", auth, { supportsMultipleAccounts: false }), |
| 16 | vscode.window.registerUriHandler({ handleUri: (uri) => void auth.handleUri(uri) }), |
| 17 | ); |
| 18 | |
| 19 | const report = (what: string) => (e: unknown) => { |
| 20 | if (e instanceof vscode.CancellationError) return; |
| 21 | log.error(what, e); |
| 22 | void vscode.window.showErrorMessage(`${what}: ${e instanceof Error ? e.message : String(e)}`); |
| 23 | }; |
| 24 | const command = (id: string, run: () => Promise<unknown>, what: string) => ctx.subscriptions.push(vscode.commands.registerCommand(id, () => run().catch(report(what)))); |
| 25 | |
| 26 | command("diffinery.signIn", async () => vscode.authentication.getSession(AUTH_ID, SCOPES, { createIfNone: true }), "Signing in to Diffinery failed"); |
| 27 | command("diffinery.signInWithToken", () => auth.signInWithToken(), "Signing in to Diffinery failed"); |
| 28 | command("diffinery.signOut", () => auth.signOut(), "Signing out of Diffinery failed"); |
| 29 | command("diffinery.checkForUpdates", () => checkForUpdates(ctx, auth.server(), version, log, true), "Checking for updates failed"); |
| 30 | |
| 31 | // ---------------------------------------------------------------- the built-in Git extensions |
| 32 | let gitBase: GitBaseAPI | undefined; |
| 33 | let git: GitAPI | undefined; |
| 34 | const remoteSources = new RemoteSources(auth); |
| 35 | const gitDisposables: vscode.Disposable[] = []; |
| 36 | ctx.subscriptions.push({ dispose: () => gitDisposables.forEach((d) => d.dispose()) }); |
| 37 | let credentials: vscode.Disposable | undefined; |
| 38 | const updateCredentials = () => { |
| 39 | const on = !!git && vscode.workspace.getConfiguration("diffinery").get<boolean>("gitAuthentication", true); |
| 40 | if (on && !credentials) credentials = git!.registerCredentialsProvider(new GitCredentials(auth)); |
| 41 | if (!on && credentials) { |
| 42 | credentials.dispose(); |
| 43 | credentials = undefined; |
| 44 | } |
| 45 | }; |
| 46 | ctx.subscriptions.push( |
| 47 | { dispose: () => credentials?.dispose() }, |
| 48 | vscode.workspace.onDidChangeConfiguration((e) => e.affectsConfiguration("diffinery.gitAuthentication") && updateCredentials()), |
| 49 | syncCredentialStore(auth, () => git, log), |
| 50 | ); |
| 51 | |
| 52 | const baseExt = vscode.extensions.getExtension<GitBaseExtension>("vscode.git-base"); |
| 53 | const gitExt = vscode.extensions.getExtension<GitExtension>("vscode.git"); |
| 54 | const connect = async () => { |
| 55 | try { |
| 56 | const base = baseExt && (baseExt.isActive ? baseExt.exports : await baseExt.activate()); |
| 57 | if (base?.enabled && !gitBase) { |
| 58 | gitBase = base.getAPI(1); |
| 59 | gitDisposables.push(gitBase.registerRemoteSourceProvider(remoteSources)); |
| 60 | } |
| 61 | const g = gitExt && (gitExt.isActive ? gitExt.exports : await gitExt.activate()); |
| 62 | if (g?.enabled && !git) { |
| 63 | git = g.getAPI(1); |
| 64 | gitDisposables.push(git.registerRemoteSourcePublisher(new Publisher(auth))); |
| 65 | updateCredentials(); |
| 66 | } |
| 67 | return { base, g }; |
| 68 | } catch (e) { |
| 69 | log.error("connecting to the built-in Git extension failed", e); |
| 70 | return {}; |
| 71 | } |
| 72 | }; |
| 73 | const { base, g } = await connect(); |
| 74 | if (base && !base.enabled) ctx.subscriptions.push(base.onDidChangeEnablement((on) => on && void connect())); |
| 75 | if (g && !g.enabled) ctx.subscriptions.push(g.onDidChangeEnablement((on) => on && void connect())); |
| 76 | if (!baseExt || !gitExt) log.warn("The editor's built-in Git extension isn't available, so Diffinery can't add itself to the clone picker."); |
| 77 | |
| 78 | command( |
| 79 | "diffinery.clone", |
| 80 | async () => { |
| 81 | if (!gitBase) throw new Error("The editor's Git support is turned off (git.enabled). Turn it on to clone."); |
| 82 | const picked = await gitBase.pickRemoteSource({ providerName: remoteSources.name, title: "Clone from Diffinery", urlLabel: "Clone from URL" }); |
| 83 | const url = typeof picked === "string" ? picked : picked?.url; |
| 84 | if (url) await vscode.commands.executeCommand("git.clone", url); |
| 85 | }, |
| 86 | "Cloning from Diffinery failed", |
| 87 | ); |
| 88 | |
| 89 | void checkForUpdates(ctx, auth.server(), version, log, false).catch((e) => log.warn("update check failed", e)); |
| 90 | } |
| 91 | |
| 92 | export function deactivate(): void {} |