diff --git a/.vscode/launch.json b/.vscode/launch.json index 5d5800f24d8c..6b78adc72f14 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -165,7 +165,10 @@ "cwd": "${workspaceFolder}", "outFiles": [ "${workspaceFolder}/out/**/*.js" - ] + ], + "env": { + "MOCHA_COLORS": "true" + } }, { "type": "chrome", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index cb019d8df7c1..6572a5c7566b 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -5,7 +5,10 @@ "type": "npm", "script": "watch", "label": "Build VS Code", - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "isBackground": true, "presentation": { "reveal": "never" @@ -96,6 +99,5 @@ "task": "hygiene", "problemMatcher": [] }, - ] } diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 1987d5ca9c08..8f781cef8dea 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -242,6 +242,10 @@ "name": "vs/workbench/services/keybinding", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/lifecycle", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/mode", "project": "vscode-workbench" diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index 83fa11915c3f..064c2186429f 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -190,7 +190,7 @@ export class Model { openRepositoriesToDispose.forEach(r => r.dispose()); } - private async onDidChangeVisibleTextEditors(editors: TextEditor[]): Promise { + private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); diff --git a/extensions/image-preview/media/main.css b/extensions/image-preview/media/main.css index 9a4844ecd200..a77d3f60cf4c 100644 --- a/extensions/image-preview/media/main.css +++ b/extensions/image-preview/media/main.css @@ -8,7 +8,6 @@ html, body { max-height: 100%; } - body img { max-width: none; max-height: none; @@ -34,6 +33,7 @@ body img { padding: 0; background-position: 0 0, 8px 8px; background-size: 16px 16px; + border: 1px solid var(--vscode-imagePreview-border); } .container.image img { diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index b7f2aeb6b8e1..7db7b2fb17ed 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -2,6 +2,7 @@ "name": "image-preview", "displayName": "%displayName%", "description": "%description%", + "extensionKind": "ui", "version": "1.0.0", "publisher": "vscode", "icon": "icon.png", diff --git a/package.json b/package.json index 52993028612d..36ee38a9851f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.8", "vscode-textmate": "^4.2.2", - "xterm": "4.0.0", + "xterm": "4.1.0-beta8", "xterm-addon-search": "0.2.0", "xterm-addon-web-links": "0.2.0", "yauzl": "^2.9.2", diff --git a/remote/package.json b/remote/package.json index 4c37100ec67b..10c377499f1a 100644 --- a/remote/package.json +++ b/remote/package.json @@ -21,7 +21,7 @@ "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.7", "vscode-textmate": "^4.2.2", - "xterm": "4.0.0", + "xterm": "4.1.0-beta8", "xterm-addon-search": "0.2.0", "xterm-addon-web-links": "0.2.0", "yauzl": "^2.9.2", diff --git a/remote/web/package.json b/remote/web/package.json index d488944039d0..ab86c010ead1 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -6,7 +6,7 @@ "onigasm-umd": "^2.2.2", "semver-umd": "^5.5.3", "vscode-textmate": "^4.2.2", - "xterm": "4.0.0", + "xterm": "4.1.0-beta8", "xterm-addon-search": "0.2.0", "xterm-addon-web-links": "0.2.0" } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 7b0ddab9d166..ccc09170f6f4 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -109,7 +109,7 @@ xterm-addon-web-links@0.2.0: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.0.tgz#b408a0be46211d8d4a0bb5e701d8f3c2bd07d473" integrity sha512-dq81c4Pzli2PgKVBgY2REte9sCVibR3df8AP3SEvCTM9uYFnUFxtxzMTplPnc7+rXabVhFdbU6x+rstIk8HNQg== -xterm@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.0.0.tgz#eac93e08cbe69cf238cbace9185ed9e38873df1a" - integrity sha512-Xbx3vvf9FnrUcI1qU31Jww7/fc/NqpXGqgByTvjj7+g3/yPvt/RvLkP/LLMcof2kLAC3evzZGMiovs7NkjdWDw== +xterm@4.1.0-beta8: + version "4.1.0-beta8" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.1.0-beta8.tgz#c1ef323ba336d92f5b52302b66f672dfff75b3ef" + integrity sha512-6lf+XVv0qT285w49P92tSYoUB406jdbgdhnPKNzxCIGtGX8kcwK+pHZ8HncDwcEhmTmI4LZ/WXPGtOQJg+onwg== diff --git a/remote/yarn.lock b/remote/yarn.lock index 037aedc8cfd1..052c1fa7847d 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -489,10 +489,10 @@ xterm-addon-web-links@0.2.0: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.0.tgz#b408a0be46211d8d4a0bb5e701d8f3c2bd07d473" integrity sha512-dq81c4Pzli2PgKVBgY2REte9sCVibR3df8AP3SEvCTM9uYFnUFxtxzMTplPnc7+rXabVhFdbU6x+rstIk8HNQg== -xterm@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.0.0.tgz#eac93e08cbe69cf238cbace9185ed9e38873df1a" - integrity sha512-Xbx3vvf9FnrUcI1qU31Jww7/fc/NqpXGqgByTvjj7+g3/yPvt/RvLkP/LLMcof2kLAC3evzZGMiovs7NkjdWDw== +xterm@4.1.0-beta8: + version "4.1.0-beta8" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.1.0-beta8.tgz#c1ef323ba336d92f5b52302b66f672dfff75b3ef" + integrity sha512-6lf+XVv0qT285w49P92tSYoUB406jdbgdhnPKNzxCIGtGX8kcwK+pHZ8HncDwcEhmTmI4LZ/WXPGtOQJg+onwg== yauzl@^2.9.2: version "2.10.0" diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index f6ea07783aea..2d0d83d61098 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -22,10 +22,9 @@ import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCod import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; /** * Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs @@ -51,14 +50,13 @@ export class QueryTextEditor extends BaseTextEditor { @ITextFileService textFileService: ITextFileService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService protected editorService: IEditorService, - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IConfigurationService private workspaceConfigurationService: IConfigurationService, - @IAccessibilityService private accessibilityService: IAccessibilityService ) { super( QueryTextEditor.ID, telemetryService, instantiationService, storageService, - configurationService, themeService, textFileService, editorService, editorGroupService, windowService); + configurationService, themeService, textFileService, editorService, editorGroupService, hostService); } public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor { diff --git a/src/sql/workbench/common/enablePreviewFeatures.ts b/src/sql/workbench/common/enablePreviewFeatures.ts index 036682867815..f384ed638a94 100644 --- a/src/sql/workbench/common/enablePreviewFeatures.ts +++ b/src/sql/workbench/common/enablePreviewFeatures.ts @@ -8,7 +8,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -19,7 +18,6 @@ export class EnablePreviewFeatures implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, - @IWindowService windowService: IWindowService, @IHostService hostService: IHostService, @IConfigurationService configurationService: IConfigurationService ) { @@ -28,7 +26,7 @@ export class EnablePreviewFeatures implements IWorkbenchContribution { return; } Promise.all([ - windowService.isFocused(), + hostService.hasFocus, hostService.windowCount ]).then(([focused, count]) => { if (!focused && count > 1) { diff --git a/src/sql/workbench/parts/accounts/browser/accounts.contribution.ts b/src/sql/workbench/parts/accounts/browser/accounts.contribution.ts index 75a1484358b7..5b4ff5ae44ba 100644 --- a/src/sql/workbench/parts/accounts/browser/accounts.contribution.ts +++ b/src/sql/workbench/parts/accounts/browser/accounts.contribution.ts @@ -6,7 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { localize } from 'vs/nls'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/sql/workbench/parts/charts/browser/actions.ts b/src/sql/workbench/parts/charts/browser/actions.ts index 6b549235d596..a498ad82004c 100644 --- a/src/sql/workbench/parts/charts/browser/actions.ts +++ b/src/sql/workbench/parts/charts/browser/actions.ts @@ -9,7 +9,6 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IWindowsService, FileFilter } from 'vs/platform/windows/common/windows'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { URI } from 'vs/base/common/uri'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; @@ -18,7 +17,7 @@ import { QueryInput } from 'sql/workbench/parts/query/common/queryInput'; import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry'; import { IInsightOptions } from 'sql/workbench/parts/charts/common/interfaces'; import { IFileService } from 'vs/platform/files/common/files'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs'; import { VSBuffer } from 'vs/base/common/buffer'; import { IOpenerService } from 'vs/platform/opener/common/opener'; diff --git a/src/sql/workbench/parts/connection/browser/connectionStatus.ts b/src/sql/workbench/parts/connection/browser/connectionStatus.ts index 4f92a6931dd7..4c4a09d613b4 100644 --- a/src/sql/workbench/parts/connection/browser/connectionStatus.ts +++ b/src/sql/workbench/parts/connection/browser/connectionStatus.ts @@ -9,7 +9,7 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import * as TaskUtilities from 'sql/workbench/browser/taskUtilities'; -import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { localize } from 'vs/nls'; diff --git a/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts b/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts index d81d1c87e894..884c26a8c3de 100644 --- a/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts @@ -22,7 +22,6 @@ import { MimeRendererComponent } from 'sql/workbench/parts/notebook/browser/outp import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/browser/nodeContext'; import { MssqlNodeContext } from 'sql/workbench/parts/dataExplorer/browser/mssqlNodeContext'; @@ -130,7 +129,6 @@ registerAction({ handler: async (accessor, options: { forceNewWindow: boolean, folderPath: URI }) => { const viewletService = accessor.get(IViewletService); const workspaceEditingService = accessor.get(IWorkspaceEditingService); - const windowService = accessor.get(IWindowService); const hostService = accessor.get(IHostService); let folders = []; if (!options.folderPath) { @@ -140,7 +138,7 @@ registerAction({ await workspaceEditingService.addFolders(folders.map(folder => ({ uri: folder }))); await viewletService.openViewlet(viewletService.getDefaultViewletId(), true); if (options.forceNewWindow) { - return windowService.openWindow([{ folderUri: folders[0] }], { forceNewWindow: options.forceNewWindow }); + return hostService.openInWindow([{ folderUri: folders[0] }], { forceNewWindow: options.forceNewWindow }); } else { return hostService.reload(); diff --git a/src/sql/workbench/parts/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/parts/profiler/browser/profilerResourceEditor.ts index 183b2761520e..a5d4ee024d18 100644 --- a/src/sql/workbench/parts/profiler/browser/profilerResourceEditor.ts +++ b/src/sql/workbench/parts/profiler/browser/profilerResourceEditor.ts @@ -21,8 +21,8 @@ import { EditorOptions } from 'vs/workbench/common/editor'; import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; class ProfilerResourceCodeEditor extends StandaloneCodeEditor { @@ -50,10 +50,10 @@ export class ProfilerResourceEditor extends BaseTextEditor { @ITextFileService textFileService: ITextFileService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IWindowService windowService: IWindowService + @IHostService hostService: IHostService ) { - super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService); + super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); } public createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor { diff --git a/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts b/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts index 9c4d2dcbaa6c..570ecd8f60b1 100644 --- a/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts +++ b/src/sql/workbench/parts/profiler/browser/profilerTableEditor.ts @@ -27,7 +27,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Dimension } from 'vs/base/browser/dom'; import { textFormatter, slickGridDataItemColumnValueExtractor } from 'sql/base/browser/ui/table/formatters'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { localize } from 'vs/nls'; import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; diff --git a/src/sql/workbench/parts/query/browser/flavorStatus.ts b/src/sql/workbench/parts/query/browser/flavorStatus.ts index 76cda5773b6c..f2e6978e9465 100644 --- a/src/sql/workbench/parts/query/browser/flavorStatus.ts +++ b/src/sql/workbench/parts/query/browser/flavorStatus.ts @@ -21,7 +21,7 @@ import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; export interface ISqlProviderEntry extends IQuickPickItem { providerId: string; diff --git a/src/sql/workbench/parts/query/browser/statusBarItems.ts b/src/sql/workbench/parts/query/browser/statusBarItems.ts index b41ab6042f9d..b03bbb32a2ff 100644 --- a/src/sql/workbench/parts/query/browser/statusBarItems.ts +++ b/src/sql/workbench/parts/query/browser/statusBarItems.ts @@ -6,7 +6,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQueryModelService } from 'sql/platform/query/common/queryModel'; import { IntervalTimer } from 'vs/base/common/async'; -import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { localize } from 'vs/nls'; @@ -14,6 +13,7 @@ import { QueryInput } from 'sql/workbench/parts/query/common/queryInput'; import QueryRunner from 'sql/platform/query/common/queryRunner'; import { parseNumAsTimeString } from 'sql/platform/connection/common/utils'; import { Event } from 'vs/base/common/event'; +import { IStatusbarService, IStatusbarEntryAccessor, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; export class TimeElapsedStatusBarContributions extends Disposable implements IWorkbenchContribution { diff --git a/src/sql/workbench/parts/query/common/resultSerializer.ts b/src/sql/workbench/parts/query/common/resultSerializer.ts index 017e5e2dcd3a..bf22d5bcd0f2 100644 --- a/src/sql/workbench/parts/query/common/resultSerializer.ts +++ b/src/sql/workbench/parts/query/common/resultSerializer.ts @@ -10,7 +10,6 @@ import { IQueryManagementService } from 'sql/platform/query/common/queryManageme import { ISaveRequest, SaveFormat } from 'sql/workbench/parts/grid/common/interfaces'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { FileFilter } from 'vs/platform/windows/common/windows'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; import * as path from 'vs/base/common/path'; @@ -24,7 +23,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/common/pathUtilities'; import { IOutputService, IOutputChannelRegistry, IOutputChannel, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; let prevSavePath: string; diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts index 36e12e578577..2cb4cb3a9c16 100644 --- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts @@ -25,6 +25,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; class TestEnvironmentService implements IWorkbenchEnvironmentService { + userDataSyncLogResource: URI; settingsSyncPreviewResource: URI; webviewExternalEndpoint: string; logFile: URI; diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts index 695f872bcd56..e7b8a156283f 100644 --- a/src/typings/xterm.d.ts +++ b/src/typings/xterm.d.ts @@ -650,24 +650,32 @@ declare module 'xterm' { clear(): void; /** - * Writes text to the terminal. - * @param data The text to write to the terminal. + * Write data to the terminal. + * @param data The data to write to the terminal. This can either be raw + * bytes given as Uint8Array from the pty or a string. Raw bytes will always + * be treated as UTF-8 encoded, string data as UTF-16. + * @param callback Optional callback that fires when the data was processed + * by the parser. */ - write(data: string): void; + write(data: string | Uint8Array, callback?: () => void): void; /** - * Writes text to the terminal, followed by a break line character (\n). - * @param data The text to write to the terminal. + * Writes data to the terminal, followed by a break line character (\n). + * @param data The data to write to the terminal. This can either be raw + * bytes given as Uint8Array from the pty or a string. Raw bytes will always + * be treated as UTF-8 encoded, string data as UTF-16. + * @param callback Optional callback that fires when the data was processed + * by the parser. */ - writeln(data: string): void; + writeln(data: string | Uint8Array, callback?: () => void): void; /** - * Writes UTF8 data to the terminal. This has a slight performance advantage - * over the string based write method due to lesser data conversions needed - * on the way from the pty to xterm.js. + * Write UTF8 data to the terminal. * @param data The data to write to the terminal. + * @param callback Optional callback when data was processed. + * @deprecated use `write` instead */ - writeUtf8(data: Uint8Array): void; + writeUtf8(data: Uint8Array, callback?: () => void): void; /** * Writes text to the terminal, performing the necessary transformations for pasted text. @@ -804,7 +812,7 @@ declare module 'xterm' { */ export interface ITerminalAddon extends IDisposable { /** - * (EXPERIMENTAL) This is called when the addon is activated. + * This is called when the addon is activated. */ activate(terminal: Terminal): void; } diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index 15660767c2cc..fca338fb5d9e 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -173,6 +173,11 @@ outline: 0; } +.menubar.compact > .menubar-menu-button { + width: 100%; + height: 100%; +} + .menubar .menubar-menu-items-holder { position: absolute; left: 0px; @@ -197,9 +202,23 @@ height: 100%; } +.menubar.compact .toolbar-toggle-more { + background-position: center; + background-repeat: no-repeat; + background-size: 16px; + cursor: pointer; + width: 100%; + height: 100%; +} + .menubar .toolbar-toggle-more { display: inline-block; padding: 0; -webkit-mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px; mask: url('ellipsis.svg') no-repeat 50% 55%/14px 14px; -} \ No newline at end of file +} + +.menubar.compact .toolbar-toggle-more { + -webkit-mask: url('menu.svg') no-repeat 50% 55%/16px 16px; + mask: url('menu.svg') no-repeat 50% 55%/16px 16px; +} diff --git a/src/vs/base/browser/ui/menu/menu.svg b/src/vs/base/browser/ui/menu/menu.svg new file mode 100644 index 000000000000..1b61c9782295 --- /dev/null +++ b/src/vs/base/browser/ui/menu/menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index e1300c08ac32..4d10bb0fe5f7 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -23,6 +23,11 @@ import { isLinux, isMacintosh } from 'vs/base/common/platform'; export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/; export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g; +export enum Direction { + Right, + Left +} + export interface IMenuOptions { context?: any; actionViewItemProvider?: IActionViewItemProvider; @@ -31,6 +36,7 @@ export interface IMenuOptions { ariaLabel?: string; enableMnemonics?: boolean; anchorAlignment?: AnchorAlignment; + expandDirection?: Direction; } export interface IMenuStyles { @@ -591,6 +597,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { private mouseOver: boolean; private showScheduler: RunOnceScheduler; private hideScheduler: RunOnceScheduler; + private expandDirection: Direction; constructor( action: IAction, @@ -600,6 +607,8 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { ) { super(action, action, submenuOptions); + this.expandDirection = submenuOptions && submenuOptions.expandDirection !== undefined ? submenuOptions.expandDirection : Direction.Right; + this.showScheduler = new RunOnceScheduler(() => { if (this.mouseOver) { this.cleanupExistingSubmenu(false); @@ -715,11 +724,17 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { const computedStyles = getComputedStyle(this.parentData.parent.domNode); const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0; - if (window.innerWidth <= boundingRect.right + childBoundingRect.width) { - this.submenuContainer.style.left = '10px'; - this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset + boundingRect.height}px`; - } else { - this.submenuContainer.style.left = `${this.element.offsetWidth}px`; + if (this.expandDirection === Direction.Right) { + if (window.innerWidth <= boundingRect.right + childBoundingRect.width) { + this.submenuContainer.style.left = '10px'; + this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset + boundingRect.height}px`; + } else { + this.submenuContainer.style.left = `${this.element.offsetWidth}px`; + this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`; + } + } else if (this.expandDirection === Direction.Left) { + this.submenuContainer.style.right = `${this.element.offsetWidth}px`; + this.submenuContainer.style.left = 'auto'; this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`; } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index cd25ca83f990..a3a61c4c5788 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -10,7 +10,7 @@ import * as nls from 'vs/nls'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; -import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, SubmenuAction, IMenuStyles } from 'vs/base/browser/ui/menu/menu'; +import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MNEMONIC_REGEX, SubmenuAction, IMenuStyles, Direction } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; @@ -29,6 +29,7 @@ export interface IMenuBarOptions { visibility?: string; getKeybinding?: (action: IAction) => ResolvedKeybinding | undefined; alwaysOnMnemonics?: boolean; + compactMode?: Direction; } export interface MenuBarMenu { @@ -92,6 +93,9 @@ export class MenuBar extends Disposable { super(); this.container.setAttribute('role', 'menubar'); + if (this.options.compactMode !== undefined) { + DOM.addClass(this.container, 'compact'); + } this.menuCache = []; this.mnemonics = new Map(); @@ -292,7 +296,7 @@ export class MenuBar extends Disposable { } createOverflowMenu(): void { - const label = nls.localize('mMore', "..."); + const label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', "..."); const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'aria-haspopup': true }); const titleElement = $('div.menubar-menu-title.toolbar-toggle-more', { 'role': 'none', 'aria-hidden': true }); @@ -421,7 +425,7 @@ export class MenuBar extends Disposable { const sizeAvailable = this.container.offsetWidth; let currentSize = 0; - let full = false; + let full = this.options.compactMode !== undefined; const prevNumMenusShown = this.numMenusShown; this.numMenusShown = 0; for (let menuBarMenu of this.menuCache) { @@ -884,8 +888,19 @@ export class MenuBar extends Disposable { const menuHolder = $('div.menubar-menu-items-holder'); DOM.addClass(customMenu.buttonElement, 'open'); - menuHolder.style.top = `${this.container.clientHeight}px`; - menuHolder.style.left = `${customMenu.buttonElement.getBoundingClientRect().left}px`; + + if (this.options.compactMode === Direction.Right) { + menuHolder.style.top = `0px`; + menuHolder.style.left = `${customMenu.buttonElement.getBoundingClientRect().left + this.container.clientWidth}px`; + } else if (this.options.compactMode === Direction.Left) { + menuHolder.style.top = `0px`; + menuHolder.style.right = `${this.container.clientWidth}px`; + menuHolder.style.left = 'auto'; + console.log(customMenu.buttonElement.getBoundingClientRect().right - this.container.clientWidth); + } else { + menuHolder.style.top = `${this.container.clientHeight}px`; + menuHolder.style.left = `${customMenu.buttonElement.getBoundingClientRect().left}px`; + } customMenu.buttonElement.appendChild(menuHolder); @@ -893,7 +908,8 @@ export class MenuBar extends Disposable { getKeyBinding: this.options.getKeybinding, actionRunner: this.actionRunner, enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics), - ariaLabel: withNullAsUndefined(customMenu.buttonElement.getAttribute('aria-label')) + ariaLabel: withNullAsUndefined(customMenu.buttonElement.getAttribute('aria-label')), + expandDirection: this.options.compactMode !== undefined ? this.options.compactMode : Direction.Right }; let menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions)); diff --git a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts index f43ac7bd68b6..32174a6db84b 100644 --- a/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts +++ b/src/vs/base/browser/ui/octiconLabel/octiconLabel.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./octicons/octicons'; -import 'vs/css!./octicons/octicons2'; -import 'vs/css!./octicons/octicons-main'; import 'vs/css!./octicons/octicons-animations'; import { escape } from 'vs/base/common/strings'; diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons-main.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons-main.css deleted file mode 100644 index 46349b9f194b..000000000000 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons-main.css +++ /dev/null @@ -1,17 +0,0 @@ -body[data-octicons-update="enabled"] { - --version: octicons2; -} - -body { - --version: octicons; -} - -.octicon, .mega-octicon { - font-family: var(--version) !important; -} - -body[data-octicons-update="enabled"] .monaco-workbench .part.statusbar > .items-container > .statusbar-item > a { - display: flex; - align-items: center; - justify-content: center; -} diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css index d9cc1b7a4fa2..2305d04890f3 100644 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.css @@ -1,7 +1,6 @@ @font-face { font-family: "octicons"; - src: url("./octicons.ttf?1b0f2a9535896866c74dd24eedeb4374") format("truetype"), -url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); + src: url("./octicons.ttf?628f71ee09945d25ba5fceb0c17f7b0f") format("truetype"); } .octicon, .mega-octicon { @@ -137,11 +136,11 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-lock:before { content: "\f06a" } .octicon-log-in:before { content: "\f036" } .octicon-log-out:before { content: "\f032" } -.octicon-logo-gist:before { content: "\f288" } -.octicon-logo-github:before { content: "\f092" } .octicon-mail-read:before { content: "\f03c" } .octicon-mail-reply:before { content: "\f28c" } .octicon-mail:before { content: "\f03b" } +.octicon-logo-gist:before { content: "\f00a" } +.octicon-logo-github:before { content: "\f00a" } .octicon-mark-github:before { content: "\f00a" } .octicon-markdown:before { content: "\f0c9" } .octicon-megaphone:before { content: "\f077" } @@ -157,7 +156,7 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-note:before { content: "\f289" } .octicon-octoface:before { content: "\f008" } .octicon-organization:before { content: "\f037" } -.octicon-organization-filled:before { content: "\26a2" } +.octicon-organization-filled:before { content: "\f037" } .octicon-organization-outline:before { content: "\f037" } .octicon-package:before { content: "\f0c4" } .octicon-paintcan:before { content: "\f0d1" } @@ -165,7 +164,7 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-person-add:before { content: "\f018" } .octicon-person-follow:before { content: "\f018" } .octicon-person:before { content: "\f018" } -.octicon-person-filled:before { content: "\26a3" } +.octicon-person-filled:before { content: "\f018" } .octicon-person-outline:before { content: "\f018" } .octicon-pin:before { content: "\f041" } .octicon-plug:before { content: "\f0d4" } @@ -202,7 +201,7 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-shield:before { content: "\f0e1" } .octicon-sign-in:before { content: "\f036" } .octicon-sign-out:before { content: "\f032" } -.octicon-smiley:before { content: "\f27d" } +.octicon-smiley:before { content: "\26b2" } .octicon-squirrel:before { content: "\f0b2" } .octicon-star-add:before { content: "\f02a" } .octicon-star-delete:before { content: "\f02a" } @@ -233,16 +232,20 @@ url("./octicons.svg?1b0f2a9535896866c74dd24eedeb4374#octicons") format("svg"); .octicon-watch:before { content: "\f0e0" } .octicon-x:before { content: "\f081" } .octicon-zap:before { content: "\26a1" } -.octicon-archive:before { content: "\f101" } -.octicon-arrow-both:before { content: "\f102" } -.octicon-error:before { content: "\f103" } -.octicon-eye-closed:before { content: "\f104" } -.octicon-fold-down:before { content: "\f105" } -.octicon-fold-up:before { content: "\f106" } -.octicon-github-action:before { content: "\f107" } -.octicon-info-outline:before { content: "\f108" } -.octicon-play:before { content: "\f109" } -.octicon-remote:before { content: "\f10a" } -.octicon-request-changes:before { content: "\f10b" } -.octicon-smiley-outline:before { content: "\f10c" } -.octicon-warning:before { content: "\f10d" } +.octicon-error:before { content: "\26b1" } +.octicon-eye-closed:before { content: "\26a3" } +.octicon-fold-down:before { content: "\26a4" } +.octicon-fold-up:before { content: "\26a5" } +.octicon-github-action:before { content: "\26a6" } +.octicon-info-outline:before { content: "\26a7" } +.octicon-play:before { content: "\26a8" } +.octicon-remote:before { content: "\26a9" } +.octicon-request-changes:before { content: "\26aa" } +.octicon-smiley-outline:before { content: "\26b2" } +.octicon-warning:before { content: "\f02d" } +.octicon-controls:before { content: "\26ad" } +.octicon-event:before { content: "\26ae" } +.octicon-record-keys:before { content: "\26af" } +.octicon-Vector:before { content: "\f101" } +.octicon-archive:before { content: "\f102" } +.octicon-arrow-both:before { content: "\f103" } diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg deleted file mode 100644 index 3f4ab4f18079..000000000000 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.svg +++ /dev/null @@ -1,582 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf index 087e8706c81c..09468d332445 100644 Binary files a/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf and b/src/vs/base/browser/ui/octiconLabel/octicons/octicons.ttf differ diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css deleted file mode 100644 index 69e7dac4e6ca..000000000000 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css +++ /dev/null @@ -1,251 +0,0 @@ -@font-face { - font-family: "octicons2"; - src: url("./octicons2.ttf?aa78025ff36faa0aebb5b864685c6dc4") format("truetype"); -} - -.octicon, .mega-octicon { - font: normal normal normal 16px/1 octicons2; - display: inline-block; - text-decoration: none; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.mega-octicon { font-size: 32px; } - - - -body[data-octicons-update="enabled"] .octicon-alert:before { content: "\f02d" } -body[data-octicons-update="enabled"] .octicon-arrow-down:before { content: "\f03f" } -body[data-octicons-update="enabled"] .octicon-arrow-left:before { content: "\f040" } -body[data-octicons-update="enabled"] .octicon-arrow-right:before { content: "\f03e" } -body[data-octicons-update="enabled"] .octicon-arrow-small-down:before { content: "\f0a0" } -body[data-octicons-update="enabled"] .octicon-arrow-small-left:before { content: "\f0a1" } -body[data-octicons-update="enabled"] .octicon-arrow-small-right:before { content: "\f071" } -body[data-octicons-update="enabled"] .octicon-arrow-small-up:before { content: "\f09f" } -body[data-octicons-update="enabled"] .octicon-arrow-up:before { content: "\f03d" } -body[data-octicons-update="enabled"] .octicon-beaker:before { content: "\f0dd" } -body[data-octicons-update="enabled"] .octicon-bell:before { content: "\f0de" } -body[data-octicons-update="enabled"] .octicon-bold:before { content: "\f282" } -body[data-octicons-update="enabled"] .octicon-book:before { content: "\f007" } -body[data-octicons-update="enabled"] .octicon-bookmark:before { content: "\f07b" } -body[data-octicons-update="enabled"] .octicon-briefcase:before { content: "\f0d3" } -body[data-octicons-update="enabled"] .octicon-broadcast:before { content: "\f048" } -body[data-octicons-update="enabled"] .octicon-browser:before { content: "\f0c5" } -body[data-octicons-update="enabled"] .octicon-bug:before { content: "\f091" } -body[data-octicons-update="enabled"] .octicon-calendar:before { content: "\f068" } -body[data-octicons-update="enabled"] .octicon-check:before { content: "\f03a" } -body[data-octicons-update="enabled"] .octicon-checklist:before { content: "\f076" } -body[data-octicons-update="enabled"] .octicon-chevron-down:before { content: "\f0a3" } -body[data-octicons-update="enabled"] .octicon-chevron-left:before { content: "\f0a4" } -body[data-octicons-update="enabled"] .octicon-chevron-right:before { content: "\f078" } -body[data-octicons-update="enabled"] .octicon-chevron-up:before { content: "\f0a2" } -body[data-octicons-update="enabled"] .octicon-circle-slash:before { content: "\f084" } -body[data-octicons-update="enabled"] .octicon-circuit-board:before { content: "\f0d6" } -body[data-octicons-update="enabled"] .octicon-clippy:before { content: "\f035" } -body[data-octicons-update="enabled"] .octicon-clock:before { content: "\f046" } -body[data-octicons-update="enabled"] .octicon-clone:before { content: "\f0dc" } -body[data-octicons-update="enabled"] .octicon-cloud-download:before { content: "\f00b" } -body[data-octicons-update="enabled"] .octicon-cloud-upload:before { content: "\f00c" } -body[data-octicons-update="enabled"] .octicon-code:before { content: "\f05f" } -body[data-octicons-update="enabled"] .octicon-color-mode:before { content: "\f065" } -body[data-octicons-update="enabled"] .octicon-comment-add:before { content: "\f02b" } -body[data-octicons-update="enabled"] .octicon-comment-discussion:before { content: "\f04f" } -body[data-octicons-update="enabled"] .octicon-comment:before { content: "\f02b" } -body[data-octicons-update="enabled"] .octicon-credit-card:before { content: "\f045" } -body[data-octicons-update="enabled"] .octicon-dash:before { content: "\f0ca" } -body[data-octicons-update="enabled"] .octicon-dashboard:before { content: "\f07d" } -body[data-octicons-update="enabled"] .octicon-database:before { content: "\f096" } -body[data-octicons-update="enabled"] .octicon-desktop-download:before { content: "\f0dc" } -body[data-octicons-update="enabled"] .octicon-device-camera-video:before { content: "\f057" } -body[data-octicons-update="enabled"] .octicon-device-camera:before { content: "\f056" } -body[data-octicons-update="enabled"] .octicon-device-desktop:before { content: "\f27c" } -body[data-octicons-update="enabled"] .octicon-device-mobile:before { content: "\f038" } -body[data-octicons-update="enabled"] .octicon-diff-added:before { content: "\f06b" } -body[data-octicons-update="enabled"] .octicon-diff-ignored:before { content: "\f099" } -body[data-octicons-update="enabled"] .octicon-diff-modified:before { content: "\f06d" } -body[data-octicons-update="enabled"] .octicon-diff-removed:before { content: "\f06c" } -body[data-octicons-update="enabled"] .octicon-diff-renamed:before { content: "\f06e" } -body[data-octicons-update="enabled"] .octicon-diff:before { content: "\f04d" } -body[data-octicons-update="enabled"] .octicon-ellipsis:before { content: "\f09a" } -body[data-octicons-update="enabled"] .octicon-eye-unwatch:before { content: "\f04e" } -body[data-octicons-update="enabled"] .octicon-eye-watch:before { content: "\f04e" } -body[data-octicons-update="enabled"] .octicon-eye:before { content: "\f04e" } -body[data-octicons-update="enabled"] .octicon-file-add:before { content: "\f05d" } -body[data-octicons-update="enabled"] .octicon-file-binary:before { content: "\f094" } -body[data-octicons-update="enabled"] .octicon-file-code:before { content: "\f010" } -body[data-octicons-update="enabled"] .octicon-file-directory-create:before { content: "\f05d" } -body[data-octicons-update="enabled"] .octicon-file-directory:before { content: "\f016" } -body[data-octicons-update="enabled"] .octicon-file-media:before { content: "\f012" } -body[data-octicons-update="enabled"] .octicon-file-pdf:before { content: "\f014" } -body[data-octicons-update="enabled"] .octicon-file-submodule:before { content: "\f017" } -body[data-octicons-update="enabled"] .octicon-file-symlink-directory:before { content: "\f0b1" } -body[data-octicons-update="enabled"] .octicon-file-symlink-file:before { content: "\f0b0" } -body[data-octicons-update="enabled"] .octicon-file-text:before { content: "\f283" } -body[data-octicons-update="enabled"] .octicon-file-zip:before { content: "\f013" } -body[data-octicons-update="enabled"] .octicon-file:before { content: "\f283" } -body[data-octicons-update="enabled"] .octicon-flame:before { content: "\f0d2" } -body[data-octicons-update="enabled"] .octicon-fold:before { content: "\f0cc" } -body[data-octicons-update="enabled"] .octicon-gear:before { content: "\f02f" } -body[data-octicons-update="enabled"] .octicon-gift:before { content: "\f042" } -body[data-octicons-update="enabled"] .octicon-gist-fork:before { content: "\f002" } -body[data-octicons-update="enabled"] .octicon-gist-new:before { content: "\f05d" } -body[data-octicons-update="enabled"] .octicon-gist-private:before { content: "\f06a" } -body[data-octicons-update="enabled"] .octicon-gist-secret:before { content: "\f08c" } -body[data-octicons-update="enabled"] .octicon-gist:before { content: "\f00e" } -body[data-octicons-update="enabled"] .octicon-git-branch-create:before { content: "\f020" } -body[data-octicons-update="enabled"] .octicon-git-branch-delete:before { content: "\f020" } -body[data-octicons-update="enabled"] .octicon-git-branch:before { content: "\f020" } -body[data-octicons-update="enabled"] .octicon-git-commit:before { content: "\f01f" } -body[data-octicons-update="enabled"] .octicon-git-compare:before { content: "\f0ac" } -body[data-octicons-update="enabled"] .octicon-git-fork-private:before { content: "\f06a" } -body[data-octicons-update="enabled"] .octicon-git-merge:before { content: "\f023" } -body[data-octicons-update="enabled"] .octicon-git-pull-request-abandoned:before { content: "\f009" } -body[data-octicons-update="enabled"] .octicon-git-pull-request:before { content: "\f009" } -body[data-octicons-update="enabled"] .octicon-globe:before { content: "\f0b6" } -body[data-octicons-update="enabled"] .octicon-grabber:before { content: "\f284" } -body[data-octicons-update="enabled"] .octicon-graph:before { content: "\f043" } -body[data-octicons-update="enabled"] .octicon-heart:before { content: "\2665" } -body[data-octicons-update="enabled"] .octicon-history:before { content: "\f07e" } -body[data-octicons-update="enabled"] .octicon-home:before { content: "\f08d" } -body[data-octicons-update="enabled"] .octicon-horizontal-rule:before { content: "\f070" } -body[data-octicons-update="enabled"] .octicon-hubot:before { content: "\f09d" } -body[data-octicons-update="enabled"] .octicon-inbox:before { content: "\f0cf" } -body[data-octicons-update="enabled"] .octicon-info:before { content: "\f059" } -body[data-octicons-update="enabled"] .octicon-issue-closed:before { content: "\f028" } -body[data-octicons-update="enabled"] .octicon-issue-opened:before { content: "\f026" } -body[data-octicons-update="enabled"] .octicon-issue-reopened:before { content: "\f027" } -body[data-octicons-update="enabled"] .octicon-italic:before { content: "\f285" } -body[data-octicons-update="enabled"] .octicon-jersey:before { content: "\f019" } -body[data-octicons-update="enabled"] .octicon-kebab-horizontal:before { content: "\f286" } -body[data-octicons-update="enabled"] .octicon-kebab-vertical:before { content: "\f287" } -body[data-octicons-update="enabled"] .octicon-key:before { content: "\f049" } -body[data-octicons-update="enabled"] .octicon-keyboard:before { content: "\f00d" } -body[data-octicons-update="enabled"] .octicon-law:before { content: "\f0d8" } -body[data-octicons-update="enabled"] .octicon-light-bulb:before { content: "\f000" } -body[data-octicons-update="enabled"] .octicon-link-external:before { content: "\f07f" } -body[data-octicons-update="enabled"] .octicon-link:before { content: "\f05c" } -body[data-octicons-update="enabled"] .octicon-list-ordered:before { content: "\f062" } -body[data-octicons-update="enabled"] .octicon-list-unordered:before { content: "\f061" } -body[data-octicons-update="enabled"] .octicon-location:before { content: "\f060" } -body[data-octicons-update="enabled"] .octicon-lock:before { content: "\f06a" } -body[data-octicons-update="enabled"] .octicon-log-in:before { content: "\f036" } -body[data-octicons-update="enabled"] .octicon-log-out:before { content: "\f032" } -body[data-octicons-update="enabled"] .octicon-logo-gist:before { content: "\f288" } -body[data-octicons-update="enabled"] .octicon-logo-github:before { content: "\f092" } -body[data-octicons-update="enabled"] .octicon-mail-read:before { content: "\f03c" } -body[data-octicons-update="enabled"] .octicon-mail-reply:before { content: "\f28c" } -body[data-octicons-update="enabled"] .octicon-mail:before { content: "\f03b" } -body[data-octicons-update="enabled"] .octicon-mark-github:before { content: "\f00a" } -body[data-octicons-update="enabled"] .octicon-markdown:before { content: "\f0c9" } -body[data-octicons-update="enabled"] .octicon-megaphone:before { content: "\f077" } -body[data-octicons-update="enabled"] .octicon-mention:before { content: "\f0be" } -body[data-octicons-update="enabled"] .octicon-microscope:before { content: "\f0dd" } -body[data-octicons-update="enabled"] .octicon-milestone:before { content: "\f075" } -body[data-octicons-update="enabled"] .octicon-mirror-private:before { content: "\f06a" } -body[data-octicons-update="enabled"] .octicon-mirror-public:before { content: "\f024" } -body[data-octicons-update="enabled"] .octicon-mirror:before { content: "\f024" } -body[data-octicons-update="enabled"] .octicon-mortar-board:before { content: "\f0d7" } -body[data-octicons-update="enabled"] .octicon-mute:before { content: "\f080" } -body[data-octicons-update="enabled"] .octicon-no-newline:before { content: "\f09c" } -body[data-octicons-update="enabled"] .octicon-note:before { content: "\f289" } -body[data-octicons-update="enabled"] .octicon-octoface:before { content: "\f008" } -body[data-octicons-update="enabled"] .octicon-organization:before { content: "\f037" } -body[data-octicons-update="enabled"] .octicon-organization-filled:before { content: "\f037" } -body[data-octicons-update="enabled"] .octicon-organization-outline:before { content: "\f037" } -body[data-octicons-update="enabled"] .octicon-package:before { content: "\f0c4" } -body[data-octicons-update="enabled"] .octicon-paintcan:before { content: "\f0d1" } -body[data-octicons-update="enabled"] .octicon-pencil:before { content: "\f058" } -body[data-octicons-update="enabled"] .octicon-person-add:before { content: "\f018" } -body[data-octicons-update="enabled"] .octicon-person-follow:before { content: "\f018" } -body[data-octicons-update="enabled"] .octicon-person:before { content: "\f018" } -body[data-octicons-update="enabled"] .octicon-person-filled:before { content: "\f018" } -body[data-octicons-update="enabled"] .octicon-person-outline:before { content: "\f018" } -body[data-octicons-update="enabled"] .octicon-pin:before { content: "\f041" } -body[data-octicons-update="enabled"] .octicon-plug:before { content: "\f0d4" } -body[data-octicons-update="enabled"] .octicon-plus-small:before { content: "\f28a" } -body[data-octicons-update="enabled"] .octicon-plus:before { content: "\f05d" } -body[data-octicons-update="enabled"] .octicon-primitive-dot:before { content: "\f052" } -body[data-octicons-update="enabled"] .octicon-primitive-square:before { content: "\f053" } -body[data-octicons-update="enabled"] .octicon-project:before { content: "\f28b" } -body[data-octicons-update="enabled"] .octicon-pulse:before { content: "\f085" } -body[data-octicons-update="enabled"] .octicon-question:before { content: "\f02c" } -body[data-octicons-update="enabled"] .octicon-quote:before { content: "\f063" } -body[data-octicons-update="enabled"] .octicon-radio-tower:before { content: "\f030" } -body[data-octicons-update="enabled"] .octicon-remove-close:before { content: "\f081" } -body[data-octicons-update="enabled"] .octicon-reply:before { content: "\f28c" } -body[data-octicons-update="enabled"] .octicon-repo-clone:before { content: "\f04c" } -body[data-octicons-update="enabled"] .octicon-repo-create:before { content: "\f05d" } -body[data-octicons-update="enabled"] .octicon-repo-delete:before { content: "\f001" } -body[data-octicons-update="enabled"] .octicon-repo-force-push:before { content: "\f04a" } -body[data-octicons-update="enabled"] .octicon-repo-forked:before { content: "\f002" } -body[data-octicons-update="enabled"] .octicon-repo-pull:before { content: "\f006" } -body[data-octicons-update="enabled"] .octicon-repo-push:before { content: "\f005" } -body[data-octicons-update="enabled"] .octicon-repo-sync:before { content: "\f087" } -body[data-octicons-update="enabled"] .octicon-repo:before { content: "\f001" } -body[data-octicons-update="enabled"] .octicon-report:before { content: "\f28d" } -body[data-octicons-update="enabled"] .octicon-rocket:before { content: "\f033" } -body[data-octicons-update="enabled"] .octicon-rss:before { content: "\f034" } -body[data-octicons-update="enabled"] .octicon-ruby:before { content: "\f047" } -body[data-octicons-update="enabled"] .octicon-screen-full:before { content: "\f066" } -body[data-octicons-update="enabled"] .octicon-screen-normal:before { content: "\f067" } -body[data-octicons-update="enabled"] .octicon-search-save:before { content: "\f02e" } -body[data-octicons-update="enabled"] .octicon-search:before { content: "\f02e" } -body[data-octicons-update="enabled"] .octicon-server:before { content: "\f097" } -body[data-octicons-update="enabled"] .octicon-settings:before { content: "\f07c" } -body[data-octicons-update="enabled"] .octicon-shield:before { content: "\f0e1" } -body[data-octicons-update="enabled"] .octicon-sign-in:before { content: "\f036" } -body[data-octicons-update="enabled"] .octicon-sign-out:before { content: "\f032" } -body[data-octicons-update="enabled"] .octicon-smiley:before { content: "\26b2" } -body[data-octicons-update="enabled"] .octicon-squirrel:before { content: "\f0b2" } -body[data-octicons-update="enabled"] .octicon-star-add:before { content: "\f02a" } -body[data-octicons-update="enabled"] .octicon-star-delete:before { content: "\f02a" } -body[data-octicons-update="enabled"] .octicon-star:before { content: "\f02a" } -body[data-octicons-update="enabled"] .octicon-stop:before { content: "\f08f" } -body[data-octicons-update="enabled"] .octicon-sync:before { content: "\f087" } -body[data-octicons-update="enabled"] .octicon-tag-add:before { content: "\f015" } -body[data-octicons-update="enabled"] .octicon-tag-remove:before { content: "\f015" } -body[data-octicons-update="enabled"] .octicon-tag:before { content: "\f015" } -body[data-octicons-update="enabled"] .octicon-tasklist:before { content: "\f27e" } -body[data-octicons-update="enabled"] .octicon-telescope:before { content: "\f088" } -body[data-octicons-update="enabled"] .octicon-terminal:before { content: "\f0c8" } -body[data-octicons-update="enabled"] .octicon-text-size:before { content: "\f27f" } -body[data-octicons-update="enabled"] .octicon-three-bars:before { content: "\f05e" } -body[data-octicons-update="enabled"] .octicon-thumbsdown:before { content: "\f0db" } -body[data-octicons-update="enabled"] .octicon-thumbsup:before { content: "\f0da" } -body[data-octicons-update="enabled"] .octicon-tools:before { content: "\f031" } -body[data-octicons-update="enabled"] .octicon-trashcan:before { content: "\f0d0" } -body[data-octicons-update="enabled"] .octicon-triangle-down:before { content: "\f05b" } -body[data-octicons-update="enabled"] .octicon-triangle-left:before { content: "\f044" } -body[data-octicons-update="enabled"] .octicon-triangle-right:before { content: "\f05a" } -body[data-octicons-update="enabled"] .octicon-triangle-up:before { content: "\f0aa" } -body[data-octicons-update="enabled"] .octicon-unfold:before { content: "\f039" } -body[data-octicons-update="enabled"] .octicon-unmute:before { content: "\f0ba" } -body[data-octicons-update="enabled"] .octicon-unverified:before { content: "\f280" } -body[data-octicons-update="enabled"] .octicon-verified:before { content: "\f281" } -body[data-octicons-update="enabled"] .octicon-versions:before { content: "\f064" } -body[data-octicons-update="enabled"] .octicon-watch:before { content: "\f0e0" } -body[data-octicons-update="enabled"] .octicon-x:before { content: "\f081" } -body[data-octicons-update="enabled"] .octicon-zap:before { content: "\26a1" } -body[data-octicons-update="enabled"] .octicon-error:before { content: "\26b1" } -body[data-octicons-update="enabled"] .octicon-eye-closed:before { content: "\26a3" } -body[data-octicons-update="enabled"] .octicon-fold-down:before { content: "\26a4" } -body[data-octicons-update="enabled"] .octicon-fold-up:before { content: "\26a5" } -body[data-octicons-update="enabled"] .octicon-github-action:before { content: "\26a6" } -body[data-octicons-update="enabled"] .octicon-info-outline:before { content: "\26a7" } -body[data-octicons-update="enabled"] .octicon-play:before { content: "\26a8" } -body[data-octicons-update="enabled"] .octicon-remote:before { content: "\26a9" } -body[data-octicons-update="enabled"] .octicon-request-changes:before { content: "\26aa" } -body[data-octicons-update="enabled"] .octicon-smiley-outline:before { content: "\26b2" } -body[data-octicons-update="enabled"] .octicon-warning:before { content: "\f02d" } -body[data-octicons-update="enabled"] .octicon-controls:before { content: "\26ad" } -body[data-octicons-update="enabled"] .octicon-event:before { content: "\26ae" } -body[data-octicons-update="enabled"] .octicon-record-keys:before { content: "\26af" } -body[data-octicons-update="enabled"] .octicon-Vector:before { content: "\f101" } -body[data-octicons-update="enabled"] .octicon-archive:before { content: "\f102" } -body[data-octicons-update="enabled"] .octicon-arrow-both:before { content: "\f103" } diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf deleted file mode 100644 index 19f3d6e11ce8..000000000000 Binary files a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf and /dev/null differ diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index 468ce7c8f581..acd55ea88830 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -20,38 +20,22 @@ font-size: 11px; font-weight: bold; text-transform: uppercase; - padding-left: 20px; overflow: hidden; display: flex; cursor: pointer; } -.monaco-panel-view .panel > .panel-header { - background-image: url('tree-collapsed-light.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -.monaco-panel-view .panel > .panel-header.expanded { - background-image: url('tree-expanded-light.svg'); - background-position: 2px center; - background-repeat: no-repeat; -} - -.vs-dark .monaco-panel-view .panel > .panel-header { - background-image: url('tree-collapsed-dark.svg'); -} - -.vs-dark .monaco-panel-view .panel > .panel-header.expanded { - background-image: url('tree-expanded-dark.svg'); -} - -.hc-black .monaco-panel-view .panel > .panel-header { - background-image: url('tree-collapsed-hc.svg'); +.monaco-panel-view .panel > .panel-header > .twisties { + width: 20px; + display: flex; + align-items: center; + justify-content: center; + transform-origin: center; + color: inherit; } -.hc-black .monaco-panel-view .panel > .panel-header.expanded { - background-image: url('tree-expanded-hc.svg'); +.monaco-panel-view .panel > .panel-header.expanded > .twisties::before { + transform: rotate(90deg); } /* TODO: actions should be part of the panel, but they aren't yet */ @@ -79,6 +63,7 @@ display: flex; align-items: center; justify-content: center; + color: inherit; } /* Bold font style does not go well with CJK fonts */ diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index c6e34af059fc..41200e6e692c 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -54,10 +54,10 @@ export interface IView { } interface ISashEvent { - sash: Sash; - start: number; - current: number; - alt: boolean; + readonly sash: Sash; + readonly start: number; + readonly current: number; + readonly alt: boolean; } type ViewItemSize = number | { cachedVisibleSize: number }; diff --git a/src/vs/base/browser/ui/splitview/tree-collapsed-dark.svg b/src/vs/base/browser/ui/splitview/tree-collapsed-dark.svg deleted file mode 100644 index c2c2298dd5c4..000000000000 --- a/src/vs/base/browser/ui/splitview/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/splitview/tree-collapsed-hc.svg b/src/vs/base/browser/ui/splitview/tree-collapsed-hc.svg deleted file mode 100644 index 3732cbc04b8d..000000000000 --- a/src/vs/base/browser/ui/splitview/tree-collapsed-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/splitview/tree-collapsed-light.svg b/src/vs/base/browser/ui/splitview/tree-collapsed-light.svg deleted file mode 100644 index 1952ad63f848..000000000000 --- a/src/vs/base/browser/ui/splitview/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/splitview/tree-expanded-dark.svg b/src/vs/base/browser/ui/splitview/tree-expanded-dark.svg deleted file mode 100644 index 5570923e1753..000000000000 --- a/src/vs/base/browser/ui/splitview/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/splitview/tree-expanded-hc.svg b/src/vs/base/browser/ui/splitview/tree-expanded-hc.svg deleted file mode 100644 index b370009330c0..000000000000 --- a/src/vs/base/browser/ui/splitview/tree-expanded-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/splitview/tree-expanded-light.svg b/src/vs/base/browser/ui/splitview/tree-expanded-light.svg deleted file mode 100644 index 939ebc8b9695..000000000000 --- a/src/vs/base/browser/ui/splitview/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 0cf2cd2bd9ba..afd9a7d07013 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -225,18 +225,14 @@ interface Collection { class EventCollection implements Collection { - private disposables = new DisposableStore(); + readonly onDidChange: Event; get elements(): T[] { return this._elements; } - constructor(readonly onDidChange: Event, private _elements: T[] = []) { - onDidChange(e => this._elements = e, null, this.disposables); - } - - dispose() { - this.disposables.dispose(); + constructor(onDidChange: Event, private _elements: T[] = []) { + this.onDidChange = Event.forEach(onDidChange, elements => this._elements = elements); } } @@ -249,7 +245,7 @@ class TreeRenderer implements IListRenderer private renderedNodes = new Map, IRenderData>(); private indent: number = TreeRenderer.DefaultIndent; - private _renderIndentGuides: RenderIndentGuides = RenderIndentGuides.None; + private shouldRenderIndentGuides: boolean = false; private renderedIndentGuides = new SetMap, HTMLDivElement>(); private activeIndentNodes = new Set>(); private indentGuidesDisposable: IDisposable = Disposable.None; @@ -279,19 +275,18 @@ class TreeRenderer implements IListRenderer } if (typeof options.renderIndentGuides !== 'undefined') { - const renderIndentGuides = options.renderIndentGuides; + const shouldRenderIndentGuides = options.renderIndentGuides !== RenderIndentGuides.None; - if (renderIndentGuides !== this._renderIndentGuides) { - this._renderIndentGuides = renderIndentGuides; + if (shouldRenderIndentGuides !== this.shouldRenderIndentGuides) { + this.shouldRenderIndentGuides = shouldRenderIndentGuides; + this.indentGuidesDisposable.dispose(); - if (renderIndentGuides) { + if (shouldRenderIndentGuides) { const disposables = new DisposableStore(); this.activeNodes.onDidChange(this._onDidChangeActiveNodes, this, disposables); this.indentGuidesDisposable = disposables; this._onDidChangeActiveNodes(this.activeNodes.elements); - } else { - this.indentGuidesDisposable.dispose(); } } } @@ -370,6 +365,8 @@ class TreeRenderer implements IListRenderer this.renderer.renderTwistie(node.element, templateData.twistie); } + toggleClass(templateData.twistie, 'codicon', node.collapsible); + toggleClass(templateData.twistie, 'codicon-chevron-down', node.collapsible); toggleClass(templateData.twistie, 'collapsible', node.collapsible); toggleClass(templateData.twistie, 'collapsed', node.collapsible && node.collapsed); @@ -384,7 +381,7 @@ class TreeRenderer implements IListRenderer clearNode(templateData.indent); templateData.indentGuidesDisposable.dispose(); - if (this._renderIndentGuides === RenderIndentGuides.None) { + if (!this.shouldRenderIndentGuides) { return; } @@ -424,7 +421,7 @@ class TreeRenderer implements IListRenderer } private _onDidChangeActiveNodes(nodes: ITreeNode[]): void { - if (this._renderIndentGuides === RenderIndentGuides.None) { + if (!this.shouldRenderIndentGuides) { return; } @@ -1001,7 +998,6 @@ class Trait { insertedNodes.forEach(node => dfs(node, insertedNodesVisitor)); const nodes: ITreeNode[] = []; - let silent = true; for (const node of this.nodes) { const id = this.identityProvider.getId(node.element).toString(); @@ -1014,13 +1010,11 @@ class Trait { if (insertedNode) { nodes.push(insertedNode); - } else { - silent = false; } } } - this._set(nodes, silent); + this._set(nodes, true); } private createNodeSet(): Set> { @@ -1228,9 +1222,8 @@ export abstract class AbstractTree implements IDisposable const treeDelegate = new ComposedTreeDelegate>(delegate); const onDidChangeCollapseStateRelay = new Relay>(); - const onDidChangeActiveNodes = new Emitter[]>(); + const onDidChangeActiveNodes = new Relay[]>(); const activeNodes = new EventCollection(onDidChangeActiveNodes.event); - this.disposables.push(activeNodes); this.renderers = renderers.map(r => new TreeRenderer(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options)); this.disposables.push(...this.renderers); @@ -1250,24 +1243,35 @@ export abstract class AbstractTree implements IDisposable this.model = this.createModel(user, this.view, _options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; - this.model.onDidSplice(e => { + const onDidModelSplice = Event.forEach(this.model.onDidSplice, e => { this.eventBufferer.bufferEvents(() => { this.focus.onDidModelSplice(e); this.selection.onDidModelSplice(e); }); + }); - const set = new Set>(); + // Make sure the `forEach` always runs + onDidModelSplice(() => null, null, this.disposables); - for (const node of this.focus.getNodes()) { - set.add(node); - } + // Active nodes can change when the model changes or when focus or selection change. + // We debouce it with 0 delay since these events may fire in the same stack and we only + // want to run this once. It also doesn't matter if it runs on the next tick since it's only + // a nice to have UI feature. + onDidChangeActiveNodes.input = Event.chain(Event.any(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange)) + .debounce(() => null, 0) + .map(() => { + const set = new Set>(); - for (const node of this.selection.getNodes()) { - set.add(node); - } + for (const node of this.focus.getNodes()) { + set.add(node); + } + + for (const node of this.selection.getNodes()) { + set.add(node); + } - onDidChangeActiveNodes.fire(fromSet(set)); - }, null, this.disposables); + return fromSet(set); + }).event; if (_options.keyboardSupport !== false) { const onKeyDown = Event.chain(this.view.onKeyDown) diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 94a2a16c32d6..4c29229a696e 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -108,7 +108,7 @@ class AsyncDataTreeRenderer implements IT } renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { - toggleClass(twistieElement, 'loading', element.slow); + toggleClass(twistieElement, 'codicon-loading', element.slow); return false; } @@ -986,7 +986,7 @@ class CompressibleAsyncDataTreeRenderer i } renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { - toggleClass(twistieElement, 'loading', element.slow); + toggleClass(twistieElement, 'codicon-loading', element.slow); return false; } diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index 8640e8ce77b7..40c61feeadad 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -27,7 +27,9 @@ function noCompress(element: ICompressedTreeElement): ITreeElement(element: ICompressedTreeElement): ITreeElement(element: ITreeElement>, index = 0 } if (index === 0 && element.element.incompressible) { - return { element: element.element.elements[index], children, incompressible: true }; + return { + element: element.element.elements[index], + children, + incompressible: true, + collapsible: element.collapsible, + collapsed: element.collapsed + }; } - return { element: element.element.elements[index], children }; + return { + element: element.element.elements[index], + children, + collapsible: element.collapsible, + collapsed: element.collapsed + }; } // Exported only for test reasons, do not use directly diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 88dccce5ad91..d4dbaf2ea3ed 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -44,6 +44,11 @@ margin-right: 6px; flex-shrink: 0; width: 16px; + display: flex !important; + align-items: center; + justify-content: center; + color: inherit !important; + transform: translateX(3px); } .monaco-tl-contents { @@ -51,43 +56,10 @@ overflow: hidden; } -.monaco-tl-twistie.collapsible { - background-size: 16px; - background-position: 3px 50%; - background-repeat: no-repeat; - background-image: url("tree-expanded-light.svg"); -} - -.monaco-tl-twistie.collapsible.collapsed:not(.loading) { - display: inline-block; - background-image: url("tree-collapsed-light.svg"); -} - -.vs-dark .monaco-tl-twistie.collapsible:not(.loading) { - background-image: url("tree-expanded-dark.svg"); -} - -.vs-dark .monaco-tl-twistie.collapsible.collapsed:not(.loading) { - background-image: url("tree-collapsed-dark.svg"); -} - -.hc-black .monaco-tl-twistie.collapsible:not(.loading) { - background-image: url("tree-expanded-hc.svg"); -} - -.hc-black .monaco-tl-twistie.collapsible.collapsed:not(.loading) { - background-image: url("tree-collapsed-hc.svg"); -} - -.monaco-tl-twistie.loading { - background-image: url("loading.svg"); - background-position: 0 center; -} - -.vs-dark .monaco-tl-twistie.loading { - background-image: url("loading-dark.svg"); +.monaco-tl-twistie.collapsed::before { + transform: rotate(-90deg); } -.hc-black .monaco-tl-twistie.loading { - background-image: url("loading-hc.svg"); +.monaco-tl-twistie.codicon-loading::before { + animation: codicon-spin 1.25s linear infinite; } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 13c0178cde7c..0ae897bde66d 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -271,6 +271,8 @@ export namespace Event { filter(fn: (e: T) => boolean): IChainableEvent; reduce(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent; latch(): IChainableEvent; + debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent; + debounce(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent; on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; once(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable; } @@ -299,6 +301,12 @@ export namespace Event { return new ChainableEvent(latch(this.event)); } + debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent; + debounce(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent; + debounce(merge: (last: R | undefined, event: T) => R, delay: number = 100, leading = false, leakWarningThreshold?: number): IChainableEvent { + return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold)); + } + on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[] | DisposableStore) { return this.event(listener, thisArgs, disposables); } diff --git a/src/vs/base/common/marshalling.ts b/src/vs/base/common/marshalling.ts index 0a06e2cf66e8..d0c93662e202 100644 --- a/src/vs/base/common/marshalling.ts +++ b/src/vs/base/common/marshalling.ts @@ -12,7 +12,7 @@ export function stringify(obj: any): string { export function parse(text: string): any { let data = JSON.parse(text); - data = revive(data, 0); + data = revive(data); return data; } @@ -32,8 +32,7 @@ function replacer(key: string, value: any): any { return value; } -export function revive(obj: any, depth: number): any { - +export function revive(obj: any, depth = 0): any { if (!obj || depth > 200) { return obj; } diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 539458e57fc2..086a6c715123 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -207,3 +207,18 @@ export function withNullAsUndefined(x: T | null): T | undefined { export function withUndefinedAsNull(x: T | undefined): T | null { return typeof x === 'undefined' ? null : x; } + +/** + * Allows to add a first parameter to functions of a type. + */ +export type AddFirstParameterToFunctions = { + + // For every property + [K in keyof Target]: + + // Function: add param to function + Target[K] extends (...args: any) => TargetFunctionsReturnType ? (firstArg: FirstParameter, ...args: Parameters) => ReturnType : + + // Else: just leave as is + Target[K] +}; diff --git a/src/vs/base/parts/ipc/node/ipcChannelCreator.ts b/src/vs/base/parts/ipc/node/ipcChannelCreator.ts new file mode 100644 index 000000000000..934d92e6fd97 --- /dev/null +++ b/src/vs/base/parts/ipc/node/ipcChannelCreator.ts @@ -0,0 +1,124 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { revive } from 'vs/base/common/marshalling'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUpperAsciiLetter } from 'vs/base/common/strings'; + +// +// Use both `createChannelReceiver` and `createChannelSender` +// for automated process <=> process communication over methods +// and events. +// + +export interface IBaseChannelOptions { + + /** + * Disables automatic marshalling of `URI`. + * If marshalling is disabled, `UriComponents` + * must be used instead. + */ + disableMarshalling?: boolean; +} + +export interface IChannelReceiverOptions extends IBaseChannelOptions { } + +export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel { + const handler = service as { [key: string]: unknown }; + const disableMarshalling = options && options.disableMarshalling; + + // Buffer any event that should be supported by + // iterating over all property keys and finding them + const mapEventNameToEvent = new Map>(); + for (const key in handler) { + if (propertyIsEvent(key)) { + mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event, true)); + } + } + + return new class implements IServerChannel { + + listen(_: unknown, event: string): Event { + const eventImpl = mapEventNameToEvent.get(event); + if (eventImpl) { + return eventImpl as Event; + } + + throw new Error(`Event not found: ${event}`); + } + + call(_: unknown, command: string, args?: any[]): Promise { + const target = handler[command]; + if (typeof target === 'function') { + + // Revive unless marshalling disabled + if (!disableMarshalling && Array.isArray(args)) { + for (let i = 0; i < args.length; i++) { + args[i] = revive(args[i]); + } + } + + return target.apply(handler, args); + } + + throw new Error(`Method not found: ${command}`); + } + }; +} + +export interface IChannelSenderOptions extends IBaseChannelOptions { + + /** + * If provided, will add the value of `context` + * to each method call to the target. + */ + context?: unknown; +} + +export function createChannelSender(channel: IChannel, options?: IChannelSenderOptions): T { + const disableMarshalling = options && options.disableMarshalling; + + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + + // Event + if (propertyIsEvent(propKey)) { + return channel.listen(propKey); + } + + // Function + return async function (...args: any[]) { + + // Add context if any + let methodArgs: any[]; + if (options && !isUndefinedOrNull(options.context)) { + methodArgs = [options.context, ...args]; + } else { + methodArgs = args; + } + + const result = await channel.call(propKey, methodArgs); + + // Revive unless marshalling disabled + if (!disableMarshalling) { + return revive(result); + } + + return result; + }; + } + + throw new Error(`Property not found: ${String(propKey)}`); + } + }) as T; +} + +function propertyIsEvent(name: string): boolean { + // Assume a property is an event if it has a form of "onSomething" + return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2)); +} diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index c0289b6fcb4e..405161811b37 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -60,8 +60,14 @@ display: none; } -/* Expansion */ +/* Highlighted */ +.monaco-tree.highlighted .monaco-tree-rows > .monaco-tree-row:not(.highlighted) { + opacity: 0.3; +} + +/* {{SQL CARBON EDIT}} lower css settings +/* Expansion */ .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before { content: ' '; position: absolute; @@ -90,13 +96,6 @@ .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { background-image: url('loading.svg'); } - -/* Highlighted */ - -.monaco-tree.highlighted .monaco-tree-rows > .monaco-tree-row:not(.highlighted) { - opacity: 0.3; -} - .vs-dark .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before { background-image: url('tree-collapsed-dark.svg'); } diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index c4df0db57e47..ab8f13650406 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -154,7 +154,7 @@ export class ViewItem implements IViewItem { } set loading(value: boolean) { - value ? this.addClass('loading') : this.removeClass('loading'); + value ? this.addClass('codicon-loading') : this.removeClass('codicon-loading'); } set draggable(value: boolean) { diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 9101761577f2..22ba15a70866 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -18,7 +18,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; @@ -26,7 +26,6 @@ import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; @@ -40,6 +39,8 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; const MAX_URL_LENGTH = 2045; @@ -296,14 +297,15 @@ export class IssueReporter extends Disposable { const mainProcessService = new MainProcessService(configuration.windowId); serviceCollection.set(IMainProcessService, mainProcessService); - serviceCollection.set(IWindowsService, new WindowsService(mainProcessService)); this.environmentService = new EnvironmentService(configuration, configuration.execPath); const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); this.logService = new FollowerLogService(loggerClient, logService); - const sharedProcess = mainProcessService.getChannel('sharedProcess').call('whenSharedProcessReady') + const sharedProcessService = createChannelSender(mainProcessService.getChannel('sharedProcess')); + + const sharedProcess = sharedProcessService.whenSharedProcessReady() .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); const instantiationService = new InstantiationService(serviceCollection, true); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js index e046557218a5..fce40c4655bf 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -14,6 +14,7 @@ bootstrap.avoidMonkeyPatchFromAppInsights(); bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { sharedProcess.startup({ - machineId: configuration.machineId + machineId: configuration.machineId, + windowId: configuration.windowId }); -}); \ No newline at end of file +}); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index e7d3dd14214e..f1e84761e2a3 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -26,10 +26,9 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; -import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows'; -import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; +import { ActiveWindowManager } from 'vs/code/node/activeWindowTracker'; import { ipcRenderer } from 'electron'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, ILoggerService } from 'vs/platform/log/common/log'; import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; @@ -51,14 +50,19 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc'; +import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { LoggerService } from 'vs/platform/log/node/loggerService'; +import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; export interface ISharedProcessConfiguration { readonly machineId: string; + readonly windowId: number; } export function startup(configuration: ISharedProcessConfiguration) { @@ -115,12 +119,16 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(ILogService, logService); services.set(IConfigurationService, configurationService); services.set(IRequestService, new SyncDescriptor(RequestService)); + services.set(ILoggerService, new SyncDescriptor(LoggerService)); const mainProcessService = new MainProcessService(server, mainRouter); services.set(IMainProcessService, mainProcessService); - const windowsService = new WindowsService(mainProcessService); - services.set(IWindowsService, windowsService); + const electronService = createChannelSender(mainProcessService.getChannel('electron'), { context: configuration.windowId }); + services.set(IElectronService, electronService); + + const activeWindowManager = new ActiveWindowManager(electronService); + const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); // Files const fileService = new FileService(logService); @@ -169,9 +177,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); - // User Data Sync Contributions - const activeWindowManager = new ActiveWindowManager(windowsService); - const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); + services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter); services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 0d301eab04a2..e801a8b80c74 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -6,9 +6,8 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; -import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; -import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc'; -import { LegacyWindowsMainService } from 'vs/platform/windows/electron-main/legacyWindowsMainService'; +import { OpenContext, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { ActiveWindowManager } from 'vs/code/node/activeWindowTracker'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; @@ -22,7 +21,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IURLService } from 'vs/platform/url/common/url'; @@ -33,7 +32,7 @@ import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import { SimpleServiceProxyChannel } from 'vs/platform/ipc/node/simpleIpcProxy'; +import { createChannelReceiver } from 'vs/base/parts/ipc/node/ipcChannelCreator'; import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -65,7 +64,7 @@ import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIp import { startsWith } from 'vs/base/common/strings'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; -import { HistoryMainService, IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; +import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { URLService } from 'vs/platform/url/node/urlService'; import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { statSync } from 'fs'; @@ -78,6 +77,7 @@ import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/ext import { IElectronService } from 'vs/platform/electron/node/electron'; import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; +import { assign } from 'vs/base/common/objects'; export class CodeApplication extends Disposable { @@ -213,14 +213,14 @@ export class CodeApplication extends Disposable { }); }); - let macOpenFileURIs: IURIToOpen[] = []; + let macOpenFileURIs: IWindowOpenable[] = []; let runningTimeout: NodeJS.Timeout | null = null; app.on('open-file', (event: Event, path: string) => { this.logService.trace('App#open-file: ', path); event.preventDefault(); // Keep in array because more might come! - macOpenFileURIs.push(this.getURIToOpenFromPathSync(path)); + macOpenFileURIs.push(this.getWindowOpenableFromPathSync(path)); // Clear previous handler if any if (runningTimeout !== null) { @@ -450,7 +450,6 @@ export class CodeApplication extends Disposable { services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess])); - services.set(IWindowsService, new SyncDescriptor(LegacyWindowsMainService)); services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics'))); @@ -467,7 +466,7 @@ export class CodeApplication extends Disposable { const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); services.set(IBackupMainService, backupMainService); - services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService)); + services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService)); services.set(IURLService, new SyncDescriptor(URLService)); services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService)); @@ -540,28 +539,24 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); - const issueChannel = new SimpleServiceProxyChannel(issueService); + const issueChannel = createChannelReceiver(issueService); electronIpcServer.registerChannel('issue', issueChannel); const electronService = accessor.get(IElectronService); - const electronChannel = new SimpleServiceProxyChannel(electronService); + const electronChannel = createChannelReceiver(electronService); electronIpcServer.registerChannel('electron', electronChannel); + sharedProcessClient.then(client => client.registerChannel('electron', electronChannel)); const sharedProcessMainService = accessor.get(ISharedProcessMainService); - const sharedProcessChannel = new SimpleServiceProxyChannel(sharedProcessMainService); + const sharedProcessChannel = createChannelReceiver(sharedProcessMainService); electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel); const workspacesMainService = accessor.get(IWorkspacesMainService); const workspacesChannel = new WorkspacesChannel(workspacesMainService, accessor.get(IWindowsMainService)); electronIpcServer.registerChannel('workspaces', workspacesChannel); - const windowsService = accessor.get(IWindowsService); - const windowsChannel = new WindowsChannel(windowsService); - electronIpcServer.registerChannel('windows', windowsChannel); - sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel)); - const menubarService = accessor.get(IMenubarService); - const menubarChannel = new SimpleServiceProxyChannel(menubarService); + const menubarChannel = createChannelReceiver(menubarService); electronIpcServer.registerChannel('menubar', menubarChannel); const urlService = accessor.get(IURLService); @@ -585,8 +580,27 @@ export class CodeApplication extends Disposable { // Propagate to clients const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); + // Create a URL handler to open file URIs in the active window + const environmentService = accessor.get(IEnvironmentService); + urlService.registerHandler({ + async handleURL(uri: URI): Promise { + + // Catch file URLs + if (uri.authority === Schemas.file && !!uri.path) { + const cli = assign(Object.create(null), environmentService.args); + const urisToOpen = [{ fileUri: uri }]; + + windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); + + return true; + } + + return false; + } + }); + // Create a URL handler which forwards to the last active window - const activeWindowManager = new ActiveWindowManager(windowsService); + const activeWindowManager = new ActiveWindowManager(electronService); const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); const urlHandlerRouter = new URLHandlerRouter(activeWindowRouter); const urlHandlerChannel = electronIpcServer.getChannel('urlHandler', urlHandlerRouter); @@ -595,8 +609,6 @@ export class CodeApplication extends Disposable { // On Mac, Code can be running without any open windows, so we must create a window to handle urls, // if there is none if (isMacintosh) { - const environmentService = accessor.get(IEnvironmentService); - urlService.registerHandler({ async handleURL(uri: URI): Promise { if (windowsMainService.getWindowCount() === 0) { @@ -649,7 +661,7 @@ export class CodeApplication extends Disposable { return windowsMainService.open({ context: OpenContext.DOCK, cli: args, - urisToOpen: macOpenFiles.map(file => this.getURIToOpenFromPathSync(file)), + urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)), noRecentEntry, waitMarkerFileURI, gotoLineMode: false, @@ -670,7 +682,7 @@ export class CodeApplication extends Disposable { }); } - private getURIToOpenFromPathSync(path: string): IURIToOpen { + private getWindowOpenableFromPathSync(path: string): IWindowOpenable { try { const fileStat = statSync(path); if (fileStat.isDirectory()) { diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index ceab5d04093b..27be4a2323b4 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -21,7 +21,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log'; import { StateService } from 'vs/platform/state/node/stateService'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { EnvironmentService, xdgRuntimeDir } from 'vs/platform/environment/node/environmentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index aeff3c626f67..3dd9b7fa845a 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -45,7 +45,8 @@ export class SharedProcess implements ISharedProcess { appRoot: this.environmentService.appRoot, machineId: this.machineId, nodeCachedDataDir: this.environmentService.nodeCachedDataDir, - userEnv: this.userEnv + userEnv: this.userEnv, + windowId: this.window.id }); const url = `${require.toUrl('vs/code/electron-browser/sharedProcess/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index f94abe0ea102..585f3c4f43cf 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -11,23 +11,24 @@ import { assign, mixin } from 'vs/base/common/objects'; import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter, shell, MessageBoxReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Display } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import product from 'vs/platform/product/common/product'; import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IRecent } from 'vs/platform/history/common/history'; -import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; +import { IRecent } from 'vs/platform/workspaces/common/workspacesHistory'; +import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; @@ -192,7 +193,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IHistoryMainService private readonly historyMainService: IHistoryMainService, + @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -508,7 +509,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri }); } } - this.historyMainService.addRecentlyOpened(recents); + this.workspacesHistoryMainService.addRecentlyOpened(recents); } // If we got started with --wait from the CLI, we need to signal to the outside when the window @@ -1018,13 +1019,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService { return undefined; } - private parseUri(uriToOpen: IURIToOpen, options: IPathParseOptions = {}): IPathToOpen | undefined { - if (!uriToOpen) { + private parseUri(toOpen: IWindowOpenable, options: IPathParseOptions = {}): IPathToOpen | undefined { + if (!toOpen) { return undefined; } - let uri = resourceFromURIToOpen(uriToOpen); + + let uri = resourceFromURIToOpen(toOpen); if (uri.scheme === Schemas.file) { - return this.parsePath(uri.fsPath, options, isFileToOpen(uriToOpen)); + return this.parsePath(uri.fsPath, options, isFileToOpen(toOpen)); } // open remote if either specified in the cli or if it's a remotehost URI @@ -1038,7 +1040,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { uri = removeTrailingPathSeparator(uri); } - if (isFileToOpen(uriToOpen)) { + // File + if (isFileToOpen(toOpen)) { if (options.gotoLineMode) { const parsedPath = parseLineAndColumnAware(uri.path); return { @@ -1052,12 +1055,17 @@ export class WindowsManager extends Disposable implements IWindowsMainService { fileUri: uri, remoteAuthority }; - } else if (isWorkspaceToOpen(uriToOpen)) { + } + + // Workspace + else if (isWorkspaceToOpen(toOpen)) { return { workspace: getWorkspaceIdentifier(uri), remoteAuthority }; } + + // Folder return { folderUri: uri, remoteAuthority @@ -1121,7 +1129,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } } catch (error) { const fileUri = URI.file(candidate); - this.historyMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent + this.workspacesHistoryMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent // assume this is a file that does not yet exist if (options && options.ignoreFileNotFound) { @@ -1582,7 +1590,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult { // Mark as recently opened - this.historyMainService.addRecentlyOpened([{ workspace: result.workspace }]); + this.workspacesHistoryMainService.addRecentlyOpened([{ workspace: result.workspace }]); // Trigger Eevent to indicate load of workspace into window this._onWindowReady.fire(win); @@ -1610,7 +1618,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { return getLastActiveWindow(WindowsManager.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority)); } - openEmptyWindow(context: OpenContext, options?: { reuse?: boolean, remoteAuthority?: string }): ICodeWindow[] { + openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[] { let cli = this.environmentService.args; const remote = options && options.remoteAuthority; if (cli && (cli.remote !== remote)) { @@ -2098,14 +2106,14 @@ class WorkspacesManager { } } -function resourceFromURIToOpen(u: IURIToOpen): URI { - if (isWorkspaceToOpen(u)) { - return u.workspaceUri; +function resourceFromURIToOpen(openable: IWindowOpenable): URI { + if (isWorkspaceToOpen(openable)) { + return openable.workspaceUri; } - if (isFolderToOpen(u)) { - return u.folderUri; + if (isFolderToOpen(openable)) { + return openable.folderUri; } - return u.fileUri; + return openable.fileUri; } diff --git a/src/vs/code/node/activeWindowTracker.ts b/src/vs/code/node/activeWindowTracker.ts new file mode 100644 index 000000000000..2f928cae442a --- /dev/null +++ b/src/vs/code/node/activeWindowTracker.ts @@ -0,0 +1,51 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class ActiveWindowManager extends Disposable { + + private readonly disposables = this._register(new DisposableStore()); + private firstActiveWindowIdPromise: CancelablePromise | undefined; + + private activeWindowId: number | undefined; + + constructor(@IElectronService electronService: IElectronService) { + super(); + + // remember last active window id upon events + const onActiveWindowChange = Event.latch(Event.any(electronService.onWindowOpen, electronService.onWindowFocus)); + onActiveWindowChange(this.setActiveWindow, this, this.disposables); + + // resolve current active window + this.firstActiveWindowIdPromise = createCancelablePromise(() => electronService.getActiveWindowId()); + (async () => { + try { + const windowId = await this.firstActiveWindowIdPromise; + this.activeWindowId = (typeof this.activeWindowId === 'number') ? this.activeWindowId : windowId; + } finally { + this.firstActiveWindowIdPromise = undefined; + } + })(); + } + + private setActiveWindow(windowId: number | undefined) { + if (this.firstActiveWindowIdPromise) { + this.firstActiveWindowIdPromise.cancel(); + this.firstActiveWindowIdPromise = undefined; + } + + this.activeWindowId = windowId; + } + + async getActiveClientId(): Promise { + const id = this.firstActiveWindowIdPromise ? (await this.firstActiveWindowIdPromise) : this.activeWindowId; + + return `window:${id}`; + } +} diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 1512375fa597..e7ca96e15fcb 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -28,7 +28,7 @@ import { ConfigurationService } from 'vs/platform/configuration/node/configurati import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { mkdirp, writeFile } from 'vs/base/node/pfs'; import { getBaseLabel } from 'vs/base/common/labels'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { StateService } from 'vs/platform/state/node/stateService'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { isPromiseCanceledError } from 'vs/base/common/errors'; diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css index ff9f04b123a3..1fec2ff7ac43 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css @@ -14,4 +14,7 @@ */ .monaco-editor .margin-view-overlays .cgmr { position: absolute; + display: flex; + align-items: center; + justify-content: center; } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 04ed7f4b888d..914f7f010353 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1405,7 +1405,7 @@ export interface IWebviewPanelOptions { /** * @internal */ -export const enum WebviewEditorState { +export const enum WebviewContentState { Readonly = 1, Unchanged = 2, Dirty = 3, diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index ef3f7fa7b29e..a6517e1f55b1 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -95,7 +95,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor return markerDecorations ? markerDecorations.getMarkers() : []; } - private _handleMarkerChange(changedResources: URI[]): void { + private _handleMarkerChange(changedResources: readonly URI[]): void { changedResources.forEach((resource) => { const markerDecorations = this._markerDecorations.get(MODEL_ID(resource)); if (markerDecorations) { diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 037688bd5e4a..520eeee395d9 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -18,6 +18,7 @@ import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './codeActionTrigger'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { isEqual } from 'vs/base/common/resources'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); @@ -47,13 +48,13 @@ class CodeActionOracle extends Disposable { return this._createEventAndSignalChange(trigger, selection); } - private _onMarkerChanges(resources: URI[]): void { + private _onMarkerChanges(resources: readonly URI[]): void { const model = this._editor.getModel(); if (!model) { return; } - if (resources.some(resource => resource.toString() === model.uri.toString())) { + if (resources.some(resource => isEqual(resource, model.uri))) { this._autoTriggerTimer.cancelAndSet(() => { this.trigger({ type: 'auto' }); }, this._delay); diff --git a/src/vs/editor/contrib/folding/folding.css b/src/vs/editor/contrib/folding/folding.css index f04ce20e6587..caf861e4ec91 100644 --- a/src/vs/editor/contrib/folding/folding.css +++ b/src/vs/editor/contrib/folding/folding.css @@ -3,7 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .margin-view-overlays .codicon { +.monaco-editor .margin-view-overlays .codicon-chevron-right, +.monaco-editor .margin-view-overlays .codicon-chevron-down { cursor: pointer; opacity: 0; transition: opacity 0.5s; diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index dfac4b910676..5764cb47c42e 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -29,6 +29,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { withNullAsUndefined } from 'vs/base/common/types'; class RenameSkeleton { @@ -45,15 +46,15 @@ class RenameSkeleton { return this._providers.length > 0; } - async resolveRenameLocation(token: CancellationToken): Promise { + async resolveRenameLocation(token: CancellationToken): Promise { const firstProvider = this._providers[0]; if (!firstProvider) { return undefined; } - let res: RenameLocation & Rejection | null | undefined; + let res: RenameLocation & Rejection | undefined; if (firstProvider.resolveRenameLocation) { - res = await firstProvider.resolveRenameLocation(this.model, this.position, token); + res = withNullAsUndefined(await firstProvider.resolveRenameLocation(this.model, this.position, token)); } if (!res) { @@ -160,7 +161,7 @@ class RenameController extends Disposable implements IEditorContribution { return undefined; } - let loc: RenameLocation & Rejection | null | undefined; + let loc: RenameLocation & Rejection | undefined; try { const resolveLocationOperation = skeleton.resolveRenameLocation(token); this._progressService.showWhile(resolveLocationOperation, 250); @@ -254,6 +255,8 @@ class RenameController extends Disposable implements IEditorContribution { if (this._activeRename) { this._activeRename.operation.cancel(); this._activeRename = undefined; + + this.cancelRenameInput(); } } } diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 35fec59c6bc3..d7a82c1f0b95 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -11,7 +11,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass, addDisposableListener, addStandardDisposableListener } from 'vs/base/browser/dom'; -import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent, IListGestureEvent } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -524,7 +524,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onThemeChange(t))); this.toDispose.add(editor.onDidLayoutChange(() => this.onEditorLayoutChange())); - this.toDispose.add(this.list.onMouseDown(e => this.onListMouseDown(e))); + this.toDispose.add(this.list.onMouseDown(e => this.onListMouseDownOrTap(e))); + this.toDispose.add(this.list.onTap(e => this.onListMouseDownOrTap(e))); this.toDispose.add(this.list.onSelectionChange(e => this.onListSelection(e))); this.toDispose.add(this.list.onFocusChange(e => this.onListFocus(e))); this.toDispose.add(this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged())); @@ -572,7 +573,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate): void { + private onListMouseDownOrTap(e: IListMouseEvent | IListGestureEvent): void { if (typeof e.element === 'undefined' || typeof e.index === 'undefined') { return; } diff --git a/src/vs/platform/diagnostics/common/diagnostics.ts b/src/vs/platform/diagnostics/common/diagnostics.ts index 3eb4e55f3419..9b17f7c701d9 100644 --- a/src/vs/platform/diagnostics/common/diagnostics.ts +++ b/src/vs/platform/diagnostics/common/diagnostics.ts @@ -63,6 +63,7 @@ export interface PerformanceInfo { export interface IWorkspaceInformation extends IWorkspace { telemetryId: string | undefined; + rendererSessionId: string; } export function isRemoteDiagnosticError(x: any): x is IRemoteDiagnosticError { diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index f1cf312dc1b7..7e005946a94c 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -539,21 +539,46 @@ export class DiagnosticsService implements IDiagnosticsService { collectWorkspaceStats(folder, ['node_modules', '.git']).then(stats => { type WorkspaceStatsClassification = { 'workspace.id': { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; - fileTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - configTypes: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; - launchConfigs: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; type WorkspaceStatsEvent = { 'workspace.id': string | undefined; - fileTypes: WorkspaceStatItem[]; - configTypes: WorkspaceStatItem[]; - launchConfigs: WorkspaceStatItem[]; + rendererSessionId: string; }; this.telemetryService.publicLog2('workspace.stats', { 'workspace.id': workspace.telemetryId, - fileTypes: stats.fileTypes, - configTypes: stats.configFiles, - launchConfigs: stats.launchConfigFiles + rendererSessionId: workspace.rendererSessionId + }); + type WorkspaceStatsFileClassification = { + rendererSessionId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + name: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + count: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + }; + type WorkspaceStatsFileEvent = { + rendererSessionId: string; + name: string; + count: number; + }; + stats.fileTypes.forEach(e => { + this.telemetryService.publicLog2('workspace.stats.file', { + rendererSessionId: workspace.rendererSessionId, + name: e.name, + count: e.count + }); + }); + stats.launchConfigFiles.forEach(e => { + this.telemetryService.publicLog2('workspace.stats.launchConfigFile', { + rendererSessionId: workspace.rendererSessionId, + name: e.name, + count: e.count + }); + }); + stats.configFiles.forEach(e => { + this.telemetryService.publicLog2('workspace.stats.configFiles', { + rendererSessionId: workspace.rendererSessionId, + name: e.name, + count: e.count + }); }); }).catch(_ => { // Report nothing if collecting metadata fails. diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 4c0bf84e71c7..a0f9bc90e6bb 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -8,9 +8,13 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { URI } from 'vs/base/common/uri'; import { basename } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { FileFilter } from 'vs/platform/windows/common/windows'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +export interface FileFilter { + extensions: string[]; + name: string; +} + export type DialogType = 'none' | 'info' | 'error' | 'question' | 'warning'; export interface IConfirmation { @@ -90,7 +94,7 @@ export interface ISaveDialogOptions { * Specifies a list of schemas for the file systems the user can save to. If not specified, uses the schema of the defaultURI or, if also not specified, * the schema of the current window. */ - availableFileSystems?: string[]; + availableFileSystems?: readonly string[]; } export interface IOpenDialogOptions { @@ -134,7 +138,7 @@ export interface IOpenDialogOptions { * Specifies a list of schemas for the file systems the user can load from. If not specified, uses the schema of the defaultURI or, if also not available, * the schema of the current window. */ - availableFileSystems?: string[]; + availableFileSystems?: readonly string[]; } diff --git a/src/vs/platform/dialogs/node/dialogs.ts b/src/vs/platform/dialogs/node/dialogs.ts new file mode 100644 index 000000000000..13a3ff569686 --- /dev/null +++ b/src/vs/platform/dialogs/node/dialogs.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; + +export interface INativeOpenDialogOptions { + forceNewWindow?: boolean; + + defaultPath?: string; + + telemetryEventName?: string; + telemetryExtraData?: ITelemetryData; +} diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 319af731f3e3..761b2e055449 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -8,7 +8,6 @@ import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platf import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import * as electron from 'electron'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { timeout } from 'vs/base/common/async'; import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -46,10 +45,9 @@ class WindowDriver extends BaseWindowDriver { } } -export async function registerWindowDriver(accessor: ServicesAccessor): Promise { +export async function registerWindowDriver(accessor: ServicesAccessor, windowId: number): Promise { const instantiationService = accessor.get(IInstantiationService); const mainProcessService = accessor.get(IMainProcessService); - const windowService = accessor.get(IWindowService); const windowDriver = instantiationService.createInstance(WindowDriver); const windowDriverChannel = new WindowDriverChannel(windowDriver); @@ -58,12 +56,12 @@ export async function registerWindowDriver(accessor: ServicesAccessor): Promise< const windowDriverRegistryChannel = mainProcessService.getChannel('windowDriverRegistry'); const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel); - await windowDriverRegistry.registerWindowDriver(windowService.windowId); + await windowDriverRegistry.registerWindowDriver(windowId); // const options = await windowDriverRegistry.registerWindowDriver(windowId); // if (options.verbose) { // windowDriver.openDevTools(); // } - return toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowService.windowId)); + return toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId)); } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 0e1305f4c1c3..23857547cb6d 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -3,35 +3,97 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu, BrowserWindow, app } from 'electron'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { OpenContext, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; -import { isMacintosh } from 'vs/base/common/platform'; +import { IOpenedWindow, OpenContext, IWindowOpenable, IOpenInWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; +import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { AddContextToFunctions } from 'vs/platform/ipc/node/simpleIpcProxy'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { AddFirstParameterToFunctions } from 'vs/base/common/types'; +import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; +import { IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspacesHistory'; +import { URI } from 'vs/base/common/uri'; -export class ElectronMainService implements AddContextToFunctions { +export class ElectronMainService implements AddFirstParameterToFunctions /* only methods, not events */, number /* window ID */> { _serviceBrand: undefined; constructor( @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService ) { } + //#region Events + + readonly onWindowOpen: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-created', (_, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)); + + readonly onWindowMaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)); + readonly onWindowUnmaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)); + + readonly onWindowBlur: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (_, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)); + readonly onWindowFocus: Event = Event.any( + Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), window => !!window), window => window!.id), + Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, window: BrowserWindow) => window.id), windowId => !!this.windowsMainService.getWindowById(windowId)) + ); + + //#endregion + //#region Window - async windowCount(windowId: number): Promise { + async getWindows(): Promise { + const windows = this.windowsMainService.getWindows(); + + return windows.map(window => ({ + id: window.id, + workspace: window.openedWorkspace, + folderUri: window.openedFolderUri, + title: window.win.getTitle(), + filename: window.getRepresentedFilename() + })); + } + + async getWindowCount(windowId: number): Promise { return this.windowsMainService.getWindowCount(); } - async openEmptyWindow(windowId: number, options?: { reuse?: boolean, remoteAuthority?: string }): Promise { + async getActiveWindowId(windowId: number): Promise { + const activeWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + if (activeWindow) { + return activeWindow.id; + } + + return undefined; + } + + async openEmptyWindow(windowId: number, options?: IOpenEmptyWindowOptions): Promise { this.windowsMainService.openEmptyWindow(OpenContext.API, options); } + async openInWindow(windowId: number, toOpen: IWindowOpenable[], options: IOpenInWindowOptions = Object.create(null)): Promise { + if (toOpen.length > 0) { + this.windowsMainService.open({ + context: OpenContext.API, + contextWindowId: windowId, + urisToOpen: toOpen, + cli: this.environmentService.args, + forceNewWindow: options.forceNewWindow, + forceReuseWindow: options.forceReuseWindow, + diffMode: options.diffMode, + addMode: options.addMode, + gotoLineMode: options.gotoLineMode, + noRecentEntry: options.noRecentEntry, + waitMarkerFileURI: options.waitMarkerFileURI + }); + } + } + async toggleFullScreen(windowId: number): Promise { const window = this.windowsMainService.getWindowById(windowId); if (window) { @@ -76,6 +138,30 @@ export class ElectronMainService implements AddContextToFunctions { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return window.win.isFocused(); + } + + return false; + } + + async focusWindow(windowId: number, options?: { windowId?: number; }): Promise { + if (options && typeof options.windowId === 'number') { + windowId = options.windowId; + } + + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + if (isMacintosh) { + window.win.show(); + } else { + window.win.focus(); + } + } + } + //#endregion //#region Dialog @@ -245,4 +331,48 @@ export class ElectronMainService implements AddContextToFunctions { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return this.workspacesHistoryMainService.getRecentlyOpened(window.config.workspace, window.config.folderUri, window.config.filesToOpenOrCreate); + } + + return this.workspacesHistoryMainService.getRecentlyOpened(); + } + + async addRecentlyOpened(windowId: number, recents: IRecent[]): Promise { + return this.workspacesHistoryMainService.addRecentlyOpened(recents); + } + + async removeFromRecentlyOpened(windowId: number, paths: URI[]): Promise { + return this.workspacesHistoryMainService.removeFromRecentlyOpened(paths); + } + + async clearRecentlyOpened(windowId: number): Promise { + return this.workspacesHistoryMainService.clearRecentlyOpened(); + } + + //#endregion + + //#region Debug + + // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) + + async openExtensionDevelopmentHostWindow(windowId: number, args: ParsedArgs, env: IProcessEnvironment): Promise { + const extDevPaths = args.extensionDevelopmentPath; + if (extDevPaths) { + this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { + context: OpenContext.API, + cli: args, + userEnv: Object.keys(env).length > 0 ? env : undefined + }); + } + } + + //#endregion } diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 9f8b41184be2..94de2856d0c7 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -3,10 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions } from 'electron'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable, IOpenInWindowOptions, IOpenEmptyWindowOptions, IOpenedWindow } from 'vs/platform/windows/common/windows'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { IRecentlyOpened, IRecent } from 'vs/platform/workspaces/common/workspacesHistory'; +import { URI } from 'vs/base/common/uri'; +import { ParsedArgs } from 'vscode-minimist'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IElectronService = createDecorator('electronService'); @@ -14,10 +20,25 @@ export interface IElectronService { _serviceBrand: undefined; + // Events + readonly onWindowOpen: Event; + + readonly onWindowMaximize: Event; + readonly onWindowUnmaximize: Event; + + readonly onWindowFocus: Event; + readonly onWindowBlur: Event; + // Window - windowCount(): Promise; - openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise; + getWindows(): Promise; + getWindowCount(): Promise; + getActiveWindowId(): Promise; + + openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise; + openInWindow(toOpen: IWindowOpenable[], options?: IOpenInWindowOptions): Promise; + toggleFullScreen(): Promise; + handleTitleDoubleClick(): Promise; isMaximized(): Promise; @@ -25,6 +46,9 @@ export interface IElectronService { unmaximizeWindow(): Promise; minimizeWindow(): Promise; + isWindowFocused(): Promise; + focusWindow(options?: { windowId?: number }): Promise; + // Dialogs showMessageBox(options: MessageBoxOptions): Promise; showSaveDialog(options: SaveDialogOptions): Promise; @@ -64,4 +88,14 @@ export interface IElectronService { // Connectivity resolveProxy(url: string): Promise; + + // Workspaces History + readonly onRecentlyOpenedChange: Event; + getRecentlyOpened(): Promise; + addRecentlyOpened(recents: IRecent[]): Promise; + removeFromRecentlyOpened(paths: URI[]): Promise; + clearRecentlyOpened(): Promise; + + // Debug (TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index cc5df6477542..6c5808746859 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -123,11 +123,14 @@ export interface IEnvironmentService { // user roaming data userRoamingDataHome: URI; settingsResource: URI; - settingsSyncPreviewResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; localeResource: URI; + // sync resources + userDataSyncLogResource: URI; + settingsSyncPreviewResource: URI; + machineSettingsHome: URI; machineSettingsResource: URI; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 5a478f53658c..a56faffef0ed 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -119,6 +119,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.settings.json'); } + @memoize + get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } + @memoize get machineSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'Machine')); } diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 48a74804620b..176f0799ba5a 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -209,7 +209,7 @@ export interface IFileSystemProvider { readonly onDidErrorOccur?: Event; // TODO@ben remove once file watchers are solid - readonly onDidChangeFile: Event; + readonly onDidChangeFile: Event; watch(resource: URI, opts: IWatchOptions): IDisposable; stat(resource: URI): Promise; @@ -389,19 +389,19 @@ export interface IFileChange { /** * The type of change that occurred to the file. */ - type: FileChangeType; + readonly type: FileChangeType; /** * The unified resource identifier of the file that changed. */ - resource: URI; + readonly resource: URI; } export class FileChangesEvent { - private _changes: IFileChange[]; + private readonly _changes: readonly IFileChange[]; - constructor(changes: IFileChange[]) { + constructor(changes: readonly IFileChange[]) { this._changes = changes; } diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 916b96cdd685..785d7a30ce7c 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -449,8 +449,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); readonly onDidErrorOccur: Event = this._onDidWatchErrorOccur.event; - private _onDidChangeFile: Emitter = this._register(new Emitter()); - get onDidChangeFile(): Event { return this._onDidChangeFile.event; } + private _onDidChangeFile = this._register(new Emitter()); + get onDidChangeFile(): Event { return this._onDidChangeFile.event; } private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined; private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = []; diff --git a/src/vs/platform/files/test/common/nullFileSystemProvider.ts b/src/vs/platform/files/test/common/nullFileSystemProvider.ts index 2bb5a28c8647..c1ed081d14b7 100644 --- a/src/vs/platform/files/test/common/nullFileSystemProvider.ts +++ b/src/vs/platform/files/test/common/nullFileSystemProvider.ts @@ -13,7 +13,7 @@ export class NullFileSystemProvider implements IFileSystemProvider { capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.Readonly; onDidChangeCapabilities: Event = Event.None; - onDidChangeFile: Event = Event.None; + onDidChangeFile: Event = Event.None; constructor(private disposableFactory: () => IDisposable = () => Disposable.None) { } @@ -30,4 +30,4 @@ export class NullFileSystemProvider implements IFileSystemProvider { close?(fd: number): Promise { return Promise.resolve(undefined!); } read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return Promise.resolve(undefined!); } write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { return Promise.resolve(undefined!); } -} \ No newline at end of file +} diff --git a/src/vs/platform/files/test/node/diskFileService.test.ts b/src/vs/platform/files/test/node/diskFileService.test.ts index 5ae6ce1ada50..c1a30353fcc2 100644 --- a/src/vs/platform/files/test/node/diskFileService.test.ts +++ b/src/vs/platform/files/test/node/diskFileService.test.ts @@ -1918,7 +1918,7 @@ suite('Disk File Service', function () { }); } - function hasChange(changes: IFileChange[], type: FileChangeType, resource: URI): boolean { + function hasChange(changes: readonly IFileChange[], type: FileChangeType, resource: URI): boolean { return changes.some(change => change.type === type && isEqual(change.resource, resource)); } diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index b67aa8d64773..687f3fcb3134 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -206,7 +206,7 @@ export class InstantiationService implements IInstantiationService { } else if (this._parent) { return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace); } else { - throw new Error('illegalState - creating UNKNOWN service instance'); + throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`); } } diff --git a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts index 8045c1573bd6..5d5718524970 100644 --- a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts @@ -4,12 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Client } from 'vs/base/parts/ipc/common/ipc.net'; -import { connect } from 'vs/base/parts/ipc/node/ipc.net'; -import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; export const ISharedProcessService = createDecorator('sharedProcessService'); @@ -23,38 +18,3 @@ export interface ISharedProcessService { whenSharedProcessReady(): Promise; toggleSharedProcessWindow(): Promise; } - -export class SharedProcessService implements ISharedProcessService { - - _serviceBrand: undefined; - - private withSharedProcessConnection: Promise>; - private sharedProcessMainChannel: IChannel; - - constructor( - @IMainProcessService mainProcessService: IMainProcessService, - @IWindowService windowService: IWindowService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess'); - - this.withSharedProcessConnection = this.whenSharedProcessReady() - .then(() => connect(environmentService.sharedIPCHandle, `window:${windowService.windowId}`)); - } - - whenSharedProcessReady(): Promise { - return this.sharedProcessMainChannel.call('whenSharedProcessReady'); - } - - getChannel(channelName: string): IChannel { - return getDelayedChannel(this.withSharedProcessConnection.then(connection => connection.getChannel(channelName))); - } - - registerChannel(channelName: string, channel: IServerChannel): void { - this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel)); - } - - toggleSharedProcessWindow(): Promise { - return this.sharedProcessMainChannel.call('toggleSharedProcessWindow'); - } -} diff --git a/src/vs/platform/ipc/node/simpleIpcProxy.ts b/src/vs/platform/ipc/node/simpleIpcProxy.ts deleted file mode 100644 index f8621595e581..000000000000 --- a/src/vs/platform/ipc/node/simpleIpcProxy.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; - -// -// Use both `SimpleServiceProxyChannel` and `createSimpleChannelProxy` -// for a very basic process <=> process communication over methods. -// - -export type AddContextToFunctions = { - // For every property: IF property is a FUNCTION ADD context as first parameter and original parameters afterwards with same return type, otherwise preserve as is - [K in keyof Target]: Target[K] extends (...args: any) => any ? (context: Context, ...args: Parameters) => ReturnType : Target[K] -}; - -interface ISimpleChannelProxyContext { - __$simpleIPCContextMarker: boolean; - proxyContext: unknown; -} - -function serializeContext(proxyContext?: unknown): ISimpleChannelProxyContext | undefined { - if (proxyContext) { - return { __$simpleIPCContextMarker: true, proxyContext }; - } - - return undefined; -} - -function deserializeContext(candidate?: ISimpleChannelProxyContext | undefined): unknown | undefined { - if (candidate && candidate.__$simpleIPCContextMarker === true) { - return candidate.proxyContext; - } - - return undefined; -} - -export class SimpleServiceProxyChannel implements IServerChannel { - - private service: { [key: string]: unknown }; - - constructor(service: unknown) { - this.service = service as { [key: string]: unknown }; - } - - listen(_: unknown, event: string): Event { - throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); - } - - call(_: unknown, command: string, args?: any[]): Promise { - const target = this.service[command]; - if (typeof target === 'function') { - if (Array.isArray(args)) { - const context = deserializeContext(args[0]); - if (context) { - args[0] = context; - } - } - - return target.apply(this.service, args); - } - - throw new Error(`Method not found: ${command}`); - } -} - -export function createSimpleChannelProxy(channel: IChannel, context?: unknown): T { - const serializedContext = serializeContext(context); - - return new Proxy({}, { - get(_target, propKey, _receiver) { - if (typeof propKey === 'string') { - return function (...args: any[]) { - let methodArgs: any[]; - if (serializedContext) { - methodArgs = [context, ...args]; - } else { - methodArgs = args; - } - - return channel.call(propKey, methodArgs); - }; - } - - throw new Error(`Unable to provide main channel proxy implementation for: ${String(propKey)}`); - } - }) as T; -} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index 656eb16d79e1..e357c1835ba0 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -5,13 +5,13 @@ import { IIssueService } from 'vs/platform/issue/node/issue'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; +import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; export class IssueService { _serviceBrand: undefined; constructor(@IMainProcessService mainProcessService: IMainProcessService) { - return createSimpleChannelProxy(mainProcessService.getChannel('issue')); + return createChannelSender(mainProcessService.getChannel('issue')); } } diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 62b00af9d594..f3ac9cc67b4d 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -5,7 +5,7 @@ import { ipcMain as ipc, app } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { Event, Emitter } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ICodeWindow } from 'vs/platform/windows/electron-main/windows'; diff --git a/src/vs/platform/log/common/bufferLog.ts b/src/vs/platform/log/common/bufferLog.ts index ce1d7497d1e9..204e831d706f 100644 --- a/src/vs/platform/log/common/bufferLog.ts +++ b/src/vs/platform/log/common/bufferLog.ts @@ -7,7 +7,7 @@ import { ILogService, LogLevel, AbstractLogService, DEFAULT_LOG_LEVEL } from 'vs interface ILog { level: LogLevel; - args: IArguments; + args: any[]; } function getLogFunction(logger: ILogService, level: LogLevel): Function { @@ -49,7 +49,7 @@ export class BufferLogService extends AbstractLogService implements ILogService this.buffer = []; } - private _log(level: LogLevel, args: IArguments): void { + private _log(level: LogLevel, ...args: any[]): void { if (this._logger) { const fn = getLogFunction(this._logger, level); fn.apply(this._logger, args); @@ -58,28 +58,28 @@ export class BufferLogService extends AbstractLogService implements ILogService } } - trace(): void { - this._log(LogLevel.Trace, arguments); + trace(message: string, ...args: any[]): void { + this._log(LogLevel.Trace, message, ...args); } - debug(): void { - this._log(LogLevel.Debug, arguments); + debug(message: string, ...args: any[]): void { + this._log(LogLevel.Debug, message, ...args); } - info(): void { - this._log(LogLevel.Info, arguments); + info(message: string, ...args: any[]): void { + this._log(LogLevel.Info, message, ...args); } - warn(): void { - this._log(LogLevel.Warning, arguments); + warn(message: string, ...args: any[]): void { + this._log(LogLevel.Warning, message, ...args); } - error(): void { - this._log(LogLevel.Error, arguments); + error(message: string | Error, ...args: any[]): void { + this._log(LogLevel.Error, message, ...args); } - critical(): void { - this._log(LogLevel.Critical, arguments); + critical(message: string | Error, ...args: any[]): void { + this._log(LogLevel.Critical, message, ...args); } dispose(): void { diff --git a/src/vs/platform/log/common/fileLogService.ts b/src/vs/platform/log/common/fileLogService.ts index 8e4edf477280..91888bd3d962 100644 --- a/src/vs/platform/log/common/fileLogService.ts +++ b/src/vs/platform/log/common/fileLogService.ts @@ -3,12 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ILogService, LogLevel, AbstractLogService } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath, basename } from 'vs/base/common/resources'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const MAX_FILE_SIZE = 1024 * 1024 * 5; @@ -16,6 +18,7 @@ export class FileLogService extends AbstractLogService implements ILogService { _serviceBrand: undefined; + private readonly initializePromise: Promise; private readonly queue: Queue; private backupIndex: number = 1; @@ -28,6 +31,7 @@ export class FileLogService extends AbstractLogService implements ILogService { super(); this.setLevel(level); this.queue = this._register(new Queue()); + this.initializePromise = this.initialize(); } trace(): void { @@ -82,8 +86,13 @@ export class FileLogService extends AbstractLogService implements ILogService { this._log(level, this.format(args)); } + private async initialize(): Promise { + await this.fileService.createFile(this.resource); + } + private _log(level: LogLevel, message: string): void { this.queue.queue(async () => { + await this.initializePromise; let content = await this.loadContent(); if (content.length > MAX_FILE_SIZE) { await this.fileService.writeFile(this.getBackupResource(), VSBuffer.fromString(content)); @@ -145,3 +154,33 @@ export class FileLogService extends AbstractLogService implements ILogService { return result; } } + +export class FileLoggerService extends Disposable implements ILoggerService { + + _serviceBrand: undefined; + + private readonly loggers = new Map(); + + constructor( + @ILogService private logService: ILogService, + @IInstantiationService private instantiationService: IInstantiationService, + ) { + super(); + this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level)))); + } + + getLogger(resource: URI): ILogger { + let logger = this.loggers.get(resource.toString()); + if (!logger) { + logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel()); + this.loggers.set(resource.toString(), logger); + } + return logger; + } + + dispose(): void { + this.loggers.forEach(logger => logger.dispose()); + this.loggers.clear(); + super.dispose(); + } +} diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 9ebc4e3d43fe..ea9c15b5188d 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -9,8 +9,10 @@ import { isWindows } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; +import { URI } from 'vs/base/common/uri'; export const ILogService = createServiceDecorator('logService'); +export const ILoggerService = createServiceDecorator('loggerService'); function now(): string { return new Date().toISOString(); @@ -28,12 +30,11 @@ export enum LogLevel { export const DEFAULT_LOG_LEVEL: LogLevel = LogLevel.Info; -export interface ILogService extends IDisposable { - _serviceBrand: undefined; +export interface ILogger extends IDisposable { onDidChangeLogLevel: Event; - getLevel(): LogLevel; setLevel(level: LogLevel): void; + trace(message: string, ...args: any[]): void; debug(message: string, ...args: any[]): void; info(message: string, ...args: any[]): void; @@ -42,6 +43,16 @@ export interface ILogService extends IDisposable { critical(message: string | Error, ...args: any[]): void; } +export interface ILogService extends ILogger { + _serviceBrand: undefined; +} + +export interface ILoggerService { + _serviceBrand: undefined; + + getLogger(file: URI): ILogger; +} + export abstract class AbstractLogService extends Disposable { private level: LogLevel = DEFAULT_LOG_LEVEL; diff --git a/src/vs/platform/log/node/loggerService.ts b/src/vs/platform/log/node/loggerService.ts new file mode 100644 index 000000000000..778ac4294bdd --- /dev/null +++ b/src/vs/platform/log/node/loggerService.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ILogService, ILoggerService, ILogger } from 'vs/platform/log/common/log'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { basename, extname, dirname } from 'vs/base/common/resources'; +import { Schemas } from 'vs/base/common/network'; +import { FileLogService } from 'vs/platform/log/common/fileLogService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { SpdLogService } from 'vs/platform/log/node/spdlogService'; + +export class LoggerService extends Disposable implements ILoggerService { + + _serviceBrand: undefined; + + private readonly loggers = new Map(); + + constructor( + @ILogService private logService: ILogService, + @IInstantiationService private instantiationService: IInstantiationService, + ) { + super(); + this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level)))); + } + + getLogger(resource: URI): ILogger { + let logger = this.loggers.get(resource.toString()); + if (!logger) { + if (resource.scheme === Schemas.file) { + const baseName = basename(resource); + const ext = extname(resource); + logger = new SpdLogService(baseName.substring(0, baseName.length - ext.length), dirname(resource).path, this.logService.getLevel()); + } else { + logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel()); + } + this.loggers.set(resource.toString(), logger); + } + return logger; + } + + dispose(): void { + this.loggers.forEach(logger => logger.dispose()); + this.loggers.clear(); + super.dispose(); + } +} + diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index e64d46a5d2e7..4b957311e1d9 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -30,16 +30,19 @@ interface ILog { message: string; } -function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string): void { +function log(logger: spdlog.RotatingLogger, level: LogLevel, message: string, sync: boolean): void { switch (level) { - case LogLevel.Trace: return logger.trace(message); - case LogLevel.Debug: return logger.debug(message); - case LogLevel.Info: return logger.info(message); - case LogLevel.Warning: return logger.warn(message); - case LogLevel.Error: return logger.error(message); - case LogLevel.Critical: return logger.critical(message); + case LogLevel.Trace: logger.trace(message); break; + case LogLevel.Debug: logger.debug(message); break; + case LogLevel.Info: logger.info(message); break; + case LogLevel.Warning: logger.warn(message); break; + case LogLevel.Error: logger.error(message); break; + case LogLevel.Critical: logger.critical(message); break; default: throw new Error('Invalid log level'); } + if (sync) { + logger.flush(); + } } export class SpdLogService extends AbstractLogService implements ILogService { @@ -50,7 +53,7 @@ export class SpdLogService extends AbstractLogService implements ILogService { private _loggerCreationPromise: Promise | undefined = undefined; private _logger: spdlog.RotatingLogger | undefined; - constructor(private readonly name: string, private readonly logsFolder: string, level: LogLevel) { + constructor(private readonly name: string, private readonly logsFolder: string, level: LogLevel, private readonly sync: boolean = false) { super(); this.setLevel(level); this._createSpdLogLogger(); @@ -69,7 +72,7 @@ export class SpdLogService extends AbstractLogService implements ILogService { this._logger = logger; this._logger.setLevel(this.getLevel()); for (const { level, message } of this.buffer) { - log(this._logger, level, message); + log(this._logger, level, message, this.sync); } this.buffer = []; } @@ -80,53 +83,52 @@ export class SpdLogService extends AbstractLogService implements ILogService { private _log(level: LogLevel, message: string): void { if (this._logger) { - log(this._logger, level, message); + log(this._logger, level, message, this.sync); } else if (this.getLevel() <= level) { this.buffer.push({ level, message }); } } - trace(): void { + trace(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Trace) { - this._log(LogLevel.Trace, this.format(arguments)); + this._log(LogLevel.Trace, this.format([message, ...args])); } } - debug(): void { + debug(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Debug) { - this._log(LogLevel.Debug, this.format(arguments)); + this._log(LogLevel.Debug, this.format([message, ...args])); } } - info(): void { + info(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Info) { - this._log(LogLevel.Info, this.format(arguments)); + this._log(LogLevel.Info, this.format([message, ...args])); } } - warn(): void { + warn(message: string, ...args: any[]): void { if (this.getLevel() <= LogLevel.Warning) { - this._log(LogLevel.Warning, this.format(arguments)); + this._log(LogLevel.Warning, this.format([message, ...args])); } } - error(): void { + error(message: string | Error, ...args: any[]): void { if (this.getLevel() <= LogLevel.Error) { - const arg = arguments[0]; - if (arg instanceof Error) { + if (message instanceof Error) { const array = Array.prototype.slice.call(arguments) as any[]; - array[0] = arg.stack; + array[0] = message.stack; this._log(LogLevel.Error, this.format(array)); } else { - this._log(LogLevel.Error, this.format(arguments)); + this._log(LogLevel.Error, this.format([message, ...args])); } } } - critical(): void { + critical(message: string | Error, ...args: any[]): void { if (this.getLevel() <= LogLevel.Critical) { - this._log(LogLevel.Critical, this.format(arguments)); + this._log(LogLevel.Critical, this.format([message, ...args])); } } @@ -163,4 +165,4 @@ export class SpdLogService extends AbstractLogService implements ILogService { return result; } -} \ No newline at end of file +} diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index 6afd9f0cf961..9bb9e6f115ea 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -123,8 +123,8 @@ export class MarkerService implements IMarkerService { _serviceBrand: undefined; - private readonly _onMarkerChanged = new Emitter(); - private _onMarkerChangedEvent: Event = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0); + private readonly _onMarkerChanged = new Emitter(); + private _onMarkerChangedEvent: Event = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0); private _byResource: MapMap = Object.create(null); private _byOwner: MapMap = Object.create(null); private _stats: MarkerStats; @@ -137,7 +137,7 @@ export class MarkerService implements IMarkerService { this._stats.dispose(); } - get onMarkerChanged(): Event { + get onMarkerChanged(): Event { return this._onMarkerChangedEvent; } diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 117c7edbe8bd..2d505c2c2d37 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -22,7 +22,7 @@ export interface IMarkerService { read(filter?: { owner?: string; resource?: URI; severities?: number, take?: number; }): IMarker[]; - onMarkerChanged: Event; + readonly onMarkerChanged: Event; } /** diff --git a/src/vs/platform/menubar/electron-browser/menubarService.ts b/src/vs/platform/menubar/electron-browser/menubarService.ts index b3b08b4be39c..68185934c5be 100644 --- a/src/vs/platform/menubar/electron-browser/menubarService.ts +++ b/src/vs/platform/menubar/electron-browser/menubarService.ts @@ -5,13 +5,13 @@ import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; +import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; export class MenubarService { _serviceBrand: undefined; constructor(@IMainProcessService mainProcessService: IMainProcessService) { - return createSimpleChannelProxy(mainProcessService.getChannel('menubar')); + return createChannelSender(mainProcessService.getChannel('menubar')); } } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 6c7235a9b55f..676d8304de0b 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { isMacintosh, language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron'; -import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest, IWindowOpenable } from 'vs/platform/windows/common/windows'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; @@ -16,10 +16,10 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { mnemonicMenuLabel as baseMnemonicLabel } from 'vs/base/common/labels'; import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; +import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService'; import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/node/menubar'; import { URI } from 'vs/base/common/uri'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; @@ -66,7 +66,7 @@ export class Menubar { @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IHistoryMainService private readonly historyMainService: IHistoryMainService, + @IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService, @IStateService private readonly stateService: IStateService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService @@ -115,7 +115,7 @@ export class Menubar { this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); // Recent Menu Items - this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.historyMainService.clearRecentlyOpened(); + this.fallbackMenuHandlers['workbench.action.clearRecentFiles'] = () => this.workspacesHistoryMainService.clearRecentlyOpened(); // Help Menu Items const twitterUrl = product.twitterUrl; @@ -480,7 +480,7 @@ export class Menubar { private createOpenRecentMenuItem(uri: URI, label: string, commandId: string): MenuItem { const revivedUri = URI.revive(uri); - const uriToOpen: IURIToOpen = + const openable: IWindowOpenable = (commandId === 'openRecentFile') ? { fileUri: revivedUri } : (commandId === 'openRecentWorkspace') ? { workspaceUri: revivedUri } : { folderUri: revivedUri }; @@ -491,13 +491,13 @@ export class Menubar { const success = this.windowsMainService.open({ context: OpenContext.MENU, cli: this.environmentService.args, - urisToOpen: [uriToOpen], + urisToOpen: [openable], forceNewWindow: openInNewWindow, gotoLineMode: false }).length > 0; if (!success) { - this.historyMainService.removeFromRecentlyOpened([revivedUri]); + this.workspacesHistoryMainService.removeFromRecentlyOpened([revivedUri]); } } }, false)); diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index c9b757d10e0b..92880756ab13 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -24,8 +24,8 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF private readonly session: string = generateUuid(); - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChange.event; + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChange.event; private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); readonly onDidErrorOccur: Event = this._onDidWatchErrorOccur.event; diff --git a/src/vs/platform/state/common/state.ts b/src/vs/platform/state/node/state.ts similarity index 100% rename from src/vs/platform/state/common/state.ts rename to src/vs/platform/state/node/state.ts diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index 66e6261d17fd..3f9094e0b9ff 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { writeFileSync, readFile } from 'vs/base/node/pfs'; import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { ILogService } from 'vs/platform/log/common/log'; type StorageDatebase = { [key: string]: any; }; diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index 6f35793e1ee2..589ba3639784 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -16,7 +16,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { onUnexpectedError } from 'vs/base/common/errors'; -export class StorageService extends Disposable implements IStorageService { +export class NativeStorageService extends Disposable implements IStorageService { _serviceBrand: undefined; @@ -83,7 +83,7 @@ export class StorageService extends Disposable implements IStorageService { // Create workspace storage and initialize mark('willInitWorkspaceStorage'); try { - await this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, StorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init(); + await this.createWorkspaceStorage(useInMemoryStorage ? SQLiteStorageDatabase.IN_MEMORY_PATH : join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME), result.wasCreated ? StorageHint.STORAGE_DOES_NOT_EXIST : undefined).init(); } finally { mark('didInitWorkspaceStorage'); } @@ -144,7 +144,7 @@ export class StorageService extends Disposable implements IStorageService { } if (meta) { - const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), StorageService.WORKSPACE_META_NAME); + const workspaceStorageMetaPath = join(this.getWorkspaceStorageFolderPath(payload), NativeStorageService.WORKSPACE_META_NAME); (async function () { try { const storageExists = await exists(workspaceStorageMetaPath); @@ -220,7 +220,7 @@ export class StorageService extends Disposable implements IStorageService { // Prepare new workspace storage folder const result = await this.prepareWorkspaceStorageFolder(toWorkspace); - const newWorkspaceStoragePath = join(result.path, StorageService.WORKSPACE_STORAGE_NAME); + const newWorkspaceStoragePath = join(result.path, NativeStorageService.WORKSPACE_STORAGE_NAME); // Copy current storage over to new workspace storage await copy(this.workspaceStoragePath, newWorkspaceStoragePath); diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index befc8d53d8cd..f49e34a43078 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -5,7 +5,7 @@ import { strictEqual, ok, equal } from 'assert'; import { StorageScope, InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { StorageService } from 'vs/platform/storage/node/storageService'; +import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { generateUuid } from 'vs/base/common/uuid'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; @@ -101,7 +101,7 @@ suite('StorageService', () => { const storageDir = uniqueStorageDir(); await mkdirp(storageDir); - const storage = new StorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(storageDir, storageDir)); + const storage = new NativeStorageService(new InMemoryStorageDatabase(), new NullLogService(), new StorageTestEnvironmentService(storageDir, storageDir)); await storage.initialize({ id: String(Date.now()) }); storage.store('bar', 'foo', StorageScope.WORKSPACE); diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/node/commonProperties.ts index e3d46f1891a3..ded37033568b 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/node/commonProperties.ts @@ -7,10 +7,20 @@ import * as Platform from 'vs/base/common/platform'; import * as os from 'os'; import * as uuid from 'vs/base/common/uuid'; import { readFile } from 'vs/base/node/pfs'; +import { mixin } from 'vs/base/common/objects'; import product from 'vs/platform/product/common/product'; // {{SQL CARBON EDIT}} const productObject = product; // {{SQL CARBON EDIT}} -export async function resolveCommonProperties(commit: string | undefined, version: string | undefined, machineId: string | undefined, msftInternalDomains: string[] | undefined, installSourcePath: string, product?: string): Promise<{ [name: string]: string | boolean | undefined; }> { + +export async function resolveCommonProperties( + commit: string | undefined, + version: string | undefined, + machineId: string | undefined, + msftInternalDomains: string[] | undefined, + installSourcePath: string, + product?: string, + resolveAdditionalProperties?: () => { [key: string]: any } +): Promise<{ [name: string]: string | boolean | undefined; }> { const result: { [name: string]: string | boolean | undefined; } = Object.create(null); // {{SQL CARBON EDIT}} start if (productObject.quality !== 'stable') { @@ -85,6 +95,10 @@ export async function resolveCommonProperties(commit: string | undefined, versio // ignore error } + if (resolveAdditionalProperties) { + mixin(result, resolveAdditionalProperties()); + } + return result; } diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index 975f81423866..12b090e76a60 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -10,8 +10,17 @@ import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtil import product from 'vs/platform/product/common/product'; // {{ SQL CARBON EDIT }} -export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, msftInternalDomains: string[] | undefined, installSourcePath: string, remoteAuthority?: string): Promise<{ [name: string]: string | boolean | undefined }> { - const result = await resolveCommonProperties(commit, version, machineId, msftInternalDomains, installSourcePath); +export async function resolveWorkbenchCommonProperties( + storageService: IStorageService, + commit: string | undefined, + version: string | undefined, + machineId: string, + msftInternalDomains: string[] | undefined, + installSourcePath: string, + remoteAuthority?: string, + resolveAdditionalProperties?: () => { [key: string]: any } +): Promise<{ [name: string]: string | boolean | undefined }> { + const result = await resolveCommonProperties(commit, version, machineId, msftInternalDomains, installSourcePath, undefined, resolveAdditionalProperties); const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; diff --git a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts index 8a564e9e0833..1ab60a73d0ed 100644 --- a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts @@ -81,4 +81,52 @@ suite('Telemetry - common properties', function () { value2 = props['common.timesincesessionstart']; assert.ok(value1 !== value2, 'timesincesessionstart'); }); + + test('mixes in additional properties', async function () { + const resolveCommonTelemetryProperties = () => { + return { + 'userId': '1' + }; + }; + + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, installSource, undefined, resolveCommonTelemetryProperties); + + assert.ok('commitHash' in props); + assert.ok('sessionID' in props); + assert.ok('timestamp' in props); + assert.ok('common.platform' in props); + assert.ok('common.nodePlatform' in props); + assert.ok('common.nodeArch' in props); + assert.ok('common.timesincesessionstart' in props); + assert.ok('common.sequence' in props); + assert.ok('common.platformVersion' in props, 'platformVersion'); + assert.ok('version' in props); + assert.ok('common.firstSessionDate' in props, 'firstSessionDate'); + assert.ok('common.lastSessionDate' in props, 'lastSessionDate'); + assert.ok('common.isNewSession' in props, 'isNewSession'); + assert.ok('common.instanceId' in props, 'instanceId'); + assert.ok('common.machineId' in props, 'machineId'); + + assert.equal(props['userId'], '1'); + }); + + test('mixes in additional dyanmic properties', async function () { + let i = 1; + const resolveCommonTelemetryProperties = () => { + return Object.defineProperties({}, { + 'userId': { + get: () => { + return i++; + }, + enumerable: true + } + }); + }; + + const props = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, installSource, undefined, resolveCommonTelemetryProperties); + assert.equal(props['userId'], '1'); + + const props2 = await resolveWorkbenchCommonProperties(testStorageService, commit, version, 'someMachineId', undefined, installSource, undefined, resolveCommonTelemetryProperties); + assert.equal(props2['userId'], '2'); + }); }); diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index cbd219670366..49a2b2c72812 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -5,7 +5,7 @@ import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { systemPreferences, ipcMain as ipc } from 'electron'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; const DEFAULT_BG_LIGHT = '#FFFFFF'; diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 423ab336d9c3..f796c4de3a51 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; -import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; @@ -47,7 +46,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser @IFileService private readonly fileService: IFileService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @ILogService private readonly logService: ILogService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { @@ -70,11 +69,13 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } async sync(): Promise { - if (!this.configurationService.getValue('userConfiguration.syncExtensions')) { + if (!this.configurationService.getValue('configurationSync.enableExtensions')) { + this.logService.trace('Extensions: Skipping synchronising extensions as it is disabled.'); return false; } if (this.status !== SyncStatus.Idle) { + this.logService.trace('Extensions: Skipping synchronising extensions as it is running already.'); return false; } @@ -86,30 +87,30 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, - this.logService.info('Failed to Synchronise extensions as there is a new remote version available. Synchronising again...'); + this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronising again...'); return this.sync(); } throw e; } + this.logService.trace('Extensions: Finised synchronising extensions.'); this.setStatus(SyncStatus.Idle); return true; } - async getRemoteExtensions(): Promise { - const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); - return remoteData.content ? JSON.parse(remoteData.content) : []; - } + stop(): void { } removeExtension(identifier: IExtensionIdentifier): Promise { return this.replaceQueue.queue(async () => { const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : []; - const removedExtensions = remoteExtensions.filter(e => areSameExtensions(e.identifier, identifier)); + const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; + const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier)); if (removedExtensions.length) { for (const removedExtension of removedExtensions) { remoteExtensions.splice(remoteExtensions.indexOf(removedExtension), 1); } + this.logService.info(`Extensions: Removing extension '${identifier.id}' from remote.`); await this.writeToRemote(remoteExtensions, remoteData.ref); } }); @@ -123,18 +124,29 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : null; const localExtensions = await this.getLocalExtensions(); + this.logService.trace('Extensions: Merging remote extensions with local extensions...'); const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions); - // update local - await this.updateLocalExtensions(added, removed, updated); + if (!added.length && !removed.length && !updated.length && !remote) { + this.logService.trace('Extensions: No changes found during synchronising extensions.'); + } + + if (added.length || removed.length || updated.length) { + this.logService.info('Extensions: Updating local extensions...'); + await this.updateLocalExtensions(added, removed, updated); + } if (remote) { // update remote + this.logService.info('Extensions: Updating remote extensions...'); remoteData = await this.writeToRemote(remote, remoteData.ref); } - // update last sync - await this.updateLastSyncValue(remoteData); + if (remoteData.content) { + // update last sync + this.logService.info('Extensions: Updating last synchronised extensions...'); + await this.updateLastSyncValue(remoteData); + } } /** @@ -144,10 +156,11 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser * - Update remote with those local extension which are newly added or updated or removed and untouched in remote. */ private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { - + const ignoredExtensions = this.configurationService.getValue('configurationSync.extensionsToIgnore') || []; // First time sync if (!remoteExtensions) { - return { added: [], removed: [], updated: [], remote: localExtensions }; + this.logService.info('Extensions: Remote extensions does not exist. Synchronising extensions for the first time.'); + return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.some(id => id.toLowerCase() === identifier.id.toLowerCase())) }; } const uuids: Map = new Map(); @@ -168,8 +181,12 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map()) : null; + const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => { + const uuid = uuids.get(id.toLowerCase()); + return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`); + }, new Set()); - const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap); + const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet); if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { // No changes found between local and remote. return { added: [], removed: [], updated: [], remote: null }; @@ -179,8 +196,8 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const removed: IExtensionIdentifier[] = []; const updated: ISyncExtension[] = []; - const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => { return { @@ -256,14 +273,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } } - const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap); + const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set()); const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null; return { added, removed, updated, remote }; } - private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { - const fromKeys = keys(from); - const toKeys = keys(to); + private compare(from: Map, to: Map, ignoredExtensions: Set): { added: Set, removed: Set, updated: Set } { + const fromKeys = keys(from).filter(key => !ignoredExtensions.has(key)); + const toKeys = keys(to).filter(key => !ignoredExtensions.has(key)); const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); const updated: Set = new Set(); @@ -289,13 +306,17 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser if (removed.length) { const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); - await Promise.all(extensionsToRemove.map(e => this.extensionManagementService.uninstall(e))); + await Promise.all(extensionsToRemove.map(e => { + this.logService.info('Extensions: Removing local extension.', e.identifier.id); + return this.extensionManagementService.uninstall(e); + })); } if (added.length || updated.length) { await Promise.all([...added, ...updated].map(async e => { const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version); if (extension) { + this.logService.info('Extensions: Installing local extension.', e.identifier.id, extension.version); await this.extensionManagementService.installFromGallery(extension); } })); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 41b638d52acb..0598b8321060 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -5,18 +5,18 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { ILogService } from 'vs/platform/log/common/log'; import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; -import { IStringDictionary } from 'vs/base/common/collections'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { startsWith } from 'vs/base/common/strings'; +import { CancellationToken } from 'vs/base/common/cancellation'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -48,7 +48,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService, - @ILogService private readonly logService: ILogService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); @@ -80,20 +80,28 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } async sync(_continue?: boolean): Promise { + if (!this.configurationService.getValue('configurationSync.enableSettings')) { + this.logService.trace('Settings: Skipping synchronising settings as it is disabled.'); + return false; + } if (_continue) { + this.logService.info('Settings: Resumed synchronising settings'); return this.continueSync(); } if (this.status !== SyncStatus.Idle) { + this.logService.trace('Settings: Skipping synchronising settings as it is running already.'); return false; } + this.logService.trace('Settings: Started synchronising settings...'); this.setStatus(SyncStatus.Syncing); try { const result = await this.getPreview(); if (result.hasConflicts) { + this.logService.info('Settings: Detected conflicts while synchronising settings.'); this.setStatus(SyncStatus.HasConflicts); return false; } @@ -104,18 +112,28 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, - this.logService.info('Failed to Synchronise settings as there is a new remote version available. Synchronising again...'); + this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronising again...'); return this.sync(); } if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { // Rejected as there is a new local version. Syncing again. - this.logService.info('Failed to Synchronise settings as there is a new local version available. Synchronising again...'); + this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronising again...'); return this.sync(); } throw e; } } + stop(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + this.logService.info('Settings: Stopped synchronising settings.'); + } + this.fileService.del(this.environmentService.settingsSyncPreviewResource); + this.setStatus(SyncStatus.Idle); + } + private async continueSync(): Promise { if (this.status !== SyncStatus.HasConflicts) { return false; @@ -133,19 +151,27 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource); const content = settingsPreivew.value.toString(); if (this.hasErrors(content)) { - return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); + const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); + this.logService.error(error); + return Promise.reject(error); } let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; - if (hasRemoteChanged) { - const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings()) : content; - const ref = await this.writeToRemote(remoteContent, remoteUserData.ref); - remoteUserData = { ref, content }; + if (!hasLocalChanged && !hasRemoteChanged) { + this.logService.trace('Settings: No changes found during synchronising settings.'); } if (hasLocalChanged) { + this.logService.info('Settings: Updating local settings'); await this.writeToLocal(content, fileContent); } + if (hasRemoteChanged) { + const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content)) : content; + this.logService.info('Settings: Updating remote settings'); + const ref = await this.writeToRemote(remoteContent, remoteUserData.ref); + remoteUserData = { ref, content }; + } if (remoteUserData.content) { + this.logService.info('Settings: Updating last synchronised sttings'); await this.updateLastSyncValue(remoteUserData); } @@ -153,6 +179,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { await this.fileService.del(this.environmentService.settingsSyncPreviewResource); } + this.logService.trace('Settings: Finised synchronising settings.'); this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); } @@ -165,12 +192,12 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private getPreview(): Promise { if (!this.syncPreviewResultPromise) { - this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview()); + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); } return this.syncPreviewResultPromise; } - private async generatePreview(): Promise { + private async generatePreview(token: CancellationToken): Promise { const lastSyncData = await this.getLastSyncUserData(); const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData); const remoteContent: string | null = remoteUserData.content; @@ -179,39 +206,56 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; let hasConflicts: boolean = false; + let previewContent = null; if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + if (this.hasErrors(localContent)) { + this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.'); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + if (!lastSyncData // First time sync || lastSyncData.content !== localContent // Local has moved forwarded || lastSyncData.content !== remoteContent // Remote has moved forwarded ) { - this.logService.trace('Settings Sync: Merging remote contents with settings file.'); + this.logService.trace('Settings: Merging remote settings with local settings...'); const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings()); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; hasRemoteChanged = result.mergeContent !== remoteContent; hasConflicts = result.hasConflicts; - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(result.mergeContent)); + previewContent = result.mergeContent; } } - return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } - // First time sync to remote - if (fileContent) { - this.logService.trace('Settings Sync: Remote contents does not exist. So sync with settings file.'); + // First time syncing to remote + else if (fileContent) { + this.logService.info('Settings: Remote settings does not exist. Synchronising settings for the first time.'); hasRemoteChanged = true; - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(fileContent.value.toString())); - return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + previewContent = fileContent.value.toString(); + } + + if (previewContent && !token.isCancellationRequested) { + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); } return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } - private getIgnoredSettings(): IStringDictionary { - return this.configurationService.getValue>('userConfiguration.ignoreSettings'); + private getIgnoredSettings(settingsContent?: string): string[] { + const value: string[] = (settingsContent ? parse(settingsContent)['configurationSync.settingsToIgnore'] : this.configurationService.getValue('configurationSync.settingsToIgnore')) || []; + const added: string[] = [], removed: string[] = []; + for (const key of value) { + if (startsWith(key, '-')) { + removed.push(key.substring(1)); + } else { + added.push(key); + } + } + return [...DEFAULT_IGNORED_SETTINGS, ...added].filter(setting => removed.indexOf(setting) === -1); } private async getLastSyncUserData(): Promise { diff --git a/src/vs/platform/userDataSync/common/settingsSyncIpc.ts b/src/vs/platform/userDataSync/common/settingsSyncIpc.ts index 09f1615cd94a..392eab5da13a 100644 --- a/src/vs/platform/userDataSync/common/settingsSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/settingsSyncIpc.ts @@ -6,7 +6,6 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; -import { IStringDictionary } from 'vs/base/common/collections'; export class SettingsMergeChannel implements IServerChannel { @@ -32,11 +31,11 @@ export class SettingsMergeChannelClient implements ISettingsMergeService { constructor(private readonly channel: IChannel) { } - merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { return this.channel.call('merge', [localContent, remoteContent, baseContent, ignoredSettings]); } - computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary): Promise { + computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise { return this.channel.call('computeRemoteContent', [localContent, remoteContent, ignoredSettings]); } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 31abae98e5a2..a5cb324298c5 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -7,51 +7,76 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { Event } from 'vs/base/common/event'; import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IStringDictionary } from 'vs/base/common/collections'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, allSettings } from 'vs/platform/configuration/common/configurationRegistry'; import { localize } from 'vs/nls'; - -export function registerConfiguration() { - Registry.as(ConfigurationExtensions.Configuration) - .registerConfiguration({ - id: 'userConfiguration', - order: 30, - title: localize('userConfiguration', "User Configuration"), - type: 'object', - properties: { - 'userConfiguration.enableSync': { - type: 'boolean', - description: localize('userConfiguration.enableSync', "When enabled, synchronises User Configuration: Settings, Keybindings, Extensions & Snippets."), - default: true, - scope: ConfigurationScope.APPLICATION - }, - 'userConfiguration.syncExtensions': { - type: 'boolean', - description: localize('userConfiguration.syncExtensions', "When enabled extensions are synchronised while synchronising user configuration."), - default: true, - scope: ConfigurationScope.APPLICATION, - }, - 'userConfiguration.ignoreSettings': { - 'type': 'object', - description: localize('userConfiguration.ignoreSettings', "Configure settings to be ignored while syncing"), - 'default': { - 'userConfiguration.enableSync': true, - 'userConfiguration.syncExtensions': true, - 'userConfiguration.ignoreSettings': true - }, - 'scope': ConfigurationScope.APPLICATION, - 'additionalProperties': { - 'anyOf': [ - { - 'type': 'boolean', - 'description': localize('ignoredSetting', "Id of the stting to be ignored. Set to true or false to enable or disable."), - } - ] - } - } +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const DEFAULT_IGNORED_SETTINGS = [ + 'configurationSync.enable', + 'configurationSync.enableSettings', + 'configurationSync.enableExtensions', +]; + +export function registerConfiguration(): IDisposable { + const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); + configurationRegistry.registerConfiguration({ + id: 'configurationSync', + order: 30, + title: localize('configurationSync', "Configuration Sync"), + type: 'object', + properties: { + 'configurationSync.enable': { + type: 'boolean', + description: localize('configurationSync.enable', "When enabled, synchronises configuration that includes Settings and Extensions."), + default: true, + scope: ConfigurationScope.APPLICATION + }, + 'configurationSync.enableSettings': { + type: 'boolean', + description: localize('configurationSync.enableSettings', "When enabled settings are synchronised while synchronising configuration."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'configurationSync.enableExtensions': { + type: 'boolean', + description: localize('configurationSync.enableExtensions', "When enabled extensions are synchronised while synchronising configuration."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'configurationSync.extensionsToIgnore': { + 'type': 'array', + description: localize('configurationSync.extensionsToIgnore', "Configure extensions to be ignored while syncing."), + 'default': [], + 'scope': ConfigurationScope.APPLICATION, + uniqueItems: true + }, + 'configurationSync.settingsToIgnore': { + 'type': 'array', + description: localize('configurationSync.settingsToIgnore', "Configure settings to be ignored while syncing. \nDefault Ignored Settings:\n\n{0}", DEFAULT_IGNORED_SETTINGS.sort().map(setting => `- ${setting}`).join('\n')), + 'default': [], + 'scope': ConfigurationScope.APPLICATION, + $ref: ignoredSettingsSchemaId, + additionalProperties: true, + uniqueItems: true + } + } + }); + const registerIgnoredSettingsSchema = () => { + const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); + const ignoredSettingsSchema: IJSONSchema = { + items: { + type: 'string', + enum: [...Object.keys(allSettings.properties).filter(setting => DEFAULT_IGNORED_SETTINGS.indexOf(setting) === -1), ...DEFAULT_IGNORED_SETTINGS.map(setting => `-${setting}`)] } - }); + }; + jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema); + }; + return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema()); } export interface IUserData { @@ -113,6 +138,7 @@ export interface ISynchroniser { readonly onDidChangeLocal: Event; sync(_continue?: boolean): Promise; + stop(): void; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); @@ -121,7 +147,6 @@ export interface IUserDataSyncService extends ISynchroniser { _serviceBrand: any; readonly conflictsSource: SyncSource | null; - getRemoteExtensions(): Promise; removeExtension(identifier: IExtensionIdentifier): Promise; } @@ -131,9 +156,15 @@ export interface ISettingsMergeService { _serviceBrand: undefined; - merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + + computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise; + +} + +export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); - computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary): Promise; +export interface IUserDataSyncLogService extends ILogService { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index 7acc2d943225..0a3e193c9502 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -24,8 +24,8 @@ export class UserDataSyncChannel implements IServerChannel { case 'sync': return this.service.sync(args[0]); case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); - case 'getRemoteExtensions': return this.service.getRemoteExtensions(); case 'removeExtension': return this.service.removeExtension(args[0]); + case 'stop': this.service.stop(); return Promise.resolve(); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncLog.ts b/src/vs/platform/userDataSync/common/userDataSyncLog.ts new file mode 100644 index 000000000000..6c4f490fa057 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncLog.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; + +export class UserDataSyncLogService extends AbstractLogService implements IUserDataSyncLogService { + + _serviceBrand: undefined; + private readonly logger: ILogger; + + constructor( + @ILoggerService loggerService: ILoggerService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + super(); + this.logger = this._register(loggerService.getLogger(environmentService.userDataSyncLogResource)); + } + + trace(message: string, ...args: any[]): void { + this.logger.trace(message, ...args); + } + + debug(message: string, ...args: any[]): void { + this.logger.debug(message, ...args); + } + + info(message: string, ...args: any[]): void { + this.logger.info(message, ...args); + } + + warn(message: string, ...args: any[]): void { + this.logger.warn(message, ...args); + } + + error(message: string | Error, ...args: any[]): void { + this.logger.error(message, ...args); + } + + critical(message: string | Error, ...args: any[]): void { + this.logger.critical(message, ...args); + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 62f97426e817..ea075a410e2b 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; @@ -57,8 +57,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return true; } - getRemoteExtensions(): Promise { - return this.extensionsSynchroniser.getRemoteExtensions(); + stop(): void { + if (!this.userDataSyncStoreService.enabled) { + throw new Error('Not enabled'); + } + for (const synchroniser of this.synchronisers) { + synchroniser.stop(); + } } removeExtension(identifier: IExtensionIdentifier): Promise { @@ -108,11 +113,20 @@ export class UserDataAutoSync extends Disposable { @IConfigurationService private readonly configurationService: IConfigurationService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncLogService private readonly userDataSyncLogService: IUserDataSyncLogService, ) { super(); if (userDataSyncStoreService.enabled) { this.sync(true); - this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('userConfiguration.enableSync'))(() => this.sync(true))); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('configurationSync.enable'))(() => { + if (this.isSyncEnabled()) { + userDataSyncLogService.info('Syncing configuration started...'); + this.sync(true); + } else { + this.userDataSyncService.stop(); + userDataSyncLogService.info('Syncing configuration stopped.'); + } + })); // Sync immediately if there is a local change. this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false))); @@ -124,7 +138,7 @@ export class UserDataAutoSync extends Disposable { try { await this.userDataSyncService.sync(); } catch (e) { - // Ignore errors + this.userDataSyncLogService.error(e); } if (loop) { await timeout(1000 * 5); // Loop sync for every 5s. @@ -134,7 +148,7 @@ export class UserDataAutoSync extends Disposable { } private isSyncEnabled(): boolean { - return this.configurationService.getValue('userConfiguration.enableSync'); + return this.configurationService.getValue('configurationSync.enable'); } } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 3aa588f615ac..91577a5fcac5 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -3,101 +3,23 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; -import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IProcessEnvironment, isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; -import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -export const IWindowsService = createDecorator('windowsService'); - -export interface INativeOpenDialogOptions { - forceNewWindow?: boolean; - - defaultPath?: string; - - telemetryEventName?: string; - telemetryExtraData?: ITelemetryData; -} - -export interface IEnterWorkspaceResult { - workspace: IWorkspaceIdentifier; - backupPath?: string; -} - -export interface OpenDialogOptions { - title?: string; - defaultPath?: string; - buttonLabel?: string; - filters?: FileFilter[]; - properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>; - message?: string; -} - -export interface FileFilter { - extensions: string[]; - name: string; -} - -export interface MessageBoxOptions { - type?: string; - buttons?: string[]; - defaultId?: number; - title?: string; - message: string; - detail?: string; - checkboxLabel?: string; - checkboxChecked?: boolean; - cancelId?: number; - noLink?: boolean; - normalizeAccessKeys?: boolean; -} - -export interface SaveDialogOptions { - title?: string; - defaultPath?: string; - buttonLabel?: string; - filters?: FileFilter[]; - message?: string; - nameFieldLabel?: string; - showsTagField?: boolean; -} - -export interface IWindowsService { - - _serviceBrand: undefined; - - readonly onWindowOpen: Event; - readonly onWindowFocus: Event; - readonly onWindowBlur: Event; - readonly onWindowMaximize: Event; - readonly onWindowUnmaximize: Event; - readonly onRecentlyOpenedChange: Event; - - addRecentlyOpened(recents: IRecent[]): Promise; - removeFromRecentlyOpened(paths: URI[]): Promise; - clearRecentlyOpened(): Promise; - getRecentlyOpened(windowId: number): Promise; - focusWindow(windowId: number): Promise; - isFocused(windowId: number): Promise; - - // Global methods - openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise; - getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; - getActiveWindowId(): Promise; +export interface IOpenedWindow { + id: number; + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + title: string; + filename?: string; } -export const IWindowService = createDecorator('windowService'); - -export interface IOpenSettings { +export interface IOpenInWindowOptions { forceNewWindow?: boolean; forceReuseWindow?: boolean; diffMode?: boolean; @@ -105,59 +27,44 @@ export interface IOpenSettings { gotoLineMode?: boolean; noRecentEntry?: boolean; waitMarkerFileURI?: URI; - args?: ParsedArgs; } -export type IURIToOpen = IWorkspaceToOpen | IFolderToOpen | IFileToOpen; - -export interface IWorkspaceToOpen { - workspaceUri: URI; - label?: string; +export interface IOpenEmptyWindowOptions { + reuse?: boolean; + remoteAuthority?: string; } -export interface IFolderToOpen { - folderUri: URI; - label?: string; -} +export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen; -export interface IFileToOpen { - fileUri: URI; +export interface IBaseWindowOpenable { label?: string; } -export function isWorkspaceToOpen(uriToOpen: IURIToOpen): uriToOpen is IWorkspaceToOpen { - return !!(uriToOpen as IWorkspaceToOpen)['workspaceUri']; +export interface IWorkspaceToOpen extends IBaseWindowOpenable { + workspaceUri: URI; } -export function isFolderToOpen(uriToOpen: IURIToOpen): uriToOpen is IFolderToOpen { - return !!(uriToOpen as IFolderToOpen)['folderUri']; +export interface IFolderToOpen extends IBaseWindowOpenable { + folderUri: URI; } -export function isFileToOpen(uriToOpen: IURIToOpen): uriToOpen is IFileToOpen { - return !!(uriToOpen as IFileToOpen)['fileUri']; +export interface IFileToOpen extends IBaseWindowOpenable { + fileUri: URI; } +export function isWorkspaceToOpen(uriToOpen: IWindowOpenable): uriToOpen is IWorkspaceToOpen { + return !!(uriToOpen as IWorkspaceToOpen).workspaceUri; +} -export interface IWindowService { - - _serviceBrand: undefined; - - readonly onDidChangeFocus: Event; - readonly onDidChangeMaximize: Event; - - readonly hasFocus: boolean; - - readonly windowId: number; +export function isFolderToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFolderToOpen { + return !!(uriToOpen as IFolderToOpen).folderUri; +} - getRecentlyOpened(): Promise; - addRecentlyOpened(recents: IRecent[]): Promise; - removeFromRecentlyOpened(paths: URI[]): Promise; - focusWindow(): Promise; - openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise; - isFocused(): Promise; +export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOpen { + return !!(uriToOpen as IFileToOpen).fileUri; } -export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden'; +export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden' | 'compact'; export interface IWindowsConfiguration { window: IWindowSettings; @@ -352,37 +259,3 @@ export interface IRunActionInWindowRequest { export interface IRunKeybindingInWindowRequest { userSettingsLabel: string; } - -export class ActiveWindowManager extends Disposable { - - private readonly disposables = this._register(new DisposableStore()); - private firstActiveWindowIdPromise: CancelablePromise | undefined; - private activeWindowId: number | undefined; - - constructor(@IWindowsService windowsService: IWindowsService) { - super(); - - const onActiveWindowChange = Event.latch(Event.any(windowsService.onWindowOpen, windowsService.onWindowFocus)); - onActiveWindowChange(this.setActiveWindow, this, this.disposables); - - this.firstActiveWindowIdPromise = createCancelablePromise(_ => windowsService.getActiveWindowId()); - this.firstActiveWindowIdPromise - .then(id => this.activeWindowId = typeof this.activeWindowId === 'number' ? this.activeWindowId : id) - .finally(this.firstActiveWindowIdPromise = undefined); - } - - private setActiveWindow(windowId: number | undefined) { - if (this.firstActiveWindowIdPromise) { - this.firstActiveWindowIdPromise.cancel(); - this.firstActiveWindowIdPromise = undefined; - } - - this.activeWindowId = windowId; - } - - async getActiveClientId(): Promise { - const id = this.firstActiveWindowIdPromise ? (await this.firstActiveWindowIdPromise) : this.activeWindowId; - - return `window:${id}`; - } -} diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts deleted file mode 100644 index ce190a4a37a4..000000000000 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWindowsService, IURIToOpen, IOpenSettings, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; -import { reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { URI } from 'vs/base/common/uri'; -import { IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/common/history'; - -export class WindowsChannel implements IServerChannel { - - private readonly onWindowOpen: Event; - private readonly onWindowFocus: Event; - private readonly onWindowBlur: Event; - private readonly onWindowMaximize: Event; - private readonly onWindowUnmaximize: Event; - private readonly onRecentlyOpenedChange: Event; - - constructor(private readonly service: IWindowsService) { - this.onWindowOpen = Event.buffer(service.onWindowOpen, true); - this.onWindowFocus = Event.buffer(service.onWindowFocus, true); - this.onWindowBlur = Event.buffer(service.onWindowBlur, true); - this.onWindowMaximize = Event.buffer(service.onWindowMaximize, true); - this.onWindowUnmaximize = Event.buffer(service.onWindowUnmaximize, true); - this.onRecentlyOpenedChange = Event.buffer(service.onRecentlyOpenedChange, true); - } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onWindowOpen': return this.onWindowOpen; - case 'onWindowFocus': return this.onWindowFocus; - case 'onWindowBlur': return this.onWindowBlur; - case 'onWindowMaximize': return this.onWindowMaximize; - case 'onWindowUnmaximize': return this.onWindowUnmaximize; - case 'onRecentlyOpenedChange': return this.onRecentlyOpenedChange; - } - - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg.map((recent: IRecent) => { - if (isRecentFile(recent)) { - recent.fileUri = URI.revive(recent.fileUri); - } else if (isRecentFolder(recent)) { - recent.folderUri = URI.revive(recent.folderUri); - } else { - recent.workspace = reviveWorkspaceIdentifier(recent.workspace); - } - return recent; - })); - case 'removeFromRecentlyOpened': return this.service.removeFromRecentlyOpened(arg.map(URI.revive)); - case 'clearRecentlyOpened': return this.service.clearRecentlyOpened(); - case 'getRecentlyOpened': return this.service.getRecentlyOpened(arg); - case 'focusWindow': return this.service.focusWindow(arg); - case 'isFocused': return this.service.isFocused(arg); - case 'openWindow': { - const urisToOpen: IURIToOpen[] = arg[1]; - const options: IOpenSettings = arg[2]; - urisToOpen.forEach(r => { - if (isWorkspaceToOpen(r)) { - r.workspaceUri = URI.revive(r.workspaceUri); - } else if (isFolderToOpen(r)) { - r.folderUri = URI.revive(r.folderUri); - } else { - r.fileUri = URI.revive(r.fileUri); - } - }); - options.waitMarkerFileURI = options.waitMarkerFileURI && URI.revive(options.waitMarkerFileURI); - return this.service.openWindow(arg[0], urisToOpen, options); - } - case 'openExtensionDevelopmentHostWindow': return (this.service as any).openExtensionDevelopmentHostWindow(arg[0], arg[1]); // TODO@Isidor move - case 'getWindows': return this.service.getWindows(); - case 'getActiveWindowId': return this.service.getActiveWindowId(); - } - - throw new Error(`Call not found: ${command}`); - } -} diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts deleted file mode 100644 index f144d497d818..000000000000 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ /dev/null @@ -1,94 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWindowsService, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; -import { URI } from 'vs/base/common/uri'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { IProcessEnvironment } from 'vs/base/common/platform'; - -export class WindowsService implements IWindowsService { - - _serviceBrand: undefined; - - private channel: IChannel; - - get onWindowOpen(): Event { return this.channel.listen('onWindowOpen'); } - get onWindowFocus(): Event { return this.channel.listen('onWindowFocus'); } - get onWindowBlur(): Event { return this.channel.listen('onWindowBlur'); } - get onWindowMaximize(): Event { return this.channel.listen('onWindowMaximize'); } - get onWindowUnmaximize(): Event { return this.channel.listen('onWindowUnmaximize'); } - get onRecentlyOpenedChange(): Event { return this.channel.listen('onRecentlyOpenedChange'); } - - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('windows'); - } - - addRecentlyOpened(recent: IRecent[]): Promise { - return this.channel.call('addRecentlyOpened', recent); - } - - removeFromRecentlyOpened(paths: Array): Promise { - return this.channel.call('removeFromRecentlyOpened', paths); - } - - clearRecentlyOpened(): Promise { - return this.channel.call('clearRecentlyOpened'); - } - - async getRecentlyOpened(windowId: number): Promise { - const recentlyOpened: IRecentlyOpened = await this.channel.call('getRecentlyOpened', windowId); - recentlyOpened.workspaces.forEach(recent => isRecentWorkspace(recent) ? recent.workspace = reviveWorkspaceIdentifier(recent.workspace) : recent.folderUri = URI.revive(recent.folderUri)); - recentlyOpened.files.forEach(recent => recent.fileUri = URI.revive(recent.fileUri)); - - return recentlyOpened; - } - - focusWindow(windowId: number): Promise { - return this.channel.call('focusWindow', windowId); - } - - isFocused(windowId: number): Promise { - return this.channel.call('isFocused', windowId); - } - - openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise { - return this.channel.call('openWindow', [windowId, uris, options]); - } - - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); - } - - async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - const result = await this.channel.call<{ - id: number; - workspace?: IWorkspaceIdentifier; - folderUri?: ISingleFolderWorkspaceIdentifier; - title: string; - filename?: string; - }[]>('getWindows'); - - for (const win of result) { - if (win.folderUri) { - win.folderUri = URI.revive(win.folderUri); - } - - if (win.workspace) { - win.workspace = reviveWorkspaceIdentifier(win.workspace); - } - } - - return result; - } - - getActiveWindowId(): Promise { - return this.channel.call('getActiveWindowId'); - } -} diff --git a/src/vs/platform/windows/electron-main/legacyWindowsMainService.ts b/src/vs/platform/windows/electron-main/legacyWindowsMainService.ts deleted file mode 100644 index 704b46d9a47a..000000000000 --- a/src/vs/platform/windows/electron-main/legacyWindowsMainService.ts +++ /dev/null @@ -1,203 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { assign } from 'vs/base/common/objects'; -import { URI } from 'vs/base/common/uri'; -import { IWindowsService, OpenContext, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { app, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron'; -import { Event } from 'vs/base/common/event'; -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; -import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; -import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { Schemas } from 'vs/base/common/network'; -import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; -import { ILogService } from 'vs/platform/log/common/log'; - -// @deprecated this should eventually go away and be implemented by host & electron service -export class LegacyWindowsMainService extends Disposable implements IWindowsService, IURLHandler { - - _serviceBrand: undefined; - - private readonly disposables = this._register(new DisposableStore()); - - private _activeWindowId: number | undefined; - - readonly onWindowOpen: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-created', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowBlur: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowMaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowUnmaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowFocus: Event = Event.any( - Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w!.id), - Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) - ); - - readonly onRecentlyOpenedChange: Event = this.historyMainService.onRecentlyOpenedChange; - - constructor( - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IURLService urlService: IURLService, - @IHistoryMainService private readonly historyMainService: IHistoryMainService, - @ILogService private readonly logService: ILogService - ) { - super(); - - urlService.registerHandler(this); - - // remember last active window id - Event.latch(Event.any(this.onWindowOpen, this.onWindowFocus)) - (id => this._activeWindowId = id, null, this.disposables); - } - - async showMessageBox(windowId: number, options: MessageBoxOptions): Promise { - this.logService.trace('windowsService#showMessageBox', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.showMessageBox(options, codeWindow), () => this.windowsMainService.showMessageBox(options))!; - } - - async showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { - this.logService.trace('windowsService#showSaveDialog', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.showSaveDialog(options, codeWindow), () => this.windowsMainService.showSaveDialog(options))!; - } - - async showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { - this.logService.trace('windowsService#showOpenDialog', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.showOpenDialog(options, codeWindow), () => this.windowsMainService.showOpenDialog(options))!; - } - - async addRecentlyOpened(recents: IRecent[]): Promise { - this.logService.trace('windowsService#addRecentlyOpened'); - this.historyMainService.addRecentlyOpened(recents); - } - - async removeFromRecentlyOpened(paths: URI[]): Promise { - this.logService.trace('windowsService#removeFromRecentlyOpened'); - - this.historyMainService.removeFromRecentlyOpened(paths); - } - - async clearRecentlyOpened(): Promise { - this.logService.trace('windowsService#clearRecentlyOpened'); - - this.historyMainService.clearRecentlyOpened(); - } - - async getRecentlyOpened(windowId: number): Promise { - this.logService.trace('windowsService#getRecentlyOpened', windowId); - - return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!; - } - - async focusWindow(windowId: number): Promise { - this.logService.trace('windowsService#focusWindow', windowId); - - if (isMacintosh) { - return this.withWindow(windowId, codeWindow => codeWindow.win.show()); - } else { - return this.withWindow(windowId, codeWindow => codeWindow.win.focus()); - } - } - - async isFocused(windowId: number): Promise { - this.logService.trace('windowsService#isFocused', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.isFocused(), () => false)!; - } - - async openWindow(windowId: number, urisToOpen: IURIToOpen[], options: IOpenSettings): Promise { - this.logService.trace('windowsService#openWindow'); - if (!urisToOpen || !urisToOpen.length) { - return undefined; - } - - this.windowsMainService.open({ - context: OpenContext.API, - contextWindowId: windowId, - urisToOpen: urisToOpen, - cli: options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args, - forceNewWindow: options.forceNewWindow, - forceReuseWindow: options.forceReuseWindow, - diffMode: options.diffMode, - addMode: options.addMode, - gotoLineMode: options.gotoLineMode, - noRecentEntry: options.noRecentEntry, - waitMarkerFileURI: options.waitMarkerFileURI - }); - } - - async openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - this.logService.trace('windowsService#openExtensionDevelopmentHostWindow ' + JSON.stringify(args)); - - const extDevPaths = args.extensionDevelopmentPath; - if (extDevPaths) { - this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { - context: OpenContext.API, - cli: args, - userEnv: Object.keys(env).length > 0 ? env : undefined - }); - } - } - - async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - this.logService.trace('windowsService#getWindows'); - - const windows = this.windowsMainService.getWindows(); - - return windows.map(window => ({ - id: window.id, - workspace: window.openedWorkspace, - folderUri: window.openedFolderUri, - title: window.win.getTitle(), - filename: window.getRepresentedFilename() - })); - } - - async getWindowCount(): Promise { - this.logService.trace('windowsService#getWindowCount'); - - return this.windowsMainService.getWindows().length; - } - - async getActiveWindowId(): Promise { - return this._activeWindowId; - } - - async handleURL(uri: URI): Promise { - - // Catch file URLs - if (uri.authority === Schemas.file && !!uri.path) { - this.openFileForURI({ fileUri: URI.file(uri.fsPath) }); // using fsPath on a non-file URI... - return true; - } - - return false; - } - - private openFileForURI(uri: IURIToOpen): void { - const cli = assign(Object.create(null), this.environmentService.args); - const urisToOpen = [uri]; - - this.windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); - } - - private withWindow(windowId: number, fn: (window: ICodeWindow) => T, fallback?: () => T): T | undefined { - const codeWindow = this.windowsMainService.getWindowById(windowId); - if (codeWindow) { - return fn(codeWindow); - } - - if (fallback) { - return fallback(); - } - - return undefined; - } -} diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index f7bafd705e22..7447fa7759e4 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,12 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowConfiguration, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, Rectangle, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron'; @@ -108,7 +109,7 @@ export interface IWindowsMainService { focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow; getLastActiveWindow(): ICodeWindow | undefined; waitForWindowCloseOrLoad(windowId: number): Promise; - openEmptyWindow(context: OpenContext, options?: { reuse?: boolean, remoteAuthority?: string }): ICodeWindow[]; + openEmptyWindow(context: OpenContext, options?: IOpenEmptyWindowOptions): ICodeWindow[]; openNewTabbedWindow(context: OpenContext): ICodeWindow[]; openExternal(url: string): Promise; sendToFocused(channel: string, ...args: any[]): void; @@ -125,7 +126,7 @@ export interface IOpenConfiguration { readonly contextWindowId?: number; readonly cli: ParsedArgs; readonly userEnv?: IProcessEnvironment; - readonly urisToOpen?: IURIToOpen[]; + readonly urisToOpen?: IWindowOpenable[]; readonly waitMarkerFileURI?: URI; readonly preferNewWindow?: boolean; readonly forceNewWindow?: boolean; diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 1e5c993c8803..2714dff743d4 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -17,7 +17,6 @@ import { normalizeDriveLetter } from 'vs/base/common/labels'; import { toSlashes } from 'vs/base/common/extpath'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -import { IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; export const WORKSPACE_EXTENSION = 'code-workspace'; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; @@ -92,6 +91,11 @@ export interface IUntitledWorkspaceInfo { remoteAuthority?: string; } +export interface IEnterWorkspaceResult { + workspace: IWorkspaceIdentifier; + backupPath?: string; +} + export const IWorkspacesService = createDecorator('workspacesService'); export interface IWorkspacesService { diff --git a/src/vs/platform/history/common/history.ts b/src/vs/platform/workspaces/common/workspacesHistory.ts similarity index 100% rename from src/vs/platform/history/common/history.ts rename to src/vs/platform/workspaces/common/workspacesHistory.ts diff --git a/src/vs/platform/history/common/historyStorage.ts b/src/vs/platform/workspaces/common/workspacesHistoryStorage.ts similarity index 98% rename from src/vs/platform/history/common/historyStorage.ts rename to src/vs/platform/workspaces/common/workspacesHistoryStorage.ts index ee0980fdd376..37ccd90371f4 100644 --- a/src/vs/platform/history/common/historyStorage.ts +++ b/src/vs/platform/workspaces/common/workspacesHistoryStorage.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { UriComponents, URI } from 'vs/base/common/uri'; -import { IRecentlyOpened, isRecentFolder } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, isRecentFolder } from 'vs/platform/workspaces/common/workspacesHistory'; import { ILogService } from 'vs/platform/log/common/log'; interface ISerializedRecentlyOpened { diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts similarity index 90% rename from src/vs/platform/history/electron-main/historyMainService.ts rename to src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index ff833d726afa..a481af2c5bea 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; -import { IStateService } from 'vs/platform/state/common/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { app, JumpListCategory } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; @@ -14,21 +14,21 @@ import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; -import { IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/workspaces/common/workspacesHistory'; import { ThrottledDelayer } from 'vs/base/common/async'; import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label'; -import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/history/common/historyStorage'; +import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/workspaces/common/workspacesHistoryStorage'; import { exists } from 'vs/base/node/pfs'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -export const IHistoryMainService = createDecorator('historyMainService'); +export const IWorkspacesHistoryMainService = createDecorator('workspacesHistoryMainService'); -export interface IHistoryMainService { +export interface IWorkspacesHistoryMainService { _serviceBrand: undefined; @@ -42,7 +42,7 @@ export interface IHistoryMainService { updateWindowsJumpList(): void; } -export class HistoryMainService implements IHistoryMainService { +export class WorkspacesHistoryMainService implements IWorkspacesHistoryMainService { private static readonly MAX_TOTAL_RECENT_ENTRIES = 100; @@ -108,7 +108,7 @@ export class HistoryMainService implements IHistoryMainService { // File else { const alreadyExistsInHistory = indexOfFile(files, curr.fileUri) >= 0; - const shouldBeFiltered = curr.fileUri.scheme === Schemas.file && HistoryMainService.COMMON_FILES_FILTER.indexOf(basename(curr.fileUri)) >= 0; + const shouldBeFiltered = curr.fileUri.scheme === Schemas.file && WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(curr.fileUri)) >= 0; if (!alreadyExistsInHistory && !shouldBeFiltered) { files.push(curr); @@ -123,12 +123,12 @@ export class HistoryMainService implements IHistoryMainService { this.addEntriesFromStorage(workspaces, files); - if (workspaces.length > HistoryMainService.MAX_TOTAL_RECENT_ENTRIES) { - workspaces.length = HistoryMainService.MAX_TOTAL_RECENT_ENTRIES; + if (workspaces.length > WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES) { + workspaces.length = WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES; } - if (files.length > HistoryMainService.MAX_TOTAL_RECENT_ENTRIES) { - files.length = HistoryMainService.MAX_TOTAL_RECENT_ENTRIES; + if (files.length > WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES) { + files.length = WorkspacesHistoryMainService.MAX_TOTAL_RECENT_ENTRIES; } this.saveRecentlyOpened({ workspaces, files }); @@ -180,7 +180,7 @@ export class HistoryMainService implements IHistoryMainService { // Collect max-N recent workspaces that are known to exist const workspaceEntries: string[] = []; let entries = 0; - for (let i = 0; i < mru.workspaces.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_WORKSPACES; i++) { + for (let i = 0; i < mru.workspaces.length && entries < WorkspacesHistoryMainService.MAX_MACOS_DOCK_RECENT_WORKSPACES; i++) { const loc = location(mru.workspaces[i]); if (loc.scheme === Schemas.file) { const workspacePath = originalFSPath(loc); @@ -193,12 +193,12 @@ export class HistoryMainService implements IHistoryMainService { // Collect max-N recent files that are known to exist const fileEntries: string[] = []; - for (let i = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES_TOTAL; i++) { + for (let i = 0; i < mru.files.length && entries < WorkspacesHistoryMainService.MAX_MACOS_DOCK_RECENT_ENTRIES_TOTAL; i++) { const loc = location(mru.files[i]); if (loc.scheme === Schemas.file) { const filePath = originalFSPath(loc); if ( - HistoryMainService.COMMON_FILES_FILTER.indexOf(basename(loc)) !== -1 || // skip some well known file entries + WorkspacesHistoryMainService.COMMON_FILES_FILTER.indexOf(basename(loc)) !== -1 || // skip some well known file entries workspaceEntries.indexOf(filePath) !== -1 // prefer a workspace entry over a file entry (e.g. for .code-workspace) ) { continue; @@ -285,7 +285,7 @@ export class HistoryMainService implements IHistoryMainService { } private getRecentlyOpenedFromStorage(): IRecentlyOpened { - const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); + const storedRecents = this.stateService.getItem(WorkspacesHistoryMainService.recentlyOpenedStorageKey); return restoreRecentlyOpened(storedRecents, this.logService); } @@ -293,7 +293,7 @@ export class HistoryMainService implements IHistoryMainService { private saveRecentlyOpened(recent: IRecentlyOpened): void { const serialized = toStoreData(recent); - this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, serialized); + this.stateService.setItem(WorkspacesHistoryMainService.recentlyOpenedStorageKey, serialized); } updateWindowsJumpList(): void { diff --git a/src/vs/platform/history/test/electron-main/historyStorage.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts similarity index 98% rename from src/vs/platform/history/test/electron-main/historyStorage.test.ts rename to src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts index 8f8475c6ad83..1366b8b47755 100644 --- a/src/vs/platform/history/test/electron-main/historyStorage.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesHistoryStorage.test.ts @@ -8,8 +8,8 @@ import * as path from 'vs/base/common/path'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace } from 'vs/platform/history/common/history'; -import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; +import { IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace } from 'vs/platform/workspaces/common/workspacesHistory'; +import { toStoreData, restoreRecentlyOpened } from 'vs/platform/workspaces/common/workspacesHistoryStorage'; import { NullLogService } from 'vs/platform/log/common/log'; function toWorkspace(uri: URI): IWorkspaceIdentifier { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 6931f1fff428..39c28034ff4f 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -914,17 +914,15 @@ declare module 'vscode' { */ export class CustomExecution2 { /** + * Constructs a CustomExecution task object. The callback will be executed the task is run, at which point the + * extension should return the Pseudoterminal it will "run in". The task should wait to do further execution until + * [Pseudoterminal.open](#Pseudoterminal.open) is called. Task cancellation should be handled using + * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire + * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). * @param process The [Pseudoterminal](#Pseudoterminal) to be used by the task to display output. * @param callback The callback that will be called when the task is started by a user. */ constructor(callback: () => Thenable); - - /** - * The callback used to execute the task. Cancellation should be handled using - * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire - * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). - */ - callback: () => Thenable; } /** @@ -1087,30 +1085,34 @@ declare module 'vscode' { //#region Custom editors, mjbvz - export enum WebviewEditorState { + export enum WebviewContentState { /** - * The webview editor's content cannot be modified. + * The webview content cannot be modified. * - * This disables save + * This disables save. */ Readonly = 1, /** - * The webview editor's content has not been changed but they can be modified and saved. + * The webview content has not been changed but they can be modified and saved. */ Unchanged = 2, /** - * The webview editor's content has been changed and can be saved. + * The webview content has been changed and can be saved. */ Dirty = 3, } - export interface WebviewEditor extends WebviewPanel { - state: WebviewEditorState; + export interface WebviewEditorState { + readonly contentState: WebviewContentState; + } + + export interface WebviewPanel { + editorState: WebviewEditorState; /** - * Fired when the webview editor is saved. + * Fired when the webview is being saved. * * Both `Unchanged` and `Dirty` editors can be saved. * @@ -1119,6 +1121,11 @@ declare module 'vscode' { readonly onWillSave: Event<{ waitUntil: (thenable: Thenable) => void }>; } + export interface WebviewEditor extends WebviewPanel { + // TODO: We likely do not want `editorState` and `onWillSave` enabled for + // resource backed webviews + } + export interface WebviewEditorProvider { /** * Fills out a `WebviewEditor` for a given resource. diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index 13fadfb87ab7..9c755dbe3d1c 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -90,11 +90,11 @@ export class MainThreadEditorInsets implements MainThreadEditorInsetsShape { const webview = this._webviewService.createWebview('' + handle, { enableFindWidget: false, - extension: { id: extensionId, location: URI.revive(extensionLocation) } }, { allowScripts: options.enableScripts, localResourceRoots: options.localResourceRoots ? options.localResourceRoots.map(uri => URI.revive(uri)) : undefined }); + webview.extension = { id: extensionId, location: URI.revive(extensionLocation) }; const webviewZone = new EditorWebviewZone(editor, line, height, webview); diff --git a/src/vs/workbench/api/browser/mainThreadCommands.ts b/src/vs/workbench/api/browser/mainThreadCommands.ts index 22fca9bcafaf..760bad0eadb2 100644 --- a/src/vs/workbench/api/browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/browser/mainThreadCommands.ts @@ -58,7 +58,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { id, CommandsRegistry.registerCommand(id, (accessor, ...args) => { return this._proxy.$executeContributedCommand(id, ...args).then(result => { - return revive(result, 0); + return revive(result); }); }) ); @@ -74,7 +74,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { async $executeCommand(id: string, args: any[], retry: boolean): Promise { for (let i = 0; i < args.length; i++) { - args[i] = revive(args[i], 0); + args[i] = revive(args[i]); } if (retry && args.length > 0 && !CommandsRegistry.getCommand(id)) { await this._extensionService.activateByEvent(`onCommand:${id}`); diff --git a/src/vs/workbench/api/browser/mainThreadFileSystem.ts b/src/vs/workbench/api/browser/mainThreadFileSystem.ts index e8c67960c12a..017ce6349379 100644 --- a/src/vs/workbench/api/browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/browser/mainThreadFileSystem.ts @@ -123,10 +123,10 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { class RemoteFileSystemProvider implements IFileSystemProvider { - private readonly _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); private readonly _registration: IDisposable; - readonly onDidChangeFile: Event = this._onDidChange.event; + readonly onDidChangeFile: Event = this._onDidChange.event; readonly capabilities: FileSystemProviderCapabilities; readonly onDidChangeCapabilities: Event = Event.None; diff --git a/src/vs/workbench/api/browser/mainThreadKeytar.ts b/src/vs/workbench/api/browser/mainThreadKeytar.ts index b2d5477529e8..14e244f87c71 100644 --- a/src/vs/workbench/api/browser/mainThreadKeytar.ts +++ b/src/vs/workbench/api/browser/mainThreadKeytar.ts @@ -5,7 +5,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadKeytarShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; @extHostNamedCustomer(MainContext.MainThreadKeytar) export class MainThreadKeytar implements MainThreadKeytarShape { diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 317b6777a05f..dd873437cc9e 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { MainThreadStatusBarShape, MainContext, IExtHostContext } from '../common/extHost.protocol'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 87170849afac..38d79a784344 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -8,7 +8,6 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -151,7 +150,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews webview.setName(value); } - public $setState(handle: WebviewPanelHandle, state: modes.WebviewEditorState): void { + public $setState(handle: WebviewPanelHandle, state: modes.WebviewContentState): void { const webview = this.getWebviewEditorInput(handle); if (webview instanceof CustomFileEditorInput) { webview.setState(state); @@ -173,6 +172,11 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews webview.webview.contentOptions = reviveWebviewOptions(options as any /*todo@mat */); } + public $setExtension(handle: WebviewPanelHandle, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void { + const webview = this.getWebviewEditorInput(handle); + webview.webview.extension = { id: extensionId, location: URI.revive(extensionLocation) }; + } + public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { const webview = this.getWebviewEditorInput(handle); if (webview.isDisposed()) { @@ -250,7 +254,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return webviewEditorInput.getTypeId() !== WebviewInput.typeId && webviewEditorInput.viewType === viewType; }, resolveWebview: async (webview) => { - const handle = generateUuid(); + const handle = webview.id; this._webviewEditorInputs.add(handle, webview); this.hookupWebviewEventDelegate(handle, webview); diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 425356f9e892..0bf7ff0c7054 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -7,9 +7,9 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ExtHostContext, ExtHostWindowShape, IExtHostContext, IOpenUriOptions, MainContext, MainThreadWindowShape } from '../common/extHost.protocol'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; @extHostNamedCustomer(MainContext.MainThreadWindow) export class MainThreadWindow implements MainThreadWindowShape { @@ -20,12 +20,12 @@ export class MainThreadWindow implements MainThreadWindowShape { constructor( extHostContext: IExtHostContext, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IOpenerService private readonly openerService: IOpenerService, ) { this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostWindow); - Event.latch(windowService.onDidChangeFocus) + Event.latch(hostService.onDidChangeFocus) (this.proxy.$onDidChangeWindowFocus, this.proxy, this.disposables); } @@ -39,7 +39,7 @@ export class MainThreadWindow implements MainThreadWindowShape { } $getWindowVisibility(): Promise { - return this.windowService.isFocused(); + return Promise.resolve(this.hostService.hasFocus); } async $openUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e439baf56f8d..62a991bedee2 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -83,7 +83,7 @@ interface IUserFriendlyViewDescriptor { // From 'remoteViewDescriptor' type group?: string; - remoteAuthority?: string; + remoteName?: string | string[]; } const viewDescriptor: IJSONSchema = { @@ -123,9 +123,12 @@ const remoteViewDescriptor: IJSONSchema = { description: localize('vscode.extension.contributes.view.group', 'Nested group in the viewlet'), type: 'string' }, - remoteAuthority: { - description: localize('vscode.extension.contributes.view.remoteAuthority', 'The remote authority associated with this view'), - type: 'string' + remoteName: { + description: localize('vscode.extension.contributes.view.remoteName', 'The name of the remote type associated with this view'), + type: ['string', 'array'], + items: { + type: 'string' + } } } }; @@ -435,7 +438,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { extensionId: extension.description.identifier, originalContainerId: entry.key, group: item.group, - remoteAuthority: item.remoteAuthority + remoteAuthority: item.remoteName || (item).remoteAuthority // TODO@roblou - delete after remote extensions are updated }; viewIds.push(viewDescriptor.id); diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index 05fc93ed0218..bc9e4fdfe9b6 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -11,9 +11,10 @@ import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IOpenSettings, IURIToOpen, IWindowService } from 'vs/platform/windows/common/windows'; +import { IOpenInWindowOptions, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; -import { IRecent } from 'vs/platform/history/common/history'; +import { IRecent } from 'vs/platform/workspaces/common/workspacesHistory'; import { Schemas } from 'vs/base/common/network'; // ----------------------------------------------------------------- @@ -50,9 +51,9 @@ export class OpenFolderAPICommand { if (!uri) { return executor.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg.forceNewWindow }); } - const options: IOpenSettings = { forceNewWindow: arg.forceNewWindow, forceReuseWindow: arg.forceReuseWindow, noRecentEntry: arg.noRecentEntry }; + const options: IOpenInWindowOptions = { forceNewWindow: arg.forceNewWindow, forceReuseWindow: arg.forceReuseWindow, noRecentEntry: arg.noRecentEntry }; uri = URI.revive(uri); - const uriToOpen: IURIToOpen = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; + const uriToOpen: IWindowOpenable = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; return executor.executeCommand('_files.windowOpen', [uriToOpen], options); } } @@ -132,8 +133,8 @@ export class OpenAPICommand { CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute)); CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) { - const windowService = accessor.get(IWindowService); - return windowService.removeFromRecentlyOpened([uri]); + const workspacesHistoryService = accessor.get(IWorkspacesHistoryService); + return workspacesHistoryService.removeFromRecentlyOpened([uri]); }); export class RemoveFromRecentlyOpenedAPICommand { @@ -163,7 +164,7 @@ interface RecentEntry { } CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async function (accessor: ServicesAccessor, recentEntry: RecentEntry) { - const windowService = accessor.get(IWindowService); + const workspacesHistoryService = accessor.get(IWorkspacesHistoryService); const workspacesService = accessor.get(IWorkspacesService); let recent: IRecent | undefined = undefined; const uri = recentEntry.uri; @@ -176,12 +177,12 @@ CommandsRegistry.registerCommand('_workbench.addToRecentlyOpened', async functio } else { recent = { fileUri: uri, label }; } - return windowService.addRecentlyOpened([recent]); + return workspacesHistoryService.addRecentlyOpened([recent]); }); CommandsRegistry.registerCommand('_workbench.getRecentlyOpened', async function (accessor: ServicesAccessor) { - const windowService = accessor.get(IWindowService); - return windowService.getRecentlyOpened(); + const workspacesHistoryService = accessor.get(IWorkspacesHistoryService); + return workspacesHistoryService.getRecentlyOpened(); }); export class SetEditorLayoutAPICommand { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 289812c7e6fa..becb137094dc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -537,7 +537,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, registerWebviewEditorProvider: (viewType: string, provider: vscode.WebviewEditorProvider) => { checkProposedApiEnabled(extension); - return extHostWebviews.registerWebviewEditorProvider(viewType, provider); + return extHostWebviews.registerWebviewEditorProvider(extension, viewType, provider); }, registerDecorationProvider(provider: vscode.DecorationProvider) { checkProposedApiEnabled(extension); @@ -912,7 +912,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyItem: extHostTypes.CallHierarchyItem, DebugConsoleMode: extHostTypes.DebugConsoleMode, Decoration: extHostTypes.Decoration, - WebviewEditorState: extHostTypes.WebviewEditorState, + WebviewContentState: extHostTypes.WebviewContentState, UIKind: UIKind }; }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index ad9220438ade..0af8834891b8 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -32,7 +32,7 @@ import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import * as quickInput from 'vs/platform/quickinput/common/quickInput'; import { RemoteAuthorityResolverErrorCode, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import * as statusbar from 'vs/platform/statusbar/common/statusbar'; +import * as statusbar from 'vs/workbench/services/statusbar/common/statusbar'; import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; @@ -549,11 +549,13 @@ export interface MainThreadWebviewsShape extends IDisposable { $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; - $setState(handle: WebviewPanelHandle, state: modes.WebviewEditorState): void; + $setState(handle: WebviewPanelHandle, state: modes.WebviewContentState): void; $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void; $setHtml(handle: WebviewPanelHandle, value: string): void; $setOptions(handle: WebviewPanelHandle, options: modes.IWebviewOptions): void; + $setExtension(handle: WebviewPanelHandle, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void; + $postMessage(handle: WebviewPanelHandle, value: any): Promise; $registerSerializer(viewType: string): void; diff --git a/src/vs/workbench/api/common/extHostCommands.ts b/src/vs/workbench/api/common/extHostCommands.ts index 609b505c930c..b2d9a6905ce8 100644 --- a/src/vs/workbench/api/common/extHostCommands.ts +++ b/src/vs/workbench/api/common/extHostCommands.ts @@ -52,7 +52,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { { processArgument(a) { // URI, Regex - return revive(a, 0); + return revive(a); } }, { @@ -141,7 +141,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { try { const result = await this._proxy.$executeCommand(id, toArgs, retry); - return revive(result, 0); + return revive(result); } catch (e) { // Rerun the command when it wasn't known, had arguments, and when retry // is enabled. We do this because the command might be registered inside diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 08632864fd42..cebe59c750ec 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { StatusBarAlignment as ExtHostStatusBarAlignment, Disposable, ThemeColor } from './extHostTypes'; import { StatusBarItem, StatusBarAlignment } from 'vscode'; import { MainContext, MainThreadStatusBarShape, IMainContext } from './extHost.protocol'; diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 8adc9c3b168c..3f098b92d82b 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -373,9 +373,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected _handleCounter: number; protected _handlers: Map; protected _taskExecutions: Map; - protected _providedCustomExecutions2: Map; + protected _providedCustomExecutions2: Map; private _notProvidedCustomExecutions: Set; // Used for custom executions tasks that are created and run through executeTask. - protected _activeCustomExecutions2: Map; + protected _activeCustomExecutions2: Map; private _lastStartedTask: string | undefined; protected readonly _onDidExecuteTask: Emitter = new Emitter(); protected readonly _onDidTerminateTask: Emitter = new Emitter(); @@ -399,9 +399,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); - this._providedCustomExecutions2 = new Map(); + this._providedCustomExecutions2 = new Map(); this._notProvidedCustomExecutions = new Set(); - this._activeCustomExecutions2 = new Map(); + this._activeCustomExecutions2 = new Map(); } public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { @@ -454,7 +454,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { } public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): Promise { - const execution2: vscode.CustomExecution2 | undefined = this._providedCustomExecutions2.get(execution.id); + const execution2: types.CustomExecution2 | undefined = this._providedCustomExecutions2.get(execution.id); if (execution2) { if (this._activeCustomExecutions2.get(execution.id) !== undefined) { throw new Error('We should not be trying to start the same custom task executions twice.'); @@ -593,7 +593,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { if (!isProvided && !this._providedCustomExecutions2.has(taskId)) { this._notProvidedCustomExecutions.add(taskId); } - this._providedCustomExecutions2.set(taskId, (task).execution2); + this._providedCustomExecutions2.set(taskId, (task).execution2); } protected async getTaskExecution(execution: tasks.TaskExecutionDTO | string, task?: vscode.Task): Promise { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 9327ff409c39..faa2755c45ca 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1163,13 +1163,13 @@ export namespace LogLevel { return types.LogLevel.Info; } } -export namespace WebviewEditorState { - export function from(state: vscode.WebviewEditorState): modes.WebviewEditorState { +export namespace WebviewContentState { + export function from(state: vscode.WebviewContentState): modes.WebviewContentState { switch (state) { - case types.WebviewEditorState.Readonly: return modes.WebviewEditorState.Readonly; - case types.WebviewEditorState.Unchanged: return modes.WebviewEditorState.Unchanged; - case types.WebviewEditorState.Dirty: return modes.WebviewEditorState.Dirty; - default: throw new Error('Unknown vscode.WebviewEditorState'); + case types.WebviewContentState.Readonly: return modes.WebviewContentState.Readonly; + case types.WebviewContentState.Unchanged: return modes.WebviewContentState.Unchanged; + case types.WebviewContentState.Dirty: return modes.WebviewContentState.Dirty; + default: throw new Error('Unknown vscode.WebviewContentState'); } } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index cf1160e40e0d..902377b40c00 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2399,7 +2399,7 @@ export class Decoration { bubble?: boolean; } -export enum WebviewEditorState { +export enum WebviewContentState { Readonly = 1, Unchanged = 2, Dirty = 3, diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index a3d167b31204..9656a0e0d698 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -13,7 +13,7 @@ import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; -import { Disposable, WebviewEditorState } from './extHostTypes'; +import { Disposable, WebviewContentState } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -93,7 +93,9 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { private _viewColumn: vscode.ViewColumn | undefined; private _visible: boolean = true; private _active: boolean = true; - private _state = WebviewEditorState.Readonly; + private _state: vscode.WebviewEditorState = { + contentState: WebviewContentState.Readonly, + }; _isDisposed: boolean = false; @@ -103,7 +105,6 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { readonly _onDidChangeViewStateEmitter = new Emitter(); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; - constructor( handle: WebviewPanelHandle, proxy: MainThreadWebviewsShape, @@ -214,13 +215,13 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { this._visible = value; } - public get state(): vscode.WebviewEditorState { + public get editorState(): vscode.WebviewEditorState { return this._state; } - public set state(newState: vscode.WebviewEditorState) { + public set editorState(newState: vscode.WebviewEditorState) { this._state = newState; - this._proxy.$setState(this._handle, typeConverters.WebviewEditorState.from(newState)); + this._proxy.$setState(this._handle, typeConverters.WebviewContentState.from(newState.contentState)); } private readonly _onWillSave = new Emitter<{ waitUntil: (thenable: Thenable) => void }>(); @@ -264,7 +265,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private readonly _proxy: MainThreadWebviewsShape; private readonly _webviewPanels = new Map(); private readonly _serializers = new Map(); - private readonly _editorProviders = new Map(); + private readonly _editorProviders = new Map(); constructor( mainContext: IMainContext, @@ -313,14 +314,15 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } public registerWebviewEditorProvider( + extension: IExtensionDescription, viewType: string, - provider: vscode.WebviewEditorProvider + provider: vscode.WebviewEditorProvider, ): vscode.Disposable { if (this._editorProviders.has(viewType)) { throw new Error(`Editor provider for '${viewType}' already registered`); } - this._editorProviders.set(viewType, provider); + this._editorProviders.set(viewType, { extension, provider, }); this._proxy.$registerEditorProvider(viewType); return new Disposable(() => { @@ -415,21 +417,24 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { async $resolveWebviewEditor( resource: UriComponents, - webviewHandle: WebviewPanelHandle, + handle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions ): Promise { - const provider = this._editorProviders.get(viewType); - if (!provider) { + const entry = this._editorProviders.get(viewType); + if (!entry) { return Promise.reject(new Error(`No provider found for '${viewType}'`)); } + const { provider, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData); - const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); - this._webviewPanels.set(webviewHandle, revivedPanel); + this._proxy.$setExtension(handle, extension.identifier, extension.extensionLocation); + + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData); + const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); + this._webviewPanels.set(handle, revivedPanel); return Promise.resolve(provider.resolveWebviewEditor(URI.revive(resource), revivedPanel)); } diff --git a/src/vs/workbench/api/common/shared/webview.ts b/src/vs/workbench/api/common/shared/webview.ts index 9837cdfc15f4..483ada34549b 100644 --- a/src/vs/workbench/api/common/shared/webview.ts +++ b/src/vs/workbench/api/common/shared/webview.ts @@ -17,7 +17,13 @@ export function asWebviewUri( resource: vscode.Uri, ): vscode.Uri { const uri = initData.webviewResourceRoot - .replace('{{resource}}', resource.toString().replace(/^\S+?:/, '')) + // Make sure we preserve the scheme of the resource but convert it into a normal path segment + // The scheme is important as we need to know if we are requesting a local or a remote resource. + .replace('{{resource}}', resource.scheme + withoutScheme(resource)) .replace('{{uuid}}', uuid); return URI.parse(uri); } + +function withoutScheme(resource: vscode.Uri): string { + return resource.toString().replace(/^\S+?:/, ''); +} diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index e8ebbd307b18..d6d6c86972ba 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -7,7 +7,7 @@ import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net'; import * as http from 'http'; import * as fs from 'fs'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; -import { IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable, IOpenInWindowOptions } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; @@ -95,7 +95,7 @@ export class CLIServer { private open(data: OpenCommandPipeArgs, res: http.ServerResponse) { let { fileURIs, folderURIs, forceNewWindow, diffMode, addMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath } = data; - const urisToOpen: IURIToOpen[] = []; + const urisToOpen: IWindowOpenable[] = []; if (Array.isArray(folderURIs)) { for (const s of folderURIs) { try { @@ -126,7 +126,7 @@ export class CLIServer { } if (urisToOpen.length) { const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined; - const windowOpenArgs: IOpenSettings = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, waitMarkerFileURI }; + const windowOpenArgs: IOpenInWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, waitMarkerFileURI }; this._commands.executeCommand('_files.windowOpen', urisToOpen, windowOpenArgs); } res.writeHead(200); diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 6944c46e0346..a8c6dcd1c6c1 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -7,7 +7,8 @@ import 'vs/css!./media/actions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -21,7 +22,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IRecentWorkspace, IRecentFolder, IRecentFile, IRecent, isRecentFolder, isRecentWorkspace } from 'vs/platform/history/common/history'; +import { IRecentWorkspace, IRecentFolder, IRecentFile, IRecent, isRecentFolder, isRecentWorkspace } from 'vs/platform/workspaces/common/workspacesHistory'; import { URI } from 'vs/base/common/uri'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { FileKind } from 'vs/platform/files/common/files'; @@ -44,13 +45,14 @@ abstract class BaseOpenRecentAction extends Action { constructor( id: string, label: string, - private windowService: IWindowService, + private workspacesHistoryService: IWorkspacesHistoryService, private quickInputService: IQuickInputService, private contextService: IWorkspaceContextService, private labelService: ILabelService, private keybindingService: IKeybindingService, private modelService: IModelService, private modeService: IModeService, + private hostService: IHostService ) { super(id, label); } @@ -58,7 +60,7 @@ abstract class BaseOpenRecentAction extends Action { protected abstract isQuickNavigate(): boolean; async run(): Promise { - const { workspaces, files } = await this.windowService.getRecentlyOpened(); + const { workspaces, files } = await this.workspacesHistoryService.getRecentlyOpened(); this.openRecent(workspaces, files); } @@ -66,7 +68,7 @@ abstract class BaseOpenRecentAction extends Action { private async openRecent(recentWorkspaces: Array, recentFiles: IRecentFile[]): Promise { const toPick = (recent: IRecent, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { - let uriToOpen: IURIToOpen | undefined; + let openable: IWindowOpenable | undefined; let iconClasses: string[]; let fullLabel: string | undefined; let resource: URI | undefined; @@ -75,7 +77,7 @@ abstract class BaseOpenRecentAction extends Action { if (isRecentFolder(recent)) { resource = recent.folderUri; iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FOLDER); - uriToOpen = { folderUri: resource }; + openable = { folderUri: resource }; fullLabel = recent.label || labelService.getWorkspaceLabel(resource, { verbose: true }); } @@ -83,7 +85,7 @@ abstract class BaseOpenRecentAction extends Action { else if (isRecentWorkspace(recent)) { resource = recent.workspace.configPath; iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.ROOT_FOLDER); - uriToOpen = { workspaceUri: resource }; + openable = { workspaceUri: resource }; fullLabel = recent.label || labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); } @@ -91,7 +93,7 @@ abstract class BaseOpenRecentAction extends Action { else { resource = recent.fileUri; iconClasses = getIconClasses(this.modelService, this.modeService, resource, FileKind.FILE); - uriToOpen = { fileUri: resource }; + openable = { fileUri: resource }; fullLabel = recent.label || labelService.getUriLabel(resource); } @@ -102,7 +104,7 @@ abstract class BaseOpenRecentAction extends Action { label: name, description: parentPath, buttons, - uriToOpen, + openable, resource }; }; @@ -128,13 +130,13 @@ abstract class BaseOpenRecentAction extends Action { onKeyMods: mods => keyMods = mods, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, onDidTriggerItemButton: async context => { - await this.windowService.removeFromRecentlyOpened([context.item.resource]); + await this.workspacesHistoryService.removeFromRecentlyOpened([context.item.resource]); context.removeItem(); } }); if (pick) { - return this.windowService.openWindow([pick.uriToOpen], { forceNewWindow: keyMods && keyMods.ctrlCmd }); + return this.hostService.openInWindow([pick.openable], { forceNewWindow: keyMods && keyMods.ctrlCmd }); } } } @@ -147,15 +149,16 @@ export class OpenRecentAction extends BaseOpenRecentAction { constructor( id: string, label: string, - @IWindowService windowService: IWindowService, + @IWorkspacesHistoryService workspacesHistoryService: IWorkspacesHistoryService, @IQuickInputService quickInputService: IQuickInputService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IHostService hostService: IHostService ) { - super(id, label, windowService, quickInputService, contextService, labelService, keybindingService, modelService, modeService); + super(id, label, workspacesHistoryService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService); } protected isQuickNavigate(): boolean { @@ -171,15 +174,16 @@ class QuickOpenRecentAction extends BaseOpenRecentAction { constructor( id: string, label: string, - @IWindowService windowService: IWindowService, + @IWorkspacesHistoryService workspacesHistoryService: IWorkspacesHistoryService, @IQuickInputService quickInputService: IQuickInputService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, - @ILabelService labelService: ILabelService + @ILabelService labelService: ILabelService, + @IHostService hostService: IHostService ) { - super(id, label, windowService, quickInputService, contextService, labelService, keybindingService, modelService, modeService); + super(id, label, workspacesHistoryService, quickInputService, contextService, labelService, keybindingService, modelService, modeService, hostService); } protected isQuickNavigate(): boolean { diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 1ae8ec799fbc..e2247b3447a9 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -7,7 +7,8 @@ import { hasWorkspaceFileExtension, IWorkspaceFolderCreationData } from 'vs/plat import { normalize } from 'vs/base/common/path'; import { basename } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; -import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { URI } from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -28,10 +29,11 @@ import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/co import { Disposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IRecentFile } from 'vs/platform/history/common/history'; +import { IRecentFile } from 'vs/platform/workspaces/common/workspacesHistory'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export interface IDraggedResource { resource: URI; @@ -160,13 +162,14 @@ export class ResourcesDropHandler { constructor( private options: IResourcesDropHandlerOptions, @IFileService private readonly fileService: IFileService, - @IWindowService private readonly windowService: IWindowService, + @IWorkspacesHistoryService private readonly workspacesHistoryService: IWorkspacesHistoryService, @ITextFileService private readonly textFileService: ITextFileService, @IBackupFileService private readonly backupFileService: IBackupFileService, @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, + @IHostService private readonly hostService: IHostService ) { } @@ -177,7 +180,7 @@ export class ResourcesDropHandler { } // Make the window active to handle the drop properly within - await this.windowService.focusWindow(); + await this.hostService.focus(); // Check for special things being dropped const isWorkspaceOpening = await this.doHandleDrop(untitledOrFileResources); @@ -187,9 +190,9 @@ export class ResourcesDropHandler { } // Add external ones to recently open list unless dropped resource is a workspace - const recents: IRecentFile[] = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => ({ fileUri: d.resource })); - if (recents.length) { - this.windowService.addRecentlyOpened(recents); + const recentFiles: IRecentFile[] = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => ({ fileUri: d.resource })); + if (recentFiles.length) { + this.workspacesHistoryService.addRecentlyOpened(recentFiles); } const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({ @@ -262,14 +265,14 @@ export class ResourcesDropHandler { } private async handleWorkspaceFileDrop(fileOnDiskResources: URI[]): Promise { - const urisToOpen: IURIToOpen[] = []; + const toOpen: IWindowOpenable[] = []; const folderURIs: IWorkspaceFolderCreationData[] = []; await Promise.all(fileOnDiskResources.map(async fileOnDiskResource => { // Check for Workspace if (hasWorkspaceFileExtension(fileOnDiskResource)) { - urisToOpen.push({ workspaceUri: fileOnDiskResource }); + toOpen.push({ workspaceUri: fileOnDiskResource }); return; } @@ -278,7 +281,7 @@ export class ResourcesDropHandler { try { const stat = await this.fileService.resolve(fileOnDiskResource); if (stat.isDirectory) { - urisToOpen.push({ folderUri: stat.resource }); + toOpen.push({ folderUri: stat.resource }); folderURIs.push({ uri: stat.resource }); } } catch (error) { @@ -287,16 +290,16 @@ export class ResourcesDropHandler { })); // Return early if no external resource is a folder or workspace - if (urisToOpen.length === 0) { + if (toOpen.length === 0) { return false; } // Pass focus to window - this.windowService.focusWindow(); + this.hostService.focus(); // Open in separate windows if we drop workspaces or just one folder - if (urisToOpen.length > folderURIs.length || folderURIs.length === 1) { - await this.windowService.openWindow(urisToOpen, { forceReuseWindow: true }); + if (toOpen.length > folderURIs.length || folderURIs.length === 1) { + await this.hostService.openInWindow(toOpen, { forceReuseWindow: true }); } // folders.length > 1: Multiple folders: Create new workspace with folders and open diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index c688ce6ec63f..5c01e2874e52 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, EventHelper, Dimension } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, Dimension } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -31,7 +31,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IFileService } from 'vs/platform/files/common/files'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -47,8 +47,6 @@ enum Settings { ZEN_MODE_RESTORE = 'zenMode.restore', - // TODO @misolori update this when finished - OCTICONS_UPDATE_ENABLED = 'workbench.octiconsUpdate.enabled', } enum Storage { @@ -186,10 +184,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi transitionDisposables: new DisposableStore() }, - // TODO @misolori update this when finished - octiconsUpdate: { - enabled: false - } }; constructor( @@ -256,11 +250,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Prevent workbench from scrolling #55456 this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); - // Prevent native context menus in web #73781 - if (isWeb) { - this._register(addDisposableListener(this.container, EventType.CONTEXT_MENU, (e) => EventHelper.stop(e, true))); - } - // Menubar visibility changes if ((isWindows || isLinux || isWeb) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); @@ -340,10 +329,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const newMenubarVisibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); - // TODO @misolori update this when finished - const newOcticonsUpdate = this.configurationService.getValue(Settings.OCTICONS_UPDATE_ENABLED); - this.setOcticonsUpdate(newOcticonsUpdate); - } private setSideBarPosition(position: Position): void { @@ -456,9 +441,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Zen mode enablement this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); - // TODO @misolori update this when finished - this.state.octiconsUpdate.enabled = this.configurationService.getValue(Settings.OCTICONS_UPDATE_ENABLED); - this.setOcticonsUpdate(this.state.octiconsUpdate.enabled); } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -571,7 +553,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case Parts.TITLEBAR_PART: if (getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { return false; - } else if (!this.state.fullscreen) { + } else if (!this.state.fullscreen && !isWeb) { return true; } else if (isMacintosh && isNative) { return false; @@ -765,18 +747,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); } - // TODO @misolori update this when finished - private setOcticonsUpdate(enabled: boolean): void { - this.state.octiconsUpdate.enabled = enabled; - - // Update DOM - if (enabled) { - document.body.dataset.octiconsUpdate = 'enabled'; - } else { - document.body.dataset.octiconsUpdate = ''; - } - } - protected createWorkbenchLayout(instantiationService: IInstantiationService): void { const titleBar = this.getPart(Parts.TITLEBAR_PART); const editorPart = this.getPart(Parts.EDITOR_PART); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index fc8ef59cb5a7..5a6e98b033df 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -266,9 +266,9 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); if (activeForegroundColor) { collector.addRule(` - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.active .action-label, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:focus .action-label, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:hover .action-label { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active .action-label, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus .action-label, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover .action-label { background-color: ${activeForegroundColor} !important; } `); @@ -278,7 +278,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const outline = theme.getColor(activeContrastBorder); if (outline) { collector.addRule(` - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:before { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:before { content: ""; position: absolute; top: 9px; @@ -287,26 +287,26 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { width: 32px; } - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.active:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.active:hover:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.checked:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.checked:hover:before { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before { outline: 1px solid; } - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:hover:before { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before { outline: 1px dashed; } - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:focus:before { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { border-left-color: ${outline}; } - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.active:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.active:hover:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.checked:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item.checked:hover:before, - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:hover:before { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.active:hover:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.checked:hover:before, + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:hover:before { outline-color: ${outline}; } `); @@ -317,7 +317,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const focusBorderColor = theme.getColor(focusBorder); if (focusBorderColor) { collector.addRule(` - .monaco-workbench .activitybar > .content .monaco-action-bar .action-item:focus:before { + .monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { border-left-color: ${focusBorderColor}; } `); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index b729804ec52d..eac46aa53c2f 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -21,7 +21,7 @@ import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem } from 'vs/workbench/browser/parts/compositeBar'; -import { Dimension, addClass } from 'vs/base/browser/dom'; +import { Dimension, addClass, removeNode } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -35,6 +35,9 @@ import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/a import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; interface ICachedViewlet { id: string; @@ -65,6 +68,10 @@ export class ActivitybarPart extends Part implements IActivityBarService { private globalActivityAction: ActivityAction; private globalActivityActionBar: ActionBar; + private customMenubar: CustomMenubarControl | undefined; + private menubar: HTMLElement | undefined; + private content: HTMLElement; + private cachedViewlets: ICachedViewlet[] = []; private compositeBar: CompositeBar; @@ -81,7 +88,8 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IExtensionService private readonly extensionService: IExtensionService, @IViewsService private readonly viewsService: IViewsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -135,6 +143,17 @@ export class ActivitybarPart extends Part implements IActivityBarService { this.compositeBar.onDidChange(() => this.saveCachedViewlets(), this, disposables); this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); })); + + // Register for configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('window.menuBarVisibility')) { + if (this.configurationService.getValue('window.menuBarVisibility') === 'compact') { + this.installMenubar(); + } else { + this.uninstallMenubar(); + } + } + })); } private onDidRegisterExtensions(): void { @@ -181,26 +200,52 @@ export class ActivitybarPart extends Part implements IActivityBarService { return toDisposable(() => this.globalActivityAction.setBadge(undefined)); } + private uninstallMenubar() { + if (this.customMenubar) { + this.customMenubar.dispose(); + } + + if (this.menubar) { + removeNode(this.menubar); + } + } + + private installMenubar() { + this.menubar = document.createElement('div'); + addClass(this.menubar, 'menubar'); + this.content.prepend(this.menubar); + + // Menubar: install a custom menu bar depending on configuration + this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); + this.customMenubar.create(this.menubar); + + } + createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; - const content = document.createElement('div'); - addClass(content, 'content'); - parent.appendChild(content); + this.content = document.createElement('div'); + addClass(this.content, 'content'); + parent.appendChild(this.content); + + // Install menubar if compact + if (this.configurationService.getValue('window.menuBarVisibility') === 'compact') { + this.installMenubar(); + } // Viewlets action bar - this.compositeBar.create(content); + this.compositeBar.create(this.content); // Global action bar const globalActivities = document.createElement('div'); addClass(globalActivities, 'global-activity'); - content.appendChild(globalActivities); + this.content.appendChild(globalActivities); this.createGlobalActivityActionBar(globalActivities); this.element.style.display = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART) ? null : 'none'; - return content; + return this.content; } updateStyles(): void { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css index 24733e95cafa..b0ff193562fd 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css @@ -3,13 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .activitybar > .content .monaco-action-bar .action-item { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item { display: block; position: relative; padding: 5px 0; } -.monaco-workbench .activitybar > .content .monaco-action-bar .action-label { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label { display: flex; overflow: hidden; height: 40px; @@ -20,7 +20,7 @@ font-size: 15px; } -.monaco-workbench .activitybar > .content .monaco-action-bar .action-item:focus:before { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { content: ""; position: absolute; top: 9px; @@ -29,19 +29,19 @@ border-left: 2px solid; } -.monaco-workbench .activitybar > .content .monaco-action-bar .action-item.clicked:focus:before { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.clicked:focus:before { border-left: none !important; /* no focus feedback when using mouse */ } -.monaco-workbench .activitybar.left > .content .monaco-action-bar .action-item:focus:before { +.monaco-workbench .activitybar.left > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { left: 1px; } -.monaco-workbench .activitybar.right > .content .monaco-action-bar .action-item:focus:before { +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-item:focus:before { right: 1px; } -.monaco-workbench .activitybar > .content .monaco-action-bar .badge { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge { position: absolute; top: 5px; left: 0; @@ -50,7 +50,7 @@ height: 40px; } -.monaco-workbench .activitybar > .content .monaco-action-bar .badge .badge-content { +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .badge-content { position: absolute; top: 20px; right: 8px; @@ -66,13 +66,13 @@ /* Right aligned */ -.monaco-workbench .activitybar.right > .content .monaco-action-bar .action-label { +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .action-label { margin-left: 0; padding: 0 50px 0 0; background-position: calc(100% - 9px) center; } -.monaco-workbench .activitybar.right > .content .monaco-action-bar .badge { +.monaco-workbench .activitybar.right > .content :not(.monaco-menu) > .monaco-action-bar .badge { left: auto; right: 0; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index f975619b4e4b..388dbf3390e2 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -29,4 +29,13 @@ .monaco-workbench .activitybar .global-activity .monaco-action-bar .action-label.update-activity { -webkit-mask: url('settings-activity-bar.svg') no-repeat 50% 50%; -} \ No newline at end of file +} + +.monaco-workbench .activitybar > .content > .composite-bar { + margin-bottom: auto; +} + +.monaco-workbench .activitybar .menubar { + width: 100%; + height: 35px; +} diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index b56682004455..ef6c37a46df9 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -19,8 +19,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { dispose } from 'vs/base/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions | undefined) => Promise; @@ -49,10 +47,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { callbacks: IOpenCallbacks, telemetryService: ITelemetryService, themeService: IThemeService, - @IFileService private readonly fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IStorageService storageService: IStorageService, - @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, telemetryService, themeService, storageService); @@ -95,11 +91,11 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { this.resourceViewerContext.dispose(); } - this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.fileService, this.binaryContainer, this.scrollbar, { + this.resourceViewerContext = ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, this.binaryContainer, this.scrollbar, { openInternalClb: () => this.handleOpenInternalCallback(input, options), openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), metadataClb: meta => this.handleMetadataChanged(meta) - }, this.instantiationService); + }); } private async handleOpenInternalCallback(input: EditorInput, options: EditorOptions | undefined): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 0df568230e56..c8e6a7cf9b79 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -16,7 +16,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -1218,7 +1218,7 @@ export class ClearRecentFilesAction extends Action { constructor( id: string, label: string, - @IWindowsService private readonly windowsService: IWindowsService, + @IWorkspacesHistoryService private readonly workspacesHistoryService: IWorkspacesHistoryService, @IHistoryService private readonly historyService: IHistoryService ) { super(id, label); @@ -1227,7 +1227,7 @@ export class ClearRecentFilesAction extends Action { run(): Promise { // Clear global recently opened - this.windowsService.clearRecentlyOpened(); + this.workspacesHistoryService.clearRecentlyOpened(); // Clear workspace specific recently opened this.historyService.clearRecentlyOpened(); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 5b582505f228..12852898c4f8 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -49,7 +49,7 @@ import { INotificationHandle, INotificationService, Severity } from 'vs/platform import { Event } from 'vs/base/common/event'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; // {{SQL CARBON EDIT}} import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; @@ -415,7 +415,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const props = { + const props: IStatusbarEntry = { text, tooltip: nls.localize('gotoLine', "Go to Line"), command: 'workbench.action.gotoLine' @@ -430,7 +430,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const props = { + const props: IStatusbarEntry = { text, tooltip: nls.localize('selectIndentation', "Select Indentation"), command: 'changeEditorIndentation' @@ -445,7 +445,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const props = { + const props: IStatusbarEntry = { text, tooltip: nls.localize('selectEncoding', "Select Encoding"), command: 'workbench.action.editor.changeEncoding' @@ -460,7 +460,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const props = { + const props: IStatusbarEntry = { text, tooltip: nls.localize('selectEOL', "Select End of Line Sequence"), command: 'workbench.action.editor.changeEOL' @@ -475,7 +475,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const props = { + const props: IStatusbarEntry = { text, tooltip: nls.localize('selectLanguageMode', "Select Language Mode"), command: 'workbench.action.editor.changeLanguageMode' @@ -490,7 +490,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return; } - const props = { + const props: IStatusbarEntry = { text, tooltip: nls.localize('fileInfo', "File Information") }; diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index f22fba9d2ef2..55815d6bc176 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -12,7 +12,7 @@ import { $, append } from 'vs/base/browser/dom'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { Disposable, dispose } from 'vs/base/common/lifecycle'; @@ -106,7 +106,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit constructor( private editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IFileService private readonly fileService: IFileService ) { @@ -163,7 +163,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit this._register(this.openWorkspaceButton.onClick(() => { const model = this.editor.getModel(); if (model) { - this.windowService.openWindow([{ workspaceUri: model.uri }]); + this.hostService.openInWindow([{ workspaceUri: model.uri }]); } })); @@ -181,4 +181,4 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index 94a4c36a3dfe..e8cfb5916af6 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -10,8 +10,6 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/resourceviewer'; import * as nls from 'vs/nls'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICssStyleCollector, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IMAGE_PREVIEW_BORDER } from 'vs/workbench/common/theme'; @@ -75,11 +73,9 @@ export class ResourceViewer { static show( descriptor: IResourceDescriptor, - fileService: IFileService, container: HTMLElement, scrollbar: DomScrollableElement, delegate: ResourceViewerDelegate, - instantiationService: IInstantiationService, ): ResourceViewerContext { // Ensure CSS class @@ -95,10 +91,8 @@ export class ResourceViewer { return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, delegate); } } - } - class FileTooLargeFileView { static create( container: HTMLElement, diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 048d6eb3ac0f..7e11fe486b27 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -30,7 +30,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -55,10 +55,10 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IClipboardService private _clipboardService: IClipboardService, ) { - super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService); + super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); } protected getEditorMemento(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento { diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 0a52a4fbec83..1e50b79c7334 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -23,7 +23,7 @@ import { isDiffEditor, isCodeEditor, getCodeEditor } from 'vs/editor/browser/edi import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState'; @@ -53,7 +53,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { @ITextFileService private readonly _textFileService: ITextFileService, @IEditorService protected editorService: IEditorService, @IEditorGroupsService protected editorGroupService: IEditorGroupsService, - @IWindowService private readonly windowService: IWindowService + @IHostService private readonly hostService: IHostService ) { super(id, telemetryService, themeService, storageService); @@ -151,7 +151,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { } this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost())); - this._register(this.windowService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); + this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused))); } private onEditorFocusLost(): void { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 2ff5fdb9d1ca..6c3828408932 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -23,7 +23,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; /** * An editor implementation that is capable of showing the contents of resource inputs. Uses @@ -41,9 +41,9 @@ export class AbstractTextResourceEditor extends BaseTextEditor { @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, - @IWindowService windowService: IWindowService + @IHostService hostService: IHostService ) { - super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService); + super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); } getTitle(): string | undefined { @@ -201,8 +201,8 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IWindowService windowService: IWindowService + @IHostService hostService: IHostService ) { - super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, windowService); + super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, hostService); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index d357cd46c24f..7f3fc322d4ee 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -103,7 +103,7 @@ export class NotificationsList extends Themable { // Clear focus when DOM focus moves out // Use document.hasFocus() to not clear the focus when the entire window lost focus - // This ensures that when the focus comes back, the notifciation is still focused + // This ensures that when the focus comes back, the notification is still focused const listFocusTracker = this._register(trackFocus(this.list.getHTMLElement())); this._register(listFocusTracker.onDidBlur(() => { if (document.hasFocus()) { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 591648240d3f..a92e2e6c759d 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications'; -import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 1ddb77eae995..044151365cb9 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -21,7 +21,7 @@ import { localize } from 'vs/nls'; import { Severity } from 'vs/platform/notification/common/notification'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { timeout } from 'vs/base/common/async'; interface INotificationToast { @@ -67,7 +67,7 @@ export class NotificationsToasts extends Themable { @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IContextKeyService contextKeyService: IContextKeyService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IWindowService private readonly windowService: IWindowService + @IHostService private readonly hostService: IHostService ) { super(themeService); @@ -240,9 +240,9 @@ export class NotificationsToasts extends Themable { // again before triggering the timeout again. This prevents an issue where // focussing the window could immediately hide the notification because the // timeout was triggered again. - if (!this.windowService.hasFocus) { + if (!this.hostService.hasFocus) { if (!listener) { - listener = this.windowService.onDidChangeFocus(focus => { + listener = this.hostService.onDidChangeFocus(focus => { if (focus) { hideAfterTimeout(); } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 1e58dbc039f3..111a3a4d5897 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -124,6 +124,8 @@ margin-right: 10px; } +/* Rotate icons when panel is on right */ +.monaco-workbench .part.panel.right .title-actions .codicon-split-horizontal, .monaco-workbench .part.panel.right .title-actions .codicon-chevron-up, .monaco-workbench .part.panel.right .title-actions .codicon-chevron-down { transform: rotate(-90deg); diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 317ed6fdeaba..5dfe4c48882c 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -13,7 +13,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { Part } from 'vs/workbench/browser/part'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action, IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 29158c4651fa..f42963e701a1 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -6,7 +6,8 @@ import * as nls from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -16,24 +17,32 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IRecentlyOpened, isRecentFolder, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, isRecentFolder, IRecent, isRecentWorkspace } from 'vs/platform/workspaces/common/workspacesHistory'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; +import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { URI } from 'vs/base/common/uri'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; -import { SubmenuAction } from 'vs/base/browser/ui/menu/menu'; +import { SubmenuAction, Direction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isFullscreen } from 'vs/base/browser/browser'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +// TODO@sbatten https://github.com/microsoft/vscode/issues/81360 +// tslint:disable-next-line: import-patterns layering TODO@sbatten +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; +// tslint:disable-next-line: import-patterns layering TODO@sbatten +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export abstract class MenubarControl extends Disposable { @@ -41,6 +50,7 @@ export abstract class MenubarControl extends Disposable { 'window.menuBarVisibility', 'window.enableMenuBarMnemonics', 'window.customMenuBarAltFocus', + 'workbench.sideBar.location', 'window.nativeTabs' ]; @@ -76,8 +86,7 @@ export abstract class MenubarControl extends Disposable { constructor( protected readonly menuService: IMenuService, - protected readonly windowService: IWindowService, - protected readonly windowsService: IWindowsService, + protected readonly workspacesHistoryService: IWorkspacesHistoryService, protected readonly contextKeyService: IContextKeyService, protected readonly keybindingService: IKeybindingService, protected readonly configurationService: IConfigurationService, @@ -86,8 +95,9 @@ export abstract class MenubarControl extends Disposable { protected readonly storageService: IStorageService, protected readonly notificationService: INotificationService, protected readonly preferencesService: IPreferencesService, - protected readonly environmentService: IEnvironmentService, - protected readonly accessibilityService: IAccessibilityService + protected readonly environmentService: IWorkbenchEnvironmentService, + protected readonly accessibilityService: IAccessibilityService, + protected readonly hostService: IHostService ) { super(); @@ -119,7 +129,7 @@ export abstract class MenubarControl extends Disposable { this.updateService.onStateChange(() => this.updateMenubar()); // Listen for changes in recently opened menu - this._register(this.windowsService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); })); + this._register(this.workspacesHistoryService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); })); // Listen to keybindings change this._register(this.keybindingService.onDidUpdateKeybindings(() => this.updateMenubar())); @@ -181,7 +191,7 @@ export abstract class MenubarControl extends Disposable { } private onRecentlyOpenedChange(): void { - this.windowService.getRecentlyOpened().then(recentlyOpened => { + this.workspacesHistoryService.getRecentlyOpened().then(recentlyOpened => { this.recentlyOpened = recentlyOpened; this.updateMenubar(); }); @@ -192,29 +202,29 @@ export abstract class MenubarControl extends Disposable { let label: string; let uri: URI; let commandId: string; - let uriToOpen: IURIToOpen; + let openable: IWindowOpenable; if (isRecentFolder(recent)) { uri = recent.folderUri; label = recent.label || this.labelService.getWorkspaceLabel(uri, { verbose: true }); commandId = 'openRecentFolder'; - uriToOpen = { folderUri: uri }; + openable = { folderUri: uri }; } else if (isRecentWorkspace(recent)) { uri = recent.workspace.configPath; label = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); commandId = 'openRecentWorkspace'; - uriToOpen = { workspaceUri: uri }; + openable = { workspaceUri: uri }; } else { uri = recent.fileUri; label = recent.label || this.labelService.getUriLabel(uri); commandId = 'openRecentFile'; - uriToOpen = { fileUri: uri }; + openable = { fileUri: uri }; } const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, (event) => { const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); - return this.windowService.openWindow([uriToOpen], { + return this.hostService.openInWindow([openable], { forceNewWindow: openInNewWindow }); }); @@ -261,8 +271,7 @@ export class CustomMenubarControl extends MenubarControl { constructor( @IMenuService menuService: IMenuService, - @IWindowService windowService: IWindowService, - @IWindowsService windowsService: IWindowsService, + @IWorkspacesHistoryService workspacesHistoryService: IWorkspacesHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, @@ -271,16 +280,18 @@ export class CustomMenubarControl extends MenubarControl { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @IPreferencesService preferencesService: IPreferencesService, - @IEnvironmentService environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, @IThemeService private readonly themeService: IThemeService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IHostService protected readonly hostService: IHostService, + @optional(IElectronService) private readonly electronService: IElectronService, + @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService ) { super( menuService, - windowService, - windowsService, + workspacesHistoryService, contextKeyService, keybindingService, configurationService, @@ -290,12 +301,14 @@ export class CustomMenubarControl extends MenubarControl { notificationService, preferencesService, environmentService, - accessibilityService); + accessibilityService, + hostService + ); this._onVisibilityChange = this._register(new Emitter()); this._onFocusStateChange = this._register(new Emitter()); - this.windowService.getRecentlyOpened().then((recentlyOpened) => { + this.workspacesHistoryService.getRecentlyOpened().then((recentlyOpened) => { this.recentlyOpened = recentlyOpened; }); @@ -315,14 +328,45 @@ export class CustomMenubarControl extends MenubarControl { `); } + const activityBarInactiveFgColor = theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND); + if (activityBarInactiveFgColor) { + collector.addRule(` + .monaco-workbench .menubar.compact > .menubar-menu-button { + color: ${activityBarInactiveFgColor}; + } + + .monaco-workbench .menubar.compact .toolbar-toggle-more { + background-color: ${activityBarInactiveFgColor} + } + `); + + } + + const activityBarFgColor = theme.getColor(ACTIVITY_BAR_FOREGROUND); + if (activityBarFgColor) { + collector.addRule(` + .monaco-workbench .menubar.compact > .menubar-menu-button.open, + .monaco-workbench .menubar.compact > .menubar-menu-button:focus, + .monaco-workbench .menubar.compact:not(:focus-within) > .menubar-menu-button:hover { + color: ${activityBarFgColor}; + } + + .monaco-workbench .menubar.compact > .menubar-menu-button.open .toolbar-toggle-more, + .monaco-workbench .menubar.compact > .menubar-menu-button:focus .toolbar-toggle-more, + .monaco-workbench .menubar.compact:not(:focus-within) > .menubar-menu-button:hover .toolbar-toggle-more { + background-color: ${activityBarFgColor} + } + `); + } + const menubarInactiveWindowFgColor = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND); if (menubarInactiveWindowFgColor) { collector.addRule(` - .monaco-workbench .menubar.inactive > .menubar-menu-button { + .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button { color: ${menubarInactiveWindowFgColor}; } - .monaco-workbench .menubar.inactive > .menubar-menu-button .toolbar-toggle-more { + .monaco-workbench .menubar.inactive:not(.compact) > .menubar-menu-button .toolbar-toggle-more { background-color: ${menubarInactiveWindowFgColor} } `); @@ -332,15 +376,15 @@ export class CustomMenubarControl extends MenubarControl { const menubarSelectedFgColor = theme.getColor(MENUBAR_SELECTION_FOREGROUND); if (menubarSelectedFgColor) { collector.addRule(` - .monaco-workbench .menubar > .menubar-menu-button.open, - .monaco-workbench .menubar > .menubar-menu-button:focus, - .monaco-workbench .menubar:not(:focus-within) > .menubar-menu-button:hover { + .monaco-workbench .menubar:not(.compact) > .menubar-menu-button.open, + .monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus, + .monaco-workbench .menubar:not(:focus-within):not(.compact) > .menubar-menu-button:hover { color: ${menubarSelectedFgColor}; } - .monaco-workbench .menubar > .menubar-menu-button.open .toolbar-toggle-more, - .monaco-workbench .menubar > .menubar-menu-button:focus .toolbar-toggle-more, - .monaco-workbench .menubar:not(:focus-within) > .menubar-menu-button:hover .toolbar-toggle-more { + .monaco-workbench .menubar:not(.compact) > .menubar-menu-button.open .toolbar-toggle-more, + .monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus .toolbar-toggle-more, + .monaco-workbench .menubar:not(:focus-within):not(.compact) > .menubar-menu-button:hover .toolbar-toggle-more { background-color: ${menubarSelectedFgColor} } `); @@ -349,9 +393,9 @@ export class CustomMenubarControl extends MenubarControl { const menubarSelectedBgColor = theme.getColor(MENUBAR_SELECTION_BACKGROUND); if (menubarSelectedBgColor) { collector.addRule(` - .monaco-workbench .menubar > .menubar-menu-button.open, - .monaco-workbench .menubar > .menubar-menu-button:focus, - .monaco-workbench .menubar:not(:focus-within) > .menubar-menu-button:hover { + .monaco-workbench .menubar:not(.compact) > .menubar-menu-button.open, + .monaco-workbench .menubar:not(.compact) > .menubar-menu-button:focus, + .monaco-workbench .menubar:not(:focus-within):not(.compact) > .menubar-menu-button:hover { background-color: ${menubarSelectedBgColor}; } `); @@ -392,7 +436,7 @@ export class CustomMenubarControl extends MenubarControl { return null; case StateType.Idle: - const windowId = this.windowService.windowId; + const windowId = this.electronEnvironmentService.windowId; return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () => this.updateService.checkForUpdates({ windowId })); @@ -466,6 +510,15 @@ export class CustomMenubarControl extends MenubarControl { return enableMenuBarMnemonics && (!isWeb || isFullscreen()); } + private get currentCompactMenuMode(): Direction | undefined { + if (this.currentMenubarVisibility !== 'compact') { + return undefined; + } + + const currentSidebarLocation = this.configurationService.getValue('workbench.sideBar.location'); + return currentSidebarLocation === 'right' ? Direction.Left : Direction.Right; + } + private setupCustomMenubar(firstTime: boolean): void { if (firstTime) { this.menubar = this._register(new MenuBar( @@ -474,12 +527,13 @@ export class CustomMenubarControl extends MenubarControl { disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), + compactMode: this.currentCompactMenuMode } )); this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { this.alwaysOnMnemonics = val; - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics }); + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); }); this._register(this.menubar.onFocusStateChange(focused => { @@ -505,7 +559,7 @@ export class CustomMenubarControl extends MenubarControl { this._register(attachMenuStyler(this.menubar, this.themeService)); } else { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics }); + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); } // Update the menu actions @@ -586,9 +640,15 @@ export class CustomMenubarControl extends MenubarControl { super.registerListeners(); // Listen for window focus changes - this._register(this.windowService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e))); - - this._register(this.windowService.onDidChangeMaximize(e => this.updateMenubar())); + this._register(this.hostService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e))); + + // Listen for maximize/unmaximize + if (!isWeb) { + this._register(Event.any( + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) + )(e => this.updateMenubar())); + } this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { this.menubar.blur(); @@ -633,7 +693,7 @@ export class CustomMenubarControl extends MenubarControl { } if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics }); + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, disableAltFocus: this.currentDisableMenuBarAltFocus, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics, compactMode: this.currentCompactMenuMode }); } } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index e358b665fd9b..e58a53d29527 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -8,7 +8,7 @@ import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; -import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction } from 'vs/base/common/actions'; @@ -25,7 +25,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; -import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, removeNode } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { template } from 'vs/base/common/labels'; @@ -38,10 +38,13 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; // TODO@sbatten https://github.com/microsoft/vscode/issues/81360 // tslint:disable-next-line: import-patterns layering import { IElectronService } from 'vs/platform/electron/node/electron'; +// tslint:disable-next-line: import-patterns layering +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class TitlebarPart extends Part implements ITitleService { @@ -55,8 +58,8 @@ export class TitlebarPart extends Part implements ITitleService { readonly minimumWidth: number = 0; readonly maximumWidth: number = Number.POSITIVE_INFINITY; - get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } - get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } + get minimumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1)); } + get maximumHeight(): number { return isMacintosh && !isWeb ? 22 / getZoomFactor() : (30 / (this.currentMenubarVisibility === 'hidden' ? getZoomFactor() : 1)); } //#endregion @@ -71,7 +74,7 @@ export class TitlebarPart extends Part implements ITitleService { private maxRestoreControl: HTMLElement; private appIcon: HTMLElement; private customMenubar: CustomMenubarControl | undefined; - private menubar: HTMLElement; + private menubar?: HTMLElement; private resizer: HTMLElement; private lastLayoutDimensions: Dimension; @@ -88,7 +91,6 @@ export class TitlebarPart extends Part implements ITitleService { constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @@ -100,7 +102,9 @@ export class TitlebarPart extends Part implements ITitleService { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IMenuService menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @optional(IElectronService) private electronService: IElectronService + @IHostService private readonly hostService: IHostService, + @optional(IElectronService) private electronService: IElectronService, + @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -110,7 +114,7 @@ export class TitlebarPart extends Part implements ITitleService { } private registerListeners(): void { - this._register(this.windowService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur())); + this._register(this.hostService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur())); this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e))); this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange())); this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.titleUpdater.schedule())); @@ -134,6 +138,14 @@ export class TitlebarPart extends Part implements ITitleService { this.titleUpdater.schedule(); } + if (event.affectsConfiguration('window.menuBarVisibility')) { + if (this.currentMenubarVisibility === 'compact') { + this.uninstallMenubar(); + } else { + this.installMenubar(); + } + } + if (event.affectsConfiguration('window.doubleClickIconToClose')) { if (this.appIcon) { this.onUpdateAppIconDragBehavior(); @@ -144,7 +156,7 @@ export class TitlebarPart extends Part implements ITitleService { private onMenubarVisibilityChanged(visible: boolean) { if (isWeb || isWindows || isLinux) { // Hide title when toggling menu bar - if (!isWeb && this.configurationService.getValue('window.menuBarVisibility') === 'toggle' && visible) { + if (!isWeb && this.currentMenubarVisibility === 'toggle' && visible) { // Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor hide(this.dragRegion); setTimeout(() => show(this.dragRegion), 50); @@ -157,7 +169,7 @@ export class TitlebarPart extends Part implements ITitleService { } private onMenubarFocusChanged(focused: boolean) { - if (!isWeb && (isWindows || isLinux)) { + if (!isWeb && (isWindows || isLinux) && this.currentMenubarVisibility === 'compact') { if (focused) { hide(this.dragRegion); } else { @@ -313,6 +325,31 @@ export class TitlebarPart extends Part implements ITitleService { }); } + private uninstallMenubar(): void { + if (this.customMenubar) { + this.customMenubar.dispose(); + this.customMenubar = undefined; + } + + if (this.menubar) { + removeNode(this.menubar); + this.menubar = undefined; + } + } + + private installMenubar(): void { + this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); + + this.menubar = this.element.insertBefore($('div.menubar'), this.title); + + this.menubar.setAttribute('role', 'menubar'); + + this.customMenubar.create(this.menubar); + + this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); + this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); + } + createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; @@ -332,15 +369,11 @@ export class TitlebarPart extends Part implements ITitleService { } // Menubar: install a custom menu bar depending on configuration - if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native' && (!isMacintosh || isWeb)) { - this.customMenubar = this._register(this.instantiationService.createInstance(CustomMenubarControl)); - this.menubar = append(this.element, $('div.menubar')); - this.menubar.setAttribute('role', 'menubar'); - - this.customMenubar.create(this.menubar); - - this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); - this._register(this.customMenubar.onFocusStateChange(e => this.onMenubarFocusChanged(e))); + // and when not in activity bar + if (getTitleBarStyle(this.configurationService, this.environmentService) !== 'native' + && (!isMacintosh || isWeb) + && this.currentMenubarVisibility !== 'compact') { + this.installMenubar(); } // Title @@ -401,13 +434,17 @@ export class TitlebarPart extends Part implements ITitleService { const isMaximized = this.environmentService.configuration.maximized ? true : false; this.onDidChangeMaximized(isMaximized); - this._register(this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this)); + + this._register(Event.any( + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) + )(e => this.onDidChangeMaximized(e))); } // Since the title area is used to drag the window, we do not want to steal focus from the // currently active element. So we restore focus after a timeout back to where it was. this._register(addDisposableListener(this.element, EventType.MOUSE_DOWN, e => { - if (e.target && isAncestor(e.target as HTMLElement, this.menubar)) { + if (e.target && this.menubar && isAncestor(e.target as HTMLElement, this.menubar)) { return; } @@ -501,7 +538,7 @@ export class TitlebarPart extends Part implements ITitleService { } private adjustTitleMarginToCenter(): void { - if (this.customMenubar) { + if (this.customMenubar && this.menubar) { const leftMarker = (this.appIcon ? this.appIcon.clientWidth : 0) + this.menubar.clientWidth + 10; const rightMarker = this.element.clientWidth - (this.windowControls ? this.windowControls.clientWidth : 0) - 10; @@ -521,12 +558,16 @@ export class TitlebarPart extends Part implements ITitleService { this.title.style.transform = 'translate(-50%, 0)'; } + private get currentMenubarVisibility(): MenuBarVisibility { + return this.configurationService.getValue('window.menuBarVisibility'); + } + updateLayout(dimension: Dimension): void { this.lastLayoutDimensions = dimension; if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { // Only prevent zooming behavior on macOS or when the menubar is not visible - if ((!isWeb && isMacintosh) || this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { + if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') { this.title.style.zoom = `${1 / getZoomFactor()}`; if (!isWeb && (isWindows || isLinux)) { this.appIcon.style.zoom = `${1 / getZoomFactor()}`; diff --git a/src/vs/workbench/browser/parts/views/media/tree-collapsed-dark.svg b/src/vs/workbench/browser/parts/views/media/tree-collapsed-dark.svg deleted file mode 100644 index c2c2298dd5c4..000000000000 --- a/src/vs/workbench/browser/parts/views/media/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/views/media/tree-collapsed-hc.svg b/src/vs/workbench/browser/parts/views/media/tree-collapsed-hc.svg deleted file mode 100644 index 3732cbc04b8d..000000000000 --- a/src/vs/workbench/browser/parts/views/media/tree-collapsed-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/views/media/tree-collapsed-light.svg b/src/vs/workbench/browser/parts/views/media/tree-collapsed-light.svg deleted file mode 100644 index 1952ad63f848..000000000000 --- a/src/vs/workbench/browser/parts/views/media/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/views/media/tree-expanded-dark.svg b/src/vs/workbench/browser/parts/views/media/tree-expanded-dark.svg deleted file mode 100644 index 5570923e1753..000000000000 --- a/src/vs/workbench/browser/parts/views/media/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/views/media/tree-expanded-hc.svg b/src/vs/workbench/browser/parts/views/media/tree-expanded-hc.svg deleted file mode 100644 index b370009330c0..000000000000 --- a/src/vs/workbench/browser/parts/views/media/tree-expanded-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/views/media/tree-expanded-light.svg b/src/vs/workbench/browser/parts/views/media/tree-expanded-light.svg deleted file mode 100644 index 939ebc8b9695..000000000000 --- a/src/vs/workbench/browser/parts/views/media/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index cbf9e1a868ef..fd1b45ad7899 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -20,31 +20,6 @@ content: ' '; } -.file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before { - background-image: url("tree-expanded-light.svg"); -} - -.file-icon-themable-tree .monaco-tree-row.has-children .content::before { - display: inline-block; - background-image: url("tree-collapsed-light.svg"); -} - -.vs-dark .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before { - background-image: url("tree-expanded-dark.svg"); -} - -.vs-dark .file-icon-themable-tree .monaco-tree-row.has-children .content::before { - background-image: url("tree-collapsed-dark.svg"); -} - -.hc-black .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before { - background-image: url("tree-expanded-hc.svg"); -} - -.hc-black .file-icon-themable-tree .monaco-tree-row.has-children .content::before { - background-image: url("tree-collapsed-hc.svg"); -} - .file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content::before, .file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before { display: none; @@ -58,6 +33,7 @@ background-image: none !important; width: 0 !important; margin-right: 0 !important; + visibility: hidden; } /* Misc */ diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index c00f152635a1..ee7e65153a45 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -71,6 +71,7 @@ export abstract class ViewletPanel extends Panel implements IView { private readonly showActionsAlways: boolean = false; private headerContainer: HTMLElement; private titleContainer: HTMLElement; + protected twistiesContainer: HTMLElement; constructor( options: IViewletPanelOptions, @@ -133,6 +134,8 @@ export abstract class ViewletPanel extends Panel implements IView { protected renderHeader(container: HTMLElement): void { this.headerContainer = container; + this.renderTwisties(container); + this.renderHeaderTitle(container, this.title); const actions = append(container, $('.actions')); @@ -153,6 +156,10 @@ export abstract class ViewletPanel extends Panel implements IView { this.updateActionsVisibility(); } + protected renderTwisties(container: HTMLElement): void { + this.twistiesContainer = append(container, $('.twisties.codicon.codicon-chevron-right')); + } + protected renderHeaderTitle(container: HTMLElement, title: string): void { this.titleContainer = append(container, $('h3.title', undefined, title)); } diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 7e608ee6d78f..3e63daf9fcda 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { mark } from 'vs/base/common/performance'; -import { domContentLoaded, addDisposableListener, EventType, addClass } from 'vs/base/browser/dom'; +import { domContentLoaded, addDisposableListener, EventType, addClass, EventHelper } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService, ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -25,6 +25,7 @@ import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; +import * as browser from 'vs/base/browser/browser'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -47,7 +48,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { IndexedDBLogProvider } from 'vs/workbench/services/log/browser/indexedDBLogProvider'; import { InMemoryLogProvider } from 'vs/workbench/services/log/common/inMemoryLogProvider'; -class CodeRendererMain extends Disposable { +class BrowserMain extends Disposable { constructor( private readonly domElement: HTMLElement, @@ -72,6 +73,20 @@ class CodeRendererMain extends Disposable { services.logService ); + // Listeners + this.registerListeners(workbench, services.storageService); + + // Driver + if (this.configuration.driver) { + (async () => this._register(await registerWindowDriver()))(); + } + + // Startup + workbench.startup(); + } + + private registerListeners(workbench: Workbench, storageService: BrowserStorageService): void { + // Layout this._register(addDisposableListener(window, EventType.RESIZE, () => workbench.layout())); @@ -80,26 +95,32 @@ class CodeRendererMain extends Disposable { e.preventDefault(); }, { passive: false })); + // Prevent native context menus in web + this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, (e) => EventHelper.stop(e, true))); + // Workbench Lifecycle this._register(workbench.onBeforeShutdown(event => { - if (services.storageService.hasPendingUpdate) { + if (storageService.hasPendingUpdate) { console.warn('Unload prevented: pending storage update'); event.veto(true); // prevent data loss from pending storage update } })); this._register(workbench.onWillShutdown(() => { - services.storageService.close(); + storageService.close(); this.saveBaseTheme(); })); this._register(workbench.onShutdown(() => this.dispose())); - // Driver - if (this.configuration.driver) { - (async () => this._register(await registerWindowDriver()))(); - } - - // Startup - workbench.startup(); + // Fullscreen + [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE].forEach(event => { + this._register(addDisposableListener(document, event, () => { + if (document.fullscreenElement || (document).webkitFullscreenElement || (document).webkitIsFullScreen) { + browser.setFullscreen(true); + } else { + browser.setFullscreen(false); + } + })); + }); } private restoreBaseTheme(): void { @@ -198,8 +219,8 @@ class CodeRendererMain extends Disposable { fileService.registerProvider(logsPath.scheme, indexedDBLogProvider); } catch (error) { - (logService).info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); - (logService).error(error); + logService.info('Error while creating indexedDB log provider. Falling back to in-memory log provider.'); + logService.error(error); fileService.registerProvider(logsPath.scheme, new InMemoryLogProvider(logsPath.scheme)); } @@ -291,7 +312,7 @@ class CodeRendererMain extends Disposable { } export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { - const renderer = new CodeRendererMain(domElement, options); + const renderer = new BrowserMain(domElement, options); return renderer.open(); } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts deleted file mode 100644 index 81c43aa58f43..000000000000 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ /dev/null @@ -1,239 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vs/base/common/uri'; -import * as browser from 'vs/base/browser/browser'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Event } from 'vs/base/common/event'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IWindowService, IURIToOpen, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/common/history'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { pathsToEditors } from 'vs/workbench/common/editor'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; - -//#region Window - -export class SimpleWindowService extends Disposable implements IWindowService { - - _serviceBrand: undefined; - - readonly onDidChangeFocus: Event = Event.None; - readonly onDidChangeMaximize: Event = Event.None; - - readonly hasFocus = true; - - readonly windowId = 0; - - static readonly RECENTLY_OPENED_KEY = 'recently.opened'; - - constructor( - @IEditorService private readonly editorService: IEditorService, - @IFileService private readonly fileService: IFileService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService, - @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, - @ILogService private readonly logService: ILogService, - ) { - super(); - - this.addWorkspaceToRecentlyOpened(); - this.registerListeners(); - } - - private addWorkspaceToRecentlyOpened(): void { - const workspace = this.workspaceService.getWorkspace(); - switch (this.workspaceService.getWorkbenchState()) { - case WorkbenchState.FOLDER: - this.addRecentlyOpened([{ folderUri: workspace.folders[0].uri }]); - break; - case WorkbenchState.WORKSPACE: - this.addRecentlyOpened([{ workspace: { id: workspace.id, configPath: workspace.configuration! } }]); - break; - } - } - - private registerListeners(): void { - this._register(addDisposableListener(document, EventType.FULLSCREEN_CHANGE, () => { - if (document.fullscreenElement || (document).webkitFullscreenElement) { - browser.setFullscreen(true); - } else { - browser.setFullscreen(false); - } - })); - - this._register(addDisposableListener(document, EventType.WK_FULLSCREEN_CHANGE, () => { - if (document.fullscreenElement || (document).webkitFullscreenElement || (document).webkitIsFullScreen) { - browser.setFullscreen(true); - } else { - browser.setFullscreen(false); - } - })); - } - - isFocused(): Promise { - return Promise.resolve(this.hasFocus); - } - - async getRecentlyOpened(): Promise { - const recentlyOpenedRaw = this.storageService.get(SimpleWindowService.RECENTLY_OPENED_KEY, StorageScope.GLOBAL); - if (recentlyOpenedRaw) { - return restoreRecentlyOpened(JSON.parse(recentlyOpenedRaw), this.logService); - } - - return { workspaces: [], files: [] }; - } - - async addRecentlyOpened(recents: IRecent[]): Promise { - const recentlyOpened = await this.getRecentlyOpened(); - - recents.forEach(recent => { - if (isRecentFile(recent)) { - this.doRemoveFromRecentlyOpened(recentlyOpened, [recent.fileUri]); - recentlyOpened.files.unshift(recent); - } else if (isRecentFolder(recent)) { - this.doRemoveFromRecentlyOpened(recentlyOpened, [recent.folderUri]); - recentlyOpened.workspaces.unshift(recent); - } else { - this.doRemoveFromRecentlyOpened(recentlyOpened, [recent.workspace.configPath]); - recentlyOpened.workspaces.unshift(recent); - } - }); - - return this.saveRecentlyOpened(recentlyOpened); - } - - async removeFromRecentlyOpened(paths: URI[]): Promise { - const recentlyOpened = await this.getRecentlyOpened(); - - this.doRemoveFromRecentlyOpened(recentlyOpened, paths); - - return this.saveRecentlyOpened(recentlyOpened); - } - - private doRemoveFromRecentlyOpened(recentlyOpened: IRecentlyOpened, paths: URI[]): void { - recentlyOpened.files = recentlyOpened.files.filter(file => { - return !paths.some(path => path.toString() === file.fileUri.toString()); - }); - - recentlyOpened.workspaces = recentlyOpened.workspaces.filter(workspace => { - return !paths.some(path => path.toString() === (isRecentFolder(workspace) ? workspace.folderUri.toString() : workspace.workspace.configPath.toString())); - }); - } - - private async saveRecentlyOpened(data: IRecentlyOpened): Promise { - return this.storageService.store(SimpleWindowService.RECENTLY_OPENED_KEY, JSON.stringify(toStoreData(data)), StorageScope.GLOBAL); - } - - focusWindow(): Promise { - return Promise.resolve(); - } - - async openWindow(_uris: IURIToOpen[], _options?: IOpenSettings): Promise { - const { openFolderInNewWindow } = this.shouldOpenNewWindow(_options); - for (let i = 0; i < _uris.length; i++) { - const uri = _uris[i]; - if ('folderUri' in uri) { - const newAddress = `${document.location.origin}${document.location.pathname}?folder=${uri.folderUri.path}`; - if (openFolderInNewWindow) { - window.open(newAddress); - } else { - window.location.href = newAddress; - } - } - if ('workspaceUri' in uri) { - const newAddress = `${document.location.origin}${document.location.pathname}?workspace=${uri.workspaceUri.path}`; - if (openFolderInNewWindow) { - window.open(newAddress); - } else { - window.location.href = newAddress; - } - } - if ('fileUri' in uri) { - const inputs: IResourceEditor[] = await pathsToEditors([uri], this.fileService); - this.editorService.openEditors(inputs); - } - } - return Promise.resolve(); - } - - private shouldOpenNewWindow(_options: IOpenSettings = {}): { openFolderInNewWindow: boolean } { - const windowConfig = this.configurationService.getValue('window'); - const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */; - let openFolderInNewWindow = !!_options.forceNewWindow && !_options.forceReuseWindow; - if (!_options.forceNewWindow && !_options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { - openFolderInNewWindow = (openFolderInNewWindowConfig === 'on'); - } - return { openFolderInNewWindow }; - } -} - -registerSingleton(IWindowService, SimpleWindowService); - -//#endregion - -//#region Window - -export class SimpleWindowsService implements IWindowsService { - _serviceBrand: undefined; - - readonly onWindowOpen: Event = Event.None; - readonly onWindowFocus: Event = Event.None; - readonly onWindowBlur: Event = Event.None; - readonly onWindowMaximize: Event = Event.None; - readonly onWindowUnmaximize: Event = Event.None; - readonly onRecentlyOpenedChange: Event = Event.None; - - isFocused(_windowId: number): Promise { - return Promise.resolve(true); - } - - addRecentlyOpened(recents: IRecent[]): Promise { - return Promise.resolve(); - } - - removeFromRecentlyOpened(_paths: URI[]): Promise { - return Promise.resolve(); - } - - clearRecentlyOpened(): Promise { - return Promise.resolve(); - } - - getRecentlyOpened(_windowId: number): Promise { - return Promise.resolve({ - workspaces: [], - files: [] - }); - } - - focusWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - // Global methods - openWindow(_windowId: number, _uris: IURIToOpen[], _options: IOpenSettings): Promise { - return Promise.resolve(); - } - - getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - return Promise.resolve([]); - } - - getActiveWindowId(): Promise { - return Promise.resolve(0); - } -} - -registerSingleton(IWindowsService, SimpleWindowsService); - -//#endregion diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 24551d32b2df..c3ed8cdbd012 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -233,11 +233,6 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), 'default': true, 'tags': ['usesOnlineServices'] - }, - 'workbench.octiconsUpdate.enabled': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('workbench.octiconsUpdate.enabled', "Controls the visibility of the new Octicons style in the workbench.") } } }); @@ -286,14 +281,15 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common }, 'window.menuBarVisibility': { 'type': 'string', - 'enum': ['default', 'visible', 'toggle', 'hidden'], + 'enum': ['default', 'visible', 'toggle', 'hidden', 'compact'], 'enumDescriptions': [ nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."), nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), - nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden.") + nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden."), + nls.localize('window.menuBarVisibility.compact', "Menu is displayed as a compact button in the sidebar.") ], - 'default': 'default', + 'default': isWeb ? 'compact' : 'default', 'scope': ConfigurationScope.APPLICATION, 'description': nls.localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. By default, the menu bar will be visible, unless the window is full screen."), 'included': isWindows || isLinux || isWeb diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index a37c171da990..78c4549a2e29 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -148,7 +148,7 @@ export interface IViewDescriptor { // For contributed remote explorer views readonly group?: string; - readonly remoteAuthority?: string; + readonly remoteAuthority?: string | string[]; } export interface IViewDescriptorCollection extends IDisposable { diff --git a/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution.ts b/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution.ts new file mode 100644 index 000000000000..b22afc7496eb --- /dev/null +++ b/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { DefaultConfigurationExportHelper } from 'vs/workbench/contrib/configExporter/node/configurationExportHelper'; + +export class ExtensionPoints implements IWorkbenchContribution { + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + ) { + // Config Exporter + if (environmentService.configuration['export-default-configuration']) { + instantiationService.createInstance(DefaultConfigurationExportHelper); + } + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(ExtensionPoints, LifecyclePhase.Restored); diff --git a/src/vs/workbench/services/configuration/node/configurationExportHelper.ts b/src/vs/workbench/contrib/configExporter/node/configurationExportHelper.ts similarity index 100% rename from src/vs/workbench/services/configuration/node/configurationExportHelper.ts rename to src/vs/workbench/contrib/configExporter/node/configurationExportHelper.ts diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 9fb3a1bb99ce..8a0c68a97d8e 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event'; import { UnownedDisposable } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { WebviewEditorState } from 'vs/editor/common/modes'; +import { WebviewContentState } from 'vs/editor/common/modes'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -26,7 +26,7 @@ export class CustomFileEditorInput extends WebviewInput { private name?: string; private _hasResolved = false; private readonly _editorResource: URI; - private _state = WebviewEditorState.Readonly; + private _state = WebviewContentState.Readonly; constructor( resource: URI, @@ -38,7 +38,7 @@ export class CustomFileEditorInput extends WebviewInput { @IExtensionService private readonly _extensionService: IExtensionService, @IDialogService private readonly dialogService: IDialogService, ) { - super(id, viewType, '', undefined, webview); + super(id, viewType, '', webview); this._editorResource = resource; } @@ -99,13 +99,13 @@ export class CustomFileEditorInput extends WebviewInput { return super.resolve(); } - public setState(newState: WebviewEditorState): void { + public setState(newState: WebviewContentState): void { this._state = newState; this._onDidChangeDirty.fire(); } public isDirty() { - return this._state === WebviewEditorState.Dirty; + return this._state === WebviewContentState.Dirty; } public async confirmSave(): Promise { diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index b0d6506c923b..069bebd332cb 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -40,12 +40,13 @@ export class CustomEditoInputFactory extends WebviewEditorInputFactory { serializedEditorInput: string ): CustomFileEditorInput { const data = this.fromJson(serializedEditorInput); - const webviewInput = this.webviewService.reviveWebview(generateUuid(), data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { + const id = data.id || generateUuid(); + const webviewInput = this.webviewService.reviveWebview(id, data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { location: data.extensionLocation, id: data.extensionId } : undefined, data.group); - const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, generateUuid(), new UnownedDisposable(webviewInput.webview)); + const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, id, new UnownedDisposable(webviewInput.webview)); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 9882920c379e..7f4e1cae507b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -7,7 +7,7 @@ import { coalesce, distinct } from 'vs/base/common/arrays'; import * as glob from 'vs/base/common/glob'; import { UnownedDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { basename, DataUri } from 'vs/base/common/resources'; +import { basename, DataUri, isEqual } from 'vs/base/common/resources'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -16,6 +16,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -151,10 +153,11 @@ export class CustomEditorService implements ICustomEditorService { public createInput( resource: URI, viewType: string, - group: IEditorGroup | undefined + group: IEditorGroup | undefined, + options?: { readonly customClasses: string }, ): CustomFileEditorInput { const id = generateUuid(); - const webview = this.webviewService.createWebviewEditorOverlay(id, {}, {}); + const webview = this.webviewService.createWebviewEditorOverlay(id, { customClasses: options ? options.customClasses : undefined }, {}); const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, new UnownedDisposable(webview)); if (group) { input.updateGroup(group!.id); @@ -169,7 +172,7 @@ export class CustomEditorService implements ICustomEditorService { group?: IEditorGroup ): Promise { if (group) { - const existingEditors = group.editors.filter(editor => editor.getResource() && editor.getResource()!.toString() === resource.toString()); + const existingEditors = group.editors.filter(editor => editor.getResource() && isEqual(editor.getResource()!, resource)); if (existingEditors.length) { await this.editorService.replaceEditors([{ editor: existingEditors[0], @@ -200,50 +203,26 @@ export class CustomEditorContribution implements IWorkbenchContribution { group: IEditorGroup ): IOpenEditorOverride | undefined { if (editor instanceof CustomFileEditorInput) { - return undefined; // {{SQL CARBON EDIT}} strict-null-check + return undefined; } if (editor instanceof DiffEditorInput) { - const getCustomEditorOverrideForSubInput = (subInput: IEditorInput): EditorInput | undefined => { - if (subInput instanceof CustomFileEditorInput) { - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - const resource = subInput.getResource(); - if (!resource) { - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - const editors = distinct([ - ...this.customEditorService.getUserConfiguredCustomEditors(resource), - ...this.customEditorService.getContributedCustomEditors(resource), - ], editor => editor.id); - - // Always prefer the first editor in the diff editor case - return editors.length - ? this.customEditorService.createInput(resource, editors[0].id, group) - : undefined; - }; - - const modifiedOverride = getCustomEditorOverrideForSubInput(editor.modifiedInput); - const originalOverride = getCustomEditorOverrideForSubInput(editor.originalInput); - - if (modifiedOverride || originalOverride) { - return { - override: (async () => { - const input = new DiffEditorInput(editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput); - return this.editorService.openEditor(input, { ...options, ignoreOverrides: true }, group); - })(), - }; - } - - return undefined; + return this.onDiffEditorOpening(editor, options, group); } const resource = editor.getResource(); - if (!resource) { - return undefined; // {{SQL CARBON EDIT}} strict-null-check + if (resource) { + return this.onResourceEditorOpening(resource, editor, options, group); } + return undefined; + } + private onResourceEditorOpening( + resource: URI, + editor: IEditorInput, + options: ITextEditorOptions | undefined, + group: IEditorGroup + ): IOpenEditorOverride | undefined { const userConfiguredEditors = this.customEditorService.getUserConfiguredCustomEditors(resource); const contributedEditors = this.customEditorService.getContributedCustomEditors(resource); @@ -261,7 +240,7 @@ export class CustomEditorContribution implements IWorkbenchContribution { } for (const input of group.editors) { - if (input instanceof CustomFileEditorInput && input.getResource().toString() === resource.toString()) { + if (input instanceof CustomFileEditorInput && isEqual(input.getResource(), resource)) { return { override: group.openEditor(input, options).then(withNullAsUndefined) }; @@ -291,30 +270,74 @@ export class CustomEditorContribution implements IWorkbenchContribution { })() }; } + + private onDiffEditorOpening( + editor: DiffEditorInput, + options: ITextEditorOptions | undefined, + group: IEditorGroup + ): IOpenEditorOverride | undefined { + const getCustomEditorOverrideForSubInput = (subInput: IEditorInput, customClasses: string): EditorInput | undefined => { + if (subInput instanceof CustomFileEditorInput) { + return undefined; + } + const resource = subInput.getResource(); + if (!resource) { + return undefined; + } + + const editors = distinct([ + ...this.customEditorService.getUserConfiguredCustomEditors(resource), + ...this.customEditorService.getContributedCustomEditors(resource), + ], editor => editor.id); + + if (!editors.length) { + return undefined; + } + // Always prefer the first editor in the diff editor case + return this.customEditorService.createInput(resource, editors[0].id, group, { customClasses }); + }; + + const modifiedOverride = getCustomEditorOverrideForSubInput(editor.modifiedInput, 'modified'); + const originalOverride = getCustomEditorOverrideForSubInput(editor.originalInput, 'original'); + + if (modifiedOverride || originalOverride) { + return { + override: (async () => { + const input = new DiffEditorInput(editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput); + return this.editorService.openEditor(input, { ...options, ignoreOverrides: true }, group); + })(), + }; + } + + return undefined; + } } function matches(selector: CustomEditorSelector, resource: URI): boolean { if (resource.scheme === Schemas.data) { + if (!selector.mime) { + return false; + } const metadata = DataUri.parseMetaData(resource); const mime = metadata.get(DataUri.META_DATA_MIME); - if (!selector.mime || !mime) { + if (!mime) { return false; } return glob.match(selector.mime, mime.toLowerCase()); } - if (!selector.filenamePattern && !selector.scheme) { - return false; - } if (selector.filenamePattern) { - if (!glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { - return false; + if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { + return true; } } - if (selector.scheme) { - if (resource.scheme !== selector.scheme) { - return false; - } - } - return true; + + return false; } + +registerThemingParticipant((theme, collector) => { + const shadow = theme.getColor(colorRegistry.scrollbarShadow); + if (shadow) { + collector.addRule(`.webview.modified { box-shadow: -6px 0 5px -5px ${shadow}; }`); + } +}); diff --git a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts index 206730c896ef..d6fd28dc764d 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts @@ -54,9 +54,9 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'string', description: nls.localize('editor.editorAssociations.viewType', "Editor view type."), }, - 'scheme': { + 'mime': { type: 'string', - description: nls.localize('editor.editorAssociations.scheme', "Uri scheme the editor should be used for."), + description: nls.localize('editor.editorAssociations.mime', "Mime type the editor should be used for. This is used for binary files."), }, 'filenamePattern': { type: 'string', diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index b827fb333d24..0ee39070952c 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -17,7 +17,7 @@ export interface ICustomEditorService { getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; - createInput(resource: URI, viewType: string, group: IEditorGroup | undefined): EditorInput; + createInput(resource: URI, viewType: string, group: IEditorGroup | undefined, options?: { readonly customClasses: string }): EditorInput; openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; @@ -29,7 +29,6 @@ export const enum CustomEditorDiscretion { } export interface CustomEditorSelector { - readonly scheme?: string; readonly filenamePattern?: string; readonly mime?: string; } diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 5f6fe629a40a..e8c2dc800560 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -91,7 +91,8 @@ export function renderExpressionValue(expressionOrValue: IExpressionContainer | } if (options.linkDetector) { container.textContent = ''; - container.appendChild(options.linkDetector.handleLinks(value)); + const session = (expressionOrValue instanceof ExpressionContainer) ? expressionOrValue.getSession() : undefined; + container.appendChild(options.linkDetector.linkify(value, false, session ? session.root : undefined)); } else { container.textContent = value; } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 1aa03d5b76df..3499c5de7c83 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -312,12 +312,12 @@ export class CallStackView extends ViewletPanel { } const actions: IAction[] = []; - const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: false }, actions, this.contextMenuService); + const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => element, + getActionsContext: () => element && element instanceof StackFrame ? element.getId() : undefined, onHide: () => dispose(actionsDisposable) }); } diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 1c3844aa852c..bd1d086f7f51 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -128,7 +128,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi const debugCategory = nls.localize('debugCategory', "Debug"); -registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }), 'Debug: Start Debugging', debugCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); diff --git a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts index d2b02188be3f..184abf71a59a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts +++ b/src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts @@ -7,12 +7,13 @@ import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { RGBA, Color } from 'vs/base/common/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; /** * @param text The content to stylize. * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. */ -export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService): HTMLSpanElement { +export function handleANSIOutput(text: string, linkDetector: LinkDetector, themeService: IThemeService, debugSession: IDebugSession): HTMLSpanElement { const root: HTMLSpanElement = document.createElement('span'); const textLength: number = text.length; @@ -53,7 +54,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme if (sequenceFound) { // Flush buffer with previous styles. - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, customFgColor, customBgColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor); buffer = ''; @@ -99,7 +100,7 @@ export function handleANSIOutput(text: string, linkDetector: LinkDetector, theme // Flush remaining text buffer if not empty. if (buffer) { - appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, customFgColor, customBgColor); + appendStylizedStringToContainer(root, buffer, styleNames, linkDetector, debugSession, customFgColor, customBgColor); } return root; @@ -267,6 +268,7 @@ export function appendStylizedStringToContainer( stringContent: string, cssClasses: string[], linkDetector: LinkDetector, + debugSession: IDebugSession, customTextColor?: RGBA, customBackgroundColor?: RGBA ): void { @@ -274,7 +276,7 @@ export function appendStylizedStringToContainer( return; } - const container = linkDetector.handleLinks(stringContent); + const container = linkDetector.linkify(stringContent, true, debugSession.root); container.className = cssClasses.join(' '); if (customTextColor) { diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index e4f2f461bd17..9e7385b80455 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -10,7 +10,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, REPL_ID, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable, Breakpoint, FunctionBreakpoint, Thread, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -60,9 +60,16 @@ export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); export const STOP_LABEL = nls.localize('stop', "Stop"); export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); -function getThreadAndRun(accessor: ServicesAccessor, thread: IThread | undefined, run: (thread: IThread) => Promise, ): void { +function getThreadAndRun(accessor: ServicesAccessor, threadId: number | undefined, run: (thread: IThread) => Promise): void { const debugService = accessor.get(IDebugService); - if (!(thread instanceof Thread)) { + let thread: IThread | undefined; + if (threadId) { + debugService.getModel().getSessions().forEach(s => { + if (!thread) { + thread = s.getThread(threadId); + } + }); + } else { thread = debugService.getViewModel().focusedThread; if (!thread) { const focusedSession = debugService.getViewModel().focusedSession; @@ -76,36 +83,58 @@ function getThreadAndRun(accessor: ServicesAccessor, thread: IThread | undefined } } +function getFrame(debugService: IDebugService, frameId: string | undefined): IStackFrame | undefined { + if (!frameId) { + return undefined; + } + + const sessions = debugService.getModel().getSessions(); + for (let s of sessions) { + for (let t of s.getAllThreads()) { + for (let sf of t.getCallStack()) { + if (sf.getId() === frameId) { + return sf; + } + } + } + } + + return undefined; +} + export function registerCommands(): void { CommandsRegistry.registerCommand({ id: COPY_STACK_TRACE_ID, - handler: async (accessor: ServicesAccessor, _: string, frame: IStackFrame) => { + handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { const textResourcePropertiesService = accessor.get(ITextResourcePropertiesService); const clipboardService = accessor.get(IClipboardService); - const eol = textResourcePropertiesService.getEOL(frame.source.uri); - await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); + let frame = getFrame(accessor.get(IDebugService), frameId); + if (frame) { + const eol = textResourcePropertiesService.getEOL(frame.source.uri); + await clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); + } } }); CommandsRegistry.registerCommand({ id: REVERSE_CONTINUE_ID, - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.reverseContinue()); + handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + getThreadAndRun(accessor, threadId, thread => thread.reverseContinue()); } }); CommandsRegistry.registerCommand({ id: STEP_BACK_ID, - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.stepBack()); + handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + getThreadAndRun(accessor, threadId, thread => thread.stepBack()); } }); CommandsRegistry.registerCommand({ id: TERMINATE_THREAD_ID, - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.terminate()); + handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + getThreadAndRun(accessor, threadId, thread => thread.terminate()); } }); @@ -184,8 +213,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F10, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.next()); + handler: (accessor: ServicesAccessor, threadId: number) => { + getThreadAndRun(accessor, threadId, (thread: IThread) => thread.next()); } }); @@ -194,8 +223,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib + 10, // Have a stronger weight to have priority over full screen when debugging primary: KeyCode.F11, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.stepIn()); + handler: (accessor: ServicesAccessor, threadId: number) => { + getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepIn()); } }); @@ -204,8 +233,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F11, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.stepOut()); + handler: (accessor: ServicesAccessor, threadId: number) => { + getThreadAndRun(accessor, threadId, (thread: IThread) => thread.stepOut()); } }); @@ -214,28 +243,16 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F6, when: CONTEXT_DEBUG_STATE.isEqualTo('running'), - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - const debugService = accessor.get(IDebugService); - if (!(thread instanceof Thread)) { - thread = debugService.getViewModel().focusedThread; - if (!thread) { - const session = debugService.getViewModel().focusedSession; - const threads = session && session.getAllThreads(); - thread = threads && threads.length ? threads[0] : undefined; - } - } - - if (thread) { - thread.pause().then(undefined, onUnexpectedError); - } + handler: (accessor: ServicesAccessor, threadId: number) => { + getThreadAndRun(accessor, threadId, thread => thread.pause()); } }); CommandsRegistry.registerCommand({ id: DISCONNECT_ID, - handler: (accessor: ServicesAccessor, _: string, session: IDebugSession | undefined) => { + handler: (accessor: ServicesAccessor, sessionId: string | undefined) => { const debugService = accessor.get(IDebugService); - session = session || debugService.getViewModel().focusedSession; + const session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession; debugService.stopSession(session).then(undefined, onUnexpectedError); } }); @@ -245,16 +262,14 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, _: string, session: IDebugSession | undefined) => { + handler: (accessor: ServicesAccessor, sessionId: string | undefined) => { const debugService = accessor.get(IDebugService); - if (!session || !session.getId) { - session = debugService.getViewModel().focusedSession; - const configurationService = accessor.get(IConfigurationService); - const showSubSessions = configurationService.getValue('debug').showSubSessionsInToolBar; - // Stop should be sent to the root parent session - while (!showSubSessions && session && session.parentSession) { - session = session.parentSession; - } + let session = debugService.getModel().getSession(sessionId) || debugService.getViewModel().focusedSession; + const configurationService = accessor.get(IConfigurationService); + const showSubSessions = configurationService.getValue('debug').showSubSessionsInToolBar; + // Stop should be sent to the root parent session + while (!showSubSessions && session && session.parentSession) { + session = session.parentSession; } debugService.stopSession(session).then(undefined, onUnexpectedError); @@ -263,13 +278,12 @@ export function registerCommands(): void { CommandsRegistry.registerCommand({ id: RESTART_FRAME_ID, - handler: (accessor: ServicesAccessor, _: string, frame: IStackFrame | undefined) => { + handler: async (accessor: ServicesAccessor, _: string, frameId: string | undefined) => { const debugService = accessor.get(IDebugService); - if (!frame) { - frame = debugService.getViewModel().focusedStackFrame; + let frame = getFrame(debugService, frameId); + if (frame) { + await frame.restart(); } - - return frame!.restart(); } }); @@ -278,8 +292,8 @@ export function registerCommands(): void { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, when: CONTEXT_IN_DEBUG_MODE, - handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.continue()); + handler: (accessor: ServicesAccessor, threadId: number | undefined) => { + getThreadAndRun(accessor, threadId, thread => thread.continue()); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index d182250281da..420393a5f649 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -21,7 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugEditorContribution, IDebugService, State, EDITOR_CONTRIBUTION_ID, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugEditorContribution, IDebugService, State, EDITOR_CONTRIBUTION_ID, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { Position } from 'vs/editor/common/core/position'; @@ -284,18 +284,18 @@ class DebugEditorContribution implements IDebugEditorContribution { } else if (sameUri) { focusedSf.thread.exceptionInfo.then(exceptionInfo => { if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) { - this.showExceptionWidget(exceptionInfo, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); + this.showExceptionWidget(exceptionInfo, this.debugService.getViewModel().focusedSession, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn); } }); } } - private showExceptionWidget(exceptionInfo: IExceptionInfo, lineNumber: number, column: number): void { + private showExceptionWidget(exceptionInfo: IExceptionInfo, debugSession: IDebugSession | undefined, lineNumber: number, column: number): void { if (this.exceptionWidget) { this.exceptionWidget.dispose(); } - this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo); + this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, debugSession); this.exceptionWidget.show({ lineNumber, column }, 0); this.editor.revealLine(lineNumber); } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 9f56965b1653..f4bc1c74e3c1 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -22,7 +22,7 @@ import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspac import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; @@ -70,7 +70,7 @@ export class DebugSession implements IDebugSession { options: IDebugSessionOptions | undefined, @IDebugService private readonly debugService: IDebugService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IConfigurationService private readonly configurationService: IConfigurationService, @IViewletService private readonly viewletService: IViewletService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -86,6 +86,7 @@ export class DebugSession implements IDebugSession { } else { this.repl = (this.parentSession as DebugSession).repl; } + this.repl.onDidChangeElements(() => this._onDidChangeREPLElements.fire()); } getId(): string { @@ -731,7 +732,7 @@ export class DebugSession implements IDebugSession { } if (this.configurationService.getValue('debug').focusWindowOnBreak) { - this.windowService.focusWindow(); + this.hostService.focus(); } } } @@ -967,25 +968,19 @@ export class DebugSession implements IDebugSession { removeReplExpressions(): void { this.repl.removeReplExpressions(); - this._onDidChangeREPLElements.fire(); } async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { - const expressionEvaluated = this.repl.addReplExpression(this, stackFrame, name); - this._onDidChangeREPLElements.fire(); - await expressionEvaluated; - this._onDidChangeREPLElements.fire(); + await this.repl.addReplExpression(this, stackFrame, name); // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. variableSetEmitter.fire(); } appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void { - this.repl.appendToRepl(data, severity, source); - this._onDidChangeREPLElements.fire(); + this.repl.appendToRepl(this, data, severity, source); } logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { this.repl.logToRepl(this, sev, args, frame); - this._onDidChangeREPLElements.fire(); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts index 2f32e2c0df27..deb81127997b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDebugService, State, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarEntry, IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 1d2a3e20fa99..868ffe827dc9 100644 --- a/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug'; +import { IExceptionInfo, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; @@ -27,7 +27,7 @@ export class ExceptionWidget extends ZoneWidget { private _backgroundColor?: Color; - constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo, + constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo, private debugSession: IDebugSession | undefined, @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -80,7 +80,7 @@ export class ExceptionWidget extends ZoneWidget { if (this.exceptionInfo.details && this.exceptionInfo.details.stackTrace) { let stackTrace = $('.stack-trace'); const linkDetector = this.instantiationService.createInstance(LinkDetector); - const linkedStackTrace = linkDetector.handleLinks(this.exceptionInfo.details.stackTrace); + const linkedStackTrace = linkDetector.linkify(this.exceptionInfo.details.stackTrace, true, this.debugSession ? this.debugSession.root : undefined); stackTrace.appendChild(linkedStackTrace); dom.append(container, stackTrace); } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index e24d0ff573d3..277ce7f71e9d 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -20,7 +20,6 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - // @IWindowService windowService: IWindowService, // TODO@weinand TODO@isidorn cyclic dependency? @IEnvironmentService environmentService: IEnvironmentService ) { const connection = remoteAgentService.getConnection(); diff --git a/src/vs/workbench/contrib/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts index ddd4b2d2741a..7690c87a850c 100644 --- a/src/vs/workbench/contrib/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -3,160 +3,199 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as strings from 'vs/base/common/strings'; -import { isAbsolute } from 'vs/base/common/path'; -import { URI as uri } from 'vs/base/common/uri'; -import { isMacintosh } from 'vs/base/common/platform'; -import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import * as osPath from 'vs/base/common/path'; +import * as platform from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class LinkDetector { - private static readonly MAX_LENGTH = 500; - private static FILE_LOCATION_PATTERNS: RegExp[] = [ - // group 0: full path with line and column - // group 1: full path without line and column, matched by `*.*` in the end to work only on paths with extensions in the end (s.t. node:10352 would not match) - // group 2: drive letter on windows with trailing backslash or leading slash on mac/linux - // group 3: line number, matched by (:(\d+)) - // group 4: column number, matched by ((?::(\d+))?) - // e.g.: at Context. (c:\Users\someone\Desktop\mocha-runner\test\test.js:26:11) - /(?![\(])(?:file:\/\/)?((?:([a-zA-Z]+:)|[^\(\)<>\'\"\[\]:\s]+)(?:[\\/][^\(\)<>\'\"\[\]:]*)?\.[a-zA-Z]+[0-9]*):(\d+)(?::(\d+))?/g - ]; +const CONTROL_CODES = '\\u0000-\\u0020\\u007f-\\u009f'; +const WEB_LINK_REGEX = new RegExp('(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s' + CONTROL_CODES + '"]{2,}[^\\s' + CONTROL_CODES + '"\')}\\],:;.!?]', 'ug'); + +const WIN_ABSOLUTE_PATH = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/; +const WIN_RELATIVE_PATH = /(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/; +const WIN_PATH = new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`); +const POSIX_PATH = /((?:\~|\.)?(?:\/[\w\.-]*)+)/; +const LINE_COLUMN = /(?:\:([\d]+))?(?:\:([\d]+))?/; +const PATH_LINK_REGEX = new RegExp(`${platform.isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`, 'g'); + +const MAX_LENGTH = 2000; +type LinkKind = 'web' | 'path' | 'text'; +type LinkPart = { + kind: LinkKind; + value: string; + captures: string[]; +}; + +export class LinkDetector { constructor( - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IFileService private readonly fileService: IFileService, + @IOpenerService private readonly openerService: IOpenerService, + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { // noop } /** - * Matches and handles absolute file links in the string provided. - * Returns element that wraps the processed string, where matched links are replaced by and unmatched parts are surrounded by elements. + * Matches and handles web urls, absolute and relative file links in the string provided. + * Returns element that wraps the processed string, where matched links are replaced by . * 'onclick' event is attached to all anchored links that opens them in the editor. - * Each line of the text, even if it contains no links, is wrapped in a and added as a child of the returned . + * When splitLines is true, each line of the text, even if it contains no links, is wrapped in a + * and added as a child of the returned . */ - handleLinks(text: string): HTMLElement { + linkify(text: string, splitLines?: boolean, workspaceFolder?: IWorkspaceFolder): HTMLElement { + if (splitLines) { + const lines = text.split('\n'); + for (let i = 0; i < lines.length - 1; i++) { + lines[i] = lines[i] + '\n'; + } + if (!lines[lines.length - 1]) { + // Remove the last element ('') that split added. + lines.pop(); + } + const elements = lines.map(line => this.linkify(line, false, workspaceFolder)); + if (elements.length === 1) { + // Do not wrap single line with extra span. + return elements[0]; + } + const container = document.createElement('span'); + elements.forEach(e => container.appendChild(e)); + return container; + } + const container = document.createElement('span'); + for (const part of this.detectLinks(text)) { + try { + switch (part.kind) { + case 'text': + container.appendChild(document.createTextNode(part.value)); + break; + case 'web': + container.appendChild(this.createWebLink(part.value)); + break; + case 'path': + const path = part.captures[0]; + const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0; + const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0; + container.appendChild(this.createPathLink(part.value, path, lineNumber, columnNumber, workspaceFolder)); + break; + } + } catch (e) { + container.appendChild(document.createTextNode(part.value)); + } + } + return container; + } - // Handle the text one line at a time - const lines = text.split('\n'); + private createWebLink(url: string): Node { + const link = this.createLink(url); + const uri = URI.parse(url); + this.decorateLink(link, () => this.openerService.open(uri)); + return link; + } - if (strings.endsWith(text, '\n')) { - // Remove the last element ('') that split added - lines.pop(); + private createPathLink(text: string, path: string, lineNumber: number, columnNumber: number, workspaceFolder: IWorkspaceFolder | undefined): Node { + if (path[0] === '/' && path[1] === '/') { + // Most likely a url part which did not match, for example ftp://path. + return document.createTextNode(text); } - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; - - // Re-introduce the newline for every line except the last (unless the last line originally ended with a newline) - if (i < lines.length - 1 || strings.endsWith(text, '\n')) { - line += '\n'; + if (path[0] === '.') { + if (!workspaceFolder) { + return document.createTextNode(text); } + const uri = workspaceFolder.toResource(path); + const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } }; + const link = this.createLink(text); + this.decorateLink(link, () => this.editorService.openEditor({ resource: uri, options })); + return link; + } - // Don't handle links for lines that are too long - if (line.length > LinkDetector.MAX_LENGTH) { - let span = document.createElement('span'); - span.textContent = line; - container.appendChild(span); - continue; + if (path[0] === '~') { + path = osPath.join(this.environmentService.userHome, path.substring(1)); + } + + const link = this.createLink(text); + const uri = URI.file(osPath.normalize(path)); + this.fileService.resolve(uri).then(stat => { + if (stat.isDirectory) { + return; } + const options = { selection: { startLineNumber: lineNumber, startColumn: columnNumber } }; + this.decorateLink(link, () => this.editorService.openEditor({ resource: uri, options })); + }); + return link; + } - const lineContainer = document.createElement('span'); - - for (let pattern of LinkDetector.FILE_LOCATION_PATTERNS) { - // Reset the state of the pattern - pattern = new RegExp(pattern); - let lastMatchIndex = 0; - - let match = pattern.exec(line); - - while (match !== null) { - let resource: uri | null = isAbsolute(match[1]) ? uri.file(match[1]) : null; - - if (!resource) { - match = pattern.exec(line); - continue; - } - - const textBeforeLink = line.substring(lastMatchIndex, match.index); - if (textBeforeLink) { - // textBeforeLink may have matches for other patterns, so we run handleLinks on it before adding it. - lineContainer.appendChild(this.handleLinks(textBeforeLink)); - } - - const link = document.createElement('a'); - link.textContent = line.substr(match.index, match[0].length); - link.title = isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link"); - lineContainer.appendChild(link); - const lineNumber = Number(match[3]); - const columnNumber = match[4] ? Number(match[4]) : undefined; - link.onclick = (e) => this.onLinkClick(new StandardMouseEvent(e), resource!, lineNumber, columnNumber); - link.onmousemove = (event) => link.classList.toggle('pointer', isMacintosh ? event.metaKey : event.ctrlKey); - link.onmouseleave = () => link.classList.remove('pointer'); - - lastMatchIndex = pattern.lastIndex; - const currentMatch = match; - match = pattern.exec(line); - - // Append last string part if no more link matches - if (!match) { - const textAfterLink = line.substr(currentMatch.index + currentMatch[0].length); - if (textAfterLink) { - // textAfterLink may have matches for other patterns, so we run handleLinks on it before adding it. - lineContainer.appendChild(this.handleLinks(textAfterLink)); - } - } - } + private createLink(text: string): HTMLElement { + const link = document.createElement('a'); + link.textContent = text; + return link; + } - // If we found any matches for this pattern, don't check any more patterns. Other parts of the line will be checked for the other patterns due to the recursion. - if (lineContainer.hasChildNodes()) { - break; - } + private decorateLink(link: HTMLElement, onclick: () => void) { + link.classList.add('link'); + link.title = platform.isMacintosh ? nls.localize('fileLinkMac', "Cmd + click to follow link") : nls.localize('fileLink', "Ctrl + click to follow link"); + link.onmousemove = (event) => link.classList.toggle('pointer', platform.isMacintosh ? event.metaKey : event.ctrlKey); + link.onmouseleave = () => link.classList.remove('pointer'); + link.onclick = (event) => { + const selection = window.getSelection(); + if (!selection || selection.type === 'Range') { + return; // do not navigate when user is selecting } - - if (lines.length === 1) { - if (lineContainer.hasChildNodes()) { - // Adding lineContainer to container would introduce an unnecessary surrounding span since there is only one line, so instead we just return lineContainer - return lineContainer; - } else { - container.textContent = line; - } - } else { - if (lineContainer.hasChildNodes()) { - // Add this line to the container - container.appendChild(lineContainer); - } else { - // No links were added, but we still need to surround the unmodified line with a span before adding it - let span = document.createElement('span'); - span.textContent = line; - container.appendChild(span); - } + if (!(platform.isMacintosh ? event.metaKey : event.ctrlKey)) { + return; } - } - - return container; + event.preventDefault(); + event.stopImmediatePropagation(); + onclick(); + }; } - private onLinkClick(event: IMouseEvent, resource: uri, line: number, column: number = 0): void { - const selection = window.getSelection(); - if (!selection || selection.type === 'Range') { - return; // do not navigate when user is selecting - } - if (!(isMacintosh ? event.metaKey : event.ctrlKey)) { - return; + private detectLinks(text: string): LinkPart[] { + if (text.length > MAX_LENGTH) { + return [{ kind: 'text', value: text, captures: [] }]; } - event.preventDefault(); + const regexes: RegExp[] = [WEB_LINK_REGEX, PATH_LINK_REGEX]; + const kinds: LinkKind[] = ['web', 'path']; + const result: LinkPart[] = []; - this.editorService.openEditor({ - resource, - options: { - selection: { - startLineNumber: line, - startColumn: column + const splitOne = (text: string, regexIndex: number) => { + if (regexIndex >= regexes.length) { + result.push({ value: text, kind: 'text', captures: [] }); + return; + } + const regex = regexes[regexIndex]; + let currentIndex = 0; + let match; + regex.lastIndex = 0; + while ((match = regex.exec(text)) !== null) { + const stringBeforeMatch = text.substring(currentIndex, match.index); + if (stringBeforeMatch) { + splitOne(stringBeforeMatch, regexIndex + 1); } + const value = match[0]; + result.push({ + value: value, + kind: kinds[regexIndex], + captures: match.slice(1) + }); + currentIndex = match.index + value.length; } - }); + const stringAfterMatches = text.substring(currentIndex); + if (stringAfterMatches) { + splitOne(stringAfterMatches, regexIndex + 1); + } + }; + + splitOne(text, 0); + return result; } } diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 1364e6a4c75c..38341b722c4d 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -167,11 +167,11 @@ /* Links */ -.monaco-workbench .monaco-list-row .expression .value a { +.monaco-workbench .monaco-list-row .expression .value a.link { text-decoration: underline; } -.monaco-workbench .monaco-list-row .expression .value a.pointer { +.monaco-workbench .monaco-list-row .expression .value a.link.pointer { cursor: pointer; } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index f36708fb792f..c20a38d4418c 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -701,7 +701,7 @@ class ReplSimpleElementsRenderer implements ITreeRenderer(); + readonly onDidChangeElements = this._onDidChangeElements.event; getReplElements(): IReplElement[] { return this.replElements; @@ -119,12 +123,12 @@ export class ReplModel { this.addReplElement(result); } - appendToRepl(data: string | IExpression, sev: severity, source?: IReplElementSource): void { + appendToRepl(session: IDebugSession, data: string | IExpression, sev: severity, source?: IReplElementSource): void { const clearAnsiSequence = '\u001b[2J'; if (typeof data === 'string' && data.indexOf(clearAnsiSequence) >= 0) { // [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php this.removeReplExpressions(); - this.appendToRepl(nls.localize('consoleCleared', "Console was cleared"), severity.Ignore); + this.appendToRepl(session, nls.localize('consoleCleared', "Console was cleared"), severity.Ignore); data = data.substr(data.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length); } @@ -133,7 +137,7 @@ export class ReplModel { if (previousElement instanceof SimpleReplElement && previousElement.severity === sev && !endsWith(previousElement.value, '\n') && !endsWith(previousElement.value, '\r\n')) { previousElement.value += data; } else { - const element = new SimpleReplElement(`topReplElement:${topReplElementCounter++}`, data, sev, source); + const element = new SimpleReplElement(session, `topReplElement:${topReplElementCounter++}`, data, sev, source); this.addReplElement(element); } } else { @@ -149,6 +153,7 @@ export class ReplModel { if (this.replElements.length > MAX_REPL_LENGTH) { this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH); } + this._onDidChangeElements.fire(); } logToRepl(session: IDebugSession, sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { @@ -185,12 +190,12 @@ export class ReplModel { // flush any existing simple values logged if (simpleVals.length) { - this.appendToRepl(simpleVals.join(' '), sev, source); + this.appendToRepl(session, simpleVals.join(' '), sev, source); simpleVals = []; } // show object - this.appendToRepl(new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source); + this.appendToRepl(session, new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source); } // string: watch out for % replacement directive @@ -220,13 +225,14 @@ export class ReplModel { // flush simple values // always append a new line for output coming from an extension such that separate logs go to separate lines #23695 if (simpleVals.length) { - this.appendToRepl(simpleVals.join(' ') + '\n', sev, source); + this.appendToRepl(session, simpleVals.join(' ') + '\n', sev, source); } } removeReplExpressions(): void { if (this.replElements.length > 0) { this.replElements = []; + this._onDidChangeElements.fire(); } } } diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index 15dad5a3e2b5..a35dc0a7c7b7 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -7,23 +7,22 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( @IMainProcessService readonly mainProcessService: IMainProcessService, - @IWindowsService private readonly windowsService: IWindowsService + @IElectronService private readonly electronService: IElectronService ) { super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); } openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - // TODO@Isidor use debug IPC channel - return (this.windowsService as WindowsService).openExtensionDevelopmentHostWindow(args, env); + // TODO@Isidor move into debug IPC channel (https://github.com/microsoft/vscode/issues/81060) + return this.electronService.openExtensionDevelopmentHostWindow(args, env); } } diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts index a4c4bcdb3225..006d00079cca 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -14,9 +14,14 @@ import { Color, RGBA } from 'vs/base/common/color'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService, TestTheme } from 'vs/platform/theme/test/common/testThemeService'; import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; +import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; +import { NullOpenerService } from 'vs/platform/opener/common/opener'; suite('Debug - ANSI Handling', () => { + let model: DebugModel; + let session: DebugSession; let linkDetector: LinkDetector; let themeService: IThemeService; @@ -24,6 +29,9 @@ suite('Debug - ANSI Handling', () => { * Instantiate services for use by the functions being tested. */ setup(() => { + model = new DebugModel([], [], [], [], [], { isDirty: (e: any) => false }); + session = new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); + const instantiationService: TestInstantiationService = workbenchInstantiationService(); linkDetector = instantiationService.createInstance(LinkDetector); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts index 5f6b0589dad2..1165ff59514b 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts @@ -464,11 +464,12 @@ suite('Debug - Model', () => { // Repl output test('repl output', () => { + const session = createMockSession(model); const repl = new ReplModel(); - repl.appendToRepl('first line\n', severity.Error); - repl.appendToRepl('second line ', severity.Error); - repl.appendToRepl('third line ', severity.Error); - repl.appendToRepl('fourth line', severity.Error); + repl.appendToRepl(session, 'first line\n', severity.Error); + repl.appendToRepl(session, 'second line ', severity.Error); + repl.appendToRepl(session, 'third line ', severity.Error); + repl.appendToRepl(session, 'fourth line', severity.Error); let elements = repl.getReplElements(); assert.equal(elements.length, 2); @@ -477,14 +478,14 @@ suite('Debug - Model', () => { assert.equal(elements[1].value, 'second line third line fourth line'); assert.equal(elements[1].severity, severity.Error); - repl.appendToRepl('1', severity.Warning); + repl.appendToRepl(session, '1', severity.Warning); elements = repl.getReplElements(); assert.equal(elements.length, 3); assert.equal(elements[2].value, '1'); assert.equal(elements[2].severity, severity.Warning); const keyValueObject = { 'key1': 2, 'key2': 'value' }; - repl.appendToRepl(new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info); + repl.appendToRepl(session, new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info); const element = repl.getReplElements()[3]; assert.equal(element.value, 'Object'); assert.deepEqual(element.valueObj, keyValueObject); @@ -492,11 +493,11 @@ suite('Debug - Model', () => { repl.removeReplExpressions(); assert.equal(repl.getReplElements().length, 0); - repl.appendToRepl('1\n', severity.Info); - repl.appendToRepl('2', severity.Info); - repl.appendToRepl('3\n4', severity.Info); - repl.appendToRepl('5\n', severity.Info); - repl.appendToRepl('6', severity.Info); + repl.appendToRepl(session, '1\n', severity.Info); + repl.appendToRepl(session, '2', severity.Info); + repl.appendToRepl(session, '3\n4', severity.Info); + repl.appendToRepl(session, '5\n', severity.Info); + repl.appendToRepl(session, '6', severity.Info); elements = repl.getReplElements(); assert.equal(elements.length, 3); assert.equal(elements[0], '1\n'); @@ -512,7 +513,11 @@ suite('Debug - Model', () => { const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' }); const child3 = createMockSession(model, 'child3', { parentSession: parent }); + let parentChanges = 0; + parent.onDidChangeReplElements(() => ++parentChanges); + parent.appendToRepl('1\n', severity.Info); + assert.equal(parentChanges, 1); assert.equal(parent.getReplElements().length, 1); assert.equal(child1.getReplElements().length, 0); assert.equal(child2.getReplElements().length, 1); @@ -520,6 +525,7 @@ suite('Debug - Model', () => { assert.equal(child3.getReplElements().length, 0); grandChild.appendToRepl('1\n', severity.Info); + assert.equal(parentChanges, 2); assert.equal(parent.getReplElements().length, 2); assert.equal(child1.getReplElements().length, 0); assert.equal(child2.getReplElements().length, 2); @@ -527,6 +533,7 @@ suite('Debug - Model', () => { assert.equal(child3.getReplElements().length, 0); child3.appendToRepl('1\n', severity.Info); + assert.equal(parentChanges, 2); assert.equal(parent.getReplElements().length, 2); assert.equal(child1.getReplElements().length, 0); assert.equal(child2.getReplElements().length, 2); @@ -534,6 +541,7 @@ suite('Debug - Model', () => { assert.equal(child3.getReplElements().length, 1); child1.appendToRepl('1\n', severity.Info); + assert.equal(parentChanges, 2); assert.equal(parent.getReplElements().length, 2); assert.equal(child1.getReplElements().length, 1); assert.equal(child2.getReplElements().length, 2); diff --git a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index 267ffbe4b40a..0ce3d5ce5600 100644 --- a/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -8,6 +8,8 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { isWindows } from 'vs/base/common/platform'; +import { WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { URI } from 'vs/base/common/uri'; suite('Debug - Link Detector', () => { @@ -22,19 +24,18 @@ suite('Debug - Link Detector', () => { }); /** - * Assert that a given Element is an anchor element with an onClick event. + * Assert that a given Element is an anchor element. * * @param element The Element to verify. */ function assertElementIsLink(element: Element) { assert(element instanceof HTMLAnchorElement); - assert.notEqual(null, (element as HTMLAnchorElement).onclick); } test('noLinks', () => { const input = 'I am a string'; const expectedOutput = 'I am a string'; - const output = linkDetector.handleLinks(input); + const output = linkDetector.linkify(input); assert.equal(0, output.children.length); assert.equal('SPAN', output.tagName); @@ -44,7 +45,17 @@ suite('Debug - Link Detector', () => { test('trailingNewline', () => { const input = 'I am a string\n'; const expectedOutput = 'I am a string\n'; - const output = linkDetector.handleLinks(input); + const output = linkDetector.linkify(input); + + assert.equal(0, output.children.length); + assert.equal('SPAN', output.tagName); + assert.equal(expectedOutput, output.outerHTML); + }); + + test('trailingNewlineSplit', () => { + const input = 'I am a string\n'; + const expectedOutput = 'I am a string\n'; + const output = linkDetector.linkify(input, true); assert.equal(0, output.children.length); assert.equal('SPAN', output.tagName); @@ -52,65 +63,71 @@ suite('Debug - Link Detector', () => { }); test('singleLineLink', () => { - const input = isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34'; - const expectedOutput = /^.*\/foo\/bar.js:12:34<\/a><\/span>$/; - const output = linkDetector.handleLinks(input); + const input = isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34'; + const expectedOutput = isWindows ? 'C:\\foo\\bar.js:12:34<\/a><\/span>' : '/Users/foo/bar.js:12:34<\/a><\/span>'; + const output = linkDetector.linkify(input); assert.equal(1, output.children.length); assert.equal('SPAN', output.tagName); assert.equal('A', output.firstElementChild!.tagName); - assert(expectedOutput.test(output.outerHTML)); + assert.equal(expectedOutput, output.outerHTML); assertElementIsLink(output.firstElementChild!); - assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.firstElementChild!.textContent); + assert.equal(isWindows ? 'C:\\foo\\bar.js:12:34' : '/Users/foo/bar.js:12:34', output.firstElementChild!.textContent); }); test('relativeLink', () => { const input = '\./foo/bar.js'; const expectedOutput = '\./foo/bar.js'; - const output = linkDetector.handleLinks(input); + const output = linkDetector.linkify(input); assert.equal(0, output.children.length); assert.equal('SPAN', output.tagName); assert.equal(expectedOutput, output.outerHTML); }); + test('relativeLinkWithWorkspace', () => { + const input = '\./foo/bar.js'; + const expectedOutput = /^\.\/foo\/bar\.js<\/a><\/span>$/; + const output = linkDetector.linkify(input, false, new WorkspaceFolder({ uri: URI.file('/path/to/workspace'), name: 'ws', index: 0 })); + + assert.equal('SPAN', output.tagName); + assert(expectedOutput.test(output.outerHTML)); + }); + test('singleLineLinkAndText', function () { const input = isWindows ? 'The link: C:/foo/bar.js:12:34' : 'The link: /Users/foo/bar.js:12:34'; - const expectedOutput = /^The link: <\/span>.*\/foo\/bar.js:12:34<\/a><\/span>$/; - const output = linkDetector.handleLinks(input); + const expectedOutput = /^The link: .*\/foo\/bar.js:12:34<\/a><\/span>$/; + const output = linkDetector.linkify(input); - assert.equal(2, output.children.length); + assert.equal(1, output.children.length); assert.equal('SPAN', output.tagName); - assert.equal('SPAN', output.children[0].tagName); - assert.equal('A', output.children[1].tagName); + assert.equal('A', output.children[0].tagName); assert(expectedOutput.test(output.outerHTML)); - assertElementIsLink(output.children[1]); - assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].textContent); + assertElementIsLink(output.children[0]); + assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[0].textContent); }); test('singleLineMultipleLinks', () => { const input = isWindows ? 'Here is a link C:/foo/bar.js:12:34 and here is another D:/boo/far.js:56:78' : 'Here is a link /Users/foo/bar.js:12:34 and here is another /Users/boo/far.js:56:78'; - const expectedOutput = /^Here is a link <\/span>.*\/foo\/bar.js:12:34<\/a> and here is another <\/span>.*\/boo\/far.js:56:78<\/a><\/span>$/; - const output = linkDetector.handleLinks(input); + const expectedOutput = /^Here is a link .*\/foo\/bar.js:12:34<\/a> and here is another .*\/boo\/far.js:56:78<\/a><\/span>$/; + const output = linkDetector.linkify(input); - assert.equal(4, output.children.length); + assert.equal(2, output.children.length); assert.equal('SPAN', output.tagName); - assert.equal('SPAN', output.children[0].tagName); + assert.equal('A', output.children[0].tagName); assert.equal('A', output.children[1].tagName); - assert.equal('SPAN', output.children[2].tagName); - assert.equal('A', output.children[3].tagName); assert(expectedOutput.test(output.outerHTML)); + assertElementIsLink(output.children[0]); assertElementIsLink(output.children[1]); - assertElementIsLink(output.children[3]); - assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].textContent); - assert.equal(isWindows ? 'D:/boo/far.js:56:78' : '/Users/boo/far.js:56:78', output.children[3].textContent); + assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[0].textContent); + assert.equal(isWindows ? 'D:/boo/far.js:56:78' : '/Users/boo/far.js:56:78', output.children[1].textContent); }); test('multilineNoLinks', () => { const input = 'Line one\nLine two\nLine three'; const expectedOutput = /^Line one\n<\/span>Line two\n<\/span>Line three<\/span><\/span>$/; - const output = linkDetector.handleLinks(input); + const output = linkDetector.linkify(input, true); assert.equal(3, output.children.length); assert.equal('SPAN', output.tagName); @@ -123,7 +140,7 @@ suite('Debug - Link Detector', () => { test('multilineTrailingNewline', () => { const input = 'I am a string\nAnd I am another\n'; const expectedOutput = 'I am a string\n<\/span>And I am another\n<\/span><\/span>'; - const output = linkDetector.handleLinks(input); + const output = linkDetector.linkify(input, true); assert.equal(2, output.children.length); assert.equal('SPAN', output.tagName); @@ -135,19 +152,17 @@ suite('Debug - Link Detector', () => { test('multilineWithLinks', () => { const input = isWindows ? 'I have a link for you\nHere it is: C:/foo/bar.js:12:34\nCool, huh?' : 'I have a link for you\nHere it is: /Users/foo/bar.js:12:34\nCool, huh?'; - const expectedOutput = /^I have a link for you\n<\/span>Here it is: <\/span>.*\/foo\/bar.js:12:34<\/a>\n<\/span><\/span>Cool, huh\?<\/span><\/span>$/; - const output = linkDetector.handleLinks(input); + const expectedOutput = /^I have a link for you\n<\/span>Here it is: .*\/foo\/bar.js:12:34<\/a>\n<\/span>Cool, huh\?<\/span><\/span>$/; + const output = linkDetector.linkify(input, true); assert.equal(3, output.children.length); assert.equal('SPAN', output.tagName); assert.equal('SPAN', output.children[0].tagName); assert.equal('SPAN', output.children[1].tagName); assert.equal('SPAN', output.children[2].tagName); - assert.equal('SPAN', output.children[1].children[0].tagName); - assert.equal('A', output.children[1].children[1].tagName); - assert.equal('SPAN', output.children[1].children[2].tagName); + assert.equal('A', output.children[1].children[0].tagName); assert(expectedOutput.test(output.outerHTML)); - assertElementIsLink(output.children[1].children[1]); - assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].children[1].textContent); + assertElementIsLink(output.children[1].children[0]); + assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.children[1].children[0].textContent); }); }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 969a6a6744f8..af0f5f6408f2 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -56,7 +56,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderMarkdownDocument } from 'vs/workbench/common/markdownDocumentRenderer'; +import { renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 3b42a0fef6cb..5dcfbf7b8732 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1629,29 +1629,6 @@ export class ShowRecommendedExtensionsAction extends Action { } } -export class ShowSyncedExtensionsAction extends Action { - - static readonly ID = 'workbench.extensions.action.listSyncedExtensions'; - static LABEL = localize('showSyncedExtensions', "Show My Accoount Extensions"); - - constructor( - id: string, - label: string, - @IViewletService private readonly viewletService: IViewletService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('@myaccount '); - viewlet.focus(); - }); - } -} - export class InstallWorkspaceRecommendedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index d1b60c67dd86..c55d65c6da60 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -22,12 +22,12 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, AutoUpdate import { ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, ShowSyncedExtensionsAction + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, SyncedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -58,7 +58,6 @@ import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsView import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; -import { SyncStatus, CONTEXT_SYNC_STATE } from 'vs/platform/userDataSync/common/userDataSync'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -71,7 +70,6 @@ const HasInstalledExtensionsContext = new RawContextKey('hasInstalledEx const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); const DefaultRecommendedExtensionsContext = new RawContextKey('defaultRecommendedExtensions', false); -const SyncedExtensionsContext = new RawContextKey('syncedExtensions', false); const viewIdNameMappings: { [id: string]: string } = { 'extensions.listView': localize('marketPlace', "Marketplace"), 'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"), @@ -113,7 +111,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor()); viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor()); viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor()); - viewDescriptors.push(this.createSyncedExtensionsViewDescriptor()); if (this.extensionManagementServerService.localExtensionManagementServer) { viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer)); @@ -319,16 +316,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio }; } - private createSyncedExtensionsViewDescriptor(): IViewDescriptor { - const id = 'extensions.syncedExtensionsList'; - return { - id, - name: viewIdNameMappings[id], - ctorDescriptor: { ctor: SyncedExtensionsView }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.userConfiguration.enableSync'), ContextKeyExpr.has('syncedExtensions')), - weight: 100 - }; - } } export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet { @@ -345,7 +332,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private hasInstalledExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; - private syncedExtensionsContextKey: IContextKey; private defaultRecommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; @@ -370,7 +356,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio @IWorkspaceContextService contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService + @IExtensionService extensionService: IExtensionService, ) { super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); @@ -387,7 +373,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService); this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); - this.syncedExtensionsContextKey = SyncedExtensionsContext.bindTo(contextKeyService); this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this)); this.searchViewletState = this.getMemento(StorageScope.WORKSPACE); @@ -487,7 +472,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL), this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL), // this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL), // {{SQL CARBON EDIT}} - this.instantiationService.createInstance(ShowSyncedExtensionsAction, ShowSyncedExtensionsAction.ID, ShowSyncedExtensionsAction.LABEL), new Separator(), // {{SQL CARBON EDIT}} //this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs'), @@ -536,7 +520,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private doSearch(): Promise { const value = this.normalizedQuery(); - const isSyncedExtensionsQuery = ExtensionsListView.isSyncedExtensionsQuery(value); const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value); this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value)); this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value)); @@ -544,8 +527,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); - this.syncedExtensionsContextKey.set(isSyncedExtensionsQuery); - this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery && !isSyncedExtensionsQuery); + this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); this.defaultViewsContextKey.set(!value); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 2a6126bc9d32..2164daf103bd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -47,7 +47,6 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -105,7 +104,6 @@ export class ExtensionsListView extends ViewletPanel { @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, - @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; @@ -457,11 +455,8 @@ export class ExtensionsListView extends ViewletPanel { return this.getAllRecommendationsModel(query, options, token); } else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) { return this.getRecommendationsModel(query, options, token); - // {{SQL CARBON EDIT}} } else if (ExtensionsListView.isAllMarketplaceExtensionsQuery(query.value)) { // {{SQL CARBON EDIT}} add if return this.getAllMarketplaceModel(query, options, token); - } else if (ExtensionsListView.isSyncedExtensionsQuery(query.value)) { - return this.getSyncedExtensionsModel(query, options, token); } if (/\bcurated:([^\s]+)\b/.test(query.value)) { @@ -769,23 +764,6 @@ export class ExtensionsListView extends ViewletPanel { .then(result => this.getPagedModel(result)); } - private async getSyncedExtensionsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { - const syncedExtensions = await this.userDataSyncService.getRemoteExtensions(); - if (!syncedExtensions.length) { - return this.showEmptyModel(); - } - const ids: string[] = [], names: string[] = []; - for (const installed of syncedExtensions) { - if (installed.identifier.uuid) { - ids.push(installed.identifier.uuid); - } else { - names.push(installed.identifier.id); - } - } - const pager = await this.extensionsWorkbenchService.queryGallery({ ids, names, pageSize: ids.length }, token); - return this.getPagedModel(pager || []); - } - // Sorts the firstPage of the pager in the same order as given array of extension ids private sortFirstPage(pager: IPager, ids: string[]) { ids = ids.map(x => x.toLowerCase()); @@ -925,10 +903,6 @@ export class ExtensionsListView extends ViewletPanel { return /@recommended:keymaps/i.test(query); } - static isSyncedExtensionsQuery(query: string): boolean { - return /@myaccount/i.test(query); - } - focus(): void { super.focus(); if (!this.list) { @@ -969,11 +943,10 @@ export class ServerExtensionsView extends ExtensionsListView { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService, - @IUserDataSyncService userDataSyncService: IUserDataSyncService + @IContextKeyService contextKeyService: IContextKeyService ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, userDataSyncService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService); this._register(onDidChangeTitle(title => this.updateTitle(title))); } @@ -1029,14 +1002,6 @@ export class BuiltInBasicsExtensionsView extends ExtensionsListView { } } -export class SyncedExtensionsView extends ExtensionsListView { - - async show(query: string): Promise> { - query = query || '@myaccount'; - return ExtensionsListView.isSyncedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); - } -} - export class DefaultRecommendedExtensionsView extends ExtensionsListView { // {{SQL CARBON EDIT}} private readonly recommendedExtensionsQuery = '@allmarketplace'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index b09958f639ca..4912874d3acb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -21,7 +21,7 @@ import { IExtensionEnablementService, EnablementState, IExtensionManagementServe import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { URI } from 'vs/base/common/uri'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -461,7 +461,7 @@ class Extensions extends Disposable { } } - private onEnablementChanged(platformExtensions: IPlatformExtension[]) { + private onEnablementChanged(platformExtensions: readonly IPlatformExtension[]) { const extensions = this.local.filter(e => platformExtensions.some(p => areSameExtensions(e.identifier, p.identifier))); for (const extension of extensions) { if (extension.local) { @@ -511,7 +511,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @INotificationService private readonly notificationService: INotificationService, @IURLService urlService: IURLService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IProgressService private readonly progressService: IProgressService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IStorageService private readonly storageService: IStorageService, @@ -1125,7 +1125,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const extension = local.filter(local => areSameExtensions(local.identifier, { id: extensionId }))[0]; if (extension) { - return this.windowService.focusWindow() + return this.hostService.focus() .then(() => this.open(extension)); } return this.queryGallery({ names: [extensionId], source: 'uri' }, CancellationToken.None).then(result => { @@ -1135,7 +1135,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension const extension = result.firstPage[0]; - return this.windowService.focusWindow().then(() => { + return this.hostService.focus().then(() => { return this.open(extension); }); }); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index efcbc5009ed9..e4c21f47d230 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -9,7 +9,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IElectronService } from 'vs/platform/electron/node/electron'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 6394df76f142..1bba4ecdf890 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -27,10 +27,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { URLService } from 'vs/platform/url/node/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -67,7 +66,6 @@ suite('ExtensionsActions Test', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); - instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, new TestConfigurationService()); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 180a54d04067..3390c8b3b641 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -27,10 +27,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { URLService } from 'vs/platform/url/node/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -79,7 +78,6 @@ suite('ExtensionsListView Tests', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); - instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, new TestConfigurationService()); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index abd8a2dd40a8..b0b523db5f18 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -27,10 +27,9 @@ import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -61,7 +60,6 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(ILogService, NullLogService); - instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IProgressService, ProgressService); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 58342a72f21f..a22ee81430f4 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -18,7 +18,7 @@ import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { IProductService } from 'vs/platform/product/common/productService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 7a43e34344a9..1417ea1a74bd 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -9,7 +9,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IStatusbarService, StatusbarAlignment, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 1bea6cf7ba1d..f6b81551d443 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -13,8 +13,6 @@ import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; /** @@ -30,9 +28,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { @IOpenerService private readonly openerService: IOpenerService, @IEditorService private readonly editorService: IEditorService, @IStorageService storageService: IStorageService, - @IFileService fileService: IFileService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IInstantiationService instantiationService: IInstantiationService, ) { super( BinaryFileEditor.ID, @@ -42,10 +38,8 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { }, telemetryService, themeService, - fileService, environmentService, storageService, - instantiationService, ); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 972a3efe9d9d..ba56563118dd 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -20,7 +20,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -47,7 +47,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWindowService private readonly windowService: IWindowService + @IHostService private readonly hostService: IHostService ) { super(); @@ -68,7 +68,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut this._register(this.editorService.onDidVisibleEditorsChange(() => this.handleOutOfWorkspaceWatchers())); // Update visible editors when focus is gained - this._register(this.windowService.onDidChangeFocus(e => this.onWindowFocusChange(e))); + this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChange(e))); // Lifecycle this.lifecycleService.onShutdown(this.dispose, this); diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index f1481570b52f..1b7456c472ec 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -24,7 +24,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -55,10 +55,10 @@ export class TextFileEditor extends BaseTextEditor { @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IExplorerService private readonly explorerService: IExplorerService ) { - super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService); + super(TextFileEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); this.updateRestoreViewStateConfiguration(); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 51ac4348a095..a88d76731b12 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -25,7 +25,7 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -753,7 +753,7 @@ export class ShowOpenedFileInNewWindow extends Action { id: string, label: string, @IEditorService private readonly editorService: IEditorService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IFileService private readonly fileService: IFileService ) { @@ -764,7 +764,7 @@ export class ShowOpenedFileInNewWindow extends Action { const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (fileResource) { if (this.fileService.canHandleResource(fileResource)) { - this.windowService.openWindow([{ fileUri: fileResource }], { forceNewWindow: true }); + this.hostService.openInWindow([{ fileUri: fileResource }], { forceNewWindow: true }); } else { this.notificationService.info(nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource.")); } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 91d05ef425a2..f652dfe4df5f 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,8 +5,8 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { toResource, IEditorCommandsContext, SideBySideEditor, EditorInput } from 'vs/workbench/common/editor'; // {{SQL CARBON EDIT}} import EditorInput -import { IWindowService, IURIToOpen, IOpenSettings, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; +import { toResource, IEditorCommandsContext, SideBySideEditor, EditorInput } from 'vs/workbench/common/editor'; // {{SQL CARBON EDIT}} add edit input +import { IWindowOpenable, IOpenInWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -37,7 +37,7 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IEditorService, SIDE_GROUP, IResourceEditorReplacement } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { basename, toLocalResource, joinPath } from 'vs/base/common/resources'; +import { basename, toLocalResource, joinPath, isEqual } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -81,27 +81,27 @@ export const ResourceSelectedForCompareContext = new RawContextKey('res export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); -export const openWindowCommand = (accessor: ServicesAccessor, urisToOpen: IURIToOpen[], options?: IOpenSettings) => { - if (Array.isArray(urisToOpen)) { - const windowService = accessor.get(IWindowService); +export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenInWindowOptions) => { + if (Array.isArray(toOpen)) { + const hostService = accessor.get(IHostService); const environmentService = accessor.get(IEnvironmentService); // rewrite untitled: workspace URIs to the absolute path on disk - urisToOpen = urisToOpen.map(uriToOpen => { - if (isWorkspaceToOpen(uriToOpen) && uriToOpen.workspaceUri.scheme === Schemas.untitled) { + toOpen = toOpen.map(openable => { + if (isWorkspaceToOpen(openable) && openable.workspaceUri.scheme === Schemas.untitled) { return { - workspaceUri: joinPath(environmentService.untitledWorkspacesHome, uriToOpen.workspaceUri.path, UNTITLED_WORKSPACE_NAME) + workspaceUri: joinPath(environmentService.untitledWorkspacesHome, openable.workspaceUri.path, UNTITLED_WORKSPACE_NAME) }; } - return uriToOpen; + return openable; }); - windowService.openWindow(urisToOpen, options); + hostService.openInWindow(toOpen, options); } }; -export const newWindowCommand = (accessor: ServicesAccessor, options?: { reuse?: boolean, remoteAuthority?: string }) => { +export const newWindowCommand = (accessor: ServicesAccessor, options?: IOpenEmptyWindowOptions) => { const hostService = accessor.get(IHostService); hostService.openEmptyWindow(options); }; @@ -156,7 +156,7 @@ async function doSaveAs( const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); if (activeTextEditorWidget) { const activeResource = toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { + if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && isEqual(activeResource, resource)) { viewStateOfSource = activeTextEditorWidget.saveViewState(); } } @@ -180,7 +180,7 @@ async function doSaveAs( target = await textFileService.saveAs(resource, undefined, options); } - if (!target || target.toString() === resource.toString()) { + if (!target || isEqual(target, resource)) { return false; // save canceled or same resource used } @@ -215,7 +215,7 @@ async function doSave( // Pin the active editor if we are saving it const activeControl = editorService.activeControl; const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); - if (activeControl && activeEditorResource && activeEditorResource.toString() === resource.toString()) { + if (activeControl && activeEditorResource && isEqual(activeEditorResource, resource)) { activeControl.group.pinEditor(activeControl.input); } @@ -254,7 +254,7 @@ async function saveAll(saveAllArguments: any, editorService: IEditorService, unt encoding: untitledEditorService.getEncoding(resource), resource, options: { - inactive: activeEditorResource ? activeEditorResource.toString() !== resource.toString() : true, + inactive: activeEditorResource ? !isEqual(activeEditorResource, resource) : true, pinned: true, preserveFocus: true, index: group.getIndexOfEditor(e) @@ -272,7 +272,7 @@ async function saveAll(saveAllArguments: any, editorService: IEditorService, unt // {{SQL CARBON EDIT}} Update untitled resources to the saved ones, so we open the proper files const replacementPairs: IResourceEditorReplacement[] = []; inputs.forEach(i => { - const targetResult = result.results.filter(r => r.success && r.source.toString() === i.resource.toString()).pop(); + const targetResult = result.results.filter(r => r.success && isEqual(r.source, i.resource)).pop(); if (targetResult && targetResult.target) { // i.resource = targetResult.target;let editor = i; const editor = i; @@ -621,7 +621,7 @@ CommandsRegistry.registerCommand({ const workspace = contextService.getWorkspace(); const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)).filter(r => // Need to verify resources are workspaces since multi selection can trigger this command on some non workspace resources - workspace.folders.some(f => f.uri.toString() === r.toString()) + workspace.folders.some(f => isEqual(f.uri, r)) ); return workspaceEditingService.removeFolders(resources); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 7212720977ac..9b6e9063124e 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -37,6 +37,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService'; import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { Schemas } from 'vs/base/common/network'; +import { WorkspaceWatcher } from 'vs/workbench/contrib/files/common/workspaceWatcher'; // Viewlet Action export class OpenExplorerViewletAction extends ShowViewletAction { @@ -171,6 +172,8 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Register uri display for file uris Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileUriLabelContribution, LifecyclePhase.Starting); +// Workspace Watcher +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 3ba3d4bae9a3..be2e844d9e87 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -86,6 +86,10 @@ display: block; } +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon { + color: inherit; +} + .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close { width: 8px; height: 22px; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index a3fc266af279..b35be0b9b004 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -38,7 +38,7 @@ import { DesktopDragAndDropData, ExternalElementsDragAndDropData, ElementsDragAn import { isMacintosh } from 'vs/base/common/platform'; import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs'; import { ITextFileService, ITextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { URI } from 'vs/base/common/uri'; import { ITask, sequence } from 'vs/base/common/async'; @@ -443,7 +443,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, - @IWindowService private windowService: IWindowService, + @IHostService private hostService: IHostService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService ) { this.toDispose = []; @@ -610,7 +610,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const result = await this.fileService.resolveAll(droppedResources); // Pass focus to window - this.windowService.focusWindow(); + this.hostService.focus(); // Handle folders by adding to workspace if we are in workspace context const folders = result.filter(r => r.success && r.stat && r.stat.isDirectory).map(result => ({ uri: result.stat!.resource })); diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts index 3ebcc6c423fe..9d67abd076ff 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts @@ -56,7 +56,7 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut } } - protected onTextFilesDirty(e: TextFileModelChangeEvent[]): void { + protected onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) { this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay } @@ -84,17 +84,17 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut })); } - protected onTextFilesSaved(e: TextFileModelChangeEvent[]): void { + protected onTextFilesSaved(e: readonly TextFileModelChangeEvent[]): void { if (this.hasDirtyCount) { this.updateActivityBadge(); } } - protected onTextFilesSaveError(e: TextFileModelChangeEvent[]): void { + protected onTextFilesSaveError(e: readonly TextFileModelChangeEvent[]): void { this.updateActivityBadge(); } - protected onTextFilesReverted(e: TextFileModelChangeEvent[]): void { + protected onTextFilesReverted(e: readonly TextFileModelChangeEvent[]): void { if (this.hasDirtyCount) { this.updateActivityBadge(); } diff --git a/src/vs/workbench/services/files/common/workspaceWatcher.ts b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts similarity index 92% rename from src/vs/workbench/services/files/common/workspaceWatcher.ts rename to src/vs/workbench/contrib/files/common/workspaceWatcher.ts index 5d8d5c03fe29..16a21dd4bb89 100644 --- a/src/vs/workbench/services/files/common/workspaceWatcher.ts +++ b/src/vs/workbench/contrib/files/common/workspaceWatcher.ts @@ -7,10 +7,7 @@ import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/files'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { ResourceMap } from 'vs/base/common/map'; import { onUnexpectedError } from 'vs/base/common/errors'; import { INotificationService, Severity, NeverShowAgainScope } from 'vs/platform/notification/common/notification'; @@ -152,5 +149,3 @@ export class WorkspaceWatcher extends Disposable { this.unwatchWorkspaces(); } } - -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkspaceWatcher, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts index e6b60c104c9e..ae3584084997 100644 --- a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts @@ -38,7 +38,7 @@ export class NativeDirtyFilesTracker extends DirtyFilesTracker { super.onUntitledDidChangeDirty(resource); } - protected onTextFilesDirty(e: TextFileModelChangeEvent[]): void { + protected onTextFilesDirty(e: readonly TextFileModelChangeEvent[]): void { if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay } @@ -46,7 +46,7 @@ export class NativeDirtyFilesTracker extends DirtyFilesTracker { super.onTextFilesDirty(e); } - protected onTextFilesSaved(e: TextFileModelChangeEvent[]): void { + protected onTextFilesSaved(e: readonly TextFileModelChangeEvent[]): void { if (this.isDocumentedEdited) { this.updateDocumentEdited(); } @@ -54,7 +54,7 @@ export class NativeDirtyFilesTracker extends DirtyFilesTracker { super.onTextFilesSaved(e); } - protected onTextFilesSaveError(e: TextFileModelChangeEvent[]): void { + protected onTextFilesSaveError(e: readonly TextFileModelChangeEvent[]): void { if (!this.isDocumentedEdited) { this.updateDocumentEdited(); } @@ -62,7 +62,7 @@ export class NativeDirtyFilesTracker extends DirtyFilesTracker { super.onTextFilesSaveError(e); } - protected onTextFilesReverted(e: TextFileModelChangeEvent[]): void { + protected onTextFilesReverted(e: readonly TextFileModelChangeEvent[]): void { if (this.isDocumentedEdited) { this.updateDocumentEdited(); } diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts index 9bfad06f079a..7c19777ef1d5 100644 --- a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -22,7 +22,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { IElectronService } from 'vs/platform/electron/node/electron'; @@ -46,10 +46,10 @@ export class NativeTextFileEditor extends TextFileEditor { @ITextFileService textFileService: ITextFileService, @IElectronService private readonly electronService: IElectronService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IExplorerService explorerService: IExplorerService ) { - super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, windowService, explorerService); + super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, hostService, explorerService); } protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts index 2fe47ed0007c..d6633f36c575 100644 --- a/src/vs/workbench/contrib/logs/common/logConstants.ts +++ b/src/vs/workbench/contrib/logs/common/logConstants.ts @@ -8,3 +8,4 @@ export const sharedLogChannelId = 'sharedLog'; export const rendererLogChannelId = 'rendererLog'; export const extHostLogChannelId = 'extHostLog'; export const telemetryLogChannelId = 'telemetryLog'; +export const userDataSyncLogChannelId = 'userDataSyncLog'; diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index cb372f113d08..671b2ee84e47 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -22,6 +22,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner'; +import { IProductService } from 'vs/platform/product/common/productService'; const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); @@ -33,9 +34,11 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ILogService private readonly logService: ILogService, @IFileService private readonly fileService: IFileService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IProductService private readonly productService: IProductService ) { super(); + this.registerCommonContributions(); if (isWeb) { this.registerWebContributions(); } else { @@ -43,8 +46,14 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } } + private registerCommonContributions(): void { + if (this.productService.settingsSyncStoreUrl) { + this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Configuration Sync"), this.environmentService.userDataSyncLogResource); + } + this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); + } + private registerWebContributions(): void { - Registry.as(OutputExt.OutputChannels).registerChannel({ id: Constants.rendererLogChannelId, label: nls.localize('rendererLog', "Window"), file: this.environmentService.logFile, log: true }); this.instantiationService.createInstance(LogsDataCleaner); const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); @@ -55,7 +64,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { private registerNativeContributions(): void { this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(this.environmentService.logsPath, `main.log`))); this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`))); - this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); const registerTelemetryChannel = (level: LogLevel) => { if (level === LogLevel.Trace && !Registry.as(OutputExt.OutputChannels).getChannel(Constants.telemetryLogChannelId)) { @@ -76,7 +84,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { const watcher = this.fileService.watch(dirname(file)); const disposable = this.fileService.onFileChanges(e => { - if (e.contains(file, FileChangeType.ADDED)) { + if (e.contains(file, FileChangeType.ADDED) || e.contains(file, FileChangeType.UPDATED)) { watcher.dispose(); disposable.dispose(); outputChannelRegistry.registerChannel({ id, label, file, log: true }); diff --git a/src/vs/workbench/common/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts similarity index 100% rename from src/vs/workbench/common/markdownDocumentRenderer.ts rename to src/vs/workbench/contrib/markdown/common/markdownDocumentRenderer.ts diff --git a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 198eb2b36d2e..f480e69e4639 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -26,7 +26,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ActivePanelContext } from 'vs/workbench/common/panel'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IMarkerService, MarkerStatistics } from 'vs/platform/markers/common/markers'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index 09ab8731289a..f949e2a2de5c 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -41,7 +41,7 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); this.markersModel.setResourceMarkers(groupBy(this.readMarkers(), compareMarkersByUri).map(group => [group[0].resource, group])); - this._register(Event.debounce>(markerService.onMarkerChanged, (resourcesMap, resources) => { + this._register(Event.debounce>(markerService.onMarkerChanged, (resourcesMap, resources) => { resourcesMap = resourcesMap ? resourcesMap : new ResourceMap(); resources.forEach(resource => resourcesMap!.set(resource, resource)); return resourcesMap; diff --git a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts index 436001217ffc..71b24eed1cf6 100644 --- a/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts @@ -19,7 +19,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; class MarkersDecorationsProvider implements IDecorationsProvider { readonly label: string = localize('label', "Problems"); - readonly onDidChange: Event; + readonly onDidChange: Event; constructor( private readonly _markerService: IMarkerService diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index 402cb768f6f2..c0ce08988d64 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -19,7 +19,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { LOG_SCHEME, IFileOutputChannelDescriptor } from 'vs/workbench/contrib/output/common/output'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class LogViewerInput extends ResourceEditorInput { @@ -54,9 +54,9 @@ export class LogViewer extends AbstractTextResourceEditor { @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, - @IWindowService windowService: IWindowService + @IHostService hostService: IHostService ) { - super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, windowService); + super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService); } protected getConfigurationOverrides(): IEditorOptions { diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 92907b8e6893..463307df0b89 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -24,7 +24,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents'; export class OutputPanel extends AbstractTextResourceEditor { @@ -44,9 +44,9 @@ export class OutputPanel extends AbstractTextResourceEditor { @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, - @IWindowService windowService: IWindowService + @IHostService hostService: IHostService ) { - super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, windowService); + super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService, editorService, hostService); this.scopedInstantiationService = instantiationService; } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 2294f27b83fe..c410a88272b5 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -888,7 +888,7 @@ class ActionsColumn extends Column { private createEditAction(keybindingItemEntry: IKeybindingItemEntry): IAction { const keybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_DEFINE); return { - class: 'edit', + class: 'codicon-edit', enabled: true, id: 'editKeybinding', tooltip: keybinding ? localize('editKeybindingLabelWithKey', "Change Keybinding {0}", `(${keybinding.getLabel()})`) : localize('editKeybindingLabel', "Change Keybinding"), diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index 79b194831865..b3af1dcb0038 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IKeymapService, areKeyboardLayoutsEqual, parseKeyboardLayoutDescription, getKeyboardLayoutId, IKeyboardLayoutInfo } from 'vs/workbench/services/keybinding/common/keymapInfo'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -13,7 +13,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchCo import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KEYBOARD_LAYOUT_OPEN_PICKER } from 'vs/workbench/contrib/preferences/common/preferences'; import { Action } from 'vs/base/common/actions'; -import { isWeb, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { QuickPickInput, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -99,9 +99,7 @@ export class KeyboardLayoutPickerAction extends Action { @IEnvironmentService private readonly environmentService: IEnvironmentService, @IEditorService private readonly editorService: IEditorService ) { - super(actionId, actionLabel); - - this.enabled = isWeb; + super(actionId, actionLabel, undefined, true); } async run(): Promise { diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index 91da22206b30..2ce1ca37c989 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -203,25 +203,17 @@ outline: 1px dotted #f38518; } -.monaco-editor .settings-group-title-widget .title-container .expand-collapse-icon { - background: url("tree-expanded-light.svg") 50% 50% no-repeat; - margin-right: 2px; +.monaco-editor .settings-group-title-widget .title-container .codicon { + margin: 0 2px; width: 16px; height: 100%; + display: flex; + align-items: center; + justify-content: center; } -.monaco-editor.vs-dark .settings-group-title-widget .title-container .expand-collapse-icon, -.monaco-editor.hc-black .settings-group-title-widget .title-container .expand-collapse-icon { - background: url("tree-expanded-dark.svg") 50% 50% no-repeat; -} - -.monaco-editor .settings-group-title-widget .title-container.collapsed .expand-collapse-icon { - background: url("tree-collapsed-light.svg") 50% 50% no-repeat; -} - -.monaco-editor.vs-dark .settings-group-title-widget .title-container.collapsed .expand-collapse-icon, -.monaco-editor.hc-black .settings-group-title-widget .title-container.collapsed .expand-collapse-icon { - background: url("tree-collapsed-dark.svg") 50% 50% no-repeat; +.monaco-editor .settings-group-title-widget .title-container.collapsed .codicon::before { + transform: rotate(-90deg); } .monaco-editor .edit-preferences-widget { diff --git a/src/vs/workbench/contrib/preferences/browser/media/tree-collapsed-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/tree-collapsed-dark.svg deleted file mode 100644 index 17de497f3365..000000000000 --- a/src/vs/workbench/contrib/preferences/browser/media/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/preferences/browser/media/tree-collapsed-light.svg b/src/vs/workbench/contrib/preferences/browser/media/tree-collapsed-light.svg deleted file mode 100644 index 296499b8e5c1..000000000000 --- a/src/vs/workbench/contrib/preferences/browser/media/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/preferences/browser/media/tree-expanded-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/tree-expanded-dark.svg deleted file mode 100644 index a1df6a8d44ae..000000000000 --- a/src/vs/workbench/contrib/preferences/browser/media/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/preferences/browser/media/tree-expanded-light.svg b/src/vs/workbench/contrib/preferences/browser/media/tree-expanded-light.svg deleted file mode 100644 index e60e357f573d..000000000000 --- a/src/vs/workbench/contrib/preferences/browser/media/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 85cd5945dbb3..d768f4501705 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -53,7 +53,7 @@ import { IFilterResult, IPreferencesService, ISetting, ISettingsEditorModel, ISe import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel, SettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; export class PreferencesEditor extends BaseEditor { @@ -985,9 +985,9 @@ export class DefaultPreferencesEditor extends BaseTextEditor { @ITextFileService textFileService: ITextFileService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService editorService: IEditorService, - @IWindowService windowService: IWindowService + @IHostService hostService: IHostService ) { - super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService); + super(DefaultPreferencesEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, hostService); } private static _getContributions(): IEditorContributionCtor[] { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 4da61db4e432..3359bfee0795 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -168,7 +168,7 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { this._register(focusTracker.onDidFocus(() => this.toggleFocus(true))); this._register(focusTracker.onDidBlur(() => this.toggleFocus(false))); - this.icon = DOM.append(this.titleContainer, DOM.$('.expand-collapse-icon')); + this.icon = DOM.append(this.titleContainer, DOM.$('.codicon.codicon-chevron-down')); this.title = DOM.append(this.titleContainer, DOM.$('.title')); this.title.textContent = this.settingsGroup.title + ` (${this.settingsGroup.sections.reduce((count, section) => count + section.settings.length, 0)})`; @@ -737,7 +737,7 @@ export class SearchWidget extends Widget { export class EditPreferenceWidget extends Disposable { - static readonly GLYPH_MARGIN_CLASS_NAME = 'edit-preferences-widget'; + static readonly GLYPH_MARGIN_CLASS_NAME = 'codicon codicon-edit'; private _line: number = -1; private _preferences: T[] = []; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 833b9a17e1e5..8d63a134b2de 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -197,9 +197,9 @@ export const tocData: ITOCEntry = { settings: ['telemetry.*'] }, { - id: 'application/sync', - label: localize('sync', "Sync"), - settings: ['userConfiguration.*'] + id: 'application/configurationSync', + label: localize('configuration sync', "Configuration Sync"), + settings: ['configurationSync.*'] } ] } diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index ad8ca6f6549b..e99f01e0add0 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -6,7 +6,7 @@ import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWindowService, IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; @@ -41,7 +41,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo constructor( @IHostService private readonly hostService: IHostService, - @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEnvironmentService private readonly envService: IEnvironmentService, @IDialogService private readonly dialogService: IDialogService @@ -124,22 +123,18 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } private doConfirm(message: string, detail: string, primaryButton: string, confirmed: () => void): void { - this.windowService.isFocused().then(focused => { - if (focused) { - return this.dialogService.confirm({ - type: 'info', - message, - detail, - primaryButton - }).then(res => { - if (res.confirmed) { - confirmed(); - } - }); - } - - return undefined; - }); + if (this.hostService.hasFocus) { + this.dialogService.confirm({ + type: 'info', + message, + detail, + primaryButton + }).then(res => { + if (res.confirmed) { + confirmed(); + } + }); + } } } diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index affc555f9520..0ca3bb4275ee 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -432,13 +432,16 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { return; } - if (typeof descriptor.viewDescriptor.remoteAuthority === 'undefined' || - descriptor.viewDescriptor.remoteAuthority === actualRemoteAuthority || - descriptor.viewDescriptor.id === HelpPanel.ID - ) { + const descriptorAuthority = descriptor.viewDescriptor.remoteAuthority; + if (typeof descriptorAuthority === 'undefined' || descriptor.viewDescriptor.id === HelpPanel.ID) { panel.setExpanded(true); } else { - panel.setExpanded(false); + const descriptorAuthorityArr = Array.isArray(descriptorAuthority) ? descriptorAuthority : [descriptorAuthority]; + if (descriptorAuthorityArr.indexOf(actualRemoteAuthority) >= 0) { + panel.setExpanded(true); + } else { + panel.setExpanded(false); + } } }); } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index e7386d39c4e0..8ef85ab67aea 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -15,7 +15,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts index ae7c95c4b88d..28d52e606769 100644 --- a/src/vs/workbench/contrib/scm/browser/activity.ts +++ b/src/vs/workbench/contrib/scm/browser/activity.ts @@ -11,7 +11,7 @@ import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/sc import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { commonPrefixLength } from 'vs/base/common/strings'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index c6673e31dbc5..563eefe8578d 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -96,10 +96,6 @@ text-decoration: line-through; } -.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label::after { - padding: 0 8px; -} - .scm-viewlet .monaco-list-row .resource-group > .count { padding: 0 8px; display: flex; diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts b/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts index f44d3052c520..3a56f002f38c 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts @@ -372,7 +372,7 @@ function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompres ? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true })) : Iterator.map(item.tree.root.children, node => asTreeElement(node, true)); - return { element: item.group, children, incompressible: true }; + return { element: item.group, children, incompressible: true, collapsible: true }; } function asTreeElement(node: INode, incompressible: boolean): ICompressedTreeElement { @@ -389,13 +389,12 @@ function asTreeElement(node: INode, incompressi } const enum ViewModelMode { - List = 'codicon-filter', - Tree = 'codicon-selection' + List, + Tree } class ViewModel { - private _mode = ViewModelMode.Tree; private readonly _onDidChangeMode = new Emitter(); readonly onDidChangeMode = this._onDidChangeMode.event; @@ -413,7 +412,8 @@ class ViewModel { constructor( private groups: ISequence, - private tree: ObjectTree + private tree: ObjectTree, + private _mode: ViewModelMode ) { } private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { @@ -507,7 +507,8 @@ export class ToggleViewModeAction extends Action { } private onDidChangeMode(mode: ViewModelMode): void { - this.class = `scm-action toggle-view-mode ${mode}`; + const iconClass = mode === ViewModelMode.List ? 'codicon-filter' : 'codicon-selection'; + this.class = `scm-action toggle-view-mode ${iconClass}`; } } @@ -693,7 +694,8 @@ export class RepositoryPanel extends ViewletPanel { this._register(this.tree.onContextMenu(this.onListContextMenu, this)); this._register(this.tree); - this.viewModel = new ViewModel(this.repository.provider.groups, this.tree); + const mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree; + this.viewModel = new ViewModel(this.repository.provider.groups, this.tree, mode); this._register(this.viewModel); addClass(this.listContainer, 'file-icon-themable-tree'); diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 024e318f1d63..6ec66eebce1c 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -106,6 +106,16 @@ Registry.as(ConfigurationExtensions.Configuration).regis ], description: localize('scm.countBadge', "Controls the Source Control count badge."), default: 'all' + }, + 'scm.defaultViewMode': { + type: 'string', + enum: ['tree', 'list'], + enumDescriptions: [ + localize('scm.defaultViewMode.tree', "Show the repository changes as a tree."), + localize('scm.defaultViewMode.list', "Show the repository changes as a list.") + ], + description: localize('scm.defaultViewMode', "Controls the default Source Control repository view mode."), + default: 'tree' } } }); diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 0a613548f849..eb1c5c4f0531 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -245,6 +245,7 @@ background-repeat: no-repeat; width: 16px; height: 16px; + color: inherit; } /* Adjusts spacing in high contrast mode so that actions are vertically centered */ diff --git a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index 2158771b13db..1dd7f8ce374e 100644 --- a/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -18,13 +18,13 @@ import { DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import * as themes from 'vs/workbench/common/theme'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { URI } from 'vs/base/common/uri'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import * as perf from 'vs/base/common/performance'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; class PartsSplash { @@ -40,8 +40,8 @@ class PartsSplash { @IThemeService private readonly _themeService: IThemeService, @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @ITextFileService private readonly _textFileService: ITextFileService, - @IEnvironmentService private readonly _envService: IEnvironmentService, - @IWindowService private readonly windowService: IWindowService, + @IWorkbenchEnvironmentService private readonly _envService: IWorkbenchEnvironmentService, + @IElectronEnvironmentService private readonly _electronEnvService: IElectronEnvironmentService, @ILifecycleService lifecycleService: ILifecycleService, @IEditorGroupsService editorGroupsService: IEditorGroupsService, @IConfigurationService configService: IConfigurationService, @@ -105,7 +105,7 @@ class PartsSplash { // the color needs to be in hex const backgroundColor = this._themeService.getTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getTheme()); const payload = JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }); - ipc.send('vscode:changeColorTheme', this.windowService.windowId, payload); + ipc.send('vscode:changeColorTheme', this._electronEnvService.windowId, payload); } } diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index 2ce600a0b1e5..e2d8d58a8fa2 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -152,7 +152,7 @@ export class WorkspaceStats implements IWorkbenchContribution { } } - private report(): void { + private async report(): Promise { // Workspace Stats this.workspaceStatsService.getTags() @@ -164,19 +164,22 @@ export class WorkspaceStats implements IWorkbenchContribution { this.reportProxyStats(); const diagnosticsChannel = this.sharedProcessService.getChannel('diagnostics'); - diagnosticsChannel.call('reportWorkspaceStats', this.getWorkspaceInformation()); + this.getWorkspaceInformation().then(stats => diagnosticsChannel.call('reportWorkspaceStats', stats)); } - private getWorkspaceInformation(): IWorkspaceInformation { + private async getWorkspaceInformation(): Promise { const workspace = this.contextService.getWorkspace(); const state = this.contextService.getWorkbenchState(); - const id = this.workspaceStatsService.getTelemetryWorkspaceId(workspace, state); - return { - id: workspace.id, - telemetryId: id, - folders: workspace.folders, - configuration: workspace.configuration - }; + const telemetryId = this.workspaceStatsService.getTelemetryWorkspaceId(workspace, state); + return this.telemetryService.getTelemetryInfo().then(info => { + return { + id: workspace.id, + telemetryId, + rendererSessionId: info.sessionId, + folders: workspace.folders, + configuration: workspace.configuration + }; + }); } private reportWorkspaceTags(tags: Tags): void { diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts index 17c2973c7e2f..16b55d423b06 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts @@ -7,7 +7,8 @@ import * as crypto from 'crypto'; import { IFileService, IResolveFileResult, IFileStat } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService, NeverShowAgainScope, INeverShowAgainOptions } from 'vs/platform/notification/common/notification'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles'; @@ -97,7 +98,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { @IFileService private readonly fileService: IFileService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ITextFileService private readonly textFileService: ITextFileService @@ -453,7 +454,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ label: localize('openWorkspace', "Open Workspace"), - run: () => this.windowService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }]) + run: () => this.hostService.openInWindow([{ workspaceUri: joinPath(folder, workspaceFile) }]) }], { neverShowAgain }); } @@ -466,7 +467,7 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)), { placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => { if (pick) { - this.windowService.openWindow([{ workspaceUri: joinPath(folder, pick.label) }]); + this.hostService.openInWindow([{ workspaceUri: joinPath(folder, pick.label) }]); } }); } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index e91193664510..0cadcfa74dd7 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -44,7 +44,7 @@ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output'; @@ -178,6 +178,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _schemaVersion: JsonSchemaVersion | undefined; private _executionEngine: ExecutionEngine | undefined; private _workspaceFolders: IWorkspaceFolder[] | undefined; + private _workspace: IWorkspace | undefined; private _ignoredWorkspaceFolders: IWorkspaceFolder[] | undefined; private _showIgnoreMessage?: boolean; private _providers: Map; @@ -425,7 +426,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this._showIgnoreMessage; } - private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion]): void { + private updateSetup(setup?: [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined]): void { if (!setup) { setup = this.computeWorkspaceFolderSetup(); } @@ -447,6 +448,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._ignoredWorkspaceFolders = setup[1]; this._executionEngine = setup[2]; this._schemaVersion = setup[3]; + this._workspace = setup[4]; } protected showOutput(runSource: TaskRunSource = TaskRunSource.User): void { @@ -1402,13 +1404,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (let folder of this.workspaceFolders) { promises.push(this.computeWorkspaceFolderTasks(folder, runSource).then((value) => value, () => undefined)); } - return Promise.all(promises).then((values) => { + return Promise.all(promises).then(async (values) => { let result = new Map(); for (let value of values) { if (value) { result.set(value.workspaceFolder.uri.toString(), value); } } + const userTasks = await this.computeUserTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); + if (userTasks) { + result.set('settings', userTasks); + } + const workspaceFileTasks = await this.computeWorkspaceFileTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); + if (workspaceFileTasks && this._workspace && this._workspace.configuration) { + result.set(this._workspace.configuration.toString(), workspaceFileTasks); + } return result; }); } @@ -1429,7 +1439,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return ProblemMatcherRegistry.onReady().then(async (): Promise => { let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme); let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter); + let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter, TaskConfig.TaskConfigSource.TasksJson); let hasErrors = false; if (!parseResult.validationStatus.isOK()) { hasErrors = true; @@ -1456,6 +1466,101 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); } + private testParseExternalConfig(config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, location: string): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, hasParseErrors: boolean } { + if (!config) { + return { config: undefined, hasParseErrors: false }; + } + let parseErrors: string[] = (config as any).$parseErrors; + if (parseErrors) { + let isAffected = false; + for (const parseError of parseErrors) { + if (/tasks\.json$/.test(parseError)) { + isAffected = true; + break; + } + } + if (isAffected) { + this._outputChannel.append(nls.localize('TaskSystem.invalidTaskJsonOther', 'Error: The content of the tasks json in {0} has syntax errors. Please correct them before executing a task.\n', location)); + this.showOutput(); + return { config, hasParseErrors: true }; + } + } + return { config, hasParseErrors: false }; + } + + private async computeWorkspaceFileTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { + if (this.executionEngine === ExecutionEngine.Process) { + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').workspace, nls.localize('TasksSystem.locationWorkspaceConfig', 'workspace file')); + let customizedTasks: { byIdentifier: IStringDictionary; } = { + byIdentifier: Object.create(null) + }; + + const custom: CustomTask[] = []; + await this.computeTasksForSingleConfig(workspaceFolder, configuration.config, runSource, custom, customizedTasks.byIdentifier, TaskConfig.TaskConfigSource.WorkspaceFile); + const engine = configuration.config ? TaskConfig.ExecutionEngine.from(configuration.config) : ExecutionEngine.Terminal; + if (engine === ExecutionEngine.Process) { + this.notificationService.warn(nls.localize('TaskSystem.versionWorkspaceFile', 'Only tasks version 2.0.0 permitted in .codeworkspace.')); + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + return { workspaceFolder, set: { tasks: custom }, configurations: customizedTasks, hasErrors: configuration.hasParseErrors }; + } + + private async computeUserTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { + if (this.executionEngine === ExecutionEngine.Process) { + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + const configuration = this.testParseExternalConfig(this.configurationService.inspect('tasks').user, nls.localize('TasksSystem.locationUserConfig', 'user settings')); + let customizedTasks: { byIdentifier: IStringDictionary; } = { + byIdentifier: Object.create(null) + }; + + const custom: CustomTask[] = []; + await this.computeTasksForSingleConfig(workspaceFolder, configuration.config, runSource, custom, customizedTasks.byIdentifier, TaskConfig.TaskConfigSource.User); + const engine = configuration.config ? TaskConfig.ExecutionEngine.from(configuration.config) : ExecutionEngine.Terminal; + if (engine === ExecutionEngine.Process) { + this.notificationService.warn(nls.localize('TaskSystem.versionSettings', 'Only tasks version 2.0.0 permitted in user settings.')); + return this.emptyWorkspaceTaskResults(workspaceFolder); + } + return { workspaceFolder, set: { tasks: custom }, configurations: customizedTasks, hasErrors: configuration.hasParseErrors }; + } + + private emptyWorkspaceTaskResults(workspaceFolder: IWorkspaceFolder): WorkspaceFolderTaskResult { + return { workspaceFolder, set: undefined, configurations: undefined, hasErrors: false }; + } + + private async computeTasksForSingleConfig(workspaceFolder: IWorkspaceFolder, config: TaskConfig.ExternalTaskRunnerConfiguration | undefined, runSource: TaskRunSource, custom: CustomTask[], customized: IStringDictionary, source: TaskConfig.TaskConfigSource): Promise { + if (!config) { + return false; + } + let taskSystemInfo: TaskSystemInfo | undefined = workspaceFolder ? this._taskSystemInfos.get(workspaceFolder.uri.scheme) : undefined; + let problemReporter = new ProblemReporter(this._outputChannel); + let parseResult = TaskConfig.parse(workspaceFolder, this._workspace, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, config, problemReporter, source); + let hasErrors = false; + if (!parseResult.validationStatus.isOK()) { + this.showOutput(runSource); + hasErrors = true; + } + if (problemReporter.status.isFatal()) { + problemReporter.fatal(nls.localize('TaskSystem.configurationErrors', 'Error: the provided task configuration has validation errors and can\'t not be used. Please correct the errors first.')); + return hasErrors; + } + if (parseResult.configured && parseResult.configured.length > 0) { + for (let task of parseResult.configured) { + customized[task.configures._key] = task; + } + } + if (!(await this._areJsonTasksSupportedPromise) && (parseResult.custom.length > 0)) { + console.warn('Custom workspace tasks are not supported.'); + } else { + for (let task of parseResult.custom) { + custom.push(task); + } + } + return hasErrors; + } + private computeConfiguration(workspaceFolder: IWorkspaceFolder): Promise { let { config, hasParseErrors } = this.getConfiguration(workspaceFolder); return Promise.resolve({ workspaceFolder, config, hasErrors: hasParseErrors }); @@ -1463,18 +1568,19 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract computeLegacyConfiguration(workspaceFolder: IWorkspaceFolder): Promise; - private computeWorkspaceFolderSetup(): [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion] { + private computeWorkspaceFolderSetup(): [IWorkspaceFolder[], IWorkspaceFolder[], ExecutionEngine, JsonSchemaVersion, IWorkspace | undefined] { let workspaceFolders: IWorkspaceFolder[] = []; let ignoredWorkspaceFolders: IWorkspaceFolder[] = []; let executionEngine = ExecutionEngine.Terminal; let schemaVersion = JsonSchemaVersion.V2_0_0; - + let workspace: IWorkspace | undefined; if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { let workspaceFolder: IWorkspaceFolder = this.contextService.getWorkspace().folders[0]; workspaceFolders.push(workspaceFolder); executionEngine = this.computeExecutionEngine(workspaceFolder); schemaVersion = this.computeJsonSchemaVersion(workspaceFolder); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + workspace = this.contextService.getWorkspace(); for (let workspaceFolder of this.contextService.getWorkspace().folders) { if (schemaVersion === this.computeJsonSchemaVersion(workspaceFolder)) { workspaceFolders.push(workspaceFolder); @@ -1487,7 +1593,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } } - return [workspaceFolders, ignoredWorkspaceFolders, executionEngine, schemaVersion]; + return [workspaceFolders, ignoredWorkspaceFolders, executionEngine, schemaVersion, workspace]; } private computeExecutionEngine(workspaceFolder: IWorkspaceFolder): ExecutionEngine { @@ -1508,7 +1614,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected getConfiguration(workspaceFolder: IWorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; hasParseErrors: boolean } { let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY - ? Objects.deepClone(this.configurationService.getValue('tasks', { resource: workspaceFolder.uri })) + ? Objects.deepClone(this.configurationService.inspect('tasks', { resource: workspaceFolder.uri }).workspaceFolder) : undefined; if (!result) { return { config: undefined, hasParseErrors: false }; @@ -1660,7 +1766,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => { let description: string | undefined; - if (this.needsFolderQualification()) { + if (task._source.kind === TaskSourceKind.User) { + description = nls.localize('taskQuickPick.userSettings', 'User Settings'); + } else if (task._source.kind === TaskSourceKind.WorkspaceFile) { + description = task.getWorkspaceFileName(); + } else if (this.needsFolderQualification()) { let workspaceFolder = task.getWorkspaceFolder(); if (workspaceFolder) { description = workspaceFolder.name; diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index db600903c5f1..8bb78cbf1c61 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -19,7 +19,7 @@ import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/ import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output'; @@ -255,9 +255,8 @@ const actionBarRegistry = Registry.as(ActionBarExtensions.Ac actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionContributor); // tasks.json validation -let schemaId = 'vscode://schemas/tasks'; let schema: IJSONSchema = { - id: schemaId, + id: tasksSchemaId, description: 'Task definition file', type: 'object', allowTrailingCommas: true, @@ -283,6 +282,7 @@ let schema: IJSONSchema = { import schemaVersion1 from '../common/jsonSchema_v1'; import schemaVersion2, { updateProblemMatchers } from '../common/jsonSchema_v2'; import { AbstractTaskService, ConfigureTaskAction } from 'vs/workbench/contrib/tasks/browser/abstractTaskService'; +import { tasksSchemaId } from 'vs/workbench/services/configuration/common/configuration'; schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, @@ -290,9 +290,9 @@ schema.definitions = { schema.oneOf = [...(schemaVersion2.oneOf || []), ...(schemaVersion1.oneOf || [])]; let jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); -jsonRegistry.registerSchema(schemaId, schema); +jsonRegistry.registerSchema(tasksSchemaId, schema); ProblemMatcherRegistry.onMatcherChanged(() => { updateProblemMatchers(); - jsonRegistry.notifySchemaChanged(schemaId); + jsonRegistry.notifySchemaChanged(tasksSchemaId); }); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 8c01a4fb63c7..d8d0877b47cb 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -18,7 +18,7 @@ import { isNamedProblemMatcher, ProblemMatcherRegistry } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from './tasks'; import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -683,6 +683,7 @@ export namespace RunOptions { interface ParseContext { workspaceFolder: IWorkspaceFolder; + workspace: IWorkspace | undefined; problemReporter: IProblemReporter; namedProblemMatchers: IStringDictionary; uuidMap: UUIDMap; @@ -1181,8 +1182,7 @@ namespace ProblemMatcherConverter { } } -const source: Partial = { - kind: Tasks.TaskSourceKind.Workspace, +const partialSource: Partial = { label: 'Workspace', config: undefined }; @@ -1328,7 +1328,7 @@ namespace ConfiguringTask { customize: string; } - export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number): Tasks.ConfiguringTask | undefined { + export function from(this: void, external: ConfiguringTask, context: ParseContext, index: number, source: TaskConfigSource): Tasks.ConfiguringTask | undefined { if (!external) { return undefined; } @@ -1383,9 +1383,24 @@ namespace ConfiguringTask { index, element: external }; + let taskSource: Tasks.FileBasedTaskSource; + switch (source) { + case TaskConfigSource.User: { + taskSource = Objects.assign({} as Tasks.UserTaskSource, partialSource, { kind: Tasks.TaskSourceKind.User, config: configElement }); + break; + } + case TaskConfigSource.WorkspaceFile: { + taskSource = Objects.assign({} as Tasks.WorkspaceFileTaskSource, partialSource, { kind: Tasks.TaskSourceKind.WorkspaceFile, config: configElement }); + break; + } + default: { + taskSource = Objects.assign({} as Tasks.WorkspaceTaskSource, partialSource, { kind: Tasks.TaskSourceKind.Workspace, config: configElement }); + break; + } + } let result: Tasks.ConfiguringTask = new Tasks.ConfiguringTask( `${typeDeclaration.extensionId}.${taskIdentifier._key}`, - Objects.assign({} as Tasks.WorkspaceTaskSource, source, { config: configElement }), + taskSource, undefined, type, taskIdentifier, @@ -1419,7 +1434,7 @@ namespace ConfiguringTask { } namespace CustomTask { - export function from(this: void, external: CustomTask, context: ParseContext, index: number): Tasks.CustomTask | undefined { + export function from(this: void, external: CustomTask, context: ParseContext, index: number, source: TaskConfigSource): Tasks.CustomTask | undefined { if (!external) { return undefined; } @@ -1440,9 +1455,25 @@ namespace CustomTask { return undefined; } + let taskSource: Tasks.FileBasedTaskSource; + switch (source) { + case TaskConfigSource.User: { + taskSource = Objects.assign({} as Tasks.UserTaskSource, partialSource, { kind: Tasks.TaskSourceKind.User, config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }); + break; + } + case TaskConfigSource.WorkspaceFile: { + taskSource = Objects.assign({} as Tasks.WorkspaceFileTaskSource, partialSource, { kind: Tasks.TaskSourceKind.WorkspaceFile, config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder, workspace: context.workspace } }); + break; + } + default: { + taskSource = Objects.assign({} as Tasks.WorkspaceTaskSource, partialSource, { kind: Tasks.TaskSourceKind.Workspace, config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }); + break; + } + } + let result: Tasks.CustomTask = new Tasks.CustomTask( context.uuidMap.getUUID(taskName), - Objects.assign({} as Tasks.WorkspaceTaskSource, source, { config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }), + taskSource, taskName, Tasks.CUSTOMIZED_TASK_TYPE, undefined, @@ -1574,7 +1605,7 @@ namespace TaskParser { return customize === undefined && (type === undefined || type === null || type === Tasks.CUSTOMIZED_TASK_TYPE || type === 'shell' || type === 'process'); } - export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext): TaskParseResult { + export function from(this: void, externals: Array | undefined, globals: Globals, context: ParseContext, source: TaskConfigSource): TaskParseResult { let result: TaskParseResult = { custom: [], configured: [] }; if (!externals) { return result; @@ -1586,7 +1617,7 @@ namespace TaskParser { for (let index = 0; index < externals.length; index++) { let external = externals[index]; if (isCustomTask(external)) { - let customTask = CustomTask.from(external, context, index); + let customTask = CustomTask.from(external, context, index, source); if (customTask) { CustomTask.fillGlobals(customTask, globals); CustomTask.fillDefaults(customTask, context); @@ -1624,7 +1655,7 @@ namespace TaskParser { result.custom.push(customTask); } } else { - let configuredTask = ConfiguringTask.from(external, context, index); + let configuredTask = ConfiguringTask.from(external, context, index, source); if (configuredTask) { configuredTask.addTaskLoadMessages(context.taskLoadIssues); result.configured.push(configuredTask); @@ -1872,25 +1903,34 @@ class UUIDMap { } } +export enum TaskConfigSource { + TasksJson, + WorkspaceFile, + User +} + class ConfigurationParser { private workspaceFolder: IWorkspaceFolder; + private workspace: IWorkspace | undefined; private problemReporter: IProblemReporter; private uuidMap: UUIDMap; private platform: Platform; - constructor(workspaceFolder: IWorkspaceFolder, platform: Platform, problemReporter: IProblemReporter, uuidMap: UUIDMap) { + constructor(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, problemReporter: IProblemReporter, uuidMap: UUIDMap) { this.workspaceFolder = workspaceFolder; + this.workspace = workspace; this.platform = platform; this.problemReporter = problemReporter; this.uuidMap = uuidMap; } - public run(fileConfig: ExternalTaskRunnerConfiguration): ParseResult { + public run(fileConfig: ExternalTaskRunnerConfiguration, source: TaskConfigSource): ParseResult { let engine = ExecutionEngine.from(fileConfig); let schemaVersion = JsonSchemaVersion.from(fileConfig); let context: ParseContext = { workspaceFolder: this.workspaceFolder, + workspace: this.workspace, problemReporter: this.problemReporter, uuidMap: this.uuidMap, namedProblemMatchers: {}, @@ -1899,7 +1939,7 @@ class ConfigurationParser { platform: this.platform, taskLoadIssues: [] }; - let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context); + let taskParseResult = this.createTaskRunnerConfiguration(fileConfig, context, source); return { validationStatus: this.problemReporter.status, custom: taskParseResult.custom, @@ -1908,7 +1948,7 @@ class ConfigurationParser { }; } - private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext): TaskParseResult { + private createTaskRunnerConfiguration(fileConfig: ExternalTaskRunnerConfiguration, context: ParseContext, source: TaskConfigSource): TaskParseResult { let globals = Globals.from(fileConfig, context); if (this.problemReporter.status.isFatal()) { return { custom: [], configured: [] }; @@ -1917,13 +1957,13 @@ class ConfigurationParser { let globalTasks: Tasks.CustomTask[] | undefined = undefined; let externalGlobalTasks: Array | undefined = undefined; if (fileConfig.windows && context.platform === Platform.Windows) { - globalTasks = TaskParser.from(fileConfig.windows.tasks, globals, context).custom; + globalTasks = TaskParser.from(fileConfig.windows.tasks, globals, context, source).custom; externalGlobalTasks = fileConfig.windows.tasks; } else if (fileConfig.osx && context.platform === Platform.Mac) { - globalTasks = TaskParser.from(fileConfig.osx.tasks, globals, context).custom; + globalTasks = TaskParser.from(fileConfig.osx.tasks, globals, context, source).custom; externalGlobalTasks = fileConfig.osx.tasks; } else if (fileConfig.linux && context.platform === Platform.Linux) { - globalTasks = TaskParser.from(fileConfig.linux.tasks, globals, context).custom; + globalTasks = TaskParser.from(fileConfig.linux.tasks, globals, context, source).custom; externalGlobalTasks = fileConfig.linux.tasks; } if (context.schemaVersion === Tasks.JsonSchemaVersion.V2_0_0 && globalTasks && globalTasks.length > 0 && externalGlobalTasks && externalGlobalTasks.length > 0) { @@ -1940,7 +1980,7 @@ class ConfigurationParser { let result: TaskParseResult = { custom: [], configured: [] }; if (fileConfig.tasks) { - result = TaskParser.from(fileConfig.tasks, globals, context); + result = TaskParser.from(fileConfig.tasks, globals, context, source); } if (globalTasks) { result.custom = TaskParser.assignTasks(result.custom, globalTasks); @@ -1989,7 +2029,7 @@ class ConfigurationParser { } let uuidMaps: Map = new Map(); -export function parse(workspaceFolder: IWorkspaceFolder, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter): ParseResult { +export function parse(workspaceFolder: IWorkspaceFolder, workspace: IWorkspace | undefined, platform: Platform, configuration: ExternalTaskRunnerConfiguration, logger: IProblemReporter, source: TaskConfigSource): ParseResult { let uuidMap = uuidMaps.get(workspaceFolder.uri.toString()); if (!uuidMap) { uuidMap = new UUIDMap(); @@ -1997,7 +2037,7 @@ export function parse(workspaceFolder: IWorkspaceFolder, platform: Platform, con } try { uuidMap.start(); - return (new ConfigurationParser(workspaceFolder, platform, logger, uuidMap)).run(configuration); + return (new ConfigurationParser(workspaceFolder, workspace, platform, logger, uuidMap)).run(configuration, source); } finally { uuidMap.finish(); } diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 1b6a7f1da62c..54309043cead 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -5,12 +5,13 @@ import * as nls from 'vs/nls'; import * as Types from 'vs/base/common/types'; +import * as resources from 'vs/base/common/resources'; import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import * as Objects from 'vs/base/common/objects'; import { UriComponents } from 'vs/base/common/uri'; import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -374,10 +375,13 @@ export namespace TaskSourceKind { export const Workspace: 'workspace' = 'workspace'; export const Extension: 'extension' = 'extension'; export const InMemory: 'inMemory' = 'inMemory'; + export const WorkspaceFile: 'workspaceFile' = 'workspaceFile'; + export const User: 'user' = 'user'; } export interface TaskSourceConfigElement { workspaceFolder: IWorkspaceFolder; + workspace?: IWorkspace; file: string; index: number; element: any; @@ -410,8 +414,20 @@ export interface InMemoryTaskSource extends BaseTaskSource { readonly kind: 'inMemory'; } -export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | InMemoryTaskSource; +export interface UserTaskSource extends BaseTaskSource { + readonly kind: 'user'; + readonly config: TaskSourceConfigElement; + readonly customizes?: KeyedTaskIdentifier; +} + +export interface WorkspaceFileTaskSource extends BaseTaskSource { + readonly kind: 'workspaceFile'; + readonly config: TaskSourceConfigElement; + readonly customizes?: KeyedTaskIdentifier; +} +export type TaskSource = WorkspaceTaskSource | ExtensionTaskSource | InMemoryTaskSource | UserTaskSource | WorkspaceFileTaskSource; +export type FileBasedTaskSource = WorkspaceTaskSource | UserTaskSource | WorkspaceFileTaskSource; export interface TaskIdentifier { type: string; [name: string]: any; @@ -566,6 +582,10 @@ export abstract class CommonTask { return undefined; } + public getWorkspaceFileName(): string | undefined { + return undefined; + } + public getTelemetryKind(): string { return 'unknown'; } @@ -619,7 +639,7 @@ export class CustomTask extends CommonTask { /** * Indicated the source of the task (e.g. tasks.json or extension) */ - _source: WorkspaceTaskSource; + _source: FileBasedTaskSource; hasDefinedMatchers: boolean; @@ -628,7 +648,7 @@ export class CustomTask extends CommonTask { */ command: CommandConfiguration = {}; - public constructor(id: string, source: WorkspaceTaskSource, label: string, type: string, command: CommandConfiguration | undefined, + public constructor(id: string, source: FileBasedTaskSource, label: string, type: string, command: CommandConfiguration | undefined, hasDefinedMatchers: boolean, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { super(id, label, undefined, runOptions, configurationProperties, source); this._source = source; @@ -700,7 +720,11 @@ export class CustomTask extends CommonTask { if (!workspaceFolder) { return undefined; } - let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder.uri.toString(), id: this.configurationProperties.identifier! }; + let id: string = this.configurationProperties.identifier!; + if (this._source.kind !== TaskSourceKind.Workspace) { + id += this._source.kind; + } + let key: CustomKey = { type: CUSTOMIZED_TASK_TYPE, folder: workspaceFolder.uri.toString(), id }; return JSON.stringify(key); } @@ -708,6 +732,10 @@ export class CustomTask extends CommonTask { return this._source.config.workspaceFolder; } + public getWorkspaceFileName(): string | undefined { + return (this._source.config.workspace && this._source.config.workspace.configuration) ? resources.basename(this._source.config.workspace.configuration) : undefined; + } + public getTelemetryKind(): string { if (this._source.customizes) { return 'workspace>extension'; @@ -726,11 +754,11 @@ export class ConfiguringTask extends CommonTask { /** * Indicated the source of the task (e.g. tasks.json or extension) */ - _source: WorkspaceTaskSource; + _source: FileBasedTaskSource; configures: KeyedTaskIdentifier; - public constructor(id: string, source: WorkspaceTaskSource, label: string | undefined, type: string | undefined, + public constructor(id: string, source: FileBasedTaskSource, label: string | undefined, type: string | undefined, configures: KeyedTaskIdentifier, runOptions: RunOptions, configurationProperties: ConfigurationProperties) { super(id, label, type, runOptions, configurationProperties, source); this._source = source; @@ -748,6 +776,10 @@ export class ConfiguringTask extends CommonTask { public getDefinition(): KeyedTaskIdentifier { return this.configures; } + + public getWorkspaceFileName(): string | undefined { + return (this._source.config.workspace && this._source.config.workspace.configuration) ? resources.basename(this._source.config.workspace.configuration) : undefined; + } } export class ContributedTask extends CommonTask { diff --git a/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts index e45fac586c4c..f454dd96b355 100644 --- a/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts @@ -10,17 +10,19 @@ import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { ValidationStatus } from 'vs/base/common/parsers'; import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { WorkspaceFolder, Workspace, IWorkspace } from 'vs/platform/workspace/common/workspace'; import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; -import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; +import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask, TaskConfigSource } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; -const workspaceFolder: IWorkspaceFolder = new WorkspaceFolder({ +const workspaceFolder: WorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), name: 'folderOne', index: 0 }); +const workspace: IWorkspace = new Workspace('id', [workspaceFolder]); + class ProblemReporter implements IProblemReporter { private _validationStatus: ValidationStatus = new ValidationStatus(); @@ -357,7 +359,7 @@ class PatternBuilder { function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, resolved: number) { let reporter = new ProblemReporter(); - let result = parse(workspaceFolder, Platform.platform, external, reporter); + let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson); assert.ok(!reporter.receivedMessage); assert.strictEqual(result.custom.length, 1); let task = result.custom[0]; @@ -368,7 +370,7 @@ function testDefaultProblemMatcher(external: ExternalTaskRunnerConfiguration, re function testConfiguration(external: ExternalTaskRunnerConfiguration, builder: ConfiguationBuilder): void { builder.done(); let reporter = new ProblemReporter(); - let result = parse(workspaceFolder, Platform.platform, external, reporter); + let result = parse(workspaceFolder, workspace, Platform.platform, external, reporter, TaskConfigSource.TasksJson); if (reporter.receivedMessage) { assert.ok(false, reporter.lastMessage); } @@ -1730,4 +1732,4 @@ suite('Bugs / regression tests', () => { runtime(Tasks.RuntimeType.Shell); testConfiguration(external, builder); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index bb719e489332..8a63cabd26ab 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -310,6 +310,11 @@ configurationRegistry.registerConfiguration({ description: nls.localize('terminal.integrated.experimentalUseTitleEvent', "An experimental setting that will use the terminal title event for the dropdown title. This setting will only apply to new terminals."), type: 'boolean', default: false + }, + 'terminal.integrated.enableFileLinks': { + description: nls.localize('terminal.integrated.enableFileLinks', "Whether to enable file links in the terminal. Links can be slow when working on a network drive in particular because each file link is verified against the file system."), + type: 'boolean', + default: true } } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 151c0ededf3e..752cb6c4a095 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -108,7 +108,9 @@ export class TerminalLinkHandler { this.registerWebLinkHandler(); if (this._processManager) { - this.registerLocalLinkHandler(); + if (this._configHelper.config.enableFileLinks) { + this.registerLocalLinkHandler(); + } this.registerGitDiffLinkHandlers(); } } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts b/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts index dc67c979a948..a0cf456dfee6 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm-private.d.ts @@ -22,11 +22,6 @@ export interface XTermCore { }; _onIntersectionChange: any; }; - - // TODO: Remove below once a synchronous write API is added - // The below are only used in tests - writeBuffer: string[]; - _innerWrite(): void; } export interface IEventEmitter { diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index e423624da30f..6e7e734f3d06 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -116,6 +116,7 @@ export interface ITerminalConfiguration { windowsEnableConpty: boolean; experimentalRefreshOnResume: boolean; experimentalUseTitleEvent: boolean; + enableFileLinks: boolean; } export interface ITerminalConfigHelper { diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index 28dfd6b42b2e..df354f69d0e0 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -13,10 +13,8 @@ interface TestTerminal extends Terminal { _core: XTermCore; } -function syncWrite(term: TestTerminal, data: string): void { - // Terminal.write is asynchronous - term._core.writeBuffer.push(data); - term._core._innerWrite(); +function writePromise(term: Terminal, data: string): Promise { + return new Promise(r => term.write(data, r)); } const ROWS = 10; diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index 7098d2c4d51f..01ddc8a8bfc8 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -9,6 +9,7 @@ import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/termin import * as strings from 'vs/base/common/strings'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { Event } from 'vs/base/common/event'; +import { ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { @@ -62,13 +63,19 @@ interface LinkFormatInfo { column?: string; } +const testConfigHelper: ITerminalConfigHelper = { + config: { + enableFileLinks: true + } +}; + suite('Workbench - TerminalLinkHandler', () => { suite('localLinkRegex', () => { test('Windows', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: '' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -144,7 +151,7 @@ suite('Workbench - TerminalLinkHandler', () => { const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -212,7 +219,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\Me' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); linkHandler.processCwd = 'C:\\base'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1'); @@ -225,7 +232,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Windows, userHome: 'C:\\Users\\M e' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); linkHandler.processCwd = 'C:\\base dir'; assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1'); @@ -239,7 +246,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); linkHandler.processCwd = '/base'; assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1'); @@ -252,7 +259,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '/home/me' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); @@ -266,7 +273,7 @@ suite('Workbench - TerminalLinkHandler', () => { const linkHandler = new TestTerminalLinkHandler(new TestXterm() as any, { os: OperatingSystem.Linux, userHome: '' - } as any, null!, null!, null!, null!, new MockTerminalInstanceService(), null!); + } as any, testConfigHelper, null!, null!, null!, new MockTerminalInstanceService(), null!); function assertAreGoodMatches(matches: RegExpMatchArray | null) { if (matches) { diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 638bfb73d7d2..802d2d9a766d 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -25,7 +25,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { generateUuid } from 'vs/base/common/uuid'; -import { renderMarkdownDocument } from 'vs/workbench/common/markdownDocumentRenderer'; +import { renderMarkdownDocument } from 'vs/workbench/contrib/markdown/common/markdownDocumentRenderer'; export class ReleaseNotesManager { diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index bdcddbf3191d..7dad05383aa4 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -9,7 +9,7 @@ import { Action } from 'vs/base/common/actions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -27,9 +27,13 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; +// TODO@Joao layer breaker +// tslint:disable-next-line: layering +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; + const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Uninitialized); let releaseNotesManager: ReleaseNotesManager | undefined = undefined; @@ -122,45 +126,42 @@ export class ProductContribution implements IWorkbenchContribution { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IOpenerService openerService: IOpenerService, @IConfigurationService configurationService: IConfigurationService, - @IWindowService windowService: IWindowService, - @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @IProductService productService: IProductService ) { - windowsService.getActiveWindowId().then(async windowId => { - if (windowId !== windowService.windowId) { - return; - } + if (!hostService.hasFocus) { + return; + } - const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); - const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); - - // was there an update? if so, open release notes - const releaseNotesUrl = productService.releaseNotesUrl; - if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { - showReleaseNotes(instantiationService, productService.version) - .then(undefined, () => { - notificationService.prompt( - severity.Info, - nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version), - [{ - label: nls.localize('releaseNotes', "Release Notes"), - run: () => { - const uri = URI.parse(releaseNotesUrl); - openerService.open(uri); - } - }], - { sticky: true } - ); - }); - } + const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, ''); + const shouldShowReleaseNotes = configurationService.getValue('update.showReleaseNotes'); + + // was there an update? if so, open release notes + const releaseNotesUrl = productService.releaseNotesUrl; + if (shouldShowReleaseNotes && !environmentService.skipReleaseNotes && releaseNotesUrl && lastVersion && productService.version !== lastVersion) { + showReleaseNotes(instantiationService, productService.version) + .then(undefined, () => { + notificationService.prompt( + severity.Info, + nls.localize('read the release notes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", productService.nameLong, productService.version), + [{ + label: nls.localize('releaseNotes', "Release Notes"), + run: () => { + const uri = URI.parse(releaseNotesUrl); + openerService.open(uri); + } + }], + { sticky: true } + ); + }); + } - // should we show the new license? - if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { - notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); - } + // should we show the new license? + if (productService.licenseUrl && lastVersion && semver.satisfies(lastVersion, '<1.0.0') && semver.satisfies(productService.version, '>=1.0.0')) { + notificationService.info(nls.localize('licenseChanged', "Our license terms have changed, please click [here]({0}) to go through them.", productService.licenseUrl)); + } - storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); - }); + storageService.store(ProductContribution.KEY, productService.version, StorageScope.GLOBAL); } } @@ -170,6 +171,8 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private readonly badgeDisposable = this._register(new MutableDisposable()); private updateStateContextKey: IContextKey; + private windowId: number | undefined = this.electronEnvironmentService ? this.electronEnvironmentService.windowId : undefined; + constructor( @IStorageService private readonly storageService: IStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -177,9 +180,9 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu @IDialogService private readonly dialogService: IDialogService, @IUpdateService private readonly updateService: IUpdateService, @IActivityService private readonly activityService: IActivityService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService ) { super(); this.state = updateService.state; @@ -215,7 +218,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu case StateType.Idle: if (state.error) { this.onError(state.error); - } else if (this.state.type === StateType.CheckingForUpdates && this.state.context && this.state.context.windowId === this.environmentService.configuration.windowId) { + } else if (this.state.type === StateType.CheckingForUpdates && this.state.context && this.state.context.windowId === this.windowId) { this.onUpdateNotAvailable(); } break; @@ -398,7 +401,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu } private registerGlobalActivityActions(): void { - CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates({ windowId: this.environmentService.configuration.windowId })); + CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates({ windowId: this.windowId })); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { group: '6_update', command: { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 3533ba433ce6..7168c908ceed 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -29,6 +29,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { isWeb } from 'vs/base/common/platform'; import { UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IEditorInput } from 'vs/workbench/common/editor'; class UserDataSyncConfigurationContribution implements IWorkbenchContribution { @@ -111,16 +112,20 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi handle.onDidClose(() => this.conflictsWarningDisposable.clear()); } } else { + const previewEditorInput = this.getPreviewEditorInput(); + if (previewEditorInput) { + previewEditorInput.dispose(); + } this.conflictsWarningDisposable.clear(); } } private async continueSync(): Promise { // Get the preview editor - const editorInput = this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource))[0]; + const previewEditorInput = this.getPreviewEditorInput(); // Save the preview - if (editorInput && editorInput.isDirty()) { - await this.textFileService.save(editorInput.getResource()!); + if (previewEditorInput && previewEditorInput.isDirty()) { + await this.textFileService.save(previewEditorInput.getResource()!); } try { // Continue Sync @@ -130,11 +135,15 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi return; } // Close the preview editor - if (editorInput) { - editorInput.dispose(); + if (previewEditorInput) { + previewEditorInput.dispose(); } } + private getPreviewEditorInput(): IEditorInput | undefined { + return this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource))[0]; + } + private async handleConflicts(): Promise { if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { const resourceInput = { @@ -166,11 +175,11 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi group: '5_sync', command: { id: 'workbench.userData.actions.syncStart', - title: localize('start sync', "Sync: Start") + title: localize('start sync', "Configuration Sync: Turn On") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not('config.userConfiguration.enableSync')), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not('config.configurationSync.enable')), }; - CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.configurationService.updateValue('userConfiguration.enableSync', true)); + CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', true)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem); MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); @@ -178,11 +187,11 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi group: '5_sync', command: { id: 'workbench.userData.actions.stopSync', - title: localize('stop sync', "Sync: Stop") + title: localize('stop sync', "Configuration Sync: Turn Off") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.userConfiguration.enableSync')), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.configurationSync.enable')), }; - CommandsRegistry.registerCommand(stopSyncMenuItem.command.id, () => this.configurationService.updateValue('userConfiguration.enableSync', false)); + CommandsRegistry.registerCommand(stopSyncMenuItem.command.id, () => this.configurationService.updateValue('configurationSync.enable', false)); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, stopSyncMenuItem); MenuRegistry.appendMenuItem(MenuId.CommandPalette, stopSyncMenuItem); @@ -190,7 +199,7 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi group: '5_sync', command: { id: 'sync.resolveConflicts', - title: localize('resolveConflicts', "Sync: Resolve Conflicts"), + title: localize('resolveConflicts', "Configuration Sync: Resolve Conflicts"), }, when: CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), }; @@ -203,14 +212,14 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: continueSyncCommandId, - title: localize('continue sync', "Sync: Continue") + title: localize('continue sync', "Configuration Sync: Continue") }, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)), }); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: continueSyncCommandId, - title: localize('continue sync', "Sync: Continue"), + title: localize('continue sync', "Configuration Sync: Continue"), iconLocation: { light: SYNC_PUSH_LIGHT_ICON_URI, dark: SYNC_PUSH_DARK_ICON_URI diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index e7a6fe17ede1..5da5747d0c48 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -24,6 +24,11 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd private _html: string = ''; private _initialScrollProgress: number = 0; private _state: string | undefined = undefined; + private _extension: { + readonly location: URI; + readonly id?: ExtensionIdentifier; + } | undefined; + private _owner: any = undefined; public constructor( @@ -82,6 +87,7 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd this._webview.value = webview; webview.state = this._state; webview.html = this._html; + webview.extension = this._extension; if (this.options.tryRestoreScrollPosition) { webview.initialScrollProgress = this._initialScrollProgress; } @@ -133,6 +139,12 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd this.withWebview(webview => webview.contentOptions = value); } + public get extension() { return this._extension; } + public set extension(value) { + this._extension = value; + this.withWebview(webview => webview.extension = value); + } + private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus: Event = this._onDidFocus.event; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 6674eb77fdfb..b4cd2b97b65c 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -412,16 +412,19 @@ newFrame.contentWindow.addEventListener('keydown', handleInnerKeydown); newFrame.contentWindow.addEventListener('DOMContentLoaded', e => { - if (host.fakeLoad) { - newFrame.contentDocument.open(); - newFrame.contentDocument.write(newDocument); - newFrame.contentDocument.close(); - hookupOnLoadHandlers(newFrame); - } - const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; - if (contentDocument) { - applyStyles(contentDocument, contentDocument.body); - } + // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325 + setTimeout(() => { + if (host.fakeLoad) { + newFrame.contentDocument.open(); + newFrame.contentDocument.write(newDocument); + newFrame.contentDocument.close(); + hookupOnLoadHandlers(newFrame); + } + const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; + if (contentDocument) { + applyStyles(contentDocument, contentDocument.body); + } + }, 0); }); const onLoad = (contentDocument, contentWindow) => { diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 437b18ea51ea..15f61d40433d 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -40,13 +40,8 @@ export interface IWebviewService { ): WebviewEditorOverlay; } -export const WebviewResourceScheme = 'vscode-resource'; - export interface WebviewOptions { - readonly extension?: { - readonly location: URI; - readonly id?: ExtensionIdentifier; - }; + readonly customClasses?: string; readonly enableFindWidget?: boolean; readonly tryRestoreScrollPosition?: boolean; readonly retainContextWhenHidden?: boolean; @@ -63,6 +58,10 @@ export interface Webview extends IDisposable { html: string; contentOptions: WebviewContentOptions; + extension: { + readonly location: URI; + readonly id?: ExtensionIdentifier; + } | undefined; initialScrollProgress: number; state: string | undefined; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 0e78c9008a6a..d958363ced86 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -11,7 +11,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; @@ -40,7 +40,7 @@ export class WebviewEditor extends BaseEditor { @IThemeService themeService: IThemeService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IEditorService private readonly _editorService: IEditorService, - @IWindowService private readonly _windowService: IWindowService, + @IHostService private readonly _hostService: IHostService, @IStorageService storageService: IStorageService ) { super(WebviewEditor.ID, telemetryService, themeService, storageService); @@ -101,7 +101,7 @@ export class WebviewEditor extends BaseEditor { super.focus(); if (!this._onFocusWindowHandler.value) { // Make sure we restore focus when switching back to a VS Code window - this._onFocusWindowHandler.value = this._windowService.onDidChangeFocus(focused => { + this._onFocusWindowHandler.value = this._hostService.onDidChangeFocus(focused => { if (focused && this._editorService.activeControl === this) { this.focus(); } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 879bdae79c38..309a3c86fba6 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { UnownedDisposable as Unowned } from 'vs/base/common/lifecycle'; @@ -67,16 +66,11 @@ export class WebviewInput extends EditorInput { public readonly id: string, public readonly viewType: string, name: string, - public readonly extension: undefined | { - readonly location: URI; - readonly id: ExtensionIdentifier; - }, webview: Unowned ) { super(); this._name = name; - this.extension = extension; this._webview = this._register(webview.acquire()); // The input owns this webview } @@ -113,6 +107,10 @@ export class WebviewInput extends EditorInput { return this._webview; } + public get extension() { + return this._webview.extension; + } + public get iconPath() { return this._iconPath; } @@ -150,14 +148,10 @@ export class RevivedWebviewEditorInput extends WebviewInput { id: string, viewType: string, name: string, - extension: undefined | { - readonly location: URI; - readonly id: ExtensionIdentifier - }, private readonly reviver: (input: WebviewInput) => Promise, webview: Unowned ) { - super(id, viewType, name, extension, webview); + super(id, viewType, name, webview); } public async resolve(): Promise { diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index 16798e07b3f8..b6cee607c329 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -150,7 +150,7 @@ export class WebviewEditorService implements IWebviewEditorService { ): WebviewInput { const webview = this.createWebiew(id, extension, options); - const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, extension, new UnownedDisposable(webview), undefined); + const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, new UnownedDisposable(webview), undefined); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -197,7 +197,7 @@ export class WebviewEditorService implements IWebviewEditorService { const webview = this.createWebiew(id, extension, options); webview.state = state; - const webviewInput = new RevivedWebviewEditorInput(id, viewType, title, extension, async (webview: WebviewInput): Promise => { + const webviewInput = new RevivedWebviewEditorInput(id, viewType, title, async (webview: WebviewInput): Promise => { const didRevive = await this.tryRevive(webview); if (didRevive) { return Promise.resolve(undefined); @@ -263,14 +263,15 @@ export class WebviewEditorService implements IWebviewEditorService { } private createWebiew(id: string, extension: { location: URI; id: ExtensionIdentifier; } | undefined, options: WebviewInputOptions) { - return this._webviewService.createWebviewEditorOverlay(id, { - extension: extension, + const webview = this._webviewService.createWebviewEditorOverlay(id, { enableFindWidget: options.enableFindWidget, retainContextWhenHidden: options.retainContextWhenHidden }, { ...options, localResourceRoots: options.localResourceRoots || this.getDefaultLocalResourceRoots(extension), }); + webview.extension = extension; + return webview; } private getDefaultLocalResourceRoots(extension: undefined | { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 5b5ccd27198c..accadd5af905 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -36,9 +36,14 @@ export class IFrameWebview extends Disposable implements Webview { private readonly _portMappingManager: WebviewPortMappingManager; + public extension: { + readonly location: URI; + readonly id?: ExtensionIdentifier; + } | undefined; + constructor( private readonly id: string, - private _options: WebviewOptions, + options: WebviewOptions, contentOptions: WebviewContentOptions, @IThemeService themeService: IThemeService, @ITunnelService tunnelService: ITunnelService, @@ -52,7 +57,7 @@ export class IFrameWebview extends Disposable implements Webview { } this._portMappingManager = this._register(new WebviewPortMappingManager( - this._options.extension ? this._options.extension.location : undefined, + () => this.extension ? this.extension.location : undefined, () => this.content.options.portMapping || [], tunnelService )); @@ -64,6 +69,7 @@ export class IFrameWebview extends Disposable implements Webview { }; this.element = document.createElement('iframe'); + this.element.className = `webview ${options.customClasses}`; this.element.sandbox.add('allow-scripts', 'allow-same-origin'); this.element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); this.element.style.border = 'none'; @@ -113,9 +119,10 @@ export class IFrameWebview extends Disposable implements Webview { case 'load-resource': { - const requestPath = e.data.data.path; - const uri = URI.file(decodeURIComponent(requestPath)); - this.loadResource(requestPath, uri); + const rawPath = e.data.data.path; + const normalizedPath = decodeURIComponent(rawPath); + const uri = URI.parse(normalizedPath.replace(/^\/(\w+)\/(.+)$/, (_, scheme, path) => scheme + ':/' + path)); + this.loadResource(rawPath, uri); return; } @@ -185,7 +192,7 @@ export class IFrameWebview extends Disposable implements Webview { private preprocessHtml(value: string): string { return value.replace(/(["'])vscode-resource:([^\s'"]+?)(["'])/gi, (_, startQuote, path, endQuote) => - `${startQuote}${this.externalEndpoint}/vscode-resource${path}${endQuote}`); + `${startQuote}${this.externalEndpoint}/vscode-resource/file${path}${endQuote}`); } public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { @@ -307,7 +314,7 @@ export class IFrameWebview extends Disposable implements Webview { private async loadResource(requestPath: string, uri: URI) { try { - const result = await loadLocalResource(uri, this.fileService, this._options.extension ? this._options.extension.location : undefined, + const result = await loadLocalResource(uri, this.fileService, this.extension ? this.extension.location : undefined, () => (this.content.options.localResourceRoots || [])); if (result.type === 'success') { diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index a50e4e552b57..cb1cb69d6e63 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -14,7 +14,7 @@ export class WebviewPortMappingManager extends Disposable { private readonly _tunnels = new Map>(); constructor( - private readonly extensionLocation: URI | undefined, + private readonly getExtensionLocation: () => URI | undefined, private readonly mappings: () => ReadonlyArray, private readonly tunnelService: ITunnelService ) { @@ -30,7 +30,8 @@ export class WebviewPortMappingManager extends Disposable { for (const mapping of this.mappings()) { if (mapping.webviewPort === requestLocalHostInfo.port) { - if (this.extensionLocation && this.extensionLocation.scheme === REMOTE_HOST_SCHEME) { + const extensionLocation = this.getExtensionLocation(); + if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort); if (tunnel) { return encodeURI(uri.with({ diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index 96189fbfa60a..cb1990e2eb9c 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -11,6 +11,8 @@ import { IFileService } from 'vs/platform/files/common/files'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { getWebviewContentMimeType } from 'vs/workbench/contrib/webview/common/mimeTypes'; +export const WebviewResourceScheme = 'vscode-resource'; + class Success { readonly type = 'success'; @@ -45,11 +47,7 @@ export async function loadLocalResource( extensionLocation: URI | undefined, getRoots: () => ReadonlyArray ): Promise { - const normalizedPath = requestUri.with({ - scheme: 'file', - fragment: '', - query: '', - }); + const normalizedPath = normalizeRequestPath(requestUri); for (const root of getRoots()) { if (!containsResource(root, normalizedPath)) { @@ -74,6 +72,21 @@ export async function loadLocalResource( return AccessDenied; } +function normalizeRequestPath(requestUri: URI) { + if (requestUri.scheme !== WebviewResourceScheme) { + return requestUri; + } + + // Modern vscode-resources uris put the scheme of the requested resource as the authority + if (requestUri.authority) { + return URI.parse(requestUri.authority + ':' + requestUri.path); + } + + // Old style vscode-resource uris lose the scheme of the resource which means they are unable to + // load a mix of local and remote content properly. + return requestUri.with({ scheme: 'file' }); +} + function containsResource(root: URI, resource: URI): boolean { const rootPath = root.fsPath + (endsWith(root.fsPath, sep) ? '' : sep); return startsWith(resource.fsPath, rootPath); diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index cf248edcb5a6..738ce026e68f 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebviewTag, WebContents, FindInPageOptions } from 'electron'; +import { FindInPageOptions, OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebContents, WebviewTag } from 'electron'; import { addClass, addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; @@ -19,12 +19,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { Webview, WebviewContentOptions, WebviewOptions, WebviewResourceScheme } from 'vs/workbench/contrib/webview/browser/webview'; +import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/portMapping'; +import { WebviewResourceScheme } from 'vs/workbench/contrib/webview/common/resourceLoader'; import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; import { registerFileProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; import { areWebviewInputOptionsEqual } from '../browser/webviewEditorService'; -import { WebviewFindWidget, WebviewFindDelegate } from '../browser/webviewFindWidget'; +import { WebviewFindDelegate, WebviewFindWidget } from '../browser/webviewFindWidget'; interface IKeydownEvent { key: string; @@ -92,7 +93,7 @@ class WebviewSession extends Disposable { class WebviewProtocolProvider extends Disposable { constructor( webview: WebviewTag, - private readonly _extensionLocation: URI | undefined, + private readonly _getExtensionLocation: () => URI | undefined, private readonly _getLocalResourceRoots: () => ReadonlyArray, private readonly _fileService: IFileService, ) { @@ -111,7 +112,7 @@ class WebviewProtocolProvider extends Disposable { return; } - registerFileProtocol(contents, WebviewResourceScheme, this._fileService, this._extensionLocation, () => + registerFileProtocol(contents, WebviewResourceScheme, this._fileService, this._getExtensionLocation(), () => this._getLocalResourceRoots() ); } @@ -123,12 +124,12 @@ class WebviewPortMappingProvider extends Disposable { constructor( session: WebviewSession, - extensionLocation: URI | undefined, + getExtensionLocation: () => URI | undefined, mappings: () => ReadonlyArray, tunnelService: ITunnelService, ) { super(); - this._manager = this._register(new WebviewPortMappingManager(extensionLocation, mappings, tunnelService)); + this._manager = this._register(new WebviewPortMappingManager(getExtensionLocation, mappings, tunnelService)); session.onBeforeRequest(async details => { const redirect = await this._manager.getRedirect(details.url); @@ -233,8 +234,13 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, private readonly _onDidFocus = this._register(new Emitter()); public readonly onDidFocus: Event = this._onDidFocus.event; + public extension: { + readonly location: URI; + readonly id?: ExtensionIdentifier; + } | undefined; + constructor( - private readonly _options: WebviewOptions, + options: WebviewOptions, contentOptions: WebviewContentOptions, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -254,6 +260,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, this._webview = document.createElement('webview'); this._webview.setAttribute('partition', `webview${Date.now()}`); this._webview.setAttribute('webpreferences', 'contextIsolation=yes'); + this._webview.className = `webview ${options.customClasses}`; this._webview.style.flex = '0 1'; this._webview.style.width = '0'; @@ -279,13 +286,13 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, this._register(new WebviewProtocolProvider( this._webview, - this._options.extension ? this._options.extension.location : undefined, + () => this.extension ? this.extension.location : undefined, () => (this.content.options.localResourceRoots || []), fileService)); this._register(new WebviewPortMappingProvider( session, - _options.extension ? _options.extension.location : undefined, + () => this.extension ? this.extension.location : undefined, () => (this.content.options.portMapping || []), tunnelService, )); @@ -382,7 +389,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, this._send('devtools-opened'); })); - if (_options.enableFindWidget) { + if (options.enableFindWidget) { this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); this._register(addDisposableListener(this._webview, 'found-in-page', e => { @@ -529,9 +536,9 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, } this._hasAlertedAboutMissingCsp = true; - if (this._options.extension && this._options.extension.id) { + if (this.extension && this.extension.id) { if (this._environementService.isExtensionDevelopment) { - this._onMissingCsp.fire(this._options.extension.id); + this._onMissingCsp.fire(this.extension.id); } type TelemetryClassification = { @@ -542,7 +549,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, }; this._telemetryService.publicLog2('webviewMissingCsp', { - extension: this._options.extension.id.value + extension: this.extension.id.value }); } } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts index fbde988eca91..94fe91907e53 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts @@ -11,7 +11,6 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { language, locale } from 'vs/base/common/platform'; @@ -29,7 +28,6 @@ export class TelemetryOptOut implements IWorkbenchContribution { @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, @INotificationService private readonly notificationService: INotificationService, - @IWindowService windowService: IWindowService, @IHostService hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExperimentService private readonly experimentService: IExperimentService, @@ -42,11 +40,10 @@ export class TelemetryOptOut implements IWorkbenchContribution { } const experimentId = 'telemetryOptOut'; Promise.all([ - windowService.isFocused(), hostService.windowCount, experimentService.getExperimentById(experimentId) - ]).then(([focused, count, experimentState]) => { - if (!focused && count > 1) { + ]).then(([count, experimentState]) => { + if (!hostService.hasFocus && count > 1) { return; } storageService.store(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL); diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 4712610f479d..f7d016182508 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -14,7 +14,8 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors'; -import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; @@ -40,7 +41,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { joinPath } from 'vs/base/common/resources'; -import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder } from 'vs/platform/workspaces/common/workspacesHistory'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; // {{SQL CARBON EDIT}} import { setProductQuality } from 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} @@ -254,7 +255,7 @@ class WelcomePage extends Disposable { constructor( @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWindowService private readonly windowService: IWindowService, + @IWorkspacesHistoryService private readonly workspacesHistoryService: IWorkspacesHistoryService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILabelService private readonly labelService: ILabelService, @@ -272,7 +273,7 @@ class WelcomePage extends Disposable { super(); this._register(lifecycleService.onShutdown(() => this.dispose())); - const recentlyOpened = this.windowService.getRecentlyOpened(); + const recentlyOpened = this.workspacesHistoryService.getRecentlyOpened(); const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions); // {{SQL CARBON EDIT}} - Redirect to ADS welcome page setProductQuality(this.environmentService.appQuality); @@ -347,13 +348,13 @@ class WelcomePage extends Disposable { private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) { return recents.map(recent => { let fullPath: string; - let uriToOpen: IURIToOpen; + let windowOpenable: IWindowOpenable; if (isRecentFolder(recent)) { - uriToOpen = { folderUri: recent.folderUri }; + windowOpenable = { folderUri: recent.folderUri }; fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true }); } else { fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); - uriToOpen = { workspaceUri: recent.workspace.configPath }; + windowOpenable = { workspaceUri: recent.workspace.configPath }; } const { name, parentPath } = splitName(fullPath); @@ -370,7 +371,7 @@ class WelcomePage extends Disposable { id: 'openRecentFolder', from: telemetryFrom }); - this.windowService.openWindow([uriToOpen], { forceNewWindow: e.ctrlKey || e.metaKey }); + this.hostService.openInWindow([windowOpenable], { forceNewWindow: e.ctrlKey || e.metaKey }); e.preventDefault(); e.stopPropagation(); }); diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 01ff746f5eb2..1c0799fca9c6 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -5,7 +5,6 @@ import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; import * as browser from 'vs/base/browser/browser'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -19,6 +18,7 @@ import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class CloseCurrentWindowAction extends Action { @@ -167,8 +167,7 @@ export abstract class BaseSwitchWindow extends Action { constructor( id: string, label: string, - private windowsService: IWindowsService, - private windowService: IWindowService, + private electronEnvironmentService: IElectronEnvironmentService, private quickInputService: IQuickInputService, private keybindingService: IKeybindingService, private modelService: IModelService, @@ -176,15 +175,14 @@ export abstract class BaseSwitchWindow extends Action { private electronService: IElectronService ) { super(id, label); - } protected abstract isQuickNavigate(): boolean; async run(): Promise { - const currentWindowId = this.windowService.windowId; + const currentWindowId = this.electronEnvironmentService.windowId; - const windows = await this.windowsService.getWindows(); + const windows = await this.electronService.getWindows(); const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to"); const picks = windows.map(win => { const resource = win.filename ? URI.file(win.filename) : win.folderUri ? win.folderUri : win.workspace ? win.workspace.configPath : undefined; @@ -211,7 +209,7 @@ export abstract class BaseSwitchWindow extends Action { }); if (pick) { - this.windowsService.focusWindow(pick.payload); + this.electronService.focusWindow({ windowId: pick.payload }); } } } @@ -224,15 +222,14 @@ export class SwitchWindow extends BaseSwitchWindow { constructor( id: string, label: string, - @IWindowsService windowsService: IWindowsService, - @IWindowService windowService: IWindowService, + @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService, @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @IElectronService electronService: IElectronService ) { - super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService, electronService); + super(id, label, electronEnvironmentService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { @@ -248,15 +245,14 @@ export class QuickSwitchWindow extends BaseSwitchWindow { constructor( id: string, label: string, - @IWindowsService windowsService: IWindowsService, - @IWindowService windowService: IWindowService, + @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService, @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @IElectronService electronService: IElectronService ) { - super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService, electronService); + super(id, label, electronEnvironmentService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { diff --git a/src/vs/workbench/electron-browser/actions/workspaceActions.ts b/src/vs/workbench/electron-browser/actions/workspaceActions.ts index 47bd64b35676..035beb1280ea 100644 --- a/src/vs/workbench/electron-browser/actions/workspaceActions.ts +++ b/src/vs/workbench/electron-browser/actions/workspaceActions.ts @@ -5,7 +5,7 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; @@ -51,7 +51,7 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { label: string, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { @@ -65,6 +65,6 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace); - return this.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + return this.hostService.openInWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); } } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index dd0daa47d910..3f97dbe55168 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -24,7 +24,7 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogInMainService } from 'vs/platform/log/common/log'; -import { StorageService } from 'vs/platform/storage/node/storageService'; +import { NativeStorageService } from 'vs/platform/storage/node/storageService'; import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/common/extpath'; @@ -44,7 +44,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; -import { DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationExportHelper'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SignService } from 'vs/platform/sign/node/signService'; @@ -53,14 +52,16 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file import { basename } from 'vs/base/common/resources'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; +import { ElectronEnvironmentService, IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; -class CodeRendererMain extends Disposable { +class DesktopMain extends Disposable { private readonly environmentService: WorkbenchEnvironmentService; - constructor(configuration: IWindowConfiguration) { + constructor(private configuration: IWindowConfiguration) { super(); - this.environmentService = new WorkbenchEnvironmentService(configuration, configuration.execPath); + + this.environmentService = new WorkbenchEnvironmentService(configuration, configuration.execPath, configuration.windowId); this.init(); } @@ -113,18 +114,15 @@ class CodeRendererMain extends Disposable { async open(): Promise { const services = await this.initServices(); + await domContentLoaded(); mark('willStartWorkbench'); // Create Workbench const workbench = new Workbench(document.body, services.serviceCollection, services.logService); - // Layout - this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench))); - - // Workbench Lifecycle - this._register(workbench.onShutdown(() => this.dispose())); - this._register(workbench.onWillShutdown(event => event.join(services.storageService.close()))); + // Listeners + this.registerListeners(workbench, services.storageService); // Startup const instantiationService = workbench.startup(); @@ -134,18 +132,23 @@ class CodeRendererMain extends Disposable { // Driver if (this.environmentService.configuration.driver) { - instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor))); - } - - // Config Exporter - if (this.environmentService.configuration['export-default-configuration']) { - instantiationService.createInstance(DefaultConfigurationExportHelper); + instantiationService.invokeFunction(async accessor => this._register(await registerWindowDriver(accessor, this.configuration.windowId))); } // Logging services.logService.trace('workbench configuration', JSON.stringify(this.environmentService.configuration)); } + private registerListeners(workbench: Workbench, storageService: NativeStorageService): void { + + // Layout + this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true, workbench))); + + // Workbench Lifecycle + this._register(workbench.onShutdown(() => this.dispose())); + this._register(workbench.onWillShutdown(event => event.join(storageService.close()))); + } + private onWindowResize(e: Event, retry: boolean, workbench: Workbench): void { if (e.target === window) { if (window.document && window.document.body && window.document.body.clientWidth === 0) { @@ -164,7 +167,7 @@ class CodeRendererMain extends Disposable { } } - private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: StorageService }> { + private async initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: NativeStorageService }> { const serviceCollection = new ServiceCollection(); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -173,11 +176,15 @@ class CodeRendererMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Main Process - const mainProcessService = this._register(new MainProcessService(this.environmentService.configuration.windowId)); + const mainProcessService = this._register(new MainProcessService(this.configuration.windowId)); serviceCollection.set(IMainProcessService, mainProcessService); // Environment serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService); + serviceCollection.set(IElectronEnvironmentService, new ElectronEnvironmentService( + this.configuration.windowId, + this.environmentService.sharedIPCHandle + )); // Product serviceCollection.set(IProductService, { _serviceBrand: undefined, ...product }); @@ -328,9 +335,9 @@ class CodeRendererMain extends Disposable { } } - private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise { + private async createStorageService(payload: IWorkspaceInitializationPayload, logService: ILogService, mainProcessService: IMainProcessService): Promise { const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')); - const storageService = new StorageService(globalStorageDatabase, logService, this.environmentService); + const storageService = new NativeStorageService(globalStorageDatabase, logService, this.environmentService); try { await storageService.initialize(payload); @@ -359,7 +366,7 @@ class CodeRendererMain extends Disposable { else { loggers.push( new ConsoleLogService(this.environmentService.configuration.logLevel), - new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel) + new SpdLogService(`renderer${this.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel) ); } @@ -368,7 +375,7 @@ class CodeRendererMain extends Disposable { } export function main(configuration: IWindowConfiguration): Promise { - const renderer = new CodeRendererMain(configuration); + const renderer = new DesktopMain(configuration); return renderer.open(); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 0b2976c4f8d1..a2e04b4fb575 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -14,7 +14,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { toResource, IUntitledResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -50,7 +50,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IUpdateService } from 'vs/platform/update/common/update'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IPreferencesService } from '../services/preferences/common/preferences'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IOpenerService, OpenOptions } from 'vs/platform/opener/common/opener'; @@ -60,6 +59,9 @@ import { posix, dirname } from 'vs/base/common/path'; import { getBaseLabel } from 'vs/base/common/labels'; import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -89,7 +91,6 @@ export class ElectronWindow extends Disposable { constructor( @IEditorService private readonly editorService: EditorServiceImpl, - @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITitleService private readonly titleService: ITitleService, @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, @@ -111,7 +112,8 @@ export class ElectronWindow extends Disposable { @IOpenerService private readonly openerService: IOpenerService, @IElectronService private readonly electronService: IElectronService, @ITunnelService private readonly tunnelService: ITunnelService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService ) { super(); @@ -389,7 +391,7 @@ export class ElectronWindow extends Disposable { this.setupOpenHandlers(); // Emit event when vscode is ready - this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.windowService.windowId)); + this.lifecycleService.when(LifecyclePhase.Ready).then(() => ipc.send('vscode:workbenchReady', this.electronEnvironmentService.windowId)); // Integrity warning this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure })); @@ -679,8 +681,7 @@ export class ElectronWindow extends Disposable { class NativeMenubarControl extends MenubarControl { constructor( @IMenuService menuService: IMenuService, - @IWindowService windowService: IWindowService, - @IWindowsService windowsService: IWindowsService, + @IWorkspacesHistoryService workspacesHistoryService: IWorkspacesHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, @@ -689,14 +690,15 @@ class NativeMenubarControl extends MenubarControl { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @IPreferencesService preferencesService: IPreferencesService, - @IEnvironmentService environmentService: IEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IAccessibilityService accessibilityService: IAccessibilityService, - @IMenubarService private readonly menubarService: IMenubarService + @IMenubarService private readonly menubarService: IMenubarService, + @IHostService hostService: IHostService, + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService ) { super( menuService, - windowService, - windowsService, + workspacesHistoryService, contextKeyService, keybindingService, configurationService, @@ -706,7 +708,9 @@ class NativeMenubarControl extends MenubarControl { notificationService, preferencesService, environmentService, - accessibilityService); + accessibilityService, + hostService + ); if (isMacintosh) { this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); @@ -720,11 +724,11 @@ class NativeMenubarControl extends MenubarControl { } } - this.windowService.getRecentlyOpened().then((recentlyOpened) => { - this.recentlyOpened = recentlyOpened; + (async () => { + this.recentlyOpened = await this.workspacesHistoryService.getRecentlyOpened(); this.doUpdateMenubar(true); - }); + })(); this.registerListeners(); } @@ -734,7 +738,7 @@ class NativeMenubarControl extends MenubarControl { // Send menus to main process to be rendered by Electron const menubarData = { menus: {}, keybindings: {} }; if (this.getMenubarMenus(menubarData)) { - this.menubarService.updateMenubar(this.windowService.windowId, menubarData); + this.menubarService.updateMenubar(this.electronEnvironmentService.windowId, menubarData); } } diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index c8d928dd8899..294945d5d3ca 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -49,7 +49,7 @@ const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(u class TestBackupEnvironmentService extends WorkbenchEnvironmentService { constructor(backupPath: string) { - super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath); + super({ ...parseArgs(process.argv, OPTIONS), ...{ backupPath, 'user-data-dir': userdataDir } } as IWindowConfiguration, process.execPath, 0); } } diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 805b1ec8717c..3c06a6815252 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -15,6 +15,7 @@ export const machineSettingsSchemaId = 'vscode://schemas/settings/machine'; export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; export const folderSettingsSchemaId = 'vscode://schemas/settings/folder'; export const launchSchemaId = 'vscode://schemas/launch'; +export const tasksSchemaId = 'vscode://schemas/tasks'; export const LOCAL_MACHINE_SCOPES = [ConfigurationScope.APPLICATION, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE]; export const REMOTE_MACHINE_SCOPES = [ConfigurationScope.MACHINE, ConfigurationScope.WINDOW, ConfigurationScope.RESOURCE, ConfigurationScope.MACHINE_OVERRIDABLE]; diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 3dd2a8b2c139..c69236a91d96 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -46,7 +46,7 @@ import { FileUserDataProvider } from 'vs/workbench/services/userData/common/file class TestEnvironmentService extends WorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); + super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index a0e4c658a0cc..8aa22e6f8871 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -51,7 +51,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ class TestEnvironmentService extends WorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); + super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 7701bc5292df..da0a7a069282 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -645,6 +645,6 @@ class MockInputsConfigurationService extends TestConfigurationService { class MockWorkbenchEnvironmentService extends WorkbenchEnvironmentService { constructor(env: platform.IProcessEnvironment) { - super({ userEnv: env } as IWindowConfiguration, process.execPath); + super({ userEnv: env } as IWindowConfiguration, process.execPath, 0); } } diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts index 8303a976fa0b..795e61ab289b 100644 --- a/src/vs/workbench/services/credentials/browser/credentialsService.ts +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; diff --git a/src/vs/platform/credentials/common/credentials.ts b/src/vs/workbench/services/credentials/common/credentials.ts similarity index 100% rename from src/vs/platform/credentials/common/credentials.ts rename to src/vs/workbench/services/credentials/common/credentials.ts diff --git a/src/vs/workbench/services/credentials/node/credentialsService.ts b/src/vs/workbench/services/credentials/node/credentialsService.ts index d20bad0457d7..d6c20bdf9a2d 100644 --- a/src/vs/workbench/services/credentials/node/credentialsService.ts +++ b/src/vs/workbench/services/credentials/node/credentialsService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; import { IdleValue } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index 5d797e91fdce..c5fd3b8b1a48 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -28,7 +28,7 @@ export interface IDecoration { export interface IDecorationsProvider { readonly label: string; - readonly onDidChange: Event; + readonly onDidChange: Event; provideDecorations(uri: URI, token: CancellationToken): IDecorationData | Promise | undefined; } diff --git a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts index 9f601f9e1882..9890b41a6cb9 100644 --- a/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts +++ b/src/vs/workbench/services/decorations/test/browser/decorationsService.test.ts @@ -29,7 +29,7 @@ suite('DecorationsService', function () { service.registerDecorationsProvider(new class implements IDecorationsProvider { readonly label: string = 'Test'; - readonly onDidChange: Event = Event.None; + readonly onDidChange: Event = Event.None; provideDecorations(uri: URI) { callCounter += 1; return new Promise(resolve => { @@ -62,7 +62,7 @@ suite('DecorationsService', function () { service.registerDecorationsProvider(new class implements IDecorationsProvider { readonly label: string = 'Test'; - readonly onDidChange: Event = Event.None; + readonly onDidChange: Event = Event.None; provideDecorations(uri: URI) { callCounter += 1; return { color: 'someBlue', tooltip: 'Z' }; @@ -80,7 +80,7 @@ suite('DecorationsService', function () { let reg = service.registerDecorationsProvider(new class implements IDecorationsProvider { readonly label: string = 'Test'; - readonly onDidChange: Event = Event.None; + readonly onDidChange: Event = Event.None; provideDecorations(uri: URI) { callCounter += 1; return { color: 'someBlue', tooltip: 'J' }; @@ -155,7 +155,7 @@ suite('DecorationsService', function () { let provider = new class implements IDecorationsProvider { _onDidChange = new Emitter(); - onDidChange: Event = this._onDidChange.event; + onDidChange: Event = this._onDidChange.event; label: string = 'foo'; diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index bf265e130ade..5a712c824b2a 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IWindowService, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; +import { IWindowOpenable } from 'vs/platform/windows/common/windows'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -18,15 +18,15 @@ import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { isWeb } from 'vs/base/common/platform'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; -export class AbstractFileDialogService { +export abstract class AbstractFileDialogService { _serviceBrand: undefined; constructor( - @IWindowService protected readonly windowService: IWindowService, + @IHostService protected readonly hostService: IHostService, @IWorkspaceContextService protected readonly contextService: IWorkspaceContextService, @IHistoryService protected readonly historyService: IHistoryService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @@ -78,15 +78,7 @@ export class AbstractFileDialogService { return this.defaultFilePath(schemeFilter); } - protected addFileSchemaIfNeeded(schema: string): string[] { - // Include File schema unless the schema is web - // Don't allow untitled schema through. - if (isWeb) { - return schema === Schemas.untitled ? [Schemas.file] : [schema]; - } else { - return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); - } - } + protected abstract addFileSchemaIfNeeded(schema: string): string[]; protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); @@ -97,9 +89,9 @@ export class AbstractFileDialogService { if (uri) { const stat = await this.fileService.resolve(uri); - const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; + const toOpen: IWindowOpenable = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; if (stat.isDirectory || options.forceNewWindow || preferNewWindow) { - return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openInWindow([toOpen], { forceNewWindow: options.forceNewWindow }); } else { return this.openerService.open(uri); } @@ -113,7 +105,7 @@ export class AbstractFileDialogService { const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { if (options.forceNewWindow || preferNewWindow) { - return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openInWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); } else { return this.openerService.open(uri); } @@ -126,7 +118,7 @@ export class AbstractFileDialogService { const uri = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); if (uri) { - return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openInWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); } } @@ -137,7 +129,7 @@ export class AbstractFileDialogService { const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); if (uri) { - return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + return this.hostService.openInWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); } } @@ -184,7 +176,7 @@ export class AbstractFileDialogService { return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME; } - protected getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { + protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); } } diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 72366bcf712c..90af2d44666c 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -7,6 +7,7 @@ import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialo import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; +import { Schemas } from 'vs/base/common/network'; export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { @@ -64,6 +65,10 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const schema = this.getFileSystemSchema(options); return this.showOpenDialogSimplified(schema, options); } + + protected addFileSchemaIfNeeded(schema: string): string[] { + return schema === Schemas.untitled ? [Schemas.file] : [schema]; + } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 178c90d279b0..0f74527243db 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -210,7 +210,7 @@ export class SimpleFileDialog { return resources.toLocalResource(URI.from({ scheme: this.scheme, path }), this.scheme === Schemas.file ? undefined : this.remoteAuthority); } - private getScheme(available: string[] | undefined, defaultUri: URI | undefined): string { + private getScheme(available: readonly string[] | undefined, defaultUri: URI | undefined): string { if (available) { if (defaultUri && (available.indexOf(defaultUri.scheme) >= 0)) { return defaultUri.scheme; @@ -321,7 +321,7 @@ export class SimpleFileDialog { isAcceptHandled = true; isResolving++; if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) { - this.options.availableFileSystems.shift(); + this.options.availableFileSystems = this.options.availableFileSystems.slice(1); } this.filePickBox.hide(); if (isSave) { diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index d5df5579d18c..53df7ddf1b2b 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -3,7 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWindowService, OpenDialogOptions, SaveDialogOptions, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { SaveDialogOptions, OpenDialogOptions } from 'electron'; +import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -23,7 +25,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil _serviceBrand: undefined; constructor( - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IHistoryService historyService: IHistoryService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @@ -32,7 +34,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IFileService fileService: IFileService, @IOpenerService openerService: IOpenerService, @IElectronService private readonly electronService: IElectronService - ) { super(windowService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } + ) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { return { @@ -172,6 +174,12 @@ export class FileDialogService extends AbstractFileDialogService implements IFil const result = await this.electronService.showOpenDialog(newOptions); return result && Array.isArray(result.filePaths) && result.filePaths.length > 0 ? result.filePaths.map(URI.file) : undefined; } + + protected addFileSchemaIfNeeded(schema: string): string[] { + // Include File schema unless the schema is web + // Don't allow untitled schema through. + return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts b/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts new file mode 100644 index 000000000000..4da6ebecff0d --- /dev/null +++ b/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IElectronEnvironmentService = createDecorator('electronEnvironmentService'); + +export interface IElectronEnvironmentService { + + _serviceBrand: undefined; + + readonly windowId: number; + + readonly sharedIPCHandle: string; +} + +export class ElectronEnvironmentService implements IElectronEnvironmentService { + + _serviceBrand: undefined; + + constructor( + public readonly windowId: number, + public readonly sharedIPCHandle: string + ) { } +} diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/workbench/services/electron/electron-browser/electronService.ts similarity index 53% rename from src/vs/platform/electron/electron-browser/electronService.ts rename to src/vs/workbench/services/electron/electron-browser/electronService.ts index cc14ba22aaa0..ceb8be782cbf 100644 --- a/src/vs/platform/electron/electron-browser/electronService.ts +++ b/src/vs/workbench/services/electron/electron-browser/electronService.ts @@ -5,8 +5,9 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { createChannelSender } from 'vs/base/parts/ipc/node/ipcChannelCreator'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class ElectronService { @@ -14,8 +15,10 @@ export class ElectronService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IWindowService windowService: IWindowService + @IElectronEnvironmentService electronEnvironmentService: IElectronEnvironmentService ) { - return createSimpleChannelProxy(mainProcessService.getChannel('electron'), windowService.windowId); + return createChannelSender(mainProcessService.getChannel('electron'), { context: electronEnvironmentService.windowId }); } } + +registerSingleton(IElectronService, ElectronService, true); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 98b3dff758ea..b1d7e9405b2d 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -85,6 +85,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); this.settingsSyncPreviewResource = joinPath(this.userRoamingDataHome, '.settings.json'); + this.userDataSyncLogResource = joinPath(options.logsPath, 'userDataSync.log'); this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); @@ -142,10 +143,11 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment appSettingsHome: URI; userRoamingDataHome: URI; settingsResource: URI; - settingsSyncPreviewResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; localeResource: URI; + settingsSyncPreviewResource: URI; + userDataSyncLogResource: URI; machineSettingsHome: URI; machineSettingsResource: URI; globalStorageHome: string; @@ -188,7 +190,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment } get webviewResourceRoot(): string { - return `${this.webviewExternalEndpoint}/vscode-resource{{resource}}`; + return `${this.webviewExternalEndpoint}/vscode-resource/{{resource}}`; } get webviewCspSource(): string { diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index 0aa16f30544c..224ccfdf70ab 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -23,12 +23,13 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I return baseEndpoint.replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); } - readonly webviewResourceRoot = 'vscode-resource:{{resource}}'; + readonly webviewResourceRoot = 'vscode-resource://{{resource}}'; readonly webviewCspSource = 'vscode-resource:'; constructor( readonly configuration: IWindowConfiguration, - execPath: string + execPath: string, + private readonly windowId: number ) { super(configuration, execPath); @@ -43,7 +44,7 @@ export class WorkbenchEnvironmentService extends EnvironmentService implements I get userRoamingDataHome(): URI { return this.appSettingsHome.with({ scheme: Schemas.userData }); } @memoize - get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.configuration.windowId}.log`)); } + get logFile(): URI { return URI.file(join(this.logsPath, `renderer${this.windowId}.log`)); } get logExtensionHostCommunication(): boolean { return !!this.args.logExtensionHostCommunication; } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 31b56a759fc7..ef0b53d34cfe 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -26,8 +26,8 @@ export class ExtensionEnablementService extends Disposable implements IExtension _serviceBrand: undefined; - private readonly _onEnablementChanged = new Emitter(); - public readonly onEnablementChanged: Event = this._onEnablementChanged.event; + private readonly _onEnablementChanged = new Emitter(); + public readonly onEnablementChanged: Event = this._onEnablementChanged.event; private readonly storageManger: StorageManager; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index fbfdf43277e5..146a41139ede 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -44,7 +44,7 @@ export interface IExtensionEnablementService { /** * Event to listen on for extension enablement changes */ - onEnablementChanged: Event; + readonly onEnablementChanged: Event; /** * Returns the enablement state for the given extension diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 0b7112e80cff..325b417b6de8 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -25,7 +25,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; @@ -38,6 +37,7 @@ import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints' import { flatten } from 'vs/base/common/arrays'; import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; class DeltaExtensionsQueueItem { constructor( @@ -67,10 +67,10 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, - @IWindowService protected readonly _windowService: IWindowService, @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, @IElectronService private readonly _electronService: IElectronService, - @IHostService private readonly _hostService: IHostService + @IHostService private readonly _hostService: IHostService, + @IElectronEnvironmentService private readonly _electronEnvironmentService: IElectronEnvironmentService ) { super( instantiationService, @@ -93,7 +93,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._remoteExtensionsEnvironmentData = new Map(); - this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.windowId}`)); + this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._electronEnvironmentService.windowId}`)); this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner); this._deltaExtensionsQueue = []; diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 5f2983b1e14f..8230a23250c0 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -19,7 +19,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; import { IExpression } from 'vs/base/common/glob'; @@ -138,7 +138,7 @@ export class HistoryService extends Disposable implements IHistoryService { @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, - @IWindowService private readonly windowService: IWindowService, + @IWorkspacesHistoryService private readonly workspacesHistoryService: IWorkspacesHistoryService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @@ -781,7 +781,7 @@ export class HistoryService extends Disposable implements IHistoryService { const input = arg1 as IResourceInput; - this.windowService.removeFromRecentlyOpened([input.resource]); + this.workspacesHistoryService.removeFromRecentlyOpened([input.resource]); } private isFileOpened(resource: URI, group: IEditorGroup): boolean { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 710b9276bda4..2aa96fd162f8 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -3,21 +3,116 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IResourceEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWindowSettings, IWindowOpenable, IOpenInWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { pathsToEditors } from 'vs/workbench/common/editor'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { trackFocus } from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; -export class BrowserHostService implements IHostService { +export class BrowserHostService extends Disposable implements IHostService { _serviceBrand: undefined; - constructor(@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService) { } + //#region Events + + get onDidChangeFocus(): Event { return this._onDidChangeFocus; } + private _onDidChangeFocus: Event; + + //#endregion + + constructor( + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IFileService private readonly fileService: IFileService, + @ILabelService private readonly labelService: ILabelService + ) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Track Focus on Window + const focusTracker = this._register(trackFocus(window)); + this._onDidChangeFocus = Event.any( + Event.map(focusTracker.onDidFocus, () => this.hasFocus), + Event.map(focusTracker.onDidBlur, () => this.hasFocus) + ); + } //#region Window readonly windowCount = Promise.resolve(1); - async openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise { + async openInWindow(toOpen: IWindowOpenable[], options?: IOpenInWindowOptions): Promise { + // TODO@Ben delegate to embedder + const { openFolderInNewWindow } = this.shouldOpenNewWindow(options); + for (let i = 0; i < toOpen.length; i++) { + const openable = toOpen[i]; + openable.label = openable.label || this.getRecentLabel(openable); + + // Folder + if (isFolderToOpen(openable)) { + const newAddress = `${document.location.origin}${document.location.pathname}?folder=${openable.folderUri.path}`; + if (openFolderInNewWindow) { + window.open(newAddress); + } else { + window.location.href = newAddress; + } + } + + // Workspace + else if (isWorkspaceToOpen(openable)) { + const newAddress = `${document.location.origin}${document.location.pathname}?workspace=${openable.workspaceUri.path}`; + if (openFolderInNewWindow) { + window.open(newAddress); + } else { + window.location.href = newAddress; + } + } + + // File + else if (isFileToOpen(openable)) { + const inputs: IResourceEditor[] = await pathsToEditors([openable], this.fileService); + this.editorService.openEditors(inputs); + } + } + } + + private getRecentLabel(openable: IWindowOpenable): string { + if (isFolderToOpen(openable)) { + return this.labelService.getWorkspaceLabel(openable.folderUri, { verbose: true }); + } + + if (isWorkspaceToOpen(openable)) { + return this.labelService.getWorkspaceLabel({ id: '', configPath: openable.workspaceUri }, { verbose: true }); + } + + return this.labelService.getUriLabel(openable.fileUri); + } + + private shouldOpenNewWindow(options: IOpenInWindowOptions = {}): { openFolderInNewWindow: boolean } { + const windowConfig = this.configurationService.getValue('window'); + const openFolderInNewWindowConfig = (windowConfig && windowConfig.openFoldersInNewWindow) || 'default' /* default */; + + let openFolderInNewWindow = !!options.forceNewWindow && !options.forceReuseWindow; + if (!options.forceNewWindow && !options.forceReuseWindow && (openFolderInNewWindowConfig === 'on' || openFolderInNewWindowConfig === 'off')) { + openFolderInNewWindow = (openFolderInNewWindowConfig === 'on'); + } + + return { openFolderInNewWindow }; + } + + async openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise { // TODO@Ben delegate to embedder const targetHref = `${document.location.origin}${document.location.pathname}?ew=true`; if (options && options.reuse) { @@ -61,6 +156,14 @@ export class BrowserHostService implements IHostService { } } + get hasFocus(): boolean { + return document.hasFocus(); + } + + async focus(): Promise { + window.focus(); + } + //#endregion async restart(): Promise { diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index e99f2df0b136..c7bfc57b3967 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -3,7 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWindowOpenable, IOpenInWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; export const IHostService = createDecorator('hostService'); @@ -11,6 +13,15 @@ export interface IHostService { _serviceBrand: undefined; + //#region Events + + /** + * Emitted when the window focus changes. + */ + readonly onDidChangeFocus: Event; + + //#endregion + //#region Window /** @@ -18,17 +29,32 @@ export interface IHostService { */ readonly windowCount: Promise; + /** + * Opens the provided array of openables in a window with the provided options. + */ + openInWindow(toOpen: IWindowOpenable[], options?: IOpenInWindowOptions): Promise; + /** * Opens an empty window. The optional parameter allows to define if * a new window should open or the existing one change to an empty. */ - openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise; + openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise; /** * Switch between fullscreen and normal window. */ toggleFullScreen(): Promise; + /** + * Find out if the window has focus or not. + */ + readonly hasFocus: boolean; + + /** + * Attempt to bring the window to the foreground and focus it. + */ + focus(): Promise; + //#endregion //#region Lifecycle diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index cdcb51a84bda..cfe46e62d5c3 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -3,21 +3,77 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IWindowOpenable, IOpenInWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; -export class DesktopHostService implements IHostService { +export class DesktopHostService extends Disposable implements IHostService { _serviceBrand: undefined; - constructor(@IElectronService private readonly electronService: IElectronService) { } + //#region Events + + get onDidChangeFocus(): Event { return this._onDidChangeFocus; } + private _onDidChangeFocus: Event = Event.any( + Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.electronEnvironmentService.windowId), _ => true), + Event.map(Event.filter(this.electronService.onWindowBlur, id => id === this.electronEnvironmentService.windowId), _ => false) + ); + + //#endregion + + constructor( + @IElectronService private readonly electronService: IElectronService, + @ILabelService private readonly labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService + ) { + super(); + + // Resolve initial window focus state + this._hasFocus = document.hasFocus(); + electronService.isWindowFocused().then(focused => this._hasFocus = focused); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.onDidChangeFocus(focus => this._hasFocus = focus)); + } //#region Window - get windowCount() { return this.electronService.windowCount(); } + private _hasFocus: boolean; + get hasFocus(): boolean { return this._hasFocus; } + + get windowCount(): Promise { return this.electronService.getWindowCount(); } - openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise { + openInWindow(toOpen: IWindowOpenable[], options?: IOpenInWindowOptions): Promise { + if (!!this.environmentService.configuration.remoteAuthority) { + toOpen.forEach(openable => openable.label = openable.label || this.getRecentLabel(openable)); + } + + return this.electronService.openInWindow(toOpen, options); + } + + private getRecentLabel(openable: IWindowOpenable): string { + if (isFolderToOpen(openable)) { + return this.labelService.getWorkspaceLabel(openable.folderUri, { verbose: true }); + } + + if (isWorkspaceToOpen(openable)) { + return this.labelService.getWorkspaceLabel({ id: '', configPath: openable.workspaceUri }, { verbose: true }); + } + + return this.labelService.getUriLabel(openable.fileUri); + } + + openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise { return this.electronService.openEmptyWindow(options); } @@ -25,6 +81,10 @@ export class DesktopHostService implements IHostService { return this.electronService.toggleFullScreen(); } + focus(): Promise { + return this.electronService.focusWindow(); + } + //#endregion restart(): Promise { diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 0ead211296e1..8b1599740b24 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -29,7 +29,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExtensionMessageCollector, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IUserKeybindingItem, KeybindingIO, OutputBuilder } from 'vs/workbench/services/keybinding/common/keybindingIO'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -154,7 +154,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { @INotificationService notificationService: INotificationService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationService configurationService: IConfigurationService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, @IKeymapService private readonly keymapService: IKeymapService @@ -291,7 +291,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { // it is possible that the document has lost focus, but the // window is still focused, e.g. when a element // has focus - return this.windowService.hasFocus; + return this.hostService.hasFocus; } private _resolveKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 5a2db1fbddfa..279a2e8634a2 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -53,7 +53,7 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; class TestEnvironmentService extends WorkbenchEnvironmentService { constructor(private _appSettingsHome: URI) { - super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); + super(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); } get appSettingsHome() { return this._appSettingsHome; } diff --git a/src/vs/platform/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts similarity index 89% rename from src/vs/platform/lifecycle/browser/lifecycleService.ts rename to src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 60f7dce6ffec..7cfebd30f650 100644 --- a/src/vs/platform/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -3,10 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; import { localize } from 'vs/nls'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class BrowserLifecycleService extends AbstractLifecycleService { @@ -61,3 +62,5 @@ export class BrowserLifecycleService extends AbstractLifecycleService { return null; } } + +registerSingleton(ILifecycleService, BrowserLifecycleService); diff --git a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts similarity index 80% rename from src/vs/platform/lifecycle/electron-browser/lifecycleService.ts rename to src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index 2ff4981aed86..a90545255246 100644 --- a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { ShutdownReason, StartupKind, handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; +import { ShutdownReason, StartupKind, handleVetos, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { ipcRenderer as ipc } from 'electron'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { onUnexpectedError } from 'vs/base/common/errors'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export class LifecycleService extends AbstractLifecycleService { +export class NativeLifecycleService extends AbstractLifecycleService { private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason'; @@ -23,7 +24,7 @@ export class LifecycleService extends AbstractLifecycleService { constructor( @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService, + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, @IStorageService readonly storageService: IStorageService, @ILogService readonly logService: ILogService ) { @@ -35,8 +36,8 @@ export class LifecycleService extends AbstractLifecycleService { } private resolveStartupKind(): StartupKind { - const lastShutdownReason = this.storageService.getNumber(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); - this.storageService.remove(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); + const lastShutdownReason = this.storageService.getNumber(NativeLifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); + this.storageService.remove(NativeLifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); let startupKind: StartupKind; if (lastShutdownReason === ShutdownReason.RELOAD) { @@ -53,7 +54,7 @@ export class LifecycleService extends AbstractLifecycleService { } private registerListeners(): void { - const windowId = this.windowService.windowId; + const windowId = this.electronEnvironmentService.windowId; // Main side indicates that window is about to unload, check for vetos ipc.on('vscode:onBeforeUnload', (_event: unknown, reply: { okChannel: string, cancelChannel: string, reason: ShutdownReason }) => { @@ -91,7 +92,7 @@ export class LifecycleService extends AbstractLifecycleService { // Save shutdown reason to retrieve on next startup this.storageService.onWillSaveState(e => { if (e.reason === WillSaveStateReason.SHUTDOWN) { - this.storageService.store(LifecycleService.LAST_SHUTDOWN_REASON_KEY, this.shutdownReason, StorageScope.WORKSPACE); + this.storageService.store(NativeLifecycleService.LAST_SHUTDOWN_REASON_KEY, this.shutdownReason, StorageScope.WORKSPACE); } }); } @@ -132,3 +133,5 @@ export class LifecycleService extends AbstractLifecycleService { } } } + +registerSingleton(ILifecycleService, NativeLifecycleService); diff --git a/src/vs/workbench/services/log/common/keyValueLogProvider.ts b/src/vs/workbench/services/log/common/keyValueLogProvider.ts index a39d2a7119ea..cb24dd77f187 100644 --- a/src/vs/workbench/services/log/common/keyValueLogProvider.ts +++ b/src/vs/workbench/services/log/common/keyValueLogProvider.ts @@ -17,8 +17,8 @@ export abstract class KeyValueLogProvider extends Disposable implements IFileSys readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities: Event = Event.None; - private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + private readonly _onDidChangeFile = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; private readonly versions: Map = new Map(); diff --git a/src/vs/workbench/services/output/node/outputChannelModelService.ts b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts similarity index 96% rename from src/vs/workbench/services/output/node/outputChannelModelService.ts rename to src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts index e4749c8fe50d..b09c208b0d33 100644 --- a/src/vs/workbench/services/output/node/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/electron-browser/outputChannelModelService.ts @@ -21,6 +21,7 @@ import { toLocalISOString } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Emitter, Event } from 'vs/base/common/event'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel { @@ -203,6 +204,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService constructor( @IInstantiationService instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService, @IFileService private readonly fileService: IFileService ) { super(instantiationService); @@ -216,7 +218,7 @@ export class OutputChannelModelService extends AsbtractOutputChannelModelService private _outputDir: Promise | null = null; private get outputDir(): Promise { if (!this._outputDir) { - const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.environmentService.configuration.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); + const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.electronEnvironmentService.windowId}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); this._outputDir = this.fileService.createFolder(outputDir).then(() => outputDir); } return this._outputDir; diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 8b2d6e8ab04b..ff0e0eb81bdd 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -9,7 +9,7 @@ import { localize } from 'vs/nls'; import { IDisposable, dispose, DisposableStore, MutableDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IProgressService, IProgressOptions, IProgressStep, ProgressLocation, IProgress, Progress, IProgressCompositeOptions, IProgressNotificationOptions, IProgressRunner, IProgressIndicator } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { StatusbarAlignment, IStatusbarService } from 'vs/workbench/services/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts new file mode 100644 index 000000000000..9ff7f391006c --- /dev/null +++ b/src/vs/workbench/services/sharedProcess/electron-browser/sharedProcessService.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Client } from 'vs/base/parts/ipc/common/ipc.net'; +import { connect } from 'vs/base/parts/ipc/node/ipc.net'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class SharedProcessService implements ISharedProcessService { + + _serviceBrand: undefined; + + private withSharedProcessConnection: Promise>; + private sharedProcessMainChannel: IChannel; + + constructor( + @IMainProcessService mainProcessService: IMainProcessService, + @IElectronEnvironmentService environmentService: IElectronEnvironmentService + ) { + this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess'); + + this.withSharedProcessConnection = this.whenSharedProcessReady() + .then(() => connect(environmentService.sharedIPCHandle, `window:${environmentService.windowId}`)); + } + + whenSharedProcessReady(): Promise { + return this.sharedProcessMainChannel.call('whenSharedProcessReady'); + } + + getChannel(channelName: string): IChannel { + return getDelayedChannel(this.withSharedProcessConnection.then(connection => connection.getChannel(channelName))); + } + + registerChannel(channelName: string, channel: IServerChannel): void { + this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel)); + } + + toggleSharedProcessWindow(): Promise { + return this.sharedProcessMainChannel.call('toggleSharedProcessWindow'); + } +} + +registerSingleton(ISharedProcessService, SharedProcessService, true); diff --git a/src/vs/platform/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts similarity index 100% rename from src/vs/platform/statusbar/common/statusbar.ts rename to src/vs/workbench/services/statusbar/common/statusbar.ts diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 49781cbd4ebf..32b05d5570e8 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -38,7 +38,7 @@ export class TelemetryService extends Disposable implements ITelemetryService { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, productService.msftInternalDomains, environmentService.installSourcePath, environmentService.configuration.remoteAuthority, environmentService.options && environmentService.options.resolveCommonTelemetryProperties), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index ef871cc07a5e..2de521df6582 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -660,7 +660,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return result; } - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: readonly string[]): Promise { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); @@ -668,7 +668,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); } - private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { + private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: readonly string[]): ISaveDialogOptions { const options: ISaveDialogOptions = { defaultUri, title: nls.localize('saveAsTitle', "Save As"), diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 70cc9f32765c..bf40906b1aff 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -38,8 +38,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private readonly _onModelOrphanedChanged: Emitter = this._register(new Emitter()); readonly onModelOrphanedChanged: Event = this._onModelOrphanedChanged.event; - private _onModelsDirty!: Event; - get onModelsDirty(): Event { + private _onModelsDirty!: Event; + get onModelsDirty(): Event { if (!this._onModelsDirty) { this._onModelsDirty = this.debounce(this.onModelDirty); } @@ -47,8 +47,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsDirty; } - private _onModelsSaveError!: Event; - get onModelsSaveError(): Event { + private _onModelsSaveError!: Event; + get onModelsSaveError(): Event { if (!this._onModelsSaveError) { this._onModelsSaveError = this.debounce(this.onModelSaveError); } @@ -56,8 +56,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaveError; } - private _onModelsSaved!: Event; - get onModelsSaved(): Event { + private _onModelsSaved!: Event; + get onModelsSaved(): Event { if (!this._onModelsSaved) { this._onModelsSaved = this.debounce(this.onModelSaved); } @@ -65,8 +65,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaved; } - private _onModelsReverted!: Event; - get onModelsReverted(): Event { + private _onModelsReverted!: Event; + get onModelsReverted(): Event { if (!this._onModelsReverted) { this._onModelsReverted = this.debounce(this.onModelReverted); } @@ -95,7 +95,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.lifecycleService.onShutdown(this.dispose, this); } - private debounce(event: Event): Event { + private debounce(event: Event): Event { return Event.debounce(event, (prev: TextFileModelChangeEvent[], cur: TextFileModelChangeEvent) => { if (!prev) { prev = [cur]; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index a4e74da7bef5..2518c5f5e954 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -412,10 +412,10 @@ export interface ITextFileEditorModelManager { readonly onModelReverted: Event; readonly onModelOrphanedChanged: Event; - readonly onModelsDirty: Event; - readonly onModelsSaveError: Event; - readonly onModelsSaved: Event; - readonly onModelsReverted: Event; + readonly onModelsDirty: Event; + readonly onModelsSaveError: Event; + readonly onModelsSaved: Event; + readonly onModelsReverted: Event; get(resource: URI): ITextFileEditorModel | undefined; @@ -433,7 +433,7 @@ export interface ISaveOptions { overwriteEncoding?: boolean; skipSaveParticipants?: boolean; writeElevated?: boolean; - availableFileSystems?: string[]; + availableFileSystems?: readonly string[]; } export interface ILoadOptions { diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 2d22e06330cb..5b4a9a602866 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,10 +7,9 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService, TestHostService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestHostService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ConfirmResult } from 'vs/workbench/common/editor'; @@ -28,7 +27,6 @@ class ServiceAccessor { @ILifecycleService public lifecycleService: TestLifecycleService, @ITextFileService public textFileService: TestTextFileService, @IUntitledEditorService public untitledEditorService: IUntitledEditorService, - @IWindowsService public windowsService: TestWindowsService, @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, @IFileService public fileService: TestFileService, diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index 118707c389ca..650958115038 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -34,22 +34,22 @@ export interface IUntitledEditorService { /** * Events for when untitled editors content changes (e.g. any keystroke). */ - onDidChangeContent: Event; + readonly onDidChangeContent: Event; /** * Events for when untitled editors change (e.g. getting dirty, saved or reverted). */ - onDidChangeDirty: Event; + readonly onDidChangeDirty: Event; /** * Events for when untitled editor encodings change. */ - onDidChangeEncoding: Event; + readonly onDidChangeEncoding: Event; /** * Events for when untitled editors are disposed. */ - onDidDisposeModel: Event; + readonly onDidDisposeModel: Event; /** * Returns if an untitled resource with the given URI exists. diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index 9250a4f9d191..723d7be4da4f 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -11,7 +11,7 @@ import { URLService } from 'vs/platform/url/node/urlService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class RelayURLService extends URLService implements IURLHandler { @@ -20,7 +20,7 @@ export class RelayURLService extends URLService implements IURLHandler { constructor( @IMainProcessService mainProcessService: IMainProcessService, @IOpenerService openerService: IOpenerService, - @IWindowService private windowService: IWindowService + @IElectronEnvironmentService private electronEnvironmentService: IElectronEnvironmentService ) { super(); @@ -35,9 +35,9 @@ export class RelayURLService extends URLService implements IURLHandler { let query = uri.query; if (!query) { - query = `windowId=${encodeURIComponent(this.windowService.windowId)}`; + query = `windowId=${encodeURIComponent(this.electronEnvironmentService.windowId)}`; } else { - query += `&windowId=${encodeURIComponent(this.windowService.windowId)}`; + query += `&windowId=${encodeURIComponent(this.electronEnvironmentService.windowId)}`; } return uri.with({ query }); diff --git a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts index dafc8adbe6b7..6066808d4e08 100644 --- a/src/vs/workbench/services/userData/common/fileUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/fileUserDataProvider.ts @@ -17,8 +17,8 @@ export class FileUserDataProvider extends Disposable implements IFileSystemProvi readonly capabilities: FileSystemProviderCapabilities = this.fileSystemProvider.capabilities; readonly onDidChangeCapabilities: Event = Event.None; - private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + private readonly _onDidChangeFile = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; private readonly userDataHome: URI; @@ -103,7 +103,7 @@ export class FileUserDataProvider extends Disposable implements IFileSystemProvi return this.fileSystemProvider.delete(this.toFileSystemResource(resource), opts); } - private handleFileChanges(changes: IFileChange[]): void { + private handleFileChanges(changes: readonly IFileChange[]): void { const userDataChanges: IFileChange[] = []; for (const change of changes) { const userDataResource = this.toUserDataResource(change.resource); @@ -139,4 +139,4 @@ export class FileUserDataProvider extends Disposable implements IFileSystemProvi return null; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index 59609811118c..30c36fe4df36 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -205,8 +205,8 @@ export class InMemoryFileSystemProvider extends Disposable implements IFileSyste // --- manage file events - private readonly _onDidChangeFile: Emitter = this._register(new Emitter()); - readonly onDidChangeFile: Event = this._onDidChangeFile.event; + private readonly _onDidChangeFile = this._register(new Emitter()); + readonly onDidChangeFile: Event = this._onDidChangeFile.event; private _bufferedChanges: IFileChange[] = []; private _fireSoonHandle?: any; diff --git a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts index 38be7fe6e2be..d52f4ac8d3b8 100644 --- a/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts +++ b/src/vs/workbench/services/userData/test/electron-browser/fileUserDataProvider.test.ts @@ -277,7 +277,7 @@ suite('FileUserDataProvider', () => { class TestFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability { - constructor(readonly onDidChangeFile: Event) { } + constructor(readonly onDidChangeFile: Event) { } readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; @@ -309,7 +309,7 @@ suite('FileUserDataProvider - Watching', () => { let userDataResource: URI; const disposables = new DisposableStore(); - const fileEventEmitter: Emitter = new Emitter(); + const fileEventEmitter: Emitter = new Emitter(); disposables.add(fileEventEmitter); setup(() => { diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts index b9badb76c146..f589f4bff8d5 100644 --- a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -27,20 +27,21 @@ class SettingsMergeService implements ISettingsMergeService { @IModeService private readonly modeService: IModeService, ) { } - async merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + async merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { const local = parse(localContent); const remote = parse(remoteContent); const base = baseContent ? parse(baseContent) : null; + const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set()); - const localToRemote = this.compare(local, remote, ignoredSettings); + const localToRemote = this.compare(local, remote, ignored); if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { // No changes found between local and remote. return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; } const conflicts: Set = new Set(); - const baseToLocal = base ? this.compare(base, local, ignoredSettings) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; - const baseToRemote = base ? this.compare(base, remote, ignoredSettings) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToLocal = base ? this.compare(base, local, ignored) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = base ? this.compare(base, remote, ignored) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); // Removed settings in Local @@ -151,11 +152,12 @@ class SettingsMergeService implements ISettingsMergeService { return { mergeContent: settingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.size > 0 }; } - async computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary): Promise { + async computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise { const remote = parse(remoteContent); const remoteModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); + const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set()); for (const key of Object.keys(ignoredSettings)) { - if (ignoredSettings[key]) { + if (ignored.has(key)) { this.editSetting(remoteModel, key, undefined); this.editSetting(remoteModel, key, remote[key]); } @@ -180,9 +182,9 @@ class SettingsMergeService implements ISettingsMergeService { } } - private compare(from: IStringDictionary, to: IStringDictionary, ignored: IStringDictionary): { added: Set, removed: Set, updated: Set } { - const fromKeys = Object.keys(from).filter(key => !ignored[key]); - const toKeys = Object.keys(to).filter(key => !ignored[key]); + private compare(from: IStringDictionary, to: IStringDictionary, ignored: Set): { added: Set, removed: Set, updated: Set } { + const fromKeys = Object.keys(from).filter(key => !ignored.has(key)); + const toKeys = Object.keys(to).filter(key => !ignored.has(key)); const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); const updated: Set = new Set(); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 918fd7f6a65f..3da51020d4e3 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SyncStatus, SyncSource, IUserDataSyncService, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, SyncSource, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; @@ -42,8 +42,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('sync', [_continue]); } - getRemoteExtensions(): Promise { - return this.channel.call('getRemoteExtensions'); + stop(): void { + this.channel.call('stop'); } removeExtension(identifier: IExtensionIdentifier): Promise { diff --git a/src/vs/workbench/services/window/electron-browser/windowService.ts b/src/vs/workbench/services/window/electron-browser/windowService.ts deleted file mode 100644 index 1cb8651eb6ab..000000000000 --- a/src/vs/workbench/services/window/electron-browser/windowService.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IWindowService, IWindowsService, IOpenSettings, IURIToOpen, isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; -import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; -import { URI } from 'vs/base/common/uri'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class WindowService extends Disposable implements IWindowService { - - readonly onDidChangeFocus: Event; - readonly onDidChangeMaximize: Event; - - _serviceBrand: undefined; - - private _windowId: number; - private remoteAuthority: string | undefined; - - private _hasFocus: boolean; - get hasFocus(): boolean { return this._hasFocus; } - - constructor( - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IWindowsService private readonly windowsService: IWindowsService, - @ILabelService private readonly labelService: ILabelService - ) { - super(); - - this._windowId = environmentService.configuration.windowId; - this.remoteAuthority = environmentService.configuration.remoteAuthority; - - const onThisWindowFocus = Event.map(Event.filter(windowsService.onWindowFocus, id => id === this._windowId), _ => true); - const onThisWindowBlur = Event.map(Event.filter(windowsService.onWindowBlur, id => id === this._windowId), _ => false); - const onThisWindowMaximize = Event.map(Event.filter(windowsService.onWindowMaximize, id => id === this._windowId), _ => true); - const onThisWindowUnmaximize = Event.map(Event.filter(windowsService.onWindowUnmaximize, id => id === this._windowId), _ => false); - this.onDidChangeFocus = Event.any(onThisWindowFocus, onThisWindowBlur); - this.onDidChangeMaximize = Event.any(onThisWindowMaximize, onThisWindowUnmaximize); - - this._hasFocus = document.hasFocus(); - this.isFocused().then(focused => this._hasFocus = focused); - this._register(this.onDidChangeFocus(focus => this._hasFocus = focus)); - } - - get windowId(): number { - return this._windowId; - } - - openWindow(uris: IURIToOpen[], options: IOpenSettings = {}): Promise { - if (!!this.remoteAuthority) { - uris.forEach(u => u.label = u.label || this.getRecentLabel(u)); - } - - return this.windowsService.openWindow(this.windowId, uris, options); - } - - getRecentlyOpened(): Promise { - return this.windowsService.getRecentlyOpened(this.windowId); - } - - addRecentlyOpened(recents: IRecent[]): Promise { - return this.windowsService.addRecentlyOpened(recents); - } - - removeFromRecentlyOpened(paths: URI[]): Promise { - return this.windowsService.removeFromRecentlyOpened(paths); - } - - focusWindow(): Promise { - return this.windowsService.focusWindow(this.windowId); - } - - isFocused(): Promise { - return this.windowsService.isFocused(this.windowId); - } - - private getRecentLabel(u: IURIToOpen): string { - if (isFolderToOpen(u)) { - return this.labelService.getWorkspaceLabel(u.folderUri, { verbose: true }); - } else if (isWorkspaceToOpen(u)) { - return this.labelService.getWorkspaceLabel({ id: '', configPath: u.workspaceUri }, { verbose: true }); - } else { - return this.labelService.getUriLabel(u.fileUri); - } - } -} - -registerSingleton(IWindowService, WindowService); diff --git a/src/vs/workbench/services/workspace/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspace/browser/abstractWorkspaceEditingService.ts new file mode 100644 index 000000000000..083f968fd821 --- /dev/null +++ b/src/vs/workbench/services/workspace/browser/abstractWorkspaceEditingService.ts @@ -0,0 +1,347 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IBackupFileService, toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; +import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { distinct } from 'vs/base/common/arrays'; +import { isEqual, getComparisonKey } from 'vs/base/common/resources'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; + +export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditingService { + + _serviceBrand: undefined; + + constructor( + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, + @IWorkspaceContextService private readonly contextService: WorkspaceService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IStorageService private readonly storageService: IStorageService, + @IExtensionService private readonly extensionService: IExtensionService, + @IBackupFileService private readonly backupFileService: IBackupFileService, + @INotificationService private readonly notificationService: INotificationService, + @ICommandService private readonly commandService: ICommandService, + @IFileService private readonly fileService: IFileService, + @ITextFileService private readonly textFileService: ITextFileService, + @IWorkspacesService protected readonly workspacesService: IWorkspacesService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IDialogService protected readonly dialogService: IDialogService, + @IHostService protected readonly hostService: IHostService + ) { } + + pickNewWorkspacePath(): Promise { + return this.fileDialogService.showSaveDialog({ + saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")), + title: nls.localize('saveWorkspace', "Save Workspace"), + filters: WORKSPACE_FILTER, + defaultUri: this.fileDialogService.defaultWorkspacePath() + }); + } + + updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { + const folders = this.contextService.getWorkspace().folders; + + let foldersToDelete: URI[] = []; + if (typeof deleteCount === 'number') { + foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri); + } + + const wantsToDelete = foldersToDelete.length > 0; + const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0; + + if (!wantsToAdd && !wantsToDelete) { + return Promise.resolve(); // return early if there is nothing to do + } + + // Add Folders + if (wantsToAdd && !wantsToDelete && Array.isArray(foldersToAdd)) { + return this.doAddFolders(foldersToAdd, index, donotNotifyError); + } + + // Delete Folders + if (wantsToDelete && !wantsToAdd) { + return this.removeFolders(foldersToDelete); + } + + // Add & Delete Folders + else { + + // if we are in single-folder state and the folder is replaced with + // other folders, we handle this specially and just enter workspace + // mode with the folders that are being added. + if (this.includesSingleFolderWorkspace(foldersToDelete)) { + return this.createAndEnterWorkspace(foldersToAdd!); + } + + // if we are not in workspace-state, we just add the folders + if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) { + return this.doAddFolders(foldersToAdd!, index, donotNotifyError); + } + + // finally, update folders within the workspace + return this.doUpdateFolders(foldersToAdd!, foldersToDelete, index, donotNotifyError); + } + } + + private async doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): Promise { + try { + await this.contextService.updateFolders(foldersToAdd, foldersToDelete, index); + } catch (error) { + if (donotNotifyError) { + throw error; + } + + this.handleWorkspaceConfigurationEditingError(error); + } + } + + addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): Promise { + return this.doAddFolders(foldersToAdd, undefined, donotNotifyError); + } + + private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { + const state = this.contextService.getWorkbenchState(); + if (this.environmentService.configuration.remoteAuthority) { + + // Do not allow workspace folders with scheme different than the current remote scheme + const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); + if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { + return Promise.reject(new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace."))); + } + } + + // If we are in no-workspace or single-folder workspace, adding folders has to + // enter a workspace. + if (state !== WorkbenchState.WORKSPACE) { + let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); + newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd); + newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri)); + + if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) { + return; // return if the operation is a no-op for the current state + } + + return this.createAndEnterWorkspace(newWorkspaceFolders); + } + + // Delegate addition of folders to workspace service otherwise + try { + await this.contextService.addFolders(foldersToAdd, index); + } catch (error) { + if (donotNotifyError) { + throw error; + } + + this.handleWorkspaceConfigurationEditingError(error); + } + } + + async removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): Promise { + + // If we are in single-folder state and the opened folder is to be removed, + // we create an empty workspace and enter it. + if (this.includesSingleFolderWorkspace(foldersToRemove)) { + return this.createAndEnterWorkspace([]); + } + + // Delegate removal of folders to workspace service otherwise + try { + await this.contextService.removeFolders(foldersToRemove); + } catch (error) { + if (donotNotifyError) { + throw error; + } + + this.handleWorkspaceConfigurationEditingError(error); + } + } + + private includesSingleFolderWorkspace(folders: URI[]): boolean { + if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { + const workspaceFolder = this.contextService.getWorkspace().folders[0]; + return (folders.some(folder => isEqual(folder, workspaceFolder.uri))); + } + + return false; + } + + async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise { + if (path && !await this.isValidTargetWorkspacePath(path)) { + return; + } + + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const untitledWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); + if (path) { + await this.saveWorkspaceAs(untitledWorkspace, path); + } else { + path = untitledWorkspace.configPath; + } + + return this.enterWorkspace(path); + } + + async saveAndEnterWorkspace(path: URI): Promise { + if (!await this.isValidTargetWorkspacePath(path)) { + return; + } + + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); + if (!workspaceIdentifier) { + return; + } + + await this.saveWorkspaceAs(workspaceIdentifier, path); + + return this.enterWorkspace(path); + } + + async isValidTargetWorkspacePath(path: URI): Promise { + return true; // OK + } + + protected async saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPathURI: URI): Promise { + const configPathURI = workspace.configPath; + + // Return early if target is same as source + if (isEqual(configPathURI, targetConfigPathURI)) { + return; + } + + // Read the contents of the workspace file, update it to new location and save it. + const raw = await this.fileService.readFile(configPathURI); + const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, targetConfigPathURI); + await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); + } + + private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void { + switch (error.code) { + case JSONEditingErrorCode.ERROR_INVALID_FILE: + this.onInvalidWorkspaceConfigurationFileError(); + break; + case JSONEditingErrorCode.ERROR_FILE_DIRTY: + this.onWorkspaceConfigurationFileDirtyError(); + break; + default: + this.notificationService.error(error.message); + } + } + + private onInvalidWorkspaceConfigurationFileError(): void { + const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again."); + this.askToOpenWorkspaceConfigurationFile(message); + } + + private onWorkspaceConfigurationFileDirtyError(): void { + const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again."); + this.askToOpenWorkspaceConfigurationFile(message); + } + + private askToOpenWorkspaceConfigurationFile(message: string): void { + this.notificationService.prompt(Severity.Error, message, + [{ + label: nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"), + run: () => this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile') + }] + ); + } + + async enterWorkspace(path: URI): Promise { + if (!!this.environmentService.extensionTestsLocationURI) { + throw new Error('Entering a new workspace is not possible in tests.'); + } + + const workspace = await this.workspacesService.getWorkspaceIdentifier(path); + + // Settings migration (only if we come from a folder workspace) + if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { + await this.migrateWorkspaceSettings(workspace); + } + + const workspaceImpl = this.contextService as WorkspaceService; + await workspaceImpl.initialize(workspace); + + const result = await this.workspacesService.enterWorkspace(path); + if (result) { + + // Migrate storage to new workspace + await this.migrateStorage(result.workspace); + + // Reinitialize backup service + this.environmentService.configuration.backupPath = result.backupPath; + this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; + if (this.backupFileService instanceof BackupFileService) { + this.backupFileService.reinitialize(); + } + } + + // TODO@aeschli: workaround until restarting works + if (this.environmentService.configuration.remoteAuthority) { + this.hostService.reload(); + } + + // Restart the extension host: entering a workspace means a new location for + // storage and potentially a change in the workspace.rootPath property. + else { + this.extensionService.restartExtensionHost(); + } + } + + private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { + return this.storageService.migrate(toWorkspace); + } + + private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { + return this.doCopyWorkspaceSettings(toWorkspace, setting => setting.scope === ConfigurationScope.WINDOW); + } + + copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { + return this.doCopyWorkspaceSettings(toWorkspace); + } + + private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): Promise { + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + const targetWorkspaceConfiguration: any = {}; + for (const key of this.configurationService.keys().workspace) { + if (configurationProperties[key]) { + if (filter && !filter(configurationProperties[key])) { + continue; + } + + targetWorkspaceConfiguration[key] = this.configurationService.inspect(key).workspace; + } + } + + return this.jsonEditingService.write(toWorkspace.configPath, [{ key: 'settings', value: targetWorkspaceConfiguration }], true); + } + + protected getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { + const workspace = this.contextService.getWorkspace(); + if (workspace && workspace.configuration) { + return { id: workspace.id, configPath: workspace.configuration }; + } + + return undefined; + } +} diff --git a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts index ec5d3ccffebc..bda610980de6 100644 --- a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts @@ -3,463 +3,49 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { URI } from 'vs/base/common/uri'; -import * as nls from 'vs/nls'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IBackupFileService, toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; -import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { distinct } from 'vs/base/common/arrays'; -import { isLinux, isWindows, isMacintosh, isWeb } from 'vs/base/common/platform'; -import { isEqual, basename, isEqualOrParent, getComparisonKey } from 'vs/base/common/resources'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILabelService } from 'vs/platform/label/common/label'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspace/browser/abstractWorkspaceEditingService'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export class WorkspaceEditingService implements IWorkspaceEditingService { +export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingService { _serviceBrand: undefined; constructor( - @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, - @IWorkspaceContextService private readonly contextService: WorkspaceService, - @IWindowService private readonly windowService: IWindowService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService private readonly storageService: IStorageService, - @IExtensionService private readonly extensionService: IExtensionService, - @IBackupFileService private readonly backupFileService: IBackupFileService, - @INotificationService private readonly notificationService: INotificationService, - @ICommandService private readonly commandService: ICommandService, - @IFileService private readonly fileService: IFileService, - @ITextFileService private readonly textFileService: ITextFileService, - @IWindowsService private readonly windowsService: IWindowsService, - @IWorkspacesService private readonly workspacesService: IWorkspacesService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IDialogService private readonly dialogService: IDialogService, - @ILifecycleService readonly lifecycleService: ILifecycleService, - @ILabelService readonly labelService: ILabelService, - @IHostService private readonly hostService: IHostService + @IJSONEditingService jsonEditingService: IJSONEditingService, + @IWorkspaceContextService contextService: WorkspaceService, + @IConfigurationService configurationService: IConfigurationService, + @IStorageService storageService: IStorageService, + @IExtensionService extensionService: IExtensionService, + @IBackupFileService backupFileService: IBackupFileService, + @INotificationService notificationService: INotificationService, + @ICommandService commandService: ICommandService, + @IFileService fileService: IFileService, + @ITextFileService textFileService: ITextFileService, + @IWorkspacesService workspacesService: IWorkspacesService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IFileDialogService fileDialogService: IFileDialogService, + @IDialogService dialogService: IDialogService, + @IHostService hostService: IHostService ) { - this.registerListeners(); - } - - private registerListeners(): void { - this.lifecycleService.onBeforeShutdown(e => { - if (isWeb) { - return; // no support for untitled in web - } - - const saveOperation = this.saveUntitedBeforeShutdown(e.reason); - if (saveOperation) { - e.veto(saveOperation); - } - }); - } - - private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise { - if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { - return false; // only interested when window is closing or loading - } - - const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); - if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { - return false; // only care about untitled workspaces to ask for saving - } - - const windowCount = await this.hostService.windowCount; - - if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { - return false; // Windows/Linux: quits when last window is closed, so do not ask then - } - - enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL - } - - const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; - const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; - const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; - - const buttons: { label: string; result: ConfirmResult; }[] = []; - if (isWindows) { - buttons.push(save, dontSave, cancel); - } else if (isLinux) { - buttons.push(dontSave, cancel, save); - } else { - buttons.push(save, cancel, dontSave); - } - - const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); - const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); - const cancelId = buttons.indexOf(cancel); - - const { choice } = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }); - - switch (buttons[choice].result) { - - // Cancel: veto unload - case ConfirmResult.CANCEL: - return true; - - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); - return false; - - // Save: save workspace, but do not veto unload if path provided - case ConfirmResult.SAVE: { - const newWorkspacePath = await this.pickNewWorkspacePath(); - if (!newWorkspacePath) { - return true; // keep veto if no target was provided - } - - try { - await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); - - const newWorkspaceIdentifier = await this.workspacesService.getWorkspaceIdentifier(newWorkspacePath); - - const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); - this.windowService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); - - this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); - } catch (error) { - // ignore - } - - return false; - } - } - } - - pickNewWorkspacePath(): Promise { - return this.fileDialogService.showSaveDialog({ - saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")), - title: nls.localize('saveWorkspace', "Save Workspace"), - filters: WORKSPACE_FILTER, - defaultUri: this.fileDialogService.defaultWorkspacePath() - }); - } - - updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { - const folders = this.contextService.getWorkspace().folders; - - let foldersToDelete: URI[] = []; - if (typeof deleteCount === 'number') { - foldersToDelete = folders.slice(index, index + deleteCount).map(f => f.uri); - } - - const wantsToDelete = foldersToDelete.length > 0; - const wantsToAdd = Array.isArray(foldersToAdd) && foldersToAdd.length > 0; - - if (!wantsToAdd && !wantsToDelete) { - return Promise.resolve(); // return early if there is nothing to do - } - - // Add Folders - if (wantsToAdd && !wantsToDelete && Array.isArray(foldersToAdd)) { - return this.doAddFolders(foldersToAdd, index, donotNotifyError); - } - - // Delete Folders - if (wantsToDelete && !wantsToAdd) { - return this.removeFolders(foldersToDelete); - } - - // Add & Delete Folders - else { - - // if we are in single-folder state and the folder is replaced with - // other folders, we handle this specially and just enter workspace - // mode with the folders that are being added. - if (this.includesSingleFolderWorkspace(foldersToDelete)) { - return this.createAndEnterWorkspace(foldersToAdd!); - } - - // if we are not in workspace-state, we just add the folders - if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) { - return this.doAddFolders(foldersToAdd!, index, donotNotifyError); - } - - // finally, update folders within the workspace - return this.doUpdateFolders(foldersToAdd!, foldersToDelete, index, donotNotifyError); - } - } - - private async doUpdateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToDelete: URI[], index?: number, donotNotifyError: boolean = false): Promise { - try { - await this.contextService.updateFolders(foldersToAdd, foldersToDelete, index); - } catch (error) { - if (donotNotifyError) { - throw error; - } - - this.handleWorkspaceConfigurationEditingError(error); - } - } - - addFolders(foldersToAdd: IWorkspaceFolderCreationData[], donotNotifyError: boolean = false): Promise { - return this.doAddFolders(foldersToAdd, undefined, donotNotifyError); - } - - private async doAddFolders(foldersToAdd: IWorkspaceFolderCreationData[], index?: number, donotNotifyError: boolean = false): Promise { - const state = this.contextService.getWorkbenchState(); - if (this.environmentService.configuration.remoteAuthority) { - - // Do not allow workspace folders with scheme different than the current remote scheme - const schemas = this.contextService.getWorkspace().folders.map(f => f.uri.scheme); - if (schemas.length && foldersToAdd.some(f => schemas.indexOf(f.uri.scheme) === -1)) { - return Promise.reject(new Error(nls.localize('differentSchemeRoots', "Workspace folders from different providers are not allowed in the same workspace."))); - } - } - - // If we are in no-workspace or single-folder workspace, adding folders has to - // enter a workspace. - if (state !== WorkbenchState.WORKSPACE) { - let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); - newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd); - newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri)); - - if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) { - return; // return if the operation is a no-op for the current state - } - - return this.createAndEnterWorkspace(newWorkspaceFolders); - } - - // Delegate addition of folders to workspace service otherwise - try { - await this.contextService.addFolders(foldersToAdd, index); - } catch (error) { - if (donotNotifyError) { - throw error; - } - - this.handleWorkspaceConfigurationEditingError(error); - } - } - - async removeFolders(foldersToRemove: URI[], donotNotifyError: boolean = false): Promise { - - // If we are in single-folder state and the opened folder is to be removed, - // we create an empty workspace and enter it. - if (this.includesSingleFolderWorkspace(foldersToRemove)) { - return this.createAndEnterWorkspace([]); - } - - // Delegate removal of folders to workspace service otherwise - try { - await this.contextService.removeFolders(foldersToRemove); - } catch (error) { - if (donotNotifyError) { - throw error; - } - - this.handleWorkspaceConfigurationEditingError(error); - } - } - - private includesSingleFolderWorkspace(folders: URI[]): boolean { - if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { - const workspaceFolder = this.contextService.getWorkspace().folders[0]; - return (folders.some(folder => isEqual(folder, workspaceFolder.uri))); - } - - return false; - } - - async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise { - if (path && !await this.isValidTargetWorkspacePath(path)) { - return; - } - - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const untitledWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); - if (path) { - await this.saveWorkspaceAs(untitledWorkspace, path); - } else { - path = untitledWorkspace.configPath; - } - - return this.enterWorkspace(path); - } - - async saveAndEnterWorkspace(path: URI): Promise { - if (!await this.isValidTargetWorkspacePath(path)) { - return; - } - - const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); - if (!workspaceIdentifier) { - return; - } - - await this.saveWorkspaceAs(workspaceIdentifier, path); - - return this.enterWorkspace(path); - } - - async isValidTargetWorkspacePath(path: URI): Promise { - const windows = await this.windowsService.getWindows(); - - // Prevent overwriting a workspace that is currently opened in another window - if (windows.some(window => !!window.workspace && isEqual(window.workspace.configPath, path))) { - await this.dialogService.show( - Severity.Info, - nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)), - [nls.localize('ok', "OK")], - { - detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") - } - ); - - return false; - } - - return true; // OK - } - - private async saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPathURI: URI): Promise { - const configPathURI = workspace.configPath; - - // Return early if target is same as source - if (isEqual(configPathURI, targetConfigPathURI)) { - return; - } - - // Read the contents of the workspace file, update it to new location and save it. - const raw = await this.fileService.readFile(configPathURI); - const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, targetConfigPathURI); - await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); - } - - private handleWorkspaceConfigurationEditingError(error: JSONEditingError): void { - switch (error.code) { - case JSONEditingErrorCode.ERROR_INVALID_FILE: - this.onInvalidWorkspaceConfigurationFileError(); - break; - case JSONEditingErrorCode.ERROR_FILE_DIRTY: - this.onWorkspaceConfigurationFileDirtyError(); - break; - default: - this.notificationService.error(error.message); - } - } - - private onInvalidWorkspaceConfigurationFileError(): void { - const message = nls.localize('errorInvalidTaskConfiguration', "Unable to write into workspace configuration file. Please open the file to correct errors/warnings in it and try again."); - this.askToOpenWorkspaceConfigurationFile(message); - } - - private onWorkspaceConfigurationFileDirtyError(): void { - const message = nls.localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file is dirty. Please save it and try again."); - this.askToOpenWorkspaceConfigurationFile(message); - } - - private askToOpenWorkspaceConfigurationFile(message: string): void { - this.notificationService.prompt(Severity.Error, message, - [{ - label: nls.localize('openWorkspaceConfigurationFile', "Open Workspace Configuration"), - run: () => this.commandService.executeCommand('workbench.action.openWorkspaceConfigFile') - }] - ); - } - - async enterWorkspace(path: URI): Promise { - if (!!this.environmentService.extensionTestsLocationURI) { - throw new Error('Entering a new workspace is not possible in tests.'); - } - - const workspace = await this.workspacesService.getWorkspaceIdentifier(path); - - // Settings migration (only if we come from a folder workspace) - if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { - await this.migrateWorkspaceSettings(workspace); - } - - const workspaceImpl = this.contextService as WorkspaceService; - await workspaceImpl.initialize(workspace); - - const result = await this.workspacesService.enterWorkspace(path); - if (result) { - - // Migrate storage to new workspace - await this.migrateStorage(result.workspace); - - // Reinitialize backup service - this.environmentService.configuration.backupPath = result.backupPath; - this.environmentService.configuration.backupWorkspaceResource = result.backupPath ? toBackupWorkspaceResource(result.backupPath, this.environmentService) : undefined; - if (this.backupFileService instanceof BackupFileService) { - this.backupFileService.reinitialize(); - } - } - - // TODO@aeschli: workaround until restarting works - if (this.environmentService.configuration.remoteAuthority) { - this.hostService.reload(); - } - - // Restart the extension host: entering a workspace means a new location for - // storage and potentially a change in the workspace.rootPath property. - else { - this.extensionService.restartExtensionHost(); - } - } - - private migrateStorage(toWorkspace: IWorkspaceIdentifier): Promise { - return this.storageService.migrate(toWorkspace); - } - - private migrateWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { - return this.doCopyWorkspaceSettings(toWorkspace, setting => setting.scope === ConfigurationScope.WINDOW); - } - - copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { - return this.doCopyWorkspaceSettings(toWorkspace); - } - - private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): Promise { - const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - const targetWorkspaceConfiguration: any = {}; - for (const key of this.configurationService.keys().workspace) { - if (configurationProperties[key]) { - if (filter && !filter(configurationProperties[key])) { - continue; - } - - targetWorkspaceConfiguration[key] = this.configurationService.inspect(key).workspace; - } - } - - return this.jsonEditingService.write(toWorkspace.configPath, [{ key: 'settings', value: targetWorkspaceConfiguration }], true); - } - - private getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { - const workspace = this.contextService.getWorkspace(); - if (workspace && workspace.configuration) { - return { id: workspace.id, configPath: workspace.configuration }; - } - return undefined; + super(jsonEditingService, contextService, configurationService, storageService, extensionService, backupFileService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); } } -registerSingleton(IWorkspaceEditingService, WorkspaceEditingService, true); +registerSingleton(IWorkspaceEditingService, BrowserWorkspaceEditingService, true); + diff --git a/src/vs/workbench/services/workspace/browser/workspacesHistoryService.ts b/src/vs/workbench/services/workspace/browser/workspacesHistoryService.ts new file mode 100644 index 000000000000..dc5fe9d1ed81 --- /dev/null +++ b/src/vs/workbench/services/workspace/browser/workspacesHistoryService.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { IRecent, IRecentlyOpened, isRecentFolder, isRecentFile } from 'vs/platform/workspaces/common/workspacesHistory'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; +import { restoreRecentlyOpened, toStoreData } from 'vs/platform/workspaces/common/workspacesHistoryStorage'; +import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; +import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ILogService } from 'vs/platform/log/common/log'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export class BrowserWorkspacesHistoryService extends Disposable implements IWorkspacesHistoryService { + + static readonly RECENTLY_OPENED_KEY = 'recently.opened'; + + _serviceBrand: undefined; + + private readonly _onRecentlyOpenedChange: Emitter = this._register(new Emitter()); + readonly onRecentlyOpenedChange: Event = this._onRecentlyOpenedChange.event; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, + @ILogService private readonly logService: ILogService, + ) { + super(); + + this.addWorkspaceToRecentlyOpened(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.storageService.onDidChangeStorage(event => { + if (event.key === BrowserWorkspacesHistoryService.RECENTLY_OPENED_KEY && event.scope === StorageScope.GLOBAL) { + this._onRecentlyOpenedChange.fire(); + } + })); + } + + private addWorkspaceToRecentlyOpened(): void { + const workspace = this.workspaceService.getWorkspace(); + switch (this.workspaceService.getWorkbenchState()) { + case WorkbenchState.FOLDER: + this.addRecentlyOpened([{ folderUri: workspace.folders[0].uri }]); + break; + case WorkbenchState.WORKSPACE: + this.addRecentlyOpened([{ workspace: { id: workspace.id, configPath: workspace.configuration! } }]); + break; + } + } + + async getRecentlyOpened(): Promise { + const recentlyOpenedRaw = this.storageService.get(BrowserWorkspacesHistoryService.RECENTLY_OPENED_KEY, StorageScope.GLOBAL); + if (recentlyOpenedRaw) { + return restoreRecentlyOpened(JSON.parse(recentlyOpenedRaw), this.logService); + } + + return { workspaces: [], files: [] }; + } + + async addRecentlyOpened(recents: IRecent[]): Promise { + const recentlyOpened = await this.getRecentlyOpened(); + + recents.forEach(recent => { + if (isRecentFile(recent)) { + this.doRemoveFromRecentlyOpened(recentlyOpened, [recent.fileUri]); + recentlyOpened.files.unshift(recent); + } else if (isRecentFolder(recent)) { + this.doRemoveFromRecentlyOpened(recentlyOpened, [recent.folderUri]); + recentlyOpened.workspaces.unshift(recent); + } else { + this.doRemoveFromRecentlyOpened(recentlyOpened, [recent.workspace.configPath]); + recentlyOpened.workspaces.unshift(recent); + } + }); + + return this.saveRecentlyOpened(recentlyOpened); + } + + async removeFromRecentlyOpened(paths: URI[]): Promise { + const recentlyOpened = await this.getRecentlyOpened(); + + this.doRemoveFromRecentlyOpened(recentlyOpened, paths); + + return this.saveRecentlyOpened(recentlyOpened); + } + + private doRemoveFromRecentlyOpened(recentlyOpened: IRecentlyOpened, paths: URI[]): void { + recentlyOpened.files = recentlyOpened.files.filter(file => { + return !paths.some(path => path.toString() === file.fileUri.toString()); + }); + + recentlyOpened.workspaces = recentlyOpened.workspaces.filter(workspace => { + return !paths.some(path => path.toString() === (isRecentFolder(workspace) ? workspace.folderUri.toString() : workspace.workspace.configPath.toString())); + }); + } + + private async saveRecentlyOpened(data: IRecentlyOpened): Promise { + return this.storageService.store(BrowserWorkspacesHistoryService.RECENTLY_OPENED_KEY, JSON.stringify(toStoreData(data)), StorageScope.GLOBAL); + } + + async clearRecentlyOpened(): Promise { + this.storageService.remove(BrowserWorkspacesHistoryService.RECENTLY_OPENED_KEY, StorageScope.GLOBAL); + } +} + +registerSingleton(IWorkspacesHistoryService, BrowserWorkspacesHistoryService, true); diff --git a/src/vs/workbench/services/workspace/browser/workspacesService.ts b/src/vs/workbench/services/workspace/browser/workspacesService.ts index f8d6668fbd62..5100923e9bba 100644 --- a/src/vs/workbench/services/workspace/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspace/browser/workspacesService.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; export class WorkspacesService implements IWorkspacesService { diff --git a/src/vs/workbench/services/workspace/common/workspacesHistoryService.ts b/src/vs/workbench/services/workspace/common/workspacesHistoryService.ts new file mode 100644 index 000000000000..714fb485cac6 --- /dev/null +++ b/src/vs/workbench/services/workspace/common/workspacesHistoryService.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { IRecent, IRecentlyOpened } from 'vs/platform/workspaces/common/workspacesHistory'; + +export const IWorkspacesHistoryService = createDecorator('workspacesHistoryService'); + +export interface IWorkspacesHistoryService { + + _serviceBrand: undefined; + + readonly onRecentlyOpenedChange: Event; + + addRecentlyOpened(recents: IRecent[]): Promise; + + removeFromRecentlyOpened(workspaces: URI[]): Promise; + clearRecentlyOpened(): Promise; + + getRecentlyOpened(): Promise; +} diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts new file mode 100644 index 000000000000..dc91cdaa4a34 --- /dev/null +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { isEqual, basename, isEqualOrParent } from 'vs/base/common/resources'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspace/browser/abstractWorkspaceEditingService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; + +export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingService { + + _serviceBrand: undefined; + + constructor( + @IJSONEditingService jsonEditingService: IJSONEditingService, + @IWorkspaceContextService contextService: WorkspaceService, + @IElectronService private electronService: IElectronService, + @IConfigurationService configurationService: IConfigurationService, + @IStorageService storageService: IStorageService, + @IExtensionService extensionService: IExtensionService, + @IBackupFileService backupFileService: IBackupFileService, + @INotificationService notificationService: INotificationService, + @ICommandService commandService: ICommandService, + @IFileService fileService: IFileService, + @ITextFileService textFileService: ITextFileService, + @IWorkspacesHistoryService private readonly workspacesHistoryService: IWorkspacesHistoryService, + @IWorkspacesService workspacesService: IWorkspacesService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IFileDialogService fileDialogService: IFileDialogService, + @IDialogService protected dialogService: IDialogService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILabelService private readonly labelService: ILabelService, + @IHostService hostService: IHostService, + ) { + super(jsonEditingService, contextService, configurationService, storageService, extensionService, backupFileService, notificationService, commandService, fileService, textFileService, workspacesService, environmentService, fileDialogService, dialogService, hostService); + + this.registerListeners(); + } + + private registerListeners(): void { + this.lifecycleService.onBeforeShutdown(e => { + const saveOperation = this.saveUntitedBeforeShutdown(e.reason); + if (saveOperation) { + e.veto(saveOperation); + } + }); + } + + private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise { + if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { + return false; // only interested when window is closing or loading + } + + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); + if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { + return false; // only care about untitled workspaces to ask for saving + } + + const windowCount = await this.hostService.windowCount; + + if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { + return false; // Windows/Linux: quits when last window is closed, so do not ask then + } + + enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL + } + + const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; + const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; + const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; + + const buttons: { label: string; result: ConfirmResult; }[] = []; + if (isWindows) { + buttons.push(save, dontSave, cancel); + } else if (isLinux) { + buttons.push(dontSave, cancel, save); + } else { + buttons.push(save, cancel, dontSave); + } + + const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); + const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); + const cancelId = buttons.indexOf(cancel); + + const { choice } = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }); + + switch (buttons[choice].result) { + + // Cancel: veto unload + case ConfirmResult.CANCEL: + return true; + + // Don't Save: delete workspace + case ConfirmResult.DONT_SAVE: + this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + + // Save: save workspace, but do not veto unload if path provided + case ConfirmResult.SAVE: { + const newWorkspacePath = await this.pickNewWorkspacePath(); + if (!newWorkspacePath) { + return true; // keep veto if no target was provided + } + + try { + await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); + + const newWorkspaceIdentifier = await this.workspacesService.getWorkspaceIdentifier(newWorkspacePath); + + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); + this.workspacesHistoryService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); + + this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); + } catch (error) { + // ignore + } + + return false; + } + } + } + + async isValidTargetWorkspacePath(path: URI): Promise { + const windows = await this.electronService.getWindows(); + + // Prevent overwriting a workspace that is currently opened in another window + if (windows.some(window => !!window.workspace && isEqual(window.workspace.configPath, path))) { + await this.dialogService.show( + Severity.Info, + nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)), + [nls.localize('ok', "OK")], + { + detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") + } + ); + + return false; + } + + return true; // OK + } +} + +registerSingleton(IWorkspaceEditingService, NativeWorkspaceEditingService, true); diff --git a/src/vs/workbench/services/workspace/electron-browser/workspacesHistoryService.ts b/src/vs/workbench/services/workspace/electron-browser/workspacesHistoryService.ts new file mode 100644 index 000000000000..e84dad690ca4 --- /dev/null +++ b/src/vs/workbench/services/workspace/electron-browser/workspacesHistoryService.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IRecent, IRecentlyOpened } from 'vs/platform/workspaces/common/workspacesHistory'; +import { IWorkspacesHistoryService } from 'vs/workbench/services/workspace/common/workspacesHistoryService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class NativeWorkspacesHistoryService implements IWorkspacesHistoryService { + + _serviceBrand: undefined; + + readonly onRecentlyOpenedChange = this.electronService.onRecentlyOpenedChange; + + constructor( + @IElectronService private readonly electronService: IElectronService + ) { } + + async getRecentlyOpened(): Promise { + return this.electronService.getRecentlyOpened(); + } + + async addRecentlyOpened(recents: IRecent[]): Promise { + return this.electronService.addRecentlyOpened(recents); + } + + async removeFromRecentlyOpened(paths: URI[]): Promise { + return this.electronService.removeFromRecentlyOpened(paths); + } + + async clearRecentlyOpened(): Promise { + return this.electronService.clearRecentlyOpened(); + } +} + +registerSingleton(IWorkspacesHistoryService, NativeWorkspacesHistoryService, true); diff --git a/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts index f0b84fa86b63..c91a7c1dd709 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, reviveWorkspaceIdentifier, IEnterWorkspaceResult } from 'vs/platform/workspaces/common/workspaces'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class WorkspacesService implements IWorkspacesService { @@ -18,13 +18,13 @@ export class WorkspacesService implements IWorkspacesService { constructor( @IMainProcessService mainProcessService: IMainProcessService, - @IWindowService private readonly windowService: IWindowService + @IElectronEnvironmentService private readonly electronEnvironmentService: IElectronEnvironmentService ) { this.channel = mainProcessService.getChannel('workspaces'); } async enterWorkspace(path: URI): Promise { - const result: IEnterWorkspaceResult = await this.channel.call('enterWorkspace', [this.windowService.windowId, path]); + const result: IEnterWorkspaceResult = await this.channel.call('enterWorkspace', [this.electronEnvironmentService.windowId, path]); if (result) { result.workspace = reviveWorkspaceIdentifier(result.workspace); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts index 2b2290553604..ff41fcb767d5 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWebview.test.ts @@ -52,37 +52,37 @@ suite('ExtHostWebview', () => { const shape = createNoopMainThreadWebviews(); const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { webviewCspSource: '', - webviewResourceRoot: 'vscode-resource:{{resource}}' + webviewResourceRoot: 'vscode-resource://{{resource}}' }); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString(), - 'vscode-resource:/Users/codey/file.html', + 'vscode-resource://file///Users/codey/file.html', 'Unix basic' ); assert.strictEqual( webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString(), - 'vscode-resource:/Users/codey/file.html#frag', + 'vscode-resource://file///Users/codey/file.html#frag', 'Unix should preserve fragment' ); assert.strictEqual( webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString(), - 'vscode-resource:/Users/codey/f%20ile.html', + 'vscode-resource://file///Users/codey/f%20ile.html', 'Unix with encoding' ); assert.strictEqual( webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString(), - 'vscode-resource://localhost/Users/codey/file.html', + 'vscode-resource://file//localhost/Users/codey/file.html', 'Unix should preserve authority' ); assert.strictEqual( webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString(), - 'vscode-resource:/c%3A/codey/file.txt', + 'vscode-resource://file///c%3A/codey/file.txt', 'Windows C drive' ); }); @@ -92,7 +92,7 @@ suite('ExtHostWebview', () => { const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { webviewCspSource: '', - webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit{{resource}}` + webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}` }); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); @@ -102,31 +102,31 @@ suite('ExtHostWebview', () => { assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html')).toString()), - 'webview.contoso.com/commit///Users/codey/file.html', + 'webview.contoso.com/commit/file///Users/codey/file.html', 'Unix basic' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/file.html#frag')).toString()), - 'webview.contoso.com/commit///Users/codey/file.html#frag', + 'webview.contoso.com/commit/file///Users/codey/file.html#frag', 'Unix should preserve fragment' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///Users/codey/f%20ile.html')).toString()), - 'webview.contoso.com/commit///Users/codey/f%20ile.html', + 'webview.contoso.com/commit/file///Users/codey/f%20ile.html', 'Unix with encoding' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file://localhost/Users/codey/file.html')).toString()), - 'webview.contoso.com/commit//localhost/Users/codey/file.html', + 'webview.contoso.com/commit/file//localhost/Users/codey/file.html', 'Unix should preserve authority' ); assert.strictEqual( stripEndpointUuid(webview.webview.asWebviewUri(URI.parse('file:///c:/codey/file.txt')).toString()), - 'webview.contoso.com/commit///c%3A/codey/file.txt', + 'webview.contoso.com/commit/file///c%3A/codey/file.txt', 'Windows C drive' ); }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 0c8961b1b8eb..6f2b5999f494 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -35,14 +35,13 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IWindowsService, IWindowService, MenuBarVisibility, IURIToOpen, IOpenSettings, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, IWindowConfiguration, IWindowOpenable, IOpenInWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; @@ -92,7 +91,7 @@ export function createFileInput(instantiationService: IInstantiationService, res return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } -export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath); +export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv, OPTIONS) as IWindowConfiguration, process.execPath, 0); export class TestContextService implements IWorkspaceContextService { public _serviceBrand: undefined; @@ -306,12 +305,10 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); - instantiationService.stub(IWindowService, new TestWindowService()); instantiationService.stub(IMenuService, new TestMenuService()); instantiationService.stub(IKeybindingService, new MockKeybindingService()); instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); - instantiationService.stub(IWindowsService, new TestWindowsService()); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -1177,45 +1174,6 @@ export class TestCodeEditorService implements ICodeEditorService { openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(undefined); } } -export class TestWindowService implements IWindowService { - - public _serviceBrand: undefined; - - onDidChangeFocus: Event = new Emitter().event; - onDidChangeMaximize: Event; - - hasFocus = true; - - readonly windowId = 0; - - isFocused(): Promise { - return Promise.resolve(false); - } - - getRecentlyOpened(): Promise { - return Promise.resolve({ - workspaces: [], - files: [] - }); - } - - addRecentlyOpened(_recents: IRecent[]): Promise { - return Promise.resolve(); - } - - removeFromRecentlyOpened(_paths: URI[]): Promise { - return Promise.resolve(); - } - - focusWindow(): Promise { - return Promise.resolve(); - } - - openWindow(_uris: IURIToOpen[], _options?: IOpenSettings): Promise { - return Promise.resolve(); - } -} - export class TestLifecycleService implements ILifecycleService { public _serviceBrand: undefined; @@ -1255,58 +1213,6 @@ export class TestLifecycleService implements ILifecycleService { } } -export class TestWindowsService implements IWindowsService { - - _serviceBrand: undefined; - - readonly onWindowOpen: Event = Event.None; - readonly onWindowFocus: Event = Event.None; - readonly onWindowBlur: Event = Event.None; - readonly onWindowMaximize: Event = Event.None; - readonly onWindowUnmaximize: Event = Event.None; - readonly onRecentlyOpenedChange: Event = Event.None; - - isFocused(_windowId: number): Promise { - return Promise.resolve(false); - } - - addRecentlyOpened(_recents: IRecent[]): Promise { - return Promise.resolve(); - } - - removeFromRecentlyOpened(_paths: URI[]): Promise { - return Promise.resolve(); - } - - clearRecentlyOpened(): Promise { - return Promise.resolve(); - } - - getRecentlyOpened(_windowId: number): Promise { - return Promise.resolve({ - workspaces: [], - files: [] - }); - } - - focusWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - // Global methods - openWindow(_windowId: number, _uris: IURIToOpen[], _options: IOpenSettings): Promise { - return Promise.resolve(); - } - - getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - throw new Error('not implemented'); - } - - getActiveWindowId(): Promise { - return Promise.resolve(undefined); - } -} - export class TestTextResourceConfigurationService implements ITextResourceConfigurationService { _serviceBrand: undefined; @@ -1367,7 +1273,12 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities = this.diskFileSystemProvider.capabilities; readonly onDidChangeCapabilities: Event = this.diskFileSystemProvider.onDidChangeCapabilities; - readonly onDidChangeFile: Event = Event.map(this.diskFileSystemProvider.onDidChangeFile, changes => changes.map(c => { c.resource = c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }); return c; })); + readonly onDidChangeFile: Event = Event.map(this.diskFileSystemProvider.onDidChangeFile, changes => changes.map((c): IFileChange => { + return { + type: c.type, + resource: c.resource.with({ scheme: Schemas.vscodeRemote, authority: this.remoteAuthority }), + }; + })); watch(resource: URI, opts: IWatchOptions): IDisposable { return this.diskFileSystemProvider.watch(this.toFileResource(resource), opts); } stat(resource: URI): Promise { return this.diskFileSystemProvider.stat(this.toFileResource(resource)); } @@ -1395,13 +1306,19 @@ export class TestHostService implements IHostService { _serviceBrand: undefined; + readonly hasFocus: boolean = true; + readonly onDidChangeFocus: Event = Event.None; + windowCount = Promise.resolve(1); async restart(): Promise { } async reload(): Promise { } async closeWorkspace(): Promise { } - async openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise { } + async focus(): Promise { } + + async openEmptyWindow(options?: IOpenEmptyWindowOptions): Promise { } + async openInWindow(toOpen: IWindowOpenable[], options?: IOpenInWindowOptions): Promise { } async toggleFullScreen(): Promise { } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 96ddb7230633..508a01dbab9b 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -80,7 +80,6 @@ import 'vs/workbench/services/extensionManagement/common/extensionEnablementServ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; -import 'vs/workbench/services/workspace/browser/workspaceEditingService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -125,9 +124,6 @@ registerSingleton(IOpenerService, OpenerService, true); //#region --- workbench contributions -// Workspace File Watching -import 'vs/workbench/services/files/common/workspaceWatcher'; - // Telemetry import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index bc20b833770e..d87bb988f377 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -32,7 +32,7 @@ import 'vs/workbench/services/dialogs/electron-browser/fileDialogService'; import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/search/node/searchService'; -import 'vs/workbench/services/output/node/outputChannelModelService'; +import 'vs/workbench/services/output/electron-browser/outputChannelModelService'; import 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import 'vs/workbench/services/dialogs/electron-browser/dialogService'; import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; @@ -41,7 +41,6 @@ import 'vs/workbench/services/extensions/electron-browser/extensionService'; import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import 'vs/workbench/services/window/electron-browser/windowService'; import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; import 'vs/workbench/services/extensionManagement/node/extensionManagementService'; @@ -54,35 +53,29 @@ import 'vs/workbench/services/workspace/electron-browser/workspacesService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; import 'vs/workbench/services/host/electron-browser/desktopHostService'; import 'vs/workbench/services/request/electron-browser/requestService'; +import 'vs/workbench/services/lifecycle/electron-browser/lifecycleService'; +import 'vs/workbench/services/sharedProcess/electron-browser/sharedProcessService'; +import 'vs/workbench/services/electron/electron-browser/electronService'; +import 'vs/workbench/services/workspace/electron-browser/workspacesHistoryService'; +import 'vs/workbench/services/workspace/electron-browser/workspaceEditingService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; -import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; -import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; -import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ElectronService } from 'vs/platform/electron/electron-browser/electronService'; registerSingleton(IClipboardService, ClipboardService, true); -registerSingleton(ILifecycleService, LifecycleService); registerSingleton(ILocalizationsService, LocalizationsService); -registerSingleton(ISharedProcessService, SharedProcessService, true); -registerSingleton(IWindowsService, WindowsService); registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); registerSingleton(IMenubarService, MenubarService); -registerSingleton(IElectronService, ElectronService, true); //#endregion @@ -260,6 +253,9 @@ import 'vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribu // Welcome import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.contribution'; +// Configuration Exporter +import 'vs/workbench/contrib/configExporter/node/configurationExportHelper.contribution'; + //#endregion // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 0c7e797d429b..835eef159160 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -91,6 +91,12 @@ interface IWorkbenchConstructionOptions { */ updateProvider?: IUpdateProvider; + + /** + * Experimental: Support adding additional properties to telemetry. + */ + resolveCommonTelemetryProperties?: () => { [key: string]: any }; + /** * Experimental: Resolves an external uri before it is opened. */ diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index fa7593caa3ad..7e0ecea0109a 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -45,24 +45,27 @@ import 'vs/workbench/services/dialogs/browser/dialogService'; import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/request/browser/requestService'; -import 'vs/workbench/browser/web.simpleservices'; +import 'vs/workbench/services/workspace/browser/workspacesHistoryService'; +import 'vs/workbench/services/workspace/browser/workspaceEditingService'; +import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; -import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { BrowserLifecycleService } from 'vs/platform/lifecycle/browser/lifecycleService'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; -import { IUserDataSyncStoreService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ILoggerService } from 'vs/platform/log/common/log'; +import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -70,9 +73,10 @@ registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); -registerSingleton(ILifecycleService, BrowserLifecycleService); registerSingleton(IContextMenuService, ContextMenuService); registerSingleton(ITunnelService, NoOpTunnelService, true); +registerSingleton(ILoggerService, FileLoggerService); +registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); registerSingleton(IUserDataSyncService, UserDataSyncService); diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts index c0ec2c172da7..899116c94d84 100644 --- a/test/automation/src/debug.ts +++ b/test/automation/src/debug.ts @@ -29,6 +29,7 @@ const SPECIFIC_STACK_FRAME = (filename: string) => `${STACK_FRAME} .file[title*= const VARIABLE = `${VIEWLET} .debug-variables .monaco-list-row .expression`; const CONSOLE_OUTPUT = `.repl .output.expression .value`; const CONSOLE_EVALUATION_RESULT = `.repl .evaluation-result.expression .value`; +const CONSOLE_LINK = `.repl .value a.link`; const REPL_FOCUSED = '.repl-input-wrapper .monaco-editor textarea'; @@ -141,6 +142,10 @@ export class Debug extends Viewlet { await this.code.waitForElements(VARIABLE, false, els => els.length === count || els.length === alternativeCount); } + async waitForLink(): Promise { + await this.code.waitForElement(CONSOLE_LINK); + } + private async waitForOutput(fn: (output: string[]) => boolean): Promise { const elements = await this.code.waitForElements(CONSOLE_OUTPUT, false, elements => fn(elements.map(e => e.textContent))); return elements.map(e => e.textContent); diff --git a/test/smoke/src/areas/debug/debug.test.ts b/test/smoke/src/areas/debug/debug.test.ts index 2e188fbd7639..3c37670b74b3 100644 --- a/test/smoke/src/areas/debug/debug.test.ts +++ b/test/smoke/src/areas/debug/debug.test.ts @@ -106,6 +106,13 @@ export function setup() { await app.workbench.debug.waitForReplCommand('2 + 2', r => r === '4'); }); + it('debug console link', async function () { + const app = this.app as Application; + + await app.workbench.debug.waitForReplCommand('"./app.js:5:1"', r => r.includes('app.js')); + await app.workbench.debug.waitForLink(); + }); + it('stop debugging', async function () { const app = this.app as Application; diff --git a/yarn.lock b/yarn.lock index 71b434ab9469..8ebcc109e31c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9695,10 +9695,10 @@ xterm-addon-web-links@0.2.0: resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.0.tgz#b408a0be46211d8d4a0bb5e701d8f3c2bd07d473" integrity sha512-dq81c4Pzli2PgKVBgY2REte9sCVibR3df8AP3SEvCTM9uYFnUFxtxzMTplPnc7+rXabVhFdbU6x+rstIk8HNQg== -xterm@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.0.0.tgz#eac93e08cbe69cf238cbace9185ed9e38873df1a" - integrity sha512-Xbx3vvf9FnrUcI1qU31Jww7/fc/NqpXGqgByTvjj7+g3/yPvt/RvLkP/LLMcof2kLAC3evzZGMiovs7NkjdWDw== +xterm@4.1.0-beta8: + version "4.1.0-beta8" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.1.0-beta8.tgz#c1ef323ba336d92f5b52302b66f672dfff75b3ef" + integrity sha512-6lf+XVv0qT285w49P92tSYoUB406jdbgdhnPKNzxCIGtGX8kcwK+pHZ8HncDwcEhmTmI4LZ/WXPGtOQJg+onwg== y18n@^3.2.1: version "3.2.1"