From 2af13c18d2cb9faf64d1e0a00f075a938b60fbf7 Mon Sep 17 00:00:00 2001 From: ADS Merger Date: Sat, 8 Feb 2020 04:50:58 +0000 Subject: [PATCH] Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5 --- .eslintrc.json | 56 +- .vscode/launch.json | 6 +- .vscode/searches/ts36031.code-search | 1 - .yarnrc | 2 +- build/azure-pipelines/common/createAsset.ts | 17 +- .../darwin/continuous-build-darwin.yml | 3 + .../azure-pipelines/darwin/entitlements.plist | 12 + .../darwin/product-build-darwin.yml | 72 +- build/azure-pipelines/darwin/publish.sh | 3 - .../linux/continuous-build-linux.yml | 3 + build/azure-pipelines/linux/xvfb.init | 4 +- .../win32/ESRPClient/packages.config | 2 +- .../win32/continuous-build-win32.yml | 3 + build/azure-pipelines/win32/sign.ps1 | 2 +- build/gulpfile.editor.js | 7 +- build/gulpfile.hygiene.js | 6 +- build/lib/eslint/vscode-dts-create-func.js | 35 + build/lib/eslint/vscode-dts-create-func.ts | 40 + build/lib/eslint/vscode-dts-event-naming.js | 81 + build/lib/eslint/vscode-dts-event-naming.ts | 91 + .../lib/eslint/vscode-dts-interface-naming.js | 30 + .../lib/eslint/vscode-dts-interface-naming.ts | 35 + .../lib/eslint/vscode-dts-literal-or-types.js | 23 + .../lib/eslint/vscode-dts-literal-or-types.ts | 25 + build/lib/i18n.resources.json | 8 + build/lib/layersChecker.js | 13 +- build/lib/layersChecker.ts | 14 +- build/{ => lib}/testSetup.js | 0 build/{ => lib}/testSetup.ts | 0 build/monaco/monaco.d.ts.recipe | 10 +- build/npm/postinstall.js | 1 + build/package.json | 2 +- build/yarn.lock | 10 +- cgmanifest.json | 14 +- .../schemas/attachContainer.schema.json | 1 + .../schemas/devContainer.schema.json | 5 +- extensions/git/package.json | 11 +- extensions/git/package.nls.json | 1 + extensions/git/src/api/api1.ts | 6 +- extensions/git/src/api/git.d.ts | 14 +- extensions/git/src/commands.ts | 63 +- extensions/git/src/git.ts | 159 +- extensions/git/src/log.ts | 50 + extensions/git/src/main.ts | 4 +- extensions/git/src/repository.ts | 31 +- extensions/git/src/test/git.test.ts | 32 +- extensions/git/src/test/index.ts | 30 + extensions/git/src/test/smoke.test.ts | 127 ++ extensions/git/src/timelineProvider.ts | 213 +++ extensions/git/src/typings/refs.d.ts | 3 +- extensions/git/src/util.ts | 14 +- extensions/git/yarn.lock | 590 ++++++- .../server/src/jsonServerMain.ts | 6 +- .../preview-src/tsconfig.json | 7 +- extensions/package.json | 2 +- extensions/r/cgmanifest.json | 4 +- extensions/r/syntaxes/r.tmLanguage.json | 4 +- .../r/test/colorize-results/test_r.json | 6 +- extensions/search-result/src/extension.ts | 4 +- .../syntaxes/generateTMLanguage.js | 4 +- .../syntaxes/searchResult.tmLanguage.json | 108 +- extensions/shared.tsconfig.json | 3 + .../theme-defaults/themes/light_defaults.json | 3 +- extensions/theme-seti/cgmanifest.json | 2 +- extensions/theme-seti/icons/seti.woff | Bin 34420 -> 34632 bytes .../theme-seti/icons/vs-seti-icon-theme.json | 606 +++---- extensions/types/lib.textEncoder.d.ts | 11 + extensions/types/lib.url.d.ts | 10 + extensions/vscode-account/package.json | 7 +- extensions/vscode-account/src/AADHelper.ts | 257 ++- extensions/vscode-account/src/authServer.ts | 4 +- extensions/vscode-account/src/extension.ts | 2 +- extensions/vscode-account/src/keychain.ts | 10 +- .../vscode-account/src/typings/refs.d.ts | 7 + .../vscode-account/src/vscode.proposed.d.ts | 61 - extensions/vscode-account/tsconfig.json | 25 +- extensions/vscode-account/yarn.lock | 5 - extensions/vscode-colorize-tests/package.json | 13 +- extensions/yarn.lock | 8 +- package.json | 22 +- remote/package.json | 9 +- remote/web/package.json | 7 +- remote/web/yarn.lock | 29 +- remote/yarn.lock | 36 +- scripts/test-integration.bat | 13 +- scripts/test-integration.sh | 5 +- scripts/test.bat | 2 +- scripts/test.sh | 4 +- .../charts/test/browser/chartView.test.ts | 2 +- .../test/electron-browser/commandLine.test.ts | 4 +- .../browser/dataExplorerViewlet.ts | 6 - .../test/browser/notebookEditorModel.test.ts | 2 +- .../test/browser/notebookInput.test.ts | 6 +- .../electron-browser/contentManagers.test.ts | 2 +- .../notebookFindModel.test.ts | 2 +- .../electron-browser/notebookModel.test.ts | 2 +- .../browser/connectionTreeActions.test.ts | 18 +- .../test/browser/serverTreeView.test.ts | 2 +- .../query/common/fileQueryEditorInput.ts | 4 +- .../query/test/browser/queryActions.test.ts | 8 +- .../query/test/browser/queryEditor.test.ts | 4 +- .../test/browser/queryInputFactory.test.ts | 2 +- .../browser/accountManagementService.test.ts | 2 +- .../browser/connectionDialogService.test.ts | 2 +- .../connectionManagementService.test.ts | 2 +- .../browser/insightsDialogController.test.ts | 2 +- .../electron-browser/insightsUtils.test.ts | 2 +- .../test/browser/taskUtilities.test.ts | 2 +- .../common/editorReplacerContribution.test.ts | 2 +- .../api/extHostAccountManagement.test.ts | 2 +- .../api/extHostCredentialManagement.test.ts | 2 +- .../api/mainThreadNotebook.test.ts | 2 +- .../workbench/test/workbenchTestServices.ts | 2 +- src/vs/base/browser/browser.ts | 2 +- src/vs/base/browser/dom.ts | 19 +- src/vs/base/browser/fastDomNode.ts | 10 + src/vs/base/browser/ui/menu/menubar.ts | 9 +- .../browser/ui/scrollbar/scrollableElement.ts | 10 + .../ui/scrollbar/scrollableElementOptions.ts | 9 + src/vs/base/browser/ui/tree/abstractTree.ts | 2 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 4 + .../ui/tree/compressedObjectTreeModel.ts | 8 + src/vs/base/browser/ui/tree/indexTreeModel.ts | 21 + src/vs/base/browser/ui/tree/objectTree.ts | 4 + .../base/browser/ui/tree/objectTreeModel.ts | 4 + src/vs/base/browser/ui/tree/tree.ts | 2 + src/vs/base/common/arrays.ts | 4 + src/vs/base/common/jsonEdit.ts | 4 - src/vs/base/common/lifecycle.ts | 7 +- src/vs/base/common/resources.ts | 5 +- src/vs/base/node/config.ts | 4 +- src/vs/base/node/pfs.ts | 190 +- src/vs/base/parts/ipc/common/ipc.ts | 116 +- src/vs/base/parts/ipc/test/node/ipc.test.ts | 76 + .../browser}/media/arrow-left-dark.svg | 0 .../browser}/media/arrow-left-light.svg | 0 .../quickinput/browser}/media/quickInput.css | 0 .../parts/quickinput/browser/quickInput.ts | 1556 +++++++++++++++++ .../quickinput/browser}/quickInputBox.ts | 21 +- .../quickinput/browser}/quickInputList.ts | 53 +- .../quickinput/browser}/quickInputUtils.ts | 0 .../parts/quickinput/common/quickInput.ts | 256 +++ .../quickopen/browser/quickOpenWidget.ts | 1 + .../parts/quickopen/browser/quickopen.css | 4 + .../browser/ui/tree/indexTreeModel.test.ts | 14 +- src/vs/base/test/common/uri.test.ts | 2 +- src/vs/base/test/common/utils.ts | 19 +- .../base/test/{common => node}/path.test.ts | 4 +- src/vs/base/test/node/pfs/pfs.test.ts | 204 +-- .../code/browser/workbench/workbench-dev.html | 1 + src/vs/code/browser/workbench/workbench.html | 1 + .../issue/issueReporterPage.ts | 2 +- .../sharedProcess/sharedProcessMain.ts | 12 +- src/vs/code/electron-main/app.ts | 37 +- src/vs/code/electron-main/window.ts | 18 +- .../browser/config/elementSizeObserver.ts | 37 +- .../editor/browser/controller/coreCommands.ts | 27 +- .../browser/controller/textAreaHandler.ts | 5 +- .../browser/controller/textAreaInput.ts | 22 +- .../browser/services/bulkEditService.ts | 2 + .../browser/view/domLineBreaksComputer.ts | 1 + .../contentWidgets/contentWidgets.ts | 5 +- .../editorScrollbar/editorScrollbar.ts | 6 +- .../viewParts/lineNumbers/lineNumbers.css | 1 + .../browser/viewParts/lines/viewLines.css | 4 + .../browser/viewParts/lines/viewLines.ts | 8 + .../browser/viewParts/minimap/minimap.ts | 39 +- .../viewParts/minimap/minimapCharRenderer.ts | 3 +- .../minimap/minimapCharRendererFactory.ts | 3 +- .../editor/browser/viewParts/rulers/rulers.ts | 8 +- .../editor/browser/widget/codeEditorWidget.ts | 36 + .../editor/browser/widget/diffEditorWidget.ts | 46 +- .../browser/widget/media/diffEditor.css | 13 +- .../common/config/commonEditorConfig.ts | 5 + src/vs/editor/common/config/editorOptions.ts | 185 +- .../common/controller/cursorMoveOperations.ts | 3 +- src/vs/editor/common/core/rgba.ts | 9 + src/vs/editor/common/editorCommon.ts | 24 + src/vs/editor/common/editorContextKeys.ts | 2 + src/vs/editor/common/model.ts | 1 + .../pieceTreeTextBuffer/pieceTreeBase.ts | 2 +- src/vs/editor/common/model/textModel.ts | 73 +- src/vs/editor/common/model/textModelEvents.ts | 1 + src/vs/editor/common/model/tokensStore.ts | 34 +- src/vs/editor/common/modes.ts | 16 +- src/vs/editor/common/modes/linkComputer.ts | 7 + .../services/editorWorkerServiceImpl.ts | 6 +- .../common/services/modelServiceImpl.ts | 51 +- .../common/standalone/standaloneEnums.ts | 89 +- .../editor/common/view/editorColorRegistry.ts | 3 +- src/vs/editor/common/view/viewEvents.ts | 3 +- .../editor/common/viewLayout/linesLayout.ts | 1 - src/vs/editor/common/viewLayout/viewLayout.ts | 18 +- .../common/viewLayout/viewLineRenderer.ts | 22 +- .../common/viewModel/splitLinesCollection.ts | 22 +- .../editor/common/viewModel/viewModelImpl.ts | 8 +- src/vs/editor/contrib/find/findWidget.css | 2 +- src/vs/editor/contrib/find/findWidget.ts | 18 +- src/vs/editor/contrib/folding/folding.ts | 2 +- src/vs/editor/contrib/format/format.ts | 8 +- src/vs/editor/contrib/format/formatActions.ts | 2 +- .../editor/contrib/format/formattingEdit.ts | 10 +- src/vs/editor/contrib/gotoError/gotoError.ts | 3 +- .../contrib/gotoError/gotoErrorWidget.ts | 1 + .../editor/contrib/gotoSymbol/goToCommands.ts | 6 +- .../link/goToDefinitionAtPosition.ts | 17 +- .../gotoSymbol/peek/referencesController.ts | 8 +- .../contrib/gotoSymbol/symbolNavigation.ts | 3 +- .../linesOperations/linesOperations.ts | 30 +- src/vs/editor/contrib/rename/rename.ts | 2 +- .../editor/contrib/suggest/media/suggest.css | 41 +- .../suggest/media/suggestStatusBar.css | 35 + src/vs/editor/contrib/suggest/suggest.ts | 7 +- .../contrib/suggest/suggestController.ts | 108 +- .../editor/contrib/suggest/suggestWidget.ts | 125 +- .../suggest/test/suggestController.test.ts | 8 + .../standalone/browser/simpleServices.ts | 4 + .../browser/standaloneCodeEditor.ts | 8 +- .../standalone/browser/standaloneLanguages.ts | 16 + .../browser/standaloneThemeServiceImpl.ts | 4 +- .../test/browser/standaloneLanguages.test.ts | 4 +- .../services/decorationRenderOptions.test.ts | 30 +- .../browser/view/minimapCharRenderer.test.ts | 42 +- .../common/model/textModelWithTokens.test.ts | 283 +-- .../test/common/model/tokensStore.test.ts | 5 +- .../test/common/modes/linkComputer.test.ts | 7 + .../common/viewLayout/lineDecorations.test.ts | 6 +- .../viewLayout/viewLineRenderer.test.ts | 94 +- .../viewModel/splitLinesCollection.test.ts | 4 +- src/vs/monaco.d.ts | 212 +-- src/vs/platform/actions/common/actions.ts | 173 +- .../actions/test/common/menuService.test.ts | 2 +- .../test/{ => common}/commands.test.ts | 0 .../common/configurationModels.ts | 4 +- .../common/configurationRegistry.ts | 2 +- .../platform/contextkey/common/contextkey.ts | 45 +- .../platform/dialogs/electron-main/dialogs.ts | 2 +- .../platform/driver/electron-main/driver.ts | 3 +- src/vs/platform/editor/common/editor.ts | 20 +- .../electron-main/electronMainService.ts | 25 +- src/vs/platform/electron/node/electron.ts | 1 - .../common/extensionEnablementService.ts | 12 +- .../common/extensionManagement.ts | 6 +- .../common/extensionManagementIpc.ts | 2 +- .../extensionManagement.test.ts | 2 +- .../extensionValidator.test.ts | 0 src/vs/platform/files/common/fileService.ts | 17 +- src/vs/platform/files/common/files.ts | 2 +- .../files/node/diskFileSystemProvider.ts | 28 +- .../node/watcher/nodejs/watcherService.ts | 4 +- .../files/test/{ => common}/files.test.ts | 1 + .../files/test/node/diskFileService.test.ts | 102 +- src/vs/platform/list/browser/listService.ts | 31 +- src/vs/platform/log/browser/log.ts | 33 + src/vs/platform/log/common/log.ts | 25 +- src/vs/platform/log/common/logIpc.ts | 6 +- .../platform/menubar/electron-main/menubar.ts | 2 +- .../notification/common/notification.ts | 4 +- src/vs/platform/progress/common/progress.ts | 2 +- .../platform/quickinput/common/quickInput.ts | 250 +-- src/vs/platform/request/common/requestIpc.ts | 6 +- .../storage/browser/storageService.ts | 5 +- .../platform/storage/node/storageService.ts | 5 +- .../telemetryService.test.ts | 0 src/vs/platform/theme/common/colorRegistry.ts | 1 + src/vs/platform/theme/common/themeService.ts | 9 +- .../common/tokenClassificationRegistry.ts | 181 +- .../theme/electron-main/themeMainService.ts | 6 +- .../theme/test/common/testThemeService.ts | 4 +- .../common/abstractSynchronizer.ts | 7 +- .../userDataSync/common/extensionsSync.ts | 87 +- .../userDataSync/common/globalStateSync.ts | 49 +- .../userDataSync/common/keybindingsSync.ts | 62 +- .../userDataSync/common/settingsMerge.ts | 28 +- .../userDataSync/common/settingsSync.ts | 158 +- ...AutoSync.ts => userDataAutoSyncService.ts} | 41 +- .../userDataSync/common/userDataSync.ts | 34 +- .../userDataSync/common/userDataSyncIpc.ts | 8 +- .../common/userDataSyncService.ts | 64 +- .../common/userDataSyncStoreService.ts | 34 +- ...AutoSync.ts => userDataAutoSyncService.ts} | 4 +- .../test/common/settingsMerge.test.ts | 2 +- .../electron-main/windowsMainService.ts | 19 +- .../platform/workspaces/common/workspaces.ts | 2 +- .../workspacesHistoryMainService.ts | 6 +- .../electron-main/workspacesService.ts | 4 +- .../workspacesMainService.test.ts | 83 +- src/vs/vscode.d.ts | 60 +- src/vs/vscode.proposed.d.ts | 224 ++- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadAuthentication.ts | 20 +- .../api/browser/mainThreadConfiguration.ts | 2 +- .../api/browser/mainThreadOutputService.ts | 24 +- src/vs/workbench/api/browser/mainThreadSCM.ts | 4 +- .../api/browser/mainThreadSaveParticipant.ts | 49 +- .../api/browser/mainThreadTimeline.ts | 72 + .../api/browser/viewsExtensionPoint.ts | 10 +- src/vs/workbench/api/common/apiCommands.ts | 2 +- .../api/common/configurationExtensionPoint.ts | 1 - .../workbench/api/common/extHost.api.impl.ts | 24 +- .../workbench/api/common/extHost.protocol.ts | 26 +- .../api/common/extHostApiCommands.ts | 6 +- .../api/common/extHostAuthentication.ts | 64 +- .../api/common/extHostConfiguration.ts | 16 +- .../api/common/extHostExtensionService.ts | 3 +- src/vs/workbench/api/common/extHostSCM.ts | 27 +- .../api/common/extHostTerminalService.ts | 1 + .../workbench/api/common/extHostTimeline.ts | 129 ++ .../api/common/extHostTunnelService.ts | 6 +- src/vs/workbench/api/common/extHostTypes.ts | 9 + .../workbench/api/common/extHostWorkspace.ts | 2 +- .../api/common/menusExtensionPoint.ts | 4 +- .../api/node/extHostTunnelService.ts | 6 +- .../browser/actions/navigationActions.ts | 8 +- .../browser/actions/windowActions.ts | 2 +- src/vs/workbench/browser/contextkeys.ts | 3 +- src/vs/workbench/browser/dnd.ts | 4 +- src/vs/workbench/browser/labels.ts | 103 +- src/vs/workbench/browser/layout.ts | 9 +- src/vs/workbench/browser/panel.ts | 6 +- .../workbench/browser/parts/compositeBar.ts | 2 +- .../browser/parts/compositeBarActions.ts | 4 +- .../parts/editor/breadcrumbsControl.ts | 3 +- .../browser/parts/editor/breadcrumbsModel.ts | 8 + .../browser/parts/editor/breadcrumbsPicker.ts | 2 +- .../parts/editor/editor.contribution.ts | 19 +- .../browser/parts/editor/editorActions.ts | 34 +- .../browser/parts/editor/editorAutoSave.ts | 22 +- .../browser/parts/editor/editorGroupView.ts | 20 +- .../browser/parts/editor/editorStatus.ts | 37 +- .../browser/parts/editor/tabsTitleControl.ts | 14 +- .../browser/parts/editor/textEditor.ts | 28 +- .../parts/editor/textResourceEditor.ts | 33 +- .../browser/parts/panel/panelActions.ts | 12 +- .../browser/parts/panel/panelPart.ts | 18 +- .../browser/parts/quickinput/quickInput.ts | 1542 ++-------------- .../browser/parts/titlebar/menubarControl.ts | 4 +- .../browser/parts/views/customView.ts | 36 +- .../browser/parts/views/viewPaneContainer.ts | 37 +- src/vs/workbench/browser/parts/views/views.ts | 237 ++- .../browser/parts/views/viewsViewlet.ts | 4 +- src/vs/workbench/browser/web.main.ts | 17 +- .../browser/workbench.contribution.ts | 115 +- src/vs/workbench/browser/workbench.ts | 14 +- src/vs/workbench/common/editor.ts | 144 +- .../common/editor/diffEditorInput.ts | 4 +- src/vs/workbench/common/editor/editorGroup.ts | 57 +- .../common/editor/resourceEditorInput.ts | 31 +- .../common/editor/textEditorModel.ts | 6 +- .../common/editor/untitledTextEditorInput.ts | 111 +- .../common/editor/untitledTextEditorModel.ts | 57 +- src/vs/workbench/common/views.ts | 49 +- .../contrib/backup/common/backupTracker.ts | 22 +- .../electron-browser/backupRestorer.test.ts | 2 +- .../electron-browser/backupTracker.test.ts | 3 +- .../bulkEdit/browser/bulkEdit.contribution.ts | 62 +- .../bulkEdit/browser/{media => }/bulkEdit.css | 4 + .../contrib/bulkEdit/browser/bulkEditPane.ts | 55 +- .../bulkEdit/browser/bulkEditPreview.ts | 63 +- .../contrib/bulkEdit/browser/bulkEditTree.ts | 51 +- .../bulkEditPreview.test.ts | 19 +- .../browser/callHierarchyPeek.ts | 2 +- .../inspectEditorTokens.css | 11 + .../inspectEditorTokens.ts | 249 ++- .../suggestEnabledInput.ts | 10 +- .../contrib/comments/browser/commentsPanel.ts | 4 +- .../contrib/comments/browser/media/review.css | 1 + .../customEditor/browser/customEditors.ts | 16 +- .../customEditor/common/customEditor.ts | 4 +- .../contrib/debug/browser/baseDebugView.ts | 20 +- .../contrib/debug/browser/breakpointsView.ts | 3 +- .../debug/browser/debug.contribution.ts | 4 +- .../contrib/debug/browser/debugHover.ts | 9 +- .../contrib/debug/browser/debugService.ts | 56 +- .../contrib/debug/browser/debugSession.ts | 3 +- .../contrib/debug/browser/debugTaskRunner.ts | 15 +- .../debug/browser/loadedScriptsView.ts | 4 +- .../debug/browser/media/debugViewlet.css | 9 +- .../contrib/debug/browser/media/repl.css | 8 - .../contrib/debug/browser/rawDebugSession.ts | 16 +- .../workbench/contrib/debug/browser/repl.ts | 38 +- .../contrib/debug/browser/replViewer.ts | 2 - .../debug/browser/watchExpressionsView.ts | 1 - .../workbench/contrib/debug/common/debug.ts | 1 + .../contrib/debug/common/debugModel.ts | 12 +- .../contrib/debug/common/debugSource.ts | 61 +- .../contrib/debug/common/debugUtils.ts | 24 +- .../debug/test/browser/baseDebugView.test.ts | 18 +- .../debug/test/browser/linkDetector.test.ts | 2 +- .../contrib/debug/test/browser/repl.test.ts | 2 +- .../debug/test/common/debugUtils.test.ts | 27 +- .../debugANSIHandling.test.ts | 2 +- .../contrib/debug/test/node/debugger.test.ts | 2 +- .../experimentService.test.ts | 4 +- .../experimentalPrompts.test.ts | 2 +- .../extensions/browser/extensionEditor.ts | 91 +- .../browser/extensions.contribution.ts | 2 +- .../extensions/browser/extensionsActions.ts | 19 +- .../extensions/browser/extensionsList.ts | 4 +- .../extensions/browser/extensionsViewlet.ts | 4 +- .../extensions/browser/extensionsViews.ts | 2 +- .../browser/extensionsWorkbenchService.ts | 6 - .../extensions/common/extensionQuery.ts | 18 +- .../contrib/extensions/common/extensions.ts | 2 +- .../extensionsActions.test.ts | 5 +- .../extensionsTipsService.test.ts | 5 +- .../electron-browser/extensionsViews.test.ts | 5 +- .../extensionsWorkbenchService.test.ts | 25 +- .../editors/textFileSaveErrorHandler.ts | 7 +- .../files/browser/fileActions.contribution.ts | 3 +- .../contrib/files/browser/fileActions.ts | 30 +- .../files/browser/files.contribution.ts | 30 +- .../views/explorerDecorationsProvider.ts | 18 +- .../files/browser/views/explorerView.ts | 27 +- .../files/browser/views/explorerViewer.ts | 2 +- .../files/browser/views/openEditorsView.ts | 20 +- .../files/common/dirtyFilesIndicator.ts | 15 +- .../files/common/editors/fileEditorInput.ts | 180 +- .../contrib/files/common/explorerModel.ts | 7 +- .../workbench/contrib/files/common/files.ts | 8 - .../fileActions.contribution.ts | 10 +- .../electron-browser/files.contribution.ts | 9 + .../files/test/browser/editorAutoSave.test.ts | 4 +- .../explorerModel.test.ts | 2 +- .../files/test/browser/explorerView.test.ts | 13 +- .../fileActions.test.ts | 0 .../test/browser/fileEditorInput.test.ts | 37 +- .../test/browser/fileEditorTracker.test.ts | 2 +- .../fileOnDiskProvider.test.ts | 2 +- .../browser/localizationsActions.ts | 7 +- .../contrib/markers/browser/constants.ts | 15 +- .../markers/browser/markers.contribution.ts | 78 +- .../contrib/markers/browser/markers.ts | 2 +- .../markers/browser/markersTreeViewer.ts | 10 +- .../contrib/markers/browser/markersView.ts | 16 +- .../markers/browser/markersViewActions.ts | 9 +- .../markersModel.test.ts | 0 .../contrib/outline/browser/outlinePane.ts | 13 +- .../contrib/output/browser/logViewer.ts | 27 +- .../output/browser/output.contribution.ts | 37 +- .../contrib/output/browser/outputActions.ts | 6 +- .../contrib/output/browser/outputServices.ts | 108 +- .../browser/{outputPanel.ts => outputView.ts} | 179 +- .../workbench/contrib/output/common/output.ts | 15 +- .../{ => browser}/outputLinkProvider.test.ts | 2 +- .../electron-browser/perfviewEditor.ts | 13 +- .../preferences/browser/keybindingsEditor.ts | 4 +- .../browser/keybindingsEditorContribution.ts | 16 +- .../browser/preferences.contribution.ts | 258 +-- .../preferences/browser/preferencesEditor.ts | 2 +- .../browser/preferencesRenderers.ts | 35 +- .../preferences/browser/preferencesWidgets.ts | 2 +- .../preferences/browser/settingsLayout.ts | 5 + .../preferences/browser/settingsTree.ts | 14 +- .../quickopen/browser/commandsHandler.ts | 3 +- .../contrib/remote/browser/remote.ts | 8 +- .../contrib/remote/browser/tunnelView.ts | 91 +- .../remote/common/remote.contribution.ts | 15 +- .../electron-browser/remote.contribution.ts | 4 +- .../workbench/contrib/scm/browser/mainPane.ts | 3 +- .../contrib/scm/browser/repositoryPane.ts | 27 +- .../contrib/scm/browser/scmViewlet.ts | 48 +- .../contrib/scm/common/scmService.ts | 9 +- .../search/browser/openSymbolHandler.ts | 2 +- .../search/browser/patternInputWidget.ts | 9 +- .../search/browser/search.contribution.ts | 97 +- .../contrib/search/browser/searchActions.ts | 119 +- .../search/browser/searchEditorActions.ts | 78 - .../search/browser/searchEditorInput.ts | 305 ---- .../contrib/search/browser/searchView.ts | 18 +- .../contrib/search/common/constants.ts | 9 - .../{common => browser}/queryBuilder.test.ts | 44 +- .../search/test/browser/searchViewlet.test.ts | 2 +- .../electron-browser/queryBuilder.test.ts | 85 + .../contrib/searchEditor/browser/constants.ts | 19 + .../browser/media/searchEditor.css | 180 ++ .../browser/searchEditor.contribution.ts | 184 ++ .../browser/searchEditor.ts | 159 +- .../browser/searchEditorActions.ts | 193 ++ .../searchEditor/browser/searchEditorInput.ts | 353 ++++ .../browser/searchEditorSerialization.ts | 50 +- .../browser/snippetCompletionProvider.ts | 3 +- .../workspaceTags.test.ts | 0 .../tasks/browser/abstractTaskService.ts | 318 ++-- .../tasks/browser/providerProgressManager.ts | 59 + .../contrib/tasks/browser/quickOpen.ts | 9 +- .../tasks/browser/task.contribution.ts | 13 +- .../tasks/browser/terminalTaskSystem.ts | 82 +- .../contrib/tasks/common/jsonSchema_v2.ts | 6 + .../contrib/tasks/common/taskConfiguration.ts | 4 +- .../contrib/tasks/common/taskService.ts | 2 +- .../contrib/tasks/common/taskSystem.ts | 1 + .../workbench/contrib/tasks/common/tasks.ts | 3 +- .../contrib/tasks/node/processTaskSystem.ts | 8 + .../configuration.test.ts | 0 .../browser/addons/commandTrackerAddon.ts | 10 +- .../terminal/browser/media/configure-dark.svg | 6 - .../terminal/browser/media/configure-hc.svg | 6 - .../browser/media/configure-light.svg | 6 - .../terminal/browser/media/terminal.css | 12 - .../terminal/browser/terminal.contribution.ts | 131 +- .../contrib/terminal/browser/terminal.ts | 6 +- .../terminal/browser/terminalActions.ts | 16 +- .../terminal/browser/terminalInstance.ts | 31 +- .../browser/terminalInstanceService.ts | 27 +- .../terminal/browser/terminalNativeService.ts | 34 - .../terminal/browser/terminalService.ts | 31 +- .../contrib/terminal/common/terminal.ts | 1 + .../terminal/common/terminalEnvironment.ts | 2 +- .../terminalInstanceService.ts | 13 +- .../terminalLinkHandler.test.ts | 9 +- .../timeline/browser/media/timelinePane.css | 15 + .../timeline/browser/timeline.contribution.ts | 52 + .../contrib/timeline/browser/timelinePane.ts | 395 +++++ .../contrib/timeline/common/timeline.ts | 82 + .../timeline/common/timelineService.ts | 175 ++ .../contrib/update/browser/update.ts | 2 +- .../url/test/browser}/linkProtection.test.ts | 0 ...AutoSync.ts => userDataAutoSyncService.ts} | 4 +- .../userDataSync/browser/userDataSync.ts | 166 +- .../contrib/webview/browser/pre/main.js | 2 +- .../webview/browser/webview.contribution.ts | 77 +- .../webview/browser/webviewCommands.ts | 76 +- .../webview/browser/webviewIconManager.ts | 36 +- .../electron-browser/webview.contribution.ts | 14 +- .../electron-browser/webviewCommands.ts | 64 +- .../electron-browser/webviewElement.ts | 6 +- .../electron-browser/desktop.contribution.ts | 17 - .../electron-browser/desktop.main.ts | 6 +- src/vs/workbench/electron-browser/window.ts | 46 +- .../browser/authenticationService.ts | 10 +- .../backupFileService.test.ts | 2 +- .../bulkEdit/browser/bulkEditService.ts | 41 +- .../services/bulkEdit/browser/conflicts.ts | 39 +- .../common/configurationEditingService.ts | 51 +- .../configurationEditingService.test.ts | 3 +- .../configurationService.test.ts | 3 +- .../browser/configurationResolverService.ts | 17 +- .../configurationResolverService.test.ts | 5 +- .../browser/abstractFileDialogService.ts | 17 +- .../electron-browser/fileDialogService.ts | 8 +- .../services/editor/common/editorService.ts | 2 +- .../test/browser/editorGroupsService.test.ts | 2 +- .../editor/test/browser/editorService.test.ts | 2 +- .../test/browser/editorsObserver.test.ts | 2 +- .../common/extensionEnablementService.ts | 18 +- .../extensionManagementServerService.ts | 5 +- .../extensionManagementServerService.ts | 3 +- .../extensionEnablementService.test.ts | 2 +- .../common/extensionHostProcessManager.ts | 39 +- .../test/{node => common}/rpcProtocol.test.ts | 2 +- .../extensions/worker/extensionHostWorker.ts | 2 - .../common/filesConfigurationService.ts | 2 +- .../services/history/browser/history.ts | 42 +- .../test/{ => browser}/history.test.ts | 2 +- .../host/browser/browserHostService.ts | 38 +- .../electron-browser/desktopHostService.ts | 19 +- .../keybinding.contribution.ts | 2 +- .../browserKeyboardMapper.test.ts | 6 +- .../keybindingEditing.test.ts | 10 +- .../keybindingIO.test.ts | 0 .../keyboardMapperTestUtils.ts | 4 +- .../{ => electron-browser}/linux_de_ch.js | 0 .../{ => electron-browser}/linux_de_ch.txt | 0 .../{ => electron-browser}/linux_en_uk.js | 0 .../{ => electron-browser}/linux_en_uk.txt | 0 .../{ => electron-browser}/linux_en_us.js | 0 .../{ => electron-browser}/linux_en_us.txt | 0 .../test/{ => electron-browser}/linux_ru.js | 0 .../test/{ => electron-browser}/linux_ru.txt | 0 .../macLinuxFallbackKeyboardMapper.test.ts | 2 +- .../macLinuxKeyboardMapper.test.ts | 2 +- .../test/{ => electron-browser}/mac_de_ch.js | 0 .../test/{ => electron-browser}/mac_de_ch.txt | 0 .../test/{ => electron-browser}/mac_en_us.js | 0 .../test/{ => electron-browser}/mac_en_us.txt | 0 .../{ => electron-browser}/mac_zh_hant.js | 0 .../{ => electron-browser}/mac_zh_hant.txt | 0 .../test/{ => electron-browser}/win_de_ch.js | 0 .../test/{ => electron-browser}/win_de_ch.txt | 0 .../test/{ => electron-browser}/win_en_us.js | 0 .../test/{ => electron-browser}/win_en_us.txt | 0 .../{ => electron-browser}/win_por_ptb.js | 0 .../{ => electron-browser}/win_por_ptb.txt | 0 .../test/{ => electron-browser}/win_ru.js | 0 .../test/{ => electron-browser}/win_ru.txt | 0 .../windowsKeyboardMapper.test.ts | 2 +- .../label/test/{ => browser}/label.test.ts | 2 +- .../services/panel/common/panelService.ts | 2 +- .../common/preferencesEditorInput.ts | 10 +- .../preferences/common/preferencesModels.ts | 2 +- .../{ => browser}/progressIndicator.test.ts | 2 +- .../common/abstractRemoteAgentService.ts | 67 +- .../common/remoteAgentEnvironmentChannel.ts | 22 +- .../common/remoteAgentFileSystemChannel.ts | 10 +- .../remote/common/remoteAgentService.ts | 1 + .../remote/common/remoteExplorerService.ts | 3 +- .../services/remote/node/tunnelService.ts | 36 +- .../request/browser/requestService.ts | 23 +- .../services/statusbar/common/statusbar.ts | 2 +- .../textfile/browser/textFileService.ts | 42 +- .../textfile/common/textFileEditorModel.ts | 221 +-- .../services/textfile/common/textfiles.ts | 22 +- .../electron-browser/nativeTextFileService.ts | 8 +- .../{ => browser}/textFileEditorModel.test.ts | 159 +- .../textFileEditorModelManager.test.ts | 2 +- .../{ => browser}/textFileService.test.ts | 4 +- .../{ => common}/saveSequenzializer.test.ts | 0 .../fixtures/binary.txt | Bin .../fixtures/index.html | 0 .../{ => electron-browser}/fixtures/lorem.txt | 0 .../fixtures/lorem_big5.txt | 0 .../fixtures/lorem_cp1252.txt | 0 .../fixtures/lorem_cp866.txt | 0 .../fixtures/lorem_gbk.txt | 0 .../fixtures/lorem_shiftjis.txt | 0 .../fixtures/lorem_utf16be.txt | Bin .../fixtures/lorem_utf16le.txt | Bin .../fixtures/lorem_utf8bom.txt | 0 .../{ => electron-browser}/fixtures/small.txt | 0 .../fixtures/small_umlaut.txt | 0 .../fixtures/some.utf16le | Bin .../fixtures/some_big5.txt | 0 .../fixtures/some_cp1252.txt | 0 .../fixtures/some_cyrillic.txt | 0 .../fixtures/some_gbk.txt | 0 .../fixtures/some_shiftjs.txt | 0 .../fixtures/some_small_cp1252.txt | 0 .../fixtures/some_utf16le.css | Bin .../fixtures/some_utf8_bom.txt | 0 .../fixtures/utf16_be_nobom.txt | Bin .../fixtures/utf16_le_nobom.txt | Bin .../textFileService.io.test.ts | 2 +- .../common/textModelResolverService.ts | 3 +- .../textModelResolverService.test.ts | 4 +- .../themes/browser/fileIconThemeStore.ts | 19 +- .../themes/browser/workbenchThemeService.ts | 8 +- .../services/themes/common/colorThemeData.ts | 92 +- .../services/themes/common/colorThemeStore.ts | 21 +- .../tokenClassificationExtensionPoint.ts | 97 +- .../themes/common/workbenchThemeService.ts | 2 + .../tokenStyleResolving.test.ts | 46 +- .../timer/electron-browser/timerService.ts | 6 - .../common/untitledTextEditorService.ts | 2 +- .../electron-browser/settingsSyncService.ts | 8 +- .../electron-browser/userDataSyncService.ts | 22 +- .../views/browser/viewDescriptorService.ts | 150 +- .../test/common/workingCopyService.test.ts | 2 +- .../workspaces/browser/workspacesService.ts | 14 +- .../workspaceEditingService.ts | 9 +- .../api/extHost.api.impl.test.ts | 0 .../api/extHostApiCommands.test.ts | 89 +- .../api/extHostCommands.test.ts | 2 +- .../api/extHostConfiguration.test.ts | 81 +- .../api/extHostDiagnostics.test.ts | 2 +- .../api/extHostDocumentData.test.ts | 2 +- .../extHostDocumentSaveParticipant.test.ts | 2 +- .../api/extHostDocumentsAndEditors.test.ts | 2 +- .../api/extHostFileSystemEventService.test.ts | 0 .../api/extHostLanguageFeatures.test.ts | 2 +- .../api/extHostMessagerService.test.ts | 5 +- .../api/extHostTextEditor.test.ts | 2 +- .../api/extHostTextEditors.test.ts | 4 +- .../api/extHostTreeViews.test.ts | 2 +- .../api/extHostTypeConverter.test.ts | 0 .../api/extHostTypes.test.ts | 0 .../api/extHostWebview.test.ts | 4 +- .../api/extHostWorkspace.test.ts | 2 +- .../api/mainThreadCommands.test.ts | 2 +- .../api/mainThreadConfiguration.test.ts | 0 .../api/mainThreadDiagnostics.test.ts | 0 ...mainThreadDocumentContentProviders.test.ts | 4 +- .../api/mainThreadDocuments.test.ts | 0 .../api/mainThreadDocumentsAndEditors.test.ts | 4 +- .../api/mainThreadEditors.test.ts | 93 +- .../{electron-browser => browser}/api/mock.ts | 0 .../api/testRPCProtocol.ts | 0 src/vs/workbench/test/browser/part.test.ts | 2 +- .../browser/parts/editor/baseEditor.test.ts | 2 +- .../parts/editor/breadcrumbModel.test.ts | 2 +- .../parts}/editor/editor.test.ts | 2 +- .../parts}/editor/editorDiffModel.test.ts | 4 +- .../parts}/editor/editorGroups.test.ts | 19 +- .../parts}/editor/editorInput.test.ts | 0 .../parts}/editor/editorModel.test.ts | 2 +- .../parts/editor/rangeDecorations.test.ts | 2 +- .../parts}/editor/resourceEditorInput.test.ts | 6 +- .../parts}/editor/untitledTextEditor.test.ts | 30 +- .../test/browser/parts/views/views.test.ts | 256 ++- .../{ => browser}/workbenchTestServices.ts | 988 ++--------- src/vs/workbench/test/common/memento.test.ts | 4 +- .../test/common/workbenchTestServices.ts | 137 ++ .../api/extHostSearch.test.ts | 4 +- .../api/mainThreadSaveParticipant.test.ts | 3 +- .../api/mainThreadWorkspace.test.ts | 4 +- .../quickopen.perf.integrationTest.ts | 3 +- .../textsearch.perf.integrationTest.ts | 3 +- .../electron-browser/workbenchTestServices.ts | 189 ++ src/vs/workbench/workbench.common.main.ts | 10 +- src/vs/workbench/workbench.web.main.ts | 5 +- test/README.md | 44 +- test/automation/package.json | 7 +- test/automation/src/application.ts | 1 + test/automation/src/code.ts | 107 +- test/automation/src/editor.ts | 2 +- test/automation/src/editors.ts | 18 +- ...puppeteerDriver.ts => playwrightDriver.ts} | 50 +- test/automation/src/problems.ts | 2 +- test/automation/src/search.ts | 5 +- test/automation/src/statusbar.ts | 20 +- test/automation/tsconfig.json | 2 +- test/automation/yarn.lock | 162 +- test/browser.js | 48 - test/integration/browser/.gitignore | 5 + test/integration/browser/README.md | 13 + test/integration/browser/package.json | 18 + test/integration/browser/src/index.ts | 116 ++ test/integration/browser/tsconfig.json | 21 + test/integration/browser/yarn.lock | 148 ++ test/smoke/README.md | 38 +- test/smoke/package.json | 2 +- test/smoke/src/areas/css/css.test.ts | 3 - test/smoke/src/areas/debug/debug.test.ts | 122 -- test/smoke/src/areas/git/git.test.ts | 80 - .../src/areas/workbench/data-loss.test.ts | 6 +- .../areas/workbench/data-migration.test.ts | 5 +- test/smoke/src/main.ts | 215 ++- test/smoke/test/index.js | 14 +- test/smoke/yarn.lock | 8 +- test/{ => ui}/splitview/package.json | 0 test/{ => ui}/splitview/public/index.html | 0 test/{ => ui}/splitview/server.js | 0 test/{ => ui}/splitview/yarn.lock | 0 test/{ => ui}/tree/package.json | 0 test/{ => ui}/tree/public/compressed.json | 0 test/{ => ui}/tree/public/index.html | 0 test/{ => ui}/tree/server.js | 0 test/{ => ui}/tree/tree.js | 0 test/{ => ui}/tree/yarn.lock | 0 test/unit/README.md | 49 + test/{ => unit}/assert.js | 91 +- test/unit/browser/index.js | 244 +++ test/unit/browser/renderer.html | 140 ++ test/{ => unit}/coverage.js | 4 +- test/{ => unit}/electron/index.js | 2 +- test/{ => unit}/electron/renderer.html | 4 +- test/{ => unit}/electron/renderer.js | 6 +- test/{ => unit/node}/all.js | 66 +- test/unit/node/browser.js | 49 + test/{ => unit/node}/css.mock.js | 0 test/{ => unit/node}/index.html | 4 +- yarn.lock | 977 +++++------ 752 files changed, 16458 insertions(+), 10063 deletions(-) create mode 100644 build/azure-pipelines/darwin/entitlements.plist create mode 100644 build/lib/eslint/vscode-dts-create-func.js create mode 100644 build/lib/eslint/vscode-dts-create-func.ts create mode 100644 build/lib/eslint/vscode-dts-event-naming.js create mode 100644 build/lib/eslint/vscode-dts-event-naming.ts create mode 100644 build/lib/eslint/vscode-dts-interface-naming.js create mode 100644 build/lib/eslint/vscode-dts-interface-naming.ts create mode 100644 build/lib/eslint/vscode-dts-literal-or-types.js create mode 100644 build/lib/eslint/vscode-dts-literal-or-types.ts rename build/{ => lib}/testSetup.js (100%) rename build/{ => lib}/testSetup.ts (100%) create mode 100644 extensions/git/src/log.ts create mode 100644 extensions/git/src/test/index.ts create mode 100644 extensions/git/src/test/smoke.test.ts create mode 100644 extensions/git/src/timelineProvider.ts create mode 100644 extensions/types/lib.textEncoder.d.ts create mode 100644 extensions/types/lib.url.d.ts create mode 100644 extensions/vscode-account/src/typings/refs.d.ts delete mode 100644 extensions/vscode-account/src/vscode.proposed.d.ts rename src/vs/{workbench/browser/parts/quickinput => base/parts/quickinput/browser}/media/arrow-left-dark.svg (100%) rename src/vs/{workbench/browser/parts/quickinput => base/parts/quickinput/browser}/media/arrow-left-light.svg (100%) rename src/vs/{workbench/browser/parts/quickinput => base/parts/quickinput/browser}/media/quickInput.css (100%) create mode 100644 src/vs/base/parts/quickinput/browser/quickInput.ts rename src/vs/{workbench/browser/parts/quickinput => base/parts/quickinput/browser}/quickInputBox.ts (68%) rename src/vs/{workbench/browser/parts/quickinput => base/parts/quickinput/browser}/quickInputList.ts (87%) rename src/vs/{workbench/browser/parts/quickinput => base/parts/quickinput/browser}/quickInputUtils.ts (100%) create mode 100644 src/vs/base/parts/quickinput/common/quickInput.ts rename src/vs/base/test/{common => node}/path.test.ts (99%) create mode 100644 src/vs/editor/contrib/suggest/media/suggestStatusBar.css rename src/vs/platform/commands/test/{ => common}/commands.test.ts (100%) rename src/vs/platform/extensionManagement/test/{electron-browser => common}/extensionManagement.test.ts (99%) rename src/vs/platform/extensions/test/{node => common}/extensionValidator.test.ts (100%) rename src/vs/platform/files/test/{ => common}/files.test.ts (99%) create mode 100644 src/vs/platform/log/browser/log.ts rename src/vs/platform/telemetry/test/{electron-browser => browser}/telemetryService.test.ts (100%) rename src/vs/platform/userDataSync/common/{userDataAutoSync.ts => userDataAutoSyncService.ts} (76%) rename src/vs/platform/userDataSync/electron-browser/{userDataAutoSync.ts => userDataAutoSyncService.ts} (88%) create mode 100644 src/vs/workbench/api/browser/mainThreadTimeline.ts create mode 100644 src/vs/workbench/api/common/extHostTimeline.ts rename src/vs/workbench/contrib/bulkEdit/browser/{media => }/bulkEdit.css (92%) rename src/vs/workbench/contrib/bulkEdit/test/{electron-brower => browser}/bulkEditPreview.test.ts (85%) rename src/vs/workbench/contrib/debug/test/{browser => electron-browser}/debugANSIHandling.test.ts (99%) rename src/vs/workbench/contrib/files/test/{electron-browser => browser}/explorerModel.test.ts (99%) rename src/vs/workbench/contrib/files/test/{electron-browser => browser}/fileActions.test.ts (100%) rename src/vs/workbench/contrib/files/test/{common => browser}/fileOnDiskProvider.test.ts (97%) rename src/vs/workbench/contrib/markers/test/{electron-browser => browser}/markersModel.test.ts (100%) rename src/vs/workbench/contrib/output/browser/{outputPanel.ts => outputView.ts} (54%) rename src/vs/workbench/contrib/output/test/{ => browser}/outputLinkProvider.test.ts (99%) delete mode 100644 src/vs/workbench/contrib/search/browser/searchEditorActions.ts delete mode 100644 src/vs/workbench/contrib/search/browser/searchEditorInput.ts rename src/vs/workbench/contrib/search/test/{common => browser}/queryBuilder.test.ts (95%) create mode 100644 src/vs/workbench/contrib/search/test/electron-browser/queryBuilder.test.ts create mode 100644 src/vs/workbench/contrib/searchEditor/browser/constants.ts create mode 100644 src/vs/workbench/contrib/searchEditor/browser/media/searchEditor.css create mode 100644 src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts rename src/vs/workbench/contrib/{search => searchEditor}/browser/searchEditor.ts (77%) create mode 100644 src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts create mode 100644 src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts rename src/vs/workbench/contrib/{search => searchEditor}/browser/searchEditorSerialization.ts (85%) rename src/vs/workbench/contrib/tags/test/{ => electron-browser}/workspaceTags.test.ts (100%) create mode 100644 src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts rename src/vs/workbench/contrib/tasks/test/{electron-browser => common}/configuration.test.ts (100%) delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/configure-dark.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/configure-hc.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/media/configure-light.svg delete mode 100644 src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts create mode 100644 src/vs/workbench/contrib/timeline/browser/media/timelinePane.css create mode 100644 src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts create mode 100644 src/vs/workbench/contrib/timeline/browser/timelinePane.ts create mode 100644 src/vs/workbench/contrib/timeline/common/timeline.ts create mode 100644 src/vs/workbench/contrib/timeline/common/timelineService.ts rename src/vs/workbench/{test/contrib => contrib/url/test/browser}/linkProtection.test.ts (100%) rename src/vs/workbench/contrib/userDataSync/browser/{userDataAutoSync.ts => userDataAutoSyncService.ts} (89%) rename src/vs/workbench/services/extensionManagement/test/{electron-browser => browser}/extensionEnablementService.test.ts (99%) rename src/vs/workbench/services/extensions/test/{node => common}/rpcProtocol.test.ts (99%) rename src/vs/workbench/services/history/test/{ => browser}/history.test.ts (99%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/browserKeyboardMapper.test.ts (95%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/keybindingIO.test.ts (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/keyboardMapperTestUtils.ts (95%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_de_ch.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_de_ch.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_en_uk.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_en_uk.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_en_us.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_en_us.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_ru.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/linux_ru.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/macLinuxFallbackKeyboardMapper.test.ts (98%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/macLinuxKeyboardMapper.test.ts (99%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/mac_de_ch.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/mac_de_ch.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/mac_en_us.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/mac_en_us.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/mac_zh_hant.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/mac_zh_hant.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_de_ch.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_de_ch.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_en_us.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_en_us.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_por_ptb.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_por_ptb.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_ru.js (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/win_ru.txt (100%) rename src/vs/workbench/services/keybinding/test/{ => electron-browser}/windowsKeyboardMapper.test.ts (99%) rename src/vs/workbench/services/label/test/{ => browser}/label.test.ts (99%) rename src/vs/workbench/services/progress/test/{ => browser}/progressIndicator.test.ts (99%) rename src/vs/{platform => workbench/services}/remote/common/remoteAgentFileSystemChannel.ts (93%) rename src/vs/workbench/services/textfile/test/{ => browser}/textFileEditorModel.test.ts (80%) rename src/vs/workbench/services/textfile/test/{ => browser}/textFileEditorModelManager.test.ts (99%) rename src/vs/workbench/services/textfile/test/{ => browser}/textFileService.test.ts (95%) rename src/vs/workbench/services/textfile/test/{ => common}/saveSequenzializer.test.ts (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/binary.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/index.html (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_big5.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_cp1252.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_cp866.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_gbk.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_shiftjis.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_utf16be.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_utf16le.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/lorem_utf8bom.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/small.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/small_umlaut.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some.utf16le (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_big5.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_cp1252.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_cyrillic.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_gbk.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_shiftjs.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_small_cp1252.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_utf16le.css (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/some_utf8_bom.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/utf16_be_nobom.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/fixtures/utf16_le_nobom.txt (100%) rename src/vs/workbench/services/textfile/test/{ => electron-browser}/textFileService.io.test.ts (99%) rename src/vs/workbench/services/textmodelResolver/test/{ => browser}/textModelResolverService.test.ts (97%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHost.api.impl.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostApiCommands.test.ts (94%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostCommands.test.ts (97%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostConfiguration.test.ts (87%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostDiagnostics.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostDocumentData.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostDocumentSaveParticipant.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostDocumentsAndEditors.test.ts (94%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostFileSystemEventService.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostLanguageFeatures.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostMessagerService.test.ts (96%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostTextEditor.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostTextEditors.test.ts (95%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostTreeViews.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostTypeConverter.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostTypes.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostWebview.test.ts (98%) rename src/vs/workbench/test/{electron-browser => browser}/api/extHostWorkspace.test.ts (99%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadCommands.test.ts (97%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadConfiguration.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadDiagnostics.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadDocumentContentProviders.test.ts (92%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadDocuments.test.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadDocumentsAndEditors.test.ts (98%) rename src/vs/workbench/test/{electron-browser => browser}/api/mainThreadEditors.test.ts (70%) rename src/vs/workbench/test/{electron-browser => browser}/api/mock.ts (100%) rename src/vs/workbench/test/{electron-browser => browser}/api/testRPCProtocol.ts (100%) rename src/vs/workbench/test/{common => browser/parts}/editor/editor.test.ts (99%) rename src/vs/workbench/test/{common => browser/parts}/editor/editorDiffModel.test.ts (92%) rename src/vs/workbench/test/{common => browser/parts}/editor/editorGroups.test.ts (98%) rename src/vs/workbench/test/{common => browser/parts}/editor/editorInput.test.ts (100%) rename src/vs/workbench/test/{common => browser/parts}/editor/editorModel.test.ts (98%) rename src/vs/workbench/test/{common => browser/parts}/editor/resourceEditorInput.test.ts (97%) rename src/vs/workbench/test/{common => browser/parts}/editor/untitledTextEditor.test.ts (94%) rename src/vs/workbench/test/{ => browser}/workbenchTestServices.ts (63%) create mode 100644 src/vs/workbench/test/common/workbenchTestServices.ts create mode 100644 src/vs/workbench/test/electron-browser/workbenchTestServices.ts rename test/automation/src/{puppeteerDriver.ts => playwrightDriver.ts} (71%) delete mode 100644 test/browser.js create mode 100644 test/integration/browser/.gitignore create mode 100644 test/integration/browser/README.md create mode 100644 test/integration/browser/package.json create mode 100644 test/integration/browser/src/index.ts create mode 100644 test/integration/browser/tsconfig.json create mode 100644 test/integration/browser/yarn.lock delete mode 100644 test/smoke/src/areas/debug/debug.test.ts delete mode 100644 test/smoke/src/areas/git/git.test.ts rename test/{ => ui}/splitview/package.json (100%) rename test/{ => ui}/splitview/public/index.html (100%) rename test/{ => ui}/splitview/server.js (100%) rename test/{ => ui}/splitview/yarn.lock (100%) rename test/{ => ui}/tree/package.json (100%) rename test/{ => ui}/tree/public/compressed.json (100%) rename test/{ => ui}/tree/public/index.html (100%) rename test/{ => ui}/tree/server.js (100%) rename test/{ => ui}/tree/tree.js (100%) rename test/{ => ui}/tree/yarn.lock (100%) create mode 100644 test/unit/README.md rename test/{ => unit}/assert.js (89%) create mode 100644 test/unit/browser/index.js create mode 100644 test/unit/browser/renderer.html rename test/{ => unit}/coverage.js (95%) rename test/{ => unit}/electron/index.js (98%) rename test/{ => unit}/electron/renderer.html (88%) rename test/{ => unit}/electron/renderer.js (97%) rename test/{ => unit/node}/all.js (73%) create mode 100644 test/unit/node/browser.js rename test/{ => unit/node}/css.mock.js (100%) rename test/{ => unit/node}/index.html (92%) diff --git a/.eslintrc.json b/.eslintrc.json index af5be347d6..7563e2e317 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,7 +26,17 @@ "no-throw-literal": "warn", "no-unsafe-finally": "warn", "no-unused-labels": "warn", - "no-restricted-globals": ["warn", "name", "length", "event", "closed", "external", "status", "origin", "orientation"], // non-complete list of globals that are easy to access unintentionally + "no-restricted-globals": [ + "warn", + "name", + "length", + "event", + "closed", + "external", + "status", + "origin", + "orientation" + ], // non-complete list of globals that are easy to access unintentionally "no-var": "warn", "jsdoc/no-types": "warn", "semi": "off", @@ -259,6 +269,50 @@ "rules": { "jsdoc/no-types": "off" } + }, + { + "files": [ + "**/vscode.d.ts", + "**/vscode.proposed.d.ts" + ], + "rules": { + "vscode-dts-create-func": "warn", + "vscode-dts-literal-or-types": "warn", + "vscode-dts-interface-naming": "warn", + "vscode-dts-event-naming": [ + "warn", + { + "allowed": [ + "onCancellationRequested", + "event" + ], + "verbs": [ + "accept", + "change", + "close", + "collapse", + "create", + "delete", + "dispose", + "end", + "expand", + "hide", + "open", + "override", + "receive", + "register", + "rename", + "save", + "send", + "start", + "terminate", + "trigger", + "unregister", + "write" + ] + } + ] + } } ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index b0c78892d1..e3ad9c874e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -95,7 +95,9 @@ "webRoot": "${workspaceFolder}", // Settings for js-debug: "pauseForSourceMap": false, - "outFiles": ["${workspaceFolder}/out/**/*.js"], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], }, { "type": "node", @@ -197,7 +199,7 @@ "type": "node", "request": "launch", "name": "Run Unit Tests", - "program": "${workspaceFolder}/test/electron/index.js", + "program": "${workspaceFolder}/test/unit/electron/index.js", "runtimeExecutable": "${workspaceFolder}/.build/electron/Azure Data Studio.app/Contents/MacOS/Electron", "windows": { "runtimeExecutable": "${workspaceFolder}/.build/electron/azuredatastudio.exe" diff --git a/.vscode/searches/ts36031.code-search b/.vscode/searches/ts36031.code-search index fb6cf8a431..a232dfbd34 100644 --- a/.vscode/searches/ts36031.code-search +++ b/.vscode/searches/ts36031.code-search @@ -1,7 +1,6 @@ # Query: \\w+\\?\\..+![(.[] # Flags: RegExp # ContextLines: 2 - src/vs/base/browser/ui/tree/asyncDataTree.ts: 270 } : undefined, 271 isChecked: options.ariaProvider!.isChecked ? (e) => { diff --git a/.yarnrc b/.yarnrc index 85baaa63a7..7808166004 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.1.6" +target "7.1.11" runtime "electron" diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index 7c41748f57..12eadf7de0 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -44,15 +44,16 @@ async function doesAssetExist(blobService: azure.BlobService, quality: string, b return existsResult.exists; } -async function uploadBlob(blobService: azure.BlobService, quality: string, blobName: string, file: string): Promise { +async function uploadBlob(blobService: azure.BlobService, quality: string, blobName: string, filePath: string, fileName: string): Promise { const blobOptions: azure.BlobService.CreateBlockBlobRequestOptions = { contentSettings: { - contentType: mime.lookup(file), + contentType: mime.lookup(filePath), + contentDisposition: `attachment; filename="${fileName}"`, cacheControl: 'max-age=31536000, public' } }; - await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, file, blobOptions, err => err ? e(err) : c())); + await new Promise((c, e) => blobService.createBlockBlobFromLocalFile(quality, blobName, filePath, blobOptions, err => err ? e(err) : c())); } function getEnv(name: string): string { @@ -66,24 +67,24 @@ function getEnv(name: string): string { } async function main(): Promise { - const [, , platform, type, name, file] = process.argv; + const [, , platform, type, fileName, filePath] = process.argv; const quality = getEnv('VSCODE_QUALITY'); const commit = getEnv('BUILD_SOURCEVERSION'); console.log('Creating asset...'); - const stat = await new Promise((c, e) => fs.stat(file, (err, stat) => err ? e(err) : c(stat))); + const stat = await new Promise((c, e) => fs.stat(filePath, (err, stat) => err ? e(err) : c(stat))); const size = stat.size; console.log('Size:', size); - const stream = fs.createReadStream(file); + const stream = fs.createReadStream(filePath); const [sha1hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); console.log('SHA1:', sha1hash); console.log('SHA256:', sha256hash); - const blobName = commit + '/' + name; + const blobName = commit + '/' + fileName; const storageAccount = process.env['AZURE_STORAGE_ACCOUNT_2']!; const blobService = azure.createBlobService(storageAccount, process.env['AZURE_STORAGE_ACCESS_KEY_2']!) @@ -98,7 +99,7 @@ async function main(): Promise { console.log('Uploading blobs to Azure storage...'); - await uploadBlob(blobService, quality, blobName, file); + await uploadBlob(blobService, quality, blobName, filePath, fileName); console.log('Blobs successfully uploaded.'); diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 409f8631af..afdd39fcaf 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -44,6 +44,9 @@ steps: - script: | ./scripts/test.sh --tfs "Unit Tests" displayName: Run Unit Tests +- script: | + yarn test-browser --browser chromium --browser webkit + displayName: Run Unit Tests (Browsers) # - script: | {{SQL CARBON EDIT}} remove step # ./scripts/test-integration.sh --tfs "Integration Tests" # displayName: Run Integration Tests diff --git a/build/azure-pipelines/darwin/entitlements.plist b/build/azure-pipelines/darwin/entitlements.plist new file mode 100644 index 0000000000..46f6756611 --- /dev/null +++ b/build/azure-pipelines/darwin/entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 77f4af38c8..55f55e0ead 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -44,6 +44,13 @@ steps: git config user.email "vscode@microsoft.com" git config user.name "VSCode" + + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain displayName: Prepare tooling - script: | @@ -114,30 +121,25 @@ steps: displayName: Run integration tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) -# Web Smoke Tests disabled due to https://github.com/microsoft/vscode/issues/80308 -# - script: | -# set -e -# cd test/smoke -# yarn compile -# cd - -# yarn smoketest --web --headless -# continueOnError: true -# displayName: Run web smoke tests -# condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - script: | set -e cd test/smoke yarn compile cd - + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin" \ yarn smoketest --web --headless continueOnError: true - displayName: Run smoke tests + displayName: Run web smoke tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e - pushd ../VSCode-darwin && zip -r -X -y ../VSCode-darwin.zip * && popd + codesign -s 99FM488X57 --deep --force --options runtime --entitlements build/azure-pipelines/darwin/entitlements.plist $(agent.builddirectory)/VSCode-darwin/*.app + displayName: Set Hardened Entitlements + +- script: | + set -e + pushd $(agent.builddirectory)/VSCode-darwin && zip -r -X -y $(agent.builddirectory)/VSCode-darwin.zip * && popd displayName: Archive build - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 @@ -151,13 +153,53 @@ steps: { "keyCode": "CP-401337-Apple", "operationSetCode": "MacAppDeveloperSign", - "parameters": [ ], + "parameters": [ + { + "parameterName": "Hardening", + "parameterValue": "--options=runtime" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 60 + displayName: Codesign + +- script: | + zip -d $(agent.builddirectory)/VSCode-darwin.zip "*.pkg" + displayName: Clean Archive + +- script: | + APP_ROOT=$(agent.builddirectory)/VSCode-darwin + APP_NAME="`ls $APP_ROOT | head -n 1`" + BUNDLE_IDENTIFIER=$(node -p "require(\"$APP_ROOT/$APP_NAME/Contents/Resources/app/product.json\").darwinBundleIdentifier") + echo "##vso[task.setvariable variable=BundleIdentifier]$BUNDLE_IDENTIFIER" + displayName: Export bundle identifier + +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: 'ESRP CodeSign' + FolderPath: '$(agent.builddirectory)' + Pattern: 'VSCode-darwin.zip' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401337-Apple", + "operationSetCode": "MacAppNotarize", + "parameters": [ + { + "parameterName": "BundleId", + "parameterValue": "$(BundleIdentifier)" + } + ], "toolName": "sign", "toolVersion": "1.0" } ] SessionTimeout: 120 - displayName: Codesign + displayName: Notarization - script: | set -e diff --git a/build/azure-pipelines/darwin/publish.sh b/build/azure-pipelines/darwin/publish.sh index a8067a5eef..58f110c5df 100755 --- a/build/azure-pipelines/darwin/publish.sh +++ b/build/azure-pipelines/darwin/publish.sh @@ -1,9 +1,6 @@ #!/usr/bin/env bash set -e -# remove pkg from archive -zip -d ../VSCode-darwin.zip "*.pkg" - # publish the build node build/azure-pipelines/common/createAsset.js \ darwin \ diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index a661b9e5d5..e5349d097d 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -52,6 +52,9 @@ steps: - script: | DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" displayName: Run Unit Tests +- script: | + DISPLAY=:10 yarn test-browser --browser chromium + displayName: Run Unit Tests (Browser) # - script: | {{SQL CARBON EDIT}} remove step # DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" # displayName: Run Integration Tests diff --git a/build/azure-pipelines/linux/xvfb.init b/build/azure-pipelines/linux/xvfb.init index 4d77d253a2..2365c09f3a 100644 --- a/build/azure-pipelines/linux/xvfb.init +++ b/build/azure-pipelines/linux/xvfb.init @@ -19,7 +19,7 @@ [ "${NETWORKING}" = "no" ] && exit 0 PROG="/usr/bin/Xvfb" -PROG_OPTIONS=":10 -ac" +PROG_OPTIONS=":10 -ac -screen 0 1024x768x24" PROG_OUTPUT="/tmp/Xvfb.out" case "$1" in @@ -50,4 +50,4 @@ case "$1" in exit 1 esac -exit $RETVAL \ No newline at end of file +exit $RETVAL diff --git a/build/azure-pipelines/win32/ESRPClient/packages.config b/build/azure-pipelines/win32/ESRPClient/packages.config index d7a6f144f4..c10bed1412 100644 --- a/build/azure-pipelines/win32/ESRPClient/packages.config +++ b/build/azure-pipelines/win32/ESRPClient/packages.config @@ -1,4 +1,4 @@ - + diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index a7938ffb47..fc80b0bef1 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -49,6 +49,9 @@ steps: - powershell: | .\scripts\test.bat --tfs "Unit Tests" displayName: Run Unit Tests +- powershell: | + yarn test-browser --browser chromium --browser webkit + displayName: Run Unit Tests (Browser) # - powershell: | {{SQL CARBON EDIT}} remove step # .\scripts\test-integration.bat --tfs "Integration Tests" # displayName: Run Integration Tests diff --git a/build/azure-pipelines/win32/sign.ps1 b/build/azure-pipelines/win32/sign.ps1 index 00c4d42d9d..840cbe4071 100644 --- a/build/azure-pipelines/win32/sign.ps1 +++ b/build/azure-pipelines/win32/sign.ps1 @@ -67,4 +67,4 @@ $Input = Create-TmpJson @{ $Output = [System.IO.Path]::GetTempFileName() $ScriptPath = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent -& "$ScriptPath\ESRPClient\packages\EsrpClient.1.0.27\tools\ESRPClient.exe" Sign -a $Auth -p $Policy -i $Input -o $Output \ No newline at end of file +& "$ScriptPath\ESRPClient\packages\Microsoft.ESRPClient.1.2.25\tools\ESRPClient.exe" Sign -a $Auth -p $Policy -i $Input -o $Output diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 993db90999..188b5df619 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -227,8 +227,13 @@ function toExternalDTS(contents) { if (line.indexOf('declare namespace monaco.') === 0) { lines[i] = line.replace('declare namespace monaco.', 'export namespace '); } + + if (line.indexOf('declare let MonacoEnvironment') === 0) { + lines[i] = `declare global {\n let MonacoEnvironment: Environment | undefined;\n}`; + // lines[i] = line.replace('declare namespace monaco.', 'export namespace '); + } } - return lines.join('\n'); + return lines.join('\n').replace(/\n\n\n+/g, '\n\n'); } function filterStream(testFunc) { diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 4d36ca661c..429d545a8a 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -33,6 +33,7 @@ const all = [ 'scripts/**/*', 'src/**/*', 'test/**/*', + '!test/**/out/**', '!**/node_modules/**' ]; @@ -53,8 +54,7 @@ const indentationFilter = [ '!src/vs/base/common/marked/marked.js', '!src/vs/base/node/terminateProcess.sh', '!src/vs/base/node/cpuUsage.sh', - '!test/assert.js', - '!build/testSetup.js', + '!test/unit/assert.js', // except specific folders '!test/automation/out/**', @@ -84,7 +84,7 @@ const indentationFilter = [ '!src/vs/*/**/*.d.ts', '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns}', + '!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns,plist}', '!build/{lib,download}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', diff --git a/build/lib/eslint/vscode-dts-create-func.js b/build/lib/eslint/vscode-dts-create-func.js new file mode 100644 index 0000000000..facc0a2bd2 --- /dev/null +++ b/build/lib/eslint/vscode-dts-create-func.js @@ -0,0 +1,35 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); +module.exports = new class ApiLiteralOrTypes { + constructor() { + this.meta = { + docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' }, + messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', } + }; + } + create(context) { + return { + ['TSDeclareFunction Identifier[name=/create.*/]']: (node) => { + var _a; + const decl = node.parent; + if (((_a = decl.returnType) === null || _a === void 0 ? void 0 : _a.typeAnnotation.type) !== experimental_utils_1.AST_NODE_TYPES.TSTypeReference) { + return; + } + if (decl.returnType.typeAnnotation.typeName.type !== experimental_utils_1.AST_NODE_TYPES.Identifier) { + return; + } + const ident = decl.returnType.typeAnnotation.typeName.name; + if (ident === 'Promise' || ident === 'Thenable') { + context.report({ + node, + messageId: 'sync' + }); + } + } + }; + } +}; diff --git a/build/lib/eslint/vscode-dts-create-func.ts b/build/lib/eslint/vscode-dts-create-func.ts new file mode 100644 index 0000000000..1d93dc4dfb --- /dev/null +++ b/build/lib/eslint/vscode-dts-create-func.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; + +export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' }, + messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + return { + ['TSDeclareFunction Identifier[name=/create.*/]']: (node: any) => { + + const decl = (node).parent; + + if (decl.returnType?.typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) { + return; + } + if (decl.returnType.typeAnnotation.typeName.type !== AST_NODE_TYPES.Identifier) { + return; + } + + const ident = decl.returnType.typeAnnotation.typeName.name; + if (ident === 'Promise' || ident === 'Thenable') { + context.report({ + node, + messageId: 'sync' + }); + } + } + }; + } +}; diff --git a/build/lib/eslint/vscode-dts-event-naming.js b/build/lib/eslint/vscode-dts-event-naming.js new file mode 100644 index 0000000000..0b399e9539 --- /dev/null +++ b/build/lib/eslint/vscode-dts-event-naming.js @@ -0,0 +1,81 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var _a; +const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); +module.exports = new (_a = class ApiEventNaming { + constructor() { + this.meta = { + docs: { + url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming' + }, + messages: { + naming: 'Event names must follow this patten: `on[Did|Will]`', + verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration', + subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API', + unknown: 'UNKNOWN event declaration, lint-rule needs tweaking' + } + }; + } + create(context) { + const config = context.options[0]; + const allowed = new Set(config.allowed); + const verbs = new Set(config.verbs); + return { + ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node) => { + var _a, _b; + const def = (_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.parent; + let ident; + if ((def === null || def === void 0 ? void 0 : def.type) === experimental_utils_1.AST_NODE_TYPES.Identifier) { + ident = def; + } + else if (((def === null || def === void 0 ? void 0 : def.type) === experimental_utils_1.AST_NODE_TYPES.TSPropertySignature || (def === null || def === void 0 ? void 0 : def.type) === experimental_utils_1.AST_NODE_TYPES.ClassProperty) && def.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { + ident = def.key; + } + if (!ident) { + // event on unknown structure... + return context.report({ + node, + message: 'unknown' + }); + } + if (allowed.has(ident.name)) { + // configured exception + return; + } + const match = ApiEventNaming._nameRegExp.exec(ident.name); + if (!match) { + context.report({ + node: ident, + messageId: 'naming' + }); + return; + } + // check that is spelled out (configured) as verb + if (!verbs.has(match[2].toLowerCase())) { + context.report({ + node: ident, + messageId: 'verb', + data: { verb: match[2] } + }); + } + // check that a subject (if present) has occurred + if (match[3]) { + const regex = new RegExp(match[3], 'ig'); + const parts = context.getSourceCode().getText().split(regex); + if (parts.length < 3) { + context.report({ + node: ident, + messageId: 'subject', + data: { subject: match[3] } + }); + } + } + } + }; + } + }, + _a._nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/, + _a); diff --git a/build/lib/eslint/vscode-dts-event-naming.ts b/build/lib/eslint/vscode-dts-event-naming.ts new file mode 100644 index 0000000000..6e23562628 --- /dev/null +++ b/build/lib/eslint/vscode-dts-event-naming.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; + +export = new class ApiEventNaming implements eslint.Rule.RuleModule { + + private static _nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/; + + readonly meta: eslint.Rule.RuleMetaData = { + docs: { + url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming' + }, + messages: { + naming: 'Event names must follow this patten: `on[Did|Will]`', + verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration', + subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API', + unknown: 'UNKNOWN event declaration, lint-rule needs tweaking' + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + const config = <{ allowed: string[], verbs: string[] }>context.options[0]; + const allowed = new Set(config.allowed); + const verbs = new Set(config.verbs); + + return { + ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node: any) => { + + const def = (node).parent?.parent?.parent; + let ident: TSESTree.Identifier | undefined; + + if (def?.type === AST_NODE_TYPES.Identifier) { + ident = def; + + } else if ((def?.type === AST_NODE_TYPES.TSPropertySignature || def?.type === AST_NODE_TYPES.ClassProperty) && def.key.type === AST_NODE_TYPES.Identifier) { + ident = def.key; + } + + if (!ident) { + // event on unknown structure... + return context.report({ + node, + message: 'unknown' + }); + } + + if (allowed.has(ident.name)) { + // configured exception + return; + } + + const match = ApiEventNaming._nameRegExp.exec(ident.name); + if (!match) { + context.report({ + node: ident, + messageId: 'naming' + }); + return; + } + + // check that is spelled out (configured) as verb + if (!verbs.has(match[2].toLowerCase())) { + context.report({ + node: ident, + messageId: 'verb', + data: { verb: match[2] } + }); + } + + // check that a subject (if present) has occurred + if (match[3]) { + const regex = new RegExp(match[3], 'ig'); + const parts = context.getSourceCode().getText().split(regex); + if (parts.length < 3) { + context.report({ + node: ident, + messageId: 'subject', + data: { subject: match[3] } + }); + } + } + } + }; + } +}; + diff --git a/build/lib/eslint/vscode-dts-interface-naming.js b/build/lib/eslint/vscode-dts-interface-naming.js new file mode 100644 index 0000000000..4b63aa203c --- /dev/null +++ b/build/lib/eslint/vscode-dts-interface-naming.js @@ -0,0 +1,30 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +var _a; +module.exports = new (_a = class ApiInterfaceNaming { + constructor() { + this.meta = { + messages: { + naming: 'Interfaces must not be prefixed with uppercase `I`', + } + }; + } + create(context) { + return { + ['TSInterfaceDeclaration Identifier']: (node) => { + const name = node.name; + if (ApiInterfaceNaming._nameRegExp.test(name)) { + context.report({ + node, + messageId: 'naming' + }); + } + } + }; + } + }, + _a._nameRegExp = /I[A-Z]/, + _a); diff --git a/build/lib/eslint/vscode-dts-interface-naming.ts b/build/lib/eslint/vscode-dts-interface-naming.ts new file mode 100644 index 0000000000..01f18b621c --- /dev/null +++ b/build/lib/eslint/vscode-dts-interface-naming.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; + +export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule { + + private static _nameRegExp = /I[A-Z]/; + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + naming: 'Interfaces must not be prefixed with uppercase `I`', + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + return { + ['TSInterfaceDeclaration Identifier']: (node: any) => { + + const name = (node).name; + if (ApiInterfaceNaming._nameRegExp.test(name)) { + context.report({ + node, + messageId: 'naming' + }); + } + } + }; + } +}; + diff --git a/build/lib/eslint/vscode-dts-literal-or-types.js b/build/lib/eslint/vscode-dts-literal-or-types.js new file mode 100644 index 0000000000..b2aadf2bf9 --- /dev/null +++ b/build/lib/eslint/vscode-dts-literal-or-types.js @@ -0,0 +1,23 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +module.exports = new class ApiLiteralOrTypes { + constructor() { + this.meta = { + docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' }, + messages: { useEnum: 'Use enums, not literal-or-types', } + }; + } + create(context) { + return { + ['TSTypeAnnotation TSUnionType TSLiteralType']: (node) => { + context.report({ + node: node, + messageId: 'useEnum' + }); + } + }; + } +}; diff --git a/build/lib/eslint/vscode-dts-literal-or-types.ts b/build/lib/eslint/vscode-dts-literal-or-types.ts new file mode 100644 index 0000000000..5258bfd455 --- /dev/null +++ b/build/lib/eslint/vscode-dts-literal-or-types.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 * as eslint from 'eslint'; + +export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' }, + messages: { useEnum: 'Use enums, not literal-or-types', } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + ['TSTypeAnnotation TSUnionType TSLiteralType']: (node: any) => { + context.report({ + node: node, + messageId: 'useEnum' + }); + } + }; + } +}; diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 174083dac8..31446bfe4d 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -142,6 +142,10 @@ "name": "vs/workbench/contrib/search", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/searchEditor", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/snippets", "project": "vscode-workbench" @@ -321,6 +325,10 @@ { "name": "vs/workbench/services/userDataSync", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/timeline", + "project": "vscode-workbench" } ] } diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index 7a0e12a615..d093dfda8c 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -38,6 +38,7 @@ const CORE_TYPES = [ 'group', 'groupEnd', 'table', + 'assert', 'Error', 'String', 'throws', @@ -60,7 +61,7 @@ const RULES = [ }, // Common: vs/base/common/platform.ts { - target: '**/vs/base/common/platform.ts', + target: '**/{vs,sql}/base/common/platform.ts', allowedTypes: [ ...CORE_TYPES, // Safe access to postMessage() and friends @@ -74,7 +75,7 @@ const RULES = [ }, // Common: vs/workbench/api/common/extHostExtensionService.ts { - target: '**/vs/workbench/api/common/extHostExtensionService.ts', + target: '**/{vs,sql}/workbench/api/common/extHostExtensionService.ts', allowedTypes: [ ...CORE_TYPES, // Safe access to global @@ -102,6 +103,14 @@ const RULES = [ '@types/node' // no node.js ] }, + // Browser (editor contrib) + { + target: '**/src/{vs,sql}/editor/contrib/**', + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + '@types/node' // no node.js + ] + }, // node.js { target: '**/{vs,sql}/**/node/**', diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 827064c501..6c52cab5b2 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -39,6 +39,7 @@ const CORE_TYPES = [ 'group', 'groupEnd', 'table', + 'assert', 'Error', 'String', 'throws', @@ -64,7 +65,7 @@ const RULES = [ // Common: vs/base/common/platform.ts { - target: '**/vs/base/common/platform.ts', + target: '**/{vs,sql}/base/common/platform.ts', allowedTypes: [ ...CORE_TYPES, @@ -80,7 +81,7 @@ const RULES = [ // Common: vs/workbench/api/common/extHostExtensionService.ts { - target: '**/vs/workbench/api/common/extHostExtensionService.ts', + target: '**/{vs,sql}/workbench/api/common/extHostExtensionService.ts', allowedTypes: [ ...CORE_TYPES, @@ -112,6 +113,15 @@ const RULES = [ ] }, + // Browser (editor contrib) + { + target: '**/src/{vs,sql}/editor/contrib/**', + allowedTypes: CORE_TYPES, + disallowedDefinitions: [ + '@types/node' // no node.js + ] + }, + // node.js { target: '**/{vs,sql}/**/node/**', diff --git a/build/testSetup.js b/build/lib/testSetup.js similarity index 100% rename from build/testSetup.js rename to build/lib/testSetup.js diff --git a/build/testSetup.ts b/build/lib/testSetup.ts similarity index 100% rename from build/testSetup.ts rename to build/lib/testSetup.ts diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 1f90daf38e..e7129d5ec0 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -3,12 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +declare let MonacoEnvironment: monaco.Environment | undefined; + declare namespace monaco { - // THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY. - export type Thenable = PromiseLike; + export interface Environment { + baseUrl?: string; + getWorker?(workerId: string, label: string): Worker; + getWorkerUrl?(workerId: string, label: string): string; + } + export interface IDisposable { dispose(): void; } diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 86f41ba3dc..f202636c87 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -72,4 +72,5 @@ runtime "${runtime}"`; yarnInstall(`build`); // node modules required for build yarnInstall('test/automation'); // node modules required for smoketest yarnInstall('test/smoke'); // node modules required for smoketest +yarnInstall('test/integration/browser'); // node modules required for integration yarnInstallBuildDependencies(); // node modules for watching, specific to host node version, not electron diff --git a/build/package.json b/build/package.json index d005a4f441..b8eb76f4e2 100644 --- a/build/package.json +++ b/build/package.json @@ -50,7 +50,7 @@ "rollup-plugin-node-resolve": "^5.2.0", "service-downloader": "github:anthonydresser/service-downloader#0.1.7", "terser": "4.3.8", - "typescript": " 3.8.0-beta", + "typescript": "^3.8.1-rc", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/yarn.lock b/build/yarn.lock index b58fe6a1c8..60d5a6dd62 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -3823,16 +3823,16 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -"typescript@ 3.8.0-beta": - version "3.8.0-beta" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-beta.tgz#acdcaf9f24c7e20b1ff0a6329d1e8d63691e2e13" - integrity sha512-mQEmQUJg0CQBhf/GSVnGscKv/jrKsrLxE01AhdjYmBNoXX2Iah3i38ufxXByXacK6Fc5Nr9oMz7MjpjgddiknA== - typescript@^3.0.1: version "3.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== +typescript@^3.8.1-rc: + version "3.8.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" + integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== + typical@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" diff --git a/cgmanifest.json b/cgmanifest.json index c102a04f70..40d6e42aa7 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" + "commitHash": "e4745133a1d3745f066e068b8033c6a269b59caf" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "76.0.3809.146" + "version": "78.0.3904.130" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" + "commitHash": "787378879acfb212ed4ff824bf9f767a24a5cb43a" } }, "isOnlyProductionDependency": true, - "version": "12.4.0" + "version": "12.8.1" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "19c705ab80cd6fdccca3d65803ec2c4addb9540a" + "commitHash": "d17dfabfcba7bd0bc994b8dac5f5d2000bef572c" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.1.6" + "version": "7.1.11" }, { "component": { @@ -98,7 +98,7 @@ "git": { "name": "vscode-codicons", "repositoryUrl": "https://github.com/microsoft/vscode-codicons", - "commitHash": "65d11e0839d0ce09faa1a159dc0b3c0bd1aa50be" + "commitHash": "f0caa623812a8ed5059516277675b4158d4c4867" } }, "license": "MIT and Creative Commons Attribution 4.0", diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index 58e59d72ee..015e32e801 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -2,6 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "description": "Configures an attached to container", "allowComments": true, + "allowTrailingCommas": true, "type": "object", "definitions": { "attachContainer": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 292fe62993..0d47b27bec 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -2,6 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "description": "Defines a dev container", "allowComments": true, + "allowTrailingCommas": true, "type": "object", "definitions": { "devContainerCommon": { @@ -28,7 +29,9 @@ "type": "array", "description": "Ports that are forwarded from the container to the local machine.", "items": { - "type": "integer" + "type": "integer", + "maximum": 65535, + "minimum": 0 } }, "remoteEnv": { diff --git a/extensions/git/package.json b/extensions/git/package.json index 2ba6515e96..c1cd8b5135 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -26,6 +26,11 @@ }, "contributes": { "commands": [ + { + "command": "git.setLogLevel", + "title": "%command.setLogLevel%", + "category": "Git" + }, { "command": "git.clone", "title": "%command.clone%", @@ -1760,6 +1765,7 @@ }, "dependencies": { "byline": "^5.0.0", + "dayjs": "1.8.19", "file-type": "^7.2.0", "iconv-lite": "^0.4.24", "jschardet": "2.1.1", @@ -1774,6 +1780,9 @@ "@types/mocha": "2.2.43", "@types/node": "^12.11.7", "@types/which": "^1.0.28", - "mocha": "^3.2.0" + "mocha": "^3.2.0", + "mocha-junit-reporter": "^1.23.3", + "mocha-multi-reporters": "^1.1.7", + "vscode": "^1.1.36" } } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5c357b40eb..f3d7551965 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -1,6 +1,7 @@ { "displayName": "Git", "description": "Git SCM Integration", + "command.setLogLevel": "Set Log Level...", "command.clone": "Clone", "command.init": "Initialize Repository", "command.openRepository": "Open Repository", diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index d5d2cb0011..03318f6481 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl } from 'vscode'; import { mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -202,6 +202,10 @@ export class ApiRepository implements Repository { log(options?: LogOptions): Promise { return this._repository.log(options); } + + commit(message: string, opts?: CommitOptions): Promise { + return this._repository.commit(message, opts); + } } export class ApiGit implements Git { diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 16ec429152..72b986ee7b 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -41,7 +41,9 @@ export interface Commit { readonly hash: string; readonly message: string; readonly parents: string[]; - readonly authorEmail?: string | undefined; + readonly authorDate?: Date; + readonly authorName?: string; + readonly authorEmail?: string; } export interface Submodule { @@ -119,6 +121,14 @@ export interface LogOptions { readonly maxEntries?: number; } +export interface CommitOptions { + all?: boolean | 'tracked'; + amend?: boolean; + signoff?: boolean; + signCommit?: boolean; + empty?: boolean; +} + export interface Repository { readonly rootUri: Uri; @@ -174,6 +184,8 @@ export interface Repository { blame(path: string): Promise; log(options?: LogOptions): Promise; + + commit(message: string, opts?: CommitOptions): Promise; } export type APIState = 'uninitialized' | 'initialized'; diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 6771bda760..6264b22232 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -9,13 +9,14 @@ import * as path from 'path'; import { commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder } from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; -import { Branch, GitErrorCodes, Ref, RefType, Status } from './api/git'; -import { CommitOptions, ForcePushMode, Git, Stash } from './git'; +import { Branch, GitErrorCodes, Ref, RefType, Status, CommitOptions } from './api/git'; +import { ForcePushMode, Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri } from './uri'; import { grep, isDescendant, pathEquals } from './util'; +import { Log, LogLevel } from './log'; const localize = nls.loadMessageBundle(); @@ -252,6 +253,36 @@ export class CommandCenter { }); } + @command('git.setLogLevel') + async setLogLevel(): Promise { + const createItem = (logLevel: LogLevel) => ({ + label: LogLevel[logLevel], + logLevel, + description: Log.logLevel === logLevel ? localize('current', "Current") : undefined + }); + + const items = [ + createItem(LogLevel.Trace), + createItem(LogLevel.Debug), + createItem(LogLevel.Info), + createItem(LogLevel.Warning), + createItem(LogLevel.Error), + createItem(LogLevel.Critical), + createItem(LogLevel.Off) + ]; + + const choice = await window.showQuickPick(items, { + placeHolder: localize('select log level', "Select log level") + }); + + if (!choice) { + return; + } + + Log.logLevel = choice.logLevel; + this.outputChannel.appendLine(localize('changed', "Log level changed to: {0}", LogLevel[Log.logLevel])); + } + @command('git.refresh', { repository: true }) async refresh(repository: Repository): Promise { await repository.status(); @@ -1292,6 +1323,9 @@ export class CommandCenter { } const enableSmartCommit = config.get('enableSmartCommit') === true; + const enableCommitSigning = config.get('enableCommitSigning') === true; + const noStagedChanges = repository.indexGroup.resourceStates.length === 0; + const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; if (promptToSaveFilesBeforeCommit !== 'never') { let documents = workspace.textDocuments @@ -1312,17 +1346,13 @@ export class CommandCenter { if (pick === saveAndCommit) { await Promise.all(documents.map(d => d.save())); - await repository.add([]); + await repository.add(documents.map(d => d.uri)); } else if (pick !== commit) { return false; // do not commit on cancel } } } - const enableCommitSigning = config.get('enableCommitSigning') === true; - const noStagedChanges = repository.indexGroup.resourceStates.length === 0; - const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; - // no changes, and the user has not configured to commit all in this case if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit) { const suggestSmartCommit = config.get('suggestSmartCommit') === true; @@ -1590,7 +1620,7 @@ export class CommandCenter { const rawBranchName = defaultName || await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), - prompt: localize('provide branch name', "Please provide a branch name"), + prompt: localize('provide branch name', "Please provide a new branch name"), value: initialValue, ignoreFocusOut: true, validateInput: (name: string) => { @@ -2301,6 +2331,23 @@ export class CommandCenter { return result && result.stash; } + @command('git.openDiff', { repository: false }) + async openDiff(uri: Uri, lhs: string, rhs: string) { + const basename = path.basename(uri.fsPath); + + let title; + if ((lhs === 'HEAD' || lhs === '~') && rhs === '') { + title = `${basename} (Working Tree)`; + } + else if (lhs === 'HEAD' && rhs === '~') { + title = `${basename} (Index)`; + } else { + title = `${basename} (${lhs.endsWith('^') ? `${lhs.substr(0, 8)}^` : lhs.substr(0, 8)}) \u27f7 ${basename} (${rhs.endsWith('^') ? `${rhs.substr(0, 8)}^` : rhs.substr(0, 8)})`; + } + + return commands.executeCommand('vscode.diff', toGitUri(uri, lhs), rhs === '' ? uri : toGitUri(uri, rhs), title); + } + private createCommand(id: string, key: string, method: Function, options: CommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 6be990e828..19567d741b 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -12,10 +12,10 @@ import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter } from './util'; -import { CancellationToken, Progress } from 'vscode'; +import { CancellationToken, Progress, Uri } from 'vscode'; import { URI } from 'vscode-uri'; import { detectEncoding } from './encoding'; -import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status } from './api/git'; +import { Ref, RefType, Branch, Remote, GitErrorCodes, LogOptions, Change, Status, CommitOptions } from './api/git'; import * as byline from 'byline'; import { StringDecoder } from 'string_decoder'; @@ -45,6 +45,15 @@ interface MutableRemote extends Remote { isReadOnly: boolean; } +// TODO[ECA]: Move to git.d.ts once we are good with the api +/** + * Log file options. + */ +export interface LogFileOptions { + /** Max number of log entries to retrieve. If not specified, the default is 32. */ + readonly maxEntries?: number; +} + function parseVersion(raw: string): string { return raw.replace(/^git version /, ''); } @@ -318,7 +327,13 @@ function getGitErrorCode(stderr: string): string | undefined { return undefined; } -const COMMIT_FORMAT = '%H\n%ae\n%P\n%B'; +// https://github.com/microsoft/vscode/issues/89373 +// https://github.com/git-for-windows/git/issues/2478 +function sanitizePath(path: string): string { + return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`); +} + +const COMMIT_FORMAT = '%H\n%aN\n%aE\n%at\n%P\n%B'; export class Git { @@ -487,6 +502,10 @@ export class Git { LANG: 'en_US.UTF-8' }); + if (options.cwd) { + options.cwd = sanitizePath(options.cwd); + } + if (options.log !== false) { this.log(`> git ${args.join(' ')}\n`); } @@ -503,7 +522,9 @@ export interface Commit { hash: string; message: string; parents: string[]; - authorEmail?: string | undefined; + authorDate?: Date; + authorName?: string; + authorEmail?: string; } export class GitStatusParser { @@ -634,14 +655,43 @@ export function parseGitmodules(raw: string): Submodule[] { return result; } -export function parseGitCommit(raw: string): Commit | null { - const match = /^([0-9a-f]{40})\n(.*)\n(.*)(\n([^]*))?$/m.exec(raw.trim()); - if (!match) { - return null; - } +const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm; - const parents = match[3] ? match[3].split(' ') : []; - return { hash: match[1], message: match[5], parents, authorEmail: match[2] }; +export function parseGitCommits(data: string): Commit[] { + let commits: Commit[] = []; + + let ref; + let name; + let email; + let date; + let parents; + let message; + let match; + + do { + match = commitRegex.exec(data); + if (match === null) { + break; + } + + [, ref, name, email, date, parents, message] = match; + + if (message[message.length - 1] === '\n') { + message = message.substr(0, message.length - 1); + } + + // Stop excessive memory usage by using substr -- https://bugs.chromium.org/p/v8/issues/detail?id=2869 + commits.push({ + hash: ` ${ref}`.substr(1), + message: ` ${message}`.substr(1), + parents: parents ? parents.split(' ') : [], + authorDate: new Date(Number(date) * 1000), + authorName: ` ${name}`.substr(1), + authorEmail: ` ${email}`.substr(1) + }); + } while (true); + + return commits; } interface LsTreeElement { @@ -675,14 +725,6 @@ export function parseLsFiles(raw: string): LsFilesElement[] { .map(([, mode, object, stage, file]) => ({ mode, object, stage, file })); } -export interface CommitOptions { - all?: boolean | 'tracked'; - amend?: boolean; - signoff?: boolean; - signCommit?: boolean; - empty?: boolean; -} - export interface PullOptions { unshallow?: boolean; tags?: boolean; @@ -760,38 +802,28 @@ export class Repository { async log(options?: LogOptions): Promise { const maxEntries = options && typeof options.maxEntries === 'number' && options.maxEntries > 0 ? options.maxEntries : 32; - const args = ['log', '-' + maxEntries, `--pretty=format:${COMMIT_FORMAT}%x00%x00`]; + const args = ['log', '-' + maxEntries, `--format:${COMMIT_FORMAT}`, '-z']; - const gitResult = await this.run(args); - if (gitResult.exitCode) { + const result = await this.run(args); + if (result.exitCode) { // An empty repo return []; } - const s = gitResult.stdout; - const result: Commit[] = []; - let index = 0; - while (index < s.length) { - let nextIndex = s.indexOf('\x00\x00', index); - if (nextIndex === -1) { - nextIndex = s.length; - } + return parseGitCommits(result.stdout); + } - let entry = s.substr(index, nextIndex - index); - if (entry.startsWith('\n')) { - entry = entry.substring(1); - } + async logFile(uri: Uri, options?: LogFileOptions): Promise { + const maxEntries = options?.maxEntries ?? 32; + const args = ['log', `-${maxEntries}`, `--format=${COMMIT_FORMAT}`, '-z', '--', uri.fsPath]; - const commit = parseGitCommit(entry); - if (!commit) { - break; - } - - result.push(commit); - index = nextIndex + 2; + const result = await this.run(args); + if (result.exitCode) { + // No file history, e.g. a new file or untracked + return []; } - return result; + return parseGitCommits(result.stdout); } async bufferString(object: string, encoding: string = 'utf8', autoGuessEncoding = false): Promise { @@ -857,12 +889,12 @@ export class Repository { } async lstree(treeish: string, path: string): Promise { - const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', path]); + const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', sanitizePath(path)]); return parseLsTree(stdout); } async lsfiles(path: string): Promise { - const { stdout } = await this.run(['ls-files', '--stage', '--', path]); + const { stdout } = await this.run(['ls-files', '--stage', '--', sanitizePath(path)]); return parseLsFiles(stdout); } @@ -956,7 +988,7 @@ export class Repository { return await this.diffFiles(false); } - const args = ['diff', '--', path]; + const args = ['diff', '--', sanitizePath(path)]; const result = await this.run(args); return result.stdout; } @@ -969,7 +1001,7 @@ export class Repository { return await this.diffFiles(false, ref); } - const args = ['diff', ref, '--', path]; + const args = ['diff', ref, '--', sanitizePath(path)]; const result = await this.run(args); return result.stdout; } @@ -982,7 +1014,7 @@ export class Repository { return await this.diffFiles(true); } - const args = ['diff', '--cached', '--', path]; + const args = ['diff', '--cached', '--', sanitizePath(path)]; const result = await this.run(args); return result.stdout; } @@ -995,7 +1027,7 @@ export class Repository { return await this.diffFiles(true, ref); } - const args = ['diff', '--cached', ref, '--', path]; + const args = ['diff', '--cached', ref, '--', sanitizePath(path)]; const result = await this.run(args); return result.stdout; } @@ -1015,7 +1047,7 @@ export class Repository { return await this.diffFiles(false, range); } - const args = ['diff', range, '--', path]; + const args = ['diff', range, '--', sanitizePath(path)]; const result = await this.run(args); return result.stdout.trim(); @@ -1128,7 +1160,7 @@ export class Repository { args.push('--'); if (paths && paths.length) { - args.push.apply(args, paths); + args.push.apply(args, paths.map(sanitizePath)); } else { args.push('.'); } @@ -1143,13 +1175,13 @@ export class Repository { return; } - args.push(...paths); + args.push(...paths.map(sanitizePath)); await this.run(args); } async stage(path: string, data: string): Promise { - const child = this.stream(['hash-object', '--stdin', '-w', '--path', path], { stdio: [null, null, null] }); + const child = this.stream(['hash-object', '--stdin', '-w', '--path', sanitizePath(path)], { stdio: [null, null, null] }); child.stdin!.end(data, 'utf8'); const { exitCode, stdout } = await exec(child); @@ -1194,7 +1226,7 @@ export class Repository { try { if (paths && paths.length > 0) { - for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { await this.run([...args, '--', ...chunk]); } } else { @@ -1333,7 +1365,7 @@ export class Repository { } async clean(paths: string[]): Promise { - const pathsByGroup = groupBy(paths, p => path.dirname(p)); + const pathsByGroup = groupBy(paths.map(sanitizePath), p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); const limiter = new Limiter(5); @@ -1379,7 +1411,7 @@ export class Repository { } if (paths && paths.length) { - args.push.apply(args, paths); + args.push.apply(args, paths.map(sanitizePath)); } else { args.push('.'); } @@ -1530,11 +1562,8 @@ export class Repository { async blame(path: string): Promise { try { - const args = ['blame']; - args.push(path); - - let result = await this.run(args); - + const args = ['blame', sanitizePath(path)]; + const result = await this.run(args); return result.stdout.trim(); } catch (err) { if (/^fatal: no such path/.test(err.stderr || '')) { @@ -1853,14 +1882,18 @@ export class Repository { } async getCommit(ref: string): Promise { - const result = await this.run(['show', '-s', `--format=${COMMIT_FORMAT}`, ref]); - return parseGitCommit(result.stdout) || Promise.reject('bad commit format'); + const result = await this.run(['show', '-s', `--format=${COMMIT_FORMAT}`, '-z', ref]); + const commits = parseGitCommits(result.stdout); + if (commits.length === 0) { + return Promise.reject('bad commit format'); + } + return commits[0]; } async updateSubmodules(paths: string[]): Promise { const args = ['submodule', 'update', '--']; - for (const chunk of splitInChunks(paths, MAX_CLI_LENGTH)) { + for (const chunk of splitInChunks(paths.map(sanitizePath), MAX_CLI_LENGTH)) { await this.run([...args, ...chunk]); } } diff --git a/extensions/git/src/log.ts b/extensions/git/src/log.ts new file mode 100644 index 0000000000..7f18054bef --- /dev/null +++ b/extensions/git/src/log.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 { Event, EventEmitter } from 'vscode'; + +/** + * The severity level of a log message + */ +export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 +} + +let _logLevel: LogLevel = LogLevel.Info; +const _onDidChangeLogLevel = new EventEmitter(); + +export const Log = { + /** + * Current logging level. + */ + get logLevel(): LogLevel { + return _logLevel; + }, + + /** + * Current logging level. + */ + set logLevel(logLevel: LogLevel) { + if (_logLevel === logLevel) { + return; + } + + _logLevel = logLevel; + _onDidChangeLogLevel.fire(logLevel); + }, + + /** + * An [event](#Event) that fires when the log level has changed. + */ + get onDidChangeLogLevel(): Event { + return _onDidChangeLogLevel.event; + } +}; diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 5e85b3420b..b3f3ae7fdc 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -22,6 +22,7 @@ import { GitExtensionImpl } from './api/extension'; // import * as path from 'path'; // import * as fs from 'fs'; import { createIPCServer, IIPCServer } from './ipc/ipcServer'; +import { GitTimelineProvider } from './timelineProvider'; const deactivateTasks: { (): Promise; }[] = []; @@ -82,7 +83,8 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann new GitContentProvider(model), new GitFileSystemProvider(model), new GitDecorations(model), - new GitProtocolHandler() + new GitProtocolHandler(), + new GitTimelineProvider(model) ); await checkGitVersion(info); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index beafc4afcc..fd49a8024c 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,16 +5,17 @@ import * as fs from 'fs'; import * as path from 'path'; -import { CancellationToken, Command, Disposable, env, Event, EventEmitter, LogLevel, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, Decoration } from 'vscode'; +import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, Decoration } from 'vscode'; import * as nls from 'vscode-nls'; -import { Branch, Change, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; +import { Branch, Change, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions } from './api/git'; import { AutoFetcher } from './autofetch'; import { debounce, memoize, throttle } from './decorators'; -import { Commit, CommitOptions, ForcePushMode, GitError, Repository as BaseRepository, Stash, Submodule } from './git'; +import { Commit, ForcePushMode, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git'; import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent } from './util'; import { IFileWatcher, watch } from './watch'; +import { Log, LogLevel } from './log'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -303,15 +304,22 @@ export const enum Operation { Apply = 'Apply', Blame = 'Blame', Log = 'Log', + LogFile = 'LogFile', } function isReadOnly(operation: Operation): boolean { switch (operation) { - case Operation.Show: - case Operation.GetCommitTemplate: + case Operation.Blame: case Operation.CheckIgnore: + case Operation.Diff: + case Operation.FindTrackingBranches: + case Operation.GetBranch: + case Operation.GetCommitTemplate: case Operation.GetObjectDetails: + case Operation.Log: + case Operation.LogFile: case Operation.MergeBase: + case Operation.Show: return true; default: return false; @@ -457,8 +465,8 @@ class FileEventLogger { private onDotGitFileChange: Event, private outputChannel: OutputChannel ) { - this.logLevelDisposable = env.onDidChangeLogLevel(this.onDidChangeLogLevel, this); - this.onDidChangeLogLevel(env.logLevel); + this.logLevelDisposable = Log.onDidChangeLogLevel(this.onDidChangeLogLevel, this); + this.onDidChangeLogLevel(Log.logLevel); } private onDidChangeLogLevel(level: LogLevel): void { @@ -519,7 +527,7 @@ class DotGitWatcher implements IFileWatcher { this.transientDisposables.push(upstreamWatcher); upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables); } catch (err) { - if (env.logLevel <= LogLevel.Error) { + if (Log.logLevel <= LogLevel.Error) { this.outputChannel.appendLine(`Failed to watch ref '${upstreamPath}', is most likely packed.\n${err.stack || err}`); } } @@ -682,7 +690,7 @@ export class Repository implements Disposable { onDotGitFileChange = dotGitFileWatcher.event; this.disposables.push(dotGitFileWatcher); } catch (err) { - if (env.logLevel <= LogLevel.Error) { + if (Log.logLevel <= LogLevel.Error) { outputChannel.appendLine(`Failed to watch '${this.dotGit}', reverting to legacy API file watched. Some events might be lost.\n${err.stack || err}`); } @@ -867,6 +875,11 @@ export class Repository implements Disposable { return this.run(Operation.Log, () => this.repository.log(options)); } + logFile(uri: Uri, options?: LogFileOptions): Promise { + // TODO: This probably needs per-uri granularity + return this.run(Operation.LogFile, () => this.repository.logFile(uri, options)); + } + @throttle async status(): Promise { await this.run(Operation.Status); diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 54e6aaab5c..c2090574ab 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'mocha'; -import { GitStatusParser, parseGitCommit, parseGitmodules, parseLsTree, parseLsFiles } from '../git'; +import { GitStatusParser, parseGitCommits, parseGitmodules, parseLsTree, parseLsFiles } from '../git'; import * as assert from 'assert'; import { splitInChunks } from '../util'; @@ -189,44 +189,56 @@ suite('git', () => { suite('parseGitCommit', () => { test('single parent commit', function () { const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 +John Doe john.doe@mail.com +1580811030 8e5a374372b8393906c7e380dbb09349c5385554 -This is a commit message.`; +This is a commit message.\x00`; - assert.deepEqual(parseGitCommit(GIT_OUTPUT_SINGLE_PARENT), { + assert.deepEqual(parseGitCommits(GIT_OUTPUT_SINGLE_PARENT), [{ hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', parents: ['8e5a374372b8393906c7e380dbb09349c5385554'], + authorDate: new Date(1580811030000), + authorName: 'John Doe', authorEmail: 'john.doe@mail.com', - }); + }]); }); test('multiple parent commits', function () { const GIT_OUTPUT_MULTIPLE_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 +John Doe john.doe@mail.com +1580811030 8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217 -This is a commit message.`; +This is a commit message.\x00`; - assert.deepEqual(parseGitCommit(GIT_OUTPUT_MULTIPLE_PARENTS), { + assert.deepEqual(parseGitCommits(GIT_OUTPUT_MULTIPLE_PARENTS), [{ hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', parents: ['8e5a374372b8393906c7e380dbb09349c5385554', 'df27d8c75b129ab9b178b386077da2822101b217'], + authorDate: new Date(1580811030000), + authorName: 'John Doe', authorEmail: 'john.doe@mail.com', - }); + }]); }); test('no parent commits', function () { const GIT_OUTPUT_NO_PARENTS = `52c293a05038d865604c2284aa8698bd087915a1 +John Doe john.doe@mail.com +1580811030 -This is a commit message.`; +This is a commit message.\x00`; - assert.deepEqual(parseGitCommit(GIT_OUTPUT_NO_PARENTS), { + assert.deepEqual(parseGitCommits(GIT_OUTPUT_NO_PARENTS), [{ hash: '52c293a05038d865604c2284aa8698bd087915a1', message: 'This is a commit message.', parents: [], + authorDate: new Date(1580811030000), + authorName: 'John Doe', authorEmail: 'john.doe@mail.com', - }); + }]); }); }); diff --git a/extensions/git/src/test/index.ts b/extensions/git/src/test/index.ts new file mode 100644 index 0000000000..5ea8c3bd9a --- /dev/null +++ b/extensions/git/src/test/index.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const path = require('path'); +const testRunner = require('vscode/lib/testrunner'); + +const suite = 'Integration Git Tests'; + +const options: any = { + ui: 'tdd', + useColors: (!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY && process.platform !== 'win32'), + timeout: 60000 +}; + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + options.reporter = 'mocha-multi-reporters'; + options.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(options); + +export = testRunner; diff --git a/extensions/git/src/test/smoke.test.ts b/extensions/git/src/test/smoke.test.ts new file mode 100644 index 0000000000..9fd8722cfc --- /dev/null +++ b/extensions/git/src/test/smoke.test.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import { workspace, commands, window, Uri, WorkspaceEdit, Range, TextDocument, extensions } from 'vscode'; +import * as cp from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; +import { GitExtension, API, Repository, Status } from '../api/git'; +import { eventToPromise } from '../util'; + +suite('git smoke test', function () { + const cwd = fs.realpathSync(workspace.workspaceFolders![0].uri.fsPath); + + function file(relativePath: string) { + return path.join(cwd, relativePath); + } + + function uri(relativePath: string) { + return Uri.file(file(relativePath)); + } + + async function open(relativePath: string) { + const doc = await workspace.openTextDocument(uri(relativePath)); + await window.showTextDocument(doc); + return doc; + } + + async function type(doc: TextDocument, text: string) { + const edit = new WorkspaceEdit(); + const end = doc.lineAt(doc.lineCount - 1).range.end; + edit.replace(doc.uri, new Range(end, end), text); + await workspace.applyEdit(edit); + } + + let git: API; + let repository: Repository; + + suiteSetup(async function () { + fs.writeFileSync(file('app.js'), 'hello', 'utf8'); + fs.writeFileSync(file('index.pug'), 'hello', 'utf8'); + cp.execSync('git init', { cwd }); + cp.execSync('git config user.name testuser', { cwd }); + cp.execSync('git config user.email monacotools@microsoft.com', { cwd }); + cp.execSync('git add .', { cwd }); + cp.execSync('git commit -m "initial commit"', { cwd }); + + // make sure git is activated + const ext = extensions.getExtension('vscode.git'); + await ext?.activate(); + git = ext!.exports.getAPI(1); + + if (git.repositories.length === 0) { + await eventToPromise(git.onDidOpenRepository); + } + + assert.equal(git.repositories.length, 1); + assert.equal(fs.realpathSync(git.repositories[0].rootUri.fsPath), cwd); + + repository = git.repositories[0]; + }); + + test('reflects working tree changes', async function () { + await commands.executeCommand('workbench.view.scm'); + + const appjs = await open('app.js'); + await type(appjs, ' world'); + await appjs.save(); + await repository.status(); + assert.equal(repository.state.workingTreeChanges.length, 1); + repository.state.workingTreeChanges.some(r => r.uri.path === appjs.uri.path && r.status === Status.MODIFIED); + + fs.writeFileSync(file('newfile.txt'), ''); + const newfile = await open('newfile.txt'); + await type(newfile, 'hey there'); + await newfile.save(); + await repository.status(); + assert.equal(repository.state.workingTreeChanges.length, 2); + repository.state.workingTreeChanges.some(r => r.uri.path === appjs.uri.path && r.status === Status.MODIFIED); + repository.state.workingTreeChanges.some(r => r.uri.path === newfile.uri.path && r.status === Status.UNTRACKED); + }); + + test('opens diff editor', async function () { + const appjs = uri('app.js'); + await commands.executeCommand('git.openChange', appjs); + + assert(window.activeTextEditor); + assert.equal(window.activeTextEditor!.document.uri.path, appjs.path); + + // TODO: how do we really know this is a diff editor? + }); + + test('stages correctly', async function () { + const appjs = uri('app.js'); + const newfile = uri('newfile.txt'); + + await commands.executeCommand('git.stage', appjs); + assert.equal(repository.state.workingTreeChanges.length, 1); + repository.state.workingTreeChanges.some(r => r.uri.path === newfile.path && r.status === Status.UNTRACKED); + assert.equal(repository.state.indexChanges.length, 1); + repository.state.indexChanges.some(r => r.uri.path === appjs.path && r.status === Status.INDEX_MODIFIED); + + await commands.executeCommand('git.unstage', appjs); + assert.equal(repository.state.workingTreeChanges.length, 2); + repository.state.workingTreeChanges.some(r => r.uri.path === appjs.path && r.status === Status.MODIFIED); + repository.state.workingTreeChanges.some(r => r.uri.path === newfile.path && r.status === Status.UNTRACKED); + }); + + test('stages, commits changes and verifies outgoing change', async function () { + const appjs = uri('app.js'); + const newfile = uri('newfile.txt'); + + await commands.executeCommand('git.stage', appjs); + await repository.commit('second commit'); + assert.equal(repository.state.workingTreeChanges.length, 1); + repository.state.workingTreeChanges.some(r => r.uri.path === newfile.path && r.status === Status.UNTRACKED); + assert.equal(repository.state.indexChanges.length, 0); + + await commands.executeCommand('git.stageAll', appjs); + await repository.commit('third commit'); + assert.equal(repository.state.workingTreeChanges.length, 0); + assert.equal(repository.state.indexChanges.length, 0); + }); +}); diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts new file mode 100644 index 0000000000..9edb4d683c --- /dev/null +++ b/extensions/git/src/timelineProvider.ts @@ -0,0 +1,213 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dayjs from 'dayjs'; +import * as advancedFormat from 'dayjs/plugin/advancedFormat'; +import * as relativeTime from 'dayjs/plugin/relativeTime'; +import { CancellationToken, Disposable, Event, EventEmitter, ThemeIcon, TimelineItem, TimelineProvider, Uri, workspace, TimelineChangeEvent } from 'vscode'; +import { Model } from './model'; +import { Repository } from './repository'; +import { debounce } from './decorators'; +import { Status } from './api/git'; + +dayjs.extend(advancedFormat); +dayjs.extend(relativeTime); + +// TODO[ECA]: Localize all the strings +// TODO[ECA]: Localize or use a setting for date format + +export class GitTimelineProvider implements TimelineProvider { + private _onDidChange = new EventEmitter(); + get onDidChange(): Event { + return this._onDidChange.event; + } + + readonly id = 'git-history'; + readonly label = 'Git History'; + + private _disposable: Disposable; + + private _repo: Repository | undefined; + private _repoDisposable: Disposable | undefined; + private _repoStatusDate: Date | undefined; + + constructor(private readonly _model: Model) { + this._disposable = Disposable.from( + _model.onDidOpenRepository(this.onRepositoriesChanged, this), + workspace.registerTimelineProvider('*', this), + ); + } + + dispose() { + this._disposable.dispose(); + } + + async provideTimeline(uri: Uri, _token: CancellationToken): Promise { + // console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`); + + const repo = this._model.getRepository(uri); + if (!repo) { + this._repoDisposable?.dispose(); + this._repoStatusDate = undefined; + this._repo = undefined; + + return []; + } + + if (this._repo?.root !== repo.root) { + this._repoDisposable?.dispose(); + + this._repo = repo; + this._repoStatusDate = new Date(); + this._repoDisposable = Disposable.from( + repo.onDidChangeRepository(uri => this.onRepositoryChanged(repo, uri)), + repo.onDidRunGitStatus(() => this.onRepositoryStatusChanged(repo)) + ); + } + + // TODO[ECA]: Ensure that the uri is a file -- if not we could get the history of the repo? + + const commits = await repo.logFile(uri); + + let dateFormatter: dayjs.Dayjs; + const items = commits.map(c => { + let message = c.message; + + const index = message.indexOf('\n'); + if (index !== -1) { + message = `${message.substring(0, index)} \u2026`; + } + + dateFormatter = dayjs(c.authorDate); + + const item = new TimelineItem(message, c.authorDate?.getTime() ?? 0); + item.id = c.hash; + item.iconPath = new (ThemeIcon as any)('git-commit'); + item.description = `${dateFormatter.fromNow()} \u2022 ${c.authorName}`; + item.detail = `${c.authorName} (${c.authorEmail}) \u2014 ${c.hash.substr(0, 8)}\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n\n${c.message}`; + item.command = { + title: 'Open Diff', + command: 'git.openDiff', + arguments: [uri, `${c.hash}^`, c.hash] + }; + + return item; + }); + + const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); + if (index) { + const date = this._repoStatusDate ?? new Date(); + dateFormatter = dayjs(date); + + let status; + switch (index.type) { + case Status.INDEX_MODIFIED: + status = 'Modified'; + break; + case Status.INDEX_ADDED: + status = 'Added'; + break; + case Status.INDEX_DELETED: + status = 'Deleted'; + break; + case Status.INDEX_RENAMED: + status = 'Renamed'; + break; + case Status.INDEX_COPIED: + status = 'Copied'; + break; + default: + status = ''; + break; + } + + const item = new TimelineItem('Staged Changes', date.getTime()); + item.id = 'index'; + // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? + item.iconPath = new (ThemeIcon as any)('git-commit'); + item.description = `${dateFormatter.fromNow()} \u2022 You`; + item.detail = `You \u2014 Index\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; + item.command = { + title: 'Open Comparison', + command: 'git.openDiff', + arguments: [uri, 'HEAD', '~'] + }; + + items.push(item); + } + + + const working = repo.workingTreeGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); + if (working) { + const date = new Date(); + dateFormatter = dayjs(date); + + let status; + switch (working.type) { + case Status.INDEX_MODIFIED: + status = 'Modified'; + break; + case Status.INDEX_ADDED: + status = 'Added'; + break; + case Status.INDEX_DELETED: + status = 'Deleted'; + break; + case Status.INDEX_RENAMED: + status = 'Renamed'; + break; + case Status.INDEX_COPIED: + status = 'Copied'; + break; + default: + status = ''; + break; + } + + const item = new TimelineItem('Uncommited Changes', date.getTime()); + item.id = 'working'; + // TODO[ECA]: Replace with a better icon -- reflecting its status maybe? + item.iconPath = new (ThemeIcon as any)('git-commit'); + item.description = `${dateFormatter.fromNow()} \u2022 You`; + item.detail = `You \u2014 Working Tree\n${dateFormatter.fromNow()} (${dateFormatter.format('MMMM Do, YYYY h:mma')})\n${status}`; + item.command = { + title: 'Open Comparison', + command: 'git.openDiff', + arguments: [uri, index ? '~' : 'HEAD', ''] + }; + + items.push(item); + } + + return items; + } + + private onRepositoriesChanged(_repo: Repository) { + // console.log(`GitTimelineProvider.onRepositoriesChanged`); + + // TODO[ECA]: Being naive for now and just always refreshing each time there is a new repository + this.fireChanged(); + } + + private onRepositoryChanged(_repo: Repository, _uri: Uri) { + // console.log(`GitTimelineProvider.onRepositoryChanged: uri=${uri.toString(true)}`); + + this.fireChanged(); + } + + private onRepositoryStatusChanged(_repo: Repository) { + // console.log(`GitTimelineProvider.onRepositoryStatusChanged`); + + // This is crappy, but for now just save the last time a status was run and use that as the timestamp for staged items + this._repoStatusDate = new Date(); + + this.fireChanged(); + } + + @debounce(500) + private fireChanged() { + this._onDidChange.fire(); + } +} diff --git a/extensions/git/src/typings/refs.d.ts b/extensions/git/src/typings/refs.d.ts index 4912c31c1a..ffe0c6c693 100644 --- a/extensions/git/src/typings/refs.d.ts +++ b/extensions/git/src/typings/refs.d.ts @@ -4,4 +4,5 @@ *--------------------------------------------------------------------------------------------*/ /// -/// \ No newline at end of file +/// +/// diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 742bcbfa05..2d3a764f80 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vscode'; +import { Event, Disposable } from 'vscode'; import { dirname, sep } from 'path'; import { Readable } from 'stream'; import { promises as fs, createReadStream } from 'fs'; @@ -33,15 +33,15 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { export const EmptyDisposable = toDisposable(() => null); export function fireEvent(event: Event): Event { - return (listener, thisArgs = null, disposables?) => event(_ => (listener as any).call(thisArgs), null, disposables); + return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(_ => (listener as any).call(thisArgs), null, disposables); } export function mapEvent(event: Event, map: (i: I) => O): Event { - return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables); + return (listener: (e: O) => any, thisArgs?: any, disposables?: Disposable[]) => event(i => listener.call(thisArgs, map(i)), null, disposables); } export function filterEvent(event: Event, filter: (e: T) => boolean): Event { - return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); + return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); } export function latchEvent(event: Event): Event { @@ -57,7 +57,7 @@ export function latchEvent(event: Event): Event { } export function anyEvent(...events: Event[]): Event { - return (listener, thisArgs = null, disposables?) => { + return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => { const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i)))); if (disposables) { @@ -73,7 +73,7 @@ export function done(promise: Promise): Promise { } export function onceEvent(event: Event): Event { - return (listener, thisArgs = null, disposables?) => { + return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => { const result = event(e => { result.dispose(); return listener.call(thisArgs, e); @@ -84,7 +84,7 @@ export function onceEvent(event: Event): Event { } export function debounceEvent(event: Event, delay: number): Event { - return (listener, thisArgs = null, disposables?) => { + return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => { let timer: NodeJS.Timer; return event(e => { clearTimeout(timer); diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 58a82bbe41..af5b10127d 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -36,6 +36,28 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + +ajv@^6.5.5: + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -45,11 +67,45 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" +asn1@~0.2.3: + version "0.2.4" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== + dependencies: + safer-buffer "~2.1.0" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= + +aws4@^1.8.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + dependencies: + tweetnacl "^0.14.3" + brace-expansion@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" @@ -63,11 +119,43 @@ browser-stdout@1.3.0: resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" integrity sha1-81HTKWnTL6XXpVZxVCY9korjvR8= +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= + +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= + +combined-stream@^1.0.6, combined-stream@~1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== + commander@2.9.0: version "2.9.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" @@ -80,6 +168,28 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= + dependencies: + assert-plus "^1.0.0" + +dayjs@1.8.19: + version "1.8.19" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.19.tgz#5117dc390d8f8e586d53891dbff3fa308f51abfe" + integrity sha512-7kqOoj3oQSmqbvtvGFLU5iYqies+SqUiEGNT0UtUPPxcPYgY1BrkXR0Cq2R9HYSimBXN+xHkEN4Hi399W+Ovlg== + debug@2.6.8: version "2.6.8" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" @@ -87,6 +197,32 @@ debug@2.6.8: dependencies: ms "2.0.0" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -104,21 +240,92 @@ diff@3.2.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" integrity sha1-yc45Okt8vQsFinJck98pkCeGj/k= +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + +ecc-jsbn@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + dependencies: + jsbn "~0.1.0" + safer-buffer "^2.1.0" + +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escape-string-regexp@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= + +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + file-type@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74" integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q= +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= + dependencies: + assert-plus "^1.0.0" + glob@7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" @@ -131,26 +338,98 @@ glob@7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.2: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= + +har-validator@~5.1.0: + version "5.1.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" + integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== + dependencies: + ajv "^6.5.5" + har-schema "^2.0.0" + has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + dependencies: + agent-base "4" + debug "3.1.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-proxy-agent@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" + integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== + dependencies: + agent-base "^4.3.0" + debug "^3.1.0" + iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -171,21 +450,66 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= + jschardet@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-2.1.1.tgz#af6f8fd0b3b0f5d46a8fd9614a4fce490575c184" integrity sha512-pA5qG9Zwm8CBpGlK/lo2GE9jPxwqRgMV7Lzc/1iaPccw6v4Rhj8Zg2BTyrdmHmxlJojnbLupLeRnaPLsq03x6Q== +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -242,7 +566,33 @@ lodash.keys@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" -minimatch@^3.0.2: +lodash@^4.16.4: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +md5@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk= + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + +mime-types@^2.1.12, mime-types@~2.1.19: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -254,13 +604,32 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -mkdirp@0.5.1: +mkdirp@0.5.1, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" +mocha-junit-reporter@^1.23.3: + version "1.23.3" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.3.tgz#941e219dd759ed732f8641e165918aa8b167c981" + integrity sha512-ed8LqbRj1RxZfjt/oC9t12sfrWsjZ3gNnbhV1nuj9R/Jb5/P3Xb4duv2eCfCDMYH+fEu0mqca7m4wsiVjsxsvA== + dependencies: + debug "^2.2.0" + md5 "^2.1.0" + mkdirp "~0.5.1" + strip-ansi "^4.0.0" + xml "^1.0.0" + +mocha-multi-reporters@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82" + integrity sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI= + dependencies: + debug "^3.1.0" + lodash "^4.16.4" + mocha@^3.2.0: version "3.5.3" resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.5.3.tgz#1e0480fe36d2da5858d1eb6acc38418b26eaa20d" @@ -279,11 +648,38 @@ mocha@^3.2.0: mkdirp "0.5.1" supports-color "3.1.2" +mocha@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" + integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== + dependencies: + browser-stdout "1.3.1" + commander "2.15.1" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.5" + he "1.1.1" + minimatch "3.0.4" + mkdirp "0.5.1" + supports-color "5.4.0" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +oauth-sign@~0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" + integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -296,7 +692,73 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -"safer-buffer@>= 2.1.2 < 3": +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= + +psl@^1.1.24: + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@~6.5.2: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + +querystringify@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" + integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + +request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + +safe-buffer@^5.0.1, safe-buffer@^5.1.2: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -306,6 +768,46 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== +semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +source-map-support@^0.5.0: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +sshpk@^1.7.0: + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" + ecc-jsbn "~0.1.1" + getpass "^0.1.1" + jsbn "~0.1.0" + safer-buffer "^2.0.2" + tweetnacl "~0.14.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + supports-color@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" @@ -313,6 +815,62 @@ supports-color@3.1.2: dependencies: has-flag "^1.0.0" +supports-color@5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + integrity sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w== + dependencies: + has-flag "^3.0.0" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +url-parse@^1.4.4: + version "1.4.7" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" + integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vscode-extension-telemetry@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.1.tgz#91387e06b33400c57abd48979b0e790415ae110b" @@ -325,11 +883,32 @@ vscode-nls@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== +vscode-test@^0.4.1: + version "0.4.3" + resolved "https://registry.yarnpkg.com/vscode-test/-/vscode-test-0.4.3.tgz#461ebf25fc4bc93d77d982aed556658a2e2b90b8" + integrity sha512-EkMGqBSefZH2MgW65nY05rdRSko15uvzq4VAPM5jVmwYuFQKE7eikKXNJDRxL+OITXHB6pI+a3XqqD32Y3KC5w== + dependencies: + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + vscode-uri@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" integrity sha512-lWXWofDSYD8r/TIyu64MdwB4FaSirQ608PP/TzUyslyOeHGwQ0eTHUZeJrK1ILOmwUHaJtV693m2JoUYroUDpw== +vscode@^1.1.36: + version "1.1.36" + resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.36.tgz#5e1a0d1bf4977d0c7bc5159a9a13d5b104d4b1b6" + integrity sha512-cGFh9jmGLcTapCpPCKvn8aG/j9zVQ+0x5hzYJq5h5YyUXVGa1iamOaB2M2PZXoumQPES4qeAP1FwkI0b6tL4bQ== + dependencies: + glob "^7.1.2" + mocha "^5.2.0" + request "^2.88.0" + semver "^5.4.1" + source-map-support "^0.5.0" + url-parse "^1.4.4" + vscode-test "^0.4.1" + which@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" @@ -342,6 +921,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +xml@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + zone.js@0.7.6: version "0.7.6" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 69bb484303..0fd5bee766 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -329,7 +329,7 @@ documents.onDidClose(event => { }); const pendingValidationRequests: { [uri: string]: NodeJS.Timer; } = {}; -const validationDelayMs = 500; +const validationDelayMs = 300; function cleanPendingValidation(textDocument: TextDocument): void { const request = pendingValidationRequests[textDocument.uri]; @@ -363,12 +363,12 @@ function validateTextDocument(textDocument: TextDocument, callback?: (diagnostic const documentSettings: DocumentLanguageSettings = textDocument.languageId === 'jsonc' ? { comments: 'ignore', trailingCommas: 'warning' } : { comments: 'error', trailingCommas: 'error' }; languageService.doValidation(textDocument, jsonDocument, documentSettings).then(diagnostics => { - setTimeout(() => { + setImmediate(() => { const currDocument = documents.get(textDocument.uri); if (currDocument && currDocument.version === version) { respond(diagnostics); // Send the computed diagnostics to VSCode. } - }, 100); + }); }, error => { connection.console.error(formatError(`Error while validating ${textDocument.uri}`, error)); }); diff --git a/extensions/markdown-language-features/preview-src/tsconfig.json b/extensions/markdown-language-features/preview-src/tsconfig.json index 85159a000d..e19cd4a675 100644 --- a/extensions/markdown-language-features/preview-src/tsconfig.json +++ b/extensions/markdown-language-features/preview-src/tsconfig.json @@ -2,6 +2,11 @@ "extends": "../../shared.tsconfig.json", "compilerOptions": { "outDir": "./dist/", - "jsx": "react" + "jsx": "react", + "lib": [ + "es2018", + "DOM", + "DOM.Iterable" + ] } } diff --git a/extensions/package.json b/extensions/package.json index f2f1225c82..df820979f1 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.7.5" + "typescript": "^3.8.1-rc" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/r/cgmanifest.json b/extensions/r/cgmanifest.json index 0781a15072..7cf785d714 100644 --- a/extensions/r/cgmanifest.json +++ b/extensions/r/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "Ikuyadeu/vscode-R", "repositoryUrl": "https://github.com/Ikuyadeu/vscode-R", - "commitHash": "1cd3d42a6b2e54276ef2d71fe33bb3fefb1d6cff" + "commitHash": "bc79e9245682ee09b4f0b742b927a37702d91b82" } }, "license": "MIT", - "version": "0.5.5" + "version": "1.1.8" } ], "version": 1 diff --git a/extensions/r/syntaxes/r.tmLanguage.json b/extensions/r/syntaxes/r.tmLanguage.json index db37b8421f..2b6c4368da 100644 --- a/extensions/r/syntaxes/r.tmLanguage.json +++ b/extensions/r/syntaxes/r.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Ikuyadeu/vscode-R/commit/1cd3d42a6b2e54276ef2d71fe33bb3fefb1d6cff", + "version": "https://github.com/Ikuyadeu/vscode-R/commit/bc79e9245682ee09b4f0b742b927a37702d91b82", "name": "R", "scopeName": "source.r", "patterns": [ @@ -417,7 +417,7 @@ "end": "(\\))", "endCaptures": { "1": { - "name": "punctuation.definition.parameters.r" + "name": "punctuation.section.parens.end.r" } }, "name": "meta.function-call.r", diff --git a/extensions/r/test/colorize-results/test_r.json b/extensions/r/test/colorize-results/test_r.json index 2d3e504f8d..3ecd0091ce 100644 --- a/extensions/r/test/colorize-results/test_r.json +++ b/extensions/r/test/colorize-results/test_r.json @@ -639,7 +639,7 @@ }, { "c": ")", - "t": "source.r meta.function-call.r punctuation.definition.parameters.r", + "t": "source.r meta.function-call.r punctuation.section.parens.end.r", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -716,7 +716,7 @@ }, { "c": ")", - "t": "source.r meta.function-call.r punctuation.definition.parameters.r", + "t": "source.r meta.function-call.r punctuation.section.parens.end.r", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1044,4 +1044,4 @@ "hc_black": "default: #FFFFFF" } } -] +] \ No newline at end of file diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index 295b620eee..69336137d4 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -77,9 +77,7 @@ export function activate(context: vscode.ExtensionContext) { const lineResult = parseSearchResults(document, token)[position.line]; if (!lineResult) { return []; } if (lineResult.type === 'file') { - // TODO: The multi-match peek UX isnt very smooth. - // return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location]; - return []; + return lineResult.allLocations; } const translateRangeSidewaysBy = (r: vscode.Range, n: number) => diff --git a/extensions/search-result/syntaxes/generateTMLanguage.js b/extensions/search-result/syntaxes/generateTMLanguage.js index 51d9e0ea7c..ce2acc34ff 100644 --- a/extensions/search-result/syntaxes/generateTMLanguage.js +++ b/extensions/search-result/syntaxes/generateTMLanguage.js @@ -115,7 +115,7 @@ mappings.forEach(([ext, scope, regexp]) => { name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaMultiLine].join(' '), begin: "^ ((\\d+) )", - while: "^ ((\\d+)(:))|((\\d+) )", + while: "^ (?:((\\d+)(:))|((\\d+) ))", beginCaptures: { "0": { name: scopes.resultBlock.result.prefix.meta }, "1": { name: scopes.resultBlock.result.prefix.metaContext }, @@ -215,7 +215,7 @@ const plainText = [ } }, { - match: "^ ((\\d+)(:))|((\\d+)( ))(.*)", + match: "^ (?:((\\d+)(:))|((\\d+)( ))(.*))", name: [scopes.resultBlock.meta, scopes.resultBlock.result.meta].join(' '), captures: { "1": { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaMatch].join(' ') }, diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json index e16ca1cb95..ea4e3efb15 100644 --- a/extensions/search-result/syntaxes/searchResult.tmLanguage.json +++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json @@ -253,7 +253,7 @@ } }, { - "match": "^ ((\\d+)(:))|((\\d+)( ))(.*)", + "match": "^ (?:((\\d+)(:))|((\\d+)( ))(.*))", "name": "meta.resultBlock.search meta.resultLine.search", "captures": { "1": { @@ -297,7 +297,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -383,7 +383,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -469,7 +469,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -555,7 +555,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -641,7 +641,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -727,7 +727,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -813,7 +813,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -899,7 +899,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -985,7 +985,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1071,7 +1071,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1157,7 +1157,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1243,7 +1243,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1329,7 +1329,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1415,7 +1415,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1501,7 +1501,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1587,7 +1587,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1673,7 +1673,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1759,7 +1759,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1845,7 +1845,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -1931,7 +1931,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2017,7 +2017,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2103,7 +2103,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2189,7 +2189,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2275,7 +2275,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2361,7 +2361,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2447,7 +2447,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2533,7 +2533,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2619,7 +2619,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2705,7 +2705,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2791,7 +2791,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2877,7 +2877,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -2963,7 +2963,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3049,7 +3049,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3135,7 +3135,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3221,7 +3221,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3307,7 +3307,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3393,7 +3393,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3479,7 +3479,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3565,7 +3565,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3651,7 +3651,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3737,7 +3737,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3823,7 +3823,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3909,7 +3909,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -3995,7 +3995,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4081,7 +4081,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4167,7 +4167,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4253,7 +4253,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4339,7 +4339,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4425,7 +4425,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4511,7 +4511,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4597,7 +4597,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4683,7 +4683,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" @@ -4769,7 +4769,7 @@ { "name": "meta.resultLine.search meta.resultLine.multiLine.search", "begin": "^ ((\\d+) )", - "while": "^ ((\\d+)(:))|((\\d+) )", + "while": "^ (?:((\\d+)(:))|((\\d+) ))", "beginCaptures": { "0": { "name": "constant.numeric.integer meta.resultLinePrefix.search" diff --git a/extensions/shared.tsconfig.json b/extensions/shared.tsconfig.json index 9671893bd7..5559704e30 100644 --- a/extensions/shared.tsconfig.json +++ b/extensions/shared.tsconfig.json @@ -1,6 +1,9 @@ { "compilerOptions": { "target": "es2018", + "lib": [ + "es2018" + ], "module": "commonjs", "strict": true, "alwaysStrict": true, diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index e28c9b8ed0..018da38e93 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -13,9 +13,10 @@ "sideBarTitle.foreground": "#6F6F6F", "list.hoverBackground": "#E8E8E8", "input.placeholderForeground": "#767676", + "searchEditor.textInputBorder": "#CECECE", "settings.textInputBorder": "#CECECE", "settings.numberInputBorder": "#CECECE", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D" } -} \ No newline at end of file +} diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 1c86b8bcb2..5ee7c0f5d5 100644 --- a/extensions/theme-seti/cgmanifest.json +++ b/extensions/theme-seti/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "seti-ui", "repositoryUrl": "https://github.com/jesseweed/seti-ui", - "commitHash": "85a222708824c6f19bbecbec71633d2c97077dad" + "commitHash": "4b3e0a3d0ca8999430bc3aa9f2c8324e6922b3de" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index b85727d01e463fc89c0bfaf2006eecb73d5d5244..85f6ee76a5bec139ca8c850ac6016c815dfb2952 100644 GIT binary patch delta 34373 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 V)5*G5$w_G^lm7rv`ja#O001zts;B?} delta 34183 zcmV)LK)Jujjso-dYoz6yRXk}pl z0Dw3E000~S001NhyahvOZFG150Dx2g001Qb00KxpH~;`_Z)0Hq0Dyb|00AZd00AZ! zcJ9b+VR&!=05*Z70000V0000W0&5BoZeeX@004o+0003V0005z-bxwFaBp*T004sI z000Ay000FdelE@clL!H7f0OwIkQ)Uiff_*s08&5<(EtE=obA+wRvcLvMd97hfd~n4 zcX#)Y5O*c+?(R;ExH6N>3~$$OlBrXB9%EpAb({X{?hCzY0dxcE3o{Et=uID*=}SNQ z7i%+<{{bI=V>&a6-(eQB%kMeNWghccz(N+Wm?bP_8OvEg3oBVw>~Cc?Ygo%V*0X_) zY+`e9-NIJ3vAtM#u#;WvW)E%bWgq)Fz(Edim?IqJ7{@umNltN^Go0ld=efW|E^(PF zT;&?qxxr0tahp5b z#&>?uS$t#-{QuRIOJmD_KXv7)Qu&q%x8$q zXNt^ciOgp!|F3FuMCNlv=JQm(p0D!t0+D?SMfNQc*|%6^-x861OGWlA6WO<1WZw#r zeJvvUR*LLfC9q>dqjF|BE7vT@82iV-Y?QVAksc4(mo{8J}lBcBGNu8(mp2A zJ}%NeA<{l6(mo~9J}uHdBho%A(mp5BJ}=U~Akw}l(!M0pzAVzdBGSGp(!M6rzAn;# zz9G`SDbl_r(!MRyz9Z7UE7HCv(!MX!ejw6*DAIl;(ta$`ej?I-D$;%?(ta+|ZWn34 z5NW>@X}=O_zZPk~5ovdbwBL%f--)!}tNfq*LFAl|BIkS(Ip?#;IbTH1`6_bGH<5F` zi=6XA2uIK4K-RIDUZu&{L4GoPTtt}`9L@^KzXd4s+O(c+@ zBsvfsyf`sYF=kA3Bswv}l?1LvjfpoTMn@+YjLeuHNhaJ&&3tR^I^B4$x%W4JU8kyc z?Y;Ke>+wDQ-(#&-R0aMK3qPe;N=ezR98wh2T0(;j)T^P;L8RrNu!j1*@o3QPBys+# zqih*6QP{_U*ubbZoO=d!t>uxJEfb4rl1H-sP{oyx)h99$IXz?_baA z&D&eGZ(&SSl@fCEX~Q7wZD<`4u6yCm&^Nex-3TE}x#cqg-NUkvh^#25l$)S`ED|ks z5NGqKwTwGii!&FoZr18{wqL_RXAc^T2jdMi9<8HZ9mV~)--EvO@-c9K5ZqElVH}BH z*-Ap9d;}QJhJ*3w+$)AB7cw`bF13unvMVlk13k6j!}j#R4Or|2IwOQ>yJl0bZWLmh zF;BNE$)1vCYNfK?(TPs9s#8?aJ#kppEX{KX=Bge>uIAVTUCY?YQs@wyFs3tAWsGs8 z>t0}$XF9W~O9NW53@hk=Txf6(&tkg4h|0_+>lAp=j-hSMs_P*1<+RqUIZVaGjcvo^ zR%+`i$3#J54Do*@O~r%}?3<|XCw?;a$M_fC_`(ZsJiWMh8hvN-3`)`7^~taF`{<49 zN={o#_(p{*hB8tvRFq)cPqJ>t$Ng?Hgz<#o9RgiRViKd)?t%g($M?RsXV1%D@w**QqlRWNL({y#3u|sco3WT4GR@F}M(mY%fqnPGAO7yg zAN-9^PW!Nk|4ylYC>zR^l73py++_^ZG8_of7>-9k0+Ao`jJLOG07xmP9bkpdc+`_L z6G88~;r7)5Fp0Q26vadhF%N+%m*h<_9vet=& z`2i5PJM8Qz2e{c=Dd-iVXS%t5pb}M0T7HciJC(?E^~ed0BBNY&)cX3|vCVo`o*{M{ zqYCs*GgK?7du6Dw)_c{w?@=o*%_cLYx)TGR5|*-m@(x$m+DpN^`WkQ+3_7yxw4786 znRamB%KlzlG#t@&EvF}9C~sDNN%?K%N#)N_fGVg9qai!Aj9MKI6j3ANeojA|s3W?) zM250}6kWLxjz@yTrXolTS`v@Nsk#`F;dlTQ3`c@k1tpDpAi+lCz9=%VgDZ zBEWTjakL?Zy^dA{P4=gQ45S*t%|ki5Qo0XDTnNR!cw7SaLu@HV-9pW((t`^cbIkQo#5_OkRvHT7^5D`Kt z(59u*Aiyfsbmnjal!T_LKrf^KavAC?EIF(i!3-8-Z2el0B2Y7+N;H+K9Hc9qw_Bwm zBV5Ixx3K1Es>Mw^aty~KCoPpM&VZyQE~&&`g}ESQD8fbsPMwZhjpb(JH`8E^!sA1K zofDN>0rUumWe~=+H?wc&YKf#qU>96I(@3Kr)NN+*W6ecqM&tPJy8#NTVW47@cp&-> zs0XPOTQ*aXZ4j(ejA^Z$8uKN_+yLl9jL)!csw#0VZeO`_=9E*m3}Ry)dWEUuQNvIv z)pZ0VFaibSLVtmu3w{Z7Dq$`GO{4mM4D<=XaZ-q{(h;>7Qgy-c1e2|#?1IKgthxtP zAZ~%)2@Oxze6yru)-QV)sb!E&P?+f!Z~N5vWB>pp_uxVm&o1o#`sHkzemu3Slu=gMM(V>(mr%mCNd*_)`jh0gYoS z8Sqrp9D=IK2q+Ia5QLuemq2t&P}3WYfhw_$HebB=^y$sbO;j4r)|ZVl+Ti$7?^LgM z>NQssKQcQ+4_tjUDox&lPF=k@JIX2x2X;t2(%&&shVT4Xx zh)adK-|YpFv#$ydm|vc6xRz$c=*ha(u{+CbcFAsAb$HMA*j&$UbHBmr=%06Ay%Dj&#WU>JpC%`otshFf&&-P(~OtzPTMk^BpHF6xQy%eyt`0%O)#JTE*K3`&`A@^vo4$(4uK;b@EKbWK zuF?vSWycdN3B*n*(4uM=cQ6e*>l7DEBh)?9r~E?n&F1CDyeAjZZ)WHNslJ&OE5&9| zH0K_m-78Yn+TsQ+*sGmI!{TcM;}mqWoe~PH8kK5cTV*mRv1tE`7z#Na zEBHSvJCr@j<$`LnZ6LaY1Pqm0K!79BM*y8uyb1+KCUpjYRS8RlByUT2%V13FW=H!86oNb)mw8KWF&?wBEdL5@| zCo0rE%=ver$>yAYS-quZS%L20>Avb?0=g=Rt#YpAyAHjdS87zT_x0hxS< z1*+~9vV$o=+L-cdukf9KVT9HQxq=u(t8flDQ#pszcaR-Qq~!XrDJIvi33b<$%FrJW zK!i=u{XVGveiMk$C!OS?$<;Bt0IU_996%4#K;6<0l#|JSua`0vHJlJ#RcxvF+gmy* zZ%wY2m#0O8$xTqER7BTx3Wm7j9KDluvZOTN#|G*PotooO&M;^+7;iZtU=<8v^bn@_ zy>~n1$SRsHGk1M|SFjL0QLUPR5v0MJRdgHSfBHv(>hM6b%l4&{-?=oU(G!)5Q_zFp zEi`8b`QhS!KUL;{8$KYc6c<>n1CuVazf|u%(T*CN?RFT5FZcm-pLM8%A|}Tu?oA;l zd@Uhh3ZZEQTFpRJ0~PVGK7~+GI3<^nAhE9Gu2l&l0A&ew&MO%XTGCGHbX(#{f+Df4 zX=}l}-`MGq6qYRaclxu{&lur-5LSSDQQ8B2(4iNYY$r*sxs3Tz66bbc4 zq=q-Jye5k$;t~+ua8whtA^wDk+8;rUL-hTBj#kmumOIg$E;{FK4r@uZ0+4-is#|`}*5&+(3A1v~%yq^|i*o0BqTr5|Dd3ru~+iao46MvGaSH zqXWdDFRAP$PXS?>KYd5Drjp5bH?QABdvDqjm11@DGqSl1Zhu$R#i2aUofC!Ucq^)ZMWxEPptTE z#fayd?E*RSj*Iti?4I2*qu;mx)=oBmAH}|f8ER*%mn;v{^+pv47@>38D`4sFJM>@;Q)n2-5{0%MTE$|WnEnP!<3D?9DN#qT)UtdDhu9=~85Q&2z9 z1bFsEyDG^JZX5LCceFd(^6?w^4?qvsa|yvf_HzM^m#1_CB1s@W-sN5V4MdKA0F3*M z4>FDV?Ud`}w(BUT&zi~7XD)kW*JbFfm>dAb^lR^@)X$2Ps<&K2)$gL6@xmuAd1!pe zd0F^+pygKz%eUK+##e5E^m8Er@LdcnSP8j-A{;3lc_nWWn912o>^u2_85l?^-w^su znl)f0pi8J0YQ}Py1uZjKxbf_NEeARQj{>h=G=&!eaaE^9>e^hbZ_$+M1kfZ3dfARr zB@H7Mnqjdzf7#VZv62SuQpp75PU9j_UIri@o^6%qU$OLZs=jJ@EpUYn*38K8L0}+) zbo*coXMid#?#;T?@f)3`?jRVZbCs512eh`>;9%F8PNBjLm}-p9>8v7u*s~8P4-1{O zjKHoLz-VlMnpi?EY7IN$K$tP3TvyA1Xi1BxD@r(EcZFH*l9JcODQVz=X&17Hivj}J z^dhN*5+M$9lTQMJo&aNg34O#vK_rNSac&^WmyqB>#=}hvPc+veB-0tGP+!lOYI5SL zDh#G>*)`89fN?|3SzvL0>_UM}gHxA)G*&&zT?;N#%K#z!(<%Xd4wY~Zxay}>!obk6 z)Vvb-LCk+#JQW441^J1d{8#R^t7^(yA_c|NG`{cs)|y_kQ@w+k^iG|DQ@(%CdjW3>s&A6J;E2 zrN>UYC(fj&(ktM7?8@N8)$-`9d&)!J*{x%HZ-ak(k8PcQJ$v>G$B%&-Lj!#ZU^+ zrx3Ke4>|~7<3Jvb@V6iO^$&gey+=Cjvkz~MF24-D@b-72=eKHC?f%H*vnS4DhYBe} z4OOKLwFu#Vk+pz|qp*&Ea)ccq9Ppj2iG=(Z`iPX@c8yjxi_RykS7_1G|8m{r&&*JZ z`9)wFPkx9_J#&l6(Ccyct=H(5c8dK+->OAVUx)r2fcHgc(zwO+Bh;R}@BH&skoRrS z#4pcfe*OYMJ?iK3L@L&Eh6pI21msjE1$%a`w7{2t@pzO-AFjM61z~;x7zMDw@~)m0 zTim9q(Fz6^bT0mxOM&|J)Lz(Cb;kc}Qm^eWtR>Q<33SMtTU{-H&RV?f#Gqin(&M3AaZn!wVS|PF-S;--9jvC-WX|= zaP0$sH^NWh+u7?ZX@WB6@Ig?ce^b_(Z+Q zSI}2RpBsN}Jo(i4JPyQvE^*-1!mgV_zesq?xK!J%?&{zi1Rrg?GyA!JPs;dq3kpjY zpYW3n!RDLdMd3n&(HskVZyqrT)o82+gk1A~>gA-{xR#KZ7O15MnqPI&UMKW|P_NIG zONEwSNiApc_mvdjZ9?qG11yxb0L9JJE9II`zVkQBCpzD#VwrpJ{wQPasMh2{?YYSuz3B5P~xbSn^l zV={?KZQqM2DJA&ft;nll<)3O`EHAMCll&TdI4Z?VL)3jz*D^4-!^6P(e2P} z_av9j(rGk{b zpEX>dAdgJShHJ;Jd#@Q)tfX#Hixqf2t2*&o4 znlox-Dz29YODn!cQ~-}w@q!mZv8hwAS+euEY%l)5l$Jmpz=qO-SQ6Zr zNN&r$=+N<@_$2euMHSU^;sv|V-)UQF5=WM$($&$*`sh;Du2wYs%E+kF$nf-k&Q@Wr zTdx*I{f)gVqb%@d+m|F*tyM0X%D4&sy21dhE5SJ8Nz|o$2!uIIo_*qh2cE#;6Ce1% z6FC0d6?fcm#peiokvp#X>^4@yUm?d~hG&JxXFHt8b4b~pdh|u3VJ?XjAKe{xigE0 zE;xDk*ufjzY++?m)R9#Wyr%LK?b%X}oNv!hZ%qPPUYO0(sFVml@Z5qP4g>}mh``|a zCZE#Wf3?9-yJ>75uP;}712#V?_~ z$ocTS_uY$D-u14@ckeiVuRj*?4*@CoN~9>`9E?U%oOK9bA0JTAW7S<(Bv&P8HMT8Rp|X9 z)Fgz%P#RFVJucb^jI>0AT+S^=5uXzCkM6vQylFkE-j1t^ht3KG=$|xw|4z z8PPz<#XzWbF%SU>Hc*219oTB|0-yZzm;XZd$^QRBZPPS=>G!_;<(Ffd7E1yvQgYuN}XVS~Zvr(6WJuf72 zM~18N!Oz{lNeu1S4iL_xVQ%a5agHdueF-Y0M^1VIgdV7iAnJzjC<}`#51@@jtjb0G zF_^An=}zl^#&z89z9dpV>UIRJZlWP&61;#ePU6ut=qBOINW|Dg^^E70&r7)r#iu@J z(X%Vm4b!VoyFv=Kp*7nMC5WbOO^udfRa*joWN0BxRS~oI)UJt5W*W_cUSC?Y zEzMLZ&?N(v&8ezRZdbzqlcgp+o{F$ky{c(J%$#d6y6*0HzB0J|)_Lu7NTo(ZbycIx zFs*{&d?COtr$7NwYDZU%sO{@osYacmQFDN@b*oH6ymrFZJ5{&rII)?!1oS<0$lw|i zZZ)ER3)@PwaBbBE$jMRUb=s=iPlA9sOiy_0NL8hkS`o2od3D~32X^6A7u0uR4uD3} zF!5AVa~LTuMrBGJ$MxLGe7s-+?qy)Qi)0edqZnYE_6>|)1%su{*gKZ^VFu4^G6+ja_8jc#pdLC@)%wqUZ=VEe}4y`ZZ4kp zQ~CHQ(7A2ppmG%M%%<3^jsO%&_$Jd~1Vj`Vw>wDgp%MoC%oeTt{B#n3n!t%eFXTm4*4qzlrMRdxCo`pK9f&Dz*+pM7;NZ z21dZrvp?L;*3ic*!Y4`rsp&f7X5$CXHBBZC0iF!6z4rcvI=XD~*;-TgJdZ&^gIh+s zmzY3Q-u?I8cWzulQc|t}IV5|8lU^jWqF@>U`5n-JA~-8!6VTfziE6+$@RJK)^Kc{% zhTE}u=ydM&$+|gj!-c02nE%>?2(M9p0Nf_n?3M{&G1fIe?WBi6<98B@>po*cLY$Iq zv;&xB->y`CRIUC1h!e1wMT3g{+nDR3IF|AZCc;%7-zwYUY z&~6aXM!}0P84?C{5*hE&La6k)O6Bi#!Vwe=O73q{&6`J7!SA@@3WsItuiJ+AfX}vLEhbgkyz#vRS(cHzp2f+jI0x$B- zsO%6%P1O*Fk;&75u)~L-i-GRC76qdY>@Es2!&;h2J)4nIT=cw(OA%qIZZwJ&&(=Hb zgRL2r?0eJxmtB1L4RIKT*B`cjYyy(vTDa3&9&K2b<^VitVO7PpMG6J5puh~m#A-C5 z8|4~{S+gsACn-S&WyOjLBFn9YFoe4~$4=7QxBs#Ow_f77X?pXqi!VNMb4m^8Na%u^ zj--D1Z{#w-8mZD%R+SUV8Rcf>oyxx;4aI1P4xpE#SEHXnKa1Xm-h=LcLmw2jrW_YB zfLs`j-7xNr1|n~uE3IfY^0QW=PE z>5z|N;N>t*dLk80B1C9^P$u|$J25N=mz`ytw-an-;qc=ukYSraj@@fWoSvq@K*6LX zyn%`E4@NBRO-V-3Vhbp-lMDw-ghg;;7>}WeU65oB=F%A@9I~RLxydsgw8B2zlAAxh znD^kCFfHSxCAtJ-CGs@JVQbKjV&LEboPmDe-KC7Gp)(%^5l$k1W3TC&CfCe2R0=NF z>{$RBdbvt0$AZ6WEgJ}v6(|Ne)v)8(ETRC6u*y_oShj6o4U`_`s*XvE>L9)WCm0N) z3RqVZQJbQVGL;dEO`o?65DkIFb=?t8Nd{KDVFC>=7gbs;9Xq*F*NmW3up?rDz-G>e$^vje>=iF7XZB zvI5J}>mct4Aq9h~LDVjjA^aVwVl%d zVnDM4YS9c4ZikuQf?p={ZRcf0f@8QC& ztT-NYL{`;6NV5&F4MfPGCvIYi5P|XsNI>D%m(37aRC$VIJQ~NMO#s!=89n7BiZy~5 zQdD1m)nVc>0anv(YE1sngi$Ow7<~qe4Pv_3aud@x=5M}b!A@PXpc4!wRl%Ji#Ay(i z>liq*plbYQUul}mZh620h%p)L(=9;%znOIB_B3!3exw!_ack#%_m!Bz-Jn-=nuNAR zwUpNkwd4Y`TBWCv1NzwnUa_%>LFp4}irM{tCk9pmv+T5E!MDNO6kQzXK*8-wGGlN| zjDo?LNI|k{dsd3G4Hs}R&$MPz4l$4#KtEC{LF;QvGb6*+TrjA@ic<`o(BRRB$c|ET zF{@>9fN>#e*RrKXaSr$f+Tl_V56}?XqaJo5OdV_6W+f{?7gc44@H!=Z;bQ|Wj{vuS zi2TFcq0|T5Gwz1Y`4nmN*r%_2jd{h^zKbtdLHkcX_vWj6YkPO>IC$vt<9D4ho~ZXX z`n4K-)bSTTaQL>?Og#DI!(aKx$@zC4zWnfk9rFinJGxw-mZZmskges1FF$GaZ##tB*iL?v4fuN@Duo)4M zQ=A$sc1pciSMLFJ(jLr^doU+SuV_c=_Qga~?}6c+8MNWsj#eGZn>2vKKMN&n@>yLZ z5IPC@47uvFBylYIOpw-`_oAE7Ls)&7*{h4Tt?DP3K}zj*$>Jv{CAK|(zv{wOZsk`m zq0_7E5)SMbMXM#4M~1e*f|z_kFr;oI?F>jY#C}0}2T!(TEv>;(shjlDKLld{YTV8PAa5q7!Vq{ zYCxf>4L~UdH%o?PSZP)unoh0ME0&t2ip`e{;!*$)MAd;0^##L-+^7JM%Jx&UGAu;@ zb}Ezbza#GeBtMXUF(Hs0GFP387`B+Xj>zKg4&y*9iwLK#89>l78ZP)~7B559%jx@Rt#88T4EH3(MiH-7@gEN-;cF4|)X*E?W6gOlI;J&T{h zjEZDyW?IXZp>f@Ew4tFo_Jh#j^yI}ykE4c>>Suf~Zwaw~b%UkLsTo>o6MZ7OBL04r zf*n-=h+zlCTVM>En|-g|*$pg1G%qk*!ya0oymZTB^IMJvrvuKpcFvcDe*m(?Rw_zM znS+s0K%%;sWy9U54@zBz#4;dsk;k2Nxi}Jr!8{s~!DIWQ;8+A7$od2UA!1RY-$ z->ZZ1s!)AlU8dv6%yRJ|mIIBt+wRlp0nh-L9BdkYyb^c@zX>=)r7gxywS-i=WNH?v zGvT;Fx9=5s$S_v~H;WcfIT0B&s-}|5w(7k!XqTID0n7%=2Zcg(&=9S#B)}jMIjnUP zxEFxt{<_MqR*QfEBRjwVJOk!gt^+**uw2Cq*hEFRh*YkU>mLAOA*jHK2FL^(1|WH8 zJ^s9Z_s>nl>Ypi^nk{gV=79muG}Eaj&KX_jfE*a(8WPY!)2t<|$NMV|cSu$xDvO zYybE)?OyLtzyFOte$7n3f3V;G`kzb>&iki-7V+1Wnle;&ftFK{$mx%hOah0&ir`t8 zd$Hm~Wd94@BqZdp)9ojD$p~m;+y=4TkAOzSH+l@LgwYlAZy?S(OeK7fHx$n#$(dsN zMk6qGzU>qAVle-J;%v`jLvZxN7i29pI z#KV`-n06DM@OIKA#>NKu06BXWKF%Fo{phokW4nL&=%YVGzq|e{ddp8rXto`-eZ=y^X=}r$DD+n8!NU2E#;ukTdr)f6$#S7xVkksNd_3#(hu_>1&am>I_|J zI`v(Lcf=Q5b^6GK#|G|I#;(Uc{c_|Lv%s7DBQgSj;I>gkcTQ@@_FsHr@3#J2Bt*tH zl(vj+aN!P2@O_gf9()k3J@|LWkN?K;<9`cM*O~mod&IwO9)Ax1??8F>PsHJWy2Ip! z$L_i3G4%ZB@4ff)IC%8L*|R4eC6Aswd-mj`QyPi!H(*p}m7U5-p|giV(K6{Ind;S~ zm51|%B-j+II!0rbjKLUUJoljF3E;V!=5dneW6OcbS99Q}m|FHWi9Y(7;j8z78Y)(U zw#cjtbVH@Srw`fA_Gn{i@D}QSM>R0^jZxdQCt6)&b>3d6GZGlDext5hEFeeLZ@bFD zAGJZ%8k;0S$^7dKmsN{iO5DIOeOEPZug@N69y{`Ww-o6*D7wzrJ)_U*T19JT3#`D8 zJF2Azo*mtwZ^t1V{2a{mvT}iPSh-Sgs^1ieYobGg&OBD7`? z*f_r^aw6o}xSM-bgw-}^2?)b@ybd_A+adOgU%|Z})j=|W8lvhJX#POOEwDsPfVLGd z(}}taDhC>i(J9r60V3lHDCNj-?Z|U}ACRqOATm!8ruexRe{?^h=CUdLcKbmfk+8E6 zCT9|&iWEyYxQ!wg@Hc&blEOu@b=nSGTes{0Kn}xeDTu+Lw^#hX!YvTrxK9euR{7nU2pJvoc+ zh$f%B{lxwEp^r}ccOTC|c+1@~`51Kr2 zaL?}j2li|pI&fXsjiQ|?$JM>BxCaN=Zv;xyrmykxeR99o#DYa?~^-_UZ zpu1Iu*-XhO3_EKUBlE|P_^QE+$ka?*uPkvj)5)O5O&+lBtnhiD+m_b@!*hc+>xQk8 zNm&32(3~?Zz@d7x+1f0}ItO6Q)9X^J$Q=ieMOoy37zvNdOno(&2hymLPCAT|F?6PATC^=Q#<;Hp*n{JO>h8d)Z8L%bhWG zc(~(#xBzxgtyamdyZFN4Lcj`U6ql=>HQQV6%pI;a(hz^@xr?&~rTcWZu%l;iE$SrJ z%+cOvu%CeO8?{W`3r5{uJpi~g|l>K1WU#+}R`5Uwo9Yj~7pGWUT z51>z>Cxmoc&$DGm`6^3+SEorrd17)VqR7JkF48*2!-4SMFXNVAS7AMZDr8fN9OQOv zn6#>54H?`!2AwAock|jnk;Z}WCIFNgEn^uj$RC=WPk|1jB)4O?lVFl*oGhAp65(%u z+>_t^Joq#kuZr|u7;cfnp7d5>+E?QozqMu9qPvnmAh*6{rK>W5L4?Oaj|p>ZJY8*E zM>$`P&gZ;rFR4N-Z-V?OYmoyc^Ei@@47CdbTP7$Amq{n*^gIqf%y}06LQ%t5WD$%( zFN(H+ZtSv^RU!CBvcE}7tfC$dyPS1@hQdl2O(%r(wl^BcgC{Q9!~pg1+Yt!d2AE5# zmiSm$?eJ24!%#602lsW55m;l0&ueZRv!cT+-4Ij&0S!`rG*76(J`Vsy`bT_BhuwC_6itGj=+k+wx2xgsK^lEe|Xup%cyP@ zOlSpCh2NTD0vaD7V@Kj|)Lq}vDToVX1Cl^0p^0ny%{WL3PN+iTBat$;YWUXOy6~83 z>QMAWGZ(~aO<}lDO&1GS!9vo1qH0Zd2%@@1Nwks)|33nQOtV*^6t-cIDMrjd99b^d zWbHQ~Y_p|5)sw4JZB~~^aehSLlJ-GQCa02+D{2BM7BvC zfpa)g;i)#ou|^p(IduGWL@d}TXhjma6fa;>V=#k6vkUy+5I6LYYP|-3n*?wX$}>TbS`O*|CzZR$?$CMHmMGuuKBS!`M=R+<;dTGekv; z=!}w}V(HA|Q9+~|5KZ@gY(rbDuFa&n5B=0Us{%^Pi&QaGL`|;O)4)d<$6^(rK&hUs z%9XLI@O1h~mFb1&R0gwL$Y$CxG)m0iUCe}GuR~jQLG#qI17|;j#!QFn03tW81b8Uu z1*=OK8BA5RvTOMW{dXi5JX=wr5Ep8VB=T$hJ-hpSniqBy|E2POHRaEh?(&-=qMz_sx- zO-_VTrx8}L*}(i1Z#Wcg>=*^fX520P^A}-q97$k{~EWyNLy7Gh*Y!}%q?Uidt zt{$hF30$DlW-}ty-=L+mGv`BLs0LUZAx(-gf;+T4qDAU|8CW6@vCbJKoeTpV<1!HL z%Rq?Wk_kMkQGxW;H7I0pzk(+3E+ljN0T+Svb1gnYE&t9my*O20g;Xywb=Sn-gxZ1` z=pH6c$t0qoDm84;6K2VfCiK)`mk_I&%bc5`ZkgQYzps&Y6Xl<`(gSW&1ky zRom5U!{NYx#GKl8Nxe+Jq7e9AL9Cx84v06mXmiu?HP-@}I$T_t7qZMndI4mL8(ImW zre=qZLruaof-mQ~&4GeJTw}323Fd+~T~1->8k65b_(HfLwZ+;|%!l_AjTxkd(4Sp& zA>Q|ZYf

Up0uo)E5#MfOG@6K*;KiBw>+vphY2_*`T@cO$b{u8Ne`g>!n4wOf%0 z;YDC?3r*QZh`N21gprl@)_@K`1cN2wnvtFbfzMZ#*3*<(uEy+w(SZ}17q-oUZMh~* z)AiGTt#-D|mi0^zTBnC;&j$0k9eNt~gN{+KnYU6T>q{&0dStriZ4-q55v;ycAj=?> z**t?K9+-hHm!ph{#8D*(T- zj+;96(f3dRc`tq&6(-M#H*S3||C)RY%}%~Enc&vcpYsLs3gvF)%gQ(5xl1CPDw8c3hDhaL*kL+I5Ebibqx5=P#GNTM z65>#usMrMHU?34b0)0xUs?OGmyMU?j`hE&uF*^c!uTuL%B(p@fgh!ra_=^5s(k^Fu zt-7SEmD$C)9w_)iv@h#apgpz$+7nvIoy51Py-EWbI$?BjCp=)jQoe4Gh8jzMdFi^c zx7Pz0*d8_aT+|fUDzzh~%4MC{0)VBt0Q5BB&J9gfqk#(WL${L)Cy!eoR~!e7H(hR| zq}rS!=$BzIq8k+MHI5(d18g!-@8F3Z(WtxIeH2X2m9Mjqg3bAG#RH{#YU&G~0sH@$ za)SGkrL z;U%G612O?%ClEU*9o5>2RjnD8c+7-GvLkL-%o7PUPy{}nv30TN&vFy7f|DD@{7Pvi za0!^_9XRq9v`2<0%6%XP8$iFlxY4vpYk<#vUMd>XL}5}^R^AJQ`Intnu3AX-KNaeOh7-m6Vs4BB)R- z>L8X~Q&j^Ru?7fTqdcw5hHdn6p*QrTwo@ohF>Ds86^cW*1Ug24)8e`ao)D2rOd?0@ zl%fMWvdOEqguO$AQYWzv53k2Y{75}rZXjZbzzc9FPuYgI<(9861F4=q=dXGO0hNNp zx(K}ttmkXdThY7Fuc42k-$EZp{~moF{Q>$T^c4DY^jGLVqi4|HqkjNw`J%L6z-qt_ z4gk?6xQJ6+!*x7=gWI@+=kW?2;$3(X@56`iF?=b$0>2zziC>9dg#i#cb3Q@n^9YRhZuuvU%Kr6MY%9YlRV9g})tWdeCC=CdyrsENcqxfZt8=yV-J4 znDm?5*TkMBneY~O&Uua3;emiQMqOWSA{Ix!M6A7`KT6;u;iPmBW+8T7>Z`yoW1rDcVFU9Dc!d^W>Dsz5KKglarpv z^y2GOcoRS*Ju7j0psOmP6_8L@ikoa<*#7v(V7980A2IbhEGxVde*Ga@6V*@tSjE53 z-UL?25q=d$ZvjL1J}5!K0@{K{eUYlrzNNM1Wb4R&FrexFg9JT_YiDl}PeW*nECB3( zu&t+YhNQGvomLykJB8{^i@nwj%jj4upP2e26@JLZX7uHFC117m+MCf`b)! zkJMg%n~>w@DnRNnJo%xt=4J9D`=XA2`GZgduD|6JX|ZK2OBvjS8`ZifXcbHt>X-__ z->Ilm3Yz-JM4w|0WpxRW%y*oj5`_uTz<`scunpy_nXI3R)1mtf-H}9Z*#NA|vd7OJbTI zU>_9vmd9c^)Kr+vL+Ie-VJ@CCd3ys0P6POVC(r|m$%6nn1qIvcG=g&#Cws z>E)=f3K3~Jk7IRHdT5@9Cw_20R!?9ARy>-UEF1%JfXeHHX#OT4bA(ud`y;qJ5(%ZA zxJlkAT!cjI2mxv>ba0*mev%e$VDeC%5`vi6pQMFpJfM&NyW+~VT7J{-3xfg6MH$&Z z?4q$GfMFGp{^ZCZOji%h4lX)`*kY~R#>L5BqwJt%boW%5e#!6wNtQ`6`ID=E4|!;}-YGOvr@3u=et;a8tN$23w~GJ#FG`}$Kt>(TZv@bp zhC;g2{6KNXGlaTg?En}q|ANsAy(?TDj`n0bro>kC}B_?+pQa~zBuPh~DSH&!>`yA=-Hnt)X4 z0=>w+PntuTiH%T({X~AoyqW)&zi1Zq&wld}bjyq1nVmcN$X72r`3Q)`&g|U6>}4-Z zK0P;w?|xxnf9}Pv-FoXIk1Twpchjx#?~#QQ%Kz(`vC@+Mt)Ko(`maCo>zAGUC_%0N z|0m}1%5iI6O`GV{II8J`zwY|GUUxw#uVjDY|h9NeO6Kfdd3TR>f#NpI6nlD+Co`9ZkY9-rD z-R}*T(6Bug+h^uW-=qVSC306enIK_&%ihfRG6`E$Wo zVloM2e|ysg;dvm7s#@0N5}`?CWn#l?PtyPrY8sWhScj&x;+Y0kzQt{^%tw!1?njWTYB5}~CUt{xas$&L!ZO17gHEM1+n zR47ty);;bEPXtE=*Q9R65neK}jU1s2Jp|8Df3Z=qY#p?9po_4b*fAJmmfGcpZE22C z;2=X@L?2D-ks-2y;usY7Tqw^*adk=XyE9>Az0lW4Ifii$1CI0ajP=x$-Uu7=pe zl=z_veAfjXwygA(vB;}x34<~gHY?b+9hB!ug-q=B6P^D^GOurI%e}C~BC;Im%Du%Y2RQl&+Xb5uWG>pwH%AH~_${+pdo}?>TKTn6q_Z$kq`WAlqLB2*H-{IwHVDeH+j~B4o_NYq>sz%iae{iFz8bHVd z(14yS0h6@0T4}(zL8!_&@yJZG)_^Y{`A)&^39}}y7puOX8qD$q8`D@lView7o?(ob zMCHV%mB|+nDwjk)fuX5(bUy%eT`af~CIoOUs@N8Xr&xiCk95p=lgw9Jb74euUeG-| ztrl7t+0hsAYtY6Pi%lxrf9Ap%FpZela!UZ0sIzm`q6tE&U_E2??7%GU20llEPsK7U zdbJ&~fDwQ$Rv8_xq=8rlu2IX=RckwMCTRx^mBXxg;ph ze*xjWy#x2Q#;A#?X(kg?M0gAor3glBr*g^jEP@(^1Rr!HT}YxBe~i!X^(W2&c>c&T zG~5?icV<%19lc*oybcSDmH}u_EtNvo#Fl}u7tVx@W_K}WZp^((J+QdrT8?xI;27gz z*aZQp8dwA$JWN4Eo69D#Swe$7We{@(8}N{2flB$shEZ$>kEsD>jutv!lAB-B=4*D& zP{v?##ooVaP)Zhxf4=Ur%Ahz~thu_ExTVqVYGJK!-58d=R%*>2JKEL^e(!zRdXP9E zgbTWB*?FntF!m(?mobuI$UEznUk z=HkDgP_gq6f6Uzt7~8JdN~V}*2mVXteagp_-_3VRl);H)d%LTtSdh^RA#Ap{Mx7c7 zNxZ#UUfej$caRqgNaEa78wdyL6h%a#D7A!J5}b$?D@^7k0>~CfY9rsgz9ZA>|2i-QDB{joEE6BgjC~O1BXkcf#R1a|5D5kYe;O1lnTQRpd>94|i_{Q;?D{U1 zcKxE2ZY~XNIJH=JJT8~;!c2fB-!qd5grKs~{KnkDRBZPo5|32kRfM_*oz6`VOcM7z`#J zoH&7lLncfTJ0Stgz#OtLiO&GeX)*tIpHyu~GS#i^ZFl?czuf!(-*1?@4|!6U$BFLa|)wG@Uk^;D^-wb))t z>z(;$7Fm)m$DJVZXKUMQZWL9bh;%sWe?Y_dNN8EI==i0QAMP}|bJdl0)+$!#47)c| z527F|uU5~sEpF6-xHM*tf3>6~uuH{MET3YFCT(HvoQDN?x?Il~x z4BB(`T6#NfOp7xpOd5Z1&@9J(X{L0rw6JpiW^ZMwnI`Nb^oYmYv{s*M4Q35%e`)c$ zXH4%B*bC6>mEKaURf_V!rq z5L-UX_6|LCc=ymDvW^eke+Wh8e>if#%|B1IZ4}NcTtjs6-opC}A1!>m@ae+m3lCDc z2bm&PptnxbR_>)d1o2V% zpi5>7I}h*yFf`iH_(P1^sKv(n7WVy#wZ{NoRrzLdTh9BQ?FGJ_QTFZprQ zI&_C&*DLg3Q%0L3{}k%Nf_ACw$@XPrlu;2CQmBe0>6J_EfXon=p9+SXU-$wS9()S8 z8DfRhFp8VQhQY!m$tz?ohR#ap&CRmT`JovAP#`?N!)EG!v`E59e-T+L&onL%hm75f zsxH<@>C>MxSc~NrW9*#$T@ZH_N_thU5ceAl&Ouqo0aH5YYLw6-y}t27_8j)v`?z>E z`{Di1eeV6IucANkmd`!s31&R|{wJRMy61l2>aly(R2N{vDy)-b^qj(fL3Fp?8(T#b zvW}GP_m1fJ2)#)-f7&;RHK5284~3Xes`uD-wEhiGIg*)kM3|F(%l-JvYy{`*;RG)w z?G4#?_L^P0z7~fvj8;EM+?+jMne|3=cxR^M5^G;39AEmb?fNfnE{e}MVN?oufB3C#^Pv5cmy!Y!nEKLN z7S3Ht-Q{bZf0qA1<24*CTu!t5ChECY749G{;ys1mDts!BaQrcV={eTS)!jT37tpx| zBZSG*3>;xPcJuGlGlK+K%+hjyyNCW8Xmm}fw1p8+HfU zD+qgqJK4_Ge=5_DBQ1L%^^-@F5=MEPIQ)gW$$;iHl}07?hdbRq|F?R7FgViPnzaLW zwpel`6Fy0ECH>i_W=Cd(!OuR9&Gm-mSaVNlc*vK~u;lln8oot=X_z0E6&c$>AkFwC zX@K3HmY|cNA&l7Mj@qR2$bJ(F=d=(A!SW0m727PCfAS#7W!U#VKVm4YxR9am3#lVA zN#%aWY+ALQ`PRBWQyy*%_72pec5yDP^?IG!Q&%IaOgFLdBUTeDA3bK??MYdeRC?`G zaE}%=Ex9PMtEo0IqHk65b=wvt()44WEf|qWmr_+8pqj=o*F0f=g<)|-(h>1a5lgBn zDeLk|e;8R2bV}*D&^_y(4?}#M^kT^+~FoiAewlePc}icpP=U66D-?q4|eW;BnDyy zR1VwS;JND11yt)aW&^6e>`xHv^&Op|4tg2we`7~gQWKsyTIJ0tnI2_63CVM)W2f7# z>2lsA;x^yJ!0Ix6iQAgwbn8^5LsoTXM5H{@M>GC0IiIGp<;4`ZkYRmmj>#;p)yljQ zk+B2!5vJ5?y;duwq+Isu3ypM^Gyz`kyK$uwd$rQSCrvk0zD=Yk!Ue+})N5q)24sTT ze}U(at|{ZU$9DZdqf&`#A-mO!X*^@kt2Kj}S}{w;AMVx36e}&%{7N#jU}{{4gF*rB-P{ip4khwY1!`;d#x2+)m2y;#2;V<(T-KzD zUT#=L3y#&D1IGCO(3|=u9rtCG-6_l?e@B+M={nU=)jXF>$87hQ=XW~@Vb%RhBeJFdsIaKWUR*C^JLh`48DS7jx$f1SUV zZrxAIX^D(VyR2%NJXS0fRk&T5iAeJo%Cx2DxNKn6Emie@RG?2cUROcAI8cX=I^|~{ zwf(ui-|&Zg1{Be`{w|f2^Oq>+wtDcHHZ&Z?pB@g%^HrZD~~5_qlzT?A&8O zI_~5XYU0nBuUlt2i$$&z^(LzMK*x${1d|E>CbrL-JsqTTF0mV?;s8=Hfl~~>_cXBs z>crG30wK`1i)6J_eS_pbX-Gcs?u62i^kiWX!)&HAt6uEO3fU`+gcwnqe;9v7`R#Je zD<{ozcUB&L#@Fp|Ejs&Q)8s*Q{<2eZWGGn*+ptuxnPxF`eLPjzc#unULAHUMll6LI zFIq_KPFzx!GEKw-)^_Kzuxv|V&Ps8@8}BY%)i~$-I_Bd%q6+!gay$#|bQB;aUoVeo zRE2!INP8j=e=MIQU%Qp6e|DIroC}O6yLhzNy0b-g7cs@TE&?iulg6cVesy(wK&*W% z!XYvB8oloMrHgG~Dq_{bFj}+3{v&1yf8fn|+Y2l68}$X>(0V@6%|NWNXR-IPzhM8H z{Se82c$v>}#G)l>f0rXRlf))z910r<3F#sIc>(#Glp!JWNG61pf0yZOo$Uu&VK*=z zb&m8W)Occy+)9$u1jdw%$?E~u!=y0|}7a%WVe+aimEFJ9fQ6C!^ zROs*!Jbi%xhkcMfe_0I!(!LM_F&tOi31zb>5&GAK&ncP$Bz54P21n;P&rDk(zc<5HW*cDe~`k)A)}n(371E9Mc4u{p(etC6au8^ zf5ouQRj6Teew^@uN4=uj8Hq{3G?>NpW8eQODk#qt=ZdweaD- z-6^_`?SDn#q{F`v!-Ypp`T|X#_^nWzh&9qzhghjEb&e#%B^{Flx+hFados~L5)0-Q zLYLrRe=90jACn~Pu!&@{x?|8d&`&ZSsWWOMAblIC{YI@Kkt-u88X@81M>?ZIZ@xQ~k*lL6t!3DnQaheHi?XM2#wi z?Y3A$mw_y_R}TGBHwvc&X!y`U&=x$giUNa5e>5A4Wzwe5%5ozTu@$;20ToIEDNC6e ze@$4aEIA~>mzdZkcI**_sby%xAV1>N%aLK*-!4&q>Rfb;M~x2Wcdx=rnTRYDdW5|3 zzu?n#X6enP;e3~Joyh$#PKEA7-h7vTuyA9ZUypbJ_HlY6&2P%cT$`sho^04mWpG>D ze^G7CTeL-`v3x={LHp?R?)(v#MX|Z?o4gBodyAz94>HDuxM}5}v9>HRYS0 zLp`kQ$PMaCrmx7Rx3C&&nRYa{myk%N?hb_K$I>)CpQIE~ui0;7TD{LKO4GpfCVMi0 z*O+H2<(Gn_h784VL@a@(i!!MI#|q3Kf1I@f^rK;y{MaV7)yGOb!+W(S+jjo6l>lgvdukzbBW0CjVtp-lSg4Y0npwT-L$p5wzz5<$|$Wg zowBk#8+a4knVqkN(OPA3Wv^95dg*YOoxe~mDWmEOji1?ct+06BY&#)tG?;1nwRq95 zIX#ViIb#7JQ(T$O7t+e+-8GbBl1p zG?}I!#`|E{pQempZT;h1n&BkBUuW=8{CzYz1Ja261K0J)DnGu_-mG@#jS#s|gL&Dj zMTes0gY!bp-aLDJw`hCg-yx|GACH^8>T;7SCet$Bs$XZ z9xAP-eS_6o#YNY~wc)i}^7Q(t>rZWN(Ax%IsLfP&p4ub(@WLBLf6MV!7p|N4Ahg(* zH4@|Mu60uem)x`=nYV$Po<(mbr}=yMcM4viQdrO{W)~Dr7OvIV;Q&LU^S6>xq;%BY zfRw^cbLtSMo_v9+X>8D>3))G(ZkCxpcEP#B`_E!8n#nlN`gr?@=PWHb98*8T?0CB# z{dZ#9dyPJ%PhLqs^>LdnculLehwJxhtwVdh9ov3|UVHvIUnf&YqH<{L0WuEhyhf=? zs2_CF6#D#g-XC`k9bz|+?|JMowmJTakjPzhe6zQ?bYl7VfAYDt&LYi|`NB4Alembb z2p!9yw}Xqhe-H9*wl(gA-U|V`4ff`-UI9SK_Hgv5aOXShtt4%iYo-#;d^s7vSpO1N zI^||e3cy3~S80_t-+IaAeczHpWB}nCv=@J)GMjk6j)m$9L(>pj_`J-v}+!KR??wHSZV7lb^6dD z9jicx*yBfwmI#`#*NxTI&dvSJWt&v7a?>K=AJ*1RI5u~qy8s3pmzGc3iaX(3VtQk@ z%IK2A_2vG7#Zj2DTJnt1LNo0g?S+mIk6ErCKqs`Ff0(B4Ez-6l2TFm>r+pubS>jPA z3)kdXNG4vA`+_HzGQItcb!<)MYe^H)v&kkd6`F&=CwQ{AVNP{*{^-0L+QHSUo4wvE z-|d)34*&cMZiuCY-E^KN*H>G~T8U<|h?n}c<>u~%o>xP*HKTJXT_|_U7cW)kSm4fA z$((-ne+8G0KW6(;$#?m8uRL`5MK8FlNrPv6>SwRJ;Ho6JIO2vfQ{!XTpDagCq-elp z?&>V zANenK(^H;)y+3{~yJx+B>e?Fu`u6tq&B8f8{6>MB_|$rx8{8o+NHn_4YGyoN-fH^ zYinM>A?a0ld|$L;323hLkBlplCP{r|r{tH49%;@ZaC{mVUj=HobYubkZQK*Dmy(RG zL#IwZuead(oO@Q$bVz$MRAc+dMUAVsf4hbxt&7KTas0qsfF)?sxq;AQw$Age+PQiw zRJL-VTCWiiN_zNZKl2i^;+7)iPWvksOEfQ@2cL<_FurV6uEyndUW^Br+Iipcp!j zuAUd^BhUXV>KfWhwbl!P3T<`f4y9;^JnIf z(W1M<)}Z@%WHoy+Os0yo%U&b$dVW@^c@3jZmML=WC{>f05gEraZXn%-Lpm@S(id*4 z$gJpq&&(FWq*-1n1u?1J>gXjqE5gp~>vqX@E%{#2N*kmrubg-OmT3m{vQ@XJ$VkYV z8=L&Cu=--tmA2)3+$JN^e;C0g!bEKBL?-XK3i% zT1nQWQ!^q{)_u}p_kE6@A})VY-numDc2qLLXM-hO_Qr=FW`FYVf5T^wJpAwz4?oNw z{+U0!883D@y?uyY=X8u`^81Lg>_V$>yzo-U*?y<1QutJp+5}HcY-{(sZPm}G+aVU` z2j>`_hOE{233@@SJ4xm?YQz6?l>HahZgf4d_L)3Iy*_Xj0k@hszH7537^5WHhQ+Gu>uTT0&*Zfx2w4g<+g z^aM>PFS;=j*|Kv>6qw?I1GW=JH?emw9Xlpkou#EO8eVw(mT1l#O~0iN`l9Vf#;NAH4ccU`L^+ zTQ_Z*9zz;dBtzZh+HZD4?Cf&A$3t`@Hr zZx(M8zb4Lz_lggSkBHwB_liFfe=WWyzA2tC=8cxIY+PboVf>WwBI7m2ZN@JeZ#F(^ zeBAhbPgHm#EjN zUsP{aZ&e>s_p3ivkEp*>|5yE+HDh(G73;8dp>?Tsm34#lJnLoFtF7CuU$fq8{g(A{ zf9tc>pIQ%FU$y?$`lj_A>mRM}TVva>UAty??2&!MzSMq-{dD^#`)2#a_RH%Eymm_j906R8pc zEoi856)~NV6;XSnb_b)b_H)Hg?4Y(se-&bi89gx#0Wu4R9aZjEMmhnSf;!b30R=S# z=mr@%NSvbb4D12V+Izf*x}?|@Zgp`v?n!f%V}MPs9epeXI>=D#@BoRHYG<-n0H3ot zoy;3KCR%i6sNoQ|$W~WnMT$K3Qd-%LO3;>G1$T~eJ3!Y0YjOFy{;*t0dVK&ne`g~U zozSEB5EUl&lPOqr>EwCc7O;>MZbaRP*<^GS;Yw)&H`Mm9i`2VAwYAen$0!{fiY?VBki9#AJXNoQ{BeiIM=;Ci)rCXI5Ndjs z?qq#cM)YNxQFE}HGNkfygblzD(EVFS=xAGJ!*rN#;}ltI$jl7@EtLQ-e|xkuqUzJD zf>9#^#?p*T6XXe^YHg4DWd-8a8Kvl^v<;B$5%JX#KpDH+NtffZCAb5~|<9iBk{vL=gfICGi?`$jp<(4PiKk6e93RSP4W(oo+ z1JqWQf;Q_6&l*wHM}2Bze|NjA20QJ3LIX+_8)AZ?-0l`hw-L?DHc}=Gbqn>lA`}Q2 zjD{J>3u?LQB9RiNNtwEeZ1yho^bTqD`J!q+R|hevb^2T^O}8=$>giG!>6UKq(0E~& zO^-&RZ%o=<440;ykxp&YrO|)^&<9=Vpn?n(nI@p4E_K654-wtke^r1b)XhB1`cy_r zUEfW1x_Y3{$M6uw9vV&a!H&*&My<-bm`GuiYYQvfe0Z$u2sEblH&{R>;01 zZj?-hX_$@j%p6dGbD-32w%ygqt7!0@q40d`nF;PrPRZ@B{oJ@-D)}9{Uj2@rrfAPWW(XN)nxTia)x-ry= zG?_7FwgGA1-J;H=wsy$?p(8C#;Yn^{q(l8w)GdX-T>=X$Lq-2r)F9^j%b zyviyxrh5M#CQqN9UrxaSXb2!e@IcfhCzWlQ06UN{K#AU_@eMgfEz^CxOs;IGs6Mq9as&J5%mKHJJkAAwTZ`5W5EMP8Tgz= zR@NWgl*BLTD80U<0`WmCi#oP9Ao7BAYXedSf@XV2BqBF+FHo8VZIY!lUPK>yl^^LS z@eFEArq!8Lp`KLDHqrX%5LsHhkG@L#;20QDe@!!eWU?U+Ktx?mTbfk+TRN{JQaVDj zKnl4&I46~=r*OAVv`1`Wo21sZUKv1hkh2OjfP{8bTey4-h%x|fMq~@RPGq;&M}0`C zgLC+HPowdv0l$rL%Uvi1zQB@}A6SMoO+zjT8AU9L3~o!{APHtg+z>0wkigk=LJiyL zfBC|+P0K>&Ff2np4|~B+GfdYo%^Lv^@zc!Urfu225n!*GS#+C=opNs3QbR=85-1S{ zJgvzZG_X{}e1uXkQ`&91!ixaafs`&tcWUKO4tR>t$1LvnbfV5Rwkws(3?OYFR3_l8 zNG17zh@eXhmji)Rs5VZ5m90J`@ z(ZOzX%E>WSx{Q}|s1d}norlZlhf5E_1B3>#ks{dAKpD(5^ig;U$h>ZNisK0Feyl>FYBP@IoW6u{VV2QgeauGGwe$q!YSCUfAO5^NL&>a|9<w1ea_Mjlc@)kfq4COnn! zSEfxD$v*-S>Pn#-g?Jxhe;)%*bBc`H;#@P)RlK2a|+sQSVrCk+=Rj1X6#{*5Iy&o=x22@UkqxL?s1T@ z#!@m&$$t;eQ??#ohOElw+a^zAtj^eN;CPxwQgA{|CGfwHO`f9|e@!L|Q~YO%nd_;= z9qb+0Xp8_0U=On#M~6*NtLW{&!@CBdu_kDf{a43H9pA%XdUEnip*f0JpyB=L$@F15 zo>mWX4gQqyGa@^eY1ADHs7Zc4s|A>V{h7Q!;Dv?wQm#sx_&>(=z-wP!6tch9ibhvHn z`PR`8T5Lt*9Z|JFcy!AaDv7$&v&?NsGwuiIYh|cCjF+jfM@U^MRt03WG!r@nU-Ong z{y;dK5mCCBf7PbxVQ3sv%&~~ZG6+brDD+?=VjiJjLk}USoX3b-fNBUrkaF0X5#9E83-yDn&E8zf2^mDM5N(e_RIdbra8y^jtJ-?$9#~%?T7Q z&Zz~=V884J!oy5(gjF=cl1)>VE@{kEJ;Tb$UHM^P#DKoXbX8n=+!LX%kQ|nXn5YPZ zENtC3E;v1PuSflFml;uG04G8dlb5lacY;6IAbG)-h}eyKkQTw-gzchzI^@Dqz?o2y z7HAqRe?mf1aH%YsJr+DE0Z>7GfsQbw0vBT*FwapNR!oCBvcl6CI3>G7UBu_ttf1=v z)!ueOTSkF21z@ZV3*qp#?U)v5$VLH@r3RERE$&>>FU&xhvMNA`q)Yi;)Qqemi>yv& zg~TvcRG^k563qf1y+(xOgc8AO0g;@*3{y4`e}yl}I&`N8H$UFe+>LGy!%IkX7<&Fo zQa4*Nbf|$AjW0Y8spG5klh}y_sS2Hv%mAnk!r@FuYg%v^W&S)+cEiA#Z($fbj1aUo z!vQlQ&aQ}vLXJ&W!&M$}RB#ijv;)^w;aCYb0!szNnCYpIG!`VK0k=oZiG#A3$f8it ze_Z4MWP_V5k`hQ#VJ%%}Es#t}?7*as)v8&9Fx))xYAqiw*1RhdA3qC`=e-Z~k0Vf)lVd8+244(+ob1ihA?`VV*YEoG@ z%i@bvf@#AS%EhFzk!KWotKlK-Ye|F>2(IV`qEbt04H25Ci3icVqDhHl@;;Gd)g|u1 zL;yV*DHUbT;&Y~E(;#`VRipyATcbho5ot|h0(cpND&tyZl4>;5V-KE)fbbW3e`}pN zK9Me?BgnC!v?$YzqfC8AjKtLP3+O7Q$|WKVCXzW5&(QfFv=q}LBeA9Kd77)_S~W)5 z;_!bK@YOBimjpnhWVRj*3)A27X;4)lb2|bFbr~2eNLMUyWl?W(4RR07848E?XohEi z3o&46=}sIMXuk9y;!td%Y`H}mf8erK2r!jwg#=en1GVWvs-|*HtX$X&4yJ>{Lt2bw7 zi$pc1HZ2sXAO^E@BQC_OMp|g9TSOOQN`YA`$QtAd6f~S3FeVwz4LFV>f69bp5!16= z>Um8wkmPKjETRfx_XP-KJ&Geyh(}tXFSQv1mmr3QfERIX zC2-h9aCq0r5VcKcq6baQ)>&KvsvT$!s4EZY}p)q zp5~~>EYAwz=ENW~xXX2rf2|%Y13hnQm<`xY3i_t$c%aOx#i;oj64P*qLl^-xSYUAA zR;iUH4PJ>MGqtZgh)`RHNL&iIHYn1NsZ3u;nyuk673qpO4Bcr0N))zkgDt>9wV;vn zTwTi*<*E5F{h7aUV`5=IL3f?% zGB+SWdrU6?=!YW&8uX>pBxoB{iyuDKo^5EH8oN>3yg&K3@AtFFf45-4|B|(f^l#8i z(w(e3O`V1C9>npswZFy`eE><6CY~*$zINg0jfU(y&p&xHYdmf#=~-p#f~>XNdDT+q zxb2h=FT_^l%^W#De=cRaN0&E^6BT>D*di6~72^lk?!kj!b0d-f!V+Z#TDs zvJ+M-r9ev4Dvh?QU9Y~-87#m?j;A?J9~b??p~4k~8w)Qj+=2bKeV~qzO&pQ(u=;t9 zKpO3F)7$sH+YhbmYhOV)QBPyN&;i=ze1e0YZrk&XN=%V)0~YDYc?GUjSW}tJ(GpccN0W2rw<~uV$N_>{TnlOtyNe=0&At^GGL4 zBDLt2O64R>Ou1GuELBtvac!pq0HD_pA)6(4))Yw)+1@qMBmouI0yhXOt@ZP3ZRJ;! z)N!Sw!kN&7e{Ub@sYNTO*yY&wY!CTH&A{8~HVre_ymj-c`6a5*bthV}V^r6+z1RuK zW(?vJ>+`mulvBzYZHGj#eLS@2c41dqm9Xp=)0-SGK(kh3wgo%lUQTN-8Ox; zRsT`me|uzUqkAx`<1gE6)=l4O{^^0u6=&04d5RNE(kiCiZaRMG1@w2{D`$3F_uaR- zdC$fBei*y_V+F5JrjiS+tJ>4@fT7%{Z~ZR4u@CI+?wvfkw!62xcJvf;UUS72(X*mc zpR6{j?BEkmuva`vf8#sYE5>&`>se>s^roqAe&Lri&%EsV(@cDTJup7|f&0&#shv6V;dh@o!%p1w z;%k@L+Tej>(=W`q`}xo4(QEv-r$2n%Z_w+O)2FYaYUHtcmx*1f!Diu7nx(HQ++Fxc zf8oKx-xr>MpW(z+a0=(w#~j^YY667;e=Th2O?|A)R$BvfDWKh?{T$~4B$cJDsee2& zPV90-&jH>o*`*r;@XcB~>JWurPb z?-V8it%UL1N`Ung$Eq7GWCtD?hJP}S>QKW-eR5o@ZZ>NrpI2?t!bB3wg!S5&Zp?w# zo6FUuvwxPa-}!c_O8TNLYIAlwDqeOWdm?yNg_H)X=xwM`D{>8Y4k#4gRwZvme~mgx zz^fNT{)}lI33M{7qa=weZBLOFT|N|fF+1ONf3_;u>tz7)BxDmn7b320wf=mrvOZUY zH8-5ys#>rluqegm#V(m_#YUYpUt3gb)?9n`itTypwwV)gSU1!H>AK8vLpM_!3rP@b zi$Fd<^+~FS4jruPcrI#Ekg5h-e<`M+OHVi3;ml7#wsJX`Subi8O>v*?wX2oh0t`mm zQbr((Q9z|0FNHOBscHD4L`s3_ueg>+3?e3N#}pMPKP7+5I+zioENy#EVp>lPo$`@v zw_AlS*_+*%qb`sAc-xd_Sgn(rZ#=qMBKAZi1`>U+7J^o9+F#hY!AD;7jUl-lNcHtKapM#ev;OF7~ z-wq~0_;QsM=S`5dMq|a+N)Hja#k6PUIbYf&oZ!SXiO0&ehKlYJ!#U9(iu1rgIf6Ci~q6R*kCI`@tb;AkLJ{|i{PU$L*dSX3rzqLx*rMu>? z#%r}>4NrKz8d3r^HtkBC@yCExCfN8xbu15&0%OZqXO$4hYK|598d+&2p9Z@lU^XG~ zg=ipEt<4cx$#^Y-f_L=0_kIE7;ei|-jWl>U z4s|jO2N^5|S`D@Zd%jqErB_3olGeMNCCBUe!WY+zNF#pVG%zcsC@*i-NU+8yV<@NB zZVJDbZbFrouVHL_=m52Cj9Hgb}+Tg2L0@G9BR z{0z~}iQaJ30I7)g?(O9Gi94g5FLICZEkv;7Wv>o)BY0OjMX4A zbTFj+kWVZp8^rXlLa)vt>i26aau?|Nj1SgG`@RT!D#`UCUTUK%z;dscu>mZ4mq!cp z2#po&=wLAZe~rQ5?!n;J!Qh_3U_Zv=^ZauKzfjj%P@z7mHd5HPGnOYc*VCe>Q_sN{ ze%Pz9%@^MAjGM)kV(psq4qPCZf29*&dY+67e!~^dd{=X`!JC^+ejh(?bnkPHzUY~0 zr9S>p@8D6F1jEU_t1nKQ3)O0~xgW3pZ~S|Omy>ptfBRBHGlfP@lbJq-I`7;h5F^)O zwbL2idOCzAPqkUeffiN3a{wtF4w!p-_J=xK>O|`1zG1mS+#9l2Sow~Znn|Tk16MRX zE9GSU#ZiUEps4XP48X9e#wOXLfrDIXHex8rNUV-v+8YT?c`~)E_N-NM!q9enJ6gWx zFxhCze==RiI=oQwONJQ=>v7_<(lgS?lun6Ews2uPB`vm9o3AE5Da6T~<&Zd1+%*?~ zG2oEh=9YbnEMw{4bdB&WQkr}9c8jF0TQXc2)uyKc)7py+tkdaikdidtC*^6oR@6y< zn#*G%R@tyL0z`2o*EWy>TBEO3c*XK<;{Z`^e`ghrrJN^$@z`2+B|C}pMUTFqXrlo`Jqz=gKwp$xS+1sNYHCCJ)mz8BH_7Bpw zf5ty7m(3$T@i*B0cYm+v3Q08vb7a&~msgUbN#z6&ODqm~7_-u6Y{{>dwk^+oRNDL{ z^j1^b?6l?Fd0}6PYV_;xlV17Y4X4ycVT5|Q#1B2`% zWGAiML~p-R8lM2=z=HMw005Infhm8W3X%%+3la-F3v3If3&;!z42}%04L%Lr4j2wL z4t@^u4=4{%4|Wg259AO05H=Bf5tb4N5{wfp6?PVa7RVQN7$6xE8JrpF8l)QL8%i6B z8^9af8~7Z09Q++F9jYD*9?TyMA7CN=BWfe~Bse5gByuIHCju(eD*`J#D^`Cib}Pax z7%Y}8EG=d&o-NETA}@9@2r%d|urf9?JTq1_5Hu<@Vl|94wl+{VWH|0QBssP^3Ow#T zdOjvTZa(loOh4Q}0zh0qz(Gtwv_ecm=0gZW>O>Ais6`k>gj29o4piP%5>+r&Y*m_7 z;8$2zepqr@PFapxK3Z~H8~`8yc${NkWME*J#KppJo&f}yfS3yi85sV9`3wLnu>yv( zkAfuue`S~CHV~Xzv02a#7(#vx8PRHe{dUa#~rv6CvX?;#yz+f_u+m#fCupq z9>ybh6p!I?Jb@?i6rRR2coxs$dAxuZ@e*FfD|i*J;dQ)$lXw$v;cdKwckv$H#|QWj zAK_zsf<1f+h6)@4gc=Q6NOYJ(p@)Wn#XcN70tPIw#0qPi!e{s#U*Jo8g|G1qzQuR= ze;z;JNBo4J@e6*%Z}=U5;7|O8zwr-Fb*4b0=*`Nc9;xXIxHRNgYZGEjb$cqTX;W zYPOKEN?A)s74uY#No`hn9SM+2YokNb$dJd?4%Jp;lE$3K8^?sAF-YZ$7;L+*f3n66 zI;72@Y}qnCS4=l)%X&3!<0wc=-T0zqeojio{T1c088aV}mR4ySdR53q8%JVCDmD1v zSfrRmJsV1;u1yv-kUZ^}pa=^#9bvf2PRCvYb-S zqQ86)cxx-)#G^Rx}zXAG5(1eyHLpO8lb9 z{8H9oJ4Ao6vH2v#DFzc|3V!3uG%RH^4QqQw { + const queryString = current.split('='); + prev[queryString[0]] = queryString[1]; + return prev; + }, {}); } export const onDidChangeSessions = new vscode.EventEmitter(); +export const REFRESH_NETWORK_FAILURE = 'Network failure'; + +class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { + public handleUri(uri: vscode.Uri) { + this.fire(uri); + } +} + export class AzureActiveDirectoryService { private _tokens: IToken[] = []; private _refreshTimeouts: Map = new Map(); + private _uriHandler: UriEventHandler; + + constructor() { + this._uriHandler = new UriEventHandler(); + vscode.window.registerUriHandler(this._uriHandler); + } public async initialize(): Promise { const storedData = await keychain.getToken(); @@ -57,7 +82,21 @@ export class AzureActiveDirectoryService { try { await this.refreshToken(session.refreshToken, session.scope); } catch (e) { - await this.logout(session.id); + if (e.message === REFRESH_NETWORK_FAILURE) { + const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope); + if (!didSucceedOnRetry) { + this._tokens.push({ + accessToken: undefined, + refreshToken: session.refreshToken, + accountName: session.accountName, + scope: session.scope, + sessionId: session.id + }); + this.pollForReconnect(session.id, session.refreshToken, session.scope); + } + } else { + await this.logout(session.id); + } } }); @@ -79,7 +118,8 @@ export class AzureActiveDirectoryService { return { id: token.sessionId, refreshToken: token.refreshToken, - scope: token.scope + scope: token.scope, + accountName: token.accountName }; }); @@ -100,7 +140,11 @@ export class AzureActiveDirectoryService { await this.refreshToken(session.refreshToken, session.scope); didChange = true; } catch (e) { - await this.logout(session.id); + if (e.message === REFRESH_NETWORK_FAILURE) { + // Ignore, will automatically retry on next poll. + } else { + await this.logout(session.id); + } } } }); @@ -136,11 +180,11 @@ export class AzureActiveDirectoryService { }, 1000 * 30); } - private convertToSession(token: IToken): vscode.Session { + private convertToSession(token: IToken): vscode.AuthenticationSession { return { id: token.sessionId, - accessToken: token.accessToken, - displayName: token.displayName, + accessToken: () => !token.accessToken ? Promise.reject('Unavailable due to network problems') : Promise.resolve(token.accessToken), + accountName: token.accountName, scopes: token.scope.split(' ') }; } @@ -154,12 +198,18 @@ export class AzureActiveDirectoryService { } } - get sessions(): vscode.Session[] { + get sessions(): vscode.AuthenticationSession[] { return this._tokens.map(token => this.convertToSession(token)); } public async login(scope: string): Promise { Logger.info('Logging in...'); + + if (vscode.env.uiKind === vscode.UIKind.Web) { + await this.loginWithoutLocalServer(scope); + return; + } + const nonce = crypto.randomBytes(16).toString('base64'); const { server, redirectPromise, codePromise } = createServer(nonce); @@ -206,6 +256,13 @@ export class AzureActiveDirectoryService { res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` }); res.end(); } + } catch (e) { + Logger.error(e.message); + + // If the error was about starting the server, try directly hitting the login endpoint instead + if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { + await this.loginWithoutLocalServer(scope); + } } finally { setTimeout(() => { server.close(); @@ -213,28 +270,101 @@ export class AzureActiveDirectoryService { } } + private getCallbackEnvironment(callbackUri: vscode.Uri): string { + switch (callbackUri.authority) { + case 'online.visualstudio.com,': + return 'vso'; + case 'online-ppe.core.vsengsaas.visualstudio.com': + return 'vsoppe,'; + case 'online.dev.core.vsengsaas.visualstudio.com': + return 'vsodev,'; + default: + return ''; + } + } + + private async loginWithoutLocalServer(scope: string): Promise { + const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.vscode-account`)); + const nonce = crypto.randomBytes(16).toString('base64'); + const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' ? 443 : 80); + const callbackEnvironment = this.getCallbackEnvironment(callbackUri); + const state = `${callbackEnvironment}${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`; + const signInUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize`; + let uri = vscode.Uri.parse(signInUrl); + const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + uri = uri.with({ + query: `response_type=code&client_id=${encodeURIComponent(clientId)}&response_mode=query&redirect_uri=${redirectUrl}&state=${state}&scope=${scope}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` + }); + vscode.env.openExternal(uri); + + const timeoutPromise = new Promise((_: (value: IToken) => void, reject) => { + const wait = setTimeout(() => { + clearTimeout(wait); + reject('Login timed out.'); + }, 1000 * 60 * 5); + }); + + return Promise.race([this.handleCodeResponse(state, codeVerifier, scope), timeoutPromise]); + } + + private async handleCodeResponse(state: string, codeVerifier: string, scope: string) { + let uriEventListener: vscode.Disposable; + return new Promise((resolve: (value: IToken) => void, reject) => { + uriEventListener = this._uriHandler.event(async (uri: vscode.Uri) => { + try { + const query = parseQuery(uri); + const code = query.code; + + if (query.state !== state) { + throw new Error('State does not match.'); + } + + const token = await this.exchangeCodeForToken(code, codeVerifier, scope); + this.setToken(token, scope); + + resolve(token); + } catch (err) { + reject(err); + } + }); + }).then(result => { + uriEventListener.dispose(); + return result; + }).catch(err => { + uriEventListener.dispose(); + throw err; + }); + } + private async setToken(token: IToken, scope: string): Promise { - const existingToken = this._tokens.findIndex(t => t.sessionId === token.sessionId); - if (existingToken) { - this._tokens.splice(existingToken, 1, token); + const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); + if (existingTokenIndex > -1) { + this._tokens.splice(existingTokenIndex, 1, token); } else { this._tokens.push(token); } - const existingTimeout = this._refreshTimeouts.get(token.sessionId); - if (existingTimeout) { - clearTimeout(existingTimeout); - } + this.clearSessionTimeout(token.sessionId); - this._refreshTimeouts.set(token.sessionId, setTimeout(async () => { - try { - await this.refreshToken(token.refreshToken, scope); - } catch (e) { - await this.logout(token.sessionId); - } finally { - onDidChangeSessions.fire(); - } - }, 1000 * (parseInt(token.expiresIn) - 10))); + if (token.expiresIn) { + this._refreshTimeouts.set(token.sessionId, setTimeout(async () => { + try { + await this.refreshToken(token.refreshToken, scope); + onDidChangeSessions.fire(); + } catch (e) { + if (e.message === REFRESH_NETWORK_FAILURE) { + const didSucceedOnRetry = await this.handleRefreshNetworkError(token.sessionId, token.refreshToken, scope); + if (!didSucceedOnRetry) { + this.pollForReconnect(token.sessionId, token.refreshToken, token.scope); + } + } else { + await this.logout(token.sessionId); + onDidChangeSessions.fire(); + } + } + }, 1000 * (parseInt(token.expiresIn) - 30))); + } this.storeTokenData(); } @@ -247,8 +377,8 @@ export class AzureActiveDirectoryService { accessToken: json.access_token, refreshToken: json.refresh_token, scope, - sessionId: claims.tid + (claims.oid || claims.altsecid) + scope, - displayName: claims.email || claims.unique_name || 'user@example.com' + sessionId: `${claims.tid}/${(claims.oid || (claims.altsecid || '' + claims.ipd || ''))}/${scope}`, + accountName: claims.email || claims.unique_name || 'user@example.com' }; } @@ -282,8 +412,10 @@ export class AzureActiveDirectoryService { }); result.on('end', () => { if (result.statusCode === 200) { + Logger.info('Exchanging login code for token success'); resolve(this.getTokenFromResponse(buffer, scope)); } else { + Logger.error('Exchanging login code for token failed'); reject(new Error('Unable to login.')); } }); @@ -344,29 +476,80 @@ export class AzureActiveDirectoryService { post.end(); post.on('error', err => { Logger.error(err.message); - reject(err); + reject(new Error(REFRESH_NETWORK_FAILURE)); }); }); } - public async logout(sessionId: string) { - Logger.info(`Logging out of session '${sessionId}'`); + private clearSessionTimeout(sessionId: string): void { + const timeout = this._refreshTimeouts.get(sessionId); + if (timeout) { + clearTimeout(timeout); + this._refreshTimeouts.delete(sessionId); + } + } + + private removeInMemorySessionData(sessionId: string) { const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); if (tokenIndex > -1) { this._tokens.splice(tokenIndex, 1); } + this.clearSessionTimeout(sessionId); + } + + private pollForReconnect(sessionId: string, refreshToken: string, scope: string): void { + this.clearSessionTimeout(sessionId); + + this._refreshTimeouts.set(sessionId, setTimeout(async () => { + try { + await this.refreshToken(refreshToken, scope); + } catch (e) { + this.pollForReconnect(sessionId, refreshToken, scope); + } + }, 1000 * 60 * 30)); + } + + private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1): Promise { + return new Promise((resolve, _) => { + if (attempts === 3) { + Logger.error('Token refresh failed after 3 attempts'); + return resolve(false); + } + + if (attempts === 1) { + const token = this._tokens.find(token => token.sessionId === sessionId); + if (token) { + token.accessToken = undefined; + } + + onDidChangeSessions.fire(); + } + + const delayBeforeRetry = 5 * attempts * attempts; + + this.clearSessionTimeout(sessionId); + + this._refreshTimeouts.set(sessionId, setTimeout(async () => { + try { + await this.refreshToken(refreshToken, scope); + return resolve(true); + } catch (e) { + return resolve(await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1)); + } + }, 1000 * delayBeforeRetry)); + }); + } + + public async logout(sessionId: string) { + Logger.info(`Logging out of session '${sessionId}'`); + this.removeInMemorySessionData(sessionId); + if (this._tokens.length === 0) { await keychain.deleteToken(); } else { this.storeTokenData(); } - - const timeout = this._refreshTimeouts.get(sessionId); - if (timeout) { - clearTimeout(timeout); - this._refreshTimeouts.delete(sessionId); - } } public async clearSessions() { diff --git a/extensions/vscode-account/src/authServer.ts b/extensions/vscode-account/src/authServer.ts index 074b7ddd93..e0d91f839c 100644 --- a/extensions/vscode-account/src/authServer.ts +++ b/extensions/vscode-account/src/authServer.ts @@ -87,8 +87,8 @@ export async function startServer(server: http.Server): Promise { } }); - server.on('error', err => { - reject(err); + server.on('error', _ => { + reject(new Error('Error listening to server')); }); server.on('close', () => { diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 3b0007ae49..1bd358034f 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; -export async function activate(context: vscode.ExtensionContext) { +export async function activate(_: vscode.ExtensionContext) { const loginService = new AzureActiveDirectoryService(); diff --git a/extensions/vscode-account/src/keychain.ts b/extensions/vscode-account/src/keychain.ts index 0e49864035..d05285eb84 100644 --- a/extensions/vscode-account/src/keychain.ts +++ b/extensions/vscode-account/src/keychain.ts @@ -7,6 +7,7 @@ // how we load it import * as keytarType from 'keytar'; import { env } from 'vscode'; +import Logger from './logger'; function getKeytar(): Keytar | undefined { try { @@ -44,22 +45,27 @@ export class Keychain { return await this.keytar.setPassword(SERVICE_ID, ACCOUNT_ID, token); } catch (e) { // Ignore + Logger.error(`Setting token failed: ${e}`); } } - async getToken() { + async getToken(): Promise { try { return await this.keytar.getPassword(SERVICE_ID, ACCOUNT_ID); } catch (e) { // Ignore + Logger.error(`Getting token failed: ${e}`); + return Promise.resolve(undefined); } } - async deleteToken() { + async deleteToken(): Promise { try { return await this.keytar.deletePassword(SERVICE_ID, ACCOUNT_ID); } catch (e) { // Ignore + Logger.error(`Deleting token failed: ${e}`); + return Promise.resolve(undefined); } } } diff --git a/extensions/vscode-account/src/typings/refs.d.ts b/extensions/vscode-account/src/typings/refs.d.ts new file mode 100644 index 0000000000..c82a621bfa --- /dev/null +++ b/extensions/vscode-account/src/typings/refs.d.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/// diff --git a/extensions/vscode-account/src/vscode.proposed.d.ts b/extensions/vscode-account/src/vscode.proposed.d.ts deleted file mode 100644 index d06d51c2c4..0000000000 --- a/extensions/vscode-account/src/vscode.proposed.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * This is the place for API experiments and proposals. - * These API are NOT stable and subject to change. They are only available in the Insiders - * distribution and CANNOT be used in published extensions. - * - * To test these API in local environment: - * - Use Insiders release of VS Code. - * - Add `"enableProposedApi": true` to your package.json. - * - Copy this file to your project. - */ - -declare module 'vscode' { - - export interface Session { - id: string; - accessToken: string; - displayName: string; - scopes: string[] - } - - export interface AuthenticationProvider { - readonly id: string; - readonly displayName: string; - readonly onDidChangeSessions: Event; - - /** - * Returns an array of current sessions. - */ - getSessions(): Promise>; - - /** - * Prompts a user to login. - */ - login(scopes: string[]): Promise; - logout(sessionId: string): Promise; - } - - export namespace authentication { - export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; - - /** - * Fires with the provider id that was registered or unregistered. - */ - export const onDidRegisterAuthenticationProvider: Event; - export const onDidUnregisterAuthenticationProvider: Event; - - export const providers: ReadonlyArray; - } - - // #region Ben - extension auth flow (desktop+web) - - export namespace env { - - export function asExternalUri(target: Uri): Thenable - } -} diff --git a/extensions/vscode-account/tsconfig.json b/extensions/vscode-account/tsconfig.json index 46be6dc958..1225709307 100644 --- a/extensions/vscode-account/tsconfig.json +++ b/extensions/vscode-account/tsconfig.json @@ -1,24 +1,13 @@ { + "extends": "../shared.tsconfig.json", "compilerOptions": { - "module": "commonjs", - "target": "es6", - "outDir": "out", - "lib": [ - "es6", - "es2016", - "dom" - ], + "outDir": "./out", + "experimentalDecorators": true, "typeRoots": [ - "node_modules/@types", - "src/typings" - ], - "sourceMap": true, - "rootDir": "src", - "strict": true, - "noImplicitAny": true + "./node_modules/@types" + ] }, - "exclude": [ - "node_modules", - ".vscode-test" + "include": [ + "src/**/*" ] } diff --git a/extensions/vscode-account/yarn.lock b/extensions/vscode-account/yarn.lock index 4fc295de4b..3acdda242e 100644 --- a/extensions/vscode-account/yarn.lock +++ b/extensions/vscode-account/yarn.lock @@ -30,11 +30,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== -"@types/vscode@^1.41.0": - version "1.41.0" - resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.41.0.tgz#b0d75920220f84e07093285e59180c0f11d336cd" - integrity sha512-7SfeY5u9jgiELwxyLB3z7l6l/GbN9CqpCQGkcRlB7tKRFBxzbz2PoBfGrLxI1vRfUCIq5+hg5vtDHExwq5j3+A== - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 152a4c9159..da71634c2e 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -26,21 +26,25 @@ "vscode": "1.1.5" }, "contributes": { - "tokenTypes": [ + "semanticTokenTypes": [ { "id": "testToken", "description": "A test token" } ], - "tokenModifiers": [ + "semanticTokenModifiers": [ { "id": "testModifier", "description": "A test modifier" } ], - "tokenStyleDefaults": [ + "semanticTokenStyleDefaults": [ { - "selector": "testToken.testModifier", + "selector": "testToken", + "scope": [ "entity.name.function.special" ] + }, + { + "selector": "*.testModifier", "light": { "fontStyle": "bold" }, @@ -50,7 +54,6 @@ "highContrast": { "fontStyle": "bold" } - } ] } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index dc1fa7c2de..1d6447cfe6 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.7.5: - version "3.7.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== +typescript@^3.8.1-rc: + version "3.8.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.1-rc.tgz#f94333c14da70927ccd887be2e91be652a9a09f6" + integrity sha512-aOIe066DyZn2uYIiND6fXMUUJ70nxwu/lKhA92QuQzXyC86fr0ywo1qvO8l2m0EnDcfjprYPuFRgNgDj7U2GlQ== diff --git a/package.json b/package.json index 761a657e25..41a0cd09af 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "private": true, "scripts": { "test": "mocha", + "test-browser": "node test/unit/browser/index.js", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "gulp compile --max_old_space_size=4095", "watch": "gulp watch --max_old_space_size=4095", "watch-client": "gulp watch-client --max_old_space_size=4095", - "monaco-editor-test": "mocha --only-monaco-editor", + "mocha": "mocha test/unit/node/all.js --delay", "precommit": "node build/gulpfile.hygiene.js", "gulp": "gulp --max_old_space_size=8192", "electron": "node build/lib/electron", @@ -47,7 +48,7 @@ "applicationinsights": "1.0.8", "chart.js": "^2.6.0", "chokidar": "3.2.3", - "graceful-fs": "4.1.11", + "graceful-fs": "4.2.3", "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", @@ -77,10 +78,11 @@ "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.9", "vscode-textmate": "4.4.0", - "xterm": "4.4.0-beta.15", - "xterm-addon-search": "0.4.0-beta4", + "xterm": "4.4.0", + "xterm-addon-search": "0.4.0", + "xterm-addon-unicode11": "0.1.1", "xterm-addon-web-links": "0.2.1", - "xterm-addon-webgl": "0.5.0-beta.7", + "xterm-addon-webgl": "0.5.0", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.8.4" @@ -91,6 +93,7 @@ "@types/chart.js": "2.7.57", "@types/chokidar": "2.1.3", "@types/cookie": "^0.3.3", + "@types/debug": "^4.1.5", "@types/graceful-fs": "4.1.2", "@types/http-proxy-agent": "^2.0.1", "@types/iconv-lite": "0.0.1", @@ -117,11 +120,10 @@ "coveralls": "^2.11.11", "cson-parser": "^1.3.3", "debounce": "^1.0.0", - "electron": "6.1.6", + "electron": "7.1.11", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", - "express": "^4.13.1", "fancy-log": "^1.3.3", "fast-plist": "0.1.2", "glob": "^5.0.13", @@ -163,6 +165,7 @@ "opn": "^6.0.0", "optimist": "0.3.5", "p-all": "^1.0.0", + "playwright": "^0.10.0", "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", @@ -172,7 +175,7 @@ "temp-write": "^3.4.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", - "typescript": "3.8.0-beta", + "typescript": "^3.8.1-rc", "typescript-formatter": "7.1.0", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", @@ -181,7 +184,8 @@ "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.3.8", - "webpack-stream": "^5.1.1" + "webpack-stream": "^5.1.1", + "yaserver": "^0.2.0" }, "repository": { "type": "git", diff --git a/remote/package.json b/remote/package.json index 95ea409067..3481542d70 100644 --- a/remote/package.json +++ b/remote/package.json @@ -5,7 +5,7 @@ "applicationinsights": "1.0.8", "chokidar": "3.2.3", "cookie": "^0.4.0", - "graceful-fs": "4.1.11", + "graceful-fs": "4.2.3", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", "iconv-lite": "0.5.0", @@ -20,10 +20,11 @@ "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", "vscode-textmate": "4.4.0", - "xterm": "4.4.0-beta.15", - "xterm-addon-search": "0.4.0-beta4", + "xterm": "4.4.0", + "xterm-addon-search": "0.4.0", + "xterm-addon-unicode11": "0.1.1", "xterm-addon-web-links": "0.2.1", - "xterm-addon-webgl": "0.5.0-beta.7", + "xterm-addon-webgl": "0.5.0", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index 5bf2c6c7d9..70a55a9074 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,9 +5,10 @@ "onigasm-umd": "2.2.5", "semver-umd": "^5.5.5", "vscode-textmate": "4.4.0", - "xterm": "4.4.0-beta.15", - "xterm-addon-search": "0.4.0-beta4", + "xterm": "4.4.0", + "xterm-addon-search": "0.4.0", + "xterm-addon-unicode11": "0.1.1", "xterm-addon-web-links": "0.2.1", - "xterm-addon-webgl": "0.5.0-beta.7" + "xterm-addon-webgl": "0.5.0" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 12afd3c3d8..b4c1b7d0b6 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -31,22 +31,27 @@ vscode-textmate@4.4.0: dependencies: oniguruma "^7.2.0" -xterm-addon-search@0.4.0-beta4: - version "0.4.0-beta4" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779" - integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA== +xterm-addon-search@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0.tgz#a7beadb3caa7330eb31fb1f17d92de25537684a1" + integrity sha512-g07qb/Z4aSfrQ25e6Z6rz6KiExm2DvesQXkx+eA715VABBr5VM/9Jf0INoCiDSYy/nn7rpna+kXiGVJejIffKg== + +xterm-addon-unicode11@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.1.1.tgz#b209ef137db38096f68636af4ef4d0c0acba85ad" + integrity sha512-z6vJTL+dpNljwAYzYoyDjJP8A2XjZuEosl0sRa+FGRf3jEyEVWquDM53MfUd1ztVdAPQ839qR6eYK1BXV04Bhw== xterm-addon-web-links@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm-addon-webgl@0.5.0-beta.7: - version "0.5.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0-beta.7.tgz#b7b95a362e942ad6f86fa286d7b7bd8ee3e7cf67" - integrity sha512-v6aCvhm1C6mvaurGwUYQfyhb2cAUyuVnzf3Ob/hy5ebtyzUj4wW0N9NbqDEJk67UeMi1lV2xZqrO5gNeTpVqFA== +xterm-addon-webgl@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0.tgz#c1031dc7599cce3509824643ab5f15361c928e3e" + integrity sha512-hQrvabKCnwXFaEZ+YtoJM9Pm0CIBXL5KSwoU+RiGStU3KYTAcqYP2GsH3dWdvKX6kTWhWLS81dtDsGkfbOciuA== -xterm@4.4.0-beta.15: - version "4.4.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.15.tgz#5897bf79d29d1a2496ccd54665aded28c341b1cc" - integrity sha512-Dvz1CMCYKeoxPF7uIDznbRgUA2Mct49Bq93K2nnrDU0pDMM3Sf1t9fkEyz59wxSx5XEHVdLS80jywsz4sjXBjQ== +xterm@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0.tgz#5915d3c4c8800fadbcf555a0a603c672ab9df589" + integrity sha512-JGIpigWM3EBWvnS3rtBuefkiToIILSK1HYMXy4BCsUpO+O4UeeV+/U1AdAXgCB6qJrnPNb7yLgBsVCQUNMteig== diff --git a/remote/yarn.lock b/remote/yarn.lock index 387226ccd2..bc865255b6 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -155,12 +155,7 @@ glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -graceful-fs@4.1.11: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= - -graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@4.2.3, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== @@ -418,25 +413,30 @@ vscode-windows-registry@1.0.2: resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a" integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA== -xterm-addon-search@0.4.0-beta4: - version "0.4.0-beta4" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0-beta4.tgz#7762ea342c6b4f5e824d83466bd93793c9d7d779" - integrity sha512-TIbEBVhydGIxcyu/CfKJbD+BKHisMGbkAfaWlCPaWis2Xmw8yE7CKrCPn+lhZYl1MdjDVEmb8lQI6WetbC2OZA== +xterm-addon-search@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.4.0.tgz#a7beadb3caa7330eb31fb1f17d92de25537684a1" + integrity sha512-g07qb/Z4aSfrQ25e6Z6rz6KiExm2DvesQXkx+eA715VABBr5VM/9Jf0INoCiDSYy/nn7rpna+kXiGVJejIffKg== + +xterm-addon-unicode11@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.1.1.tgz#b209ef137db38096f68636af4ef4d0c0acba85ad" + integrity sha512-z6vJTL+dpNljwAYzYoyDjJP8A2XjZuEosl0sRa+FGRf3jEyEVWquDM53MfUd1ztVdAPQ839qR6eYK1BXV04Bhw== xterm-addon-web-links@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.2.1.tgz#6d1f2ce613e09870badf17615e7a1170a31542b2" integrity sha512-2KnHtiq0IG7hfwv3jw2/jQeH1RBk2d5CH4zvgwQe00rLofSJqSfgnJ7gwowxxpGHrpbPr6Lv4AmH/joaNw2+HQ== -xterm-addon-webgl@0.5.0-beta.7: - version "0.5.0-beta.7" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0-beta.7.tgz#b7b95a362e942ad6f86fa286d7b7bd8ee3e7cf67" - integrity sha512-v6aCvhm1C6mvaurGwUYQfyhb2cAUyuVnzf3Ob/hy5ebtyzUj4wW0N9NbqDEJk67UeMi1lV2xZqrO5gNeTpVqFA== +xterm-addon-webgl@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.5.0.tgz#c1031dc7599cce3509824643ab5f15361c928e3e" + integrity sha512-hQrvabKCnwXFaEZ+YtoJM9Pm0CIBXL5KSwoU+RiGStU3KYTAcqYP2GsH3dWdvKX6kTWhWLS81dtDsGkfbOciuA== -xterm@4.4.0-beta.15: - version "4.4.0-beta.15" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0-beta.15.tgz#5897bf79d29d1a2496ccd54665aded28c341b1cc" - integrity sha512-Dvz1CMCYKeoxPF7uIDznbRgUA2Mct49Bq93K2nnrDU0pDMM3Sf1t9fkEyz59wxSx5XEHVdLS80jywsz4sjXBjQ== +xterm@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.4.0.tgz#5915d3c4c8800fadbcf555a0a603c672ab9df589" + integrity sha512-JGIpigWM3EBWvnS3rtBuefkiToIILSK1HYMXy4BCsUpO+O4UeeV+/U1AdAXgCB6qJrnPNb7yLgBsVCQUNMteig== yauzl@^2.9.2: version "2.10.0" diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 61ba87ef63..20bbbb03b9 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -3,7 +3,7 @@ setlocal pushd %~dp0\.. -set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5% +set VSCODEUSERDATADIR=%TEMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,2% :: Figure out which Electron to use for running tests if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( @@ -12,7 +12,7 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1 - echo "Running integration tests out of sources." + echo Running integration tests out of sources. ) else ( :: Run from a built: need to compile all test extensions call yarn gulp compile-extension:vscode-api-tests @@ -22,13 +22,14 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( call yarn gulp compile-extension:css-language-features-server call yarn gulp compile-extension:html-language-features-server call yarn gulp compile-extension:json-language-features-server + call yarn gulp compile-extension:git :: Configuration for more verbose output set VSCODE_CLI=1 set ELECTRON_ENABLE_LOGGING=1 set ELECTRON_ENABLE_STACK_DUMPING=1 - echo "Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build." + echo Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build. ) :: Integration & performance tests in AMD @@ -53,6 +54,12 @@ if %errorlevel% neq 0 exit /b %errorlevel% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% if %errorlevel% neq 0 exit /b %errorlevel% +for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i +set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% +mkdir %GITWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% + :: Tests in commonJS (HTML, CSS, JSON language server tests...) call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\*\server\out\test\**\*.test.js if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 32498a378f..691dcf1077 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -4,13 +4,12 @@ set -e if [[ "$OSTYPE" == "darwin"* ]]; then realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; } ROOT=$(dirname $(dirname $(realpath "$0"))) - VSCODEUSERDATADIR=`mktemp -d -t 'myuserdatadir'` else ROOT=$(dirname $(dirname $(readlink -f $0))) - VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` LINUX_NO_SANDBOX="--no-sandbox" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi +VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` cd $ROOT # Figure out which Electron to use for running tests @@ -29,6 +28,7 @@ else yarn gulp compile-extension:css-language-features-server yarn gulp compile-extension:html-language-features-server yarn gulp compile-extension:json-language-features-server + yarn gulp compile-extension:git # Configuration for more verbose output export VSCODE_CLI=1 @@ -47,6 +47,7 @@ fi "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR mkdir -p $ROOT/extensions/emmet/test-fixtures "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --user-data-dir=$VSCODEUSERDATADIR diff --git a/scripts/test.bat b/scripts/test.bat index 2906a09f9b..82295d8987 100644 --- a/scripts/test.bat +++ b/scripts/test.bat @@ -24,7 +24,7 @@ if "%ADS_TEST_GREP%" == "" ( :: Run tests set ELECTRON_ENABLE_LOGGING=1 -%CODE% .\test\electron\index.js %* +%CODE% .\test\unit\electron\index.js %* popd diff --git a/scripts/test.sh b/scripts/test.sh index 3fcc904302..31008811e1 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -36,10 +36,10 @@ if [[ "$OSTYPE" == "darwin"* ]] || [[ "$AGENT_OS" == "Darwin"* ]]; then cd $ROOT ; ulimit -n 4096 ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/electron/index.js $PASSED_ARGS + test/unit/electron/index.js "$@" else cd $ROOT ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/electron/index.js --no-sandbox $PASSED_ARGS # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. + test/unit/electron/index.js --no-sandbox "$@" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi diff --git a/src/sql/workbench/contrib/charts/test/browser/chartView.test.ts b/src/sql/workbench/contrib/charts/test/browser/chartView.test.ts index b8819a2ed9..c839a3fc34 100644 --- a/src/sql/workbench/contrib/charts/test/browser/chartView.test.ts +++ b/src/sql/workbench/contrib/charts/test/browser/chartView.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { ChartView } from 'sql/workbench/contrib/charts/browser/chartView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { TestLayoutService } from 'vs/workbench/test/workbenchTestServices'; +import { TestLayoutService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IThemeService } from 'vs/platform/theme/common/themeService'; diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts index f7da1c77d0..a4a919c23f 100644 --- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts +++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts @@ -22,7 +22,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { TestEditorService, TestDialogService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEditorService, TestDialogService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; import { TestQueryModelService } from 'sql/workbench/services/query/test/common/testQueryModelService'; @@ -393,7 +393,7 @@ suite('commandLineService tests', () => { querymodelService.setup(c => c.onRunQueryComplete).returns(() => Event.None); const instantiationService = new TestInstantiationService(); let uri = URI.file(args._[0]); - const untitledEditorInput = new UntitledTextEditorInput(uri, false, '', '', '', instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined); + const untitledEditorInput = new UntitledTextEditorInput(uri, false, undefined, undefined, undefined, instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); const queryInput = new UntitledQueryEditorInput(undefined, untitledEditorInput, undefined, connectionManagementService.object, querymodelService.object, configurationService.object); queryInput.state.connected = true; const editorService: TypeMoq.Mock = TypeMoq.Mock.ofType(TestEditorService, TypeMoq.MockBehavior.Strict); diff --git a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts index 835fd58c29..b65e123df1 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet.ts @@ -15,7 +15,6 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ConnectionViewletPanel } from 'sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel'; import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -146,11 +145,6 @@ export class DataExplorerViewPaneContainer extends ViewPaneContainer { return actions; } - protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewPane[] { - const addedViews = super.onDidAddViews(added); - return addedViews; - } - protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane { let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewPane; this._register(viewletPanel); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts index 172ffe2a09..044bd9d17c 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { TestEnvironmentService, TestLifecycleService, TestStorageService, TestTextFileService, workbenchInstantiationService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEnvironmentService, TestLifecycleService, TestStorageService, TestTextFileService, workbenchInstantiationService, TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Range } from 'vs/editor/common/core/range'; import { nb } from 'azdata'; import { Emitter } from 'vs/base/common/event'; diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts index 0f81b7e8eb..a3a213f6a6 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookInput.test.ts @@ -6,7 +6,7 @@ import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; import { nb } from 'azdata'; -import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput'; @@ -48,7 +48,7 @@ suite('Notebook Input', function (): void { let untitledNotebookInput: UntitledNotebookInput; setup(() => { - untitledTextInput = new UntitledTextEditorInput(untitledUri, false, '', '', '', instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined); + untitledTextInput = new UntitledTextEditorInput(untitledUri, false, undefined, undefined, undefined, instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); untitledNotebookInput = new UntitledNotebookInput( testTitle, untitledUri, untitledTextInput, undefined, instantiationService, mockNotebookService.object, mockExtensionService.object); @@ -169,7 +169,7 @@ suite('Notebook Input', function (): void { assert.ok(untitledNotebookInput.matches(untitledNotebookInput), 'Input should match itself.'); let otherTestUri = URI.from({ scheme: Schemas.untitled, path: 'OtherTestPath' }); - let otherTextInput = new UntitledTextEditorInput(otherTestUri, false, '', '', '', instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined); + let otherTextInput = new UntitledTextEditorInput(otherTestUri, false, undefined, undefined, undefined, instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); let otherInput = instantiationService.createInstance(UntitledNotebookInput, 'OtherTestInput', otherTestUri, otherTextInput); assert.strictEqual(untitledNotebookInput.matches(otherInput), false, 'Input should not match different input.'); diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts index 55d31f14f0..5af9275334 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts @@ -11,7 +11,7 @@ import * as tempWrite from 'temp-write'; import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager'; import { CellTypes } from 'sql/workbench/contrib/notebook/common/models/contracts'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IFileService, IReadFileOptions, IFileContent, IWriteFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import * as pfs from 'vs/base/node/pfs'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts index 55692f3615..3b68fec9f0 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts @@ -25,7 +25,7 @@ import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/t import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ClientSession } from 'sql/workbench/contrib/notebook/browser/models/clientSession'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { NotebookRange } from 'sql/workbench/contrib/notebook/find/notebookFindDecorations'; import { NotebookEditorContentManager } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts index 883c1cbeac..e88a5138e0 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts @@ -22,7 +22,7 @@ import { Memento } from 'vs/workbench/common/memento'; import { Emitter } from 'vs/base/common/event'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts index b4cbbb6e35..829e986186 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts @@ -22,7 +22,7 @@ import { TreeNode } from 'sql/workbench/contrib/objectExplorer/common/treeNode'; import { NodeType } from 'sql/workbench/contrib/objectExplorer/common/nodeType'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { ServerTreeDataSource } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeDataSource'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions'; import { IConnectionResult, IConnectionParams } from 'sql/platform/connection/common/connectionManagement'; @@ -31,7 +31,7 @@ import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/t import { UNSAVED_GROUP_ID, mssqlProviderName } from 'sql/platform/connection/common/constants'; import { $ } from 'vs/base/browser/dom'; import { OEManageConnectionAction } from 'sql/workbench/contrib/dashboard/browser/dashboardActions'; -import { IViewsService, IView, ViewContainer, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IViewsService, IView } from 'vs/workbench/common/views'; import { ConsoleLogService } from 'vs/platform/log/common/log'; suite('SQL Connection Tree Action tests', () => { @@ -109,18 +109,22 @@ suite('SQL Connection Tree Action tests', () => { }); const viewsService = new class implements IViewsService { - getActiveViewWithId(id: string): IView { + getActiveViewWithId(id: string): T | null { throw new Error('Method not implemented.'); } _serviceBrand: undefined; - openView(id: string, focus?: boolean): Promise { - return Promise.resolve({ + openView(id: string, focus?: boolean): Promise { + return Promise.resolve({ id: '', serversTree: undefined }); } - getViewDescriptors(container: ViewContainer): IViewDescriptorCollection { - throw new Error('Method not implemented.'); + onDidChangeViewVisibility: Event<{ id: string, visible: boolean }> = Event.None; + closeView(id: string): void { + return; + } + isViewVisible(id: string): boolean { + return true; } }; diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts index 677aecef50..712a4bcfd6 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts @@ -8,7 +8,7 @@ import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/ser import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import * as TypeMoq from 'typemoq'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; diff --git a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts index 4f909c0574..85370428e2 100644 --- a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts +++ b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts @@ -11,8 +11,8 @@ import { IQueryModelService } from 'sql/workbench/services/query/common/queryMod import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EncodingMode } from 'vs/workbench/common/editor'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; +import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; type PublicPart = { [K in keyof T]: T[K] }; @@ -31,7 +31,7 @@ export class FileQueryEditorInput extends QueryEditorInput implements PublicPart super(description, text, results, connectionManagementService, queryModelService, configurationService); } - public resolve(): Promise { + public resolve(): Promise { return this.text.resolve(); } diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index c8992bcb96..4ef6980ecc 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -23,7 +23,7 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; -import { TestStorageService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -70,7 +70,7 @@ suite('SQL QueryAction Tests', () => { connectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService); connectionManagementService.setup(q => q.onDisconnect).returns(() => Event.None); const instantiationService = new TestInstantiationService(); - let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined); + let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, undefined, undefined, undefined, instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); // Setup a reusable mock QueryInput testQueryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); testQueryInput.setup(x => x.uri).returns(() => testUri); @@ -175,7 +175,7 @@ suite('SQL QueryAction Tests', () => { queryModelService.setup(x => x.onRunQueryStart).returns(() => Event.None); queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None); const instantiationService = new TestInstantiationService(); - let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined); + let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, undefined, undefined, undefined, instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); // ... Mock "isSelectionEmpty" in QueryEditor let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); @@ -224,7 +224,7 @@ suite('SQL QueryAction Tests', () => { // ... Mock "getSelection" in QueryEditor const instantiationService = new TestInstantiationService(); - let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined); + let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, undefined, undefined, undefined, instantiationService, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object); queryInput.setup(x => x.uri).returns(() => testUri); diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts index 2d41241b47..bd58cfb784 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts @@ -18,7 +18,7 @@ import * as TypeMoq from 'typemoq'; import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput'; @@ -285,7 +285,7 @@ suite('SQL QueryEditor Tests', () => { return new RunQueryAction(undefined, undefined, undefined); }); - let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, new LabelService(undefined, undefined), undefined, undefined); + let fileInput = new UntitledTextEditorInput(URI.parse('file://testUri'), false, undefined, undefined, undefined, instantiationService.object, undefined, new LabelService(undefined, undefined), undefined, undefined, new TestFileService(), undefined); queryModelService = TypeMoq.Mock.ofType(TestQueryModelService, TypeMoq.MockBehavior.Strict); queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny())); queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None); diff --git a/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts b/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts index 23a316e1bb..0449cbb07f 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryInputFactory.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestEditorService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorInput } from 'vs/workbench/common/editor'; diff --git a/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts b/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts index 5c5060213c..e7f546964b 100644 --- a/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts +++ b/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts @@ -13,7 +13,7 @@ import { AccountAdditionResult, AccountProviderAddedEventParams, UpdateAccountLi import { IAccountStore } from 'sql/platform/accounts/common/interfaces'; import { AccountProviderStub } from 'sql/platform/accounts/test/common/testAccountManagementService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { EventVerifierSingle } from 'sql/base/test/common/event'; // SUITE CONSTANTS ///////////////////////////////////////////////////////// diff --git a/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts index 2b8f188843..c3baff0ef7 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts @@ -10,7 +10,7 @@ import { ConnectionType, IConnectableInput, IConnectionResult, INewConnectionPar import { TestErrorMessageService } from 'sql/platform/errorMessage/test/common/testErrorMessageService'; import * as TypeMoq from 'typemoq'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts index 9b46514a5d..09fda856d8 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts @@ -28,7 +28,7 @@ import * as TypeMoq from 'typemoq'; import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService'; -import { TestStorageService, TestEnvironmentService, TestEditorService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService, TestEnvironmentService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { NullLogService } from 'vs/platform/log/common/log'; diff --git a/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts b/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts index 8123d1e83d..c7de35a7b5 100644 --- a/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts +++ b/src/sql/workbench/services/insights/test/browser/insightsDialogController.test.ts @@ -13,7 +13,7 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import * as azdata from 'azdata'; import { equal } from 'assert'; import { Mock, MockBehavior, It } from 'typemoq'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Emitter } from 'vs/base/common/event'; import { InsightsDialogModel } from 'sql/workbench/services/insights/browser/insightsDialogModel'; import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRegistry'; 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 d8659a56f5..34b59985ad 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 @@ -12,7 +12,7 @@ import * as path from 'vs/base/common/path'; import { Workspace, toWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; -import { TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IExtensionHostDebugParams, IDebugParams, ParsedArgs } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; diff --git a/src/sql/workbench/test/browser/taskUtilities.test.ts b/src/sql/workbench/test/browser/taskUtilities.test.ts index c979059fff..56287456c9 100644 --- a/src/sql/workbench/test/browser/taskUtilities.test.ts +++ b/src/sql/workbench/test/browser/taskUtilities.test.ts @@ -10,7 +10,7 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; -import { TestEditorService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { assign } from 'vs/base/common/objects'; suite('TaskUtilities', function () { diff --git a/src/sql/workbench/test/common/editorReplacerContribution.test.ts b/src/sql/workbench/test/common/editorReplacerContribution.test.ts index 076c61eab6..e303538982 100644 --- a/src/sql/workbench/test/common/editorReplacerContribution.test.ts +++ b/src/sql/workbench/test/common/editorReplacerContribution.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { EditorReplacementContribution } from 'sql/workbench/common/editorReplacerContribution'; -import { TestEditorService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { Event } from 'vs/base/common/event'; import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; diff --git a/src/sql/workbench/test/electron-browser/api/extHostAccountManagement.test.ts b/src/sql/workbench/test/electron-browser/api/extHostAccountManagement.test.ts index 0ae61eb2fa..efa0e1049e 100644 --- a/src/sql/workbench/test/electron-browser/api/extHostAccountManagement.test.ts +++ b/src/sql/workbench/test/electron-browser/api/extHostAccountManagement.test.ts @@ -8,13 +8,13 @@ import * as azdata from 'azdata'; import * as TypeMoq from 'typemoq'; import { AccountProviderStub, TestAccountManagementService } from 'sql/platform/accounts/test/common/testAccountManagementService'; import { ExtHostAccountManagement } from 'sql/workbench/api/common/extHostAccountManagement'; -import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { SqlMainContext } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { MainThreadAccountManagement } from 'sql/workbench/api/browser/mainThreadAccountManagement'; import { IAccountManagementService, AzureResource } from 'sql/platform/accounts/common/interfaces'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; const IRPCProtocol = createDecorator('rpcProtocol'); diff --git a/src/sql/workbench/test/electron-browser/api/extHostCredentialManagement.test.ts b/src/sql/workbench/test/electron-browser/api/extHostCredentialManagement.test.ts index 5b36aa977f..3320ea40e1 100644 --- a/src/sql/workbench/test/electron-browser/api/extHostCredentialManagement.test.ts +++ b/src/sql/workbench/test/electron-browser/api/extHostCredentialManagement.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ExtHostCredentialManagement } from 'sql/workbench/api/common/extHostCredentialManagement'; import { SqlMainContext } from 'sql/workbench/api/common/sqlExtHost.protocol'; @@ -14,6 +13,7 @@ import { ICredentialsService } from 'sql/platform/credentials/common/credentials import { Credential, CredentialProvider } from 'azdata'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TestCredentialsService, TestCredentialsProvider } from 'sql/platform/credentials/test/common/testCredentialsService'; +import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol'; const IRPCProtocol = createDecorator('rpcProtocol'); diff --git a/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts b/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts index b0518062a7..3d5c4ea684 100644 --- a/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts +++ b/src/sql/workbench/test/electron-browser/api/mainThreadNotebook.test.ts @@ -15,7 +15,7 @@ import { NotebookService } from 'sql/workbench/services/notebook/browser/noteboo import { INotebookProvider } from 'sql/workbench/services/notebook/browser/notebookService'; import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails } from 'sql/workbench/api/common/sqlExtHostTypes'; import { LocalContentManager } from 'sql/workbench/services/notebook/common/localContentManager'; -import { TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; +import { TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ExtHostNotebookShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; diff --git a/src/sql/workbench/test/workbenchTestServices.ts b/src/sql/workbench/test/workbenchTestServices.ts index 7282a62111..8dcb5b3b1b 100644 --- a/src/sql/workbench/test/workbenchTestServices.ts +++ b/src/sql/workbench/test/workbenchTestServices.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITestInstantiationService, workbenchInstantiationService as vsworkbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { ITestInstantiationService, workbenchInstantiationService as vsworkbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel'; import { TestQueryModelService } from 'sql/workbench/services/query/test/common/testQueryModelService'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index e5600bb4f4..57da73ec85 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -120,6 +120,6 @@ export const isWebKit = (userAgent.indexOf('AppleWebKit') >= 0); export const isChrome = (userAgent.indexOf('Chrome') >= 0); export const isSafari = (!isChrome && (userAgent.indexOf('Safari') >= 0)); export const isWebkitWebView = (!isChrome && !isSafari && isWebKit); -export const isIPad = (userAgent.indexOf('iPad') >= 0); +export const isIPad = (userAgent.indexOf('iPad') >= 0 || (isSafari && navigator.maxTouchPoints > 0)); export const isEdgeWebView = isEdge && (userAgent.indexOf('WebView/') >= 0); export const isStandalone = (window.matchMedia && window.matchMedia('(display-mode: standalone)').matches); diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 5fb7fd0ebe..e749f1e1bd 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1313,7 +1313,22 @@ export function asCSSUrl(uri: URI): string { return `url('${asDomUri(uri).toString(true).replace(/'/g, '%27')}')`; } -export function triggerDownload(uri: URI, name: string): void { + +export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void { + + // If the data is provided as Buffer, we create a + // blog URL out of it to produce a valid link + let url: string; + if (URI.isUri(dataOrUri)) { + url = dataOrUri.toString(true); + } else { + const blob = new Blob([dataOrUri]); + url = URL.createObjectURL(blob); + + // Ensure to free the data from DOM eventually + setTimeout(() => URL.revokeObjectURL(url)); + } + // In order to download from the browser, the only way seems // to be creating a element with download attribute that // points to the file to download. @@ -1321,7 +1336,7 @@ export function triggerDownload(uri: URI, name: string): void { const anchor = document.createElement('a'); document.body.appendChild(anchor); anchor.download = name; - anchor.href = uri.toString(true); + anchor.href = url; anchor.click(); // Ensure to remove the element from DOM eventually diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index 8be66b6d90..021c363526 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -28,6 +28,7 @@ export class FastDomNode { private _backgroundColor: string; private _layerHint: boolean; private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'; + private _boxShadow: string; constructor(domNode: T) { this.domNode = domNode; @@ -51,6 +52,7 @@ export class FastDomNode { this._backgroundColor = ''; this._layerHint = false; this._contain = 'none'; + this._boxShadow = ''; } public setMaxWidth(maxWidth: number): void { @@ -218,6 +220,14 @@ export class FastDomNode { this.domNode.style.transform = this._layerHint ? 'translate3d(0px, 0px, 0px)' : ''; } + public setBoxShadow(boxShadow: string): void { + if (this._boxShadow === boxShadow) { + return; + } + this._boxShadow = boxShadow; + this.domNode.style.boxShadow = boxShadow; + } + public setContain(contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'): void { if (this._contain === contain) { return; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 0331a871a7..5c25b68f7b 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -88,7 +88,7 @@ export class MenuBar extends Disposable { private numMenusShown: number = 0; private menuStyle: IMenuStyles | undefined; - private overflowLayoutScheduled: IDisposable | null = null; + private overflowLayoutScheduled: IDisposable | undefined = undefined; constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) { super(); @@ -419,9 +419,8 @@ export class MenuBar extends Disposable { DOM.removeNode(this.overflowMenu.titleElement); DOM.removeNode(this.overflowMenu.buttonElement); - if (this.overflowLayoutScheduled) { - this.overflowLayoutScheduled = dispose(this.overflowLayoutScheduled); - } + dispose(this.overflowLayoutScheduled); + this.overflowLayoutScheduled = undefined; } blur(): void { @@ -561,7 +560,7 @@ export class MenuBar extends Disposable { if (!this.overflowLayoutScheduled) { this.overflowLayoutScheduled = DOM.scheduleAtNextAnimationFrame(() => { this.updateOverflowAction(); - this.overflowLayoutScheduled = null; + this.overflowLayoutScheduled = undefined; }); } diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 5fa413dc17..9130435aa4 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -287,6 +287,7 @@ export abstract class AbstractScrollableElement extends Widget { this._options.handleMouseWheel = massagedOptions.handleMouseWheel; this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity; this._options.fastScrollSensitivity = massagedOptions.fastScrollSensitivity; + this._options.scrollPredominantAxis = massagedOptions.scrollPredominantAxis; this._setListeningToMouseWheel(this._options.handleMouseWheel); if (!this._options.lazyRender) { @@ -334,6 +335,14 @@ export abstract class AbstractScrollableElement extends Widget { let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity; let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity; + if (this._options.scrollPredominantAxis) { + if (Math.abs(deltaY) >= Math.abs(deltaX)) { + deltaX = 0; + } else { + deltaY = 0; + } + } + if (this._options.flipAxes) { [deltaY, deltaX] = [deltaX, deltaY]; } @@ -553,6 +562,7 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false), mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1), fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5), + scrollPredominantAxis: (typeof opts.scrollPredominantAxis !== 'undefined' ? opts.scrollPredominantAxis : true), mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true), arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11), diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index 7276a5769a..10449df397 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -55,6 +55,13 @@ export interface ScrollableElementCreationOptions { * Defaults to 5. */ fastScrollSensitivity?: number; + /** + * Whether the scrollable will only scroll along the predominant axis when scrolling both + * vertically and horizontally at the same time. + * Prevents horizontal drift when scrolling vertically on a trackpad. + * Defaults to true. + */ + scrollPredominantAxis?: boolean; /** * Height for vertical arrows (top/bottom) and width for horizontal arrows (left/right). * Defaults to 11. @@ -113,6 +120,7 @@ export interface ScrollableElementChangeOptions { handleMouseWheel?: boolean; mouseWheelScrollSensitivity?: number; fastScrollSensitivity: number; + scrollPredominantAxis: boolean; } export interface ScrollableElementResolvedOptions { @@ -125,6 +133,7 @@ export interface ScrollableElementResolvedOptions { alwaysConsumeMouseWheel: boolean; mouseWheelScrollSensitivity: number; fastScrollSensitivity: number; + scrollPredominantAxis: boolean; mouseWheelSmoothScroll: boolean; arrowSize: number; listenOnDomNode: HTMLElement | null; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index c2fc137dab..e9c7c135d4 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -1585,7 +1585,7 @@ export abstract class AbstractTree implements IDisposable } open(elements: TRef[], browserEvent?: UIEvent): void { - const indexes = elements.map(e => this.model.getListIndex(e)); + const indexes = elements.map(e => this.model.getListIndex(e)).filter(i => i >= 0); this.view.open(indexes, browserEvent); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 8fbae3e6aa..c2670776ea 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -562,6 +562,10 @@ export class AsyncDataTree implements IDisposable const node = this.getDataNode(element); + if (this.tree.hasElement(node) && !this.tree.isCollapsible(node)) { + return false; + } + if (node.refreshPromise) { await this.root.refreshPromise; await Event.toPromise(this._onDidRender.event); diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index bc9347e5c2..e624ff634e 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -205,6 +205,10 @@ export class CompressedObjectTreeModel, TFilterData e this.model.setChildren(node, children, _onDidCreateNode, _onDidDeleteNode); } + has(element: T | null): boolean { + return this.nodes.has(element); + } + getListIndex(location: T | null): number { const node = this.getCompressedNode(location); return this.model.getListIndex(node); @@ -421,6 +425,10 @@ export class CompressibleObjectTreeModel, TFilterData this.model.setCompressionEnabled(enabled); } + has(location: T | null): boolean { + return this.model.has(location); + } + getListIndex(location: T | null): number { return this.model.getListIndex(location); } diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 61da572b86..64947bf012 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -199,6 +199,10 @@ export class IndexTreeModel, TFilterData = voi } } + has(location: number[]): boolean { + return this.hasTreeNode(location); + } + getListIndex(location: number[]): number { const { listIndex, visible, revealed } = this.getTreeNodeWithListIndex(location); return visible && revealed ? listIndex : -1; @@ -291,6 +295,8 @@ export class IndexTreeModel, TFilterData = voi if (isCollapsibleStateUpdate(update)) { result = node.collapsible !== update.collapsible; node.collapsible = update.collapsible; + } else if (!node.collapsible) { + result = false; } else { result = node.collapsed !== update.collapsed; node.collapsed = update.collapsed; @@ -516,6 +522,21 @@ export class IndexTreeModel, TFilterData = voi } } + // cheap + private hasTreeNode(location: number[], node: IIndexTreeNode = this.root): boolean { + if (!location || location.length === 0) { + return true; + } + + const [index, ...rest] = location; + + if (index < 0 || index > node.children.length) { + return false; + } + + return this.hasTreeNode(rest, node.children[index]); + } + // cheap private getTreeNode(location: number[], node: IIndexTreeNode = this.root): IIndexTreeNode { if (!location || location.length === 0) { diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index 19930f9b00..a21f9b8be4 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -50,6 +50,10 @@ export class ObjectTree, TFilterData = void> extends this.model.resort(element, recursive); } + hasElement(element: T): boolean { + return this.model.has(element); + } + protected createModel(user: string, view: ISpliceable>, options: IObjectTreeOptions): ITreeModel { return new ObjectTreeModel(user, view, options); } diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 268d6b4d50..f0e0abdd47 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -195,6 +195,10 @@ export class ObjectTreeModel, TFilterData extends Non return this.model.getLastElementAncestor(location); } + has(element: T | null): boolean { + return this.nodes.has(element); + } + getListIndex(element: T | null): number { const location = this.getElementLocation(element); return this.model.getListIndex(location); diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index c4e257f084..d9bcf6519f 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -108,6 +108,8 @@ export interface ITreeModel { readonly onDidChangeCollapseState: Event>; readonly onDidChangeRenderNodeCount: Event>; + has(location: TRef): boolean; + getListIndex(location: TRef): number; getListRenderCount(location: TRef): number; getNode(location?: TRef): ITreeNode; diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index af9e0bf08c..ee7b34d63a 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -584,3 +584,7 @@ export function mapArrayOrNot(items: T | T[], fn: (_: T) => U): U | U[] { export function asArray(x: T | T[]): T[] { return Array.isArray(x) ? x : [x]; } + +export function getRandomElement(arr: T[]): T | undefined { + return arr[Math.floor(Math.random() * arr.length)]; +} diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts index e8df6c3188..b4e0330c8e 100644 --- a/src/vs/base/common/jsonEdit.ts +++ b/src/vs/base/common/jsonEdit.ts @@ -175,7 +175,3 @@ export function applyEdits(text: string, edits: Edit[]): string { } return text; } - -export function isWS(text: string, offset: number) { - return '\r\n \t'.indexOf(text.charAt(offset)) !== -1; -} diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index ac1b7fa145..d8ad2bb6f0 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -91,6 +91,9 @@ export function toDisposable(fn: () => void): IDisposable { } export class DisposableStore implements IDisposable { + + static DISABLE_DISPOSED_WARNING = false; + private _toDispose = new Set(); private _isDisposed = false; @@ -127,7 +130,9 @@ export class DisposableStore implements IDisposable { markTracked(t); if (this._isDisposed) { - console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); + if (!DisposableStore.DISABLE_DISPOSED_WARNING) { + console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack); + } } else { this._toDispose.add(t); } diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 4381cc7777..26f23dd95d 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -182,7 +182,7 @@ export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep) return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep; } else { const p = resource.path; - return p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash; // ignore the slash at offset 0 + return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0 } } @@ -191,6 +191,7 @@ export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep) * Important: Doesn't remove the first slash, it would make the URI invalid */ export function removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI { + // Make sure that the path isn't a drive letter. A trailing separator there is not removable. if (hasTrailingPathSeparator(resource, sep)) { return resource.with({ path: resource.path.substr(0, resource.path.length - 1) }); } @@ -226,7 +227,7 @@ export function relativePath(from: URI, to: URI, ignoreCase = hasToIgnoreCase(fr return undefined; } if (from.scheme === Schemas.file) { - const relativePath = paths.relative(from.path, to.path); + const relativePath = paths.relative(originalFSPath(from), originalFSPath(to)); return isWindows ? extpath.toSlashes(relativePath) : relativePath; } let fromPath = from.path || '/', toPath = to.path || '/'; diff --git a/src/vs/base/node/config.ts b/src/vs/base/node/config.ts index 8dcb97d410..41dac28ee2 100644 --- a/src/vs/base/node/config.ts +++ b/src/vs/base/node/config.ts @@ -126,8 +126,8 @@ export class ConfigWatcher extends Disposable implements IConfigWatcher { } private async handleSymbolicLink(): Promise { - const { stat, isSymbolicLink } = await statLink(this._path); - if (isSymbolicLink && !stat.isDirectory()) { + const { stat, symbolicLink } = await statLink(this._path); + if (symbolicLink && !stat.isDirectory()) { const realPath = await realpath(this._path); this.watch(realPath, false); diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 3ec31d9a9e..424d2d1ba3 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -14,7 +14,18 @@ import { promisify } from 'util'; import { isRootOrDriveLetter } from 'vs/base/common/extpath'; import { generateUuid } from 'vs/base/common/uuid'; import { normalizeNFC } from 'vs/base/common/normalization'; -import { encode, encodeStream } from 'vs/base/node/encoding'; +import { encode } from 'vs/base/node/encoding'; + +// See https://github.com/Microsoft/vscode/issues/30180 +const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB +const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB + +// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149 +const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB +const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB + +export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE; +export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; export enum RimRafMode { @@ -178,30 +189,52 @@ export function stat(path: string): Promise { } export interface IStatAndLink { + + // The stats of the file. If the file is a symbolic + // link, the stats will be of that target file and + // not the link itself. + // If the file is a symbolic link pointing to a non + // existing file, the stat will be of the link and + // the `dangling` flag will indicate this. stat: fs.Stats; - isSymbolicLink: boolean; + + // Will be provided if the resource is a symbolic link + // on disk. Use the `dangling` flag to find out if it + // points to a resource that does not exist on disk. + symbolicLink?: { dangling: boolean }; } export async function statLink(path: string): Promise { // First stat the link - let linkStat: fs.Stats | undefined; - let linkStatError: NodeJS.ErrnoException | undefined; + let lstats: fs.Stats | undefined; try { - linkStat = await lstat(path); + lstats = await lstat(path); + + // Return early if the stat is not a symbolic link at all + if (!lstats.isSymbolicLink()) { + return { stat: lstats }; + } } catch (error) { - linkStatError = error; + /* ignore - use stat() instead */ } - // Then stat the target and return that - const isLink = !!(linkStat && linkStat.isSymbolicLink()); - if (linkStatError || isLink) { - const fileStat = await stat(path); + // If the stat is a symbolic link or failed to stat, use fs.stat() + // which for symbolic links will stat the target they point to + try { + const stats = await stat(path); - return { stat: fileStat, isSymbolicLink: isLink }; + return { stat: stats, symbolicLink: lstats?.isSymbolicLink() ? { dangling: false } : undefined }; + } catch (error) { + + // If the link points to a non-existing file we still want + // to return it as result while setting dangling: true flag + if (error.code === 'ENOENT' && lstats) { + return { stat: lstats, symbolicLink: { dangling: true } }; + } + + throw error; } - - return { stat: linkStat!, isSymbolicLink: false }; } export function lstat(path: string): Promise { @@ -213,9 +246,7 @@ export function rename(oldPath: string, newPath: string): Promise { } export function renameIgnoreError(oldPath: string, newPath: string): Promise { - return new Promise(resolve => { - fs.rename(oldPath, newPath, () => resolve()); - }); + return new Promise(resolve => fs.rename(oldPath, newPath, () => resolve())); } export function unlink(path: string): Promise { @@ -236,6 +267,10 @@ export function readFile(path: string, encoding?: string): Promise { + return promisify(fs.mkdir)(path, { mode, recursive: true }); +} + // According to node.js docs (https://nodejs.org/docs/v6.5.0/api/fs.html#fs_fs_writefile_file_data_options_callback) // it is not safe to call writeFile() on the same path multiple times without waiting for the callback to return. // Therefor we use a Queue on the path that is given to us to sequentialize calls to the same path properly. @@ -244,12 +279,15 @@ const writeFilePathQueues: Map> = new Map(); export function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise; export function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise; export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: NodeJS.ReadableStream, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options?: IWriteFileOptions): Promise { +export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise; +export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise { const queueKey = toQueueKey(path); - return ensureWriteFileQueue(queueKey).queue(() => writeFileAndFlush(path, data, options)); + return ensureWriteFileQueue(queueKey).queue(() => { + const ensuredOptions = ensureWriteOptions(options); + + return new Promise((resolve, reject) => doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve())); + }); } function toQueueKey(path: string): string { @@ -294,103 +332,6 @@ interface IEnsuredWriteFileOptions extends IWriteFileOptions { } let canFlush = true; -function writeFileAndFlush(path: string, data: string | Buffer | NodeJS.ReadableStream | Uint8Array, options: IWriteFileOptions | undefined): Promise { - const ensuredOptions = ensureWriteOptions(options); - - return new Promise((resolve, reject) => { - if (typeof data === 'string' || Buffer.isBuffer(data) || data instanceof Uint8Array) { - doWriteFileAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()); - } else { - doWriteFileStreamAndFlush(path, data, ensuredOptions, error => error ? reject(error) : resolve()); - } - }); -} - -function doWriteFileStreamAndFlush(path: string, reader: NodeJS.ReadableStream, options: IEnsuredWriteFileOptions, callback: (error?: Error) => void): void { - - // finish only once - let finished = false; - const finish = (error?: Error) => { - if (!finished) { - finished = true; - - // in error cases we need to manually close streams - // if the write stream was successfully opened - if (error) { - if (isOpen) { - writer.once('close', () => callback(error)); - writer.destroy(); - } else { - callback(error); - } - } - - // otherwise just return without error - else { - callback(); - } - } - }; - - // create writer to target. we set autoClose: false because we want to use the streams - // file descriptor to call fs.fdatasync to ensure the data is flushed to disk - const writer = fs.createWriteStream(path, { mode: options.mode, flags: options.flag, autoClose: false }); - - // Event: 'open' - // Purpose: save the fd for later use and start piping - // Notes: will not be called when there is an error opening the file descriptor! - let fd: number; - let isOpen: boolean; - writer.once('open', descriptor => { - fd = descriptor; - isOpen = true; - - // if an encoding is provided, we need to pipe the stream through - // an encoder stream and forward the encoding related options - if (options.encoding) { - reader = reader.pipe(encodeStream(options.encoding.charset, { addBOM: options.encoding.addBOM })); - } - - // start data piping only when we got a successful open. this ensures that we do - // not consume the stream when an error happens and helps to fix this issue: - // https://github.com/Microsoft/vscode/issues/42542 - reader.pipe(writer); - }); - - // Event: 'error' - // Purpose: to return the error to the outside and to close the write stream (does not happen automatically) - reader.once('error', error => finish(error)); - writer.once('error', error => finish(error)); - - // Event: 'finish' - // Purpose: use fs.fdatasync to flush the contents to disk - // Notes: event is called when the writer has finished writing to the underlying resource. we must call writer.close() - // because we have created the WriteStream with autoClose: false - writer.once('finish', () => { - - // flush to disk - if (canFlush && isOpen) { - fs.fdatasync(fd, (syncError: Error) => { - - // In some exotic setups it is well possible that node fails to sync - // In that case we disable flushing and warn to the console - if (syncError) { - console.warn('[node.js fs] fdatasync is now disabled for this session because it failed: ', syncError); - canFlush = false; - } - - writer.destroy(); - }); - } else { - writer.destroy(); - } - }); - - // Event: 'close' - // Purpose: signal we are done to the outside - // Notes: event is called when the writer's filedescriptor is closed - writer.once('close', () => finish()); -} // Calls fs.writeFile() followed by a fs.sync() call to flush the changes to disk // We do this in cases where we want to make sure the data is really on disk and @@ -631,18 +572,3 @@ async function doCopyFile(source: string, target: string, mode: number): Promise reader.pipe(writer); }); } - -export async function mkdirp(path: string, mode?: number): Promise { - return promisify(fs.mkdir)(path, { mode, recursive: true }); -} - -// See https://github.com/Microsoft/vscode/issues/30180 -const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB -const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB - -// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149 -const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB -const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB - -export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE; -export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index e93a9ffd81..b489412021 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -3,12 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Emitter, Relay } from 'vs/base/common/event'; -import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter, Relay, EventMultiplexer } from 'vs/base/common/event'; +import { IDisposable, toDisposable, combinedDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { VSBuffer } from 'vs/base/common/buffer'; +import { getRandomElement } from 'vs/base/common/arrays'; +import { isFunction } from 'vs/base/common/types'; /** * An `IChannel` is an abstraction over a collection of commands. @@ -95,7 +97,8 @@ export interface Client { export interface IConnectionHub { readonly connections: Connection[]; - readonly onDidChangeConnections: Event>; + readonly onDidAddConnection: Event>; + readonly onDidRemoveConnection: Event>; } /** @@ -116,7 +119,7 @@ export interface IClientRouter { * order to pick the right one. */ export interface IRoutingChannelClient { - getChannel(channelName: string, router: IClientRouter): T; + getChannel(channelName: string, router?: IClientRouter): T; } interface IReader { @@ -659,8 +662,11 @@ export class IPCServer implements IChannelServer, I private channels = new Map>(); private _connections = new Set>(); - private readonly _onDidChangeConnections = new Emitter>(); - readonly onDidChangeConnections: Event> = this._onDidChangeConnections.event; + private readonly _onDidAddConnection = new Emitter>(); + readonly onDidAddConnection: Event> = this._onDidAddConnection.event; + + private readonly _onDidRemoveConnection = new Emitter>(); + readonly onDidRemoveConnection: Event> = this._onDidRemoveConnection.event; get connections(): Connection[] { const result: Connection[] = []; @@ -683,30 +689,59 @@ export class IPCServer implements IChannelServer, I const connection: Connection = { channelServer, channelClient, ctx }; this._connections.add(connection); - this._onDidChangeConnections.fire(connection); + this._onDidAddConnection.fire(connection); onDidClientDisconnect(() => { channelServer.dispose(); channelClient.dispose(); this._connections.delete(connection); + this._onDidRemoveConnection.fire(connection); }); }); }); } - getChannel(channelName: string, router: IClientRouter): T { + /** + * Get a channel from a remote client. When passed a router, + * one can specify which client it wants to call and listen to/from. + * Otherwise, when calling without a router, a random client will + * be selected and when listening without a router, every client + * will be listened to. + */ + getChannel(channelName: string, router: IClientRouter): T; + getChannel(channelName: string, clientFilter: (client: Client) => boolean): T; + getChannel(channelName: string, routerOrClientFilter: IClientRouter | ((client: Client) => boolean)): T { const that = this; return { - call(command: string, arg?: any, cancellationToken?: CancellationToken) { - const channelPromise = router.routeCall(that, command, arg) + call(command: string, arg?: any, cancellationToken?: CancellationToken): Promise { + let connectionPromise: Promise>; + + if (isFunction(routerOrClientFilter)) { + // when no router is provided, we go random client picking + let connection = getRandomElement(that.connections.filter(routerOrClientFilter)); + + connectionPromise = connection + // if we found a client, let's call on it + ? Promise.resolve(connection) + // else, let's wait for a client to come along + : Event.toPromise(Event.filter(that.onDidAddConnection, routerOrClientFilter)); + } else { + connectionPromise = routerOrClientFilter.routeCall(that, command, arg); + } + + const channelPromise = connectionPromise .then(connection => (connection as Connection).channelClient.getChannel(channelName)); return getDelayedChannel(channelPromise) .call(command, arg, cancellationToken); }, - listen(event: string, arg: any) { - const channelPromise = router.routeEvent(that, event, arg) + listen(event: string, arg: any): Event { + if (isFunction(routerOrClientFilter)) { + return that.getMulticastEvent(channelName, routerOrClientFilter, event, arg); + } + + const channelPromise = routerOrClientFilter.routeEvent(that, event, arg) .then(connection => (connection as Connection).channelClient.getChannel(channelName)); return getDelayedChannel(channelPromise) @@ -715,6 +750,58 @@ export class IPCServer implements IChannelServer, I } as T; } + private getMulticastEvent(channelName: string, clientFilter: (client: Client) => boolean, eventName: string, arg: any): Event { + const that = this; + let disposables = new DisposableStore(); + + // Create an emitter which hooks up to all clients + // as soon as first listener is added. It also + // disconnects from all clients as soon as the last listener + // is removed. + const emitter = new Emitter({ + onFirstListenerAdd: () => { + disposables = new DisposableStore(); + + // The event multiplexer is useful since the active + // client list is dynamic. We need to hook up and disconnection + // to/from clients as they come and go. + const eventMultiplexer = new EventMultiplexer(); + const map = new Map, IDisposable>(); + + const onDidAddConnection = (connection: Connection) => { + const channel = connection.channelClient.getChannel(channelName); + const event = channel.listen(eventName, arg); + const disposable = eventMultiplexer.add(event); + + map.set(connection, disposable); + }; + + const onDidRemoveConnection = (connection: Connection) => { + const disposable = map.get(connection); + + if (!disposable) { + return; + } + + disposable.dispose(); + map.delete(connection); + }; + + that.connections.filter(clientFilter).forEach(onDidAddConnection); + Event.filter(that.onDidAddConnection, clientFilter)(onDidAddConnection, undefined, disposables); + that.onDidRemoveConnection(onDidRemoveConnection, undefined, disposables); + eventMultiplexer.event(emitter.fire, emitter, disposables); + + disposables.add(eventMultiplexer); + }, + onLastListenerRemove: () => { + disposables.dispose(); + } + }); + + return emitter.event; + } + registerChannel(channelName: string, channel: IServerChannel): void { this.channels.set(channelName, channel); @@ -726,7 +813,8 @@ export class IPCServer implements IChannelServer, I dispose(): void { this.channels.clear(); this._connections.clear(); - this._onDidChangeConnections.dispose(); + this._onDidAddConnection.dispose(); + this._onDidRemoveConnection.dispose(); } } @@ -827,7 +915,7 @@ export class StaticRouter implements IClientRouter } } - await Event.toPromise(hub.onDidChangeConnections); + await Event.toPromise(hub.onDidAddConnection); return await this.route(hub); } } diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index 227659bf5f..1109070d6b 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -415,4 +415,80 @@ suite('Base IPC', function () { return assert.equal(r, 'Super Context'); }); }); + + suite('one to many', function () { + test('all clients get pinged', async function () { + const service = new TestService(); + const channel = new TestChannel(service); + const server = new TestIPCServer(); + server.registerChannel('channel', channel); + + let client1GotPinged = false; + const client1 = server.createConnection('client1'); + const ipcService1 = new TestChannelClient(client1.getChannel('channel')); + ipcService1.onPong(() => client1GotPinged = true); + + let client2GotPinged = false; + const client2 = server.createConnection('client2'); + const ipcService2 = new TestChannelClient(client2.getChannel('channel')); + ipcService2.onPong(() => client2GotPinged = true); + + await timeout(1); + service.ping('hello'); + + await timeout(1); + assert(client1GotPinged, 'client 1 got pinged'); + assert(client2GotPinged, 'client 2 got pinged'); + + client1.dispose(); + client2.dispose(); + server.dispose(); + }); + + test('server gets pings from all clients (broadcast channel)', async function () { + const server = new TestIPCServer(); + + const client1 = server.createConnection('client1'); + const clientService1 = new TestService(); + const clientChannel1 = new TestChannel(clientService1); + client1.registerChannel('channel', clientChannel1); + + const pings: string[] = []; + const channel = server.getChannel('channel', () => true); + const service = new TestChannelClient(channel); + service.onPong(msg => pings.push(msg)); + + await timeout(1); + clientService1.ping('hello 1'); + + await timeout(1); + assert.deepEqual(pings, ['hello 1']); + + const client2 = server.createConnection('client2'); + const clientService2 = new TestService(); + const clientChannel2 = new TestChannel(clientService2); + client2.registerChannel('channel', clientChannel2); + + await timeout(1); + clientService2.ping('hello 2'); + + await timeout(1); + assert.deepEqual(pings, ['hello 1', 'hello 2']); + + client1.dispose(); + clientService1.ping('hello 1'); + + await timeout(1); + assert.deepEqual(pings, ['hello 1', 'hello 2']); + + await timeout(1); + clientService2.ping('hello again 2'); + + await timeout(1); + assert.deepEqual(pings, ['hello 1', 'hello 2', 'hello again 2']); + + client2.dispose(); + server.dispose(); + }); + }); }); diff --git a/src/vs/workbench/browser/parts/quickinput/media/arrow-left-dark.svg b/src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg similarity index 100% rename from src/vs/workbench/browser/parts/quickinput/media/arrow-left-dark.svg rename to src/vs/base/parts/quickinput/browser/media/arrow-left-dark.svg diff --git a/src/vs/workbench/browser/parts/quickinput/media/arrow-left-light.svg b/src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg similarity index 100% rename from src/vs/workbench/browser/parts/quickinput/media/arrow-left-light.svg rename to src/vs/base/parts/quickinput/browser/media/arrow-left-light.svg diff --git a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css similarity index 100% rename from src/vs/workbench/browser/parts/quickinput/media/quickInput.css rename to src/vs/base/parts/quickinput/browser/media/quickInput.css diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts new file mode 100644 index 0000000000..27169f6f7f --- /dev/null +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -0,0 +1,1556 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/quickInput'; +import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput'; +import * as dom from 'vs/base/browser/dom'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { QuickInputList } from './quickInputList'; +import { QuickInputBox } from './quickInputBox'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { localize } from 'vs/nls'; +import { CountBadge, ICountBadgetyles } from 'vs/base/browser/ui/countBadge/countBadge'; +import { ProgressBar, IProgressBarStyles } from 'vs/base/browser/ui/progressbar/progressbar'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; +import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import Severity from 'vs/base/common/severity'; +import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action } from 'vs/base/common/actions'; +import { URI } from 'vs/base/common/uri'; +import { equals } from 'vs/base/common/arrays'; +import { TimeoutTimer } from 'vs/base/common/async'; +import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; +import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; +import { Color } from 'vs/base/common/color'; + +export interface IQuickInputOptions { + idPrefix: string; + container: HTMLElement; + ignoreFocusOut(): boolean; + isScreenReaderOptimized(): boolean; + backKeybindingLabel(): string | undefined; + setContextKey(id?: string): void; + returnFocus(): void; + createList( + user: string, + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: IListRenderer[], + options: IListOptions, + ): List; + styles: IQuickInputStyles; +} + +export interface IQuickInputStyles { + widget: IQuickInputWidgetStyles; + inputBox: IInputBoxStyles; + countBadge: ICountBadgetyles; + button: IButtonStyles; + progressBar: IProgressBarStyles; + list: IListStyles & { listInactiveFocusForeground?: Color; pickerGroupBorder?: Color; pickerGroupForeground?: Color; }; +} + +export interface IQuickInputWidgetStyles { + quickInputBackground?: Color; + quickInputForeground?: Color; + contrastBorder?: Color; + widgetShadow?: Color; + titleColor: string | undefined; +} + +const $ = dom.$; + +type Writeable = { -readonly [P in keyof T]: T[P] }; + +const backButton = { + iconPath: { + dark: URI.parse(registerAndGetAmdImageURL('vs/base/parts/quickinput/browser/media/arrow-left-dark.svg')), + light: URI.parse(registerAndGetAmdImageURL('vs/base/parts/quickinput/browser/media/arrow-left-light.svg')) + }, + tooltip: localize('quickInput.back', "Back"), + handle: -1 // TODO +}; + +interface QuickInputUI { + container: HTMLElement; + styleSheet: HTMLStyleElement; + leftActionBar: ActionBar; + titleBar: HTMLElement; + title: HTMLElement; + description: HTMLElement; + rightActionBar: ActionBar; + checkAll: HTMLInputElement; + filterContainer: HTMLElement; + inputBox: QuickInputBox; + visibleCountContainer: HTMLElement; + visibleCount: CountBadge; + countContainer: HTMLElement; + count: CountBadge; + okContainer: HTMLElement; + ok: Button; + message: HTMLElement; + customButtonContainer: HTMLElement; + customButton: Button; + progressBar: ProgressBar; + list: QuickInputList; + onDidAccept: Event; + onDidCustom: Event; + onDidTriggerButton: Event; + ignoreFocusOut: boolean; + keyMods: Writeable; + isScreenReaderOptimized(): boolean; + show(controller: QuickInput): void; + setVisibilities(visibilities: Visibilities): void; + setComboboxAccessibility(enabled: boolean): void; + setEnabled(enabled: boolean): void; + setContextKey(contextKey?: string): void; + hide(): void; +} + +type Visibilities = { + title?: boolean; + description?: boolean; + checkAll?: boolean; + inputBox?: boolean; + visibleCount?: boolean; + count?: boolean; + message?: boolean; + list?: boolean; + ok?: boolean; + customButton?: boolean; +}; + +class QuickInput extends Disposable implements IQuickInput { + + private _title: string | undefined; + private _description: string | undefined; + private _steps: number | undefined; + private _totalSteps: number | undefined; + protected visible = false; + private _enabled = true; + private _contextKey: string | undefined; + private _busy = false; + private _ignoreFocusOut = false; + private _buttons: IQuickInputButton[] = []; + private buttonsUpdated = false; + private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); + private readonly onDidHideEmitter = this._register(new Emitter()); + + protected readonly visibleDisposables = this._register(new DisposableStore()); + + private busyDelay: TimeoutTimer | undefined; + + constructor( + protected ui: QuickInputUI + ) { + super(); + } + + get title() { + return this._title; + } + + set title(title: string | undefined) { + this._title = title; + this.update(); + } + + get description() { + return this._description; + } + + set description(description: string | undefined) { + this._description = description; + this.update(); + } + + get step() { + return this._steps; + } + + set step(step: number | undefined) { + this._steps = step; + this.update(); + } + + get totalSteps() { + return this._totalSteps; + } + + set totalSteps(totalSteps: number | undefined) { + this._totalSteps = totalSteps; + this.update(); + } + + get enabled() { + return this._enabled; + } + + set enabled(enabled: boolean) { + this._enabled = enabled; + this.update(); + } + + get contextKey() { + return this._contextKey; + } + + set contextKey(contextKey: string | undefined) { + this._contextKey = contextKey; + this.update(); + } + + get busy() { + return this._busy; + } + + set busy(busy: boolean) { + this._busy = busy; + this.update(); + } + + get ignoreFocusOut() { + return this._ignoreFocusOut; + } + + set ignoreFocusOut(ignoreFocusOut: boolean) { + this._ignoreFocusOut = ignoreFocusOut; + this.update(); + } + + get buttons() { + return this._buttons; + } + + set buttons(buttons: IQuickInputButton[]) { + this._buttons = buttons; + this.buttonsUpdated = true; + this.update(); + } + + onDidTriggerButton = this.onDidTriggerButtonEmitter.event; + + show(): void { + if (this.visible) { + return; + } + this.visibleDisposables.add( + this.ui.onDidTriggerButton(button => { + if (this.buttons.indexOf(button) !== -1) { + this.onDidTriggerButtonEmitter.fire(button); + } + }), + ); + this.ui.show(this); + this.visible = true; + this.update(); + } + + hide(): void { + if (!this.visible) { + return; + } + this.ui.hide(); + } + + didHide(): void { + this.visible = false; + this.visibleDisposables.clear(); + this.onDidHideEmitter.fire(); + } + + onDidHide = this.onDidHideEmitter.event; + + protected update() { + if (!this.visible) { + return; + } + const title = this.getTitle(); + if (this.ui.title.textContent !== title) { + this.ui.title.textContent = title; + } + const description = this.getDescription(); + if (this.ui.description.textContent !== description) { + this.ui.description.textContent = description; + } + if (this.busy && !this.busyDelay) { + this.busyDelay = new TimeoutTimer(); + this.busyDelay.setIfNotSet(() => { + if (this.visible) { + this.ui.progressBar.infinite(); + } + }, 800); + } + if (!this.busy && this.busyDelay) { + this.ui.progressBar.stop(); + this.busyDelay.cancel(); + this.busyDelay = undefined; + } + if (this.buttonsUpdated) { + this.buttonsUpdated = false; + this.ui.leftActionBar.clear(); + const leftButtons = this.buttons.filter(button => button === backButton); + this.ui.leftActionBar.push(leftButtons.map((button, index) => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + this.onDidTriggerButtonEmitter.fire(button); + return Promise.resolve(null); + }); + action.tooltip = button.tooltip || ''; + return action; + }), { icon: true, label: false }); + this.ui.rightActionBar.clear(); + const rightButtons = this.buttons.filter(button => button !== backButton); + this.ui.rightActionBar.push(rightButtons.map((button, index) => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + this.onDidTriggerButtonEmitter.fire(button); + return Promise.resolve(null); + }); + action.tooltip = button.tooltip || ''; + return action; + }), { icon: true, label: false }); + } + this.ui.ignoreFocusOut = this.ignoreFocusOut; + this.ui.setEnabled(this.enabled); + this.ui.setContextKey(this.contextKey); + } + + private getTitle() { + if (this.title && this.step) { + return `${this.title} (${this.getSteps()})`; + } + if (this.title) { + return this.title; + } + if (this.step) { + return this.getSteps(); + } + return ''; + } + + private getDescription() { + return this.description || ''; + } + + private getSteps() { + if (this.step && this.totalSteps) { + return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps); + } + if (this.step) { + return String(this.step); + } + return ''; + } + + protected showMessageDecoration(severity: Severity) { + this.ui.inputBox.showDecoration(severity); + if (severity === Severity.Error) { + const styles = this.ui.inputBox.stylesForType(severity); + this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : ''; + this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : ''; + this.ui.message.style.paddingBottom = '4px'; + } else { + this.ui.message.style.backgroundColor = ''; + this.ui.message.style.border = ''; + this.ui.message.style.paddingBottom = ''; + } + } + + public dispose(): void { + this.hide(); + super.dispose(); + } +} + +class QuickPick extends QuickInput implements IQuickPick { + + private static readonly INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); + + private _value = ''; + private _placeholder: string | undefined; + private readonly onDidChangeValueEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidCustomEmitter = this._register(new Emitter()); + private _items: Array = []; + private itemsUpdated = false; + private _canSelectMany = false; + private _matchOnDescription = false; + private _matchOnDetail = false; + private _matchOnLabel = true; + private _sortByLabel = true; + private _autoFocusOnList = true; + private _activeItems: T[] = []; + private activeItemsUpdated = false; + private activeItemsToConfirm: T[] | null = []; + private readonly onDidChangeActiveEmitter = this._register(new Emitter()); + private _selectedItems: T[] = []; + private selectedItemsUpdated = false; + private selectedItemsToConfirm: T[] | null = []; + private readonly onDidChangeSelectionEmitter = this._register(new Emitter()); + private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter>()); + private _valueSelection: Readonly<[number, number]> | undefined; + private valueSelectionUpdated = true; + private _validationMessage: string | undefined; + private _ok = false; + private _customButton = false; + private _customButtonLabel: string | undefined; + private _customButtonHover: string | undefined; + + quickNavigate: IQuickNavigateConfiguration | undefined; + + + get value() { + return this._value; + } + + set value(value: string) { + this._value = value || ''; + this.update(); + } + + get placeholder() { + return this._placeholder; + } + + set placeholder(placeholder: string | undefined) { + this._placeholder = placeholder; + this.update(); + } + + onDidChangeValue = this.onDidChangeValueEmitter.event; + + onDidAccept = this.onDidAcceptEmitter.event; + + onDidCustom = this.onDidCustomEmitter.event; + + get items() { + return this._items; + } + + set items(items: Array) { + this._items = items; + this.itemsUpdated = true; + this.update(); + } + + get canSelectMany() { + return this._canSelectMany; + } + + set canSelectMany(canSelectMany: boolean) { + this._canSelectMany = canSelectMany; + this.update(); + } + + get matchOnDescription() { + return this._matchOnDescription; + } + + set matchOnDescription(matchOnDescription: boolean) { + this._matchOnDescription = matchOnDescription; + this.update(); + } + + get matchOnDetail() { + return this._matchOnDetail; + } + + set matchOnDetail(matchOnDetail: boolean) { + this._matchOnDetail = matchOnDetail; + this.update(); + } + + get matchOnLabel() { + return this._matchOnLabel; + } + + set matchOnLabel(matchOnLabel: boolean) { + this._matchOnLabel = matchOnLabel; + this.update(); + } + + get sortByLabel() { + return this._sortByLabel; + } + + set sortByLabel(sortByLabel: boolean) { + this._sortByLabel = sortByLabel; + this.update(); + } + + + get autoFocusOnList() { + return this._autoFocusOnList; + } + + set autoFocusOnList(autoFocusOnList: boolean) { + this._autoFocusOnList = autoFocusOnList; + this.update(); + } + + get activeItems() { + return this._activeItems; + } + + set activeItems(activeItems: T[]) { + this._activeItems = activeItems; + this.activeItemsUpdated = true; + this.update(); + } + + onDidChangeActive = this.onDidChangeActiveEmitter.event; + + get selectedItems() { + return this._selectedItems; + } + + set selectedItems(selectedItems: T[]) { + this._selectedItems = selectedItems; + this.selectedItemsUpdated = true; + this.update(); + } + + get keyMods() { + return this.ui.keyMods; + } + + set valueSelection(valueSelection: Readonly<[number, number]>) { + this._valueSelection = valueSelection; + this.valueSelectionUpdated = true; + this.update(); + } + + get validationMessage() { + return this._validationMessage; + } + + set validationMessage(validationMessage: string | undefined) { + this._validationMessage = validationMessage; + this.update(); + } + + get customButton() { + return this._customButton; + } + + set customButton(showCustomButton: boolean) { + this._customButton = showCustomButton; + this.update(); + } + + get customLabel() { + return this._customButtonLabel; + } + + set customLabel(label: string | undefined) { + this._customButtonLabel = label; + this.update(); + } + + get customHover() { + return this._customButtonHover; + } + + set customHover(hover: string | undefined) { + this._customButtonHover = hover; + this.update(); + } + + get ok() { + return this._ok; + } + + set ok(showOkButton: boolean) { + this._ok = showOkButton; + this.update(); + } + + public inputHasFocus(): boolean { + return this.visible ? this.ui.inputBox.hasFocus() : false; + } + + public focusOnInput() { + this.ui.inputBox.setFocus(); + } + + onDidChangeSelection = this.onDidChangeSelectionEmitter.event; + + onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; + + private trySelectFirst() { + if (this.autoFocusOnList) { + if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) { + this.ui.list.focus('First'); + } + } + } + + show() { + if (!this.visible) { + this.visibleDisposables.add( + this.ui.inputBox.onDidChange(value => { + if (value === this.value) { + return; + } + this._value = value; + this.ui.list.filter(this.ui.inputBox.value); + this.trySelectFirst(); + this.onDidChangeValueEmitter.fire(value); + })); + this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => { + if (!this.autoFocusOnList) { + this.ui.list.clearFocus(); + } + })); + this.visibleDisposables.add(this.ui.inputBox.onKeyDown(event => { + switch (event.keyCode) { + case KeyCode.DownArrow: + this.ui.list.focus('Next'); + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + case KeyCode.UpArrow: + if (this.ui.list.getFocusedElements().length) { + this.ui.list.focus('Previous'); + } else { + this.ui.list.focus('Last'); + } + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + case KeyCode.PageDown: + if (this.ui.list.getFocusedElements().length) { + this.ui.list.focus('NextPage'); + } else { + this.ui.list.focus('First'); + } + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + case KeyCode.PageUp: + if (this.ui.list.getFocusedElements().length) { + this.ui.list.focus('PreviousPage'); + } else { + this.ui.list.focus('Last'); + } + if (this.canSelectMany) { + this.ui.list.domFocus(); + } + event.preventDefault(); + break; + } + })); + this.visibleDisposables.add(this.ui.onDidAccept(() => { + if (!this.canSelectMany && this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + } + this.onDidAcceptEmitter.fire(undefined); + })); + this.visibleDisposables.add(this.ui.onDidCustom(() => { + this.onDidCustomEmitter.fire(undefined); + })); + this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => { + if (this.activeItemsUpdated) { + return; // Expect another event. + } + if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) { + return; + } + this._activeItems = focusedItems as T[]; + this.onDidChangeActiveEmitter.fire(focusedItems as T[]); + })); + this.visibleDisposables.add(this.ui.list.onDidChangeSelection(selectedItems => { + if (this.canSelectMany) { + if (selectedItems.length) { + this.ui.list.setSelectedElements([]); + } + return; + } + if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) { + return; + } + this._selectedItems = selectedItems as T[]; + this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); + if (selectedItems.length) { + this.onDidAcceptEmitter.fire(undefined); + } + })); + this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { + if (!this.canSelectMany) { + return; + } + if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) { + return; + } + this._selectedItems = checkedItems as T[]; + this.onDidChangeSelectionEmitter.fire(checkedItems as T[]); + })); + this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent))); + this.visibleDisposables.add(this.registerQuickNavigation()); + this.valueSelectionUpdated = true; + } + super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.) + } + + private registerQuickNavigation() { + return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => { + if (this.canSelectMany || !this.quickNavigate) { + return; + } + + const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); + const keyCode = keyboardEvent.keyCode; + + // Select element when keys are pressed that signal it + const quickNavKeys = this.quickNavigate.keybindings; + const wasTriggerKeyPressed = quickNavKeys.some(k => { + const [firstPart, chordPart] = k.getParts(); + if (chordPart) { + return false; + } + + if (firstPart.shiftKey && keyCode === KeyCode.Shift) { + if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { + return false; // this is an optimistic check for the shift key being used to navigate back in quick open + } + + return true; + } + + if (firstPart.altKey && keyCode === KeyCode.Alt) { + return true; + } + + if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { + return true; + } + + if (firstPart.metaKey && keyCode === KeyCode.Meta) { + return true; + } + + return false; + }); + + if (wasTriggerKeyPressed && this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.onDidAcceptEmitter.fire(undefined); + } + }); + } + + protected update() { + if (!this.visible) { + return; + } + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, description: !!this.description, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: this.ok, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, description: !!this.description, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); + super.update(); + if (this.ui.inputBox.value !== this.value) { + this.ui.inputBox.value = this.value; + } + if (this.valueSelectionUpdated) { + this.valueSelectionUpdated = false; + this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); + } + if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { + this.ui.inputBox.placeholder = (this.placeholder || ''); + } + if (this.itemsUpdated) { + this.itemsUpdated = false; + this.ui.list.setElements(this.items); + this.ui.list.filter(this.ui.inputBox.value); + this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); + this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); + this.ui.count.setCount(this.ui.list.getCheckedCount()); + this.trySelectFirst(); + } + if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) { + if (this.canSelectMany) { + this.ui.list.clearFocus(); + } else { + this.trySelectFirst(); + } + } + if (this.activeItemsUpdated) { + this.activeItemsUpdated = false; + this.activeItemsToConfirm = this._activeItems; + this.ui.list.setFocusedElements(this.activeItems); + if (this.activeItemsToConfirm === this._activeItems) { + this.activeItemsToConfirm = null; + } + } + if (this.selectedItemsUpdated) { + this.selectedItemsUpdated = false; + this.selectedItemsToConfirm = this._selectedItems; + if (this.canSelectMany) { + this.ui.list.setCheckedElements(this.selectedItems); + } else { + this.ui.list.setSelectedElements(this.selectedItems); + } + if (this.selectedItemsToConfirm === this._selectedItems) { + this.selectedItemsToConfirm = null; + } + } + if (this.validationMessage) { + this.ui.message.textContent = this.validationMessage; + this.showMessageDecoration(Severity.Error); + } else { + this.ui.message.textContent = null; + this.showMessageDecoration(Severity.Ignore); + } + this.ui.customButton.label = this.customLabel || ''; + this.ui.customButton.element.title = this.customHover || ''; + this.ui.list.matchOnDescription = this.matchOnDescription; + this.ui.list.matchOnDetail = this.matchOnDetail; + this.ui.list.matchOnLabel = this.matchOnLabel; + this.ui.list.sortByLabel = this.sortByLabel; + this.ui.setComboboxAccessibility(true); + this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); + } +} + +class InputBox extends QuickInput implements IInputBox { + + private static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); + + private _value = ''; + private _valueSelection: Readonly<[number, number]> | undefined; + private valueSelectionUpdated = true; + private _placeholder: string | undefined; + private _password = false; + private _prompt: string | undefined; + private noValidationMessage = InputBox.noPromptMessage; + private _validationMessage: string | undefined; + private readonly onDidValueChangeEmitter = this._register(new Emitter()); + private readonly onDidAcceptEmitter = this._register(new Emitter()); + + get value() { + return this._value; + } + + set value(value: string) { + this._value = value || ''; + this.update(); + } + + set valueSelection(valueSelection: Readonly<[number, number]>) { + this._valueSelection = valueSelection; + this.valueSelectionUpdated = true; + this.update(); + } + + get placeholder() { + return this._placeholder; + } + + set placeholder(placeholder: string | undefined) { + this._placeholder = placeholder; + this.update(); + } + + get password() { + return this._password; + } + + set password(password: boolean) { + this._password = password; + this.update(); + } + + get prompt() { + return this._prompt; + } + + set prompt(prompt: string | undefined) { + this._prompt = prompt; + this.noValidationMessage = prompt + ? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt) + : InputBox.noPromptMessage; + this.update(); + } + + get validationMessage() { + return this._validationMessage; + } + + set validationMessage(validationMessage: string | undefined) { + this._validationMessage = validationMessage; + this.update(); + } + + readonly onDidChangeValue = this.onDidValueChangeEmitter.event; + + readonly onDidAccept = this.onDidAcceptEmitter.event; + + show() { + if (!this.visible) { + this.visibleDisposables.add( + this.ui.inputBox.onDidChange(value => { + if (value === this.value) { + return; + } + this._value = value; + this.onDidValueChangeEmitter.fire(value); + })); + this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire(undefined))); + this.valueSelectionUpdated = true; + } + super.show(); + } + + protected update() { + if (!this.visible) { + return; + } + this.ui.setVisibilities({ title: !!this.title || !!this.step, description: !!this.description || !!this.step, inputBox: true, message: true }); + super.update(); + if (this.ui.inputBox.value !== this.value) { + this.ui.inputBox.value = this.value; + } + if (this.valueSelectionUpdated) { + this.valueSelectionUpdated = false; + this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); + } + if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { + this.ui.inputBox.placeholder = (this.placeholder || ''); + } + if (this.ui.inputBox.password !== this.password) { + this.ui.inputBox.password = this.password; + } + if (!this.validationMessage && this.ui.message.textContent !== this.noValidationMessage) { + this.ui.message.textContent = this.noValidationMessage; + this.showMessageDecoration(Severity.Ignore); + } + if (this.validationMessage && this.ui.message.textContent !== this.validationMessage) { + this.ui.message.textContent = this.validationMessage; + this.showMessageDecoration(Severity.Error); + } + } +} + +export class QuickInputController extends Disposable { + private static readonly MAX_WIDTH = 600; // Max total width of quick open widget + + private idPrefix: string; + private ui: QuickInputUI | undefined; + private dimension?: dom.Dimension; + private titleBarOffset?: number; + private comboboxAccessibility = false; + private enabled = true; + private readonly onDidAcceptEmitter = this._register(new Emitter()); + private readonly onDidCustomEmitter = this._register(new Emitter()); + private readonly onDidTriggerButtonEmitter = this._register(new Emitter()); + private keyMods: Writeable = { ctrlCmd: false, alt: false }; + + private controller: QuickInput | null = null; + + private parentElement: HTMLElement; + private styles: IQuickInputStyles; + private onShowEmitter = new Emitter(); + private onHideEmitter = new Emitter(); + public onShow = this.onShowEmitter.event; + public onHide = this.onHideEmitter.event; + + constructor(private options: IQuickInputOptions) { + super(); + this.idPrefix = options.idPrefix; + this.parentElement = options.container; + this.styles = options.styles; + this.registerKeyModsListeners(); + } + + private registerKeyModsListeners() { + this._register(dom.addDisposableListener(this.parentElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + switch (event.keyCode) { + case KeyCode.Ctrl: + case KeyCode.Meta: + this.keyMods.ctrlCmd = true; + break; + case KeyCode.Alt: + this.keyMods.alt = true; + break; + } + })); + this._register(dom.addDisposableListener(this.parentElement, dom.EventType.KEY_UP, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + switch (event.keyCode) { + case KeyCode.Ctrl: + case KeyCode.Meta: + this.keyMods.ctrlCmd = false; + break; + case KeyCode.Alt: + this.keyMods.alt = false; + break; + } + })); + } + + private getUI() { + if (this.ui) { + return this.ui; + } + + const container = dom.append(this.parentElement, $('.quick-input-widget.show-file-icons')); + container.tabIndex = -1; + container.style.display = 'none'; + + const styleSheet = dom.createStyleSheet(container); + + const titleBar = dom.append(container, $('.quick-input-titlebar')); + + const leftActionBar = this._register(new ActionBar(titleBar)); + leftActionBar.domNode.classList.add('quick-input-left-action-bar'); + + const title = dom.append(titleBar, $('.quick-input-title')); + + const rightActionBar = this._register(new ActionBar(titleBar)); + rightActionBar.domNode.classList.add('quick-input-right-action-bar'); + + const description = dom.append(container, $('.quick-input-description')); + + const headerContainer = dom.append(container, $('.quick-input-header')); + + const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); + checkAll.type = 'checkbox'; + this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => { + const checked = checkAll.checked; + list.setAllVisibleChecked(checked); + })); + this._register(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => { + if (e.x || e.y) { // Avoid 'click' triggered by 'space'... + inputBox.setFocus(); + } + })); + + const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); + const filterContainer = dom.append(extraContainer, $('.quick-input-filter')); + + const inputBox = this._register(new QuickInputBox(filterContainer)); + inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); + + const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); + visibleCountContainer.setAttribute('aria-live', 'polite'); + visibleCountContainer.setAttribute('aria-atomic', 'true'); + const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); + + const countContainer = dom.append(filterContainer, $('.quick-input-count')); + countContainer.setAttribute('aria-live', 'polite'); + const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); + + const okContainer = dom.append(headerContainer, $('.quick-input-action')); + const ok = new Button(okContainer); + ok.label = localize('ok', "OK"); + this._register(ok.onDidClick(e => { + this.onDidAcceptEmitter.fire(); + })); + + const customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); + const customButton = new Button(customButtonContainer); + customButton.label = localize('custom', "Custom"); + this._register(customButton.onDidClick(e => { + this.onDidCustomEmitter.fire(); + })); + + const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`)); + + const progressBar = new ProgressBar(container); + dom.addClass(progressBar.getContainer(), 'quick-input-progress'); + + const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options)); + this._register(list.onChangedAllVisibleChecked(checked => { + checkAll.checked = checked; + })); + this._register(list.onChangedVisibleCount(c => { + visibleCount.setCount(c); + })); + this._register(list.onChangedCheckedCount(c => { + count.setCount(c); + })); + this._register(list.onLeave(() => { + // Defer to avoid the input field reacting to the triggering key. + setTimeout(() => { + inputBox.setFocus(); + if (this.controller instanceof QuickPick && this.controller.canSelectMany) { + list.clearFocus(); + } + }, 0); + })); + this._register(list.onDidChangeFocus(() => { + if (this.comboboxAccessibility) { + this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); + } + })); + + const focusTracker = dom.trackFocus(container); + this._register(focusTracker); + this._register(focusTracker.onDidBlur(() => { + if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) { + this.hide(true); + } + })); + this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { + inputBox.setFocus(); + })); + this._register(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + switch (event.keyCode) { + case KeyCode.Enter: + dom.EventHelper.stop(e, true); + this.onDidAcceptEmitter.fire(); + break; + case KeyCode.Escape: + dom.EventHelper.stop(e, true); + this.hide(); + break; + case KeyCode.Tab: + if (!event.altKey && !event.ctrlKey && !event.metaKey) { + const selectors = ['.action-label.codicon']; + if (container.classList.contains('show-checkboxes')) { + selectors.push('input'); + } else { + selectors.push('input[type=text]'); + } + if (this.getUI().list.isDisplayed()) { + selectors.push('.monaco-list'); + } + const stops = container.querySelectorAll(selectors.join(', ')); + if (event.shiftKey && event.target === stops[0]) { + dom.EventHelper.stop(e, true); + stops[stops.length - 1].focus(); + } else if (!event.shiftKey && event.target === stops[stops.length - 1]) { + dom.EventHelper.stop(e, true); + stops[0].focus(); + } + } + break; + } + })); + + this.ui = { + container, + styleSheet, + leftActionBar, + titleBar, + title, + description, + rightActionBar, + checkAll, + filterContainer, + inputBox, + visibleCountContainer, + visibleCount, + countContainer, + count, + okContainer, + ok, + message, + customButtonContainer, + customButton, + progressBar, + list, + onDidAccept: this.onDidAcceptEmitter.event, + onDidCustom: this.onDidCustomEmitter.event, + onDidTriggerButton: this.onDidTriggerButtonEmitter.event, + ignoreFocusOut: false, + keyMods: this.keyMods, + isScreenReaderOptimized: () => this.options.isScreenReaderOptimized(), + show: controller => this.show(controller), + hide: () => this.hide(), + setVisibilities: visibilities => this.setVisibilities(visibilities), + setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled), + setEnabled: enabled => this.setEnabled(enabled), + setContextKey: contextKey => this.options.setContextKey(contextKey), + }; + this.updateStyles(); + return this.ui; + } + + pick>(picks: Promise[]> | QuickPickInput[], options: O = {}, token: CancellationToken = CancellationToken.None): Promise { + return new Promise((doResolve, reject) => { + let resolve = (result: any) => { + resolve = doResolve; + if (options.onKeyMods) { + options.onKeyMods(input.keyMods); + } + doResolve(result); + }; + if (token.isCancellationRequested) { + resolve(undefined); + return; + } + const input = this.createQuickPick(); + let activeItem: T | undefined; + const disposables = [ + input, + input.onDidAccept(() => { + if (input.canSelectMany) { + resolve(input.selectedItems.slice()); + input.hide(); + } else { + const result = input.activeItems[0]; + if (result) { + resolve(result); + input.hide(); + } + } + }), + input.onDidChangeActive(items => { + const focused = items[0]; + if (focused && options.onDidFocus) { + options.onDidFocus(focused); + } + }), + input.onDidChangeSelection(items => { + if (!input.canSelectMany) { + const result = items[0]; + if (result) { + resolve(result); + input.hide(); + } + } + }), + input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton({ + ...event, + removeItem: () => { + const index = input.items.indexOf(event.item); + if (index !== -1) { + const items = input.items.slice(); + items.splice(index, 1); + input.items = items; + } + } + })), + input.onDidChangeValue(value => { + if (activeItem && !value && (input.activeItems.length !== 1 || input.activeItems[0] !== activeItem)) { + input.activeItems = [activeItem]; + } + }), + token.onCancellationRequested(() => { + input.hide(); + }), + input.onDidHide(() => { + dispose(disposables); + resolve(undefined); + }), + ]; + input.canSelectMany = !!options.canPickMany; + input.placeholder = options.placeHolder; + input.ignoreFocusOut = !!options.ignoreFocusLost; + input.matchOnDescription = !!options.matchOnDescription; + input.matchOnDetail = !!options.matchOnDetail; + input.matchOnLabel = (options.matchOnLabel === undefined) || options.matchOnLabel; // default to true + input.autoFocusOnList = (options.autoFocusOnList === undefined) || options.autoFocusOnList; // default to true + input.quickNavigate = options.quickNavigate; + input.contextKey = options.contextKey; + input.busy = true; + Promise.all[], T | undefined>([picks, options.activeItem]) + .then(([items, _activeItem]) => { + activeItem = _activeItem; + input.busy = false; + input.items = items; + if (input.canSelectMany) { + input.selectedItems = items.filter(item => item.type !== 'separator' && item.picked) as T[]; + } + if (activeItem) { + input.activeItems = [activeItem]; + } + }); + input.show(); + Promise.resolve(picks).then(undefined, err => { + reject(err); + input.hide(); + }); + }); + } + + input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise { + return new Promise((resolve, reject) => { + if (token.isCancellationRequested) { + resolve(undefined); + return; + } + const input = this.createInputBox(); + const validateInput = options.validateInput || (() => >Promise.resolve(undefined)); + const onDidValueChange = Event.debounce(input.onDidChangeValue, (last, cur) => cur, 100); + let validationValue = options.value || ''; + let validation = Promise.resolve(validateInput(validationValue)); + const disposables = [ + input, + onDidValueChange(value => { + if (value !== validationValue) { + validation = Promise.resolve(validateInput(value)); + validationValue = value; + } + validation.then(result => { + if (value === validationValue) { + input.validationMessage = result || undefined; + } + }); + }), + input.onDidAccept(() => { + const value = input.value; + if (value !== validationValue) { + validation = Promise.resolve(validateInput(value)); + validationValue = value; + } + validation.then(result => { + if (!result) { + resolve(value); + input.hide(); + } else if (value === validationValue) { + input.validationMessage = result; + } + }); + }), + token.onCancellationRequested(() => { + input.hide(); + }), + input.onDidHide(() => { + dispose(disposables); + resolve(undefined); + }), + ]; + input.value = options.value || ''; + input.valueSelection = options.valueSelection; + input.prompt = options.prompt; + input.placeholder = options.placeHolder; + input.password = !!options.password; + input.ignoreFocusOut = !!options.ignoreFocusLost; + input.show(); + }); + } + + backButton = backButton; + + createQuickPick(): IQuickPick { + const ui = this.getUI(); + return new QuickPick(ui); + } + + createInputBox(): IInputBox { + const ui = this.getUI(); + return new InputBox(ui); + } + + private show(controller: QuickInput) { + const ui = this.getUI(); + this.onShowEmitter.fire(); + const oldController = this.controller; + this.controller = controller; + if (oldController) { + oldController.didHide(); + } + + this.setEnabled(true); + ui.leftActionBar.clear(); + ui.title.textContent = ''; + ui.description.textContent = ''; + ui.rightActionBar.clear(); + ui.checkAll.checked = false; + // ui.inputBox.value = ''; Avoid triggering an event. + ui.inputBox.placeholder = ''; + ui.inputBox.password = false; + ui.inputBox.showDecoration(Severity.Ignore); + ui.visibleCount.setCount(0); + ui.count.setCount(0); + ui.message.textContent = ''; + ui.progressBar.stop(); + ui.list.setElements([]); + ui.list.matchOnDescription = false; + ui.list.matchOnDetail = false; + ui.list.matchOnLabel = true; + ui.list.sortByLabel = true; + ui.ignoreFocusOut = false; + this.setComboboxAccessibility(false); + ui.inputBox.removeAttribute('aria-label'); + + const backKeybindingLabel = this.options.backKeybindingLabel(); + backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back"); + + ui.container.style.display = ''; + this.updateLayout(); + ui.inputBox.setFocus(); + } + + private setVisibilities(visibilities: Visibilities) { + const ui = this.getUI(); + ui.title.style.display = visibilities.title ? '' : 'none'; + ui.description.style.display = visibilities.description ? '' : 'none'; + ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; + ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; + ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; + ui.countContainer.style.display = visibilities.count ? '' : 'none'; + ui.okContainer.style.display = visibilities.ok ? '' : 'none'; + ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; + ui.message.style.display = visibilities.message ? '' : 'none'; + ui.list.display(!!visibilities.list); + ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); + this.updateLayout(); // TODO + } + + private setComboboxAccessibility(enabled: boolean) { + if (enabled !== this.comboboxAccessibility) { + const ui = this.getUI(); + this.comboboxAccessibility = enabled; + if (this.comboboxAccessibility) { + ui.inputBox.setAttribute('role', 'combobox'); + ui.inputBox.setAttribute('aria-haspopup', 'true'); + ui.inputBox.setAttribute('aria-autocomplete', 'list'); + ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); + } else { + ui.inputBox.removeAttribute('role'); + ui.inputBox.removeAttribute('aria-haspopup'); + ui.inputBox.removeAttribute('aria-autocomplete'); + ui.inputBox.removeAttribute('aria-activedescendant'); + } + } + } + + private setEnabled(enabled: boolean) { + if (enabled !== this.enabled) { + this.enabled = enabled; + for (const item of this.getUI().leftActionBar.viewItems) { + (item as ActionViewItem).getAction().enabled = enabled; + } + for (const item of this.getUI().rightActionBar.viewItems) { + (item as ActionViewItem).getAction().enabled = enabled; + } + this.getUI().checkAll.disabled = !enabled; + // this.getUI().inputBox.enabled = enabled; Avoid loosing focus. + this.getUI().ok.enabled = enabled; + this.getUI().list.enabled = enabled; + } + } + + public hide(focusLost?: boolean) { + const controller = this.controller; + if (controller) { + this.controller = null; + this.onHideEmitter.fire(); + this.getUI().container.style.display = 'none'; + if (!focusLost) { + this.options.returnFocus(); + } + controller.didHide(); + } + } + + focus() { + if (this.isDisplayed()) { + this.getUI().inputBox.setFocus(); + } + } + + toggle() { + if (this.isDisplayed() && this.controller instanceof QuickPick && this.controller.canSelectMany) { + this.getUI().list.toggleCheckbox(); + } + } + + navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { + if (this.isDisplayed() && this.getUI().list.isDisplayed()) { + this.getUI().list.focus(next ? 'Next' : 'Previous'); + if (quickNavigate && this.controller instanceof QuickPick) { + this.controller.quickNavigate = quickNavigate; + } + } + } + + accept() { + this.onDidAcceptEmitter.fire(); + return Promise.resolve(undefined); + } + + back() { + this.onDidTriggerButtonEmitter.fire(this.backButton); + return Promise.resolve(undefined); + } + + cancel() { + this.hide(); + return Promise.resolve(undefined); + } + + layout(dimension: dom.Dimension, titleBarOffset: number): void { + this.dimension = dimension; + this.titleBarOffset = titleBarOffset; + this.updateLayout(); + } + + private updateLayout() { + if (this.ui) { + this.ui.container.style.top = `${this.titleBarOffset}px`; + + const style = this.ui.container.style; + const width = Math.min(this.dimension!.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH); + style.width = width + 'px'; + style.marginLeft = '-' + (width / 2) + 'px'; + + this.ui.inputBox.layout(); + this.ui.list.layout(this.dimension && this.dimension.height * 0.4); + } + } + + public applyStyles(styles: IQuickInputStyles) { + this.styles = styles; + this.updateStyles(); + } + + private updateStyles() { + if (this.ui) { + const { + titleColor, + quickInputBackground, + quickInputForeground, + contrastBorder, + widgetShadow, + } = this.styles.widget; + this.ui.titleBar.style.backgroundColor = titleColor || ''; + this.ui.container.style.backgroundColor = quickInputBackground ? quickInputBackground.toString() : ''; + this.ui.container.style.color = quickInputForeground ? quickInputForeground.toString() : ''; + this.ui.container.style.border = contrastBorder ? `1px solid ${contrastBorder}` : ''; + this.ui.container.style.boxShadow = widgetShadow ? `0 5px 8px ${widgetShadow}` : ''; + this.ui.inputBox.style(this.styles.inputBox); + this.ui.count.style(this.styles.countBadge); + this.ui.ok.style(this.styles.button); + this.ui.customButton.style(this.styles.button); + this.ui.progressBar.style(this.styles.progressBar); + this.ui.list.style(this.styles.list); + + const content: string[] = []; + if (this.styles.list.listInactiveFocusForeground) { + content.push(`.monaco-list .monaco-list-row.focused { color: ${this.styles.list.listInactiveFocusForeground}; }`); + content.push(`.monaco-list .monaco-list-row.focused:hover { color: ${this.styles.list.listInactiveFocusForeground}; }`); // overwrite :hover style in this case! + } + if (this.styles.list.pickerGroupBorder) { + content.push(`.quick-input-list .quick-input-list-entry { border-top-color: ${this.styles.list.pickerGroupBorder}; }`); + } + if (this.styles.list.pickerGroupForeground) { + content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.list.pickerGroupForeground}; }`); + } + const newStyles = content.join('\n'); + if (newStyles !== this.ui.styleSheet.innerHTML) { + this.ui.styleSheet.innerHTML = newStyles; + } + } + } + + private isDisplayed() { + return this.ui && this.ui.container.style.display !== 'none'; + } +} diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts similarity index 68% rename from src/vs/workbench/browser/parts/quickinput/quickInputBox.ts rename to src/vs/base/parts/quickinput/browser/quickInputBox.ts index 55ca955a79..d047c88520 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputBox.ts @@ -5,9 +5,7 @@ import 'vs/css!./media/quickInput'; import * as dom from 'vs/base/browser/dom'; -import { InputBox, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; -import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; -import { ITheme } from 'vs/platform/theme/common/themeService'; +import { InputBox, IRange, MessageType, IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import Severity from 'vs/base/common/severity'; @@ -112,20 +110,7 @@ export class QuickInputBox extends Disposable { this.inputBox.layout(); } - style(theme: ITheme) { - this.inputBox.style({ - inputForeground: theme.getColor(inputForeground), - inputBackground: theme.getColor(inputBackground), - inputBorder: theme.getColor(inputBorder), - inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground), - inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground), - inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground), - inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground), - inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), - inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), - inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), - }); + style(styles: IInputBoxStyles) { + this.inputBox.style(styles); } } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/base/parts/quickinput/browser/quickInputList.ts similarity index 87% rename from src/vs/workbench/browser/parts/quickinput/quickInputList.ts rename to src/vs/base/parts/quickinput/browser/quickInputList.ts index 1765f4deb5..aa6e6a7896 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/base/parts/quickinput/browser/quickInputList.ts @@ -7,9 +7,7 @@ import 'vs/css!./media/quickInput'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { WorkbenchList, IWorkbenchListOptions } from 'vs/platform/list/browser/listService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon'; import { compareAnything } from 'vs/base/common/comparers'; @@ -22,13 +20,12 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { memoize } from 'vs/base/common/decorators'; import { range } from 'vs/base/common/arrays'; import * as platform from 'vs/base/common/platform'; -import { listFocusBackground, pickerGroupBorder, pickerGroupForeground, activeContrastBorder, listFocusForeground } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; -import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; +import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { QUICK_INPUT_BACKGROUND } from 'vs/workbench/common/theme'; +import { IQuickInputOptions } from 'vs/base/parts/quickinput/browser/quickInput'; +import { IListOptions, List, IListStyles } from 'vs/base/browser/ui/list/listWidget'; const $ = dom.$; @@ -215,7 +212,7 @@ export class QuickInputList { readonly id: string; private container: HTMLElement; - private list: WorkbenchList; + private list: List; private inputElements: Array = []; private elements: ListElement[] = []; private elementsToIndexes = new Map(); @@ -242,21 +239,18 @@ export class QuickInputList { constructor( private parent: HTMLElement, id: string, - @IInstantiationService private readonly instantiationService: IInstantiationService + options: IQuickInputOptions, ) { this.id = id; this.container = dom.append(this.parent, $('.quick-input-list')); const delegate = new ListElementDelegate(); - this.list = this.instantiationService.createInstance(WorkbenchList, 'QuickInput', this.container, delegate, [new ListElementRenderer()], { + this.list = options.createList('QuickInput', this.container, delegate, [new ListElementRenderer()], { identityProvider: { getId: element => element.saneLabel }, openController: { shouldOpen: () => false }, // Workaround #58124 setRowLineHeight: false, multipleSelectionSupport: false, horizontalScrolling: false, - overrideStyles: { - listBackground: QUICK_INPUT_BACKGROUND - } - } as IWorkbenchListOptions); + } as IListOptions); this.list.getHTMLElement().id = id; this.disposables.push(this.list); this.disposables.push(this.list.onKeyDown(e => { @@ -577,6 +571,10 @@ export class QuickInputList { private fireButtonTriggered(event: IQuickPickItemButtonEvent) { this._onButtonTriggered.fire(event); } + + style(styles: IListStyles) { + this.list.style(styles); + } } function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number { @@ -593,30 +591,3 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor); } - -registerThemingParticipant((theme, collector) => { - // Override inactive focus foreground with active focus foreground for single-pick case. - const listInactiveFocusForeground = theme.getColor(listFocusForeground); - if (listInactiveFocusForeground) { - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { color: ${listInactiveFocusForeground}; }`); - } - // Override inactive focus background with active focus background for single-pick case. - const listInactiveFocusBackground = theme.getColor(listFocusBackground); - if (listInactiveFocusBackground) { - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`); - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`); - } - // dotted instead of solid (as in listWidget.ts) to match QuickOpen - const activeContrast = theme.getColor(activeContrastBorder); - if (activeContrast) { - collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { outline: 1px dotted ${activeContrast}; outline-offset: -1px; }`); - } - const pickerGroupBorderColor = theme.getColor(pickerGroupBorder); - if (pickerGroupBorderColor) { - collector.addRule(`.quick-input-list .quick-input-list-entry { border-top-color: ${pickerGroupBorderColor}; }`); - } - const pickerGroupForegroundColor = theme.getColor(pickerGroupForeground); - if (pickerGroupForegroundColor) { - collector.addRule(`.quick-input-list .quick-input-list-separator { color: ${pickerGroupForegroundColor}; }`); - } -}); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts b/src/vs/base/parts/quickinput/browser/quickInputUtils.ts similarity index 100% rename from src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts rename to src/vs/base/parts/quickinput/browser/quickInputUtils.ts diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts new file mode 100644 index 0000000000..7228ca0649 --- /dev/null +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -0,0 +1,256 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { URI } from 'vs/base/common/uri'; +import { Event } from 'vs/base/common/event'; + +export interface IQuickPickItem { + type?: 'item'; + id?: string; + label: string; + description?: string; + detail?: string; + iconClasses?: string[]; + buttons?: IQuickInputButton[]; + picked?: boolean; + alwaysShow?: boolean; +} + +export interface IQuickPickSeparator { + type: 'separator'; + label?: string; +} + +export interface IKeyMods { + readonly ctrlCmd: boolean; + readonly alt: boolean; +} + +export interface IQuickNavigateConfiguration { + keybindings: ResolvedKeybinding[]; +} + +export interface IPickOptions { + + /** + * an optional string to show as placeholder in the input box to guide the user what she picks on + */ + placeHolder?: string; + + /** + * an optional flag to include the description when filtering the picks + */ + matchOnDescription?: boolean; + + /** + * an optional flag to include the detail when filtering the picks + */ + matchOnDetail?: boolean; + + /** + * an optional flag to filter the picks based on label. Defaults to true. + */ + matchOnLabel?: boolean; + + /** + * an option flag to control whether focus is always automatically brought to a list item. Defaults to true. + */ + autoFocusOnList?: boolean; + + /** + * an optional flag to not close the picker on focus lost + */ + ignoreFocusLost?: boolean; + + /** + * an optional flag to make this picker multi-select + */ + canPickMany?: boolean; + + /** + * enables quick navigate in the picker to open an element without typing + */ + quickNavigate?: IQuickNavigateConfiguration; + + /** + * a context key to set when this picker is active + */ + contextKey?: string; + + /** + * an optional property for the item to focus initially. + */ + activeItem?: Promise | T; + + onKeyMods?: (keyMods: IKeyMods) => void; + onDidFocus?: (entry: T) => void; + onDidTriggerItemButton?: (context: IQuickPickItemButtonContext) => void; +} + +export interface IInputOptions { + + /** + * the value to prefill in the input box + */ + value?: string; + + /** + * the selection of value, default to the whole word + */ + valueSelection?: [number, number]; + + /** + * the text to display underneath the input box + */ + prompt?: string; + + /** + * an optional string to show as placeholder in the input box to guide the user what to type + */ + placeHolder?: string; + + /** + * set to true to show a password prompt that will not show the typed value + */ + password?: boolean; + + ignoreFocusLost?: boolean; + + /** + * an optional function that is used to validate user input. + */ + validateInput?: (input: string) => Promise; +} + +export interface IQuickInput { + + title: string | undefined; + + description: string | undefined; + + step: number | undefined; + + totalSteps: number | undefined; + + enabled: boolean; + + contextKey: string | undefined; + + busy: boolean; + + ignoreFocusOut: boolean; + + show(): void; + + hide(): void; + + onDidHide: Event; + + dispose(): void; +} + +export interface IQuickPick extends IQuickInput { + + value: string; + + placeholder: string | undefined; + + readonly onDidChangeValue: Event; + + readonly onDidAccept: Event; + + ok: boolean; + + readonly onDidCustom: Event; + + customButton: boolean; + + customLabel: string | undefined; + + customHover: string | undefined; + + buttons: ReadonlyArray; + + readonly onDidTriggerButton: Event; + + readonly onDidTriggerItemButton: Event>; + + items: ReadonlyArray; + + canSelectMany: boolean; + + matchOnDescription: boolean; + + matchOnDetail: boolean; + + matchOnLabel: boolean; + + sortByLabel: boolean; + + autoFocusOnList: boolean; + + quickNavigate: IQuickNavigateConfiguration | undefined; + + activeItems: ReadonlyArray; + + readonly onDidChangeActive: Event; + + selectedItems: ReadonlyArray; + + readonly onDidChangeSelection: Event; + + readonly keyMods: IKeyMods; + + valueSelection: Readonly<[number, number]> | undefined; + + validationMessage: string | undefined; + + inputHasFocus(): boolean; + + focusOnInput(): void; +} + +export interface IInputBox extends IQuickInput { + + value: string; + + valueSelection: Readonly<[number, number]> | undefined; + + placeholder: string | undefined; + + password: boolean; + + readonly onDidChangeValue: Event; + + readonly onDidAccept: Event; + + buttons: ReadonlyArray; + + readonly onDidTriggerButton: Event; + + prompt: string | undefined; + + validationMessage: string | undefined; +} + +export interface IQuickInputButton { + /** iconPath or iconClass required */ + iconPath?: { dark: URI; light?: URI; }; + /** iconPath or iconClass required */ + iconClass?: string; + tooltip?: string; +} + +export interface IQuickPickItemButtonEvent { + button: IQuickInputButton; + item: T; +} + +export interface IQuickPickItemButtonContext extends IQuickPickItemButtonEvent { + removeItem(): void; +} + +export type QuickPickInput = T | IQuickPickSeparator; diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index a44fc8008f..b54ac4aeb3 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -754,6 +754,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider, IThem else if (autoFocus.autoFocusLastEntry) { if (entries.length > 1) { this.tree.focusLast(); + this.tree.reveal(this.tree.getFocus()); } } } diff --git a/src/vs/base/parts/quickopen/browser/quickopen.css b/src/vs/base/parts/quickopen/browser/quickopen.css index 6132b67329..d7e2f8a242 100644 --- a/src/vs/base/parts/quickopen/browser/quickopen.css +++ b/src/vs/base/parts/quickopen/browser/quickopen.css @@ -85,6 +85,10 @@ opacity: 1; } +.monaco-quick-open-widget .quick-open-tree .quick-open-entry .monaco-highlighted-label .codicon { + vertical-align: sub; /* vertically align codicon */ +} + .monaco-quick-open-widget .quick-open-tree .quick-open-entry-meta { opacity: 0.7; line-height: normal; diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index bbfc063f20..c7a8db9776 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -356,13 +356,15 @@ suite('IndexTreeModel', function () { assert.deepEqual(list[1].collapsible, false); assert.deepEqual(list[1].collapsed, false); - model.setCollapsed([0], true); - assert.deepEqual(list.length, 1); + assert.deepEqual(model.setCollapsed([0], true), false); assert.deepEqual(list[0].element, 0); assert.deepEqual(list[0].collapsible, false); - assert.deepEqual(list[0].collapsed, true); + assert.deepEqual(list[0].collapsed, false); + assert.deepEqual(list[1].element, 10); + assert.deepEqual(list[1].collapsible, false); + assert.deepEqual(list[1].collapsed, false); - model.setCollapsed([0], false); + assert.deepEqual(model.setCollapsed([0], false), false); assert.deepEqual(list[0].element, 0); assert.deepEqual(list[0].collapsible, false); assert.deepEqual(list[0].collapsed, false); @@ -379,13 +381,13 @@ suite('IndexTreeModel', function () { assert.deepEqual(list[1].collapsible, false); assert.deepEqual(list[1].collapsed, false); - model.setCollapsed([0], true); + assert.deepEqual(model.setCollapsed([0], true), true); assert.deepEqual(list.length, 1); assert.deepEqual(list[0].element, 0); assert.deepEqual(list[0].collapsible, true); assert.deepEqual(list[0].collapsed, true); - model.setCollapsed([0], false); + assert.deepEqual(model.setCollapsed([0], false), true); assert.deepEqual(list[0].element, 0); assert.deepEqual(list[0].collapsible, true); assert.deepEqual(list[0].collapsed, false); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index a85930d3ef..253ab63bd8 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -404,7 +404,7 @@ suite('URI', () => { path = 'foo/bar'; assert.equal(URI.file(path).path, '/foo/bar'); path = './foo/bar'; - assert.equal(URI.file(path).path, '/./foo/bar'); // todo@joh missing normalization + assert.equal(URI.file(path).path, '/./foo/bar'); // missing normalization const fileUri1 = URI.parse(`file:foo/bar`); assert.equal(fileUri1.path, '/foo/bar'); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 18e864443f..865004bbf7 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -26,35 +26,32 @@ export class DeferredPromise { public complete(value: T) { return new Promise(resolve => { - process.nextTick(() => { - this.completeCallback(value); - resolve(); - }); + this.completeCallback(value); + resolve(); }); } public error(err: any) { return new Promise(resolve => { - process.nextTick(() => { - this.errorCallback(err); - resolve(); - }); + this.errorCallback(err); + resolve(); }); } public cancel() { - process.nextTick(() => { + new Promise(resolve => { this.errorCallback(canceled()); + resolve(); }); } } export function toResource(this: any, path: string) { if (isWindows) { - return URI.file(join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + return URI.file(join('C:\\', btoa(this.test.fullTitle()), path)); } - return URI.file(join('/', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + return URI.file(join('/', btoa(this.test.fullTitle()), path)); } export function suiteRepeat(n: number, description: string, callback: (this: any) => void): void { diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/node/path.test.ts similarity index 99% rename from src/vs/base/test/common/path.test.ts rename to src/vs/base/test/node/path.test.ts index aca6388008..1b25fbe018 100644 --- a/src/vs/base/test/common/path.test.ts +++ b/src/vs/base/test/node/path.test.ts @@ -176,8 +176,8 @@ suite('Paths (Node Implementation)', () => { }); test('dirname', () => { - assert.strictEqual(path.dirname(path.normalize(__filename)).substr(-11), - isWindows ? 'test\\common' : 'test/common'); + assert.strictEqual(path.dirname(path.normalize(__filename)).substr(-9), + isWindows ? 'test\\node' : 'test/node'); assert.strictEqual(path.posix.dirname('/a/b/'), '/a'); assert.strictEqual(path.posix.dirname('/a/b'), '/a'); diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 7b62153765..b597b75324 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -7,47 +7,14 @@ import * as assert from 'assert'; import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as fs from 'fs'; -import { Readable } from 'stream'; import * as uuid from 'vs/base/common/uuid'; import * as pfs from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; -const chunkSize = 64 * 1024; -const readError = 'Error while reading'; -function toReadable(value: string, throwError?: boolean): Readable { - const totalChunks = Math.ceil(value.length / chunkSize); - const stringChunks: string[] = []; - - for (let i = 0, j = 0; i < totalChunks; ++i, j += chunkSize) { - stringChunks[i] = value.substr(j, chunkSize); - } - - let counter = 0; - return new Readable({ - read: function () { - if (throwError) { - this.emit('error', new Error(readError)); - } - - let res!: string; - let canPush = true; - while (canPush && (res = stringChunks[counter++])) { - canPush = this.push(res); - } - - // EOS - if (!res) { - this.push(null); - } - }, - encoding: 'utf8' - }); -} - suite('PFS', function () { // Given issues such as https://github.com/microsoft/vscode/issues/84066 @@ -334,7 +301,7 @@ suite('PFS', function () { test('stat link', async () => { if (isWindows) { - return Promise.resolve(); // Symlinks are not the same on win, and we can not create them programitically without admin privileges + return; // Symlinks are not the same on win, and we can not create them programitically without admin privileges } const id1 = uuid.generateUuid(); @@ -349,14 +316,38 @@ suite('PFS', function () { fs.symlinkSync(directory, symbolicLink); let statAndIsLink = await pfs.statLink(directory); - assert.ok(!statAndIsLink!.isSymbolicLink); + assert.ok(!statAndIsLink?.symbolicLink); statAndIsLink = await pfs.statLink(symbolicLink); - assert.ok(statAndIsLink!.isSymbolicLink); + assert.ok(statAndIsLink?.symbolicLink); + assert.ok(!statAndIsLink?.symbolicLink?.dangling); pfs.rimrafSync(directory); }); + test('stat link (non existing target)', async () => { + if (isWindows) { + return; // Symlinks are not the same on win, and we can not create them programitically without admin privileges + } + + const id1 = uuid.generateUuid(); + const parentDir = path.join(os.tmpdir(), 'vsctests', id1); + const directory = path.join(parentDir, 'pfs', id1); + + const id2 = uuid.generateUuid(); + const symbolicLink = path.join(parentDir, 'pfs', id2); + + await pfs.mkdirp(directory, 493); + + fs.symlinkSync(directory, symbolicLink); + + pfs.rimrafSync(directory); + + const statAndIsLink = await pfs.statLink(symbolicLink); + assert.ok(statAndIsLink?.symbolicLink); + assert.ok(statAndIsLink?.symbolicLink?.dangling); + }); + test('readdir', async () => { if (canNormalize && typeof process.versions['electron'] !== 'undefined' /* needs electron */) { const id = uuid.generateUuid(); @@ -420,17 +411,10 @@ suite('PFS', function () { return testWriteFileAndFlush(VSBuffer.fromString(smallData).buffer, smallData, VSBuffer.fromString(bigData).buffer, bigData); }); - test('writeFile (stream)', async () => { - const smallData = 'Hello World'; - const bigData = (new Array(100 * 1024)).join('Large String\n'); - - return testWriteFileAndFlush(toReadable(smallData), smallData, toReadable(bigData), bigData); - }); - async function testWriteFileAndFlush( - smallData: string | Buffer | NodeJS.ReadableStream | Uint8Array, + smallData: string | Buffer | Uint8Array, smallDataValue: string, - bigData: string | Buffer | NodeJS.ReadableStream | Uint8Array, + bigData: string | Buffer | Uint8Array, bigDataValue: string ): Promise { const id = uuid.generateUuid(); @@ -450,22 +434,6 @@ suite('PFS', function () { await pfs.rimraf(parentDir); } - test('writeFile (file stream)', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const sourceFile = getPathFromAmdModule(require, './fixtures/index.html'); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); - - await pfs.mkdirp(newDir, 493); - assert.ok(fs.existsSync(newDir)); - - await pfs.writeFile(testFile, fs.createReadStream(sourceFile)); - assert.equal(fs.readFileSync(testFile).toString(), fs.readFileSync(sourceFile).toString()); - - await pfs.rimraf(parentDir); - }); - test('writeFile (string, error handling)', async () => { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); @@ -490,118 +458,6 @@ suite('PFS', function () { await pfs.rimraf(parentDir); }); - test('writeFile (stream, error handling EISDIR)', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); - - await pfs.mkdirp(newDir, 493); - - assert.ok(fs.existsSync(newDir)); - - fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory! - - const readable = toReadable('Hello World'); - - let expectedError: Error | undefined; - try { - await pfs.writeFile(testFile, readable); - } catch (error) { - expectedError = error; - } - - if (!expectedError || (expectedError).code !== 'EISDIR') { - throw new Error('Expected EISDIR error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error')); - } - - // verify that the stream is still consumable (for https://github.com/Microsoft/vscode/issues/42542) - assert.equal(readable.read(), 'Hello World'); - - await pfs.rimraf(parentDir); - }); - - test('writeFile (stream, error handling READERROR)', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); - - await pfs.mkdirp(newDir, 493); - assert.ok(fs.existsSync(newDir)); - - let expectedError: Error | undefined; - try { - await pfs.writeFile(testFile, toReadable('Hello World', true /* throw error */)); - } catch (error) { - expectedError = error; - } - - if (!expectedError || expectedError.message !== readError) { - throw new Error('Expected error for writing to folder'); - } - - await pfs.rimraf(parentDir); - }); - - test('writeFile (stream, error handling EACCES)', async () => { - if (isLinux) { - return Promise.resolve(); // somehow this test fails on Linux in our TFS builds - } - - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); - - await pfs.mkdirp(newDir, 493); - - assert.ok(fs.existsSync(newDir)); - - fs.writeFileSync(testFile, ''); - fs.chmodSync(testFile, 33060); // make readonly - - let expectedError: Error | undefined; - try { - await pfs.writeFile(testFile, toReadable('Hello World')); - } catch (error) { - expectedError = error; - } - - if (!expectedError || !((expectedError).code !== 'EACCES' || (expectedError).code !== 'EPERM')) { - throw new Error('Expected EACCES/EPERM error for writing to folder but got: ' + (expectedError ? (expectedError).code : 'no error')); - } - - await pfs.rimraf(parentDir); - }); - - test('writeFile (file stream, error handling)', async () => { - const id = uuid.generateUuid(); - const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const sourceFile = getPathFromAmdModule(require, './fixtures/index.html'); - const newDir = path.join(parentDir, 'pfs', id); - const testFile = path.join(newDir, 'flushed.txt'); - - await pfs.mkdirp(newDir, 493); - - assert.ok(fs.existsSync(newDir)); - - fs.mkdirSync(testFile); // this will trigger an error because testFile is now a directory! - - let expectedError: Error | undefined; - try { - await pfs.writeFile(testFile, fs.createReadStream(sourceFile)); - } catch (error) { - expectedError = error; - } - - if (!expectedError) { - throw new Error('Expected error for writing to folder'); - } - - await pfs.rimraf(parentDir); - }); - test('writeFileSync', async () => { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index af6317d035..7355cabbb7 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -31,6 +31,7 @@ 'onigasm-umd': `${window.location.origin}/static/remote/web/node_modules/onigasm-umd/release/main`, 'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`, 'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, + 'xterm-addon-unicode11': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, 'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`, 'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`, diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index 8a6a9f54e6..db61683164 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -35,6 +35,7 @@ 'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`, 'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`, 'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, + 'xterm-addon-unicode11': `${window.location.origin}/static/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, 'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, 'xterm-addon-webgl': `${window.location.origin}/static/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`, 'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`, diff --git a/src/vs/code/electron-browser/issue/issueReporterPage.ts b/src/vs/code/electron-browser/issue/issueReporterPage.ts index d5f7045839..0366e8803f 100644 --- a/src/vs/code/electron-browser/issue/issueReporterPage.ts +++ b/src/vs/code/electron-browser/issue/issueReporterPage.ts @@ -35,7 +35,7 @@ export default (): string => ` diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 16fa69ac51..1e70210593 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -26,7 +26,6 @@ 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 { ActiveWindowManager } from 'vs/code/node/activeWindowTracker'; import { ipcRenderer } from 'electron'; import { ILogService, LogLevel, ILoggerService } from 'vs/platform/log/common/log'; import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; @@ -59,7 +58,7 @@ import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; -import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync'; +import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService'; @@ -131,9 +130,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat 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); services.set(IFileService, fileService); @@ -184,8 +180,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService)); services.set(IUserDataAuthTokenService, new SyncDescriptor(UserDataAuthTokenService)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); - services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter))); - services.set(IGlobalExtensionEnablementService, new GlobalExtensionEnablementServiceClient(server.getChannel('globalExtensionEnablement', activeWindowRouter))); + services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); + services.set(IGlobalExtensionEnablementService, new GlobalExtensionEnablementServiceClient(server.getChannel('globalExtensionEnablement', client => client.ctx !== 'main'))); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); @@ -219,7 +215,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); server.registerChannel('userDataSync', userDataSyncChannel); - const userDataAutoSync = instantiationService2.createInstance(UserDataAutoSync); + const userDataAutoSync = instantiationService2.createInstance(UserDataAutoSyncService); const userDataAutoSyncChannel = new UserDataAutoSyncChannel(userDataAutoSync); server.registerChannel('userDataAutoSync', userDataAutoSyncChannel); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c715f4d517..e149132b4f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -171,7 +171,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (_event: Event, contents) => { contents.on('will-attach-webview', (event: Event, webPreferences, params) => { - const isValidWebviewSource = (source: string): boolean => { + const isValidWebviewSource = (source: string | undefined): boolean => { if (!source) { return false; } @@ -191,11 +191,12 @@ export class CodeApplication extends Disposable { webPreferences.nodeIntegration = false; // Verify URLs being loaded - if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) { + // https://github.com/electron/electron/issues/21553 + if (isValidWebviewSource(params.src) && isValidWebviewSource((webPreferences as { preloadURL: string }).preloadURL)) { return; } - delete webPreferences.preloadUrl; + delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553 // Otherwise prevent loading this.logService.error('webContents#web-contents-created: Prevented webview attach'); @@ -497,27 +498,27 @@ export class CodeApplication extends Disposable { this.logService.info(`Tracing: waiting for windows to get ready...`); let recordingStopped = false; - const stopRecording = (timeout: boolean) => { + const stopRecording = async (timeout: boolean) => { if (recordingStopped) { return; } recordingStopped = true; // only once - contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { - if (!timeout) { - if (this.dialogMainService) { - this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); - } - } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + const path = await contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`)); + + if (!timeout) { + if (this.dialogMainService) { + this.dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "Ok")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } - }); + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + } }; // Wait up to 30s before creating the trace anyways @@ -594,8 +595,6 @@ export class CodeApplication extends Disposable { // Catch file URLs if (uri.authority === Schemas.file && !!uri.path) { const cli = assign(Object.create(null), environmentService.args); - - // hey Ben, we need to convert this `code://file` URI into a `file://` URI const urisToOpen = [{ fileUri: URI.file(uri.fsPath) }]; windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index b8929ca792..be27392a3f 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -347,9 +347,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => { - const responseHeaders = details.responseHeaders as { [key: string]: string[] }; + const responseHeaders = details.responseHeaders as Record; - const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']); + const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { return callback({ cancel: true }); } @@ -441,7 +441,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => - this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } }))); + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as Record }))); } private onWindowError(error: WindowError): void { @@ -648,7 +648,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (windowConfig?.autoDetectHighContrast === false) { autoDetectHighContrast = false; } - windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme(); + windowConfiguration.highContrast = isWindows && autoDetectHighContrast && nativeTheme.shouldUseInvertedColorScheme; windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; // Title style related @@ -1007,22 +1007,22 @@ export class CodeWindow extends Disposable implements ICodeWindow { switch (visibility) { case ('default'): this._win.setMenuBarVisibility(!isFullscreen); - this._win.setAutoHideMenuBar(isFullscreen); + this._win.autoHideMenuBar = isFullscreen; break; case ('visible'): this._win.setMenuBarVisibility(true); - this._win.setAutoHideMenuBar(false); + this._win.autoHideMenuBar = false; break; case ('toggle'): this._win.setMenuBarVisibility(false); - this._win.setAutoHideMenuBar(true); + this._win.autoHideMenuBar = true; break; case ('hidden'): this._win.setMenuBarVisibility(false); - this._win.setAutoHideMenuBar(false); + this._win.autoHideMenuBar = false; break; } } diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index f26929fe21..d285162fd4 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -3,24 +3,27 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IDimension } from 'vs/editor/common/editorCommon'; +import * as dom from 'vs/base/browser/dom'; export class ElementSizeObserver extends Disposable { private readonly referenceDomElement: HTMLElement | null; - private measureReferenceDomElementToken: any; private readonly changeCallback: () => void; private width: number; private height: number; + private mutationObserver: MutationObserver | null; + private windowSizeListener: IDisposable | null; constructor(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void) { super(); this.referenceDomElement = referenceDomElement; this.changeCallback = changeCallback; - this.measureReferenceDomElementToken = -1; this.width = -1; this.height = -1; + this.mutationObserver = null; + this.windowSizeListener = null; this.measureReferenceDomElement(false, dimension); } @@ -38,15 +41,25 @@ export class ElementSizeObserver extends Disposable { } public startObserving(): void { - if (this.measureReferenceDomElementToken === -1) { - this.measureReferenceDomElementToken = setInterval(() => this.measureReferenceDomElement(true), 100); + if (!this.mutationObserver && this.referenceDomElement) { + this.mutationObserver = new MutationObserver(() => this._onDidMutate()); + this.mutationObserver.observe(this.referenceDomElement, { + attributes: true, + }); + } + if (!this.windowSizeListener) { + this.windowSizeListener = dom.addDisposableListener(window, 'resize', () => this._onDidResizeWindow()); } } public stopObserving(): void { - if (this.measureReferenceDomElementToken !== -1) { - clearInterval(this.measureReferenceDomElementToken); - this.measureReferenceDomElementToken = -1; + if (this.mutationObserver) { + this.mutationObserver.disconnect(); + this.mutationObserver = null; + } + if (this.windowSizeListener) { + this.windowSizeListener.dispose(); + this.windowSizeListener = null; } } @@ -54,6 +67,14 @@ export class ElementSizeObserver extends Disposable { this.measureReferenceDomElement(true, dimension); } + private _onDidMutate(): void { + this.measureReferenceDomElement(true); + } + + private _onDidResizeWindow(): void { + this.measureReferenceDomElement(true); + } + private measureReferenceDomElement(callChangeCallback: boolean, dimension?: IDimension): void { let observedWidth = 0; let observedHeight = 0; diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 41d5b451ae..6eddab8779 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -1753,12 +1753,17 @@ registerCommand(new EditorOrNativeTextInputCommand({ kbExpr: null, primary: KeyMod.CtrlCmd | KeyCode.KEY_A }, - menuOpts: { + menuOpts: [{ menuId: MenuId.MenubarEditMenu, // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu group: '4_find_global', // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), order: 1 - } + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('selectAll', "Select All"), + order: 1 + }] })); registerCommand(new EditorOrNativeTextInputCommand({ @@ -1771,12 +1776,17 @@ registerCommand(new EditorOrNativeTextInputCommand({ kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_Z }, - menuOpts: { + menuOpts: [{ menuId: MenuId.MenubarEditMenu, group: '1_do', title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), order: 1 - } + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('undo', "Undo"), + order: 1 + }] })); registerCommand(new EditorHandlerCommand('default:' + Handler.Undo, Handler.Undo)); @@ -1792,12 +1802,17 @@ registerCommand(new EditorOrNativeTextInputCommand({ secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z], mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z } }, - menuOpts: { + menuOpts: [{ menuId: MenuId.MenubarEditMenu, group: '1_do', title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), order: 2 - } + }, { + menuId: MenuId.CommandPalette, + group: '', + title: nls.localize('redo', "Redo"), + order: 1 + }] })); registerCommand(new EditorHandlerCommand('default:' + Handler.Redo, Handler.Redo)); diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 94cacfb550..c17496f940 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -357,8 +357,9 @@ export class TextAreaHandler extends ViewPart { this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) { - // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 1000 for a better experience - this._accessibilityPageSize = 1000; + // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 160 for a better experience + // If we put more than 160 lines the nvda can not handle this https://github.com/microsoft/vscode/issues/89717 + this._accessibilityPageSize = 160; } else { this._accessibilityPageSize = accessibilityPageSize; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index e3f98ab2e0..00d6b566ee 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -331,7 +331,7 @@ export class TextAreaInput extends Disposable { this._onType.fire(typeInput); } } else { - if (typeInput.text !== '') { + if (typeInput.text !== '' || typeInput.replaceCharCnt !== 0) { this._firePaste(typeInput.text, null); } this._nextCommand = ReadFromTextArea.Type; @@ -483,6 +483,9 @@ export class TextAreaInput extends Disposable { // Setting this._hasFocus and writing the screen reader content // will result in a focus() and setSelectionRange() in the textarea this._setHasFocus(true); + + // If the editor is off DOM, focus cannot be really set, so let's double check that we have managed to set the focus + this.refreshFocusState(); } public isFocused(): boolean { @@ -490,8 +493,11 @@ export class TextAreaInput extends Disposable { } public refreshFocusState(): void { - if (document.body.contains(this.textArea.domNode) && document.activeElement === this.textArea.domNode) { - this._setHasFocus(true); + const shadowRoot = dom.getShadowRoot(this.textArea.domNode); + if (shadowRoot) { + this._setHasFocus(shadowRoot.activeElement === this.textArea.domNode); + } else if (dom.isInDOM(this.textArea.domNode)) { + this._setHasFocus(document.activeElement === this.textArea.domNode); } else { this._setHasFocus(false); } @@ -696,7 +702,15 @@ class TextAreaWrapper extends Disposable implements ITextAreaWrapper { public setSelectionRange(reason: string, selectionStart: number, selectionEnd: number): void { const textArea = this._actual.domNode; - const currentIsFocused = (document.activeElement === textArea); + let activeElement: Element | null = null; + const shadowRoot = dom.getShadowRoot(textArea); + if (shadowRoot) { + activeElement = shadowRoot.activeElement; + } else { + activeElement = document.activeElement; + } + + const currentIsFocused = (activeElement === textArea); const currentSelectionStart = textArea.selectionStart; const currentSelectionEnd = textArea.selectionEnd; diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index a0edab2a19..e7dc08a8f1 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -27,6 +27,8 @@ export type IBulkEditPreviewHandler = (edit: WorkspaceEdit, options?: IBulkEditO export interface IBulkEditService { _serviceBrand: undefined; + hasPreviewHandler(): boolean; + setPreviewHandler(handler: IBulkEditPreviewHandler): IDisposable; apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise; diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index d0d182a269..2fb294eb06 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -111,6 +111,7 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe containerDomNode.style.position = 'absolute'; containerDomNode.style.top = '10000'; + containerDomNode.style.wordWrap = 'break-word'; document.body.appendChild(containerDomNode); let range = document.createRange(); diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index ae516cf568..c76e4fee40 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -316,8 +316,9 @@ class Widget { } private _layoutHorizontalSegmentInPage(windowSize: dom.Dimension, domNodePosition: dom.IDomNodePagePosition, left: number, width: number): [number, number] { - const MIN_LIMIT = (width <= domNodePosition.width - 20 ? domNodePosition.left : 0); - const MAX_LIMIT = (width <= domNodePosition.width - 20 ? domNodePosition.left + domNodePosition.width - 20 : windowSize.width - 20); + // Initially, the limits are defined as the dom node limits + const MIN_LIMIT = Math.max(0, domNodePosition.left - width); + const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width); let absoluteLeft = domNodePosition.left + left - dom.StandardWindow.scrollX; diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index 9480b73578..356d93a11a 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -34,6 +34,7 @@ export class EditorScrollbar extends ViewPart { const scrollbar = options.get(EditorOption.scrollbar); const mouseWheelScrollSensitivity = options.get(EditorOption.mouseWheelScrollSensitivity); const fastScrollSensitivity = options.get(EditorOption.fastScrollSensitivity); + const scrollPredominantAxis = options.get(EditorOption.scrollPredominantAxis); const scrollbarOptions: ScrollableElementCreationOptions = { listenOnDomNode: viewDomNode.domNode, @@ -54,6 +55,7 @@ export class EditorScrollbar extends ViewPart { arrowSize: scrollbar.arrowSize, mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, fastScrollSensitivity: fastScrollSensitivity, + scrollPredominantAxis: scrollPredominantAxis, }; this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.getScrollable())); @@ -140,10 +142,12 @@ export class EditorScrollbar extends ViewPart { const scrollbar = options.get(EditorOption.scrollbar); const mouseWheelScrollSensitivity = options.get(EditorOption.mouseWheelScrollSensitivity); const fastScrollSensitivity = options.get(EditorOption.fastScrollSensitivity); + const scrollPredominantAxis = options.get(EditorOption.scrollPredominantAxis); const newOpts: ScrollableElementChangeOptions = { handleMouseWheel: scrollbar.handleMouseWheel, mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, - fastScrollSensitivity: fastScrollSensitivity + fastScrollSensitivity: fastScrollSensitivity, + scrollPredominantAxis: scrollPredominantAxis }; this.scrollbar.updateOptions(newOpts); } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css index 680cccb1fa..54091d8645 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.css @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor .margin-view-overlays .line-numbers { + font-variant-numeric: tabular-nums; position: absolute; text-align: right; display: inline-block; diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.css b/src/vs/editor/browser/viewParts/lines/viewLines.css index 46c57aa0fa..94577d7651 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.css +++ b/src/vs/editor/browser/viewParts/lines/viewLines.css @@ -37,6 +37,10 @@ width: 100%; } +.monaco-editor .mtkz { + display: inline-block; +} + /* TODO@tokenization bootstrap fix */ /*.monaco-editor .view-line > span > span { float: none; diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 664e0d2575..e98e03ca61 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -589,6 +589,14 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, if (boxEndY - boxStartY > viewportHeight) { // the box is larger than the viewport ... scroll to its top newScrollTop = boxStartY; + } else if (verticalType === viewEvents.VerticalRevealType.NearTop) { + // We want a gap that is 20% of the viewport, but with a minimum of 5 lines + const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2); + // Try to scroll just above the box with the desired gap + const desiredScrollTop = boxStartY - desiredGapAbove; + // But ensure that the box is not pushed out of viewport + const minScrollTop = boxEndY - viewportHeight; + newScrollTop = Math.max(minScrollTop, desiredScrollTop); } else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) { if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { // Box is already in the viewport... do nothing diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index fbaf98ef90..bf3cea82f9 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -25,8 +25,8 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; -import { scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapSelection } from 'vs/platform/theme/common/colorRegistry'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { minimapSelection, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, minimapBackground } from 'vs/platform/theme/common/colorRegistry'; +import { registerThemingParticipant, ITheme } from 'vs/platform/theme/common/themeService'; import { ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel'; import { Selection } from 'vs/editor/common/core/selection'; import { Color } from 'vs/base/common/color'; @@ -107,7 +107,9 @@ class MinimapOptions { */ public readonly canvasOuterHeight: number; - constructor(configuration: IConfiguration) { + public readonly backgroundColor: RGBA8; + + constructor(configuration: IConfiguration, theme: ITheme, tokensColorTracker: MinimapTokensColorTracker) { const options = configuration.options; const pixelRatio = options.get(EditorOption.pixelRatio); const layoutInfo = options.get(EditorOption.layoutInfo); @@ -131,6 +133,16 @@ class MinimapOptions { this.canvasOuterWidth = this.canvasInnerWidth / pixelRatio; this.canvasOuterHeight = this.canvasInnerHeight / pixelRatio; + + this.backgroundColor = MinimapOptions._getMinimapBackground(theme, tokensColorTracker); + } + + private static _getMinimapBackground(theme: ITheme, tokensColorTracker: MinimapTokensColorTracker): RGBA8 { + const themeColor = theme.getColor(minimapBackground); + if (themeColor) { + return new RGBA8(themeColor.rgba.r, themeColor.rgba.g, themeColor.rgba.b, themeColor.rgba.a); + } + return tokensColorTracker.getColor(ColorId.DefaultBackground); } public equals(other: MinimapOptions): boolean { @@ -148,6 +160,7 @@ class MinimapOptions { && this.canvasInnerHeight === other.canvasInnerHeight && this.canvasOuterWidth === other.canvasOuterWidth && this.canvasOuterHeight === other.canvasOuterHeight + && this.backgroundColor.equals(other.backgroundColor) ); } } @@ -447,13 +460,13 @@ class MinimapBuffers { export class Minimap extends ViewPart { + private readonly _tokensColorTracker: MinimapTokensColorTracker; private readonly _domNode: FastDomNode; private readonly _shadow: FastDomNode; private readonly _canvas: FastDomNode; private readonly _decorationsCanvas: FastDomNode; private readonly _slider: FastDomNode; private readonly _sliderHorizontal: FastDomNode; - private readonly _tokensColorTracker: MinimapTokensColorTracker; private readonly _mouseDownListener: IDisposable; private readonly _sliderMouseMoveMonitor: GlobalMouseMoveMonitor; private readonly _sliderMouseDownListener: IDisposable; @@ -473,7 +486,8 @@ export class Minimap extends ViewPart { constructor(context: ViewContext) { super(context); - this._options = new MinimapOptions(this._context.configuration); + this._tokensColorTracker = MinimapTokensColorTracker.getInstance(); + this._options = new MinimapOptions(this._context.configuration, this._context.theme, this._tokensColorTracker); this._lastRenderData = null; this._buffers = null; this._selectionColor = this._context.theme.getColor(minimapSelection); @@ -512,8 +526,6 @@ export class Minimap extends ViewPart { this._sliderHorizontal.setClassName('minimap-slider-horizontal'); this._slider.appendChild(this._sliderHorizontal); - this._tokensColorTracker = MinimapTokensColorTracker.getInstance(); - this._applyLayout(); this._mouseDownListener = dom.addStandardDisposableListener(this._domNode.domNode, 'mousedown', (e) => { @@ -664,7 +676,7 @@ export class Minimap extends ViewPart { this._canvas.domNode.getContext('2d')!, this._options.canvasInnerWidth, this._options.canvasInnerHeight, - this._tokensColorTracker.getColor(ColorId.DefaultBackground) + this._options.backgroundColor ); } } @@ -672,7 +684,7 @@ export class Minimap extends ViewPart { } private _onOptionsMaybeChanged(): boolean { - const opts = new MinimapOptions(this._context.configuration); + const opts = new MinimapOptions(this._context.configuration, this._context.theme, this._tokensColorTracker); if (this._options.equals(opts)) { return false; } @@ -745,6 +757,7 @@ export class Minimap extends ViewPart { this._context.model.invalidateMinimapColorCache(); this._selectionColor = this._context.theme.getColor(minimapSelection); this._renderDecorations = true; + this._onOptionsMaybeChanged(); return true; } @@ -856,7 +869,7 @@ export class Minimap extends ViewPart { const y = (lineNumber - layout.startLineNumber) * lineHeight; // Skip rendering the line if it's vertically outside our viewport - if (y + height < 0 || y > this._options.canvasOuterHeight) { + if (y + height < 0 || y > this._options.canvasInnerHeight) { return; } @@ -942,7 +955,7 @@ export class Minimap extends ViewPart { // Fetch rendering info from view model for rest of lines that need rendering. const lineInfo = this._context.model.getMinimapLinesRenderingData(startLineNumber, endLineNumber, needed); const tabSize = lineInfo.tabSize; - const background = this._tokensColorTracker.getColor(ColorId.DefaultBackground); + const background = this._options.backgroundColor; const useLighterFont = this._tokensColorTracker.backgroundIsLight(); // Render the rest of lines @@ -1141,6 +1154,10 @@ export class Minimap extends ViewPart { } registerThemingParticipant((theme, collector) => { + const minimapBackgroundValue = theme.getColor(minimapBackground); + if (minimapBackgroundValue) { + collector.addRule(`.monaco-editor .minimap > canvas { opacity: ${minimapBackgroundValue.rgba.a}; will-change: opacity; }`); + } const sliderBackground = theme.getColor(scrollbarSliderBackground); if (sliderBackground) { const halfSliderBackground = sliderBackground.transparent(0.5); diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts index f4f5144e27..9b44e5a7f0 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRenderer.ts @@ -5,6 +5,7 @@ import { RGBA8 } from 'vs/editor/common/core/rgba'; import { Constants, getCharIndex } from './minimapCharSheet'; +import { toUint8 } from 'vs/base/common/uint'; export class MinimapCharRenderer { _minimapCharRendererBrand: void; @@ -20,7 +21,7 @@ export class MinimapCharRenderer { private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray { let result = new Uint8ClampedArray(input.length); for (let i = 0, len = input.length; i < len; i++) { - result[i] = input[i] * ratio; + result[i] = toUint8(input[i] * ratio); } return result; } diff --git a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts index 300d51789f..09aa705489 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimapCharRendererFactory.ts @@ -7,6 +7,7 @@ import { MinimapCharRenderer } from 'vs/editor/browser/viewParts/minimap/minimap import { allCharCodes } from 'vs/editor/browser/viewParts/minimap/minimapCharSheet'; import { prebakedMiniMaps } from 'vs/editor/browser/viewParts/minimap/minimapPreBaked'; import { Constants } from './minimapCharSheet'; +import { toUint8 } from 'vs/base/common/uint'; /** * Creates character renderers. It takes a 'scale' that determines how large @@ -135,7 +136,7 @@ export class MinimapCharRendererFactory { const final = value / samples; brightest = Math.max(brightest, final); - dest[targetIndex++] = final; + dest[targetIndex++] = toUint8(final); } } diff --git a/src/vs/editor/browser/viewParts/rulers/rulers.ts b/src/vs/editor/browser/viewParts/rulers/rulers.ts index 08fe9ec92c..faae9d3ad3 100644 --- a/src/vs/editor/browser/viewParts/rulers/rulers.ts +++ b/src/vs/editor/browser/viewParts/rulers/rulers.ts @@ -11,13 +11,13 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, IRulerOption } from 'vs/editor/common/config/editorOptions'; export class Rulers extends ViewPart { public domNode: FastDomNode; private readonly _renderedRulers: FastDomNode[]; - private _rulers: number[]; + private _rulers: IRulerOption[]; private _typicalHalfwidthCharacterWidth: number; constructor(context: ViewContext) { @@ -92,9 +92,11 @@ export class Rulers extends ViewPart { for (let i = 0, len = this._rulers.length; i < len; i++) { const node = this._renderedRulers[i]; + const ruler = this._rulers[i]; + node.setBoxShadow(ruler.color ? `1px 0 0 0 ${ruler.color} inset` : ``); node.setHeight(Math.min(ctx.scrollHeight, 1000000)); - node.setLeft(this._rulers[i] * this._typicalHalfwidthCharacterWidth); + node.setLeft(ruler.column * this._typicalHalfwidthCharacterWidth); } } } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index ff0586eb8e..b816a125ac 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -557,6 +557,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._revealLine(lineNumber, VerticalRevealType.CenterIfOutsideViewport, scrollType); } + public revealLineNearTop(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealLine(lineNumber, VerticalRevealType.NearTop, scrollType); + } + private _revealLine(lineNumber: number, revealType: VerticalRevealType, scrollType: editorCommon.ScrollType): void { if (typeof lineNumber !== 'number') { throw new Error('Invalid arguments'); @@ -597,6 +601,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealPositionNearTop(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealPosition( + position, + VerticalRevealType.NearTop, + true, + scrollType + ); + } + private _revealPosition(position: IPosition, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { if (!Position.isIPosition(position)) { throw new Error('Invalid arguments'); @@ -685,6 +698,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealLinesNearTop(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealLines( + startLineNumber, + endLineNumber, + VerticalRevealType.NearTop, + scrollType + ); + } + private _revealLines(startLineNumber: number, endLineNumber: number, verticalType: VerticalRevealType, scrollType: editorCommon.ScrollType): void { if (typeof startLineNumber !== 'number' || typeof endLineNumber !== 'number') { throw new Error('Invalid arguments'); @@ -725,6 +747,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealRangeNearTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealRange( + range, + VerticalRevealType.NearTop, + true, + scrollType + ); + } + public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this._revealRange( range, @@ -1613,6 +1644,7 @@ export class BooleanEventEmitter extends Disposable { class EditorContextKeysManager extends Disposable { private readonly _editor: CodeEditorWidget; + private readonly _editorSimpleInput: IContextKey; private readonly _editorFocus: IContextKey; private readonly _textInputFocus: IContextKey; private readonly _editorTextFocus: IContextKey; @@ -1632,6 +1664,8 @@ class EditorContextKeysManager extends Disposable { this._editor = editor; contextKeyService.createKey('editorId', editor.getId()); + + this._editorSimpleInput = EditorContextKeys.editorSimpleInput.bindTo(contextKeyService); this._editorFocus = EditorContextKeys.focus.bindTo(contextKeyService); this._textInputFocus = EditorContextKeys.textInputFocus.bindTo(contextKeyService); this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService); @@ -1655,6 +1689,8 @@ class EditorContextKeysManager extends Disposable { this._updateFromSelection(); this._updateFromFocus(); this._updateFromModel(); + + this._editorSimpleInput.set(this._editor.isSimpleWidget); } private _updateFromConfig(): void { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 42306aa635..c2e3889c3d 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -38,7 +38,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; +import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, getThemeTypeSelector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { reverseLineChanges } from 'sql/editor/browser/diffEditorHelper'; // {{SQL CARBON EDIT}} import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -768,6 +768,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealLineInCenterIfOutsideViewport(lineNumber, scrollType); } + public revealLineNearTop(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealLineNearTop(lineNumber, scrollType); + } + public revealPosition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this.modifiedEditor.revealPosition(position, scrollType); } @@ -780,6 +784,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealPositionInCenterIfOutsideViewport(position, scrollType); } + public revealPositionNearTop(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealPositionNearTop(position, scrollType); + } + public getSelection(): Selection | null { return this.modifiedEditor.getSelection(); } @@ -812,6 +820,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealLinesInCenterIfOutsideViewport(startLineNumber, endLineNumber, scrollType); } + public revealLinesNearTop(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealLinesNearTop(startLineNumber, endLineNumber, scrollType); + } + public revealRange(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth, revealVerticalInCenter: boolean = false, revealHorizontal: boolean = true): void { this.modifiedEditor.revealRange(range, scrollType, revealVerticalInCenter, revealHorizontal); } @@ -824,6 +836,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealRangeInCenterIfOutsideViewport(range, scrollType); } + public revealRangeNearTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealRangeNearTop(range, scrollType); + } + public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this.modifiedEditor.revealRangeAtTop(range, scrollType); } @@ -2227,4 +2243,32 @@ registerThemingParticipant((theme, collector) => { if (border) { collector.addRule(`.monaco-diff-editor.side-by-side .editor.modified { border-left: 1px solid ${border}; }`); } + + const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); + if (scrollbarSliderBackgroundColor) { + collector.addRule(` + .monaco-diff-editor .diffViewport { + background: ${scrollbarSliderBackgroundColor}; + } + `); + } + + const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); + if (scrollbarSliderHoverBackgroundColor) { + collector.addRule(` + .monaco-diff-editor .diffViewport:hover { + background: ${scrollbarSliderHoverBackgroundColor}; + } + `); + } + + const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); + if (scrollbarSliderActiveBackgroundColor) { + collector.addRule(` + .monaco-diff-editor .diffViewport:active { + background: ${scrollbarSliderActiveBackgroundColor}; + } + `); + } + }); diff --git a/src/vs/editor/browser/widget/media/diffEditor.css b/src/vs/editor/browser/widget/media/diffEditor.css index 7765a27a16..c4a81bfd50 100644 --- a/src/vs/editor/browser/widget/media/diffEditor.css +++ b/src/vs/editor/browser/widget/media/diffEditor.css @@ -8,19 +8,14 @@ z-index: 9; } +.monaco-diff-editor .diffOverview .diffViewport { + z-index: 10; +} + /* colors not externalized: using transparancy on background */ .monaco-diff-editor.vs .diffOverview { background: rgba(0, 0, 0, 0.03); } .monaco-diff-editor.vs-dark .diffOverview { background: rgba(255, 255, 255, 0.01); } -.monaco-diff-editor .diffViewport { - box-shadow: inset 0px 0px 1px 0px #B9B9B9; - background: rgba(0, 0, 0, 0.10); -} - -.monaco-diff-editor.vs-dark .diffViewport, -.monaco-diff-editor.hc-black .diffViewport { - background: rgba(255, 255, 255, 0.10); -} .monaco-scrollable-element.modified-in-monaco-diff-editor.vs .scrollbar { background: rgba(0,0,0,0); } .monaco-scrollable-element.modified-in-monaco-diff-editor.vs-dark .scrollbar { background: rgba(0,0,0,0); } .monaco-scrollable-element.modified-in-monaco-diff-editor.hc-black .scrollbar { background: none; } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 9512311777..c56cacffb4 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -484,6 +484,11 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('wordBasedSuggestions', "Controls whether completions should be computed based on words in the document.") }, + 'editor.semanticHighlighting.enabled': { + type: 'boolean', + default: true, + description: nls.localize('semanticHighlighting.enabled', "Controls whether the semanticHighlighting is shown for the languages that support it.") + }, 'editor.stablePeek': { type: 'boolean', default: false, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index f4961be23b..8d2897733b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -58,7 +58,7 @@ export interface IEditorOptions { * Render vertical lines at the specified columns. * Defaults to empty array. */ - rulers?: number[]; + rulers?: (number | IRulerOption)[]; /** * A string containing the word separators used when doing word navigation. * Defaults to `~!@#$%^&*()-=+[{]}\\|;:\'",.<>/? @@ -74,7 +74,7 @@ export interface IEditorOptions { * If it is a function, it will be invoked when rendering a line number and the return value will be rendered. * Otherwise, if it is a truey, line numbers will be rendered normally (equivalent of using an identity function). * Otherwise, line numbers will not be rendered. - * Defaults to true. + * Defaults to `on`. */ lineNumbers?: LineNumbersType; /** @@ -267,10 +267,10 @@ export interface IEditorOptions { */ wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent'; /** - * Controls the wrapping algorithm to use. - * Defaults to 'monospace'. + * Controls the wrapping strategy to use. + * Defaults to 'simple'. */ - wrappingAlgorithm?: 'monospace' | 'dom'; + wrappingStrategy?: 'simple' | 'advanced'; /** * Configure word wrapping characters. A break will be introduced before these characters. * Defaults to '([{‘“〈《「『【〔([{「£¥$£¥++'. @@ -319,6 +319,11 @@ export interface IEditorOptions { * Defaults to 5. */ fastScrollSensitivity?: number; + /** + * Enable that the editor scrolls only the predominant axis. Prevents horizontal drift when scrolling vertically on a trackpad. + * Defaults to true. + */ + scrollPredominantAxis?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -385,8 +390,8 @@ export interface IEditorOptions { */ autoSurround?: EditorAutoSurroundStrategy; /** - * Enable auto indentation adjustment. - * Defaults to false. + * Controls whether the editor should automatically adjust the indentation when users type, paste, move or indent lines. + * Defaults to advanced. */ autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'; /** @@ -555,6 +560,11 @@ export interface IEditorOptions { * Defaults to false. */ peekWidgetDefaultFocus?: 'tree' | 'editor'; + /** + * Controls whether the definition link opens element in the peek widget. + * Defaults to false. + */ + definitionLinkOpensInPeek?: boolean; } export interface IEditorConstructionOptions extends IEditorOptions { @@ -625,9 +635,6 @@ export class ConfigurationChangedEvent { constructor(values: boolean[]) { this._values = values; } - /** - * @internal - */ public hasChanged(id: EditorOption): boolean { return this._values[id]; } @@ -1592,55 +1599,6 @@ class EditorHover extends BaseEditorOption>; - -class EditorSemanticHighlighting extends BaseEditorOption { - - constructor() { - const defaults: EditorSemanticHighlightingOptions = { - enabled: true - }; - super( - EditorOption.semanticHighlighting, 'semanticHighlighting', defaults, - { - 'editor.semanticHighlighting.enabled': { - type: 'boolean', - default: defaults.enabled, - description: nls.localize('semanticHighlighting.enabled', "Controls whether the semanticHighlighting is shown for the languages that support it.") - } - } - ); - } - - public validate(_input: any): EditorSemanticHighlightingOptions { - if (typeof _input !== 'object') { - return this.defaultValue; - } - const input = _input as IEditorSemanticHighlightingOptions; - return { - enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled) - }; - } -} - -//#endregion - //#region layoutInfo /** @@ -2362,16 +2320,37 @@ export function filterValidationDecorations(options: IComputedEditorOptions): bo //#region rulers -class EditorRulers extends SimpleEditorOption { +export interface IRulerOption { + readonly column: number; + readonly color: string | null; +} + +class EditorRulers extends BaseEditorOption { constructor() { - const defaults: number[] = []; + const defaults: IRulerOption[] = []; + const columnSchema: IJSONSchema = { type: 'number', description: nls.localize('rulers.size', "Number of monospace characters at which this editor ruler will render.") }; super( EditorOption.rulers, 'rulers', defaults, { type: 'array', items: { - type: 'number' + anyOf: [ + columnSchema, + { + type: [ + 'object' + ], + properties: { + column: columnSchema, + color: { + type: 'string', + description: nls.localize('rulers.color', "Color of this editor ruler."), + format: 'color-hex' + } + } + } + ] }, default: defaults, description: nls.localize('rulers', "Render vertical rulers after a certain number of monospace characters. Use multiple values for multiple rulers. No rulers are drawn if array is empty.") @@ -2379,13 +2358,24 @@ class EditorRulers extends SimpleEditorOption { ); } - public validate(input: any): number[] { + public validate(input: any): IRulerOption[] { if (Array.isArray(input)) { - let rulers: number[] = []; - for (let value of input) { - rulers.push(EditorIntOption.clampedInt(value, 0, 0, 10000)); + let rulers: IRulerOption[] = []; + for (let _element of input) { + if (typeof _element === 'number') { + rulers.push({ + column: EditorIntOption.clampedInt(_element, 0, 0, 10000), + color: null + }); + } else if (typeof _element === 'object') { + const element = _element as IRulerOption; + rulers.push({ + column: EditorIntOption.clampedInt(element.column, 0, 0, 10000), + color: element.color + }); + } } - rulers.sort((a, b) => a - b); + rulers.sort((a, b) => a.column - b.column); return rulers; } return this.defaultValue; @@ -2674,6 +2664,10 @@ export interface ISuggestOptions { * Show snippet-suggestions. */ showSnippets?: boolean; + /** + * Controls the visibility of the status bar at the bottom of the suggest widget. + */ + hideStatusBar?: boolean; } export type InternalSuggestOptions = Readonly>; @@ -2683,7 +2677,7 @@ class EditorSuggest extends BaseEditorOption('editorSimpleInput', false); /** * A context key that is set when the editor's text has focus (cursor is blinking). */ diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index d4003b047b..dc970cea98 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -290,6 +290,7 @@ export const enum EndOfLineSequence { /** * An identifier for a single edit operation. + * @internal */ export interface ISingleEditOperationIdentifier { /** diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 4c1fa536a2..9815baa5a3 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -670,7 +670,7 @@ export class PieceTreeBase { if (searcher._wordSeparators) { searchText = buffer.buffer.substring(start, end); offsetInBuffer = (offset: number) => offset + start; - searcher.reset(-1); + searcher.reset(0); } else { searchText = buffer.buffer; offsetInBuffer = (offset: number) => offset; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index d65b4a0058..dd438c146e 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2640,14 +2640,16 @@ export class TextModel extends Disposable implements model.ITextModel { let goDown = true; let indent = 0; + let initialIndent = 0; + for (let distance = 0; goUp || goDown; distance++) { const upLineNumber = lineNumber - distance; const downLineNumber = lineNumber + distance; - if (distance !== 0 && (upLineNumber < 1 || upLineNumber < minLineNumber)) { + if (distance > 1 && (upLineNumber < 1 || upLineNumber < minLineNumber)) { goUp = false; } - if (distance !== 0 && (downLineNumber > lineCount || downLineNumber > maxLineNumber)) { + if (distance > 1 && (downLineNumber > lineCount || downLineNumber > maxLineNumber)) { goDown = false; } if (distance > 50000) { @@ -2656,10 +2658,9 @@ export class TextModel extends Disposable implements model.ITextModel { goDown = false; } + let upLineIndentLevel: number = -1; if (goUp) { // compute indent level going up - let upLineIndentLevel: number; - const currentIndent = this._computeIndentLevel(upLineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) @@ -2671,30 +2672,11 @@ export class TextModel extends Disposable implements model.ITextModel { up_resolveIndents(upLineNumber); upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent); } - - if (distance === 0) { - // This is the initial line number - startLineNumber = upLineNumber; - endLineNumber = downLineNumber; - indent = upLineIndentLevel; - if (indent === 0) { - // No need to continue - return { startLineNumber, endLineNumber, indent }; - } - continue; - } - - if (upLineIndentLevel >= indent) { - startLineNumber = upLineNumber; - } else { - goUp = false; - } } + let downLineIndentLevel = -1; if (goDown) { // compute indent level going down - let downLineIndentLevel: number; - const currentIndent = this._computeIndentLevel(downLineNumber - 1); if (currentIndent >= 0) { // This line has content (besides whitespace) @@ -2706,7 +2688,50 @@ export class TextModel extends Disposable implements model.ITextModel { down_resolveIndents(downLineNumber); downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent); } + } + if (distance === 0) { + initialIndent = upLineIndentLevel; + continue; + } + + if (distance === 1) { + if (downLineNumber <= lineCount && downLineIndentLevel >= 0 && initialIndent + 1 === downLineIndentLevel) { + // This is the beginning of a scope, we have special handling here, since we want the + // child scope indent to be active, not the parent scope + goUp = false; + startLineNumber = downLineNumber; + endLineNumber = downLineNumber; + indent = downLineIndentLevel; + continue; + } + + if (upLineNumber >= 1 && upLineIndentLevel >= 0 && upLineIndentLevel - 1 === initialIndent) { + // This is the end of a scope, just like above + goDown = false; + startLineNumber = upLineNumber; + endLineNumber = upLineNumber; + indent = upLineIndentLevel; + continue; + } + + startLineNumber = lineNumber; + endLineNumber = lineNumber; + indent = initialIndent; + if (indent === 0) { + // No need to continue + return { startLineNumber, endLineNumber, indent }; + } + } + + if (goUp) { + if (upLineIndentLevel >= indent) { + startLineNumber = upLineNumber; + } else { + goUp = false; + } + } + if (goDown) { if (downLineIndentLevel >= indent) { endLineNumber = downLineNumber; } else { diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 95a179c735..dcb0e807ec 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -80,6 +80,7 @@ export interface IModelDecorationsChangedEvent { /** * An event describing that some ranges of lines have been tokenized (their tokens have changed). + * @internal */ export interface IModelTokensChangedEvent { readonly tokenizationSupportChanged: boolean; diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 5f80d4252b..d329f0b0ff 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -786,6 +786,15 @@ export class TokensStore2 { const bEndCharacter = bTokens.getEndCharacter(bIndex); const bMetadata = bTokens.getMetadata(bIndex); + const bMask = ( + ((bMetadata & MetadataConsts.SEMANTIC_USE_ITALIC) ? MetadataConsts.ITALIC_MASK : 0) + | ((bMetadata & MetadataConsts.SEMANTIC_USE_BOLD) ? MetadataConsts.BOLD_MASK : 0) + | ((bMetadata & MetadataConsts.SEMANTIC_USE_UNDERLINE) ? MetadataConsts.UNDERLINE_MASK : 0) + | ((bMetadata & MetadataConsts.SEMANTIC_USE_FOREGROUND) ? MetadataConsts.FOREGROUND_MASK : 0) + | ((bMetadata & MetadataConsts.SEMANTIC_USE_BACKGROUND) ? MetadataConsts.BACKGROUND_MASK : 0) + ) >>> 0; + const aMask = (~bMask) >>> 0; + // push any token from `a` that is before `b` while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) { result[resultLen++] = aTokens.getEndOffset(aIndex); @@ -800,21 +809,24 @@ export class TokensStore2 { } // skip any tokens from `a` that are contained inside `b` - while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bEndCharacter) { + while (aIndex < aLen && aTokens.getEndOffset(aIndex) < bEndCharacter) { + result[resultLen++] = aTokens.getEndOffset(aIndex); + result[resultLen++] = (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask); aIndex++; } - const aMetadata = aTokens.getMetadata(Math.min(Math.max(0, aIndex - 1), aLen - 1)); - const languageId = TokenMetadata.getLanguageId(aMetadata); - const tokenType = TokenMetadata.getTokenType(aMetadata); + if (aIndex < aLen && aTokens.getEndOffset(aIndex) === bEndCharacter) { + // `a` ends exactly at the same spot as `b`! + result[resultLen++] = aTokens.getEndOffset(aIndex); + result[resultLen++] = (aTokens.getMetadata(aIndex) & aMask) | (bMetadata & bMask); + aIndex++; + } else { + const aMergeIndex = Math.min(Math.max(0, aIndex - 1), aLen - 1); - // push the token from `b` - result[resultLen++] = bEndCharacter; - result[resultLen++] = ( - (bMetadata & MetadataConsts.LANG_TTYPE_CMPL) - | ((languageId << MetadataConsts.LANGUAGEID_OFFSET) >>> 0) - | ((tokenType << MetadataConsts.TOKEN_TYPE_OFFSET) >>> 0) - ); + // push the token from `b` + result[resultLen++] = bEndCharacter; + result[resultLen++] = (aTokens.getMetadata(aMergeIndex) & aMask) | (bMetadata & bMask); + } } // push the remaining tokens from `a` diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 56042681e7..3beda583a2 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -125,7 +125,15 @@ export const enum MetadataConsts { FOREGROUND_MASK = 0b00000000011111111100000000000000, BACKGROUND_MASK = 0b11111111100000000000000000000000, - LANG_TTYPE_CMPL = 0b11111111111111111111100000000000, + ITALIC_MASK = 0b00000000000000000000100000000000, + BOLD_MASK = 0b00000000000000000001000000000000, + UNDERLINE_MASK = 0b00000000000000000010000000000000, + + SEMANTIC_USE_ITALIC = 0b00000000000000000000000000000001, + SEMANTIC_USE_BOLD = 0b00000000000000000000000000000010, + SEMANTIC_USE_UNDERLINE = 0b00000000000000000000000000000100, + SEMANTIC_USE_FOREGROUND = 0b00000000000000000000000000001000, + SEMANTIC_USE_BACKGROUND = 0b00000000000000000000000000010000, LANGUAGEID_OFFSET = 0, TOKEN_TYPE_OFFSET = 8, @@ -1330,10 +1338,10 @@ export interface RenameProvider { /** * @internal */ -export interface Session { +export interface AuthenticationSession { id: string; - accessToken: string; - displayName: string; + accessToken(): Promise; + accountName: string; } export interface Command { diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index a446446b8d..b2e6c6b826 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -223,6 +223,7 @@ export class LinkComputer { let state = State.Start; let hasOpenParens = false; let hasOpenSquareBracket = false; + let inSquareBrackets = false; let hasOpenCurlyBracket = false; while (j < len) { @@ -241,10 +242,12 @@ export class LinkComputer { chClass = (hasOpenParens ? CharacterClass.None : CharacterClass.ForceTermination); break; case CharCode.OpenSquareBracket: + inSquareBrackets = true; hasOpenSquareBracket = true; chClass = CharacterClass.None; break; case CharCode.CloseSquareBracket: + inSquareBrackets = false; chClass = (hasOpenSquareBracket ? CharacterClass.None : CharacterClass.ForceTermination); break; case CharCode.OpenCurlyBrace: @@ -272,6 +275,10 @@ export class LinkComputer { // `|` terminates a link if the link began with `|` chClass = (linkBeginChCode === CharCode.Pipe) ? CharacterClass.ForceTermination : CharacterClass.None; break; + case CharCode.Space: + // ` ` allow space in between [ and ] + chClass = (inSquareBrackets ? CharacterClass.None : CharacterClass.ForceTermination); + break; default: chClass = classifier.get(chCode); } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index ce61d1d056..a50f51209b 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -45,11 +45,13 @@ function canSyncModel(modelService: IModelService, resource: URI): boolean { } export class EditorWorkerServiceImpl extends Disposable implements IEditorWorkerService { - public _serviceBrand: undefined; + + _serviceBrand: undefined; private readonly _modelService: IModelService; private readonly _workerManager: WorkerManager; private readonly _logService: ILogService; + constructor( @IModelService modelService: IModelService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, @@ -60,7 +62,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker this._workerManager = this._register(new WorkerManager(this._modelService)); this._logService = logService; - // todo@joh make sure this happens only once + // register default link-provider and default completions-provider this._register(modes.LinkProviderRegistry.register('*', { provideLinks: (model, token) => { if (!canSyncModel(this._modelService, model.uri)) { diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index b8243ef9c7..7b460d7737 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -8,13 +8,13 @@ import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecyc import * as platform from 'vs/base/common/platform'; import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { EDITOR_MODEL_DEFAULTS, IEditorSemanticHighlightingOptions } from 'vs/editor/common/config/editorOptions'; +import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata } from 'vs/editor/common/modes'; +import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata, FontStyle, MetadataConsts } from 'vs/editor/common/modes'; import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -26,6 +26,10 @@ import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/to import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; +export interface IEditorSemanticHighlightingOptions { + enabled?: boolean; +} + function MODEL_ID(resource: URI): string { return resource.toString(); } @@ -629,7 +633,7 @@ class SemanticColoringProviderStyling { public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number { const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet); - let metadata: number | undefined; + let metadata: number; if (entry) { metadata = entry.metadata; } else { @@ -643,9 +647,31 @@ class SemanticColoringProviderStyling { modifierSet = modifierSet >> 1; } - metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); - if (typeof metadata === 'undefined') { + const tokenStyle = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers); + if (typeof tokenStyle === 'undefined') { metadata = Constants.NO_STYLING; + } else { + metadata = 0; + if (typeof tokenStyle.italic !== 'undefined') { + const italicBit = (tokenStyle.italic ? FontStyle.Italic : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= italicBit | MetadataConsts.SEMANTIC_USE_ITALIC; + } + if (typeof tokenStyle.bold !== 'undefined') { + const boldBit = (tokenStyle.bold ? FontStyle.Bold : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= boldBit | MetadataConsts.SEMANTIC_USE_BOLD; + } + if (typeof tokenStyle.underline !== 'undefined') { + const underlineBit = (tokenStyle.underline ? FontStyle.Underline : 0) << MetadataConsts.FONT_STYLE_OFFSET; + metadata |= underlineBit | MetadataConsts.SEMANTIC_USE_UNDERLINE; + } + if (tokenStyle.foreground) { + const foregroundBits = (tokenStyle.foreground) << MetadataConsts.FOREGROUND_OFFSET; + metadata |= foregroundBits | MetadataConsts.SEMANTIC_USE_FOREGROUND; + } + if (metadata === 0) { + // Nothing! + metadata = Constants.NO_STYLING; + } } this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata); } @@ -763,10 +789,21 @@ class ModelSemanticColoring extends Disposable { contentChangeListener.dispose(); this._setSemanticTokens(provider, res || null, styling, pendingChanges); }, (err) => { - errors.onUnexpectedError(err); + if (!err || typeof err.message !== 'string' || err.message.indexOf('busy') === -1) { + errors.onUnexpectedError(err); + } + + // Semantic tokens eats up all errors and considers errors to mean that the result is temporarily not available + // The API does not have a special error kind to express this... this._currentRequestCancellationTokenSource = null; contentChangeListener.dispose(); - this._setSemanticTokens(provider, null, styling, pendingChanges); + + if (pendingChanges.length > 0) { + // More changes occurred while the request was running + if (!this._fetchSemanticTokens.isScheduled()) { + this._fetchSemanticTokens.schedule(); + } + } }); } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index da44ff3fa5..c876882ebe 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -231,50 +231,51 @@ export enum EditorOption { overviewRulerLanes = 63, parameterHints = 64, peekWidgetDefaultFocus = 65, - quickSuggestions = 66, - quickSuggestionsDelay = 67, - readOnly = 68, - renderControlCharacters = 69, - renderIndentGuides = 70, - renderFinalNewline = 71, - renderLineHighlight = 72, - renderValidationDecorations = 73, - renderWhitespace = 74, - revealHorizontalRightPadding = 75, - roundedSelection = 76, - rulers = 77, - scrollbar = 78, - scrollBeyondLastColumn = 79, - scrollBeyondLastLine = 80, - selectionClipboard = 81, - selectionHighlight = 82, - selectOnLineNumbers = 83, - semanticHighlighting = 84, - showFoldingControls = 85, - showUnused = 86, - snippetSuggestions = 87, - smoothScrolling = 88, - stopRenderingLineAfter = 89, - suggest = 90, - suggestFontSize = 91, - suggestLineHeight = 92, - suggestOnTriggerCharacters = 93, - suggestSelection = 94, - tabCompletion = 95, - useTabStops = 96, - wordSeparators = 97, - wordWrap = 98, - wordWrapBreakAfterCharacters = 99, - wordWrapBreakBeforeCharacters = 100, - wordWrapColumn = 101, - wordWrapMinified = 102, - wrappingIndent = 103, - wrappingAlgorithm = 104, - editorClassName = 105, - pixelRatio = 106, - tabFocusMode = 107, - layoutInfo = 108, - wrappingInfo = 109 + definitionLinkOpensInPeek = 66, + quickSuggestions = 67, + quickSuggestionsDelay = 68, + readOnly = 69, + renderControlCharacters = 70, + renderIndentGuides = 71, + renderFinalNewline = 72, + renderLineHighlight = 73, + renderValidationDecorations = 74, + renderWhitespace = 75, + revealHorizontalRightPadding = 76, + roundedSelection = 77, + rulers = 78, + scrollbar = 79, + scrollBeyondLastColumn = 80, + scrollBeyondLastLine = 81, + scrollPredominantAxis = 82, + selectionClipboard = 83, + selectionHighlight = 84, + selectOnLineNumbers = 85, + showFoldingControls = 86, + showUnused = 87, + snippetSuggestions = 88, + smoothScrolling = 89, + stopRenderingLineAfter = 90, + suggest = 91, + suggestFontSize = 92, + suggestLineHeight = 93, + suggestOnTriggerCharacters = 94, + suggestSelection = 95, + tabCompletion = 96, + useTabStops = 97, + wordSeparators = 98, + wordWrap = 99, + wordWrapBreakAfterCharacters = 100, + wordWrapBreakBeforeCharacters = 101, + wordWrapColumn = 102, + wordWrapMinified = 103, + wrappingIndent = 104, + wrappingStrategy = 105, + editorClassName = 106, + pixelRatio = 107, + tabFocusMode = 108, + layoutInfo = 109, + wrappingInfo = 110 } /** diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index 6b30ef7d27..76afd2655d 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -87,6 +87,7 @@ registerThemingParticipant((theme, collector) => { const invisibles = theme.getColor(editorWhitespaces); if (invisibles) { - collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`); + collector.addRule(`.monaco-editor .mtkw { color: ${invisibles} !important; }`); + collector.addRule(`.monaco-editor .mtkz { color: ${invisibles} !important; }`); } }); diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 7715c163fd..4db5ffb766 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -183,7 +183,8 @@ export const enum VerticalRevealType { Center = 1, CenterIfOutsideViewport = 2, Top = 3, - Bottom = 4 + Bottom = 4, + NearTop = 5, } export class ViewRevealRangeRequestEvent { diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index be71fcb116..ca7ca0cf70 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -262,7 +262,6 @@ export class LinesLayout { private _checkPendingChanges(): void { if (this._pendingChanges.mustCommit()) { - console.warn(`Commiting pending changes before change accessor leaves due to read access.`); this._pendingChanges.commit(this); } } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 45c8e3b017..e7238286d3 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -300,13 +300,23 @@ export class ViewLayout extends Disposable implements IViewLayout { private _computeContentWidth(maxLineWidth: number): number { const options = this._configuration.options; const wrappingInfo = options.get(EditorOption.wrappingInfo); - let isViewportWrapping = wrappingInfo.isViewportWrapping; - if (!isViewportWrapping) { - const extraHorizontalSpace = options.get(EditorOption.scrollBeyondLastColumn) * options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; + const fontInfo = options.get(EditorOption.fontInfo); + if (wrappingInfo.isViewportWrapping) { + const layoutInfo = options.get(EditorOption.layoutInfo); + const minimap = options.get(EditorOption.minimap); + if (maxLineWidth > layoutInfo.contentWidth + fontInfo.typicalHalfwidthCharacterWidth) { + // This is a case where viewport wrapping is on, but the line extends above the viewport + if (minimap.enabled && minimap.side === 'right') { + // We need to accomodate the scrollbar width + return maxLineWidth + layoutInfo.verticalScrollbarWidth; + } + } + return maxLineWidth; + } else { + const extraHorizontalSpace = options.get(EditorOption.scrollBeyondLastColumn) * fontInfo.typicalHalfwidthCharacterWidth; const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth(); return Math.max(maxLineWidth + extraHorizontalSpace, whitespaceMinWidth); } - return maxLineWidth; } public onMaxLineWidthChanged(maxLineWidth: number): void { diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 4f4f76bce4..ff8c4568b2 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -549,7 +549,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces: } /** - * Whitespace is rendered by "replacing" tokens with a special-purpose `vs-whitespace` type that is later recognized in the rendering phase. + * Whitespace is rendered by "replacing" tokens with a special-purpose `mtkw` type that is later recognized in the rendering phase. * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (→ or ·) do not have the same width as  . * The rendering phase will generate `style="width:..."` for these tokens. */ @@ -616,7 +616,7 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW // was in whitespace token if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) { // leaving whitespace token or entering a new indent - result[resultLen++] = new LinePart(charIndex, 'vs-whitespace'); + result[resultLen++] = new LinePart(charIndex, 'mtkw'); tmpIndent = tmpIndent % tabSize; } } else { @@ -661,7 +661,7 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW } } - result[resultLen++] = new LinePart(len, generateWhitespace ? 'vs-whitespace' : tokenType); + result[resultLen++] = new LinePart(len, generateWhitespace ? 'mtkw' : tokenType); return result; } @@ -763,11 +763,12 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render const part = parts[partIndex]; const partEndIndex = part.endIndex; const partType = part.type; - const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('vs-whitespace') >= 0)); + const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('mtkw') >= 0)); + const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw'/*only whitespace*/ || !containsForeignElements); charOffsetInPart = 0; sb.appendASCIIString(' { const foldBackground = theme.getColor(foldBackgroundBackground); diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 8baeacfae4..fa7492a850 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -178,10 +178,8 @@ export async function formatDocumentRangeWithProvider( if (isCodeEditor(editorOrModel)) { // use editor to apply edits - FormattingEdit.execute(editorOrModel, edits); + FormattingEdit.execute(editorOrModel, edits, true); alertFormattingEdits(edits); - editorOrModel.pushUndoStop(); - editorOrModel.focus(); editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate); } else { @@ -266,12 +264,10 @@ export async function formatDocumentWithProvider( if (isCodeEditor(editorOrModel)) { // use editor to apply edits - FormattingEdit.execute(editorOrModel, edits); + FormattingEdit.execute(editorOrModel, edits, mode !== FormattingMode.Silent); if (mode !== FormattingMode.Silent) { alertFormattingEdits(edits); - editorOrModel.pushUndoStop(); - editorOrModel.focus(); editorOrModel.revealPositionInCenterIfOutsideViewport(editorOrModel.getPosition(), ScrollType.Immediate); } diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 30afe93381..cb5281b24f 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -138,7 +138,7 @@ class FormatOnType implements IEditorContribution { } if (isNonEmptyArray(edits)) { - FormattingEdit.execute(this._editor, edits); + FormattingEdit.execute(this._editor, edits, true); alertFormattingEdits(edits); } diff --git a/src/vs/editor/contrib/format/formattingEdit.ts b/src/vs/editor/contrib/format/formattingEdit.ts index 431adda97c..9b44a46d64 100644 --- a/src/vs/editor/contrib/format/formattingEdit.ts +++ b/src/vs/editor/contrib/format/formattingEdit.ts @@ -43,8 +43,10 @@ export class FormattingEdit { return fullModelRange.equalsRange(editRange); } - static execute(editor: ICodeEditor, _edits: TextEdit[]) { - editor.pushUndoStop(); + static execute(editor: ICodeEditor, _edits: TextEdit[], addUndoStops: boolean) { + if (addUndoStops) { + editor.pushUndoStop(); + } const edits = FormattingEdit._handleEolEdits(editor, _edits); if (edits.length === 1 && FormattingEdit._isFullModelReplaceEdit(editor, edits[0])) { // We use replace semantics and hope that markers stay put... @@ -52,6 +54,8 @@ export class FormattingEdit { } else { editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replaceMove(Range.lift(edit.range), edit.text))); } - editor.pushUndoStop(); + if (addUndoStops) { + editor.pushUndoStop(); + } } } diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 7fe02c5bd1..e8180d367d 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -28,6 +28,7 @@ import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isEqual } from 'vs/base/common/resources'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; class MarkerModel { @@ -396,7 +397,7 @@ class MarkerNavigationAction extends EditorAction { return editorService.openCodeEditor({ resource: newMarker.resource, - options: { pinned: false, revealIfOpened: true, revealInCenterIfOutsideViewport: true, selection: newMarker } + options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, selection: newMarker } }, editor).then(editor => { if (!editor) { return undefined; diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 832247fb8a..1117300566 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -317,6 +317,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._icon.className = `codicon ${SeverityIcon.className(MarkerSeverity.toSeverity(this._severity))}`; this.editor.revealPositionInCenter(position, ScrollType.Smooth); + this.editor.focus(); } updateMarker(marker: IMarker): void { diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index e129d659e5..676e4ed823 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -36,6 +36,8 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ScrollType, IEditorAction } from 'vs/editor/common/editorCommon'; import { assertType } from 'vs/base/common/types'; +import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; MenuRegistry.appendMenuItem(MenuId.EditorContext, { @@ -129,7 +131,7 @@ abstract class SymbolNavigationAction extends EditorAction { private async _onResult(editorService: ICodeEditorService, symbolNavService: ISymbolNavigationService, editor: IActiveCodeEditor, model: ReferencesModel): Promise { const gotoLocation = this._getGoToPreference(editor); - if (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1)) { + if (!(editor instanceof EmbeddedCodeEditorWidget) && (this._configuration.openInPeek || (gotoLocation === 'peek' && model.references.length > 1))) { this._openInPeek(editor, model); } else { @@ -165,7 +167,7 @@ abstract class SymbolNavigationAction extends EditorAction { resource: reference.uri, options: { selection: Range.collapseToStart(range), - revealInCenterIfOutsideViewport: true + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport } }, editor, sideBySide); diff --git a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts index 24629d038a..6a26beb2be 100644 --- a/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts +++ b/src/vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition.ts @@ -27,6 +27,10 @@ import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } fro import { Position } from 'vs/editor/common/core/position'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { PeekContext } from 'vs/editor/contrib/peekView/peekView'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export class GotoDefinitionAtPositionEditorContribution implements IEditorContribution { @@ -334,8 +338,17 @@ export class GotoDefinitionAtPositionEditorContribution implements IEditorContri private gotoDefinition(position: Position, openToSide: boolean): Promise { this.editor.setPosition(position); - const action = new DefinitionAction({ openToSide, openInPeek: false, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined }); - return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor)); + const definitionLinkOpensInPeek = this.editor.getOption(EditorOption.definitionLinkOpensInPeek); + return this.editor.invokeWithinContext((accessor) => { + const canPeek = definitionLinkOpensInPeek && !this.isInPeekEditor(accessor); + const action = new DefinitionAction({ openToSide, openInPeek: canPeek, muteMessage: true }, { alias: '', label: '', id: '', precondition: undefined }); + return action.run(accessor, this.editor); + }); + } + + private isInPeekEditor(accessor: ServicesAccessor): boolean | undefined { + const contextKeyService = accessor.get(IContextKeyService); + return PeekContext.inPeekEditor.getValue(contextKeyService); } public dispose(): void { diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 2a32c3b2b7..3958e96b8c 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -216,14 +216,16 @@ export abstract class ReferencesController implements IEditorContribution { } } - closeWidget(): void { + closeWidget(focusEditor = true): void { this._referenceSearchVisible.reset(); this._disposables.clear(); dispose(this._widget); dispose(this._model); this._widget = undefined; this._model = undefined; - this._editor.focus(); + if (focusEditor) { + this._editor.focus(); + } this._requestIdPool += 1; // Cancel pending requests } @@ -300,7 +302,7 @@ function withController(accessor: ServicesAccessor, fn: (controller: ReferencesC } KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'changePeekFocus', + id: 'togglePeekWidgetFocus', weight: KeybindingWeight.EditorContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.F2), when: ContextKeyExpr.or(ctxReferenceSearchVisible, PeekContext.inPeekEditor), diff --git a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index a6bba6e922..d8e54eb47d 100644 --- a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -19,6 +19,7 @@ import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isEqual } from 'vs/base/common/resources'; +import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; export const ctxHasSymbols = new RawContextKey('hasSymbols', false); @@ -127,7 +128,7 @@ class SymbolNavigationService implements ISymbolNavigationService { resource: reference.uri, options: { selection: Range.collapseToStart(reference.range), - revealInCenterIfOutsideViewport: true + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport } }, source).finally(() => { this._ignoreEditorChange = false; diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 087cb8d303..80e3d2ae25 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -37,12 +37,36 @@ abstract class AbstractCopyLinesAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { + if (!editor.hasModel()) { + return; + } + + const selections = editor.getSelections().map((selection, index) => ({ selection, index, ignore: false })); + selections.sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection)); + + // Remove selections that would result in copying the same line + let prev = selections[0]; + for (let i = 1; i < selections.length; i++) { + const curr = selections[i]; + if (prev.selection.endLineNumber === curr.selection.startLineNumber) { + // these two selections would copy the same line + if (prev.index < curr.index) { + // prev wins + curr.ignore = true; + } else { + // curr wins + prev.ignore = true; + prev = curr; + } + } + } const commands: ICommand[] = []; - const selections = editor.getSelections() || []; - for (const selection of selections) { - commands.push(new CopyLinesCommand(selection, this.down)); + if (selection.ignore) { + continue; + } + commands.push(new CopyLinesCommand(selection.selection, this.down)); } editor.pushUndoStop(); diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index 2d0897bf63..13e857c231 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -179,7 +179,7 @@ class RenameController implements IEditorContribution { selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn; } - const supportPreview = this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); + const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); const inputFieldResult = await this._renameInputField.getValue().getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview); // no result, only hint to focus the editor or not diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index e5c418b354..852b9f65d4 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -111,6 +111,44 @@ font-weight: bold; } +/** Status Bar **/ + +.monaco-editor .suggest-widget > .suggest-status-bar { + visibility: hidden; + + position: absolute; + left: 0; + + box-sizing: border-box; + + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + + width: 100%; + + font-size: 80%; + + border-left-width: 1px; + border-left-style: solid; + border-right-width: 1px; + border-right-style: solid; + border-bottom-width: 1px; + border-bottom-style: solid; + + padding: 0 8px 0 4px; + + box-shadow: 0 -.5px 3px #ddd; +} + +.monaco-editor .suggest-widget.list-right.docs-side > .suggest-status-bar { + left: auto; + right: 0; +} +.monaco-editor .suggest-widget.docs-side > .suggest-status-bar { + width: 50%; +} + /** ReadMore Icon styles **/ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .header > .codicon-close, @@ -190,7 +228,7 @@ overflow: hidden; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .left > .monaco-icon-label { - flex-shrink: 0; + flex-shrink: 1; } .monaco-editor .suggest-widget .monaco-list .monaco-list-row > .contents > .main > .right { overflow: hidden; @@ -324,6 +362,7 @@ .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs { padding: 0; white-space: initial; + min-height: calc(1rem + 8px); } .monaco-editor .suggest-widget .details > .monaco-scrollable-element > .body > .docs.markdown-docs > div, diff --git a/src/vs/editor/contrib/suggest/media/suggestStatusBar.css b/src/vs/editor/contrib/suggest/media/suggestStatusBar.css new file mode 100644 index 0000000000..eb05ba733a --- /dev/null +++ b/src/vs/editor/contrib/suggest/media/suggestStatusBar.css @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar { + visibility: visible; +} +.monaco-editor .suggest-widget.with-status-bar > .tree { + margin-bottom: 18px; +} + +.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-label { + min-height: 18px; + opacity: 0.5; + color: inherit; +} + +.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label { + margin-right: 0; +} + +.monaco-editor .suggest-widget.with-status-bar .suggest-status-bar .action-item:not(:last-of-type) .action-label::after { + content: ', '; + margin-right: 0.3em; +} + +.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row > .contents > .main > .right > .readMore, +.monaco-editor .suggest-widget.with-status-bar .monaco-list .monaco-list-row.focused > .contents > .main > .right:not(.always-show-details) > .readMore { + display: none; +} + +.monaco-editor .suggest-widget.with-status-bar:not(.docs-side) .monaco-list .monaco-list-row:hover > .contents > .main > .right.can-expand-details > .details-label { + width: 100%; +} diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index f4bb3fd65b..da63de0b90 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -17,14 +17,19 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Range } from 'vs/editor/common/core/range'; import { FuzzyScore } from 'vs/base/common/filters'; import { isDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { MenuId } from 'vs/platform/actions/common/actions'; export const Context = { Visible: new RawContextKey('suggestWidgetVisible', false), + DetailsVisible: new RawContextKey('suggestWidgetDetailsVisible', false), MultipleSuggestions: new RawContextKey('suggestWidgetMultipleSuggestions', false), MakesTextEdit: new RawContextKey('suggestionMakesTextEdit', true), - AcceptSuggestionsOnEnter: new RawContextKey('acceptSuggestionOnEnter', true) + AcceptSuggestionsOnEnter: new RawContextKey('acceptSuggestionOnEnter', true), + HasInsertAndReplaceRange: new RawContextKey('suggestionHasInsertAndReplaceRange', false), }; +export const suggestWidgetStatusbarMenu = new MenuId('suggestWidgetStatusBar'); + export class CompletionItem { _brand!: 'ISuggestionItem'; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 9a706b214c..691ae91a3f 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -23,7 +23,7 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { Context as SuggestContext, CompletionItem } from './suggest'; +import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } from './suggest'; import { SuggestAlternatives } from './suggestAlternatives'; import { State, SuggestModel } from './suggestModel'; import { ISelectedSuggestion, SuggestWidget } from './suggestWidget'; @@ -33,17 +33,16 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { IdleValue } from 'vs/base/common/async'; import { isObject, assertType } from 'vs/base/common/types'; import { CommitCharacterController } from './suggestCommitCharacters'; -import { IPosition } from 'vs/editor/common/core/position'; +import { IPosition, Position } from 'vs/editor/common/core/position'; import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import * as platform from 'vs/base/common/platform'; import { SuggestRangeHighlighter } from 'vs/editor/contrib/suggest/suggestRangeHighlighter'; +import { MenuRegistry } from 'vs/platform/actions/common/actions'; -/** - * Stop suggest widget from disappearing when clicking into other areas - * For development purpose only - */ -const _sticky = false; +// sticky suggest widget which doesn't disappear on focus out and such +let _sticky = false; +// _sticky = Boolean("true"); // done "weirdly" so that a lint warning prevents you from pushing this class LineSuffix { @@ -138,9 +137,17 @@ export class SuggestController implements IEditorContribution { })); // Wire up makes text edit context key - let makesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService); + const ctxMakesTextEdit = SuggestContext.MakesTextEdit.bindTo(this._contextKeyService); + const ctxHasInsertAndReplace = SuggestContext.HasInsertAndReplaceRange.bindTo(this._contextKeyService); + + this._toDispose.add(toDisposable(() => { + ctxMakesTextEdit.reset(); + ctxHasInsertAndReplace.reset(); + })); + this._toDispose.add(widget.onDidFocus(({ item }) => { + // (ctx: makesTextEdit) const position = this.editor.getPosition()!; const startColumn = item.editStart.column; const endColumn = position.column; @@ -161,9 +168,11 @@ export class SuggestController implements IEditorContribution { }); value = oldText !== item.completion.insertText; } - makesTextEdit.set(value); + ctxMakesTextEdit.set(value); + + // (ctx: hasInsertAndReplaceRange) + ctxHasInsertAndReplace.set(!Position.equals(item.editInsertEnd, item.editReplaceEnd)); })); - this._toDispose.add(toDisposable(() => makesTextEdit.reset())); this._toDispose.add(widget.onDetailsKeyDown(e => { // cmd + c on macOS, ctrl + c on Win / Linux @@ -435,7 +444,6 @@ export class SuggestController implements IEditorContribution { } this._insertSuggestion(item, flags); } - acceptNextSuggestion() { this._alternatives.getValue().next(); } @@ -528,11 +536,8 @@ const SuggestCommand = EditorCommand.bindToContribution(Sugge registerEditorCommand(new SuggestCommand({ id: 'acceptSelectedSuggestion', precondition: SuggestContext.Visible, - handler(x, args) { - const alternative: boolean = typeof args === 'object' && typeof args.alternative === 'boolean' - ? args.alternative - : false; - x.acceptSelectedSuggestion(true, alternative); + handler(x) { + x.acceptSelectedSuggestion(true, false); } })); @@ -549,19 +554,55 @@ KeybindingsRegistry.registerKeybindingRule({ id: 'acceptSelectedSuggestion', when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus, SuggestContext.AcceptSuggestionsOnEnter, SuggestContext.MakesTextEdit), primary: KeyCode.Enter, - weight + weight, }); -// shift+enter and shift+tab use the alternative-flag so that the suggest controller -// is doing the opposite of the editor.suggest.overwriteOnAccept-configuration -KeybindingsRegistry.registerKeybindingRule({ - id: 'acceptSelectedSuggestion', - when: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), - primary: KeyMod.Shift | KeyCode.Tab, - secondary: [KeyMod.Shift | KeyCode.Enter], - args: { alternative: true }, - weight +MenuRegistry.appendMenuItem(suggestWidgetStatusbarMenu, { + command: { id: 'acceptSelectedSuggestion', title: nls.localize({ key: 'accept.accept', comment: ['{0} will be a keybinding, e.g "Enter to insert"'] }, "{0} to insert") }, + group: 'left', + order: 1, + when: SuggestContext.HasInsertAndReplaceRange.toNegated() }); +MenuRegistry.appendMenuItem(suggestWidgetStatusbarMenu, { + command: { id: 'acceptSelectedSuggestion', title: nls.localize({ key: 'accept.insert', comment: ['{0} will be a keybinding, e.g "Enter to insert"'] }, "{0} to insert") }, + group: 'left', + order: 1, + when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'insert')) +}); +MenuRegistry.appendMenuItem(suggestWidgetStatusbarMenu, { + command: { id: 'acceptSelectedSuggestion', title: nls.localize({ key: 'accept.replace', comment: ['{0} will be a keybinding, e.g "Enter to replace"'] }, "{0} to replace") }, + group: 'left', + order: 1, + when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'replace')) +}); + +registerEditorCommand(new SuggestCommand({ + id: 'acceptAlternativeSelectedSuggestion', + precondition: ContextKeyExpr.and(SuggestContext.Visible, EditorContextKeys.textInputFocus), + kbOpts: { + weight: weight, + kbExpr: EditorContextKeys.textInputFocus, + primary: KeyMod.Shift | KeyCode.Enter, + secondary: [KeyMod.Shift | KeyCode.Tab], + }, + handler(x) { + x.acceptSelectedSuggestion(false, true); + }, + menuOpts: [{ + menuId: suggestWidgetStatusbarMenu, + group: 'left', + order: 2, + when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'insert')), + title: nls.localize({ key: 'accept.replace', comment: ['{0} will be a keybinding, e.g "Enter to replace"'] }, "{0} to replace") + }, { + menuId: suggestWidgetStatusbarMenu, + group: 'left', + order: 2, + when: ContextKeyExpr.and(SuggestContext.HasInsertAndReplaceRange, ContextKeyExpr.equals('config.editor.suggest.insertMode', 'replace')), + title: nls.localize({ key: 'accept.insert', comment: ['{0} will be a keybinding, e.g "Enter to insert"'] }, "{0} to insert") + }] +})); + // continue to support the old command CommandsRegistry.registerCommandAlias('acceptSelectedSuggestionOnEnter', 'acceptSelectedSuggestion'); @@ -649,7 +690,20 @@ registerEditorCommand(new SuggestCommand({ kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.Space, mac: { primary: KeyMod.WinCtrl | KeyCode.Space } - } + }, + menuOpts: [{ + menuId: suggestWidgetStatusbarMenu, + group: 'right', + order: 1, + when: SuggestContext.DetailsVisible, + title: nls.localize('detail.more', "show less") + }, { + menuId: suggestWidgetStatusbarMenu, + group: 'right', + order: 1, + when: SuggestContext.DetailsVisible.toNegated(), + title: nls.localize('detail.less', "show more") + }] })); registerEditorCommand(new SuggestCommand({ diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index ae5a36dc2d..c61ed33c3f 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/suggest'; +import 'vs/css!./media/suggestStatusBar'; import 'vs/base/browser/ui/codiconLabel/codiconLabel'; // The codicon symbol styles are defined here and must be loaded import 'vs/editor/contrib/documentSymbols/outlineTree'; // The codicon symbol colors are defined here and must be loaded import * as nls from 'vs/nls'; @@ -20,7 +21,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; -import { Context as SuggestContext, CompletionItem } from './suggest'; +import { Context as SuggestContext, CompletionItem, suggestWidgetStatusbarMenu } from './suggest'; import { CompletionModel } from './completionModel'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachListStyler } from 'vs/platform/theme/common/styler'; @@ -39,8 +40,11 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FileKind } from 'vs/platform/files/common/files'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { flatten } from 'vs/base/common/arrays'; +import { flatten, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction } from 'vs/base/common/actions'; const expandSuggestionDocsByDefault = false; @@ -49,8 +53,8 @@ interface ISuggestionTemplateData { /** * Flexbox - * < ------- left ------- > < -------- right -------- > - *