diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml index ef93a76a..94b15286 100644 --- a/.github/workflows/stale_issues.yml +++ b/.github/workflows/stale_issues.yml @@ -16,6 +16,8 @@ jobs: days-before-issue-close: 14 days-before-pr-stale: 30 days-before-pr-close: 14 + exempt-issue-labels: "waiting-external" + exempt-pr-labels: "waiting-external" stale-issue-label: "stale" stale-pr-label: "stale" stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." diff --git a/README.md b/README.md index edb71096..457aae13 100644 --- a/README.md +++ b/README.md @@ -282,15 +282,27 @@ The api will use the manifest of a default vscode extension, which can be overri You can also register a new extension from its manifest: ```typescript -import { registerExtension, initialize } from 'vscode/extensions' +import { registerExtension, initialize, ExtensionHostKind } from 'vscode/extensions' await initialize() -const { registerFile: registerExtensionFile, getApi } = registerExtension(defaultThemesExtensions) +const extension = { + name: 'my-extension', + publisher: 'someone', + version: '1.0.0', + engines: { + vscode: '*' + }, + contributes: { + } +} +const { registerFileUrl, getApi } = registerExtension(extension, ExtensionHostKind.LocalProcess) -registerExtensionFile('/file.json', async () => fileContent) +registerFileUrl('/file-extension-path.json', new URL('./file-real-path.json', import.meta.url).toString()) -getApi().then(vscodeApi => vscodeApi.languages.registerCompletionItemProvider(...)) +const vscode = await getApi() + +vscode.languages.registerCompletionItemProvider(...) ``` diff --git a/demo/src/main.ts b/demo/src/main.ts index d331007a..58019148 100644 --- a/demo/src/main.ts +++ b/demo/src/main.ts @@ -5,6 +5,8 @@ import { registerFileSystemOverlay, HTMLFileSystemProvider } from '@codingame/mo import * as vscode from 'vscode' import { ILogService, StandaloneServices, IPreferencesService, IEditorService, IDialogService, getService } from 'vscode/services' import { ConfirmResult, 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 { clearStorage, remoteAuthority } from './setup' import { CustomEditorInput } from './features/customView' import './features/debugger' @@ -94,29 +96,7 @@ diagnostics.set(modelRef.object.textEditorModel!.uri, [{ source: 'Demo', code: 42 }]) - -const settingsModelReference = await createModelReference(monaco.Uri.from({ scheme: 'user-store', path: '/User/settings.json' }), `{ - "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" -}`) +const settingsModelReference = await createModelReference(defaultUserConfigurationFile) const settingEditor = createConfiguredEditor(document.getElementById('settings-editor')!, { model: settingsModelReference.object.textEditorModel, automaticLayout: true @@ -134,13 +114,7 @@ settingEditor.addAction({ contextMenuGroupId: 'custom' }) -const keybindingsModelReference = await createModelReference(monaco.Uri.from({ scheme: 'user-store', path: '/User/keybindings.json' }), `[ - { - "key": "ctrl+d", - "command": "editor.action.deleteLines", - "when": "editorTextFocus" - } -]`) +const keybindingsModelReference = await createModelReference(defaultUserKeybindindsFile) createConfiguredEditor(document.getElementById('keybindings-editor')!, { model: keybindingsModelReference.object.textEditorModel, automaticLayout: true diff --git a/demo/src/setup.ts b/demo/src/setup.ts index ae88362f..6b268ec0 100644 --- a/demo/src/setup.ts +++ b/demo/src/setup.ts @@ -3,8 +3,8 @@ import { initialize as initializeVscodeExtensions } from 'vscode/extensions' import getModelServiceOverride from '@codingame/monaco-vscode-model-service-override' import getNotificationServiceOverride from '@codingame/monaco-vscode-notifications-service-override' import getDialogsServiceOverride from '@codingame/monaco-vscode-dialogs-service-override' -import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override' -import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override' +import getConfigurationServiceOverride, { initUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override' +import getKeybindingsServiceOverride, { initUserKeybindings } from '@codingame/monaco-vscode-keybindings-service-override' import getTextmateServiceOverride from '@codingame/monaco-vscode-textmate-service-override' import getThemeServiceOverride from '@codingame/monaco-vscode-theme-service-override' import getLanguagesServiceOverride from '@codingame/monaco-vscode-languages-service-override' @@ -68,6 +68,39 @@ export const remoteAuthority = params.get('remoteAuthority') ?? undefined const connectionToken = params.get('connectionToken') ?? undefined const remotePath = remoteAuthority != null ? params.get('remotePath') ?? undefined : undefined +// 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" + } + ]`) +]) + // Override services await initializeMonacoService({ ...getExtensionServiceOverride(toWorkerConfig(ExtensionHostWorker)), diff --git a/scripts/vscode.patch b/scripts/vscode.patch index 64aa96f5..5dc19ffb 100644 --- a/scripts/vscode.patch +++ b/scripts/vscode.patch @@ -1150,6 +1150,19 @@ index f23c48b7db1..3a638425a2d 100644 } isVisible(): boolean { +diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts +index 84cee2b04a6..63167893dd7 100644 +--- a/src/vs/workbench/common/views.ts ++++ b/src/vs/workbench/common/views.ts +@@ -217,7 +217,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe + const viewContainers = getOrSet(this.viewContainers, viewContainerLocation, []); + viewContainers.push(viewContainer); + if (options?.isDefault) { +- this.defaultViewContainers.push(viewContainer); ++ this.defaultViewContainers.unshift(viewContainer); + } + this._onDidRegister.fire({ viewContainer, viewContainerLocation }); + return viewContainer; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e6ae569ad9d..3b2ac782391 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts diff --git a/src/service-override/configuration.ts b/src/service-override/configuration.ts index bf07e07d..37378ece 100644 --- a/src/service-override/configuration.ts +++ b/src/service-override/configuration.ts @@ -8,7 +8,7 @@ import { ConfigurationScope } from 'vscode/src/vs/platform/configuration/common/ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurationDefaults } from 'vs/platform/configuration/common/configurationRegistry' import { Registry } from 'vs/platform/registry/common/platform' import { VSBuffer } from 'vs/base/common/buffer' -import { IFileService } from 'vs/platform/files/common/files' +import { IFileService, IFileWriteOptions } from 'vs/platform/files/common/files' import { ILogService } from 'vs/platform/log/common/log' import { IColorCustomizations, IThemeScopedColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService' import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile' @@ -32,12 +32,26 @@ import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspace import { URI } from 'vs/base/common/uri' import 'vs/workbench/api/common/configurationExtensionPoint' import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService' -import getFileServiceOverride from './files' +import getFileServiceOverride, { initFile } from './files' import { memoizedConstructor, unsupported } from '../tools' import { registerServiceInitializePreParticipant } from '../lifecycle' +import { getService } from '../services' +// 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' }) + +/** + * Should be called only BEFORE the service are initialized to initialize the file on the filesystem before the configuration service initializes + */ +async function initUserConfiguration (configurationJson: string, options?: Partial, file: URI = defaultUserConfigurationFile): Promise { + await initFile(defaultUserConfigurationFile.scheme, file, configurationJson, options) +} + +/** + * Can be called at any time after the services are initialized to update the user configuration + */ async function updateUserConfiguration (configurationJson: string): Promise { - const userDataProfilesService = StandaloneServices.get(IUserDataProfilesService) + const userDataProfilesService = await getService(IUserDataProfilesService) await StandaloneServices.get(IFileService).writeFile(userDataProfilesService.defaultProfile.settingsResource, VSBuffer.fromString(configurationJson)) } @@ -125,6 +139,8 @@ export default function getServiceOverride (defaultWorkspace: URI | IAnyWorkspac } export { + defaultUserConfigurationFile, + initUserConfiguration, updateUserConfiguration, getUserConfiguration, onUserConfigurationChange, diff --git a/src/service-override/files.ts b/src/service-override/files.ts index 352a9dda..49ed7df2 100644 --- a/src/service-override/files.ts +++ b/src/service-override/files.ts @@ -460,11 +460,16 @@ const fileSystemProvider = new OverlayFileSystemProvider() fileSystemProvider.register(0, new MkdirpOnWriteInMemoryFileSystemProvider()) const extensionFileSystemProvider = new RegisteredFileSystemProvider(true) +const userDataFileSystemProvider = new InMemoryFileSystemProvider() + +// Initialize /User/ folder to be able to write configuration and keybindings in it before the fileService is initialized +// The `mkdirp` logic is inside the service, and the provider will just fail if asked to write a file in a non-existent directory +void userDataFileSystemProvider.mkdir(URI.from({ scheme: Schemas.vscodeUserData, path: '/User/' })) const providers: Record = { extension: extensionFileSystemProvider, logs: new InMemoryFileSystemProvider(), - [Schemas.vscodeUserData]: new InMemoryFileSystemProvider(), + [Schemas.vscodeUserData]: userDataFileSystemProvider, [Schemas.tmp]: new InMemoryFileSystemProvider(), file: fileSystemProvider } @@ -478,7 +483,7 @@ class MemoryFileService extends FileService { if (provider instanceof OverlayFileSystemProvider) { provider.onDidChangeOverlays(() => { disposable.dispose() - disposable = this.registerProvider('file', fileSystemProvider) + disposable = this.registerProvider(scheme, fileSystemProvider) }) } } @@ -503,6 +508,24 @@ export function registerExtensionFile (extensionLocation: URI, filePath: string, return extensionFileSystemProvider.registerFile(new RegisteredReadOnlyFile(joinPath(extensionLocation, filePath), getContent)) } +/** + * Can be used to create a file before the fileService is initialized + */ +export async function initFile (scheme: string, file: URI, content: Uint8Array | string, options?: Partial): Promise { + const provider = providers[scheme] + if (provider == null || provider.writeFile == null) { + throw new Error(`${scheme} provider doesn't exist or doesn't support writing files`) + } + + await provider.writeFile(file, content instanceof Uint8Array ? content : encoder.encode(content), { + atomic: false, + create: true, + overwrite: false, + unlock: false, + ...options + }) +} + /** * Register a file system overlay * diff --git a/src/service-override/keybindings.ts b/src/service-override/keybindings.ts index 9268d68f..c766e06b 100644 --- a/src/service-override/keybindings.ts +++ b/src/service-override/keybindings.ts @@ -6,7 +6,7 @@ import { VSBuffer } from 'vs/base/common/buffer' import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile' import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout' import { BrowserKeyboardLayoutService } from 'vs/workbench/services/keybinding/browser/keyboardLayoutService' -import { IFileService } from 'vs/platform/files/common/files' +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' @@ -21,12 +21,28 @@ import { IHostService } from 'vs/workbench/services/host/browser/host' import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' import { ILogService } from 'vs/platform/log/common/log' import { WorkbenchContextKeysHandler } from 'vs/workbench/browser/contextkeys' -import getFileServiceOverride from './files' +import { Schemas } from 'vs/base/common/network' +import { URI } from 'vs/base/common/uri' +import getFileServiceOverride, { initFile } from './files' import { DynamicKeybindingService } from '../monaco' -import 'vs/workbench/browser/workbench.contribution' -import 'vs/workbench/contrib/keybindings/browser/keybindings.contribution' import { onRenderWorkbench } from '../lifecycle' import { IInstantiationService } from '../services' +import 'vs/workbench/browser/workbench.contribution' +import 'vs/workbench/contrib/keybindings/browser/keybindings.contribution' + +// This is the default value, but can be overriden by overriding the Environment or UserDataProfileService service +const defaultUserKeybindindsFile = URI.from({ scheme: Schemas.vscodeUserData, path: '/User/keybindings.json' }) + +/** + * Should be called only BEFORE the service are initialized to initialize the file on the filesystem before the keybindings service initializes + */ +async function initUserKeybindings (configurationJson: string, options?: Partial, file: URI = defaultUserKeybindindsFile): Promise { + await initFile(defaultUserKeybindindsFile.scheme, file, configurationJson, options) +} + +/** + * Can be called at any time after the services are initialized to update the user configuration + */ async function updateUserKeybindings (keybindingsJson: string): Promise { const userDataProfilesService: IUserDataProfilesService = StandaloneServices.get(IUserDataProfilesService) @@ -100,6 +116,8 @@ export default function getServiceOverride ({ shouldUseGlobalKeybindings = () => } export { + defaultUserKeybindindsFile, + initUserKeybindings, updateUserKeybindings, IUserFriendlyKeybinding } diff --git a/src/service-override/views.ts b/src/service-override/views.ts index d1dceae2..dadf19b9 100644 --- a/src/service-override/views.ts +++ b/src/service-override/views.ts @@ -77,7 +77,6 @@ import { WebviewService } from 'vs/workbench/contrib/webview/browser/webviewServ import { IWebviewWorkbenchService, WebviewEditorService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService' import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview' import { IWebviewViewService, WebviewViewService } from 'vs/workbench/contrib/webviewView/browser/webviewViewService' -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle' import { IWorkbenchLayoutService, Parts, Position, positionToString } from 'vs/workbench/services/layout/browser/layoutService' import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor' import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane' @@ -349,7 +348,9 @@ function registerCustomView (options: CustomViewOption): IDisposable { ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [options.id, { mergeViewWithContainerWhenSingleView: true }]), hideIfEmpty: true, icon: iconUrl - }, options.location) + }, options.location, { + isDefault: options.default + }) const views: IViewDescriptor[] = [{ id: options.id, @@ -389,12 +390,6 @@ function registerCustomView (options: CustomViewOption): IDisposable { Registry.as(ViewExtensions.ViewsRegistry).registerViews(views, VIEW_CONTAINER) - if (options.default ?? false) { - void StandaloneServices.get(ILifecycleService).when(LifecyclePhase.Eventually).then(() => { - void StandaloneServices.get(IViewsService).openViewContainer(options.id) - }) - } - const disposableCollection = new DisposableStore() disposableCollection.add({ dispose () {