From 796973487f7f6bf80a1e7c1d675bec026aa594b7 Mon Sep 17 00:00:00 2001
From: Dennis Huebner <dennis.huebner@gmail.com>
Date: Mon, 22 Apr 2024 16:17:02 +0200
Subject: [PATCH] Add a new built-in handler to open files with system
 application #13535 (#13601)

---
 packages/core/src/electron-browser/preload.ts |  5 +-
 .../core/src/electron-common/electron-api.ts  |  2 +
 .../src/electron-main/electron-api-main.ts    |  7 ++-
 .../electron-navigator-menu-contribution.ts   | 47 +++++++++++++++----
 4 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/packages/core/src/electron-browser/preload.ts b/packages/core/src/electron-browser/preload.ts
index d7f6f30aa4ee0..b0903d1a764af 100644
--- a/packages/core/src/electron-browser/preload.ts
+++ b/packages/core/src/electron-browser/preload.ts
@@ -26,7 +26,7 @@ import {
     CHANNEL_IS_FULL_SCREEN, CHANNEL_SET_MENU_BAR_VISIBLE, CHANNEL_REQUEST_CLOSE, CHANNEL_SET_TITLE_STYLE, CHANNEL_RESTART,
     CHANNEL_REQUEST_RELOAD, CHANNEL_APP_STATE_CHANGED, CHANNEL_SHOW_ITEM_IN_FOLDER, CHANNEL_READ_CLIPBOARD, CHANNEL_WRITE_CLIPBOARD,
     CHANNEL_KEYBOARD_LAYOUT_CHANGED, CHANNEL_IPC_CONNECTION, InternalMenuDto, CHANNEL_REQUEST_SECONDARY_CLOSE, CHANNEL_SET_BACKGROUND_COLOR,
-    CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE
+    CHANNEL_WC_METADATA, CHANNEL_ABOUT_TO_CLOSE, CHANNEL_OPEN_WITH_SYSTEM_APP
 } from '../electron-common/electron-api';
 
 // eslint-disable-next-line import/no-extraneous-dependencies
@@ -79,6 +79,9 @@ const api: TheiaCoreAPI = {
     showItemInFolder: fsPath => {
         ipcRenderer.send(CHANNEL_SHOW_ITEM_IN_FOLDER, fsPath);
     },
+    openWithSystemApp: fsPath => {
+        ipcRenderer.send(CHANNEL_OPEN_WITH_SYSTEM_APP, fsPath);
+    },
     attachSecurityToken: (endpoint: string) => ipcRenderer.invoke(CHANNEL_ATTACH_SECURITY_TOKEN, endpoint),
 
     popup: async function (menu: MenuDto[], x: number, y: number, onClosed: () => void, windowName?: string): Promise<number> {
diff --git a/packages/core/src/electron-common/electron-api.ts b/packages/core/src/electron-common/electron-api.ts
index 6abe167e8a784..6bcde6a4fb333 100644
--- a/packages/core/src/electron-common/electron-api.ts
+++ b/packages/core/src/electron-common/electron-api.ts
@@ -56,6 +56,7 @@ export interface TheiaCoreAPI {
     focusWindow(name?: string): void;
 
     showItemInFolder(fsPath: string): void;
+    openWithSystemApp(fsPath: string): void;
 
     getTitleBarStyleAtStartup(): Promise<string>;
     setTitleBarStyle(style: string): void;
@@ -112,6 +113,7 @@ export const CHANNEL_FOCUS_WINDOW = 'FocusWindow';
 export const CHANNEL_SHOW_OPEN = 'ShowOpenDialog';
 export const CHANNEL_SHOW_SAVE = 'ShowSaveDialog';
 export const CHANNEL_SHOW_ITEM_IN_FOLDER = 'ShowItemInFolder';
+export const CHANNEL_OPEN_WITH_SYSTEM_APP = 'OpenWithSystemApp';
 export const CHANNEL_ATTACH_SECURITY_TOKEN = 'AttachSecurityToken';
 
 export const CHANNEL_GET_TITLE_STYLE_AT_STARTUP = 'GetTitleStyleAtStartup';
diff --git a/packages/core/src/electron-main/electron-api-main.ts b/packages/core/src/electron-main/electron-api-main.ts
index 485d917c9a288..d5966536dd470 100644
--- a/packages/core/src/electron-main/electron-api-main.ts
+++ b/packages/core/src/electron-main/electron-api-main.ts
@@ -53,7 +53,8 @@ import {
     CHANNEL_REQUEST_SECONDARY_CLOSE,
     CHANNEL_SET_BACKGROUND_COLOR,
     CHANNEL_WC_METADATA,
-    CHANNEL_ABOUT_TO_CLOSE
+    CHANNEL_ABOUT_TO_CLOSE,
+    CHANNEL_OPEN_WITH_SYSTEM_APP
 } from '../electron-common/electron-api';
 import { ElectronMainApplication, ElectronMainApplicationContribution } from './electron-main-application';
 import { Disposable, DisposableCollection, isOSX, MaybePromise } from '../common';
@@ -164,6 +165,10 @@ export class TheiaMainApi implements ElectronMainApplicationContribution {
             shell.showItemInFolder(fsPath);
         });
 
+        ipcMain.on(CHANNEL_OPEN_WITH_SYSTEM_APP, (event, fsPath) => {
+            shell.openPath(fsPath);
+        });
+
         ipcMain.handle(CHANNEL_GET_TITLE_STYLE_AT_STARTUP, event => application.getTitleBarStyleAtStartup(event.sender));
 
         ipcMain.on(CHANNEL_SET_TITLE_STYLE, (event, style) => application.setTitleBarStyle(event.sender, style));
diff --git a/packages/navigator/src/electron-browser/electron-navigator-menu-contribution.ts b/packages/navigator/src/electron-browser/electron-navigator-menu-contribution.ts
index 6e12555784473..3020bd5f4c335 100644
--- a/packages/navigator/src/electron-browser/electron-navigator-menu-contribution.ts
+++ b/packages/navigator/src/electron-browser/electron-navigator-menu-contribution.ts
@@ -14,17 +14,19 @@
 // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
 // *****************************************************************************
 
-import { Command, CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry, SelectionService } from '@theia/core';
-import { CommonCommands, KeybindingContribution, KeybindingRegistry } from '@theia/core/lib/browser';
+import { Command, CommandContribution, CommandRegistry, MenuContribution, MenuModelRegistry, SelectionService, URI } from '@theia/core';
+import { CommonCommands, KeybindingContribution, KeybindingRegistry, OpenWithService } from '@theia/core/lib/browser';
 import { WidgetManager } from '@theia/core/lib/browser/widget-manager';
+import { nls } from '@theia/core/lib/common';
+import { FileUri } from '@theia/core/lib/common/file-uri';
+import { isOSX, isWindows } from '@theia/core/lib/common/os';
+import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
+import '@theia/core/lib/electron-common/electron-api';
 import { inject, injectable } from '@theia/core/shared/inversify';
 import { FileStatNode } from '@theia/filesystem/lib/browser';
-import { FileNavigatorWidget, FILE_NAVIGATOR_ID } from '../browser';
-import { NavigatorContextMenu, SHELL_TABBAR_CONTEXT_REVEAL } from '../browser/navigator-contribution';
-import { isWindows, isOSX } from '@theia/core/lib/common/os';
 import { WorkspaceService } from '@theia/workspace/lib/browser';
-import { UriAwareCommandHandler } from '@theia/core/lib/common/uri-command-handler';
-import '@theia/core/lib/electron-common/electron-api';
+import { FILE_NAVIGATOR_ID, FileNavigatorWidget } from '../browser';
+import { NavigatorContextMenu, SHELL_TABBAR_CONTEXT_REVEAL } from '../browser/navigator-contribution';
 
 export const OPEN_CONTAINING_FOLDER = Command.toDefaultLocalizedCommand({
     id: 'revealFileInOS',
@@ -34,6 +36,12 @@ export const OPEN_CONTAINING_FOLDER = Command.toDefaultLocalizedCommand({
         /* linux */ 'Open Containing Folder'
 });
 
+export const OPEN_WITH_SYSTEM_APP = Command.toDefaultLocalizedCommand({
+    id: 'openWithSystemApp',
+    category: CommonCommands.FILE_CATEGORY,
+    label: 'Open With System Editor'
+});
+
 @injectable()
 export class ElectronNavigatorMenuContribution implements MenuContribution, CommandContribution, KeybindingContribution {
 
@@ -46,14 +54,37 @@ export class ElectronNavigatorMenuContribution implements MenuContribution, Comm
     @inject(WorkspaceService)
     protected readonly workspaceService: WorkspaceService;
 
+    @inject(OpenWithService)
+    protected readonly openWithService: OpenWithService;
+
     registerCommands(commands: CommandRegistry): void {
         commands.registerCommand(OPEN_CONTAINING_FOLDER, UriAwareCommandHandler.MonoSelect(this.selectionService, {
             execute: async uri => {
-                window.electronTheiaCore.showItemInFolder(uri['codeUri'].fsPath);
+                window.electronTheiaCore.showItemInFolder(FileUri.fsPath(uri));
             },
             isEnabled: uri => !!this.workspaceService.getWorkspaceRootUri(uri),
             isVisible: uri => !!this.workspaceService.getWorkspaceRootUri(uri),
         }));
+        commands.registerCommand(OPEN_WITH_SYSTEM_APP, UriAwareCommandHandler.MonoSelect(this.selectionService, {
+            execute: async uri => {
+                this.openWithSystemApplication(uri);
+            }
+        }));
+        this.openWithService.registerHandler({
+            id: 'system-editor',
+            label: nls.localize('theia/navigator/systemEditor', 'System Editor'),
+            providerName: nls.localizeByDefault('Built-in'),
+            // Low priority to avoid conflicts with other open handlers.
+            canHandle: uri => (uri.scheme === 'file') ? 10 : 0,
+            open: uri => {
+                this.openWithSystemApplication(uri);
+                return {};
+            }
+        });
+    }
+
+    protected openWithSystemApplication(uri: URI): void {
+        window.electronTheiaCore.openWithSystemApp(FileUri.fsPath(uri));
     }
 
     registerMenus(menus: MenuModelRegistry): void {