69 |
70 |

71 |
72 |
73 |
CodeFuse IDE 有新版本更新
74 |
CodeFuse IDE {updateInfo.version} 可供下载,您现在的版本是 {process.env.IDE_VERSION}。
75 |
更新日志:
76 |
{
80 | const target = e.target as HTMLAnchorElement;
81 | if (target && target.tagName === 'A' && target.href) {
82 | shell.openExternal(target.href);
83 | e.preventDefault();
84 | }
85 | }}
86 | />
87 |
88 |
89 | {updateState === UpdateState.Downloading ? `正在下载更新 (${progressPercent}%) ...` : ''}
90 | {updateState === UpdateState.Downloaded ? '下载完成,准备重启' : ''}
91 | {updateState === UpdateState.DownloadError ? '下载失败,请稍后重试' : ''}
92 | {updateState === UpdateState.UpdateError ? '更新失败,请稍后重试' : ''}
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | )
102 | }
103 |
--------------------------------------------------------------------------------
/src/auto-updater/update-window/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
CodeFuse IDE Update
6 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/auto-updater/update-window/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createRoot } from 'react-dom/client'
3 | import { UpdateView } from './UpdateView'
4 |
5 | createRoot(document.getElementById('main')!).render(
)
6 |
--------------------------------------------------------------------------------
/src/auto-updater/update-window/style.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | min-height: 0;
4 | font-size: 14px;
5 | padding: 32px;
6 | height: 100%;
7 | box-sizing: border-box;
8 | &.error {
9 | justify-content: center;
10 | align-items: center;
11 | }
12 | }
13 |
14 | .icon {
15 | width: 52px;
16 | height: 52px;
17 | img {
18 | width: 100%;
19 | }
20 | }
21 |
22 | .body {
23 | display: flex;
24 | flex: 1;
25 | flex-direction: column;
26 | padding-left: 20px;
27 | }
28 |
29 | .title {
30 | margin-bottom: 4px;
31 | }
32 |
33 | .subtitle {
34 | margin-bottom: 8px;
35 | }
36 |
37 | .changelogTitle {
38 | margin-bottom: 12px;
39 | }
40 |
41 | .pageLoading {
42 | display: flex;
43 | flex-direction: column;
44 | height: 100%;
45 | align-items: center;
46 | justify-content: center;
47 | }
48 |
49 | .changelog {
50 | padding: 16px;
51 | flex: 1;
52 | min-height: 0;
53 | overflow: auto;
54 | border-radius: 8px;
55 | background-color: #f0f2f5;
56 | margin-bottom: 16px;
57 | li {
58 | margin: 0;
59 | padding: 0;
60 | }
61 | ul {
62 | padding-left: 15px;
63 | }
64 | }
65 |
66 | .footer {
67 | display: flex;
68 | justify-content: space-between;
69 | align-items: center;
70 | button {
71 | border: none;
72 | outline: none;
73 | cursor: pointer;
74 | display: inline-flex;
75 | justify-content: center;
76 | align-items: center;
77 | padding: 0px 12px;
78 | height: 28px;
79 | line-height: 1;
80 | box-sizing: border-box;
81 | border-radius: 4px;
82 | white-space: pre-wrap;
83 | color: #000a1aff;
84 | background-color: #fafafb;
85 | &:hover {
86 | background-color: #F5F5F6;
87 | }
88 | &.installBtn {
89 | color: #FFF;
90 | margin-left: 8px;
91 | background-color: #3c8dff;
92 | &:hover {
93 | background-color: rgba(60, 141, 255, 0.65);
94 | }
95 | &:disabled {
96 | background-color: rgba(21, 27, 33, 0.04);
97 | color: rgba(21, 27, 33, 0.35);
98 | cursor: not-allowed;
99 | }
100 | }
101 | }
102 | .progress {
103 | &.error {
104 | color: rgb(218, 80, 21);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/common-modules.ts:
--------------------------------------------------------------------------------
1 | import { MainLayoutModule } from '@opensumi/ide-main-layout/lib/browser';
2 | import { MenuBarModule } from '@opensumi/ide-menu-bar/lib/browser';
3 | import { MonacoModule } from '@opensumi/ide-monaco/lib/browser';
4 | import { WorkspaceModule } from '@opensumi/ide-workspace/lib/browser';
5 | import { StatusBarModule } from '@opensumi/ide-status-bar/lib/browser';
6 | import { EditorModule } from '@opensumi/ide-editor/lib/browser';
7 | import { ExplorerModule } from '@opensumi/ide-explorer/lib/browser';
8 | import { FileTreeNextModule } from '@opensumi/ide-file-tree-next/lib/browser';
9 | import { FileServiceClientModule } from '@opensumi/ide-file-service/lib/browser';
10 | import { SearchModule } from '@opensumi/ide-search/lib/browser';
11 | import { FileSchemeModule } from '@opensumi/ide-file-scheme/lib/browser';
12 | import { OutputModule } from '@opensumi/ide-output/lib/browser';
13 | import { QuickOpenModule } from '@opensumi/ide-quick-open/lib/browser';
14 | import { BrowserModule, ClientCommonModule, ConstructorOf } from '@opensumi/ide-core-browser';
15 | import { ThemeModule } from '@opensumi/ide-theme/lib/browser';
16 | import { OpenedEditorModule } from '@opensumi/ide-opened-editor/lib/browser';
17 | import { RemoteOpenerModule } from '@opensumi/ide-remote-opener/lib/browser';
18 | import { OutlineModule } from '@opensumi/ide-outline/lib/browser';
19 | import { PreferencesModule } from '@opensumi/ide-preferences/lib/browser';
20 | import { ToolbarModule } from '@opensumi/ide-toolbar/lib/browser';
21 | import { OverlayModule } from '@opensumi/ide-overlay/lib/browser';
22 | import { ExtensionStorageModule } from '@opensumi/ide-extension-storage/lib/browser';
23 | import { StorageModule } from '@opensumi/ide-storage/lib/browser';
24 | import { SCMModule } from '@opensumi/ide-scm/lib/browser';
25 | import { MarkersModule } from '@opensumi/ide-markers/lib/browser';
26 | import { WebviewModule } from '@opensumi/ide-webview';
27 | import { MarkdownModule } from '@opensumi/ide-markdown';
28 | import { LogModule } from '@opensumi/ide-logs/lib/browser';
29 | import { WorkspaceEditModule } from '@opensumi/ide-workspace-edit/lib/browser';
30 | import { ExtensionModule } from '@opensumi/ide-extension/lib/browser';
31 | import { DecorationModule } from '@opensumi/ide-decoration/lib/browser';
32 | import { DebugModule } from '@opensumi/ide-debug/lib/browser';
33 | import { VariableModule } from '@opensumi/ide-variable/lib/browser';
34 | import { KeymapsModule } from '@opensumi/ide-keymaps/lib/browser';
35 | import { MonacoEnhanceModule } from '@opensumi/ide-monaco-enhance/lib/browser/module';
36 | import { OpenVsxExtensionManagerModule } from '@opensumi/ide-extension-manager/lib/browser';
37 | import { TerminalNextModule } from '@opensumi/ide-terminal-next/lib/browser';
38 | import { CommentsModule } from '@opensumi/ide-comments/lib/browser';
39 | import { ClientAddonModule } from '@opensumi/ide-addons/lib/browser';
40 | import { TaskModule } from '@opensumi/ide-task/lib/browser';
41 | import { TestingModule } from '@opensumi/ide-testing/lib/browser';
42 | import {CoreBrowserModule} from "@/core/browser";
43 | import {DesignModule} from "@opensumi/ide-design/lib/browser";
44 | import {AINativeModule} from "@opensumi/ide-ai-native/lib/browser";
45 | import {AIFeatureModule} from "@/ai/browser";
46 | import {AutoUpdaterModule} from "@/auto-updater/browser";
47 |
48 | export const CommonBrowserModules: ConstructorOf
[] = [
49 | MainLayoutModule,
50 | OverlayModule,
51 | LogModule,
52 | ClientCommonModule,
53 | MenuBarModule,
54 | MonacoModule,
55 | StatusBarModule,
56 | EditorModule,
57 | ExplorerModule,
58 | FileTreeNextModule,
59 | FileServiceClientModule,
60 | SearchModule,
61 | FileSchemeModule,
62 | OutputModule,
63 | QuickOpenModule,
64 | MarkersModule,
65 | ThemeModule,
66 | WorkspaceModule,
67 | ExtensionStorageModule,
68 | StorageModule,
69 | OpenedEditorModule,
70 | OutlineModule,
71 | PreferencesModule,
72 | ToolbarModule,
73 | WebviewModule,
74 | MarkdownModule,
75 | WorkspaceEditModule,
76 | SCMModule,
77 | DecorationModule,
78 | DebugModule,
79 | VariableModule,
80 | KeymapsModule,
81 | TerminalNextModule,
82 | ExtensionModule,
83 | OpenVsxExtensionManagerModule,
84 | MonacoEnhanceModule,
85 | ClientAddonModule,
86 | CommentsModule,
87 | TaskModule,
88 | CoreBrowserModule,
89 | TestingModule, RemoteOpenerModule,
90 | // ai
91 | DesignModule,
92 | AINativeModule,
93 | AIFeatureModule,
94 | AutoUpdaterModule,
95 | ];
96 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/core-commands.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Autowired } from '@opensumi/di';
2 | import { CommandContribution, CommandRegistry, Domain, FILE_COMMANDS } from '@opensumi/ide-core-browser';
3 | import { IWindowDialogService } from '@opensumi/ide-overlay';
4 | import { IWorkspaceService } from '@opensumi/ide-workspace';
5 |
6 | @Injectable()
7 | @Domain(CommandContribution)
8 | export class CoreCommandContribution implements CommandContribution {
9 | @Autowired(IWindowDialogService)
10 | private window: IWindowDialogService;
11 |
12 | @Autowired(IWorkspaceService)
13 | private workspace: IWorkspaceService;
14 |
15 | registerCommands(commands: CommandRegistry) {
16 | commands.registerCommand(FILE_COMMANDS.OPEN_FOLDER, {
17 | execute: async () => {
18 | const newWorkspace = await this.window.showOpenDialog({
19 | canSelectFolders: true,
20 | canSelectMany: false,
21 | });
22 | if (newWorkspace) {
23 | if (this.workspace.workspace?.uri.toString() === newWorkspace[0].toString()) {
24 | return;
25 | }
26 | window.open(`${window.location.protocol}//${window.location.host}?workspaceDir=${newWorkspace[0].codeUri.fsPath.toString()}`);
27 | }
28 | }
29 | })
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/index.ts:
--------------------------------------------------------------------------------
1 | import '@opensumi/ide-i18n/lib/browser';
2 | import {ExpressFileServerModule} from '@opensumi/ide-express-file-server/lib/browser';
3 | import '@opensumi/ide-core-browser/lib/style/index.less';
4 | import '@opensumi/ide-core-browser/lib/style/icon.less';
5 |
6 | import {renderApp} from './render-app';
7 | import {CommonBrowserModules} from '@/bootstrap-web/browser/common-modules';
8 | import {layoutConfig} from './layout-config';
9 | import './main.less';
10 | import './styles.less';
11 | import {AILayout} from "@opensumi/ide-ai-native/lib/browser/layout/ai-layout";
12 | import {DEFAULT_LAYOUT_VIEW_SIZE} from "@opensumi/ide-core-browser/lib/layout/constants";
13 | import {AINativeSettingSectionsId} from "@opensumi/ide-core-common";
14 | import logo from '@/core/browser/assets/logo.svg'
15 |
16 |
17 | renderApp({
18 | modules: [
19 | ...CommonBrowserModules,
20 | ExpressFileServerModule,
21 | ],
22 | layoutConfig,
23 | layoutComponent: AILayout,
24 | layoutViewSize: {
25 | bigSurTitleBarHeight: DEFAULT_LAYOUT_VIEW_SIZE.menubarHeight,
26 | },
27 | useCdnIcon: false,
28 | useExperimentalShadowDom: false,
29 | defaultPreferences: {
30 | 'settings.userBeforeWorkspace': true,
31 | 'general.icon': 'vs-seti',
32 | [AINativeSettingSectionsId.IntelligentCompletionsPromptEngineeringEnabled]: false,
33 | // 总是显示智能提示
34 | [AINativeSettingSectionsId.IntelligentCompletionsAlwaysVisible]: true,
35 | },
36 | AINativeConfig: {
37 | layout: {
38 | menubarLogo: logo,
39 | }
40 | }
41 | });
42 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/layout-config.ts:
--------------------------------------------------------------------------------
1 | import { SlotLocation } from '@opensumi/ide-core-browser/lib/react-providers/slot';
2 | import { defaultConfig } from '@opensumi/ide-main-layout/lib/browser/default-config';
3 | import { DESIGN_MENUBAR_CONTAINER_VIEW_ID } from '@opensumi/ide-design/lib/common/constants';
4 | import {DESIGN_MENU_BAR_LEFT} from "@opensumi/ide-design";
5 | import {AI_MENU_BAR_LEFT_ACTION} from "@/ai/browser";
6 |
7 | export const layoutConfig = {
8 | ...defaultConfig,
9 | [SlotLocation.top]: {
10 | modules: [DESIGN_MENUBAR_CONTAINER_VIEW_ID],
11 | },
12 | [SlotLocation.bottom]: {
13 | modules: [
14 | '@opensumi/ide-terminal-next',
15 | '@opensumi/ide-output',
16 | 'debug-console',
17 | '@opensumi/ide-markers',
18 | '@opensumi/ide-refactor-preview',
19 | ],
20 | },
21 |
22 | [DESIGN_MENU_BAR_LEFT]: {
23 | modules: [AI_MENU_BAR_LEFT_ACTION]
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/main.less:
--------------------------------------------------------------------------------
1 | :root {
2 | --tabBar-height: 35px !important;
3 | }
4 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/render-app.ts:
--------------------------------------------------------------------------------
1 | import {Injector} from '@opensumi/di';
2 | import {IClientAppOpts} from '@opensumi/ide-core-browser';
3 | import {ClientApp} from '@opensumi/ide-core-browser/lib/bootstrap/app';
4 | import {ToolbarActionBasedLayout} from '@opensumi/ide-core-browser/lib/components';
5 | import logo from '@/core/browser/assets/logo.svg'
6 | import {CoreCommandContribution} from "@/bootstrap-web/browser/core-commands";
7 |
8 | export async function renderApp(opts: IClientAppOpts) {
9 | const injector = new Injector();
10 | injector.addProviders(CoreCommandContribution);
11 |
12 | const hostname = window.location.hostname;
13 | const query = new URLSearchParams(window.location.search);
14 | // 线上的静态服务和 IDE 后端是一个 Server
15 | const serverPort = process.env.DEVELOPMENT ? 8000 : window.location.port;
16 | const staticServerPort = process.env.DEVELOPMENT ? 8080 : window.location.port;
17 | const webviewEndpointPort = process.env.DEVELOPMENT ? 8899 : window.location.port;
18 | opts.appName= 'CodeFuse IDE';
19 | opts.workspaceDir = opts.workspaceDir || query.get('workspaceDir') || process.env.WORKSPACE_DIR;
20 |
21 | opts.extensionDir = opts.extensionDir || process.env.EXTENSION_DIR;
22 |
23 | opts.wsPath = process.env.WS_PATH || (window.location.protocol == 'https:' ? `wss://${hostname}:${serverPort}` : `ws://${hostname}:${serverPort}`);
24 | console.log(opts.wsPath)
25 | opts.extWorkerHost = opts.extWorkerHost || process.env.EXTENSION_WORKER_HOST || `http://${hostname}:${staticServerPort}/ext-host/worker-host.js`;
26 | opts.staticServicePath = `http://${hostname}:${serverPort}`;
27 | const anotherHostName = process.env.WEBVIEW_HOST || hostname;
28 | opts.webviewEndpoint = `http://${anotherHostName}:${webviewEndpointPort}/webview`;
29 | opts.layoutComponent = opts.layoutComponent || ToolbarActionBasedLayout;
30 | opts.injector = injector
31 | opts.isElectronRenderer = false
32 | opts.AINativeConfig = {
33 | layout: {
34 | menubarLogo: logo,
35 | }
36 | }
37 | const app = new ClientApp(opts);
38 |
39 | app.fireOnReload = () => {
40 | window.location.reload();
41 | };
42 |
43 | app.start(document.getElementById('main')!, 'web');
44 | }
45 |
--------------------------------------------------------------------------------
/src/bootstrap-web/browser/styles.less:
--------------------------------------------------------------------------------
1 | @import '~@opensumi/ide-core-browser/lib/style/variable.less';
2 |
3 | #main {
4 | display: flex;
5 | flex-direction: column;
6 | width: 100vw;
7 | height: 100vh;
8 | margin: 0;
9 | padding: 0;
10 | overflow: hidden;
11 | background-color: var(--background);
12 | }
13 |
--------------------------------------------------------------------------------
/src/bootstrap-web/common/index.ts:
--------------------------------------------------------------------------------
1 | import {config} from 'dotenv'
2 | import path from "path";
3 |
4 | config({
5 | path: path.resolve(__dirname, '../../../..env.sample')
6 | })
7 |
--------------------------------------------------------------------------------
/src/bootstrap-web/ext-host/index.ts:
--------------------------------------------------------------------------------
1 | import '../common'
2 |
3 | import '@/core/common/asar'
4 | import { extProcessInit } from '@opensumi/ide-extension/lib/hosted/ext.process-base.js';
5 | import { Injector } from '@opensumi/di';
6 | import { LogServiceManager } from '@/logger/node/log-manager';
7 | import { LogServiceManager as LogServiceManagerToken } from '@opensumi/ide-logs/lib/node/log-manager';
8 |
9 | const injector = new Injector()
10 | injector.addProviders(
11 | {
12 | token: LogServiceManagerToken,
13 | useClass: LogServiceManager
14 | },
15 | )
16 |
17 | extProcessInit({
18 | injector,
19 | })
20 |
--------------------------------------------------------------------------------
/src/bootstrap-web/ext-host/index.worker.ts:
--------------------------------------------------------------------------------
1 | import '@opensumi/ide-extension/lib/hosted/worker.host-preload';
2 |
--------------------------------------------------------------------------------
/src/bootstrap-web/node/common-modules.ts:
--------------------------------------------------------------------------------
1 | import { NodeModule, ConstructorOf } from '@opensumi/ide-core-node';
2 | import { ServerCommonModule } from '@opensumi/ide-core-node';
3 | import { FileServiceModule } from '@opensumi/ide-file-service/lib/node';
4 | import { OpenerModule } from '@opensumi/ide-remote-opener/lib/node';
5 | import { ProcessModule } from '@opensumi/ide-process/lib/node';
6 | import { FileSearchModule } from '@opensumi/ide-file-search/lib/node';
7 | import { SearchModule } from '@opensumi/ide-search/lib/node';
8 | import { TerminalNodePtyModule } from '@opensumi/ide-terminal-next/lib/node';
9 | import { LogServiceModule } from '@opensumi/ide-logs/lib/node';
10 | import { ExtensionModule } from '@opensumi/ide-extension/lib/node';
11 | import { OpenVsxExtensionManagerModule } from '@opensumi/ide-extension-manager/lib/node';
12 | import { FileSchemeNodeModule } from '@opensumi/ide-file-scheme/lib/node';
13 | import { AddonsModule } from '@opensumi/ide-addons/lib/node';
14 | import {CoreNodeModule} from "@/core/node";
15 | import {LoggerModule} from "@/logger/node";
16 | import {AINativeModule} from "@opensumi/ide-ai-native/lib/node";
17 | import {AIServiceModule} from "@/ai/node";
18 |
19 | export const CommonNodeModules: ConstructorOf[] = [
20 | ServerCommonModule,
21 | LogServiceModule,
22 | FileServiceModule,
23 | ProcessModule,
24 | FileSearchModule,
25 | SearchModule,
26 | TerminalNodePtyModule,
27 | ExtensionModule,
28 | OpenVsxExtensionManagerModule,
29 | FileSchemeNodeModule,
30 | AddonsModule,
31 | CoreNodeModule,
32 | LoggerModule,
33 | OpenerModule,
34 | // ai
35 | AINativeModule,
36 | AIServiceModule,
37 | ];
38 |
--------------------------------------------------------------------------------
/src/bootstrap-web/node/index.ts:
--------------------------------------------------------------------------------
1 | import '../common'
2 |
3 | import {startServer} from './start-server';
4 | import {ExpressFileServerModule} from '@opensumi/ide-express-file-server/lib/node';
5 | import {CommonNodeModules} from './common-modules';
6 |
7 | startServer({
8 | modules: [
9 | ...CommonNodeModules,
10 | ExpressFileServerModule,
11 | ],
12 | });
13 |
--------------------------------------------------------------------------------
/src/bootstrap-web/node/start-server.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as http from 'http';
3 | import Koa from 'koa';
4 | import koaStatic from 'koa-static';
5 | import { Deferred } from '@opensumi/ide-core-common';
6 | import { IServerAppOpts, ServerApp, NodeModule } from '@opensumi/ide-core-node';
7 |
8 | export async function startServer(arg1: NodeModule[] | Partial) {
9 | const app = new Koa();
10 | const deferred = new Deferred();
11 | process.env.EXT_MODE = 'js';
12 | const port = process.env.IDE_SERVER_PORT || 8000;
13 | const workspaceDir = process.env.WORKSPACE_DIR || process.env.NODE_ENV === 'production' ? path.join(__dirname, '../../workspace') : path.join(__dirname, '../../../workspace');
14 | const extensionDir = process.env.EXTENSION_DIR || process.env.NODE_ENV === 'production' ? path.join(__dirname, '../../extensions') : path.join(__dirname, '../../../extensions');
15 | const extensionHost = process.env.EXTENSION_HOST_ENTRY ||
16 | process.env.NODE_ENV === 'production' ? path.join(__dirname, '..', '..', 'out/ext-host/index.js') : path.join(__dirname, '..', '..', '..', 'out/ext-host/index.js');
17 |
18 | let opts: IServerAppOpts = {
19 | use: app.use.bind(app),
20 | processCloseExitThreshold: 5 * 60 * 1000,
21 | terminalPtyCloseThreshold: 5 * 60 * 1000,
22 | staticAllowOrigin: '*',
23 | staticAllowPath: [
24 | workspaceDir,
25 | extensionDir,
26 | '/',
27 | ],
28 | extHost: extensionHost,
29 | };
30 |
31 | opts.marketplace = {
32 | showBuiltinExtensions: true,
33 | }
34 |
35 | if (Array.isArray(arg1)) {
36 | opts = {
37 | ...opts,
38 | modulesInstances: arg1,
39 | };
40 | } else {
41 | opts = {
42 | ...opts,
43 | ...arg1,
44 | };
45 | }
46 |
47 | const serverApp = new ServerApp(opts);
48 | const server = http.createServer(app.callback());
49 |
50 | if (process.env.NODE_ENV === 'production') {
51 | app.use(koaStatic(path.join(__dirname, '../../out')));
52 | }
53 |
54 | await serverApp.start(server);
55 |
56 | server.on('error', (err) => {
57 | deferred.reject(err);
58 | console.error('Server error: ' + err.message);
59 | setTimeout(process.exit, 0, 1);
60 | });
61 |
62 | server.listen(port, () => {
63 | console.log(`Server listen on port ${port}`);
64 | deferred.resolve(server);
65 | });
66 | return deferred.promise;
67 | }
68 |
--------------------------------------------------------------------------------
/src/bootstrap/browser/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 | Codefuse IDE
17 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/bootstrap/browser/index.less:
--------------------------------------------------------------------------------
1 | select.kt_select {
2 | background-color: var(--editor-background);
3 | }
4 |
5 | quick-open-container {
6 | top: 36px !important;
7 | }
8 |
--------------------------------------------------------------------------------
/src/bootstrap/browser/preload.js:
--------------------------------------------------------------------------------
1 | const net = require('net');
2 | const os = require('os');
3 | const path = require('path')
4 |
5 | const { ipcRenderer } = require('electron');
6 |
7 | const electronEnv = {};
8 |
9 | const urlParams = new URLSearchParams(decodeURIComponent(window.location.search));
10 | window.id = Number(urlParams.get('windowId'));
11 | const webContentsId = Number(urlParams.get('webContentsId'));
12 |
13 | function createRPCNetConnection() {
14 | const rpcListenPath = ipcRenderer.sendSync('window-rpc-listen-path', electronEnv.currentWindowId);
15 | return net.createConnection(rpcListenPath);
16 | }
17 |
18 | function createNetConnection(connectPath) {
19 | return net.createConnection(connectPath);
20 | }
21 |
22 | electronEnv.ElectronIpcRenderer = ipcRenderer;
23 | electronEnv.createNetConnection = createNetConnection;
24 | electronEnv.createRPCNetConnection = createRPCNetConnection;
25 |
26 | electronEnv.platform = os.platform();
27 | electronEnv.osRelease = os.release();
28 |
29 | electronEnv.isElectronRenderer = true;
30 | electronEnv.BufferBridge = Buffer;
31 | electronEnv.currentWindowId = window.id;
32 | electronEnv.currentWebContentsId = webContentsId;
33 | electronEnv.monacoWorkerPath = path.join(__dirname, 'editor.worker.bundle.js');
34 |
35 | const metaData = JSON.parse(ipcRenderer.sendSync('window-metadata', electronEnv.currentWindowId));
36 | electronEnv.metadata = metaData;
37 | process.env = Object.assign({}, process.env, metaData.env, { WORKSPACE_DIR: metaData.workspace });
38 |
39 | electronEnv.onigWasmPath = path.join(__dirname, '..', '..', '..', metaData.environment.isDev ? 'node_modules' : 'node_modules.asar.unpacked', 'vscode-oniguruma/release/onig.wasm' );
40 | electronEnv.treeSitterWasmDirectoryPath = path.join(__dirname, '..', '..', '..', metaData.environment.isDev ? 'node_modules' : 'node_modules.asar.unpacked', '@opensumi/tree-sitter-wasm' );
41 | electronEnv.appPath = metaData.appPath;
42 | electronEnv.env = Object.assign({}, process.env);
43 | electronEnv.webviewPreload = metaData.webview.webviewPreload;
44 | electronEnv.plainWebviewPreload = metaData.webview.plainWebviewPreload;
45 | electronEnv.env.EXTENSION_DIR = metaData.extensionDir[0];
46 |
47 | global.electronEnv = electronEnv;
48 | Object.assign(global, electronEnv);
49 |
50 | if (metaData.preloads) {
51 | metaData.preloads.forEach((preload) => {
52 | require(preload);
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/src/bootstrap/electron-main/index.ts:
--------------------------------------------------------------------------------
1 | import '@/core/common/asar'
2 | import '@/i18n'
3 | import { app } from 'electron';
4 | import * as path from 'node:path';
5 | import { URI } from '@opensumi/ide-core-common'
6 | import { WebviewElectronMainModule } from '@opensumi/ide-webview/lib/electron-main';
7 | import { ElectronMainApp } from '@/core/electron-main'
8 | import { CoreElectronMainModule } from '@/core/electron-main';
9 | import { LoggerModule } from '@/logger/electron-main'
10 | import { AutoUpdaterModule } from '@/auto-updater/electron-main'
11 |
12 | const modules = [
13 | CoreElectronMainModule,
14 | WebviewElectronMainModule,
15 | LoggerModule,
16 | AutoUpdaterModule,
17 | ]
18 |
19 | startMain();
20 |
21 | function startMain() {
22 | const mainApp = new ElectronMainApp({
23 | modules,
24 | browserUrl: __CODE_WINDOW_DEV_SERVER_URL__ || URI.file(path.join(__dirname, `../renderer/${__CODE_WINDOW_NAME__}/index.html`)).toString(),
25 | browserPreload: path.resolve(__dirname, `../renderer/${__CODE_WINDOW_NAME__}/preload.js`),
26 | nodeEntry: path.join(__dirname, '../node/index.js'),
27 | extensionEntry: path.join(__dirname, '../ext-host/index.js'),
28 | extensionWorkerEntry: path.join(__dirname, '../ext-host/worker-host.js'),
29 | webviewPreload: path.join(__dirname, '../webview/host-preload.js'),
30 | plainWebviewPreload: path.join(__dirname, '../webview/plain-preload.js'),
31 | extensionDir: path.join(app.getAppPath(), 'extensions'),
32 | extensionCandidate: [],
33 | browserNodeIntegrated: true,
34 | })
35 |
36 | mainApp.start();
37 | }
38 |
--------------------------------------------------------------------------------
/src/bootstrap/ext-host/index.ts:
--------------------------------------------------------------------------------
1 | import '@/core/common/asar'
2 | import { extProcessInit } from '@opensumi/ide-extension/lib/hosted/ext.process-base.js';
3 | import { Injector } from '@opensumi/di';
4 | import { LogServiceManager } from '@/logger/node/log-manager';
5 | import { LogServiceManager as LogServiceManagerToken } from '@opensumi/ide-logs/lib/node/log-manager';
6 |
7 | const injector = new Injector()
8 | injector.addProviders(
9 | {
10 | token: LogServiceManagerToken,
11 | useClass: LogServiceManager
12 | },
13 | )
14 |
15 | extProcessInit({
16 | injector,
17 | })
18 |
--------------------------------------------------------------------------------
/src/bootstrap/ext-host/index.worker.ts:
--------------------------------------------------------------------------------
1 | import '@opensumi/ide-extension/lib/hosted/worker.host-preload';
2 |
--------------------------------------------------------------------------------
/src/bootstrap/node/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import '@/core/common/asar'
3 | import * as net from 'node:net';
4 | import path from 'node:path';
5 | import mri from 'mri'
6 | import { IServerAppOpts, ServerApp, ConstructorOf, NodeModule } from '@opensumi/ide-core-node';
7 | import { ServerCommonModule } from '@opensumi/ide-core-node';
8 | import { FileServiceModule } from '@opensumi/ide-file-service/lib/node';
9 | import { ProcessModule } from '@opensumi/ide-process/lib/node';
10 | import { FileSearchModule } from '@opensumi/ide-file-search/lib/node';
11 | import { SearchModule } from '@opensumi/ide-search/lib/node';
12 | import { TerminalNodePtyModule } from '@opensumi/ide-terminal-next/lib/node';
13 | import { terminalPreferenceSchema } from '@opensumi/ide-terminal-next/lib/common/preference'
14 | import { LogServiceModule } from '@opensumi/ide-logs/lib/node';
15 | import { ExtensionModule } from '@opensumi/ide-extension/lib/node';
16 | import { FileSchemeNodeModule } from '@opensumi/ide-file-scheme/lib/node';
17 | import { AddonsModule } from '@opensumi/ide-addons/lib/node';
18 | import { OpenVsxExtensionManagerModule } from '@opensumi/ide-extension-manager/lib/node';
19 | import { AINativeModule } from '@opensumi/ide-ai-native/lib/node';
20 | import { CoreNodeModule } from '@/core/node';
21 | import { LoggerModule } from '@/logger/node'
22 | import { AIServiceModule } from '@/ai/node';
23 |
24 | const modules: ConstructorOf[] = [
25 | ServerCommonModule,
26 | LogServiceModule,
27 | FileServiceModule,
28 | ProcessModule,
29 | FileSearchModule,
30 | SearchModule,
31 | TerminalNodePtyModule,
32 | ExtensionModule,
33 | OpenVsxExtensionManagerModule,
34 | FileSchemeNodeModule,
35 | AddonsModule,
36 | CoreNodeModule,
37 | LoggerModule,
38 | // ai
39 | AINativeModule,
40 | AIServiceModule,
41 | ]
42 |
43 | startServer();
44 |
45 | async function startServer() {
46 | const opts: IServerAppOpts = {
47 | modules,
48 | webSocketHandler: [],
49 | marketplace: {
50 | showBuiltinExtensions: true,
51 | extensionDir: process.env.IDE_EXTENSIONS_PATH!,
52 | },
53 | watcherHost: path.join(__dirname, '../watcher-host/index'),
54 | };
55 |
56 | const server = net.createServer();
57 | const serverApp = new ServerApp(opts);
58 | await serverApp.start(server);
59 |
60 | server.on('error', () => {
61 | setTimeout(() => {
62 | process.exit(1);
63 | });
64 | });
65 |
66 | const listenPath = mri(process.argv).listenPath;
67 | server.listen(listenPath, () => {
68 | process.send?.('ready');
69 | });
70 | }
71 |
--------------------------------------------------------------------------------
/src/bootstrap/watcher-host/index.ts:
--------------------------------------------------------------------------------
1 | import '@/core/common/asar';
2 | import { createConnection } from 'net';
3 |
4 | import { Injector } from '@opensumi/di';
5 | import { SumiConnectionMultiplexer } from '@opensumi/ide-connection';
6 | import { NetSocketConnection } from '@opensumi/ide-connection/lib/common/connection/drivers';
7 | import { argv } from '@opensumi/ide-core-common/lib/node/cli';
8 | import { suppressNodeJSEpipeError } from '@opensumi/ide-core-common/lib/node/utils';
9 | import { CommonProcessReporter, IReporter, ReporterProcessMessage } from '@opensumi/ide-core-common/lib/types';
10 | import { Emitter, isPromiseCanceledError } from '@opensumi/ide-utils';
11 |
12 | import { SUMI_WATCHER_PROCESS_SOCK_KEY, WATCHER_INIT_DATA_KEY } from '@opensumi/ide-file-service/lib/common';
13 |
14 | import { WatcherProcessLogger } from '@opensumi/ide-file-service/lib/node/hosted/watch-process-log';
15 | import { WatcherHostServiceImpl } from '@opensumi/ide-file-service/lib/node/hosted/watcher.host.service';
16 | import { LogServiceManager as LogServiceManagerToken } from '@opensumi/ide-logs/lib/node/log-manager';
17 | import { LogServiceManager } from '@/logger/node/log-manager';
18 |
19 | Error.stackTraceLimit = 100;
20 | const logger: any = console;
21 |
22 | async function initWatcherProcess() {
23 | patchConsole();
24 | patchProcess();
25 | const watcherInjector = new Injector();
26 | const reporterEmitter = new Emitter();
27 |
28 | watcherInjector.addProviders({
29 | token: IReporter,
30 | useValue: new CommonProcessReporter(reporterEmitter),
31 | }, {
32 | token: LogServiceManagerToken,
33 | useClass: LogServiceManager
34 | });
35 |
36 | const initData = JSON.parse(argv[WATCHER_INIT_DATA_KEY]);
37 | const connection = JSON.parse(argv[SUMI_WATCHER_PROCESS_SOCK_KEY]);
38 |
39 | const socket = createConnection(connection);
40 |
41 | const watcherProtocol = new SumiConnectionMultiplexer(new NetSocketConnection(socket), {
42 | timeout: -1,
43 | });
44 |
45 | const logger = new WatcherProcessLogger(watcherInjector, initData.logDir, initData.logLevel);
46 | const watcherHostService = new WatcherHostServiceImpl(watcherProtocol, logger);
47 | watcherHostService.initWatcherServer();
48 | }
49 |
50 | (async () => {
51 | await initWatcherProcess();
52 | })();
53 |
54 | function getErrorLogger() {
55 | // eslint-disable-next-line no-console
56 | return (logger && logger.error.bind(logger)) || console.error.bind(console);
57 | }
58 |
59 | function getWarnLogger() {
60 | // eslint-disable-next-line no-console
61 | return (logger && logger.warn.bind(logger)) || console.warn.bind(console);
62 | }
63 |
64 | function patchProcess() {
65 | process.exit = function (code?: number) {
66 | const err = new Error(`An extension called process.exit(${code ?? ''}) and this was prevented.`);
67 | getWarnLogger()(err.stack);
68 | } as (code?: number) => never;
69 |
70 | // override Electron's process.crash() method
71 | process.crash = function () {
72 | const err = new Error('An extension called process.crash() and this was prevented.');
73 | getWarnLogger()(err.stack);
74 | };
75 | }
76 |
77 | function _wrapConsoleMethod(method: 'log' | 'info' | 'warn' | 'error') {
78 | // eslint-disable-next-line no-console
79 | const original = console[method].bind(console);
80 |
81 | Object.defineProperty(console, method, {
82 | set: () => {
83 | // empty
84 | },
85 | get: () =>
86 | function (...args: any[]) {
87 | original(...args);
88 | },
89 | });
90 | }
91 |
92 | function patchConsole() {
93 | _wrapConsoleMethod('info');
94 | _wrapConsoleMethod('log');
95 | _wrapConsoleMethod('warn');
96 | _wrapConsoleMethod('error');
97 | }
98 |
99 | function unexpectedErrorHandler(e: any) {
100 | setTimeout(() => {
101 | getErrorLogger()('[Watcehr-Host]', e.message, e.stack && '\n\n' + e.stack);
102 | }, 0);
103 | }
104 |
105 | function onUnexpectedError(e: any) {
106 | let err = e;
107 | if (!err) {
108 | getWarnLogger()(`Unknown Exception ${err}`);
109 | return;
110 | }
111 |
112 | if (isPromiseCanceledError(err)) {
113 | getWarnLogger()(`Canceled ${err.message}`);
114 | return;
115 | }
116 |
117 | if (!(err instanceof Error)) {
118 | err = new Error(e);
119 | }
120 |
121 | unexpectedErrorHandler(err);
122 | }
123 |
124 | suppressNodeJSEpipeError(process, (msg) => {
125 | getErrorLogger()(msg);
126 | });
127 |
128 | process.on('uncaughtException', (err) => {
129 | onUnexpectedError(err);
130 | });
131 |
132 | const unhandledPromises: Promise[] = [];
133 | process.on('unhandledRejection', (reason, promise) => {
134 | unhandledPromises.push(promise);
135 | setTimeout(() => {
136 | const idx = unhandledPromises.indexOf(promise);
137 | if (idx >= 0) {
138 | promise.catch((e) => {
139 | unhandledPromises.splice(idx, 1);
140 | onUnexpectedError(e);
141 | });
142 | }
143 | }, 1000);
144 | });
145 |
146 | process.on('rejectionHandled', (promise: Promise) => {
147 | const idx = unhandledPromises.indexOf(promise);
148 | if (idx >= 0) {
149 | unhandledPromises.splice(idx, 1);
150 | }
151 | });
152 |
--------------------------------------------------------------------------------
/src/core/browser/header/header.contribution.ts:
--------------------------------------------------------------------------------
1 | import { Domain } from '@opensumi/ide-core-browser';
2 | import { ComponentContribution, ComponentRegistry } from '@opensumi/ide-core-browser/lib/layout';
3 | import { ElectronHeaderBar } from './header.view'
4 |
5 | export const ELECTRON_HEADER = 'electron_header';
6 | export const WINDOW = 'electron_header';
7 |
8 | @Domain(ComponentContribution)
9 | export class HeaderContribution implements ComponentContribution {
10 | registerComponent(registry: ComponentRegistry): void {
11 | registry.register(
12 | ELECTRON_HEADER,
13 | {
14 | id: ELECTRON_HEADER,
15 | component: ElectronHeaderBar,
16 | },
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/core/browser/header/header.module.less:
--------------------------------------------------------------------------------
1 | :global(#top) {
2 | position: relative;
3 | }
4 |
5 | .header {
6 | position: absolute;
7 | background: var(--kt-menu-background);
8 | color: var(--titlebar-activeForeground);
9 | display: flex;
10 | align-items: center;
11 | text-align: center;
12 | line-height: 100%;
13 | font-size: 12px;
14 | user-select: none;
15 |
16 | .title_info {
17 | height: 100%;
18 | display: flex;
19 | align-items: center;
20 | -webkit-app-region: drag;
21 | flex-grow: 1;
22 | text-align: left;
23 | }
24 |
25 | :global .menu-bar {
26 | flex-shrink: 0;
27 | }
28 |
29 | :global .menubarWrapper {
30 | background-color: var(--menu-background);
31 | }
32 |
33 | .windowActions {
34 | display: flex;
35 | align-items: center;
36 | -webkit-app-region: no-drag;
37 | user-select: none;
38 |
39 | > .icon {
40 | font-size: 16px;
41 | padding: 0 15px;
42 | height: 100%;
43 | width: 100%;
44 | display: flex;
45 | align-items: center;
46 |
47 | &:hover {
48 | background-color: hsla(0, 0%, 100%, 0.1);
49 | }
50 |
51 | &:hover:last-child {
52 | background-color: rgba(232, 17, 35, 0.9);
53 | color: white;
54 | }
55 | }
56 | }
57 | }
58 |
59 | :global(.vs) {
60 | .header {
61 | .windowActions {
62 | > .icon:not(:last-child):hover {
63 | background-color: rgba(0, 0, 0, 0.1);
64 | color: #696767;
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/core/browser/header/header.view.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useMemo, useRef, useState, useLayoutEffect } from 'react';
2 |
3 | import {
4 | ComponentRegistry,
5 | ComponentRenderer,
6 | Disposable,
7 | DomListener,
8 | IWindowService,
9 | electronEnv,
10 | getIcon,
11 | isMacintosh,
12 | useEventEffect,
13 | useInjectable,
14 | } from '@opensumi/ide-core-browser';
15 | import { LayoutViewSizeConfig } from '@opensumi/ide-core-browser/lib/layout/constants';
16 | import { IElectronMainUIService } from '@opensumi/ide-core-common/lib/electron';
17 | import { IElectronHeaderService } from '@opensumi/ide-electron-basic/lib/common/header';
18 |
19 | import styles from './header.module.less';
20 |
21 | const macTrafficWidth = 72;
22 | const winActionWidth = 138;
23 | const menuBarLeftWidth = 286
24 | const menuBarRightWidth = 28
25 | const extraWidth = 150
26 |
27 | const useMaximize = () => {
28 | const uiService: IElectronMainUIService = useInjectable(IElectronMainUIService);
29 |
30 | const [maximized, setMaximized] = useState(false);
31 |
32 | const getMaximized = async () => uiService.isMaximized(electronEnv.currentWindowId);
33 |
34 | useEffect(() => {
35 | const maximizeListener = uiService.on('maximizeStatusChange', (windowId, isMaximized) => {
36 | if (windowId === electronEnv.currentWindowId) {
37 | setMaximized(isMaximized);
38 | }
39 | });
40 | getMaximized().then((maximized) => {
41 | setMaximized(maximized);
42 | });
43 | return () => {
44 | maximizeListener.dispose();
45 | };
46 | }, []);
47 |
48 | return {
49 | maximized,
50 | getMaximized,
51 | };
52 | };
53 |
54 | /**
55 | * autoHide: Hide the HeaderBar when the macOS full screen
56 | */
57 | export const ElectronHeaderBar = () => {
58 | const ref = useRef(null)
59 | const windowService: IWindowService = useInjectable(IWindowService);
60 | const layoutViewSize = useInjectable(LayoutViewSizeConfig);
61 |
62 | const { getMaximized } = useMaximize();
63 |
64 | const safeHeight = useMemo(() => {
65 | return layoutViewSize.calcElectronHeaderHeight();
66 | }, [layoutViewSize]);
67 |
68 | useLayoutEffect(() => {
69 | const currentElement = ref.current
70 | if (!currentElement) return
71 | const { parentElement } = currentElement
72 | if (!parentElement) return
73 | if (isMacintosh) {
74 | parentElement.style.paddingLeft = `${macTrafficWidth}px`
75 | currentElement.style.left = `${macTrafficWidth + menuBarLeftWidth + extraWidth}px`
76 | currentElement.style.right = `${menuBarRightWidth + extraWidth}px`
77 | } else {
78 | parentElement.style.paddingRight = `${winActionWidth}px`
79 | currentElement.style.left = `${menuBarLeftWidth + extraWidth}px`
80 | currentElement.style.right = `${menuBarRightWidth + winActionWidth + extraWidth}px`
81 | }
82 | }, [])
83 |
84 | return (
85 | {
89 | if (await getMaximized()) {
90 | windowService.unmaximize();
91 | } else {
92 | windowService.maximize();
93 | }
94 | }}
95 | ref={ref}
96 | >
97 |
98 |
99 | );
100 | };
101 |
102 | export const HeaderBarTitleComponent = () => {
103 | const headerService = useInjectable(IElectronHeaderService) as IElectronHeaderService;
104 | const ref = useRef(null);
105 | const spanRef = useRef(null);
106 | const [appTitle, setAppTitle] = useState('');
107 |
108 | useEffect(() => {
109 | const defaultAppTitle = 'CodeFuse IDE'
110 | setAppTitle(headerService.appTitle || defaultAppTitle)
111 | const disposable = headerService.onTitleChanged((v) => {
112 | setAppTitle(v || defaultAppTitle);
113 | })
114 | return () => {
115 | disposable.dispose()
116 | }
117 | }, [])
118 |
119 | useEffect(() => {
120 | setPosition();
121 | const disposer = new Disposable();
122 |
123 | disposer.addDispose(
124 | new DomListener(window, 'resize', () => {
125 | setPosition();
126 | }),
127 | );
128 | }, []);
129 |
130 | function setPosition() {
131 | window.requestAnimationFrame(() => {
132 | if (ref.current && spanRef.current) {
133 | const windowWidth = window.innerWidth;
134 | const leftWidth = menuBarLeftWidth + extraWidth + (isMacintosh ? macTrafficWidth : 0)
135 | const left = Math.max(0, windowWidth * 0.5 - leftWidth - spanRef.current.offsetWidth * 0.5);
136 | ref.current.style.paddingLeft = left + 'px';
137 | ref.current.style.visibility = 'visible';
138 | }
139 | });
140 | }
141 |
142 | // 同时更新 document Title
143 | useEffect(() => {
144 | document.title = appTitle;
145 | }, [appTitle]);
146 |
147 | return (
148 |
149 | {appTitle}
150 |
151 | );
152 | };
153 |
--------------------------------------------------------------------------------
/src/core/browser/index.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule, createElectronMainApi, IElectronNativeDialogService } from '@opensumi/ide-core-browser';
2 | import { Injectable } from '@opensumi/di';
3 | import { ElectronBasicContribution } from '@opensumi/ide-electron-basic/lib/browser'
4 | import { ElectronNativeDialogService } from '@opensumi/ide-electron-basic/lib/browser/dialog'
5 | import { ElectronHeaderService } from '@opensumi/ide-electron-basic/lib/browser/header/header.service'
6 | import { ElectronPreferenceContribution } from '@opensumi/ide-electron-basic/lib/browser/electron-preference.contribution'
7 | import { IElectronHeaderService } from '@opensumi/ide-electron-basic/lib/common/header'
8 |
9 | import { ProjectSwitcherContribution } from './project.contribution';
10 | import { LocalMenuContribution } from './menu.contribution';
11 | import { LocalThemeContribution } from './theme.contribution';
12 | import { patchProviders } from './patch'
13 | import { IStorageService, IAppMenuService, IThemeService } from '../common';
14 | import { HeaderContribution, ELECTRON_HEADER } from './header/header.contribution'
15 | import { WelcomeContribution } from './welcome/welcome.contribution'
16 |
17 | export { ELECTRON_HEADER }
18 |
19 | @Injectable()
20 | export class CoreBrowserModule extends BrowserModule {
21 | providers = [
22 | {
23 | token: IElectronNativeDialogService,
24 | useClass: ElectronNativeDialogService,
25 | },
26 | {
27 | token: IElectronHeaderService,
28 | useClass: ElectronHeaderService,
29 | },
30 | ElectronBasicContribution,
31 | ElectronPreferenceContribution,
32 | WelcomeContribution,
33 | HeaderContribution,
34 | ProjectSwitcherContribution,
35 | LocalMenuContribution,
36 | LocalThemeContribution,
37 | {
38 | token: IStorageService,
39 | useValue: createElectronMainApi(IStorageService),
40 | },
41 | {
42 | token: IThemeService,
43 | useValue: createElectronMainApi(IThemeService),
44 | },
45 | {
46 | token: IAppMenuService,
47 | useValue: createElectronMainApi(IAppMenuService),
48 | },
49 | ...patchProviders,
50 | ];
51 | }
52 |
--------------------------------------------------------------------------------
/src/core/browser/menu.contribution.ts:
--------------------------------------------------------------------------------
1 | import { Autowired } from '@opensumi/di'
2 | import { CommandContribution, CommandRegistry, Domain, MaybePromise } from '@opensumi/ide-core-common'
3 | import { ClientAppContribution, electronEnv } from '@opensumi/ide-core-browser'
4 | import { IMenuRegistry, MenuId, MenuContribution } from "@opensumi/ide-core-browser/lib/menu/next";
5 | import { localize } from "@opensumi/ide-core-common/lib/localize";
6 | import { IWorkspaceService } from '@opensumi/ide-workspace';
7 | import { IAppMenuService } from '../common';
8 | import { IElectronMainUIService } from '@opensumi/ide-core-common/lib/electron';
9 |
10 | const OPEN_LOGO_DIR_COMMAND_ID = {
11 | id: 'codefuse-ide.openLogDir',
12 | label: localize('codefuse-ide.openLogDir'),
13 | }
14 |
15 | @Domain(ClientAppContribution, MenuContribution, CommandContribution)
16 | export class LocalMenuContribution implements MenuContribution, ClientAppContribution {
17 | @Autowired(IWorkspaceService)
18 | workspaceService: IWorkspaceService;
19 |
20 | @Autowired(IAppMenuService)
21 | menuService: IAppMenuService;
22 |
23 | @Autowired(IElectronMainUIService)
24 | private electronMainUIService: IElectronMainUIService;
25 |
26 | initialize(): MaybePromise {
27 | // this.renderAppMenu();
28 | }
29 |
30 | async renderAppMenu() {
31 | const workspaces = await this.workspaceService.getMostRecentlyUsedWorkspaces();
32 | await this.menuService.renderRecentWorkspaces(workspaces);
33 | }
34 |
35 | registerCommands(registry: CommandRegistry) {
36 | registry.registerCommand(OPEN_LOGO_DIR_COMMAND_ID, {
37 | execute: () => {
38 | this.electronMainUIService.revealInFinder(electronEnv.metadata.environment.logRoot);
39 | },
40 | });
41 | }
42 |
43 | registerMenus(menuRegistry: IMenuRegistry) {
44 | menuRegistry.registerMenuItem(MenuId.MenubarAppMenu, {
45 | submenu: MenuId.SettingsIconMenu,
46 | label: localize('common.preferences'),
47 | group: '2_preference',
48 | });
49 |
50 | menuRegistry.registerMenuItem(MenuId.MenubarHelpMenu, {
51 | command: OPEN_LOGO_DIR_COMMAND_ID,
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/core/browser/patch.ts:
--------------------------------------------------------------------------------
1 | import { Autowired, Provider } from '@opensumi/di'
2 | import { Domain, Schemes, URI } from '@opensumi/ide-core-common'
3 | import { AppConfig, WorkspaceScope } from '@opensumi/ide-core-browser'
4 | import { IMenuRegistry, MenuId, MenuContribution, IMenuItem } from "@opensumi/ide-core-browser/lib/menu/next";
5 | import { FILE_COMMANDS, ClientAppContribution, formatLocalize, StaticResourceContribution, StaticResourceService, electronEnv } from '@opensumi/ide-core-browser'
6 | import { IViewsRegistry } from '@opensumi/ide-main-layout';
7 | import { RESOURCE_VIEW_ID } from '@opensumi/ide-file-tree-next'
8 | import { IPreferenceSettingsService } from '@opensumi/ide-core-browser/lib/preferences';
9 | import { PreferenceSettingsService } from '@opensumi/ide-preferences/lib/browser/preference-settings.service'
10 |
11 | @Domain(ClientAppContribution, MenuContribution, StaticResourceContribution)
12 | export class PatchContribution implements MenuContribution, ClientAppContribution, StaticResourceContribution {
13 | @Autowired(IViewsRegistry)
14 | private viewsRegistry: IViewsRegistry;
15 |
16 | async onStart() {
17 | const viewContents = this.viewsRegistry.getViewWelcomeContent(RESOURCE_VIEW_ID);
18 | const openFolderContent = viewContents.find(item => item.content.includes(`(command:${FILE_COMMANDS.OPEN_FOLDER.id})`))
19 | if (openFolderContent) {
20 | Object.assign(openFolderContent, {
21 | content: formatLocalize('welcome-view.noFolderHelp', `${FILE_COMMANDS.OPEN_FOLDER.id}?{"newWindow":false}`)
22 | })
23 | }
24 | }
25 |
26 | registerMenus(menuRegistry: IMenuRegistry) {
27 | const openFolderMenu = menuRegistry.getMenuItems(MenuId.MenubarFileMenu).find(item => {
28 | return 'command' in item && item.command === FILE_COMMANDS.OPEN_FOLDER.id;
29 | }) as IMenuItem
30 | if (openFolderMenu) {
31 | openFolderMenu.extraTailArgs = [{ newWindow: false }]
32 | }
33 | }
34 |
35 | registerStaticResolver(service: StaticResourceService): void {
36 | service.registerStaticResourceProvider({
37 | scheme: Schemes.monaco,
38 | resolveStaticResource: (uri) => {
39 | const path = uri.codeUri.path;
40 |
41 | switch (path) {
42 | case 'worker': {
43 | const query = uri.query;
44 | if (query) {
45 | const { moduleId } = JSON.parse(query);
46 | if (moduleId === 'workerMain.js') {
47 | return URI.file(electronEnv.monacoWorkerPath);
48 | }
49 | }
50 | break;
51 | }
52 | }
53 |
54 | return uri;
55 | },
56 | });
57 | }
58 | }
59 |
60 | export class PatchPreferenceSettingsService extends PreferenceSettingsService {
61 | @Autowired(AppConfig)
62 | appConfig: AppConfig
63 |
64 | constructor() {
65 | super()
66 | if (!this.appConfig.workspaceDir) {
67 | this.tabList = this.tabList.filter(item => item !== WorkspaceScope);
68 | }
69 | this._currentScope = this.tabList[0]
70 | }
71 | }
72 |
73 | export const patchProviders: Provider[] = [
74 | PatchContribution,
75 | {
76 | token: IPreferenceSettingsService,
77 | useClass: PatchPreferenceSettingsService,
78 | override: true,
79 | }
80 | ]
81 |
--------------------------------------------------------------------------------
/src/core/browser/project.contribution.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Domain,
3 | CommandContribution,
4 | CommandRegistry,
5 | URI,
6 | electronEnv,
7 | ClientAppContribution,
8 | StorageProvider,
9 | FILE_COMMANDS,
10 | } from '@opensumi/ide-core-browser';
11 | import { IMenuRegistry, MenuId, MenuContribution } from '@opensumi/ide-core-browser/lib/menu/next';
12 | import { Autowired } from '@opensumi/di';
13 | import { IWorkspaceService } from '@opensumi/ide-workspace/lib/common';
14 | import { IWindowService, WORKSPACE_COMMANDS } from '@opensumi/ide-core-browser';
15 | import { ITerminalController } from '@opensumi/ide-terminal-next';
16 | import { IMainLayoutService } from '@opensumi/ide-main-layout';
17 | import { BrowserEditorContribution, WorkbenchEditorService } from '@opensumi/ide-editor/lib/browser';
18 | import { IThemeService } from '@opensumi/ide-theme';
19 |
20 | @Domain(MenuContribution, BrowserEditorContribution, ClientAppContribution)
21 | export class ProjectSwitcherContribution
22 | implements MenuContribution, BrowserEditorContribution, ClientAppContribution
23 | {
24 | @Autowired(IWorkspaceService)
25 | workspaceService: IWorkspaceService;
26 |
27 | @Autowired(IWindowService)
28 | windowService: IWindowService;
29 |
30 | @Autowired(ITerminalController)
31 | terminalService: ITerminalController;
32 |
33 | @Autowired(WorkbenchEditorService)
34 | editorService: WorkbenchEditorService;
35 |
36 | @Autowired(IMainLayoutService)
37 | private mainLayoutService: IMainLayoutService;
38 |
39 | @Autowired(IThemeService)
40 | private themeService: IThemeService;
41 |
42 | @Autowired(StorageProvider)
43 | getStorage: StorageProvider;
44 |
45 | async onStart() {}
46 |
47 | registerMenus(registry: IMenuRegistry) {
48 | registry.registerMenuItem(MenuId.MenubarFileMenu, {
49 | submenu: 'recentProjects',
50 | label: '最近项目',
51 | group: '1_open',
52 | });
53 |
54 | this.workspaceService.getMostRecentlyUsedWorkspaces().then((workspaces) => {
55 | registry.registerMenuItems(
56 | 'recentProjects',
57 | workspaces.map((workspace) => ({
58 | command: {
59 | id: FILE_COMMANDS.VSCODE_OPEN_FOLDER.id,
60 | label: new URI(workspace).codeUri.fsPath,
61 | },
62 | extraTailArgs: [workspace, false],
63 | })),
64 | );
65 | });
66 | }
67 |
68 | onDidRestoreState() {
69 | if (electronEnv.metadata.launchToOpenFile) {
70 | this.editorService.open(URI.file(electronEnv.metadata.launchToOpenFile));
71 | }
72 | electronEnv.ipcRenderer.on('openFile', (event, file) => {
73 | this.editorService.open(URI.file(file));
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/core/browser/theme.contribution.ts:
--------------------------------------------------------------------------------
1 | import { Autowired } from '@opensumi/di';
2 | import { ClientAppContribution } from '@opensumi/ide-core-browser/lib/common';
3 | import { Domain, OnEvent, WithEventBus } from '@opensumi/ide-core-common';
4 | import { ThemeChangedEvent } from '@opensumi/ide-theme/lib/common';
5 | import { IThemeService } from '../common';
6 | import { electronEnv } from '@opensumi/ide-core-browser';
7 |
8 | @Domain(ClientAppContribution)
9 | export class LocalThemeContribution extends WithEventBus implements ClientAppContribution {
10 | @Autowired(IThemeService)
11 | private readonly themeService: IThemeService;
12 |
13 | initialize() {}
14 |
15 | @OnEvent(ThemeChangedEvent)
16 | onThemeChanged({ payload: { theme } }: ThemeChangedEvent) {
17 | this.themeService.setTheme(electronEnv.currentWindowId, {
18 | themeType: theme.type,
19 | menuBarBackground: theme.getColor('kt.menubar.background')?.toString(),
20 | sideBarBackground: theme.getColor('sideBar.background')?.toString(),
21 | editorBackground: theme.getColor('editor.background')?.toString(),
22 | panelBackground: theme.getColor('panel.background')?.toString(),
23 | statusBarBackground: theme.getColor('statusBar.background')?.toString(),
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/core/browser/welcome/common.ts:
--------------------------------------------------------------------------------
1 | export interface IWelcomeMetaData {
2 | recentWorkspaces: string[];
3 | recentFiles: string[];
4 | }
--------------------------------------------------------------------------------
/src/core/browser/welcome/welcome.component.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | CommandService,
5 | FILE_COMMANDS,
6 | FileUri,
7 | IWindowService,
8 | URI,
9 | localize,
10 | useInjectable,
11 | } from '@opensumi/ide-core-browser';
12 | import { ReactEditorComponent } from '@opensumi/ide-editor/lib/browser';
13 | import { IFileServiceClient } from '@opensumi/ide-file-service';
14 | import { IMessageService } from '@opensumi/ide-overlay';
15 | import { posix, win32 } from '@opensumi/ide-utils/lib/path'
16 |
17 | import { IWelcomeMetaData } from './common';
18 | import styles from './welcome.module.less';
19 |
20 | export const EditorWelcomeComponent: ReactEditorComponent = ({ resource }) => {
21 | const commandService: CommandService = useInjectable(CommandService);
22 | const windowService: IWindowService = useInjectable(IWindowService);
23 | const fileService: IFileServiceClient = useInjectable(IFileServiceClient);
24 | const messageService: IMessageService = useInjectable(IMessageService);
25 |
26 | return (
27 |
28 |
29 |
{localize('welcome.quickStart')}
30 |
39 |
40 |
41 |
{localize('welcome.recent.workspace')}
42 | {resource.metadata?.recentWorkspaces.map((workspace) => {
43 | let workspacePath = workspace;
44 | if (workspace.startsWith('file://')) {
45 | workspacePath = FileUri.fsPath(workspace);
46 | }
47 | const p = workspacePath.indexOf('/') !== -1 ? posix : win32;
48 | let name = p.basename(workspacePath);
49 | let parentPath = p.dirname(workspacePath);
50 | if (!name.length) {
51 | name = parentPath
52 | parentPath = ''
53 | }
54 | // only the root segment
55 | return (
56 |
72 | );
73 | })}
74 |
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/core/browser/welcome/welcome.contribution.ts:
--------------------------------------------------------------------------------
1 | import { Autowired } from '@opensumi/di';
2 | import { ClientAppContribution, Domain, RecentFilesManager, URI, localize } from '@opensumi/ide-core-browser';
3 | import { IResource, ResourceService, WorkbenchEditorService } from '@opensumi/ide-editor';
4 | import {
5 | BrowserEditorContribution,
6 | EditorComponentRegistry,
7 | EditorComponentRenderMode,
8 | EditorOpenType,
9 | } from '@opensumi/ide-editor/lib/browser';
10 | import { IWorkspaceService } from '@opensumi/ide-workspace';
11 |
12 | import { IWelcomeMetaData } from './common'
13 | import { EditorWelcomeComponent } from './welcome.component';
14 |
15 | @Domain(BrowserEditorContribution, ClientAppContribution)
16 | export class WelcomeContribution implements BrowserEditorContribution, ClientAppContribution {
17 | @Autowired(IWorkspaceService)
18 | private readonly workspaceService: IWorkspaceService;
19 |
20 | @Autowired(WorkbenchEditorService)
21 | private readonly editorService: WorkbenchEditorService;
22 |
23 | @Autowired(RecentFilesManager)
24 | private readonly recentFilesManager: RecentFilesManager;
25 |
26 | registerEditorComponent(registry: EditorComponentRegistry) {
27 | registry.registerEditorComponent({
28 | uid: 'welcome',
29 | scheme: 'welcome',
30 | component: EditorWelcomeComponent,
31 | renderMode: EditorComponentRenderMode.ONE_PER_WORKBENCH,
32 | });
33 | registry.registerEditorComponentResolver('welcome', (resource, results) => {
34 | results.push({
35 | type: EditorOpenType.component,
36 | componentId: 'welcome',
37 | });
38 | });
39 | }
40 |
41 | registerResource(service: ResourceService) {
42 | service.registerResourceProvider({
43 | scheme: 'welcome',
44 | provideResource: async (uri: URI): Promise> =>
45 | Promise.all([
46 | this.workspaceService.getMostRecentlyUsedWorkspaces(),
47 | this.recentFilesManager.getMostRecentlyOpenedFiles(),
48 | ]).then(([workspaces, files]) => ({
49 | uri,
50 | name: localize('welcome.title'),
51 | icon: '',
52 | metadata: {
53 | recentWorkspaces: workspaces || [],
54 | recentFiles: files || [],
55 | },
56 | })),
57 | });
58 | }
59 |
60 | onDidStart() {
61 | if (!this.workspaceService.workspace) {
62 | this.editorService.open(new URI('welcome://'));
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/core/browser/welcome/welcome.module.less:
--------------------------------------------------------------------------------
1 | .welcome {
2 | h2 {
3 | color: var(--foreground);
4 | }
5 | padding: 20px 40px;
6 | > div {
7 | margin-bottom: 20px;
8 | }
9 |
10 | .recentRow {
11 | margin: 2px;
12 | line-height: 20px;
13 | .path {
14 | padding-left: 1em;
15 | opacity: 0.85;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/core/common/asar.ts:
--------------------------------------------------------------------------------
1 | import path from 'node:path'
2 | import module from 'node:module'
3 |
4 | function enableASARSupport() {
5 | const NODE_MODULES_PATH = path.join(__dirname, '../../node_modules');
6 | const NODE_MODULES_ASAR_PATH = `${NODE_MODULES_PATH}.asar`;
7 |
8 | const Module = module.Module as any;
9 | const originalResolveLookupPaths = Module._resolveLookupPaths;
10 | Module._resolveLookupPaths = function (request: any, parent: any) {
11 | const paths = originalResolveLookupPaths(request, parent);
12 | if (Array.isArray(paths)) {
13 | for (let i = 0, len = paths.length; i < len; i++) {
14 | if (paths[i] === NODE_MODULES_PATH) {
15 | paths.splice(i, 0, NODE_MODULES_ASAR_PATH);
16 | break;
17 | }
18 | }
19 | }
20 | return paths;
21 | };
22 | }
23 |
24 | enableASARSupport()
25 |
--------------------------------------------------------------------------------
/src/core/common/constants.ts:
--------------------------------------------------------------------------------
1 | export const StorageKey = {
2 | THEME_BG_COLOR: 'themeBackgroundColor',
3 | }
4 |
--------------------------------------------------------------------------------
/src/core/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './constants'
2 | export * from './types'
--------------------------------------------------------------------------------
/src/core/common/types.ts:
--------------------------------------------------------------------------------
1 | import type { ThemeType } from '@opensumi/ide-theme';
2 |
3 | export { ThemeType }
4 |
5 | export interface ThemeData {
6 | menuBarBackground?: string;
7 | sideBarBackground?: string;
8 | editorBackground?: string;
9 | panelBackground?: string;
10 | statusBarBackground?: string;
11 | }
12 |
13 | export const IStorageService = 'IStorageService';
14 |
15 | export type IStorageData = object | string | number | boolean | undefined | null;
16 | export interface IStorageService {
17 | getItem(key: string, defaultValue: T): T;
18 | getItem(key: string, defaultValue?: T): T | undefined;
19 | setItem(key: string, data?: IStorageData): void;
20 | setItems(items: readonly { key: string; data?: IStorageData }[]): void;
21 | removeItem(key: string): void;
22 | close(): Promise;
23 | }
24 |
25 | export const IThemeService = 'IThemeService';
26 |
27 | export interface ThemeData {
28 | menuBarBackground?: string;
29 | sideBarBackground?: string;
30 | editorBackground?: string;
31 | panelBackground?: string;
32 | statusBarBackground?: string;
33 | themeType?: ThemeType;
34 | }
35 |
36 | export interface IThemeService {
37 | setTheme(windowId: number, themeData: ThemeData): void;
38 | }
39 |
40 | export const IAppMenuService = 'IAppMenuService';
41 |
42 | export interface IAppMenuService {
43 | renderRecentWorkspaces(workspaces: string[]): Promise;
44 | }
45 |
46 | export const IProduct = Symbol('IProduct');
47 | export interface IProduct {
48 | productName: string;
49 | applicationName: string;
50 | autoUpdaterConfigUrl: string;
51 | dataFolderName: string;
52 | commit: string,
53 | date: string,
54 | }
55 |
56 | export const IEnvironmentService = Symbol('IEnvironmentService');
57 | export interface IEnvironmentService {
58 | isDev: boolean;
59 | dataFolderName: string;
60 | appRoot: string;
61 | userHome: string;
62 | userDataPath: string;
63 | userSettingPath: string;
64 | storagePath: string;
65 | extensionsPath: string;
66 | logRoot: string;
67 | logHome: string;
68 | }
69 |
--------------------------------------------------------------------------------
/src/core/electron-main/app.ts:
--------------------------------------------------------------------------------
1 | import { app, dialog } from 'electron';
2 | import { Injector } from '@opensumi/di'
3 | import { ElectronMainApp as BaseElectronMainApp, ElectronAppConfig } from '@opensumi/ide-core-electron-main';
4 | import { ILogService } from '@/logger/common'
5 | import { ElectronMainContribution } from './types'
6 | import { isMacintosh } from '@opensumi/ide-core-common';
7 | import { WindowsManager } from './window/windows-manager';
8 |
9 | export class ElectronMainApp {
10 | private injector = new Injector;
11 | private baseApp: BaseElectronMainApp;
12 | private logger: ILogService
13 | private pendingQuit = false;
14 |
15 | constructor(config: ElectronAppConfig) {
16 | this.baseApp = new BaseElectronMainApp({
17 | ...config,
18 | injector: this.injector,
19 | })
20 | this.logger = this.injector.get(ILogService);
21 | for (const contribution of this.contributions) {
22 | if (contribution.onBeforeReady) {
23 | contribution.onBeforeReady();
24 | }
25 | }
26 | }
27 |
28 | get contributions() {
29 | return this.baseApp.contributions as ElectronMainContribution[];
30 | }
31 |
32 | async start() {
33 | this.logger.log('start')
34 | await app.whenReady();
35 | this.registerListenerAfterReady()
36 |
37 | this.logger.log('trigger onWillStart')
38 | await Promise.all(this.contributions.map(contribution => contribution.onWillStart?.()))
39 | this.claimInstance();
40 | this.moveToApplication()
41 |
42 | this.logger.log('trigger onStart')
43 | await Promise.all(this.contributions.map(contribution => contribution.onStart?.()))
44 | }
45 |
46 | private registerListenerAfterReady() {
47 | const handleBeforeQuit = () => {
48 | if (this.pendingQuit) return
49 | this.logger.debug('lifecycle#before-quit')
50 | this.pendingQuit = true;
51 | }
52 | app.on('before-quit', handleBeforeQuit)
53 |
54 | const handleWindowAllClose = () => {
55 | this.logger.debug('lifecycle#window-all-closed')
56 | if (this.pendingQuit || !isMacintosh) {
57 | app.quit();
58 | }
59 | }
60 | app.on('window-all-closed', handleWindowAllClose);
61 |
62 | app.once('will-quit', (e) => {
63 | e.preventDefault()
64 | Promise.allSettled(this.contributions.map(contribution => contribution.onWillQuit?.()))
65 | .finally(() => {
66 | app.removeListener('before-quit', handleBeforeQuit)
67 | app.removeListener('window-all-closed', handleWindowAllClose)
68 | this.logger.debug('lifecycle#will-quit')
69 | setTimeout(() => {
70 | app.quit()
71 | })
72 | })
73 | })
74 | }
75 |
76 | private claimInstance() {
77 | const gotTheLock = app.requestSingleInstanceLock({ pid: process.pid })
78 | this.logger.log('gotTheLock:', gotTheLock, process.pid)
79 | if (!gotTheLock) {
80 | app.exit()
81 | } else {
82 | app.on('second-instance', (_event, argv, workingDirectory, additionalData) => {
83 | this.logger.log('second-instance', argv, workingDirectory, additionalData)
84 | if (isMacintosh) {
85 | app.focus({ steal: true });
86 | }
87 | this.injector.get(WindowsManager).createCodeWindow()
88 | })
89 | }
90 | }
91 |
92 | private moveToApplication() {
93 | if (process.platform !== 'darwin' || !app.isPackaged || app.isInApplicationsFolder()) return
94 | const chosen = dialog.showMessageBoxSync({
95 | type: 'question',
96 | buttons: ['移动', '不移动'],
97 | message: '是否移动到 Applications 目录',
98 | defaultId: 0,
99 | cancelId: 1,
100 | })
101 |
102 | if (chosen !== 0) return
103 |
104 | try {
105 | app.moveToApplicationsFolder({
106 | conflictHandler: (conflictType) => {
107 | if (conflictType === 'existsAndRunning') {
108 | dialog.showMessageBoxSync({
109 | type: 'info',
110 | message: '无法移动到 Applications 目录',
111 | detail:
112 | 'Applications 目录已运行另一个版本的 CodeFuse IDE,请先关闭后重试。',
113 | })
114 | }
115 | return true
116 | },
117 | })
118 | } catch (err: any) {
119 | this.logger.error(`Failed to move to applications folder: ${err?.message}}`)
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/core/electron-main/environment.service.ts:
--------------------------------------------------------------------------------
1 | import { app } from 'electron'
2 | import * as os from 'node:os'
3 | import * as path from 'node:path'
4 | import { Injectable, Autowired } from '@opensumi/di'
5 | import { memoize } from '@opensumi/ide-core-common'
6 | import { IEnvironmentService, IProduct } from '../common'
7 |
8 | @Injectable()
9 | export class EnvironmentService implements IEnvironmentService {
10 | @Autowired(IProduct)
11 | product: IProduct
12 |
13 | @memoize
14 | get isDev() { return process.env.NODE_ENV === 'development' }
15 |
16 | @memoize
17 | get dataFolderName() { return this.product.dataFolderName }
18 |
19 | @memoize
20 | get appRoot(): string { return app.getAppPath() }
21 |
22 | @memoize
23 | get userHome() { return os.homedir() }
24 |
25 | @memoize
26 | get userDataPath(): string { return app.getPath('userData') }
27 |
28 | @memoize
29 | get userSettingPath(): string { return path.join(this.userDataPath, 'user') }
30 |
31 | @memoize
32 | get storagePath(): string { return path.join(this.userSettingPath, 'storage.json') }
33 |
34 | @memoize
35 | get extensionsPath() {
36 | return path.join(this.userHome, this.product.dataFolderName, 'extensions')
37 | }
38 |
39 | @memoize
40 | get logRoot() {
41 | return path.join(this.userDataPath, 'logs')
42 | }
43 |
44 | @memoize
45 | get logHome() {
46 | const date = new Date();
47 | const logName = `${date.getFullYear()}${String((date.getMonth() + 1)).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`;
48 | return path.join(this.logRoot, logName)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/core/electron-main/index.ts:
--------------------------------------------------------------------------------
1 | export * from './app'
2 | export * from './module'
3 | export * from './types'
--------------------------------------------------------------------------------
/src/core/electron-main/lifecycle.contribution.ts:
--------------------------------------------------------------------------------
1 | import { app } from 'electron'
2 | import * as fs from 'node:fs/promises'
3 | import { Autowired } from '@opensumi/di'
4 | import { Domain } from '@opensumi/ide-core-common'
5 | import { ILogService } from '@/logger/common'
6 | import { ElectronMainContribution } from './types'
7 | import { IEnvironmentService } from '../common'
8 | import { StorageService } from './storage.service'
9 | import { WindowsManager } from './window/windows-manager'
10 |
11 | @Domain(ElectronMainContribution)
12 | export class LifecycleContribution implements ElectronMainContribution {
13 | @Autowired(IEnvironmentService)
14 | environmentService: IEnvironmentService
15 |
16 | @Autowired(StorageService)
17 | storageService: StorageService;
18 |
19 | @Autowired(WindowsManager)
20 | windowsManager: WindowsManager
21 |
22 | @Autowired(ILogService)
23 | logger: ILogService
24 |
25 | async onWillStart() {
26 | this.setProcessEnv();
27 | await Promise.all([
28 | Promise.all([
29 | this.environmentService.logHome,
30 | this.environmentService.extensionsPath,
31 | ].map(filepath => filepath ? fs.mkdir(filepath, { recursive: true }) : null)),
32 | this.storageService.init(),
33 | ])
34 | }
35 |
36 | onStart() {
37 | this.windowsManager.createCodeWindow()
38 |
39 | app.on('activate', (_e, hasVisibleWindows) => {
40 | this.logger.debug('lifecycle#activate')
41 | if (!hasVisibleWindows) {
42 | this.windowsManager.createCodeWindow()
43 | }
44 | })
45 | }
46 |
47 | private setProcessEnv() {
48 | const { dataFolderName, logRoot, logHome, extensionsPath } = this.environmentService;
49 | process.env.IDE_VERSION = app.getVersion();
50 | process.env.IDE_DATA_FOLDER_NAME = dataFolderName;
51 | process.env.IDE_LOG_ROOT = logRoot
52 | process.env.IDE_LOG_HOME = logHome
53 | process.env.IDE_EXTENSIONS_PATH = extensionsPath
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/core/electron-main/menu.contribution.ts:
--------------------------------------------------------------------------------
1 | import { app, Menu, MenuItem } from 'electron'
2 | import { Autowired, Injectable } from '@opensumi/di'
3 | import { Domain, isMacintosh, localize, FileUri } from '@opensumi/ide-core-common'
4 | import {
5 | ElectronMainApiRegistry,
6 | ElectronMainApiProvider,
7 | } from '@opensumi/ide-core-electron-main/lib/bootstrap/types';
8 | import { IAppMenuService } from '../common'
9 | import { ElectronMainContribution } from './types'
10 | import { WindowsManager } from './window/windows-manager'
11 |
12 | @Injectable()
13 | export class AppMenuService extends ElectronMainApiProvider implements IAppMenuService {
14 | async renderRecentWorkspaces(recentWorkspaces: string[]): Promise {
15 | const workspaces = recentWorkspaces.slice(0, 7)
16 | if (isMacintosh) {
17 | this.updateMacOSRecentDocuments(workspaces)
18 | }
19 | }
20 |
21 | private async updateMacOSRecentDocuments(workspaces: string[]): Promise {
22 | app.clearRecentDocuments();
23 | workspaces.forEach(workspace => {
24 | let workspacePath = workspace;
25 | if (workspace.startsWith('file://')) {
26 | workspacePath = FileUri.fsPath(workspace);
27 | }
28 | app.addRecentDocument(workspacePath)
29 | });
30 | }
31 | }
32 |
33 | @Domain(ElectronMainContribution)
34 | export class AppMenuContribution implements ElectronMainContribution {
35 | @Autowired(AppMenuService)
36 | menuService: AppMenuService;
37 |
38 | @Autowired(WindowsManager)
39 | windowsManager: WindowsManager
40 |
41 | #appMenuInstalled = false;
42 |
43 | registerMainApi(registry: ElectronMainApiRegistry) {
44 | registry.registerMainApi(IAppMenuService, this.menuService);
45 | }
46 |
47 | onStart(): void {
48 | this.installMenu()
49 | }
50 |
51 | installMenu() {
52 | if (isMacintosh && !this.#appMenuInstalled) {
53 | this.#appMenuInstalled = true;
54 |
55 | const dockMenu = new Menu();
56 | dockMenu.append(new MenuItem({ label: localize('common.newWindow'), click: () => this.windowsManager.createCodeWindow() }));
57 | app.dock.setMenu(dockMenu);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/core/electron-main/module.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@opensumi/di';
2 | import { ElectronMainModule } from '@opensumi/ide-core-electron-main/lib/electron-main-module';
3 | import { StorageContribution, StorageService } from './storage.service';
4 | import { ThemeContribution, ThemeService } from './theme.service';
5 | import { LifecycleContribution } from './lifecycle.contribution'
6 | import { IProduct, IEnvironmentService } from '../common'
7 | import { EnvironmentService } from './environment.service'
8 | import { WindowsManager } from './window/windows-manager'
9 | import { AppMenuContribution, AppMenuService } from './menu.contribution'
10 | import { WindowContribution } from './window/window.contribution'
11 | import { WorkspaceHistoryContribution } from './workspace/workspace-history.contribution'
12 |
13 | export * from './storage.service'
14 |
15 | @Injectable()
16 | export class CoreElectronMainModule extends ElectronMainModule {
17 | providers = [
18 | LifecycleContribution,
19 | StorageContribution,
20 | StorageService,
21 | ThemeContribution,
22 | ThemeService,
23 | AppMenuContribution,
24 | AppMenuService,
25 | WindowContribution,
26 | WindowsManager,
27 | WorkspaceHistoryContribution,
28 | {
29 | token: IProduct,
30 | useValue: __PRODUCT__,
31 | },
32 | {
33 | token: IEnvironmentService,
34 | useClass: EnvironmentService,
35 | },
36 | ];
37 | }
38 |
--------------------------------------------------------------------------------
/src/core/electron-main/storage.service.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path';
2 | import * as fs from 'node:fs/promises';
3 | import { Injectable, Autowired } from '@opensumi/di';
4 | import { Domain, isUndefinedOrNull, isUndefined, ThrottledDelayer, IDisposable } from '@opensumi/ide-core-common';
5 | import {
6 | ElectronMainApiRegistry,
7 | ElectronMainContribution,
8 | ElectronMainApiProvider,
9 | } from '@opensumi/ide-core-electron-main/lib/bootstrap/types';
10 |
11 | import { ILogService } from '@/logger/common';
12 | import { IStorageService, IEnvironmentService, IStorageData } from '../common/types';
13 |
14 | @Injectable()
15 | export class StorageService extends ElectronMainApiProvider implements IStorageService, IDisposable {
16 | #cache: Record = Object.create(null);
17 | #lastContents = '';
18 | #initializing: Promise | undefined = undefined;
19 | #closing: Promise | undefined = undefined;
20 | readonly #flushDelayer = new ThrottledDelayer(100);
21 |
22 | @Autowired(IEnvironmentService)
23 | environmentService: IEnvironmentService;
24 |
25 | @Autowired(ILogService)
26 | logService: ILogService
27 |
28 | init(): Promise {
29 | if (!this.#initializing) {
30 | this.#initializing = this.doInit();
31 | }
32 |
33 | return this.#initializing;
34 | }
35 |
36 | private async doInit() {
37 | try {
38 | await fs.mkdir(path.dirname(this.environmentService.storagePath), { recursive: true });
39 | this.#lastContents = await fs.readFile(this.environmentService.storagePath, 'utf8');
40 | this.#cache = JSON.parse(this.#lastContents);
41 | } catch (error: any) {
42 | if (error.code !== 'ENOENT') {
43 | this.logService.error(error);
44 | }
45 | }
46 | }
47 |
48 | getItem(key: string, defaultValue: T): T;
49 | getItem(key: string, defaultValue?: T): T | undefined;
50 | getItem(key: string, defaultValue?: T): T | undefined {
51 | const res = this.#cache[key];
52 | if (isUndefinedOrNull(res)) {
53 | return defaultValue;
54 | }
55 |
56 | return res as T;
57 | }
58 |
59 | setItem(key: string, data?: IStorageData): void {
60 | this.setItems([{ key, data }]);
61 | }
62 |
63 | setItems(items: readonly { key: string; data?: IStorageData }[]): void {
64 | let needSave = false;
65 |
66 | for (const { key, data } of items) {
67 | if (this.#cache[key] === data) continue;
68 | if (isUndefinedOrNull(data) && isUndefined(this.#cache[key])) continue;
69 | this.#cache[key] = isUndefinedOrNull(data) ? undefined : data;
70 | needSave = true
71 | }
72 |
73 | if (needSave) {
74 | this.save();
75 | }
76 | }
77 |
78 | removeItem(key: string): void {
79 | if (!isUndefined(this.#cache[key])) {
80 | this.#cache[key] = undefined;
81 | this.save();
82 | }
83 | }
84 |
85 | private async save(): Promise {
86 | if (this.#closing) {
87 | return;
88 | }
89 |
90 | return this.#flushDelayer.trigger(() => this.doSave());
91 | }
92 |
93 | private async doSave(): Promise {
94 | if (!this.#initializing) {
95 | return;
96 | }
97 | await this.#initializing;
98 |
99 | const serializedContent = JSON.stringify(this.#cache, null, 4);
100 | if (serializedContent === this.#lastContents) {
101 | return;
102 | }
103 |
104 | try {
105 | await fs.writeFile(this.environmentService.storagePath, serializedContent);
106 | this.#lastContents = serializedContent;
107 | } catch (error) {
108 | this.logService.error(error);
109 | }
110 | }
111 |
112 | async close(): Promise {
113 | if (!this.#closing) {
114 | this.#closing = this.#flushDelayer.trigger(() => this.doSave(), 0);
115 | }
116 |
117 | return this.#closing;
118 | }
119 |
120 | dispose(): void {
121 | this.#flushDelayer.dispose();
122 | }
123 | }
124 |
125 | @Domain(ElectronMainContribution)
126 | export class StorageContribution implements ElectronMainContribution {
127 | @Autowired(StorageService)
128 | storageService: StorageService;
129 |
130 | registerMainApi(registry: ElectronMainApiRegistry) {
131 | registry.registerMainApi(IStorageService, this.storageService);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/core/electron-main/theme.service.ts:
--------------------------------------------------------------------------------
1 | import { nativeTheme } from 'electron';
2 | import { Injectable, Autowired } from '@opensumi/di';
3 | import { Domain, isWindows } from '@opensumi/ide-core-common';
4 | import {
5 | ElectronMainApiRegistry,
6 | ElectronMainContribution,
7 | ElectronMainApiProvider,
8 | } from '@opensumi/ide-core-electron-main/lib/bootstrap/types';
9 | import { IElectronMainApp } from '@opensumi/ide-core-electron-main'
10 | import { Color } from '@opensumi/ide-theme/lib/common/color';
11 |
12 | import { StorageService } from './storage.service'
13 | import { IThemeService, ThemeData, ThemeType } from '../common/types';
14 | import { StorageKey } from '../common';
15 |
16 | @Injectable()
17 | export class ThemeService extends ElectronMainApiProvider implements IThemeService {
18 | @Autowired(StorageService)
19 | storageService: StorageService
20 |
21 | @Autowired(IElectronMainApp)
22 | electronMainApp: IElectronMainApp
23 |
24 | setTheme(windowId: number, themeData: ThemeData): void {
25 | this.storageService.setItem(StorageKey.THEME_BG_COLOR, themeData)
26 | this.updateSystemColorTheme(themeData.themeType);
27 | if (themeData.menuBarBackground && isWindows) {
28 | const currentWindow = this.electronMainApp.getCodeWindows().find(codeWindow => codeWindow.getBrowserWindow().id === windowId)
29 | if (currentWindow) {
30 | currentWindow.getBrowserWindow().setTitleBarOverlay(this.getTitleBarOverlay(themeData.menuBarBackground!))
31 | }
32 | }
33 | }
34 |
35 | getTitleBarOverlay(color: string) {
36 | return {
37 | height: 35,
38 | color,
39 | symbolColor: Color.fromHex(color).isDarker() ? '#FFFFFF' : '#000000',
40 | }
41 | }
42 |
43 | get themeBackgroundColor() {
44 | return this.storageService.getItem(StorageKey.THEME_BG_COLOR, {})
45 | }
46 |
47 | setSystemTheme() {
48 | const theme = this.storageService.getItem(StorageKey.THEME_BG_COLOR)
49 | this.updateSystemColorTheme(theme?.themeType || 'dark');
50 | }
51 |
52 | private updateSystemColorTheme(themeType?: ThemeType) {
53 | switch (themeType) {
54 | case 'light': nativeTheme.themeSource = 'light'; break;
55 | case 'dark': nativeTheme.themeSource = 'dark'; break;
56 | default: nativeTheme.themeSource = 'system';
57 | }
58 | }
59 | }
60 |
61 | @Domain(ElectronMainContribution)
62 | export class ThemeContribution implements ElectronMainContribution {
63 | @Autowired(ThemeService)
64 | themeService: ThemeService;
65 |
66 | registerMainApi(registry: ElectronMainApiRegistry) {
67 | registry.registerMainApi(IThemeService, this.themeService);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/core/electron-main/types.ts:
--------------------------------------------------------------------------------
1 | import { MaybePromise, } from '@opensumi/ide-core-common'
2 | import { ElectronMainContribution as BaseElectronMainContribution } from '@opensumi/ide-core-electron-main';
3 |
4 | export const ElectronMainContribution = BaseElectronMainContribution;
5 |
6 | export interface ElectronMainContribution extends BaseElectronMainContribution {
7 | /**
8 | * app.isReady 之前
9 | */
10 | onBeforeReady?(): void;
11 | /**
12 | * after app.isReady
13 | */
14 | onWillStart?(): MaybePromise;
15 |
16 | /**
17 | * after all onWillStart
18 | */
19 | onStart?(): MaybePromise;
20 |
21 | /**
22 | * app event will-quit
23 | */
24 | onWillQuit?(): MaybePromise;
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/src/core/electron-main/window/window-lifecycle.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindow } from 'electron';
2 | import { Injector } from '@opensumi/di'
3 | import { IElectronMainApiProvider, ElectronMainApp, IWindowOpenOptions } from '@opensumi/ide-core-electron-main'
4 | import { ExtensionCandidate, URI } from '@opensumi/ide-core-common';
5 | import { WindowsManager } from './windows-manager'
6 |
7 | export class WindowLifecycle implements IElectronMainApiProvider {
8 | eventEmitter: undefined;
9 |
10 | constructor(private app: ElectronMainApp, private injector: Injector) {}
11 |
12 | openWorkspace(workspace: string, openOptions: IWindowOpenOptions) {
13 | this.injector.get(WindowsManager).openCodeWindow(URI.parse(workspace), openOptions);
14 | }
15 |
16 | minimizeWindow(windowId: number) {
17 | const window = BrowserWindow.fromId(windowId);
18 | if (window) {
19 | window.minimize();
20 | }
21 | }
22 |
23 | fullscreenWindow(windowId: number) {
24 | const window = BrowserWindow.fromId(windowId);
25 | if (window) {
26 | window.setFullScreen(true);
27 | }
28 | }
29 | maximizeWindow(windowId: number) {
30 | const window = BrowserWindow.fromId(windowId);
31 | if (window) {
32 | window.maximize();
33 | }
34 | }
35 |
36 | unmaximizeWindow(windowId: number) {
37 | const window = BrowserWindow.fromId(windowId);
38 | if (window) {
39 | window.unmaximize();
40 | }
41 | }
42 | closeWindow(windowId: number) {
43 | const window = BrowserWindow.fromId(windowId);
44 | if (window) {
45 | const codeWindow = this.app.getCodeWindowByElectronBrowserWindowId(windowId);
46 | if (!codeWindow) {
47 | window.close();
48 | return;
49 | }
50 |
51 | if (codeWindow.isReloading) {
52 | codeWindow.isReloading = false;
53 |
54 | if (!codeWindow.isRemote) {
55 | // reload 的情况下不需要等待 startNode 执行完
56 | // 所以可以同时执行 startNode 和 reload 前端
57 | codeWindow.startNode();
58 | }
59 | window.webContents.reload();
60 | } else {
61 | // 正常关闭窗口的情况下,需要回收子进程,耗时可能会比较长
62 | // 这里先隐藏窗口,体感会更快
63 | window.hide();
64 | codeWindow.clear().finally(() => {
65 | window.close();
66 | });
67 | }
68 | }
69 | }
70 |
71 | reloadWindow(windowId: number) {
72 | const codeWindow = this.app.getCodeWindowByElectronBrowserWindowId(windowId);
73 | if (codeWindow) {
74 | codeWindow.reload();
75 | }
76 | }
77 |
78 | setExtensionDir(extensionDir: string, windowId: number) {
79 | const window = BrowserWindow.fromId(windowId);
80 | if (window) {
81 | const codeWindow = this.app.getCodeWindowByElectronBrowserWindowId(windowId);
82 | if (codeWindow) {
83 | codeWindow.setExtensionDir(extensionDir);
84 | }
85 | }
86 | }
87 |
88 | setExtensionCandidate(candidate: ExtensionCandidate[], windowId: number) {
89 | const window = BrowserWindow.fromId(windowId);
90 | if (window) {
91 | const codeWindow = this.app.getCodeWindowByElectronBrowserWindowId(windowId);
92 | if (codeWindow) {
93 | codeWindow.setExtensionCandidate(candidate);
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/core/electron-main/window/window.contribution.ts:
--------------------------------------------------------------------------------
1 | import { Injector, Autowired, INJECTOR_TOKEN } from '@opensumi/di'
2 | import { Domain } from '@opensumi/ide-core-common'
3 | import {
4 | ElectronMainApiRegistry,
5 | ElectronMainContribution,
6 | IElectronMainApp,
7 | } from '@opensumi/ide-core-electron-main/lib/bootstrap/types';
8 | import { ElectronMainApp } from '@opensumi/ide-core-electron-main';
9 | import { IElectronMainLifeCycleService } from '@opensumi/ide-core-common/lib/electron';
10 | import { WindowLifecycle } from './window-lifecycle'
11 |
12 | @Domain(ElectronMainContribution)
13 | export class WindowContribution implements ElectronMainContribution {
14 | @Autowired(INJECTOR_TOKEN)
15 | injector: Injector
16 |
17 | @Autowired(IElectronMainApp)
18 | electronApp: ElectronMainApp;
19 |
20 | registerMainApi(registry: ElectronMainApiRegistry): void {
21 | registry.registerMainApi(IElectronMainLifeCycleService, new WindowLifecycle(this.electronApp, this.injector));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/core/electron-main/window/windows-manager.ts:
--------------------------------------------------------------------------------
1 | import { BrowserWindowConstructorOptions } from 'electron'
2 | import { isWindows, URI } from '@opensumi/ide-core-common';
3 | import { Injectable, INJECTOR_TOKEN, Autowired, Injector } from '@opensumi/di'
4 | import { IWindowOpenOptions, ElectronAppConfig, IElectronMainApp, ElectronMainApp } from '@opensumi/ide-core-electron-main'
5 | import { IEnvironmentService, StorageKey } from '../../common'
6 | import { ThemeService } from '../theme.service'
7 |
8 | @Injectable()
9 | export class WindowsManager {
10 | @Autowired(INJECTOR_TOKEN)
11 | injector: Injector
12 |
13 | @Autowired(ElectronAppConfig)
14 | appConfig: ElectronAppConfig
15 |
16 | @Autowired(IElectronMainApp)
17 | mainApp: ElectronMainApp
18 |
19 | @Autowired(IEnvironmentService)
20 | environmentService: IEnvironmentService
21 |
22 | @Autowired(ThemeService)
23 | themeService: ThemeService;
24 |
25 | openCodeWindow(workspaceUri?: URI, options?: IWindowOpenOptions) {
26 | if (workspaceUri) {
27 | for (const codeWindow of this.mainApp.getCodeWindows()) {
28 | if (codeWindow.workspace?.toString() === workspaceUri.toString()) {
29 | codeWindow.getBrowserWindow().show()
30 | return;
31 | }
32 | }
33 | }
34 | if (options?.windowId) {
35 | const codeWindow = this.mainApp.getCodeWindowByElectronBrowserWindowId(options.windowId)
36 | if (codeWindow) {
37 | if (workspaceUri) {
38 | codeWindow.setWorkspace(workspaceUri.toString());
39 | }
40 | codeWindow.reload();
41 | return;
42 | }
43 | }
44 | this.createCodeWindow(workspaceUri);
45 | }
46 |
47 | createCodeWindow(
48 | workspaceUri?: URI,
49 | metadata?: any,
50 | browserWindowOptions?: BrowserWindowConstructorOptions,
51 | ) {
52 | this.themeService.setSystemTheme();
53 | const editorBackground = this.themeService.themeBackgroundColor.editorBackground || '#1e1e1e'
54 | const menuBarBackground = this.themeService.themeBackgroundColor.menuBarBackground || editorBackground;
55 |
56 | const codeWindow = this.mainApp.loadWorkspace(
57 | workspaceUri ? workspaceUri.toString() : undefined,
58 | {
59 | ...metadata,
60 | environment: {
61 | dataFolderName: this.environmentService.dataFolderName,
62 | isDev: this.environmentService.isDev,
63 | logRoot: this.environmentService.logRoot,
64 | },
65 | },
66 | {
67 | trafficLightPosition: {
68 | x: 10,
69 | y: 10,
70 | },
71 | ...(isWindows ? {
72 | titleBarOverlay: this.themeService.getTitleBarOverlay(menuBarBackground)
73 | } : null),
74 | show: false,
75 | backgroundColor: editorBackground,
76 | ...browserWindowOptions,
77 | webPreferences: {
78 | preload: this.appConfig.browserPreload,
79 | nodeIntegration: this.appConfig.browserNodeIntegrated,
80 | webviewTag: true,
81 | contextIsolation: false,
82 | webSecurity: !this.environmentService.isDev,
83 | },
84 | }
85 | );
86 |
87 | const browserWindow = codeWindow.getBrowserWindow()
88 | // 默认全屏
89 | // TODO: 支持窗口状态缓存
90 | browserWindow.maximize();
91 | browserWindow.show();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/core/electron-main/workspace/workspace-history.contribution.ts:
--------------------------------------------------------------------------------
1 | import { Autowired } from '@opensumi/di'
2 | import { app, JumpListCategory } from 'electron'
3 | import { Domain, isWindows, localize, MaybePromise } from '@opensumi/ide-core-common'
4 | import { ILogService } from '@/logger/common'
5 | import { ElectronMainContribution } from '../types'
6 |
7 | @Domain(ElectronMainContribution)
8 | export class WorkspaceHistoryContribution implements ElectronMainContribution {
9 | @Autowired(ILogService)
10 | logger: ILogService
11 |
12 | onWillStart(): MaybePromise {
13 | this.handleWindowsJumpList()
14 | }
15 |
16 | private async handleWindowsJumpList(): Promise {
17 | if (!isWindows) {
18 | return;
19 | }
20 | await this.updateWindowsJumpList();
21 | }
22 |
23 | private async updateWindowsJumpList(): Promise {
24 | const jumpList: JumpListCategory[] = [];
25 | jumpList.push({
26 | type: 'tasks',
27 | items: [
28 | {
29 | type: 'task',
30 | title: localize('common.newWindow'),
31 | description: localize('common.newWindowDesc'),
32 | program: process.execPath,
33 | iconPath: process.execPath,
34 | iconIndex: 0
35 | }
36 | ]
37 | });
38 |
39 | try {
40 | const res = app.setJumpList(jumpList);
41 | if (res && res !== 'ok') {
42 | this.logger.warn(`updateWindowsJumpList#setJumpList unexpected result: ${res}`);
43 | }
44 | } catch (error) {
45 | this.logger.warn('updateWindowsJumpList#setJumpList', error);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/core/node/index.ts:
--------------------------------------------------------------------------------
1 | import { NodeModule } from '@opensumi/ide-core-node';
2 | import { Injectable } from '@opensumi/di';
3 |
4 | @Injectable()
5 | export class CoreNodeModule extends NodeModule {
6 | providers = [];
7 | }
8 |
--------------------------------------------------------------------------------
/src/i18n/en-US.ts:
--------------------------------------------------------------------------------
1 | export const localizationBundle = {
2 | languageId: 'en-US',
3 | languageName: 'English',
4 | localizedLanguageName: 'English',
5 | contents: {
6 | 'common.about': 'About',
7 | 'common.preferences': 'Preferences',
8 | 'common.newWindow': 'New Window',
9 | 'common.newWindowDesc': 'Open a new window',
10 |
11 | 'custom.quick_open': 'Quick Open',
12 | 'custom.command_palette': 'Command Palette',
13 | 'custom.terminal_panel': 'Switch to Terminal Panel',
14 | 'custom.search_panel': 'Switch to Search Panel',
15 |
16 | 'preference.ai.model.title': 'Completion Model',
17 | 'preference.ai.model.baseUrl': 'Base URL',
18 | 'preference.ai.model.api_key': 'API Key',
19 | 'preference.ai.model.code': 'Code > Completion',
20 | 'preference.ai.model.code.modelName': 'Code > Model Name',
21 | 'preference.ai.model.code.systemPrompt': 'Code > System Prompt',
22 | 'preference.ai.model.code.temperature': 'Code > temperature',
23 | 'preference.ai.model.code.maxTokens': 'Code > max_tokens',
24 | 'preference.ai.model.code.presencePenalty': 'Code > presence_penalty',
25 | 'preference.ai.model.code.frequencyPenalty': 'Code > frequency_penalty',
26 | 'preference.ai.model.code.topP': 'Code Completion > top_p',
27 | 'preference.ai.model.code.modelName.tooltip': 'The default is same as Chat Model Name',
28 | 'preference.ai.model.code.fimTemplate': 'Code > FIM Template',
29 | 'preference.ai.model.code.fimTemplate.tooltip': 'If no template is provided, the pre-cursor and post-cursor code will be sent directly to the api, and if a template is provided, the following format should be configured\n{prefix}{suffix}\n{prefix} will be replaced with the pre-cursor code, and {suffix} will be replaced with the post-cursor code',
30 | 'preference.ai.model.temperature.description': 'What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\nWe generally recommend altering this or top_p but not both.',
31 | 'preference.ai.model.maxTokens.description': 'The maximum number of tokens that can be generated in the chat completion.',
32 | 'preference.ai.model.presencePenalty.description': 'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
33 | 'preference.ai.model.frequencyPenalty.description': 'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
34 | 'preference.ai.model.topP.description': 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\nWe generally recommend altering this or temperature but not both.',
35 |
36 | 'ai.model.noConfig': 'Please configure the AI model service for a better experience',
37 | 'ai.model.go': 'Go',
38 |
39 | 'autoUpdater.checkForUpdates': 'Check for Updates...',
40 | 'codefuse-ide.openLogDir': 'Open Log Folder',
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import { registerLocalizationBundle } from '@opensumi/ide-core-common/lib/localize';
2 | import { localizationBundle as zh } from './zh-CN';
3 | import { localizationBundle as en } from './en-US';
4 |
5 | // 先初始化语言包
6 | registerLocalizationBundle(zh);
7 | registerLocalizationBundle(en);
8 |
--------------------------------------------------------------------------------
/src/i18n/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export const localizationBundle = {
2 | languageId: 'zh-CN',
3 | languageName: 'Chinese',
4 | localizedLanguageName: '中文(中国)',
5 | contents: {
6 | 'common.about': '关于',
7 | 'common.preferences': '首选项',
8 | 'common.newWindow': '新建窗口',
9 | 'common.newWindowDesc': '打开新的窗口',
10 |
11 | 'custom.quick_open': '转到文件',
12 | 'custom.command_palette': '显示所有命令',
13 | 'custom.terminal_panel': '切换终端',
14 | 'custom.search_panel': '切换搜索面板',
15 |
16 | 'preference.ai.model.title': '补全模型配置',
17 | 'preference.ai.model.baseUrl': 'API URL 前缀',
18 | 'preference.ai.model.apiKey': 'API Key',
19 | 'preference.ai.model.code': '代码 > 补全',
20 | 'preference.ai.model.code.modelName': '代码 > 模型名称',
21 | 'preference.ai.model.code.systemPrompt': '代码 > 系统提示词',
22 | 'preference.ai.model.code.temperature': '代码 > temperature',
23 | 'preference.ai.model.code.maxTokens': '代码 > max_tokens',
24 | 'preference.ai.model.code.presencePenalty': '代码 > presence_penalty',
25 | 'preference.ai.model.code.frequencyPenalty': '代码 > frequency_penalty',
26 | 'preference.ai.model.code.topP': '代码 > top_p',
27 | 'preference.ai.model.code.modelName.tooltip': '默认和对话模型一致',
28 | 'preference.ai.model.code.fimTemplate': 'FIM 模版',
29 | 'preference.ai.model.code.fimTemplate.tooltip': '如果未提供模版, 则将光标前后代码直接发送到接口, 如果提供了模版, 配置如下格式:“{prefix}{suffix}”,{prefix} 会替换为光标前代码,{suffix} 会替换为光标后代码',
30 | 'preference.ai.model.temperature.description': '采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中性和确定性。\n通常建议只改变 top_p 或 temperature,不要两个都改',
31 | 'preference.ai.model.maxTokens.description': '补全完成时可以生成的最大 token 数。',
32 | 'preference.ai.model.presencePenalty.description': '存在惩罚,介于 -2.0 和 2.0 之间的数字。正值会根据新生成的词汇是否出现在目前的文本中来进行惩罚,从而增加模型讨论新话题的可能性。',
33 | 'preference.ai.model.frequencyPenalty.description': '频率惩罚,介于 -2.0 和 2.0 之间的数字。正值根据新标记到目前为止在文本中的现有频率对其进行惩罚,从而降低了模型逐字重复同一行的可能性。',
34 | 'preference.ai.model.topP.description': '温度采样的一种替代方法,称为原子核抽样,模型只会考虑前 top_p 概率质量的标记结果。因此,0.1 表示仅考虑前 10% 概率质量的标记。\n通常建议只改变 top_p 或 temperature,不要两个都改',
35 |
36 | 'ai.model.noConfig': '为了更好的体验,请先配置 AI 模型服务',
37 | 'ai.model.go': '前往',
38 |
39 | 'autoUpdater.checkForUpdates': '检查更新',
40 | 'codefuse-ide.openLogDir': '打开日志文件夹',
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/src/logger/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './log-manager'
2 | export * from './log-service'
3 | export * from './types'
--------------------------------------------------------------------------------
/src/logger/common/log-manager.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'node:path'
2 | import { Emitter } from '@opensumi/ide-core-common';
3 | import { ILogServiceManager, LogLevel, SupportLogNamespace, ILogService, BaseLogServiceOptions, Archive } from '@opensumi/ide-logs'
4 | import { SpdLogger } from './log-service'
5 |
6 | export abstract class AbstractLogServiceManager implements ILogServiceManager {
7 | #logLevel = process.env.NODE_ENV === 'development' ? LogLevel.Debug : LogLevel.Info;
8 | #logMap = new Map();
9 | #logLevelChangeEmitter = new Emitter();
10 |
11 | getLogger(namespace: SupportLogNamespace | string, loggerOptions?: BaseLogServiceOptions): ILogService {
12 | let logger = this.#logMap.get(namespace);
13 | if (logger) {
14 | if (loggerOptions) {
15 | logger.setOptions(loggerOptions)
16 | }
17 | return logger
18 | }
19 | // 默认使用 spdlog,上层也可拿到 logger 再次封装
20 | const options = {
21 | namespace,
22 | logLevel: this.getGlobalLogLevel(),
23 | logServiceManager: this,
24 | ...loggerOptions,
25 | }
26 | logger = new SpdLogger({
27 | logLevel: options.logLevel,
28 | logPath: options.logDir || path.join(this.getLogFolder(), `${namespace}.log`),
29 | });
30 | this.#logMap.set(namespace, logger);
31 | return logger;
32 | }
33 |
34 | removeLogger(namespace: SupportLogNamespace) {
35 | this.#logMap.delete(namespace);
36 | }
37 |
38 | getGlobalLogLevel(): LogLevel {
39 | return this.#logLevel;
40 | }
41 |
42 | setGlobalLogLevel(level: LogLevel) {
43 | this.#logLevel = level;
44 | }
45 |
46 | get onDidChangeLogLevel() {
47 | return this.#logLevelChangeEmitter.event;
48 | }
49 |
50 | abstract getLogFolder(): string
51 |
52 | abstract getRootLogFolder(): string
53 |
54 | async cleanOldLogs() {}
55 |
56 | async cleanAllLogs() {}
57 |
58 | async cleanExpiredLogs(_day: number) {}
59 |
60 | async getLogZipArchiveByDay(_day: number): Promise { return { pipe: () => null } }
61 |
62 | async getLogZipArchiveByFolder(_foldPath: string): Promise { return { pipe: () => null } }
63 |
64 | dispose() {
65 | this.#logLevelChangeEmitter.dispose();
66 | this.#logMap.forEach((logger) => {
67 | logger.dispose();
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/logger/common/log-service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BaseLogServiceOptions,
3 | LogLevel,
4 | format,
5 | } from '@opensumi/ide-logs';
6 | import { uuid } from '@opensumi/ide-core-common';
7 | import type * as spdlog from '@vscode/spdlog'
8 | import { ILogService } from './types'
9 |
10 | interface ILog {
11 | level: LogLevel;
12 | message: string;
13 | }
14 |
15 | interface ILogServiceOptions {
16 | logPath: string;
17 | logLevel: LogLevel;
18 | pid?: number;
19 | }
20 |
21 | enum SpdLogLevel {
22 | Trace,
23 | Debug,
24 | Info,
25 | Warning,
26 | Error,
27 | Critical,
28 | Off
29 | }
30 |
31 | export abstract class AbstractLogService implements ILogService {
32 | protected logger: SpdLogger | undefined;
33 | protected logPath: string;
34 | protected logLevel: LogLevel;
35 |
36 | constructor(options: ILogServiceOptions) {
37 | this.logPath = options.logPath;
38 | this.logLevel = options.logLevel || LogLevel.Info;
39 | }
40 |
41 | abstract sendLog(level: LogLevel, message: string): void
42 |
43 | protected shouldLog(level: LogLevel): boolean {
44 | return this.getLevel() <= level;
45 | }
46 |
47 | verbose(): void {
48 | if (!this.shouldLog(LogLevel.Verbose)) return
49 | this.sendLog(LogLevel.Verbose, format(arguments));
50 | }
51 |
52 | debug(): void {
53 | if (!this.shouldLog(LogLevel.Debug)) return
54 | this.sendLog(LogLevel.Debug, format(arguments));
55 | }
56 |
57 | log(): void {
58 | if (!this.shouldLog(LogLevel.Info)) return
59 | this.sendLog(LogLevel.Info, format(arguments));
60 | }
61 |
62 | info(): void {
63 | if (!this.shouldLog(LogLevel.Info)) return
64 | this.sendLog(LogLevel.Info, format(arguments));
65 | }
66 |
67 | warn(): void {
68 | if (!this.shouldLog(LogLevel.Warning)) return
69 | this.sendLog(LogLevel.Warning, format(arguments));
70 | }
71 |
72 | error(): void {
73 | if (!this.shouldLog(LogLevel.Error)) return
74 | const arg = arguments[0];
75 | let message: string;
76 |
77 | if (arg instanceof Error) {
78 | const array = Array.prototype.slice.call(arguments) as any[];
79 | array[0] = arg.stack;
80 | message = format(array);
81 | this.sendLog(LogLevel.Error, message);
82 | } else {
83 | message = format(arguments);
84 | this.sendLog(LogLevel.Error, message);
85 | }
86 | }
87 |
88 | critical(): void {
89 | if (!this.shouldLog(LogLevel.Critical)) return
90 | this.sendLog(LogLevel.Critical, format(arguments));
91 | }
92 |
93 | setOptions(options: BaseLogServiceOptions) {
94 | if (options.logLevel) {
95 | this.logLevel = options.logLevel;
96 | }
97 | }
98 |
99 | getLevel(): LogLevel {
100 | return this.logLevel;
101 | }
102 |
103 | setLevel(level: LogLevel): void {
104 | this.logLevel = level;
105 | }
106 |
107 | async drop() {}
108 |
109 | async flush() {}
110 |
111 | dispose() {}
112 | }
113 |
114 | export class SpdLogger extends AbstractLogService {
115 | #buffer: ILog[] = [];
116 | #spdLoggerCreatePromise: Promise;
117 | #logger: spdlog.Logger | undefined;
118 |
119 | constructor(options: ILogServiceOptions) {
120 | super(options);
121 | this.#spdLoggerCreatePromise = this.#createSpdLogLogger();
122 | }
123 |
124 | async #createSpdLogLogger(): Promise {
125 | const fileCount = 6;
126 | const fileSize = 5 * 1024 * 1024;
127 | try {
128 | const _spdlog = await import('@vscode/spdlog');
129 | _spdlog.setFlushOn(SpdLogLevel.Trace);
130 | const logger = await _spdlog.createAsyncRotatingLogger(uuid(), this.logPath, fileSize, fileCount);
131 | this.#logger = logger;
132 | logger.setPattern('%Y-%m-%d %H:%M:%S.%e [%l] %v');
133 | logger.setLevel(this.getSpdLogLevel(this.getLevel()))
134 | for (const { level, message } of this.#buffer) {
135 | this.sendLog( level, message);
136 | }
137 | this.#buffer = [];
138 | } catch (e) {
139 | console.error(e);
140 | }
141 | }
142 |
143 | sendLog(level: LogLevel, message: string): void {
144 | if (this.#logger) {
145 | switch (level) {
146 | case LogLevel.Verbose:
147 | return this.#logger.trace(message);
148 | case LogLevel.Debug:
149 | return this.#logger.debug(message);
150 | case LogLevel.Info:
151 | return this.#logger.info(message);
152 | case LogLevel.Warning:
153 | return this.#logger.warn(message);
154 | case LogLevel.Error:
155 | return this.#logger.error(message);
156 | case LogLevel.Critical:
157 | return this.#logger.critical(message);
158 | default:
159 | throw new Error('Invalid log level');
160 | }
161 | } else if (this.getLevel() <= level) {
162 | this.#buffer.push({ level, message });
163 | }
164 | }
165 |
166 | override async flush() {
167 | if (this.#logger) {
168 | this.#logger.flush();
169 | } else {
170 | this.#spdLoggerCreatePromise.then(() => this.flush());
171 | }
172 | }
173 |
174 | override async drop() {
175 | if (this.#logger) {
176 | this.#logger.drop();
177 | } else {
178 | return this.#spdLoggerCreatePromise.then(() => this.drop());
179 | }
180 | }
181 |
182 | override dispose(): void {
183 | this.drop();
184 | }
185 |
186 | private getSpdLogLevel(level: LogLevel): SpdLogLevel {
187 | switch (level) {
188 | case LogLevel.Verbose: return SpdLogLevel.Trace;
189 | case LogLevel.Debug: return SpdLogLevel.Debug;
190 | case LogLevel.Info: return SpdLogLevel.Info;
191 | case LogLevel.Warning: return SpdLogLevel.Warning;
192 | case LogLevel.Error: return SpdLogLevel.Error;
193 | case LogLevel.Critical: return SpdLogLevel.Critical;
194 | default: return SpdLogLevel.Off;
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/logger/common/types.ts:
--------------------------------------------------------------------------------
1 | import type { ILogService as _ILogService } from '@opensumi/ide-logs/lib/common'
2 | export const ILogService = Symbol('ILogService');
3 | export interface ILogService extends _ILogService {
4 | info(...args: any[]): void;
5 | }
6 |
--------------------------------------------------------------------------------
/src/logger/electron-main/index.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Provider, Injector } from '@opensumi/di';
2 | import { ElectronMainModule } from '@opensumi/ide-core-electron-main/lib/electron-main-module';
3 | import { ILogServiceManager, SupportLogNamespace } from '@opensumi/ide-logs';
4 | import { LogServiceManager } from './log-manager'
5 | import { ILogService } from '../common'
6 |
7 | @Injectable()
8 | export class LoggerModule extends ElectronMainModule {
9 | providers: Provider[] = [
10 | {
11 | token: ILogServiceManager,
12 | useClass: LogServiceManager,
13 | },
14 | {
15 | token: ILogService,
16 | useFactory: (injector: Injector) => {
17 | return (injector.get(ILogServiceManager)).getLogger(SupportLogNamespace.Main)
18 | }
19 | }
20 | ];
21 | }
22 |
--------------------------------------------------------------------------------
/src/logger/electron-main/log-manager.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'node:fs/promises'
2 | import * as path from 'path'
3 | import { Injectable, Autowired } from '@opensumi/di';
4 | import { IEnvironmentService } from '@/core/common'
5 | import { AbstractLogServiceManager } from '../common'
6 |
7 | @Injectable()
8 | export class LogServiceManager extends AbstractLogServiceManager {
9 | @Autowired(IEnvironmentService)
10 | environmentService: IEnvironmentService
11 |
12 | constructor() {
13 | super();
14 | // 启动时清除旧日志,后续加个定时任务清除
15 | this.cleanOldLogs();
16 | }
17 |
18 | getRootLogFolder(): string {
19 | return this.environmentService.logRoot;
20 | }
21 |
22 | getLogFolder(): string {
23 | return this.environmentService.logHome;
24 | }
25 |
26 | async cleanOldLogs(): Promise {
27 | try {
28 | const { logHome, logRoot } = this.environmentService;
29 | const currentLog = path.basename(logHome);
30 | const children = await fs.readdir(logRoot);
31 | const allSessions = children.filter((name) => /^\d{8}$/.test(name));
32 | const oldSessions = allSessions.sort().filter((d) => d !== currentLog);
33 | const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 4));
34 | if (toDelete.length > 0) {
35 | await Promise.all(toDelete.map((name) => fs.rm(path.join(logRoot, name), { recursive: true, force: true, maxRetries: 3 })));
36 | }
37 | } catch {
38 | // noop
39 | }
40 | }
41 |
42 | async cleanAllLogs(): Promise {
43 | try {
44 | const { logRoot } = this.environmentService;
45 | const children = await fs.readdir(logRoot);
46 | const allSessions = children.filter((name) => /^\d{8}$/.test(name));
47 | if (allSessions.length > 0) {
48 | await Promise.all(allSessions.map((name) => fs.rm(path.join(logRoot, name), { recursive: true, force: true, maxRetries: 3 })));
49 | }
50 | } catch {
51 | // noop
52 | }
53 | }
54 |
55 | async cleanExpiredLogs(day: number): Promise {
56 | try {
57 | const { logRoot } = this.environmentService;
58 | const children = await fs.readdir(logRoot);
59 | const expiredSessions = children.filter((name) => /^\d{8}$/.test(name) && Number(name) < day);
60 | if (expiredSessions.length > 0) {
61 | await Promise.all(expiredSessions.map((name) => fs.rm(path.join(logRoot, name), { recursive: true, force: true, maxRetries: 3 })));
62 | }
63 | } catch {
64 | // noop
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/logger/node/index.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Provider } from '@opensumi/di';
2 | import { NodeModule } from '@opensumi/ide-core-node';
3 | import { ILogServiceManager } from '@opensumi/ide-logs';
4 | import { LogServiceManager } from './log-manager'
5 |
6 | @Injectable()
7 | export class LoggerModule extends NodeModule {
8 | providers: Provider[] = [
9 | {
10 | token: ILogServiceManager,
11 | useClass: LogServiceManager,
12 | override: true,
13 | },
14 | ];
15 | }
16 |
--------------------------------------------------------------------------------
/src/logger/node/log-manager.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path'
2 | import { Injectable } from '@opensumi/di';
3 | import { AbstractLogServiceManager } from '../common'
4 | import * as process from "node:process";
5 |
6 | @Injectable()
7 | export class LogServiceManager extends AbstractLogServiceManager {
8 | getRootLogFolder(): string {
9 | return process.env.IDE_LOG_ROOT!;
10 | }
11 |
12 | getLogFolder(): string {
13 | return path.join(process.env.IDE_LOG_HOME || '', `window${process.env.CODE_WINDOW_CLIENT_ID?.slice('CODE_WINDOW_CLIENT_ID:'?.length)}`)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "target": "ES2022",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "strictPropertyInitialization": false,
8 | "sourceMap": true,
9 | "experimentalDecorators": true,
10 | "emitDecoratorMetadata": true,
11 | "importHelpers": true,
12 | "resolveJsonModule": true,
13 | "noFallthroughCasesInSwitch": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "downlevelIteration": true,
16 | "noEmitOnError": false,
17 | "noImplicitAny": false,
18 | "skipLibCheck": true,
19 | "strictFunctionTypes": false,
20 | "jsx": "react",
21 | "baseUrl": ".",
22 | "rootDir": ".",
23 | "outDir": "dist",
24 | "noUnusedLocals": false,
25 | "allowSyntheticDefaultImports": true,
26 | "esModuleInterop": true,
27 | "declaration": true,
28 | "useDefineForClassFields": false,
29 | "lib": [
30 | "DOM",
31 | "ES2022",
32 | ],
33 | "paths": {
34 | "@/*": ["./src/*"],
35 | },
36 | "typeRoots": [
37 | "./node_modules/@types",
38 | "./typings"
39 | ]
40 | },
41 | "include": [
42 | "src",
43 | "build",
44 | "typings",
45 | ],
46 | "exclude": [
47 | "node_modules",
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/typings/global/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.less';
2 | declare module '*.png';
3 | declare module '*.svg';
4 |
5 | declare const __PRODUCT__: any;
6 | declare const __CODE_WINDOW_DEV_SERVER_URL__: string;
7 | declare const __CODE_WINDOW_NAME__: string;
8 | declare const __UPDATE_WINDOW_NAME__: string;
9 | declare const __UPDATE_WINDOW_DEV_SERVER_URL__: string;
10 |
--------------------------------------------------------------------------------