diff --git a/demo/index.html b/demo/index.html index 6badb7b6..7bd97242 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,7 +22,7 @@

Editor

- +
@@ -55,9 +55,11 @@

Editor

Settings

+

Keybindings

+
diff --git a/demo/src/features/customView.ts b/demo/src/features/customView.ts index 8e5a4d49..8c1e8836 100644 --- a/demo/src/features/customView.ts +++ b/demo/src/features/customView.ts @@ -1,5 +1,5 @@ -import { IDialogService, EditorInput, ITelemetryService, IThemeService, IStorageService, createInstance } from 'vscode/services' -import { registerCustomView, registerEditorPane, registerEditor, ViewContainerLocation, SimpleEditorPane, SimpleEditorInput, RegisteredEditorPriority, IEditorCloseHandler, ConfirmResult } from '@codingame/monaco-vscode-views-service-override' +import { IDialogService, EditorInput, ITelemetryService, IThemeService, IStorageService, createInstance, IInstantiationService } from 'vscode/services' +import { registerCustomView, registerEditorPane, registerEditor, registerEditorSerializer, ViewContainerLocation, SimpleEditorPane, SimpleEditorInput, RegisteredEditorPriority, IEditorCloseHandler, ConfirmResult, IEditorSerializer } from '@codingame/monaco-vscode-views-service-override' import * as monaco from 'monaco-editor' registerCustomView({ @@ -114,4 +114,26 @@ registerEditor('*.customeditor', { } }) +interface ISerializedCustomEditorInput { + resourceJSON?: monaco.UriComponents +} +registerEditorSerializer(CustomEditorPane.ID, class implements IEditorSerializer { + canSerialize (): boolean { + return true + } + + serialize (editor: CustomEditorInput): string | undefined { + const serializedFileEditorInput: ISerializedCustomEditorInput = { + resourceJSON: editor.resource?.toJSON() + } + + return JSON.stringify(serializedFileEditorInput) + } + + deserialize (instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { + const serializedFileEditorInput: ISerializedCustomEditorInput = JSON.parse(serializedEditor) + return instantiationService.createInstance(CustomEditorInput, monaco.Uri.revive(serializedFileEditorInput.resourceJSON)) + } +}) + export { CustomEditorInput } diff --git a/demo/src/main.ts b/demo/src/main.ts index 513619e5..9a9ed573 100644 --- a/demo/src/main.ts +++ b/demo/src/main.ts @@ -5,8 +5,8 @@ import { registerFileSystemOverlay, HTMLFileSystemProvider } from '@codingame/mo import * as vscode from 'vscode' import { ILogService, StandaloneServices, IPreferencesService, IEditorService, IDialogService, getService, createInstance } from 'vscode/services' import { Parts, isPartVisibile, setPartVisibility } from '@codingame/monaco-vscode-views-service-override' -import { defaultUserConfigurationFile } from '@codingame/monaco-vscode-configuration-service-override' -import { defaultUserKeybindindsFile } from '@codingame/monaco-vscode-keybindings-service-override' +import { defaultUserConfigurationFile, updateUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override' +import { defaultUserKeybindindsFile, updateUserKeybindings } from '@codingame/monaco-vscode-keybindings-service-override' import { clearStorage, remoteAuthority } from './setup' import { CustomEditorInput } from './features/customView' import './features/debugger' @@ -57,6 +57,8 @@ import '@codingame/monaco-vscode-configuration-editing-default-extension' import '@codingame/monaco-vscode-markdown-math-default-extension' import '@codingame/monaco-vscode-npm-default-extension' import '@codingame/monaco-vscode-media-preview-default-extension' +import defaultConfiguration from './user/configuration.json?raw' +import defaultKeybindings from './user/keybindings.json?raw' if (remoteAuthority != null) { import('./features/remoteExtension') @@ -145,6 +147,14 @@ document.querySelector('#settingsui')!.addEventListener('click', async () => { window.scrollTo({ top: 0, behavior: 'smooth' }) }) +document.querySelector('#resetsettings')!.addEventListener('click', async () => { + await updateUserConfiguration(defaultConfiguration) +}) + +document.querySelector('#resetkeybindings')!.addEventListener('click', async () => { + await updateUserKeybindings(defaultKeybindings) +}) + document.querySelector('#keybindingsui')!.addEventListener('click', async () => { await StandaloneServices.get(IPreferencesService).openGlobalKeybindingSettings(false) window.scrollTo({ top: 0, behavior: 'smooth' }) diff --git a/demo/src/setup.ts b/demo/src/setup.ts index 6b268ec0..80ba55dd 100644 --- a/demo/src/setup.ts +++ b/demo/src/setup.ts @@ -38,12 +38,17 @@ import getWorkspaceTrustOverride from '@codingame/monaco-vscode-workspace-trust- import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker' import TextMateWorker from '@codingame/monaco-vscode-textmate-service-override/worker?worker' import OutputLinkComputerWorker from '@codingame/monaco-vscode-output-service-override/worker?worker' +import { createIndexedDBProviders } from '@codingame/monaco-vscode-files-service-override' import ExtensionHostWorker from 'vscode/workers/extensionHost.worker?worker' import LanguageDetectionWorker from '@codingame/monaco-vscode-language-detection-worker-service-override/worker?worker' import * as monaco from 'monaco-editor' import { TerminalBackend } from './features/terminal' import { openNewCodeEditor } from './features/editor' import { toCrossOriginWorker, toWorkerConfig } from './tools/workers' +import defaultConfiguration from './user/configuration.json?raw' +import defaultKeybindings from './user/keybindings.json?raw' + +const userDataProvider = await createIndexedDBProviders() // Workers export type WorkerLoader = () => Worker @@ -70,35 +75,8 @@ const remotePath = remoteAuthority != null ? params.get('remotePath') ?? undefin // Set configuration before initializing service so it's directly available (especially for the theme, to prevent a flicker) await Promise.all([ - initUserConfiguration(`{ - "workbench.colorTheme": "Default Dark+", - "workbench.iconTheme": "vs-seti", - "editor.autoClosingBrackets": "languageDefined", - "editor.autoClosingQuotes": "languageDefined", - "editor.scrollBeyondLastLine": true, - "editor.mouseWheelZoom": true, - "editor.wordBasedSuggestions": false, - "editor.acceptSuggestionOnEnter": "on", - "editor.foldingHighlight": false, - "editor.semanticHighlighting.enabled": true, - "editor.bracketPairColorization.enabled": false, - "editor.fontSize": 12, - "audioCues.lineHasError": "on", - "audioCues.onDebugBreak": "on", - "files.autoSave": "afterDelay", - "files.autoSaveDelay": 1000, - "debug.toolBarLocation": "docked", - "editor.experimental.asyncTokenization": true, - "terminal.integrated.tabs.title": "\${sequence}", - "typescript.tsserver.log": "normal" - }`), - initUserKeybindings(`[ - { - "key": "ctrl+d", - "command": "editor.action.deleteLines", - "when": "editorTextFocus" - } - ]`) + initUserConfiguration(defaultConfiguration), + initUserKeybindings(defaultKeybindings) ]) // Override services @@ -107,9 +85,7 @@ await initializeMonacoService({ ...getModelServiceOverride(), ...getNotificationServiceOverride(), ...getDialogsServiceOverride(), - ...getConfigurationServiceOverride(remotePath == null - ? monaco.Uri.file('/tmp') - : { id: 'remote-workspace', uri: monaco.Uri.from({ scheme: 'vscode-remote', path: remotePath, authority: remoteAuthority }) }), + ...getConfigurationServiceOverride(), ...getKeybindingsServiceOverride(), ...getTextmateServiceOverride(), ...getThemeServiceOverride(), @@ -117,7 +93,7 @@ await initializeMonacoService({ ...getAudioCueServiceOverride(), ...getDebugServiceOverride(), ...getPreferencesServiceOverride(), - ...getViewsServiceOverride(openNewCodeEditor), + ...getViewsServiceOverride(openNewCodeEditor, undefined, true), ...getBannerServiceOverride(), ...getStatusBarServiceOverride(), ...getTitleBarServiceOverride(), @@ -135,15 +111,25 @@ await initializeMonacoService({ ...getStorageServiceOverride(), ...getRemoteAgentServiceOverride(connectionToken), ...getLifecycleServiceOverride(), - ...getEnvironmentServiceOverride({ - remoteAuthority, - enableWorkspaceTrust: true - }), + ...getEnvironmentServiceOverride(), ...getWorkspaceTrustOverride() +}, document.body, { + remoteAuthority, + enableWorkspaceTrust: true, + workspaceProvider: { + trusted: true, + async open () { + return false + }, + workspace: { + folderUri: remotePath == null ? monaco.Uri.file('/tmp') : monaco.Uri.from({ scheme: 'vscode-remote', path: remotePath, authority: remoteAuthority }) + } + } }) StandaloneServices.get(ILogService).setLevel(LogLevel.Off) export async function clearStorage (): Promise { + await userDataProvider.reset() await (await getService(IStorageService) as BrowserStorageService).clear() } diff --git a/demo/src/user/configuration.json b/demo/src/user/configuration.json new file mode 100644 index 00000000..aabf8638 --- /dev/null +++ b/demo/src/user/configuration.json @@ -0,0 +1,22 @@ +{ + "workbench.colorTheme": "Default Dark+", + "workbench.iconTheme": "vs-seti", + "editor.autoClosingBrackets": "languageDefined", + "editor.autoClosingQuotes": "languageDefined", + "editor.scrollBeyondLastLine": true, + "editor.mouseWheelZoom": true, + "editor.wordBasedSuggestions": false, + "editor.acceptSuggestionOnEnter": "on", + "editor.foldingHighlight": false, + "editor.semanticHighlighting.enabled": true, + "editor.bracketPairColorization.enabled": false, + "editor.fontSize": 12, + "audioCues.lineHasError": "on", + "audioCues.onDebugBreak": "on", + "files.autoSave": "afterDelay", + "files.autoSaveDelay": 1000, + "debug.toolBarLocation": "docked", + "editor.experimental.asyncTokenization": true, + "terminal.integrated.tabs.title": "${sequence}", + "typescript.tsserver.log": "normal" +} \ No newline at end of file diff --git a/demo/src/user/keybindings.json b/demo/src/user/keybindings.json new file mode 100644 index 00000000..0549e1ea --- /dev/null +++ b/demo/src/user/keybindings.json @@ -0,0 +1,7 @@ +[ + { + "key": "ctrl+d", + "command": "editor.action.deleteLines", + "when": "editorTextFocus" + } +] \ No newline at end of file diff --git a/demo/vite.config.ts b/demo/vite.config.ts index 0fafef83..1e8f6427 100644 --- a/demo/vite.config.ts +++ b/demo/vite.config.ts @@ -59,7 +59,7 @@ export default defineConfig({ // These 2 lines prevent vite from reloading the whole page when starting a worker (so 2 times in a row after cleaning the vite cache - for the editor then the textmate workers) // it's mainly empirical and probably not the best way, fix me if you find a better way 'monaco-editor/esm/vs/nls.js', 'monaco-editor/esm/vs/editor/editor.worker.js', 'vscode-textmate', 'vscode-oniguruma', '@vscode/vscode-languagedetection', - ...(await glob('monaco-editor/esm/vs/**/common/**/*.js', { cwd: path.resolve(__dirname, '../node_modules') })), + ...(await glob('monaco-editor/esm/vs/**/common/**/*.js', { cwd: path.resolve(__dirname, '../node_modules') })) ], exclude: [], esbuildOptions: { diff --git a/rollup/rollup.config.ts b/rollup/rollup.config.ts index 8d979484..6213a161 100644 --- a/rollup/rollup.config.ts +++ b/rollup/rollup.config.ts @@ -859,6 +859,10 @@ export default (args: Record): rollup.RollupOptions[] => { types: './lifecycle.d.ts', default: './lifecycle.js' }, + './workbench': { + types: './workbench.d.ts', + default: './workbench.js' + }, './service-override/*': { types: './service-override/*.d.ts', default: './service-override/*.js' @@ -898,6 +902,9 @@ export default (args: Record): rollup.RollupOptions[] => { lifecycle: [ './lifecycle.d.ts' ], + workbench: [ + './workbench.d.ts' + ], l10n: [ './l10n.d.ts' ], @@ -988,7 +995,7 @@ export default (args: Record): rollup.RollupOptions[] => { const resolvedWithExtension = resolved.endsWith('.js') ? resolved : `${resolved}.js` const isNotExclusive = (resolved.startsWith(VSCODE_SRC_DIST_DIR) || path.dirname(resolved) === path.resolve(DIST_DIR_MAIN, 'service-override')) && !exclusiveModules.has(resolvedWithExtension) - const shouldBeShared = resolvedWithExtension === path.resolve(DIST_DIR_MAIN, 'assets.js') || resolvedWithExtension === path.resolve(DIST_DIR_MAIN, 'lifecycle.js') + const shouldBeShared = resolvedWithExtension === path.resolve(DIST_DIR_MAIN, 'assets.js') || resolvedWithExtension === path.resolve(DIST_DIR_MAIN, 'lifecycle.js') || resolvedWithExtension === path.resolve(DIST_DIR_MAIN, 'workbench.js') if (isNotExclusive || shouldBeShared) { // Those modules will be imported from external monaco-vscode-api diff --git a/rollup/rollup.default-extensions.ts b/rollup/rollup.default-extensions.ts index eabe7a5f..885604f0 100644 --- a/rollup/rollup.default-extensions.ts +++ b/rollup/rollup.default-extensions.ts @@ -83,20 +83,6 @@ export default rollup.defineConfig([ extensionDirectoryPlugin({ include: `${DEFAULT_EXTENSIONS_PATH}/**/*`, transformManifest (manifest) { - if (manifest.name === 'configuration-editing') { - manifest = { - ...manifest, - contributes: { - ...manifest.contributes, - jsonValidation: manifest.contributes!.jsonValidation!.map(validation => { - return { - fileMatch: (validation.fileMatch as string).replaceAll('%APP_SETTINGS_HOME%', 'user:'), - url: validation.url - } - }) - } - } - } return { ...manifest, main: undefined diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 529b78e3..e93fba06 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -397,7 +397,7 @@ index 23d547570e9..31dfb4fd8d4 100644 export * from 'vs/editor/editor.api'; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts -index 01f2c6987ac..a4853357178 100644 +index 01f2c6987ac..e3bf9b1e9e4 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -89,8 +89,6 @@ import { DefaultConfiguration } from 'vs/platform/configuration/common/configura @@ -409,7 +409,13 @@ index 01f2c6987ac..a4853357178 100644 import { ExtensionKind, IEnvironmentService, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; class SimpleModel implements IResolvedTextEditorModel { -@@ -517,10 +515,14 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { +@@ -512,15 +510,19 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { + }); + } + +- private updateResolver(): void { ++ protected updateResolver(): void { + this._cachedResolver = null; this._onDidUpdateKeybindings.fire(); } diff --git a/src/lifecycle.ts b/src/lifecycle.ts index bf3ad2d2..313de522 100644 --- a/src/lifecycle.ts +++ b/src/lifecycle.ts @@ -4,6 +4,7 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation' import { Barrier, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async' import { Emitter } from 'vs/base/common/event' +import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor' const renderWorkbenchEmitter = new Emitter() export const onRenderWorkbench = renderWorkbenchEmitter.event @@ -52,6 +53,7 @@ export async function startup (instantiationService: IInstantiationService): Pro const lifecycleService = accessor.get(ILifecycleService) Registry.as(WorkbenchExtensions.Workbench).start(accessor) + Registry.as(EditorExtensions.EditorFactory).start(accessor) renderWorkbenchEmitter.fire(accessor) diff --git a/src/monaco.ts b/src/monaco.ts index 78e2ea81..249a32b2 100644 --- a/src/monaco.ts +++ b/src/monaco.ts @@ -43,6 +43,8 @@ import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/commo import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver' import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem' import { Keybinding, ResolvedKeybinding } from 'vs/base/common/keybindings' +import { Event } from 'vs/base/common/event' +import { Emitter } from 'monaco-editor' import { createInjectedClass } from './tools/injection' // Selectively comes from vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts import 'vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch' @@ -182,8 +184,13 @@ export async function createModelReference (resource: URI, content?: string): Pr return (await StandaloneServices.get(ITextModelService).createModelReference(resource)) as IReference } +export interface KeybindingProvider { + provideKeybindings (): ResolvedKeybindingItem[] + onDidChangeKeybindings: Event +} + export interface DynamicKeybindingService extends IKeybindingService { - registerKeybindingProvider (provider: () => ResolvedKeybindingItem[]): IDisposable + registerKeybindingProvider (provider: KeybindingProvider): IDisposable _getResolver(): KeybindingResolver } @@ -195,6 +202,7 @@ function isDynamicKeybindingService (keybindingService: IKeybindingService) { // This class use useful so editor.addAction and editor.addCommand still work // Monaco do an `instanceof` on the KeybindingService so we need it to extends `StandaloneKeybindingService` class DelegateStandaloneKeybindingService extends StandaloneKeybindingService { + private _onDidChangeKeybindings = new Emitter() constructor ( private delegate: DynamicKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @@ -206,13 +214,23 @@ class DelegateStandaloneKeybindingService extends StandaloneKeybindingService { ) { super(contextKeyService, commandService, telemetryService, notificationService, logService, codeEditorService) - this._register(delegate.registerKeybindingProvider(() => this.getUserKeybindingItems())) + this._register(delegate.registerKeybindingProvider({ + provideKeybindings: () => { + return this.getUserKeybindingItems() + }, + onDidChangeKeybindings: this._onDidChangeKeybindings.event + })) } protected override _getResolver (): KeybindingResolver { return this.delegate._getResolver() } + protected override updateResolver (): void { + super.updateResolver() + this._onDidChangeKeybindings.fire() + } + override resolveKeyboardEvent (keyboardEvent: IKeyboardEvent): ResolvedKeybinding { return this.delegate.resolveKeyboardEvent(keyboardEvent) } diff --git a/src/service-override/configuration.ts b/src/service-override/configuration.ts index 1431739f..cd3bd596 100644 --- a/src/service-override/configuration.ts +++ b/src/service-override/configuration.ts @@ -36,6 +36,7 @@ import getFileServiceOverride, { initFile } from './files' import { memoizedConstructor, unsupported } from '../tools' import { registerServiceInitializePreParticipant } from '../lifecycle' import { getService } from '../services' +import { getWorkspaceIdentifier } from '../workbench' // This is the default value, but can be overriden by overriding the Environment or UserDataProfileService service const defaultUserConfigurationFile = URI.from({ scheme: Schemas.vscodeUserData, path: '/User/settings.json' }) @@ -91,21 +92,25 @@ class MonacoWorkspaceEditingService extends AbstractWorkspaceEditingService { enterWorkspace = unsupported } -let _defaultWorkspace: URI | IAnyWorkspaceIdentifier = URI.file('/workspace') +/** + * @deprecated + */ +let _defaultWorkspace: URI | IAnyWorkspaceIdentifier | undefined registerServiceInitializePreParticipant(async (accessor) => { const workspaceService = accessor.get(IWorkspaceContextService) as WorkspaceService workspaceService.acquireInstantiationService(accessor.get(IInstantiationService)) - if (URI.isUri(_defaultWorkspace)) { - const configPath = _defaultWorkspace.with({ path: '/workspace.code-workspace' }) + const workspace = _defaultWorkspace ?? getWorkspaceIdentifier() + if (URI.isUri(workspace)) { + const configPath = workspace.with({ path: '/workspace.code-workspace' }) try { const fileService = accessor.get(IFileService) // Create the directory in the memory filesystem to prevent a warn log - await fileService.createFolder(_defaultWorkspace) + await fileService.createFolder(workspace) await fileService.writeFile(configPath, VSBuffer.fromString(JSON.stringify({ folders: [ { - path: _defaultWorkspace.path + path: workspace.path } ] }))) @@ -118,7 +123,7 @@ registerServiceInitializePreParticipant(async (accessor) => { configPath }) } else { - await workspaceService.initialize(_defaultWorkspace) + await workspaceService.initialize(workspace) } }) @@ -129,7 +134,13 @@ export async function reinitializeWorkspace (workspace: IAnyWorkspaceIdentifier) await workspaceService.initialize(workspace) } -export default function getServiceOverride (defaultWorkspace: URI | IAnyWorkspaceIdentifier): IEditorOverrideServices { +function getServiceOverride (): IEditorOverrideServices +/** + * @deprecated Provide workspace via the services `initialize` function `configuration.workspaceProvider` parameter + */ +function getServiceOverride (defaultWorkspace?: URI | IAnyWorkspaceIdentifier): IEditorOverrideServices + +function getServiceOverride (defaultWorkspace?: URI | IAnyWorkspaceIdentifier): IEditorOverrideServices { _defaultWorkspace = defaultWorkspace return { @@ -143,6 +154,8 @@ export default function getServiceOverride (defaultWorkspace: URI | IAnyWorkspac } } +export default getServiceOverride + export { defaultUserConfigurationFile, initUserConfiguration, diff --git a/src/service-override/dialogs.ts b/src/service-override/dialogs.ts index 99c99709..7cdb0c2a 100644 --- a/src/service-override/dialogs.ts +++ b/src/service-override/dialogs.ts @@ -60,10 +60,10 @@ class FileDialogService extends AbstractFileDialogService { } } -export default function getServiceOverride (container?: HTMLElement): IEditorOverrideServices { +export default function getServiceOverride (): IEditorOverrideServices { return { [IDialogService.toString()]: new SyncDescriptor(DialogService, undefined, true), [IFileDialogService.toString()]: new SyncDescriptor(FileDialogService, undefined, true), - ...getLayoutServiceOverride(container) + ...getLayoutServiceOverride() } } diff --git a/src/service-override/environment.ts b/src/service-override/environment.ts index e367a854..5237bbe7 100644 --- a/src/service-override/environment.ts +++ b/src/service-override/environment.ts @@ -1,24 +1,32 @@ import { IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices' import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' import { BrowserWorkbenchEnvironmentService, IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService' -import { URI } from 'vs/base/common/uri' import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IProductService } from 'vs/platform/product/common/productService' import { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api' +import { getWorkbenchConstructionOptions, getWorkspaceIdentifier, logsPath } from '../workbench' class InjectedBrowserWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvironmentService { - constructor (options: IWorkbenchConstructionOptions, @IProductService productService: IProductService) { - super('default', URI.from({ scheme: 'logs', path: '/' }), options, productService) + constructor (workspaceId: string = getWorkspaceIdentifier().id, options: IWorkbenchConstructionOptions = getWorkbenchConstructionOptions(), @IProductService productService: IProductService) { + super(workspaceId, logsPath, options, productService) } } -export default function getServiceOverride (options: IWorkbenchConstructionOptions = {}): IEditorOverrideServices { +/** + * @deprecated Provide construction option via the services `initialize` function `configuration` parameter + */ +function getServiceOverride (options: IWorkbenchConstructionOptions): IEditorOverrideServices +function getServiceOverride (): IEditorOverrideServices + +function getServiceOverride (options?: IWorkbenchConstructionOptions): IEditorOverrideServices { return { [IEnvironmentService.toString()]: new SyncDescriptor(InjectedBrowserWorkbenchEnvironmentService, [], true), - [IBrowserWorkbenchEnvironmentService.toString()]: new SyncDescriptor(InjectedBrowserWorkbenchEnvironmentService, [options], true) + [IBrowserWorkbenchEnvironmentService.toString()]: new SyncDescriptor(InjectedBrowserWorkbenchEnvironmentService, [undefined, options], true) } } +export default getServiceOverride + export { IWorkbenchConstructionOptions } diff --git a/src/service-override/files.ts b/src/service-override/files.ts index 49ed7df2..c4629f27 100644 --- a/src/service-override/files.ts +++ b/src/service-override/files.ts @@ -13,8 +13,10 @@ import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystem import * as path from 'vs/base/common/path' import 'vs/workbench/contrib/files/browser/files.contribution.js?include=registerConfiguration' import { Schemas } from 'vs/base/common/network' -import { IndexedDBFileSystemProvider } from 'vs/platform/files/browser/indexedDBFileSystemProvider' +import { IndexedDBFileSystemProvider, IndexedDBFileSystemProviderErrorData, IndexedDBFileSystemProviderErrorDataClassification } from 'vs/platform/files/browser/indexedDBFileSystemProvider' import { IndexedDB } from 'vs/base/browser/indexedDB' +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry' +import { logsPath } from '../workbench' abstract class RegisteredFile { private ctime: number @@ -468,14 +470,14 @@ void userDataFileSystemProvider.mkdir(URI.from({ scheme: Schemas.vscodeUserData, const providers: Record = { extension: extensionFileSystemProvider, - logs: new InMemoryFileSystemProvider(), + [logsPath.scheme]: new InMemoryFileSystemProvider(), [Schemas.vscodeUserData]: userDataFileSystemProvider, [Schemas.tmp]: new InMemoryFileSystemProvider(), file: fileSystemProvider } class MemoryFileService extends FileService { - constructor (@ILogService logService: ILogService) { + constructor (@ILogService logService: ILogService, @ITelemetryService telemetryService: ITelemetryService) { super(logService) for (const [scheme, provider] of Object.entries(providers)) { @@ -486,6 +488,10 @@ class MemoryFileService extends FileService { disposable = this.registerProvider(scheme, fileSystemProvider) }) } + + if (provider instanceof IndexedDBFileSystemProvider) { + this._register(provider.onReportError(e => telemetryService.publicLog2('indexedDBFileSystemProviderError', e))) + } } } } @@ -516,6 +522,14 @@ export async function initFile (scheme: string, file: URI, content: Uint8Array | if (provider == null || provider.writeFile == null) { throw new Error(`${scheme} provider doesn't exist or doesn't support writing files`) } + if (!(options?.overwrite ?? false)) { + try { + await provider.stat(file) + // The file already exists, do nothing + return + } catch (error) { + } + } await provider.writeFile(file, content instanceof Uint8Array ? content : encoder.encode(content), { atomic: false, @@ -526,6 +540,23 @@ export async function initFile (scheme: string, file: URI, content: Uint8Array | }) } +/** + * Can be used to replace memory providers by indexeddb providers before the fileService is initialized + */ +export async function createIndexedDBProviders (): Promise { + const userDataStore = 'vscode-userdata-store' + const logsStore = 'vscode-logs-store' + const indexedDB = await IndexedDB.create('vscode-web-db', 3, [userDataStore, logsStore]) + + // Logger + providers[logsPath.scheme] = new IndexedDBFileSystemProvider(logsPath.scheme, indexedDB, logsStore, false) + + const userDataProvider = new IndexedDBFileSystemProvider(Schemas.vscodeUserData, indexedDB, userDataStore, true) + providers[Schemas.vscodeUserData] = userDataProvider + + return userDataProvider +} + /** * Register a file system overlay * diff --git a/src/service-override/keybindings.ts b/src/service-override/keybindings.ts index c766e06b..a900aa2a 100644 --- a/src/service-override/keybindings.ts +++ b/src/service-override/keybindings.ts @@ -9,7 +9,6 @@ import { BrowserKeyboardLayoutService } from 'vs/workbench/services/keybinding/b import { IFileService, IFileWriteOptions } from 'vs/platform/files/common/files' import { ICommandService } from 'vs/platform/commands/common/commands' import { CommandService } from 'vs/workbench/services/commands/common/commandService' -import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem' import { toDisposable } from 'vs/base/common/lifecycle' import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver' import { IContextKeyService, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey' @@ -24,7 +23,7 @@ import { WorkbenchContextKeysHandler } from 'vs/workbench/browser/contextkeys' import { Schemas } from 'vs/base/common/network' import { URI } from 'vs/base/common/uri' import getFileServiceOverride, { initFile } from './files' -import { DynamicKeybindingService } from '../monaco' +import { DisposableStore, DynamicKeybindingService, KeybindingProvider } from '../monaco' import { onRenderWorkbench } from '../lifecycle' import { IInstantiationService } from '../services' import 'vs/workbench/browser/workbench.contribution' @@ -50,7 +49,7 @@ async function updateUserKeybindings (keybindingsJson: string): Promise { } class DynamicWorkbenchKeybindingService extends WorkbenchKeybindingService implements DynamicKeybindingService { - private keybindingProviders: (() => ResolvedKeybindingItem[])[] = [] + private keybindingProviders: KeybindingProvider[] = [] constructor ( private shouldUseGlobalKeybindings: () => boolean, @@ -69,17 +68,24 @@ class DynamicWorkbenchKeybindingService extends WorkbenchKeybindingService imple super(contextKeyService, commandService, telemetryService, notificationService, userDataProfileService, hostService, extensionService, fileService, uriIdentityService, logService, keyboardLayoutService) } - public registerKeybindingProvider (provider: () => ResolvedKeybindingItem[]) { + public registerKeybindingProvider (provider: KeybindingProvider) { this.keybindingProviders.push(provider) this.updateResolver() - return toDisposable(() => { + const store = new DisposableStore() + store.add(provider.onDidChangeKeybindings(() => { + this.updateResolver() + })) + + store.add(toDisposable(() => { const idx = this.keybindingProviders.indexOf(provider) if (idx >= 0) { this.keybindingProviders.splice(idx, 1) this.updateResolver() } - }) + })) + + return store } public override _getResolver (): KeybindingResolver { @@ -94,7 +100,7 @@ class DynamicWorkbenchKeybindingService extends WorkbenchKeybindingService imple } protected override getUserKeybindingItems () { - return [...super.getUserKeybindingItems(), ...this.keybindingProviders.flatMap(provider => provider())] + return [...super.getUserKeybindingItems(), ...this.keybindingProviders.flatMap(provider => provider.provideKeybindings())] } } diff --git a/src/service-override/layout.ts b/src/service-override/layout.ts index d9dbd6b0..a90031b9 100644 --- a/src/service-override/layout.ts +++ b/src/service-override/layout.ts @@ -16,6 +16,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IStatusbarService } from 'vs/workbench/services/statusbar/browser/statusbar' import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation' import { onRenderWorkbench } from '../lifecycle' +import { getWorkbenchContainer } from '../workbench' export class LayoutService implements ILayoutService, IWorkbenchLayoutService { declare readonly _serviceBrand: undefined @@ -26,7 +27,7 @@ export class LayoutService implements ILayoutService, IWorkbenchLayoutService { private viewDescriptorService!: IViewDescriptorService constructor ( - public container: HTMLElement + public container: HTMLElement = getWorkbenchContainer() ) { window.addEventListener('resize', () => this.layout()) this.layout() @@ -280,9 +281,7 @@ onRenderWorkbench((accessor) => { if (layoutService instanceof LayoutService) { layoutService.init(accessor) } -}) -export default function getServiceOverride (container: HTMLElement = document.body): IEditorOverrideServices { const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac' const workbenchClasses = coalesce([ 'monaco-workbench', @@ -291,12 +290,21 @@ export default function getServiceOverride (container: HTMLElement = document.bo isChrome ? 'chromium' : isFirefox ? 'firefox' : isSafari ? 'safari' : undefined ]) - container.classList.add(...workbenchClasses) + layoutService.container.classList.add(...workbenchClasses) document.body.classList.add(platformClass) document.body.classList.add('web') +}) +function getServiceOverride (): IEditorOverrideServices +/** + * @deprecated Provide container via the services `initialize` function + */ +function getServiceOverride (container?: HTMLElement): IEditorOverrideServices + +function getServiceOverride (container?: HTMLElement): IEditorOverrideServices { return { - [ILayoutService.toString()]: new SyncDescriptor(LayoutService, [container], true), - [IWorkbenchLayoutService.toString()]: new SyncDescriptor(LayoutService, [container], true) + [ILayoutService.toString()]: new SyncDescriptor(LayoutService, [container], true) } } + +export default getServiceOverride diff --git a/src/service-override/notifications.ts b/src/service-override/notifications.ts index b0a38fad..ea7a96b4 100644 --- a/src/service-override/notifications.ts +++ b/src/service-override/notifications.ts @@ -31,9 +31,9 @@ onRenderWorkbench(async (accessor) => { }) }) -export default function getServiceOverride (container?: HTMLElement): IEditorOverrideServices { +export default function getServiceOverride (): IEditorOverrideServices { return { [INotificationService.toString()]: new SyncDescriptor(NotificationService, undefined, true), - ...getLayoutServiceOverride(container) + ...getLayoutServiceOverride() } } diff --git a/src/service-override/output.ts b/src/service-override/output.ts index 188b6292..f2a9a621 100644 --- a/src/service-override/output.ts +++ b/src/service-override/output.ts @@ -1,19 +1,28 @@ import { IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices' import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' -import { URI } from 'vs/base/common/uri' import { IFileService } from 'vs/platform/files/common/files' -import { ILoggerService, LogLevel } from 'vs/platform/log/common/log' +import { ILoggerService, LogLevel, getLogLevel } from 'vs/platform/log/common/log' import { FileLoggerService } from 'vs/platform/log/common/fileLog' import 'vs/workbench/contrib/output/browser/output.contribution' +import { IEnvironmentService } from 'vs/platform/environment/common/environment' +import { logsPath } from '../workbench' class _FileLoggerService extends FileLoggerService { - constructor (logLevel: LogLevel, @IFileService fileService: IFileService) { - super(logLevel, URI.from({ scheme: 'logs', path: '/' }), fileService) + constructor (logLevel: LogLevel | undefined, @IFileService fileService: IFileService, @IEnvironmentService environmentService: IEnvironmentService) { + super(logLevel ?? getLogLevel(environmentService), logsPath, fileService) } } -export default function getServiceOverride (logLevel: LogLevel = LogLevel.Info): IEditorOverrideServices { +function getServiceOverride (): IEditorOverrideServices +/** + * @deprecated Provide logLevel via the services `initialize` function `configuration.developmentOptions.logLevel` parameter + */ +function getServiceOverride (logLevel?: LogLevel): IEditorOverrideServices + +function getServiceOverride (logLevel?: LogLevel): IEditorOverrideServices { return { [ILoggerService.toString()]: new SyncDescriptor(_FileLoggerService, [logLevel], true) } } + +export default getServiceOverride diff --git a/src/service-override/storage.ts b/src/service-override/storage.ts index 600a708b..c57bc676 100644 --- a/src/service-override/storage.ts +++ b/src/service-override/storage.ts @@ -9,8 +9,9 @@ import { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace' import { BrowserStorageService } from 'vs/workbench/services/storage/browser/storageService' import { ILogService } from 'vs/platform/log/common/log' import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile' -import { generateUuid } from 'vs/base/common/uuid' import { registerServiceInitializePreParticipant } from '../lifecycle' +import { getWorkspaceIdentifier } from '../workbench' +import { IHostService } from '../services' export enum StorageScope { APPLICATION = VSStorageScope.APPLICATION, @@ -130,7 +131,17 @@ class ExternalStorageService extends AbstractStorageService { } registerServiceInitializePreParticipant(async (accessor) => { - await (accessor.get(IStorageService) as ExternalStorageService).initialize() + const storageService = accessor.get(IStorageService) + const hostService = accessor.get(IHostService) + if (storageService instanceof AbstractStorageService) { + await storageService.initialize() + + hostService.onDidChangeFocus(focus => { + if (!focus) { + void storageService.flush() + } + }) + } }) class InjectedBrowserStorageService extends BrowserStorageService { @@ -138,9 +149,7 @@ class InjectedBrowserStorageService extends BrowserStorageService { @IUserDataProfileService userDataProfileService: IUserDataProfileService, @ILogService logService: ILogService ) { - super({ - id: generateUuid() - }, userDataProfileService, logService) + super(getWorkspaceIdentifier(), userDataProfileService, logService) } } diff --git a/src/service-override/views.ts b/src/service-override/views.ts index 9c4c827b..467c1772 100644 --- a/src/service-override/views.ts +++ b/src/service-override/views.ts @@ -54,7 +54,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService' import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService' import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput' -import { EditorExtensions, EditorInputCapabilities, IEditorOpenContext, Verbosity } from 'vs/workbench/common/editor' +import { EditorExtensions, EditorInputCapabilities, IEditorFactoryRegistry, IEditorOpenContext, IEditorSerializer, Verbosity } from 'vs/workbench/common/editor' import { IEditorOptions } from 'vs/platform/editor/common/editor' import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService' import { ITextEditorService, TextEditorService } from 'vs/workbench/services/textfile/common/textEditorService' @@ -100,7 +100,7 @@ import getQuickAccessOverride from './quickaccess' import getKeybindingsOverride from './keybindings' import { changeUrlDomain } from './tools/url' import { registerAssets } from '../assets' -import { registerServiceInitializePostParticipant } from '../lifecycle' +import { onRenderWorkbench } from '../lifecycle' import { withReadyServices } from '../services' function createPart (id: string, role: string, classes: string[]): HTMLElement { @@ -370,6 +370,12 @@ function registerEditor (globPattern: string, editorInfo: RegisteredEditorInfo, }) } +function registerEditorSerializer (editorTypeId: string, ctor: { + new (...Services: Services): IEditorSerializer +}): IDisposable { + return Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(editorTypeId, ctor) +} + interface CustomViewOption { readonly id: string name: string @@ -621,7 +627,7 @@ registerAssets({ }) let restoreEditorView = false -registerServiceInitializePostParticipant(async (accessor) => { +onRenderWorkbench(async (accessor) => { const paneCompositePartService = accessor.get(IPaneCompositePartService) const viewDescriptorService = accessor.get(IViewDescriptorService) @@ -630,8 +636,6 @@ registerServiceInitializePostParticipant(async (accessor) => { const withBannerPart = accessor.get(IBannerService) instanceof Part const withTitlePart = accessor.get(ITitleService) instanceof Part - paneCompositePartService.getPaneComposites(ViewContainerLocation.Panel) - const layoutService = accessor.get(ILayoutService) as LayoutService const invisibleContainer = document.createElement('div') @@ -726,6 +730,8 @@ export { AbstractTextResourceEditorInput, EditorInput, registerEditor, + IEditorSerializer, + registerEditorSerializer, RegisteredEditorInfo, RegisteredEditorOptions, EditorInputFactoryObject, diff --git a/src/services.ts b/src/services.ts index 2d3acfca..51cd2152 100644 --- a/src/services.ts +++ b/src/services.ts @@ -8,12 +8,14 @@ import { IEditorOverrideServices, StandaloneServices } from 'vs/editor/standalon import { GetLeadingNonServiceArgs, IInstantiationService, ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation' import { IAction } from 'vs/base/common/actions' import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle' +import { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api' import getLayoutServiceOverride from './service-override/layout' import getEnvironmentServiceOverride from './service-override/environment' import getExtensionsServiceOverride from './service-override/extensions' import getFileServiceOverride from './service-override/files' import getQuickAccessOverride from './service-override/quickaccess' import { serviceInitializedBarrier, serviceInitializedEmitter, startup } from './lifecycle' +import { initialize as initializeWorkbench } from './workbench' let servicesInitialized = false StandaloneServices.withServices(() => { @@ -21,12 +23,15 @@ StandaloneServices.withServices(() => { return Disposable.None }) -export async function initialize (overrides: IEditorOverrideServices, container?: HTMLElement): Promise { +export async function initialize (overrides: IEditorOverrideServices, container: HTMLElement = document.body, configuration: IWorkbenchConstructionOptions = {}): Promise { if (servicesInitialized) { throw new Error('The services are already initialized.') } + + initializeWorkbench(container, configuration) + const instantiationService = StandaloneServices.initialize({ - ...getLayoutServiceOverride(container), // Always override layout service to break cyclic dependency with ICodeEditorService + ...getLayoutServiceOverride(), // Always override layout service to break cyclic dependency with ICodeEditorService ...getEnvironmentServiceOverride(), ...getExtensionsServiceOverride(), ...getFileServiceOverride(), @@ -248,5 +253,6 @@ export { IColorTheme, StorageScope, StorageTarget, - Severity + Severity, + IWorkbenchConstructionOptions } diff --git a/src/workbench.ts b/src/workbench.ts new file mode 100644 index 00000000..abf354db --- /dev/null +++ b/src/workbench.ts @@ -0,0 +1,51 @@ +import './missing-services' +import { IAnyWorkspaceIdentifier, UNKNOWN_EMPTY_WINDOW_WORKSPACE } from 'vs/platform/workspace/common/workspace' +import { IWorkspace } from 'vs/workbench/services/host/browser/browserHostService' +import { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api' +import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/window/common/window' +import { getSingleFolderWorkspaceIdentifier, getWorkspaceIdentifier as getWorkspaceIdentifierFromUri } from 'vs/workbench/services/workspaces/browser/workspaces' +import { URI } from 'vs/base/common/uri' +import { toLocalISOString } from 'vs/base/common/date' + +let workbenchContainer: HTMLElement = document.body +let workbenchConstructionOptions: IWorkbenchConstructionOptions = {} +let workspaceIdentifier: IAnyWorkspaceIdentifier = UNKNOWN_EMPTY_WINDOW_WORKSPACE +export const logsPath = URI.file(toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')).with({ scheme: 'vscode-log' }) + +export function getWorkbenchContainer (): HTMLElement { + return workbenchContainer +} + +export function getWorkbenchConstructionOptions (): IWorkbenchConstructionOptions { + return workbenchConstructionOptions +} + +export function getWorkspaceIdentifier (): IAnyWorkspaceIdentifier { + return workspaceIdentifier +} + +function resolveWorkspace (configuration: IWorkbenchConstructionOptions): IAnyWorkspaceIdentifier { + let workspace: IWorkspace | undefined + if (configuration.workspaceProvider != null) { + workspace = configuration.workspaceProvider.workspace + } + + // Multi-root workspace + if (workspace != null && isWorkspaceToOpen(workspace)) { + return getWorkspaceIdentifierFromUri(workspace.workspaceUri) + } + + // Single-folder workspace + if (workspace != null && isFolderToOpen(workspace)) { + return getSingleFolderWorkspaceIdentifier(workspace.folderUri) + } + + // Empty window workspace + return UNKNOWN_EMPTY_WINDOW_WORKSPACE +} + +export function initialize (container: HTMLElement, options: IWorkbenchConstructionOptions): void { + workbenchContainer = container + workbenchConstructionOptions = options + workspaceIdentifier = resolveWorkspace(options) +}