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