mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-02-23 16:40:50 -08:00
feat: openInGitGuiApp
This commit is contained in:
parent
d0e21361a2
commit
009ee3bf9c
9 changed files with 185 additions and 20 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
"AreYouSure": "你确定要移除这个工作区吗?移除工作区会删除本应用中的工作区,但不会删除硬盘上的文件夹。如果你选择一并删除Wiki文件夹,则所有内容都会被被删除。",
|
||||
"BadWorkspacePath": "工作区路径有问题",
|
||||
"OpenWorkspaceFolder": "打开文件夹",
|
||||
"OpenWorkspaceFolderInEditor": "用外部编辑器打开文件夹"
|
||||
"OpenWorkspaceFolderInEditor": "用外部编辑器打开文件夹",
|
||||
"OpenWorkspaceFolderInGitGUI": "用可视化Git工具打开"
|
||||
},
|
||||
"ContextMenu": {
|
||||
"OpenTiddlyGit": "打开太记",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue