From 009ee3bf9c915b2abc2eda8d672a81db028ce077 Mon Sep 17 00:00:00 2001 From: tiddlygit-test Date: Sat, 16 Oct 2021 17:04:35 +0800 Subject: [PATCH] feat: openInGitGuiApp --- localization/locales/en/translation.json | 3 +- localization/locales/zh_CN/translation.json | 3 +- src/services/native/externalApp/darwin.ts | 34 +++++++- src/services/native/externalApp/linux.ts | 24 ++++++ src/services/native/externalApp/lookup.ts | 81 ++++++++++++++++--- src/services/native/externalApp/win32.ts | 41 +++++++++- src/services/native/index.ts | 11 ++- src/services/native/interface.ts | 2 + .../workspaces/getWorkspaceMenuTemplate.ts | 6 +- 9 files changed, 185 insertions(+), 20 deletions(-) diff --git a/localization/locales/en/translation.json b/localization/locales/en/translation.json index 89566ed5..2e5e6605 100644 --- a/localization/locales/en/translation.json +++ b/localization/locales/en/translation.json @@ -14,7 +14,8 @@ "WakeUpWorkspace": "WakeUp Workspace", "OpenWorkspaceFolder": "Open Folder", "ReloadCurrentWorkspace": "Reload Current Workspace", - "OpenWorkspaceFolderInEditor": "Open Folder In External Editor" + "OpenWorkspaceFolderInEditor": "Open Folder In External Editor", + "OpenWorkspaceFolderInGitGUI": "Open in Git GUI" }, "ContextMenu": { "OpenTiddlyGit": "Open TiddlyGit", diff --git a/localization/locales/zh_CN/translation.json b/localization/locales/zh_CN/translation.json index 5cfab8c4..52a8829f 100644 --- a/localization/locales/zh_CN/translation.json +++ b/localization/locales/zh_CN/translation.json @@ -15,7 +15,8 @@ "AreYouSure": "你确定要移除这个工作区吗?移除工作区会删除本应用中的工作区,但不会删除硬盘上的文件夹。如果你选择一并删除Wiki文件夹,则所有内容都会被被删除。", "BadWorkspacePath": "工作区路径有问题", "OpenWorkspaceFolder": "打开文件夹", - "OpenWorkspaceFolderInEditor": "用外部编辑器打开文件夹" + "OpenWorkspaceFolderInEditor": "用外部编辑器打开文件夹", + "OpenWorkspaceFolderInGitGUI": "用可视化Git工具打开" }, "ContextMenu": { "OpenTiddlyGit": "打开太记", diff --git a/src/services/native/externalApp/darwin.ts b/src/services/native/externalApp/darwin.ts index ba6c8bfb..7457e644 100644 --- a/src/services/native/externalApp/darwin.ts +++ b/src/services/native/externalApp/darwin.ts @@ -1,6 +1,7 @@ import { pathExists } from 'fs-extra'; import { IFoundEditor } from './found-editor'; import appPath from 'app-path'; +import { logger } from '@services/libs/log'; /** Represents an external editor on macOS */ interface IDarwinExternalEditor { @@ -117,6 +118,17 @@ const editors: IDarwinExternalEditor[] = [ }, ]; +/** + * This list contains all the external git GUI app supported on macOS. Add a new + * entry here to add support for your favorite git GUI app. + **/ +const gitGUIApp: IDarwinExternalEditor[] = [ + { + name: 'GitHub Desktop', + bundleIdentifiers: ['com.github.GitHubClient'], + }, +]; + async function findApplication(editor: IDarwinExternalEditor): Promise { for (const identifier of editor.bundleIdentifiers) { try { @@ -135,9 +147,9 @@ async function findApplication(editor: IDarwinExternalEditor): Promise>> { + const results: Array> = []; + + for (const guiApp of gitGUIApp) { + const path = await findApplication(guiApp); + + if (path) { + results.push({ editor: guiApp.name, path }); + } + } + + return results; +} diff --git a/src/services/native/externalApp/linux.ts b/src/services/native/externalApp/linux.ts index 3e8c7417..d1b52ced 100644 --- a/src/services/native/externalApp/linux.ts +++ b/src/services/native/externalApp/linux.ts @@ -56,6 +56,17 @@ const editors: ILinuxExternalEditor[] = [ }, ]; +/** + * This list contains all the external git GUI app supported on Linux. Add a new + * entry here to add support for your favorite git GUI app. + **/ +const gitGUIApp: ILinuxExternalEditor[] = [ + { + name: 'GitHub Desktop', + paths: ['/snap/bin/desktop', '/usr/bin/desktop'], + }, +]; + async function getAvailablePath(paths: string[]): Promise { for (const path of paths) { if (await pathExists(path)) { @@ -78,3 +89,16 @@ export async function getAvailableEditors(): Promise>> { + const results: Array> = []; + + for (const guiApp of gitGUIApp) { + const path = await getAvailablePath(guiApp.paths); + if (path) { + results.push({ editor: guiApp.name, path }); + } + } + + return results; +} diff --git a/src/services/native/externalApp/lookup.ts b/src/services/native/externalApp/lookup.ts index 430b07a0..066f499b 100644 --- a/src/services/native/externalApp/lookup.ts +++ b/src/services/native/externalApp/lookup.ts @@ -1,17 +1,19 @@ import { ExternalEditorError } from './shared'; import { IFoundEditor } from './found-editor'; -import { getAvailableEditors as getAvailableEditorsDarwin } from './darwin'; -import { getAvailableEditors as getAvailableEditorsWindows } from './win32'; -import { getAvailableEditors as getAvailableEditorsLinux } from './linux'; +import { getAvailableEditors as getAvailableEditorsDarwin, getAvailableGitGUIApps as getAvailableGitGUIAppsDarwin } from './darwin'; +import { getAvailableEditors as getAvailableEditorsWindows, getAvailableGitGUIApps as getAvailableGitGUIAppsWindows } from './win32'; +import { getAvailableEditors as getAvailableEditorsLinux, getAvailableGitGUIApps as getAvailableGitGUIAppsLinux } from './linux'; +import { logger } from '@services/libs/log'; -let editorCache: ReadonlyArray> | null = null; +let editorCache: ReadonlyArray> | undefined; +let gitGUIAppCache: ReadonlyArray> | undefined; /** * Resolve a list of installed editors on the user's machine, using the known * install identifiers that each OS supports. */ export async function getAvailableEditors(): Promise>> { - if (editorCache != undefined && editorCache.length > 0) { + if (editorCache !== undefined && editorCache.length > 0) { return editorCache; } @@ -30,7 +32,7 @@ export async function getAvailableEditors(): Promise | null> { +export async function findEditorOrDefault(name?: string): Promise | undefined> { const editors = await getAvailableEditors(); if (editors.length === 0) { - return null; + return; } - if (name) { - const match = editors.find((p) => p.editor === name) != undefined || null; - if (match == undefined) { + if (name !== undefined) { + const match = editors.find((p) => p.editor === name); + if (match === undefined) { const menuItemName = process.platform === 'darwin' ? 'Preferences' : 'Options'; const message = `The editor '${name}' could not be found. Please open ${menuItemName} and choose an available editor.`; @@ -62,3 +64,60 @@ export async function findEditorOrDefault(name?: string): Promise>> { + if (gitGUIAppCache !== undefined && gitGUIAppCache.length > 0) { + return gitGUIAppCache; + } + + if (process.platform === 'darwin') { + gitGUIAppCache = await getAvailableGitGUIAppsDarwin(); + return gitGUIAppCache; + } + + if (process.platform === 'win32') { + gitGUIAppCache = await getAvailableGitGUIAppsWindows(); + return gitGUIAppCache; + } + + if (process.platform === 'linux') { + gitGUIAppCache = await getAvailableGitGUIAppsLinux(); + return gitGUIAppCache; + } + + logger.warn(`Platform not currently supported for resolving gitGUIApps: ${process.platform}`); + + return []; +} + +/** + * Find an git GUI app installed on the machine using the friendly name, or the + * first valid git GUI app if `undefined` is provided. + * + * Will throw an error if no git GUI app are found, or if the editor name cannot + * be found (i.e. it has been removed). + */ +export async function findGitGUIAppOrDefault(name?: string): Promise | undefined> { + const gitGUIApps = await getAvailableGitGUIApps(); + if (gitGUIApps.length === 0) { + return; + } + + if (name !== undefined) { + const match = gitGUIApps.find((p) => p.editor === name); + if (match === undefined) { + const menuItemName = process.platform === 'darwin' ? 'Preferences' : 'Options'; + const message = `The gitGUIApp '${name}' could not be found. Please open ${menuItemName} and choose an available gitGUIApp.`; + + throw new ExternalEditorError(message, { openPreferences: true }); + } + + return match; + } + + return gitGUIApps[0]; +} diff --git a/src/services/native/externalApp/win32.ts b/src/services/native/externalApp/win32.ts index 88e39c5a..4669358f 100644 --- a/src/services/native/externalApp/win32.ts +++ b/src/services/native/externalApp/win32.ts @@ -4,6 +4,7 @@ import { enumerateValues, HKEY, RegistryValue, RegistryValueType } from 'registr import { pathExists } from 'fs-extra'; import { IFoundEditor } from './found-editor'; +import { logger } from '@services/libs/log'; interface IWindowsAppInformation { displayName: string; @@ -317,6 +318,20 @@ const editors: WindowsExternalEditor[] = [ }, ]; +/** + * This list contains all the external git GUI app supported on Windows. Add a new + * entry here to add support for your favorite git GUI app. + **/ +const gitGUIApp: WindowsExternalEditor[] = [ + { + name: 'GitHub Desktop', + registryKeys: [CurrentUserUninstallKey('desktop')], + executableShimPaths: [['bin', 'desktop.cmd']], + displayNamePrefix: 'Desktop', + publisher: 'GitHub Inc.', + }, +]; + function getKeyOrEmpty(keys: readonly RegistryValue[], key: string): string { const entry = keys.find((k) => k.name === key); return entry && entry.type === RegistryValueType.REG_SZ ? entry.data : ''; @@ -339,7 +354,7 @@ async function findApplication(editor: WindowsExternalEditor) { const { displayName, publisher, installLocation } = getAppInfo(editor, keys); if (!displayName.startsWith(editor.displayNamePrefix) || publisher !== editor.publisher) { - log.debug(`Unexpected registry entries for ${editor.name}`); + logger.debug(`Unexpected registry entries for ${editor.name}`); continue; } @@ -352,7 +367,7 @@ async function findApplication(editor: WindowsExternalEditor) { return path; } - log.debug(`Executable for ${editor.name} not found at '${path}'`); + logger.debug(`Executable for ${editor.name} not found at '${path}'`); } } @@ -380,3 +395,25 @@ export async function getAvailableEditors(): Promise>> { + const results: Array> = []; + + for (const guiApp of gitGUIApp) { + const path = await findApplication(guiApp); + + if (path) { + results.push({ + editor: guiApp.name, + path, + usesShell: path.endsWith('.cmd'), + }); + } + } + + return results; +} diff --git a/src/services/native/index.ts b/src/services/native/index.ts index a2983dbc..3c40673a 100644 --- a/src/services/native/index.ts +++ b/src/services/native/index.ts @@ -16,7 +16,7 @@ import type { IZxFileInput, ZxWorker } from './zxWorker'; import { ZX_FOLDER } from '@/constants/paths'; import { logger } from '@services/libs/log'; import { ZxInitializationError, ZxInitializationRetryFailedError, ZxNotInitializedError } from './error'; -import { findEditorOrDefault, launchExternalEditor } from './externalApp'; +import { findEditorOrDefault, findGitGUIAppOrDefault, launchExternalEditor } from './externalApp'; @injectable() export class NativeService implements INativeService { @@ -45,7 +45,14 @@ export class NativeService implements INativeService { public async openInEditor(filePath: string, editorName?: string): Promise { const defaultEditor = await findEditorOrDefault(editorName); - if (defaultEditor !== null) { + if (defaultEditor !== undefined) { + await launchExternalEditor(filePath, defaultEditor); + } + } + + public async openInGitGuiApp(filePath: string, editorName?: string): Promise { + const defaultEditor = await findGitGUIAppOrDefault(editorName); + if (defaultEditor !== undefined) { await launchExternalEditor(filePath, defaultEditor); } } diff --git a/src/services/native/interface.ts b/src/services/native/interface.ts index 3f8d77f3..ce8529d5 100644 --- a/src/services/native/interface.ts +++ b/src/services/native/interface.ts @@ -13,6 +13,7 @@ export interface INativeService { executeZxScript$(zxWorkerArguments: IZxFileInput): Observable; open(uri: string, isDirectory?: boolean): Promise; openInEditor(filePath: string, editorName?: string | undefined): Promise; + openInGitGuiApp(filePath: string, editorName?: string | undefined): Promise; pickDirectory(defaultPath?: string): Promise; pickFile(filters?: Electron.OpenDialogOptions['filters']): Promise; quit(): void; @@ -24,6 +25,7 @@ export const NativeServiceIPCDescriptor = { executeZxScript$: ProxyPropertyType.Function$, open: ProxyPropertyType.Function, openInEditor: ProxyPropertyType.Function, + openInGitGuiApp: ProxyPropertyType.Function, pickDirectory: ProxyPropertyType.Function, pickFile: ProxyPropertyType.Function, quit: ProxyPropertyType.Function, diff --git a/src/services/workspaces/getWorkspaceMenuTemplate.ts b/src/services/workspaces/getWorkspaceMenuTemplate.ts index 1ece09ea..dddc7875 100644 --- a/src/services/workspaces/getWorkspaceMenuTemplate.ts +++ b/src/services/workspaces/getWorkspaceMenuTemplate.ts @@ -10,7 +10,7 @@ import type { IWikiService } from '@services/wiki/interface'; import type { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface'; interface IWorkspaceMenuRequiredServices { - native: Pick; + native: Pick; view: Pick; wiki: Pick; wikiGitWorkspace: Pick; @@ -67,6 +67,10 @@ export function getWorkspaceMenuTemplate(workspace: IWorkspace, t: TFunction, se label: t('WorkspaceSelector.OpenWorkspaceFolderInEditor'), click: async () => await service.native.openInEditor(wikiFolderLocation), }, + { + label: t('WorkspaceSelector.OpenWorkspaceFolderInGitGUI'), + click: async () => await service.native.openInGitGuiApp(wikiFolderLocation), + }, { label: t('ContextMenu.Reload'), click: async () => await service.view.reloadViewsWebContents(id),