| 1 | import * as vscode from "vscode"; |
| 2 | import type { DiffineryAuth } from "./auth"; |
| 3 | import type { RemoteSource, RemoteSourceAction, RemoteSourceProvider } from "./typings/git"; |
| 4 | import { dedupe, matches, repoRef, type RepoJson } from "./util"; |
| 5 | |
| 6 | /** How long the list of your repositories is reused while you type in the picker. */ |
| 7 | const MINE_TTL_MS = 60_000; |
| 8 | |
| 9 | /** |
| 10 | * "Clone from Diffinery" in the editor's clone picker (and "Add remote from Diffinery"): your repositories and |
| 11 | * your organizations', most recently pushed first, and as you type, any repository you can read that matches. |
| 12 | */ |
| 13 | export class RemoteSources implements RemoteSourceProvider { |
| 14 | readonly name = "Diffinery"; |
| 15 | readonly icon = "diffinery-logo"; |
| 16 | readonly supportsQuery = true; |
| 17 | readonly placeholder = "Repository name or owner/name (type to search)"; |
| 18 | |
| 19 | private mine: RepoJson[] = []; |
| 20 | private mineAt = 0; |
| 21 | |
| 22 | constructor(private readonly auth: DiffineryAuth) {} |
| 23 | |
| 24 | async getRemoteSources(query?: string): Promise<RemoteSource[]> { |
| 25 | const api = await this.auth.api(true); |
| 26 | if (!api) return []; |
| 27 | const q = (query ?? "").trim(); |
| 28 | if (!q || Date.now() - this.mineAt > MINE_TTL_MS) { |
| 29 | this.mine = await api.myRepos(); |
| 30 | this.mineAt = Date.now(); |
| 31 | } |
| 32 | if (!q) return this.mine.map(source); |
| 33 | const ref = repoRef(api.server, q); |
| 34 | const [direct, found] = await Promise.all([ref ? api.repo(ref).then((r) => [r], () => []) : Promise.resolve([]), api.search(q).catch(() => [] as RepoJson[])]); |
| 35 | return dedupe(direct, this.mine.filter((r) => matches(r, q)), found).map(source); |
| 36 | } |
| 37 | |
| 38 | async getBranches(url: string): Promise<string[]> { |
| 39 | const api = await this.auth.api(true); |
| 40 | const ref = api && repoRef(api.server, url); |
| 41 | if (!api || !ref) return []; |
| 42 | const [branches, repo] = await Promise.all([api.branches(ref), api.repo(ref)]); |
| 43 | const main = repo.default_branch; |
| 44 | return branches.sort((a, b) => (a === main ? -1 : b === main ? 1 : 0)); |
| 45 | } |
| 46 | |
| 47 | getRemoteSourceActions(url: string): RemoteSourceAction[] { |
| 48 | let server: string; |
| 49 | try { |
| 50 | server = this.auth.server(); |
| 51 | } catch { |
| 52 | return []; |
| 53 | } |
| 54 | const ref = repoRef(server, url); |
| 55 | if (!ref || !/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) return []; |
| 56 | return [ |
| 57 | { |
| 58 | label: "Open on Diffinery", |
| 59 | icon: "link-external", |
| 60 | run: (branch: string) => { |
| 61 | const base = `${server}/${ref.owner}/${ref.repo}`; |
| 62 | void vscode.env.openExternal(vscode.Uri.parse(branch ? `${base}/tree/${branch.split("/").map(encodeURIComponent).join("/")}` : base)); |
| 63 | }, |
| 64 | }, |
| 65 | ]; |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | function source(r: RepoJson): RemoteSource { |
| 70 | const tags = [r.private ? "Private" : "Public", r.archived ? "archived" : "", r.fork ? "fork" : ""].filter(Boolean); |
| 71 | return { name: r.full_name, icon: "diffinery-logo", description: tags.join(" · "), detail: r.description || undefined, url: r.clone_url }; |
| 72 | } |