feat: openInGitGuiApp

This commit is contained in:
tiddlygit-test 2021-10-16 17:04:35 +08:00
parent d0e21361a2
commit 009ee3bf9c
9 changed files with 185 additions and 20 deletions

View file

@ -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",

View file

@ -15,7 +15,8 @@
"AreYouSure": "你确定要移除这个工作区吗移除工作区会删除本应用中的工作区但不会删除硬盘上的文件夹。如果你选择一并删除Wiki文件夹则所有内容都会被被删除。",
"BadWorkspacePath": "工作区路径有问题",
"OpenWorkspaceFolder": "打开文件夹",
"OpenWorkspaceFolderInEditor": "用外部编辑器打开文件夹"
"OpenWorkspaceFolderInEditor": "用外部编辑器打开文件夹",
"OpenWorkspaceFolderInGitGUI": "用可视化Git工具打开"
},
"ContextMenu": {
"OpenTiddlyGit": "打开太记",

View file

@ -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<string | null> {
for (const identifier of editor.bundleIdentifiers) {
try {
@ -135,9 +147,9 @@ async function findApplication(editor: IDarwinExternalEditor): Promise<string |
return installPath;
}
log.debug(`App installation for ${editor.name} not found at '${installPath}'`);
logger.debug(`App installation for ${editor.name} not found at '${installPath}'`);
} catch (error) {
log.debug(`Unable to locate ${editor.name} installation`, error);
logger.debug(`Unable to locate ${editor.name} installation`, error);
}
}
@ -161,3 +173,21 @@ export async function getAvailableEditors(): Promise<ReadonlyArray<IFoundEditor<
return results;
}
/**
* Lookup known external git GUI app using the bundle ID that each uses
* to register itself on a user's machine when installing.
*/
export async function getAvailableGitGUIApps(): Promise<ReadonlyArray<IFoundEditor<string>>> {
const results: Array<IFoundEditor<string>> = [];
for (const guiApp of gitGUIApp) {
const path = await findApplication(guiApp);
if (path) {
results.push({ editor: guiApp.name, path });
}
}
return results;
}

View file

@ -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<string | null> {
for (const path of paths) {
if (await pathExists(path)) {
@ -78,3 +89,16 @@ export async function getAvailableEditors(): Promise<ReadonlyArray<IFoundEditor<
return results;
}
export async function getAvailableGitGUIApps(): Promise<ReadonlyArray<IFoundEditor<string>>> {
const results: Array<IFoundEditor<string>> = [];
for (const guiApp of gitGUIApp) {
const path = await getAvailablePath(guiApp.paths);
if (path) {
results.push({ editor: guiApp.name, path });
}
}
return results;
}

View file

@ -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<IFoundEditor<string>> | null = null;
let editorCache: ReadonlyArray<IFoundEditor<string>> | undefined;
let gitGUIAppCache: ReadonlyArray<IFoundEditor<string>> | 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<ReadonlyArray<IFoundEditor<string>>> {
if (editorCache != undefined && editorCache.length > 0) {
if (editorCache !== undefined && editorCache.length > 0) {
return editorCache;
}
@ -30,7 +32,7 @@ export async function getAvailableEditors(): Promise<ReadonlyArray<IFoundEditor<
return editorCache;
}
log.warn(`Platform not currently supported for resolving editors: ${process.platform}`);
logger.warn(`Platform not currently supported for resolving editors: ${process.platform}`);
return [];
}
@ -42,15 +44,15 @@ export async function getAvailableEditors(): Promise<ReadonlyArray<IFoundEditor<
* Will throw an error if no editors are found, or if the editor name cannot
* be found (i.e. it has been removed).
*/
export async function findEditorOrDefault(name?: string): Promise<IFoundEditor<string> | null> {
export async function findEditorOrDefault(name?: string): Promise<IFoundEditor<string> | 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<IFoundEditor<s
return editors[0];
}
/**
* Resolve a list of installed git GUI app on the user's machine, using the known
* install identifiers that each OS supports.
*/
export async function getAvailableGitGUIApps(): Promise<ReadonlyArray<IFoundEditor<string>>> {
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<IFoundEditor<string> | 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];
}

View file

@ -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<ReadonlyArray<IFoundEditor<
return results;
}
/**
* Lookup known external git GUI app using the Windows registry to find installed
* applications and their location on disk for Desktop to launch.
*/
export async function getAvailableGitGUIApps(): Promise<ReadonlyArray<IFoundEditor<string>>> {
const results: Array<IFoundEditor<string>> = [];
for (const guiApp of gitGUIApp) {
const path = await findApplication(guiApp);
if (path) {
results.push({
editor: guiApp.name,
path,
usesShell: path.endsWith('.cmd'),
});
}
}
return results;
}

View file

@ -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<void> {
const defaultEditor = await findEditorOrDefault(editorName);
if (defaultEditor !== null) {
if (defaultEditor !== undefined) {
await launchExternalEditor(filePath, defaultEditor);
}
}
public async openInGitGuiApp(filePath: string, editorName?: string): Promise<void> {
const defaultEditor = await findGitGUIAppOrDefault(editorName);
if (defaultEditor !== undefined) {
await launchExternalEditor(filePath, defaultEditor);
}
}

View file

@ -13,6 +13,7 @@ export interface INativeService {
executeZxScript$(zxWorkerArguments: IZxFileInput): Observable<string>;
open(uri: string, isDirectory?: boolean): Promise<void>;
openInEditor(filePath: string, editorName?: string | undefined): Promise<void>;
openInGitGuiApp(filePath: string, editorName?: string | undefined): Promise<void>;
pickDirectory(defaultPath?: string): Promise<string[]>;
pickFile(filters?: Electron.OpenDialogOptions['filters']): Promise<string[]>;
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,

View file

@ -10,7 +10,7 @@ import type { IWikiService } from '@services/wiki/interface';
import type { IWikiGitWorkspaceService } from '@services/wikiGitWorkspace/interface';
interface IWorkspaceMenuRequiredServices {
native: Pick<INativeService, 'open' | 'openInEditor'>;
native: Pick<INativeService, 'open' | 'openInEditor' | 'openInGitGuiApp'>;
view: Pick<IViewService, 'reloadViewsWebContents'>;
wiki: Pick<IWikiService, 'requestOpenTiddlerInWiki' | 'requestWikiSendActionMessage'>;
wikiGitWorkspace: Pick<IWikiGitWorkspaceService, 'removeWorkspace'>;
@ -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),