diff --git a/app/package.json b/app/package.json index b341ccff3..42c154069 100644 --- a/app/package.json +++ b/app/package.json @@ -17,7 +17,7 @@ "vite-plugin-mock-dev-server": "^1.4.0", "vite-plugin-monaco-editor": "^1.1.0", "vscode-oniguruma": "^2.0.1", - "vscode-textmate": "6.x", + "vscode-textmate": "9.2.0", "@dtinsight/molecule": "workspace:*" } } \ No newline at end of file diff --git a/app/plugin.ts b/app/plugin.ts index acb8bd571..6b568eae1 100644 --- a/app/plugin.ts +++ b/app/plugin.ts @@ -18,6 +18,14 @@ export function esbuildPluginMonacoEditorNls(): EsbuildPlugin { }; }); + // Handle nls.messages.js + build.onLoad({ filter: /esm[\\\/]vs[\\\/]nls\.messages\.js/ }, async () => { + return { + contents: getNLSMessagesCode(), + loader: 'js', + }; + }); + build.onLoad({ filter: /monaco-editor[\\\/]esm[\\\/]vs.+\.js/ }, async (args) => { return { contents: transformLocalizeFuncCode(args.path), @@ -40,29 +48,46 @@ function transformLocalizeFuncCode(filepath: string) { if (re.exec(filepath)) { let path = RegExp.$1; path = path.replaceAll('\\', '/'); - code = code.replace(/localize\(/g, `localize('${path}', `); + code = code.replace(/localize\(/g, `localize('${path}', `).replace(/localize2\(/g, `localize2('${path}', `); } return code; } function getLocalizeCode() { return ` -// replace monaco-editor/esm/vs/nls.js _format +// replace monaco-editor/esm/vs/nls.js +import { getNLSLanguage, getNLSMessages } from './nls.messages.js'; + function _format(message, args) { + // Make sure message is a string + if (typeof message !== 'string') { + message = String(message || ''); + } + let result; if (args.length === 0) { result = message; } else { - result = String(message).replace(/\{(\d+)\}/g, function (match, rest) { + result = message.replace(/\{(\d+)\}/g, function (match, rest) { const index = rest[0]; - return typeof args[index] !== "undefined" ? args[index] : match; + const arg = args[index]; + let result = match; + if (typeof arg === 'string') { + result = arg; + } + else if (typeof arg === 'number' || typeof arg === 'boolean' || arg === void 0 || arg === null) { + result = String(arg); + } + return result; }); } return result; } -// replace monaco-editor/esm/vs/nls.js localize -function localize(path, data, defaultMessage) { +/** + * @skipMangle + */ +export function localize(path, data, defaultMessage) { const key = typeof data === "object" ? data.key : data; const lang = document?.documentElement.getAttribute("lang") || "en"; const _data = window.__locale__?.[lang] || {}; @@ -70,26 +95,80 @@ function localize(path, data, defaultMessage) { if (!message) { message = defaultMessage; } + + // Make sure message is a string + if (typeof message !== 'string') { + return defaultMessage || key || ''; + } + const args = []; for (let _i = 3; _i < arguments.length; _i++) { args[_i - 3] = arguments[_i]; } return _format(message, args); } -module.exports["localize"] = localize; -function loadMessageBundle(_file) { - return localize; +/** + * Only used when built: Looks up the message in the global NLS table. + * This table is being made available as a global through bootstrapping + * depending on the target context. + */ +function lookupMessage(index, fallback) { + const message = getNLSMessages()?.[index]; + if (typeof message !== 'string') { + if (typeof fallback === 'string') { + return fallback; + } + throw new Error(\`!!! NLS MISSING: \${index} !!!\`); + } + return message; +} + +/** + * @skipMangle + */ +export function localize2(path, data, originalMessage) { + const key = typeof data === "object" ? data.key : data; + const lang = document?.documentElement.getAttribute("lang") || "en"; + const _data = window.__locale__?.[lang] || {}; + let message = (_data[path] || {})[key]; + if (!message) { + message = originalMessage; + } + + // Make sure message is a string + if (typeof message !== 'string') { + message = originalMessage || key || ''; + } + if (typeof originalMessage !== 'string') { + originalMessage = key || ''; + } + + const args = []; + for (let _i = 3; _i < arguments.length; _i++) { + args[_i - 3] = arguments[_i]; + } + const value = _format(message, args); + return { + value, + original: originalMessage === message ? value : _format(originalMessage, args) + }; +} + +// Re-export from nls.messages.js for compatibility +export { getNLSLanguage, getNLSMessages } from './nls.messages.js'; +`; } -module.exports["loadMessageBundle"] = loadMessageBundle; -function config(_opt) { - return loadMessageBundle; +function getNLSMessagesCode() { + return ` +// replace monaco-editor/esm/vs/nls.messages.js +export function getNLSMessages() { + return globalThis._VSCODE_NLS_MESSAGES; } -module.exports["config"] = config; -function getConfiguredDefaultLocale() { - return undefined; +export function getNLSLanguage() { + return document?.documentElement.getAttribute("lang") || "en"; } -module.exports["getConfiguredDefaultLocale"] = getConfiguredDefaultLocale;`; +`; } diff --git a/app/src/components/testPane.tsx b/app/src/components/testPane.tsx index a7b20eeb1..f5be4adda 100644 --- a/app/src/components/testPane.tsx +++ b/app/src/components/testPane.tsx @@ -201,8 +201,8 @@ export default function TestPane({ context: molecule }: { context: IMoleculeCont name, icon: 'file', value: `// editor-${key} - // export interface Type { new (...args: any[]): T; } - // export type GenericClassDecorator = (target: T) => void;`, +export interface User { name: string; } +export type Variable = string | number;`, language: 'typescript', breadcrumb: [ { id: 'app', name: 'app' }, diff --git a/package.json b/package.json index c399d8998..123223fdd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dtinsight/molecule", - "version": "2.0.0-alpha.8", + "version": "2.0.0-alpha.10", "description": "A Web IDE UI Framework built with React.js, inspired by VSCode.", "module": "./esm/index.js", "typings": "./esm/index.d.ts", @@ -32,7 +32,7 @@ "react": ">=16.13.1", "react-dom": ">=16.13.1", "vscode-oniguruma": "^2.0.1", - "vscode-textmate": "6.x" + "vscode-textmate": "9.2.0" }, "peerDependenciesMeta": { "vscode-oniguruma": { @@ -67,7 +67,7 @@ "tsc-alias": "^1.8.7", "typescript": "4.7.4", "vscode-oniguruma": "^2.0.1", - "vscode-textmate": "6.x", + "vscode-textmate": "9.2.0", "yargs": "^17.7.2" }, "dependencies": { @@ -75,8 +75,8 @@ "@vscode/codicons": "^0.0.33", "immer": "^10.0.3", "lodash-es": "^4.17.21", - "monaco-editor": "^0.31.0", - "monaco-editor-nls": "2.0.0", + "monaco-editor": "0.52.2", + "monaco-editor-nls": "3.1.0", "normalize.css": "^8.0.1", "rc-dropdown": "^4.1.0", "rc-menu": "^9.11.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18244a4dd..9c9dd34c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,11 +21,11 @@ importers: specifier: ^4.17.21 version: 4.17.21 monaco-editor: - specifier: ^0.31.0 - version: 0.31.1 + specifier: 0.52.2 + version: 0.52.2 monaco-editor-nls: - specifier: 2.0.0 - version: 2.0.0 + specifier: 3.1.0 + version: 3.1.0 normalize.css: specifier: ^8.0.1 version: 8.0.1 @@ -124,8 +124,8 @@ importers: specifier: ^2.0.1 version: 2.0.1 vscode-textmate: - specifier: 6.x - version: 6.0.0 + specifier: 9.2.0 + version: 9.2.0 yargs: specifier: ^17.7.2 version: 17.7.2 @@ -158,13 +158,13 @@ importers: version: 1.8.4(esbuild@0.18.20)(rollup@3.29.5)(vite@4.5.9(@types/node@20.4.7)(sass@1.77.8)) vite-plugin-monaco-editor: specifier: ^1.1.0 - version: 1.1.0(monaco-editor@0.31.1) + version: 1.1.0(monaco-editor@0.52.2) vscode-oniguruma: specifier: ^2.0.1 version: 2.0.1 vscode-textmate: - specifier: 6.x - version: 6.0.0 + specifier: 9.2.0 + version: 9.2.0 packages: @@ -2156,11 +2156,11 @@ packages: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} - monaco-editor-nls@2.0.0: - resolution: {integrity: sha512-gQy0LxyUkvmGFRfjRaFHFDdv3UjCX2DfjoL4Dy6mhcq94r6pu2mzcCWIxJz+y7FCrQwpj+xXXUpy0ynOrmONoQ==} + monaco-editor-nls@3.1.0: + resolution: {integrity: sha512-GdzgKRAiwXlGI/ude/HbzP8ukml28yUoZ7rx/xsSsmYf9W13jTyWjXjoMK/+pjL6JorAThH2WkCYaDoCnIX7Jg==} - monaco-editor@0.31.1: - resolution: {integrity: sha512-FYPwxGZAeP6mRRyrr5XTGHD9gRXVjy7GUzF4IPChnyt3fS5WrNxIkS8DNujWf6EQy0Zlzpxw8oTVE+mWI2/D1Q==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -3218,8 +3218,8 @@ packages: vscode-oniguruma@2.0.1: resolution: {integrity: sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==} - vscode-textmate@6.0.0: - resolution: {integrity: sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==} + vscode-textmate@9.2.0: + resolution: {integrity: sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==} which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -5523,9 +5523,9 @@ snapshots: modify-values@1.0.1: {} - monaco-editor-nls@2.0.0: {} + monaco-editor-nls@3.1.0: {} - monaco-editor@0.31.1: {} + monaco-editor@0.52.2: {} ms@2.0.0: {} @@ -6641,9 +6641,9 @@ snapshots: - supports-color - utf-8-validate - vite-plugin-monaco-editor@1.1.0(monaco-editor@0.31.1): + vite-plugin-monaco-editor@1.1.0(monaco-editor@0.52.2): dependencies: - monaco-editor: 0.31.1 + monaco-editor: 0.52.2 vite@4.5.9(@types/node@20.4.7)(sass@1.77.8): dependencies: @@ -6657,7 +6657,7 @@ snapshots: vscode-oniguruma@2.0.1: {} - vscode-textmate@6.0.0: {} + vscode-textmate@9.2.0: {} which-boxed-primitive@1.0.2: dependencies: diff --git a/src/client/classNames/common.css b/src/client/classNames/common.css index 0540fa739..a7852133e 100644 --- a/src/client/classNames/common.css +++ b/src/client/classNames/common.css @@ -37,6 +37,11 @@ body .monaco-list:focus { } body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji'; +} + +/* Display the quickInputWidget of monaco editor uniformly at the top of the page */ +.monaco-editor .overflow-guard .overlayWidgets > div[widgetid='editor.contrib.quickInputWidget'] { + position: fixed !important; } diff --git a/src/client/components/diffEditor/index.tsx b/src/client/components/diffEditor/index.tsx index d01c2b536..868300de2 100644 --- a/src/client/components/diffEditor/index.tsx +++ b/src/client/components/diffEditor/index.tsx @@ -52,7 +52,7 @@ export default function MonacoDiffEditor({ useEffect(() => { if (!parent.current) return; - const container = instance?.getDomNode(); + const container = instance?.getContainerDomNode(); if (container) { // performance if (parent.current.firstChild === container) return; diff --git a/src/const/options.ts b/src/const/options.ts index 633ea9edf..66076c30d 100644 --- a/src/const/options.ts +++ b/src/const/options.ts @@ -7,7 +7,7 @@ export default { detectIndentation: true, trimAutoWhitespace: true, largeFileOptimizations: true, - wordBasedSuggestions: true, + wordBasedSuggestions: 'currentDocument', 'semanticHighlighting.enabled': 'configuredByTheme', stablePeek: false, maxTokenizationLineLength: 20000, diff --git a/src/monaco/index.ts b/src/monaco/index.ts index f8d98a08f..131473211 100644 --- a/src/monaco/index.ts +++ b/src/monaco/index.ts @@ -3,7 +3,6 @@ */ import 'monaco-editor/esm/vs/editor/editor.all'; -import 'monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp'; import 'monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard'; import 'monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens'; import 'monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; diff --git a/src/monaco/override/index.ts b/src/monaco/override/index.ts index ea4c74f36..dbf222b62 100644 --- a/src/monaco/override/index.ts +++ b/src/monaco/override/index.ts @@ -91,14 +91,24 @@ export function registerCommandsQuickAccessProvider(services: IMoleculeContext & ); } - protected async getCommandPicks(_: IDisposable, token: CancellationToken): Promise> { - if (token.isCancellationRequested) { + protected async getCommandPicks(_: IDisposable, token?: CancellationToken): Promise> { + const cancellationToken = token || { isCancellationRequested: false }; + + if (cancellationToken.isCancellationRequested) { return []; } return [...this.getCodeEditorCommandPicks(), ...this.getGlobalCommandPicks()]; } + protected hasAdditionalCommandPicks(): boolean { + return true; + } + + protected async getAdditionalCommandPicks(): Promise> { + return this.getGlobalCommandPicks(); + } + private getGlobalCommandPicks(): ICommandQuickPick[] { const globalCommandPicks: ICommandQuickPick[] = []; MenuRegistry.getCommands().forEach((value, key) => { diff --git a/src/monaco/override/providers.ts b/src/monaco/override/providers.ts index 9d5960e31..60d483fde 100644 --- a/src/monaco/override/providers.ts +++ b/src/monaco/override/providers.ts @@ -1,5 +1,5 @@ import type * as monaco from 'monaco-editor'; -import type { IGrammar, IGrammarConfiguration, IOnigLib, IRawGrammar, IRawTheme, StackElement } from 'vscode-textmate'; +import type { IGrammar, IGrammarConfiguration, IOnigLib, IRawGrammar, IRawTheme, StateStack } from 'vscode-textmate'; import { INITIAL, parseRawGrammar, Registry } from 'vscode-textmate'; import { Color, generateTokensCSSForColorMap, TokenizationRegistry } from '../types'; @@ -151,7 +151,7 @@ export class SimpleLanguageInfoProvider { } class TokenizerState implements monaco.languages.IState { - constructor(public readonly stateStack: StackElement) {} + constructor(public readonly stateStack: StateStack) {} clone(): monaco.languages.IState { return new TokenizerState(this.stateStack); diff --git a/src/monaco/types.ts b/src/monaco/types.ts index 196f6e34e..8647af062 100644 --- a/src/monaco/types.ts +++ b/src/monaco/types.ts @@ -10,33 +10,31 @@ import { } from 'monaco-editor/esm/vs/base/common/lifecycle'; import { ICodeEditorService as MonacoICodeEditorService } from 'monaco-editor/esm/vs/editor/browser/services/codeEditorService'; import { OpenerService as MonacoOpenerService } from 'monaco-editor/esm/vs/editor/browser/services/openerService'; -import { TokenizationRegistry as MonacoTokenizationRegistry } from 'monaco-editor/esm/vs/editor/common/modes.js'; -import { generateTokensCSSForColorMap as MonacoGenerateTokensCSSForColorMap } from 'monaco-editor/esm/vs/editor/common/modes/supports/tokenization.js'; -import { IModelService as MonacoIModelService } from 'monaco-editor/esm/vs/editor/common/services/modelService.js'; -import { IModeService as MonacoIModeService } from 'monaco-editor/esm/vs/editor/common/services/modeService.js'; +import { TokenizationRegistry as MonacoTokenizationRegistry } from 'monaco-editor/esm/vs/editor/common/languages'; +import { ILanguageService as MonacoILanguageService } from 'monaco-editor/esm/vs/editor/common/languages/language'; +import { ILanguageConfigurationService as MonacoILanguageConfigurationService } from 'monaco-editor/esm/vs/editor/common/languages/languageConfigurationRegistry'; +import { generateTokensCSSForColorMap as MonacoGenerateTokensCSSForColorMap } from 'monaco-editor/esm/vs/editor/common/languages/supports/tokenization'; +import { IEditorWorkerService as MonacoIEditorWorkerService } from 'monaco-editor/esm/vs/editor/common/services/editorWorker'; +import { ILanguageFeaturesService as MonacoILanguageFeaturesService } from 'monaco-editor/esm/vs/editor/common/services/languageFeatures'; +import { IModelService as MonacoIModelService } from 'monaco-editor/esm/vs/editor/common/services/model.js'; import { ITextModelService as MonacoITextModelService } from 'monaco-editor/esm/vs/editor/common/services/resolverService'; -import { IEditorWorkerService as MonacoIEditorWorkerService } from 'monaco-editor/esm/vs/editor/common/services/editorWorkerService'; -import { AbstractEditorCommandsQuickAccessProvider as MonacoAbstractEditorCommandsQuickAccessProvider } from 'monaco-editor/esm/vs/editor/contrib/quickAccess/commandsQuickAccess'; -import { AbstractGotoLineQuickAccessProvider as MonacoAbstractGotoLineQuickAccessProvider } from 'monaco-editor/esm/vs/editor/contrib/quickAccess/gotoLineQuickAccess'; -import { - SimpleEditorModelResolverService as MonacoSimpleEditorModelResolverService, - SimpleLayoutService as MonacoSimpleLayoutService, -} from 'monaco-editor/esm/vs/editor/standalone/browser/simpleServices'; +import { AbstractEditorCommandsQuickAccessProvider as MonacoAbstractEditorCommandsQuickAccessProvider } from 'monaco-editor/esm/vs/editor/contrib/quickAccess/browser/commandsQuickAccess'; +import { AbstractGotoLineQuickAccessProvider as MonacoAbstractGotoLineQuickAccessProvider } from 'monaco-editor/esm/vs/editor/contrib/quickAccess/browser/gotoLineQuickAccess'; import { + StandaloneDiffEditor2 as MonacoStandaloneDiffEditor, StandaloneEditor as MonacoStandaloneEditor, - StandaloneDiffEditor as MonacoStandaloneDiffEditor, } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneCodeEditor'; -import { - DynamicStandaloneServices as MonacoDynamicStandaloneServices, - StaticServices as MonacoStaticServices, -} from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices'; -import { IStandaloneThemeService as MonacoIStandaloneThemeService } from 'monaco-editor/esm/vs/editor/standalone/common/standaloneThemeService'; +import { EditorScopedLayoutService as MonacoEditorScopedLayoutService } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneLayoutService'; +import { StandaloneServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices'; +import { IStandaloneThemeService as MonacoIStandaloneThemeService } from 'monaco-editor/esm/vs/editor/standalone/common/standaloneTheme'; // no import { localize as MonacoLocalize } from 'monaco-editor/esm/vs/nls'; import { IAccessibilityService as MonacoIAccessibilityService } from 'monaco-editor/esm/vs/platform/accessibility/common/accessibility'; +import { IAccessibilitySignalService as MonacoIAccessibilitySignalService } from 'monaco-editor/esm/vs/platform/accessibilitySignal/browser/accessibilitySignalService'; import { MenuId as MonacoMenuId, MenuRegistry as MonacoMenuRegistry, } from 'monaco-editor/esm/vs/platform/actions/common/actions'; +import { IClipboardService as MonacoIClipboardService } from 'monaco-editor/esm/vs/platform/clipboard/common/clipboardService'; import { CommandsRegistry as MonacoCommandsRegistry, ICommandService as MonacoICommandService, @@ -46,8 +44,12 @@ import { ContextKeyExpr as MonacoContextKeyExpr, IContextKeyService as MonacoIContextKeyService, } from 'monaco-editor/esm/vs/platform/contextkey/common/contextkey'; -import { IContextViewService as MonacoIContextViewService } from 'monaco-editor/esm/vs/platform/contextview/browser/contextView'; +import { + IContextMenuService as MonacoIContextMenuService, + IContextViewService as MonacoIContextViewService, +} from 'monaco-editor/esm/vs/platform/contextview/browser/contextView'; import { IDialogService as MonacoIDialogService } from 'monaco-editor/esm/vs/platform/dialogs/common/dialogs'; +import { IHoverService as MonacoIHoverService } from 'monaco-editor/esm/vs/platform/hover/browser/hover'; import { _util as _monacoUtil, IInstantiationService as MonacoIInstantiationService, @@ -59,7 +61,8 @@ import { ResolvedKeybindingItem as MonacoResolvedKeybindingItem } from 'monaco-e import { ILayoutService as MonacoILayoutService } from 'monaco-editor/esm/vs/platform/layout/browser/layoutService'; import { INotificationService as MonacoINotificationService } from 'monaco-editor/esm/vs/platform/notification/common/notification'; import { IOpenerService as MonacoIOpenerService } from 'monaco-editor/esm/vs/platform/opener/common/opener'; -import { QuickInputService as MonacoQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/browser/quickInput'; +import { IEditorProgressService as MonacoIEditorProgressService } from 'monaco-editor/esm/vs/platform/progress/common/progress'; +import { QuickInputService as MonacoQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/browser/quickInputService'; import { Extensions as MonacoExtensions, IQuickAccessRegistry, @@ -67,9 +70,13 @@ import { import { IQuickInputService as MonacoIQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; import { Registry as MonacoRegistry } from 'monaco-editor/esm/vs/platform/registry/common/platform'; import { ITelemetryService as MonacoITelemetryService } from 'monaco-editor/esm/vs/platform/telemetry/common/telemetry'; -import { IContextMenuService as MonacoIContextMenuService } from 'monaco-editor/esm/vs/platform/contextview/browser/contextView'; -import { IEditorProgressService as MonacoIEditorProgressService } from 'monaco-editor/esm/vs/platform/progress/common/progress'; -import { IClipboardService as MonacoIClipboardService } from 'monaco-editor/esm/vs/platform/clipboard/common/clipboardService'; + +// Lazy initialization to avoid circular dependency issues +export const MonacoSimpleLayoutService = () => StandaloneServices.get(ILayoutService); + +// Export updated types +export type IEditorOptions = editor.IStandaloneEditorConstructionOptions; +export type IDiffEditorOptions = editor.IStandaloneDiffEditorConstructionOptions; export const _util: { serviceIds: Map>; @@ -401,7 +408,11 @@ interface IOpenerService { new (editorService: any, commandService: any): {} & IDisposable; } -interface IModeService {} +interface ILanguageService {} +interface ILanguageConfigurationService {} +interface ILanguageFeaturesService {} +interface IHoverService {} +interface IAccessibilitySignalService {} interface IModelService {} interface ITextModelService {} @@ -410,7 +421,12 @@ const KeyChord: (firstPart: any, secondPart?: any) => number = MonacoKeyChord; const localize: (data: string, message: string, ...args: any[]) => string = MonacoLocalize; const ICodeEditorService: ServiceIdentifier = MonacoICodeEditorService; const IQuickInputService: ServiceIdentifier = MonacoIQuickInputService; -const IModeService: ServiceIdentifier = MonacoIModeService; +const ILanguageService: ServiceIdentifier = MonacoILanguageService; +const ILanguageConfigurationService: ServiceIdentifier = + MonacoILanguageConfigurationService; +const ILanguageFeaturesService: ServiceIdentifier = MonacoILanguageFeaturesService; +const IHoverService: ServiceIdentifier = MonacoIHoverService; +const IAccessibilitySignalService: ServiceIdentifier = MonacoIAccessibilitySignalService; const IModelService: ServiceIdentifier = MonacoIModelService; const ITextModelService: ServiceIdentifier = MonacoITextModelService; const OpenerService: IOpenerService = MonacoOpenerService; @@ -512,68 +528,84 @@ interface Color { new (): ColorClass; } -interface SimpleEditorModelResolverService { - new (modelService: IModelService): SimpleEditorModelResolverService & IDisposable; - setEditor(editor: editor.IStandaloneCodeEditor | editor.IStandaloneDiffEditor): void; -} -const SimpleEditorModelResolverService: SimpleEditorModelResolverService = MonacoSimpleEditorModelResolverService; - interface SimpleLayoutService { dimension: any; container: any; focus(): void; } -const SimpleLayoutService: new (_codeEditorService: ICodeEditorService, _container: any) => SimpleLayoutService = +const SimpleLayoutService: new (_codeEditorService: ICodeEditorService) => SimpleLayoutService = MonacoSimpleLayoutService; +interface EditorScopedLayoutService extends SimpleLayoutService { + mainContainer: any; +} +const EditorScopedLayoutService: new ( + _container: any, + _codeEditorService: ICodeEditorService +) => EditorScopedLayoutService = MonacoEditorScopedLayoutService; + const Color: Color = MonacoColor; type StandaloneEditor = new ( domElement: any, _options: any, - toDispose: any, instantiationService: any, codeEditorService: any, commandService: any, contextKeyService: any, + hoverService: any, keybindingService: any, - contextViewService: any, themeService: any, notificationService: any, configurationService: any, accessibilityService: any, modelService: any, - modeService: any + languageService: any, + languageConfigurationService: any, + languageFeaturesService: any ) => editor.IStandaloneCodeEditor; const StandaloneEditor: StandaloneEditor = MonacoStandaloneEditor; type StandaloneDiffEditor = new ( domElement: any, _options: any, - toDispose: any, instantiationService: any, contextKeyService: any, - keybindingService: any, - contextViewService: any, - editorWorkerService: any, codeEditorService: any, themeService: any, notificationService: any, configurationService: any, contextMenuService: any, editorProgressService: any, - clipboardService: any + clipboardService: any, + accessibilitySignalService: any ) => editor.IStandaloneDiffEditor; const StandaloneDiffEditor: StandaloneDiffEditor = MonacoStandaloneDiffEditor; -type DynamicStandaloneServices = new ( - domElement: HTMLElement | null, - overrides?: editor.IEditorOverrideServices -) => ServicesAccessor; -const DynamicStandaloneServices: DynamicStandaloneServices = MonacoDynamicStandaloneServices; - -type StaticServices = Record; -const StaticServices: StaticServices = MonacoStaticServices; +type StaticServices = { + modelService: { get(): any }; + codeEditorService: { get(serviceId: any): any }; +}; +const StaticServices: StaticServices = { + modelService: { + get: () => { + // Ensure services are initialized before accessing + if (!StandaloneServices.get(IModelService)) { + StandaloneServices.initialize({}); + } + return StandaloneServices.get(IModelService); + }, + }, + codeEditorService: { + get: (serviceId: any) => { + // Ensure services are initialized before accessing + if (!StandaloneServices.get(serviceId)) { + StandaloneServices.initialize({}); + } + return StandaloneServices.get(serviceId); + }, + }, +}; type IStandaloneThemeService = any; const IStandaloneThemeService: IStandaloneThemeService = MonacoIStandaloneThemeService; @@ -646,27 +678,31 @@ export { CommandsRegistry, ContextKeyExpr, DisposableStore, - DynamicStandaloneServices, + EditorScopedLayoutService, IAccessibilityService, + IAccessibilitySignalService, + IClipboardService, ICodeEditorService, ICommandService, IConfigurationService, IContextKeyService, + IContextMenuService, IContextViewService, + IEditorProgressService, + IEditorWorkerService, + IHoverService, IInstantiationService, IKeybindingService, + ILanguageConfigurationService, + ILanguageFeaturesService, + ILanguageService, ILayoutService, IModelService, - IModeService, INotificationService, IOpenerService, IQuickInputService, IStandaloneThemeService, ITextModelService, - IEditorWorkerService, - IContextMenuService, - IEditorProgressService, - IClipboardService, KeybindingsRegistry, KeyChord, localize, @@ -676,9 +712,9 @@ export { QuickInputService, ResolvedKeybindingItem, ServiceCollection, - SimpleEditorModelResolverService, SimpleLayoutService, - StandaloneEditor, StandaloneDiffEditor, + StandaloneEditor, + StandaloneServices, StaticServices, }; diff --git a/src/services/action.ts b/src/services/action.ts index 0abe9a085..51d93f36b 100644 --- a/src/services/action.ts +++ b/src/services/action.ts @@ -24,6 +24,7 @@ import type { EditorService } from './editor'; import type { EditorTreeService } from './editorTree'; import type { ExplorerService } from './explorer'; import type { FolderTreeService } from './folderTree'; +import type { KeyboardFocusService } from './keyboardFocus'; import type { LayoutService } from './layout'; import type { LocaleService } from './locale'; import type { MenuBarService } from './menuBar'; @@ -52,6 +53,7 @@ export class ActionService extends BaseService { @inject('sidebar') private sidebar: SidebarService, @inject('explorer') private explorer: ExplorerService, @inject('folderTree') private folderTree: FolderTreeService, + @inject('keyboardFocus') private keyboardFocus: KeyboardFocusService, @inject('panel') private panel: PanelService, @inject('output') private output: OutputService, @inject('editor') private editor: EditorService, @@ -78,6 +80,7 @@ export class ActionService extends BaseService { sidebar: this.sidebar, explorer: this.explorer, folderTree: this.folderTree, + keyboardFocus: this.keyboardFocus, panel: this.panel, output: this.output, editor: this.editor, @@ -101,7 +104,14 @@ export class ActionService extends BaseService { disposables.add( CommandsRegistry.registerCommand({ id: command.id, - handler: (accessor: any, ...args: any) => action.run(accessor, ...args), + handler: (accessor: any, ...args: any) => { + const ctx = this.getContext(); + + // Ensure the hidden editor has focus when QuickInputService operations are needed + ctx.keyboardFocus.ensureQuickInputContext(); + + action.run(accessor, ...args); + }, description, }) ); @@ -169,8 +179,19 @@ export class ActionService extends BaseService { // Get lower priority keybinding const lowerPriorty = targetKeybinding[targetKeybinding.length - 1]; // keybinding which is chord key[组合键] can get more than 1 parts - const keybindings: ISimpleKeybinding[] = lowerPriorty.keybinding; - return keybindings; + // The keybinding property is a Keybinding object with chords property + const keybindingObj = lowerPriorty.keybinding; + if (keybindingObj && keybindingObj.chords) { + // Convert KeyCodeChord[] to ISimpleKeybinding[] + const keybindings: ISimpleKeybinding[] = keybindingObj.chords.map((chord: any) => ({ + ctrlKey: chord.ctrlKey, + shiftKey: chord.shiftKey, + altKey: chord.altKey, + metaKey: chord.metaKey, + keyCode: chord.keyCode, + })); + return keybindings; + } } return null; } diff --git a/src/services/editor.ts b/src/services/editor.ts index 132b8510e..1b7320d64 100644 --- a/src/services/editor.ts +++ b/src/services/editor.ts @@ -14,7 +14,9 @@ import type { Variant, } from 'mo/types'; import { getPrevOrNext, randomId, searchById } from 'mo/utils'; -import { injectable } from 'tsyringe'; +import { inject, injectable } from 'tsyringe'; + +import { KeyboardFocusService } from './keyboardFocus'; type EditorContextMenu = ContextMenuHandler<[tabId: UniqueId, groupId: UniqueId]>; @@ -22,7 +24,7 @@ type EditorContextMenu = ContextMenuHandler<[tabId: UniqueId, groupId: UniqueId] export class EditorService extends BaseService { protected state: EditorModel; - constructor() { + constructor(@inject('keyboardFocus') private keyboardFocus: KeyboardFocusService) { super('editor'); this.state = new EditorModel(); } @@ -81,7 +83,8 @@ export class EditorService extends BaseService { public setOptions(options: IEditorOptions) { this.dispatch((draft) => { - draft.options = options; + // TODO: fix ts type + draft.options = options as any; }); } @@ -167,13 +170,22 @@ export class EditorService extends BaseService { const [group] = draft.groups.splice(idx, 1); group.editorInstance?.dispose(); }); + + let modelNumber = 0; + // Dispose models closed.forEach((tab) => { // Can't disposed model directly as model maybe shared by different group's tab if (!tab.model || draft.groups.find((group) => group.data.find((i) => i.model === tab.model))) return; tab.model.dispose(); + modelNumber += 1; }); + if (modelNumber > 0) { + // Recreate hidden editor to fix focus loss issue + this.keyboardFocus.recreateHiddenEditor(); + } + // ===================== effects ===================== this.emit(EditorEvent.onClose, closed); const currentActiveTab = draft.groups.find(searchById(draft.current))?.activeTab; diff --git a/src/services/extension.ts b/src/services/extension.ts index 878702ce6..eedeac600 100644 --- a/src/services/extension.ts +++ b/src/services/extension.ts @@ -21,6 +21,7 @@ import type { EditorService } from './editor'; import type { EditorTreeService } from './editorTree'; import type { ExplorerService } from './explorer'; import type { FolderTreeService } from './folderTree'; +import type { KeyboardFocusService } from './keyboardFocus'; import type { LayoutService } from './layout'; import type { LocaleService } from './locale'; import type { MenuBarService } from './menuBar'; @@ -50,6 +51,7 @@ export class ExtensionService extends BaseService { @inject('sidebar') private sidebar: SidebarService, @inject('explorer') private explorer: ExplorerService, @inject('folderTree') private folderTree: FolderTreeService, + @inject('keyboardFocus') private keyboardFocus: KeyboardFocusService, @inject('panel') private panel: PanelService, @inject('output') private output: OutputService, @inject('editor') private editor: EditorService, @@ -79,6 +81,7 @@ export class ExtensionService extends BaseService { sidebar: this.sidebar, explorer: this.explorer, folderTree: this.folderTree, + keyboardFocus: this.keyboardFocus, panel: this.panel, output: this.output, editor: this.editor, diff --git a/src/services/instance.ts b/src/services/instance.ts index e5bbb733e..210bdfe79 100644 --- a/src/services/instance.ts +++ b/src/services/instance.ts @@ -19,6 +19,7 @@ import type { EditorTreeService } from './editorTree'; import type { ExplorerService } from './explorer'; import type { ExtensionService } from './extension'; import type { FolderTreeService } from './folderTree'; +import type { KeyboardFocusService } from './keyboardFocus'; import type { LayoutService } from './layout'; import type { LocaleService } from './locale'; import type { MenuBarService } from './menuBar'; @@ -86,7 +87,7 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp private childContainer = container.createChildContainer(); - private wrapper?: RenderFunction + private wrapper?: RenderFunction; private register(token: string, cto: constructor) { this.childContainer.register(token, cto, { @@ -117,14 +118,19 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp private getServices() { const locale = this.resolve('locale'); locale.setCurrent(this._config.defaultLocale); + const builtin = this.resolve('builtin'); this.emit(InstanceHookKind.beforeInit); - this.emit(InstanceHookKind.beforeLoad); + const module = this.resolve('module'); const colorTheme = this.resolve('colorTheme'); colorTheme.setCurrent(this._config.defaultColorTheme); + + const keyboardFocus = this.resolve('keyboardFocus'); const monaco = this.resolve('monaco'); + monaco.setKeyboardFocusService(keyboardFocus); + const contextMenu = this.resolve('contextMenu'); const auxiliaryBar = this.resolve('auxiliaryBar'); const layout = this.resolve('layout'); @@ -167,6 +173,7 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp notification, search, settings, + keyboardFocus, monaco, module, extension, @@ -188,6 +195,7 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp import('./explorer'), import('./extension'), import('./folderTree'), + import('./keyboardFocus'), import('./layout'), import('./locale'), import('./menuBar'), @@ -213,6 +221,7 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp { ExplorerService }, { ExtensionService }, { FolderTreeService }, + { KeyboardFocusService }, { LayoutService }, { LocaleService }, { MenuBarService }, @@ -238,6 +247,7 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp this.register('explorer', ExplorerService); this.register('extension', ExtensionService); this.register('folderTree', FolderTreeService); + this.register('keyboardFocus', KeyboardFocusService); this.register('layout', LayoutService); this.register('locale', LocaleService); this.register('menuBar', MenuBarService); @@ -329,7 +339,7 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp private predict: Parameters[0] = undefined; public render = (container?: HTMLElement | null, wrapper?: RenderFunction) => { this.wrapper = wrapper; - + if (this.loading) { this.predict = container; return; @@ -363,12 +373,12 @@ export class InstanceService extends GlobalEvent implements IInstanceServiceProp modules: services.module.modules, controllers, }, - }) + }); if (wrapper) { - this.root.render(wrapper(child)) + this.root.render(wrapper(child)); } else { - this.root.render(child) - }; + this.root.render(child); + } container.appendChild(root); }; diff --git a/src/services/keyboardFocus.ts b/src/services/keyboardFocus.ts new file mode 100644 index 000000000..c934bbdd9 --- /dev/null +++ b/src/services/keyboardFocus.ts @@ -0,0 +1,219 @@ +import { BaseService } from 'mo/glue'; +import { + type editor as MonacoEditor, + IAccessibilityService, + ICodeEditorService, + ICommandService, + IConfigurationService, + IContextKeyService, + IHoverService, + IInstantiationService, + IKeybindingService, + ILanguageConfigurationService, + ILanguageFeaturesService, + ILanguageService, + IModelService, + INotificationService, + IStandaloneThemeService, + ServiceCollection, + StandaloneEditor, +} from 'mo/monaco'; +import { injectable } from 'tsyringe'; + +/** + * Service responsible for managing keyboard focus and global shortcut key functionality. + * This service creates and manages a hidden editor to ensure Monaco Editor's shortcut keys + * work even when no visible editor has focus. + */ +@injectable() +export class KeyboardFocusService extends BaseService { + protected state = null; + private _hiddenEditor: MonacoEditor.IStandaloneCodeEditor | null = null; + private _hiddenEditorContainer: HTMLElement | null = null; + private _hiddenEditorStyle: HTMLStyleElement | null = null; + private _focusedEditor: MonacoEditor.IStandaloneCodeEditor | null = null; + private _isInitialized = false; + private _globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null; + private _services: ServiceCollection | null = null; + + constructor() { + super('keyboardFocus'); + } + + public initialize(services: ServiceCollection): void { + if (this._isInitialized) return; + this._services = services; + + // Create hidden editor after Monaco services are available + // Use RAF to ensure DOM is ready and services are fully initialized + window.requestAnimationFrame(() => { + this.createHiddenEditor(); + this._isInitialized = true; + }); + } + + public dispose(): void { + if (!this._isInitialized) return; + this.disposeHiddenEditor(); + this._isInitialized = false; + } + + /** + * Ensure the hidden editor has focus when QuickInputService operations are needed + */ + public ensureQuickInputContext(): void { + if (!this._isInitialized) return; + + // If there's a focused editor with text focus, use it + if (this._focusedEditor && this._focusedEditor.hasTextFocus()) return; + + this.focusHiddenEditor(); + } + + /** + * Focus the hidden editor with retry mechanism and improved reliability + */ + private focusHiddenEditor(): void { + if (!this._hiddenEditor || !this._hiddenEditorContainer) return; + this._hiddenEditor.focus(); + + // Check if the hidden editor has focus + const isFocused = this._hiddenEditor.hasTextFocus(); + + // If the hidden editor fails to focus, try to refocus it + window.requestAnimationFrame(() => { + if (!isFocused) { + this._hiddenEditor?.focus(); + } + }); + } + + public registerEditor(editor: MonacoEditor.IStandaloneCodeEditor): void { + if (!this._isInitialized) return; + this.setupEditorFocusTracking(editor); + } + + /** + * Force recreation of the hidden editor (for debugging or recovery purposes) + */ + public recreateHiddenEditor(): void { + if (!this._isInitialized) return; + this.disposeHiddenEditor(); + window.requestAnimationFrame(() => { + this.createHiddenEditor(); + }); + } + + /** + * Create a hidden editor to ensure QuickInputService always has a focused editor context + */ + private createHiddenEditor(): void { + if (!this._services) return; + try { + // Create a hidden container for the editor + this._hiddenEditorContainer = document.createElement('div'); + this._hiddenEditorContainer.className = 'mo-hidden-editor-container'; + document.body.appendChild(this._hiddenEditorContainer); + + // Create a style element to hide the editor + this._hiddenEditorStyle = document.createElement('style'); + this._hiddenEditorStyle.textContent = ` + .mo-hidden-editor-container { + pointer-events: none; + position: fixed; + left: -9999px; + top: -9999px; + width: 100vw; + height: 100vh; + z-index: 9999; + } + .mo-hidden-editor-container .monaco-editor { + background-color: transparent; + outline: none; + } + .mo-hidden-editor-container .monaco-editor > *:not(.overflow-guard) { + opacity: 0; + } + .mo-hidden-editor-container .monaco-editor .overflow-guard > *:not(.overlayWidgets) { + opacity: 0; + } + .mo-hidden-editor-container .monaco-editor .overflow-guard .overlayWidgets { + pointer-events: auto; + } + `; + document.head.appendChild(this._hiddenEditorStyle); + + // Create the hidden editor with minimal configuration + this._hiddenEditor = new StandaloneEditor( + this._hiddenEditorContainer, + { + readOnly: true, + minimap: { enabled: false }, + scrollbar: { vertical: 'hidden', horizontal: 'hidden' }, + lineNumbers: 'off', + }, + this._services.get(IInstantiationService), + this._services.get(ICodeEditorService), + this._services.get(ICommandService), + this._services.get(IContextKeyService), + this._services.get(IHoverService), + this._services.get(IKeybindingService), + this._services.get(IStandaloneThemeService), + this._services.get(INotificationService), + this._services.get(IConfigurationService), + this._services.get(IAccessibilityService), + this._services.get(IModelService), + this._services.get(ILanguageService), + this._services.get(ILanguageConfigurationService), + this._services.get(ILanguageFeaturesService) + ); + + // Setup global keydown event listener + this._globalKeydownHandler = this.handleGlobalKeydown.bind(this); + document.addEventListener('keydown', this._globalKeydownHandler); + } catch (error) { + console.warn('Failed to create hidden editor:', error); + } + } + + private disposeHiddenEditor(): void { + if (this._hiddenEditor) { + this._hiddenEditor.dispose(); + this._hiddenEditor = null; + } + + if (this._hiddenEditorContainer && this._hiddenEditorContainer.parentNode) { + this._hiddenEditorContainer.parentNode.removeChild(this._hiddenEditorContainer); + this._hiddenEditorContainer = null; + } + + if (this._hiddenEditorStyle && this._hiddenEditorStyle.parentNode) { + this._hiddenEditorStyle.parentNode.removeChild(this._hiddenEditorStyle); + this._hiddenEditorStyle = null; + } + + if (this._globalKeydownHandler) { + document.removeEventListener('keydown', this._globalKeydownHandler); + this._globalKeydownHandler = null; + } + + this._focusedEditor = null; + } + + private setupEditorFocusTracking(editor: MonacoEditor.IStandaloneCodeEditor): void { + editor.onDidFocusEditorText(() => { + this._focusedEditor = editor; + }); + editor.onDidBlurEditorText(() => { + if (this._focusedEditor === editor) { + this._focusedEditor = null; + } + }); + } + + private handleGlobalKeydown(e: KeyboardEvent): void { + // Only handle events with modifier keys + if (!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) return; + this.ensureQuickInputContext(); + } +} diff --git a/src/services/monaco.ts b/src/services/monaco.ts index 23589dc8e..b64ca5f8b 100644 --- a/src/services/monaco.ts +++ b/src/services/monaco.ts @@ -1,49 +1,57 @@ import { - DynamicStandaloneServices, type editor as MonacoEditor, + EditorScopedLayoutService, IAccessibilityService, + IAccessibilitySignalService, + IClipboardService, ICodeEditorService, ICommandService, IConfigurationService, IContextKeyService, + IContextMenuService, IContextViewService, + IEditorProgressService, + IEditorWorkerService, + IHoverService, IInstantiationService, IKeybindingService, + ILanguageConfigurationService, + ILanguageFeaturesService, + ILanguageService, ILayoutService, IModelService, - IModeService, INotificationService, IOpenerService, IQuickInputService, IStandaloneThemeService, - ITextModelService, - IEditorWorkerService, - IContextMenuService, - IEditorProgressService, - IClipboardService, + ITelemetryService, OpenerService, QuickInputService, ServiceCollection, - SimpleEditorModelResolverService, - SimpleLayoutService, - StandaloneEditor, StandaloneDiffEditor, + StandaloneEditor, + StandaloneServices, StaticServices, } from 'mo/monaco'; import { inject, injectable } from 'tsyringe'; import { ColorThemeService } from './colorTheme'; +import { KeyboardFocusService } from './keyboardFocus'; type IEditorOverrideServices = MonacoEditor.IEditorOverrideServices; @injectable() export class MonacoService { private _services: ServiceCollection; - private simpleEditorModelResolverService: SimpleEditorModelResolverService | null = null; private _container!: HTMLElement | null; + private _keyboardFocusService!: KeyboardFocusService | null; constructor(@inject('colorTheme') private colorTheme: ColorThemeService) {} + public setKeyboardFocusService(keyboardFocusService: KeyboardFocusService): void { + this._keyboardFocusService = keyboardFocusService; + } + public initWorkspace(container: HTMLElement) { this._container = container; this._services = this.createStandaloneServices(); @@ -87,38 +95,34 @@ export class MonacoService { const services = this.services; this.mergeEditorServices(overrides); - if (!services.has(ITextModelService)) { - this.simpleEditorModelResolverService = new SimpleEditorModelResolverService( - StaticServices.modelService.get() - ); - services.set(ITextModelService, this.simpleEditorModelResolverService); - } const standaloneEditor = new StandaloneEditor( domElement, options, - services, services.get(IInstantiationService), services.get(ICodeEditorService), services.get(ICommandService), services.get(IContextKeyService), + services.get(IHoverService), services.get(IKeybindingService), - services.get(IContextViewService), services.get(IStandaloneThemeService), services.get(INotificationService), services.get(IConfigurationService), services.get(IAccessibilityService), services.get(IModelService), - services.get(IModeService) + services.get(ILanguageService), + services.get(ILanguageConfigurationService), + services.get(ILanguageFeaturesService) ); - if (this.simpleEditorModelResolverService) { - this.simpleEditorModelResolverService.setEditor(standaloneEditor); - } - // Should be called after the editor is created this.colorTheme.setCurrent(this.colorTheme.getCurrent()); + // Register editor with keyboard focus service for focus tracking + if (this._keyboardFocusService) { + this._keyboardFocusService.registerEditor(standaloneEditor); + } + return standaloneEditor; } @@ -130,35 +134,22 @@ export class MonacoService { const services = this.services; this.mergeEditorServices(overrides); - if (!services.has(ITextModelService)) { - this.simpleEditorModelResolverService = new SimpleEditorModelResolverService( - StaticServices.modelService.get() - ); - services.set(ITextModelService, this.simpleEditorModelResolverService); - } const standaloneDiffEditor = new StandaloneDiffEditor( domElement, options, - services, services.get(IInstantiationService), services.get(IContextKeyService), - services.get(IKeybindingService), - services.get(IContextViewService), - services.get(IEditorWorkerService), services.get(ICodeEditorService), services.get(IStandaloneThemeService), services.get(INotificationService), services.get(IConfigurationService), services.get(IContextMenuService), services.get(IEditorProgressService), - services.get(IClipboardService) + services.get(IClipboardService), + services.get(IAccessibilitySignalService) ); - if (this.simpleEditorModelResolverService) { - this.simpleEditorModelResolverService.setEditor(standaloneDiffEditor); - } - // Should be called after the editor is created this.colorTheme.setCurrent(this.colorTheme.getCurrent()); @@ -166,14 +157,57 @@ export class MonacoService { } // When Application will unmount, call it - public dispose() {} + public dispose() { + if (this._keyboardFocusService) { + this._keyboardFocusService.dispose(); + } + } - private createStandaloneServices(): ServiceCollection { - const services = new DynamicStandaloneServices(this.container); + /** + * Ensure the hidden editor has focus when QuickInputService operations are needed + * This method should be called before any QuickInputService operations + */ + public ensureQuickInputContext(): void { + if (this._keyboardFocusService) { + this._keyboardFocusService.ensureQuickInputContext(); + } + } - const instantiationService = services.get(IInstantiationService); + private createStandaloneServices(): ServiceCollection { + const instantiationService = StandaloneServices.initialize({}); + const services = new ServiceCollection(); + const serviceIds = [ + IInstantiationService, + ICodeEditorService, + ICommandService, + IConfigurationService, + IContextKeyService, + IKeybindingService, + IContextViewService, + IStandaloneThemeService, + INotificationService, + IAccessibilityService, + IAccessibilitySignalService, + IModelService, + ILanguageService, + ILanguageConfigurationService, + ILanguageFeaturesService, + IHoverService, + IEditorWorkerService, + IContextMenuService, + IEditorProgressService, + IClipboardService, + ITelemetryService, + ]; + + serviceIds.forEach((serviceId) => { + const service = StandaloneServices.get(serviceId); + if (service) { + services.set(serviceId, service); + } + }); - if (!services.has(IOpenerService)) { + if (!services.get(IOpenerService)) { services.set( IOpenerService, new OpenerService(services.get(ICodeEditorService), services.get(ICommandService)) @@ -181,9 +215,9 @@ export class MonacoService { } const quickInputService = instantiationService.createInstance(QuickInputService); - const layoutService = new SimpleLayoutService( - StaticServices.codeEditorService.get(ICodeEditorService), - this.container + const layoutService = new EditorScopedLayoutService( + this.container, + StaticServices.codeEditorService.get(ICodeEditorService) ); // Override layoutService @@ -195,6 +229,12 @@ export class MonacoService { // Override dispose for prevent disposed by instance this.dispose = services.dispose; services.dispose = () => {}; + + // Initialize keyboard focus service after services are set up + if (this._keyboardFocusService) { + this._keyboardFocusService.initialize(services); + } + return services; } } diff --git a/src/types.ts b/src/types.ts index edf3527b3..1286afff2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,7 @@ import type { EditorService } from './services/editor'; import type { EditorTreeService } from './services/editorTree'; import type { ExplorerService } from './services/explorer'; import type { FolderTreeService } from './services/folderTree'; +import type { KeyboardFocusService } from './services/keyboardFocus'; import type { LayoutService } from './services/layout'; import type { LocaleService } from './services/locale'; import type { MenuBarService } from './services/menuBar'; @@ -224,6 +225,7 @@ export interface IContext { sidebar: SidebarService; explorer: ExplorerService; folderTree: FolderTreeService; + keyboardFocus: KeyboardFocusService; panel: PanelService; output: OutputService; editor: EditorService;