From de5f1eb78040a489492312e2e3bf9e1751b95ac8 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Fri, 21 Feb 2020 23:42:19 -0800 Subject: [PATCH] Merge from vscode 33a65245075e4d18908652865a79cf5489c30f40 (#9279) * Merge from vscode 33a65245075e4d18908652865a79cf5489c30f40 * remove github --- .eslintrc.json | 1 + build/package.json | 2 +- build/yarn.lock | 10 +- extensions/configuration-editing/package.json | 4 +- extensions/configuration-editing/yarn.lock | 16 +- extensions/extension-editing/package.json | 4 +- extensions/extension-editing/yarn.lock | 16 +- extensions/image-preview/src/preview.ts | 10 +- .../json-language-features/package.json | 30 +- .../server/package.json | 6 +- .../server/src/jsonServerMain.ts | 2 +- .../json-language-features/server/yarn.lock | 58 +-- extensions/json-language-features/yarn.lock | 28 +- .../src/features/previewManager.ts | 10 +- extensions/package.json | 2 +- extensions/vscode-colorize-tests/package.json | 2 +- extensions/vscode-colorize-tests/yarn.lock | 8 +- extensions/yarn.lock | 8 +- package.json | 2 +- src/main.js | 17 +- .../workbench/browser/modal/optionsDialog.ts | 6 +- .../browser/parts/views/customView.ts | 6 +- .../browser/accountDialogController.test.ts | 2 +- .../test/electron-browser/commandLine.test.ts | 3 +- .../browser/connectionViewletPanel.ts | 6 +- .../contrib/query/browser/gridPanel.ts | 3 +- .../browser/accountDialog.ts | 10 +- .../insights/browser/insightsDialogView.ts | 4 +- src/vs/base/common/extpath.ts | 17 + src/vs/base/common/types.ts | 20 + .../parts/quickinput/browser/quickInput.ts | 4 - .../parts/quickinput/common/quickInput.ts | 2 - .../editor/browser/controller/coreCommands.ts | 22 +- .../editor/browser/controller/mouseHandler.ts | 1 + src/vs/editor/browser/view/viewController.ts | 10 +- .../editor/browser/widget/codeEditorWidget.ts | 3 + src/vs/editor/common/config/editorOptions.ts | 10 + src/vs/editor/common/editorContextKeys.ts | 1 + src/vs/editor/common/model.ts | 9 +- src/vs/editor/common/model/editStack.ts | 198 +++++---- src/vs/editor/common/model/textModel.ts | 56 +-- src/vs/editor/common/modes.ts | 3 + .../common/standalone/standaloneEnums.ts | 199 ++++----- .../test/bracketMatching.test.ts | 14 +- .../editor/contrib/codeAction/codeAction.ts | 6 +- .../contrib/codeAction/codeActionModel.ts | 4 +- .../codeAction/test/codeAction.test.ts | 24 +- .../codeAction/test/codeActionModel.test.ts | 3 +- src/vs/editor/contrib/dnd/dnd.ts | 4 +- .../documentSymbols/test/outlineModel.test.ts | 6 +- .../contrib/find/test/findModel.test.ts | 4 +- .../contrib/folding/test/foldingModel.test.ts | 29 +- .../folding/test/foldingRanges.test.ts | 8 +- .../folding/test/hiddenRangeModel.test.ts | 4 +- .../contrib/folding/test/indentFold.test.ts | 4 +- .../folding/test/indentRangeProvider.test.ts | 4 +- .../contrib/folding/test/syntaxFold.test.ts | 4 +- src/vs/editor/contrib/format/format.ts | 3 + src/vs/editor/contrib/format/formatActions.ts | 3 +- .../gotoSymbol/peek/referencesWidget.ts | 6 +- .../editor/contrib/hover/modesContentHover.ts | 2 + .../test/parameterHintsModel.test.ts | 4 +- .../smartSelect/test/smartSelect.test.ts | 4 +- .../snippet/test/snippetController2.test.ts | 3 +- .../snippet/test/snippetSession.test.ts | 3 +- .../snippet/test/snippetVariables.test.ts | 15 +- .../contrib/suggest/test/suggest.test.ts | 3 +- .../suggest/test/suggestController.test.ts | 3 +- .../suggest/test/suggestMemory.test.ts | 4 +- .../contrib/suggest/test/suggestModel.test.ts | 7 +- .../contrib/suggest/test/wordDistance.test.ts | 4 +- .../standalone/browser/standaloneServices.ts | 2 +- .../test/browser/commands/sideEditing.test.ts | 4 +- .../test/browser/controller/cursor.test.ts | 24 ++ .../controller/cursorMoveCommand.test.ts | 3 +- .../browser/controller/textAreaState.test.ts | 4 +- src/vs/editor/test/browser/testCodeEditor.ts | 6 +- src/vs/editor/test/browser/testCommand.ts | 4 +- src/vs/editor/test/common/editorTestUtils.ts | 10 +- .../common/model/editableTextModel.test.ts | 3 +- .../model/editableTextModelTestUtils.ts | 3 +- .../test/common/model/model.line.test.ts | 5 +- .../test/common/model/model.modes.test.ts | 5 +- src/vs/editor/test/common/model/model.test.ts | 13 +- .../common/model/modelDecorations.test.ts | 19 +- .../common/model/modelEditOperation.test.ts | 3 +- .../pieceTreeTextBuffer.test.ts | 12 +- .../test/common/model/textModel.test.ts | 44 +- .../test/common/model/textModelSearch.test.ts | 33 +- .../common/model/textModelWithTokens.test.ts | 25 +- .../test/common/model/tokensStore.test.ts | 3 +- .../test/common/services/modelService.test.ts | 19 +- .../viewModel/splitLinesCollection.test.ts | 5 +- .../test/common/viewModel/testViewModel.ts | 3 +- src/vs/monaco.d.ts | 207 ++++----- src/vs/platform/actions/common/actions.ts | 7 +- .../dialogs/test/common/testDialogService.ts | 16 + .../common/instantiationService.ts | 2 +- src/vs/platform/progress/common/progress.ts | 4 +- src/vs/platform/undoRedo/common/undoRedo.ts | 56 +-- .../undoRedo/common/undoRedoService.ts | 380 ++++++++++++----- .../common/abstractSynchronizer.ts | 63 ++- .../userDataSync/common/extensionsSync.ts | 11 +- .../userDataSync/common/globalStateSync.ts | 11 +- .../userDataSync/common/keybindingsSync.ts | 8 +- .../userDataSync/common/settingsMerge.ts | 8 +- .../userDataSync/common/settingsSync.ts | 46 +- .../userDataSync/common/userDataSync.ts | 14 +- .../userDataSync/common/userDataSyncIpc.ts | 6 + .../common/userDataSyncService.ts | 24 +- .../test/common/userDataSyncClient.ts | 6 +- src/vs/vscode.proposed.d.ts | 184 ++++++-- .../api/browser/mainThreadLanguageFeatures.ts | 7 +- .../workbench/api/browser/mainThreadTask.ts | 2 +- .../api/browser/mainThreadWebview.ts | 91 ++-- .../workbench/api/common/extHost.api.impl.ts | 10 +- .../workbench/api/common/extHost.protocol.ts | 35 +- .../api/common/extHostLanguageFeatures.ts | 2 +- src/vs/workbench/api/common/extHostTask.ts | 5 +- src/vs/workbench/api/common/extHostWebview.ts | 402 ++++++++++++++---- src/vs/workbench/api/common/shared/tasks.ts | 2 +- src/vs/workbench/browser/labels.ts | 28 +- .../workbench/browser/parts/compositeBar.ts | 31 +- .../workbench/browser/parts/compositePart.ts | 2 + .../browser/parts/editor/baseEditor.ts | 31 ++ .../browser/parts/editor/editorStatus.ts | 2 + .../browser/parts/editor/textEditor.ts | 21 +- .../notifications/notificationsCenter.ts | 10 +- .../notifications/notificationsCommands.ts | 2 +- .../notifications/notificationsStatus.ts | 94 ++-- .../notifications/notificationsToasts.ts | 38 +- .../notifications/notificationsViewer.ts | 10 +- .../browser/parts/sidebar/sidebarPart.ts | 26 ++ .../browser/parts/views/customView.ts | 4 +- .../browser/parts/views/viewPaneContainer.ts | 32 +- .../browser/workbench.contribution.ts | 13 + src/vs/workbench/browser/workbench.ts | 6 +- src/vs/workbench/common/editor.ts | 4 +- src/vs/workbench/common/notifications.ts | 14 +- src/vs/workbench/common/views.ts | 5 + .../electron-browser/backupRestorer.test.ts | 17 +- .../electron-browser/backupTracker.test.ts | 33 +- .../contrib/bulkEdit/browser/bulkEditPane.ts | 4 +- .../contrib/bulkEdit/browser/bulkEditTree.ts | 8 +- .../browser/callHierarchy.contribution.ts | 2 +- .../browser/callHierarchyPeek.ts | 2 +- .../browser/callHierarchyTree.ts | 2 +- .../{browser => common}/callHierarchy.ts | 0 .../browser/codeEditor.contribution.ts | 1 + .../codeEditor/browser/saveParticipants.ts | 57 ++- .../browser/toggleColumnSelection.ts | 43 ++ .../test/browser/saveParticipant.test.ts | 14 +- .../contrib/comments/browser/commentsView.ts | 6 +- .../contrib/customEditor/browser/commands.ts | 36 +- .../customEditor/browser/customEditorInput.ts | 170 ++++++-- .../browser/customEditorInputFactory.ts | 16 +- .../customEditor/browser/customEditors.ts | 22 +- .../browser/webviewEditor.contribution.ts | 4 +- .../customEditor/common/customEditor.ts | 11 +- .../customEditor/common/customEditorModel.ts | 137 ++---- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 4 +- .../browser/debugConfigurationManager.ts | 12 +- .../contrib/debug/browser/debugSession.ts | 5 +- .../debug/browser/loadedScriptsView.ts | 4 +- .../workbench/contrib/debug/browser/repl.ts | 6 +- .../contrib/debug/browser/startView.ts | 79 ++-- .../contrib/debug/browser/variablesView.ts | 4 +- .../debug/browser/watchExpressionsView.ts | 4 +- .../workbench/contrib/debug/common/debug.ts | 2 +- .../debug/test/browser/breakpoints.test.ts | 3 +- .../extensions/browser/extensionsViews.ts | 6 +- .../files/browser/editors/textFileEditor.ts | 15 +- .../contrib/files/browser/views/emptyView.ts | 6 +- .../files/browser/views/explorerView.ts | 6 +- .../files/browser/views/openEditorsView.ts | 6 +- .../files/common/editors/fileEditorInput.ts | 20 +- .../files/test/browser/editorAutoSave.test.ts | 18 +- .../test/browser/fileEditorInput.test.ts | 19 +- .../test/browser/fileEditorTracker.test.ts | 22 +- .../test/browser/fileOnDiskProvider.test.ts | 14 +- .../markers/browser/markersTreeViewer.ts | 3 +- .../contrib/markers/browser/markersView.ts | 4 +- .../contrib/outline/browser/outlinePane.ts | 4 +- .../contrib/output/browser/outputView.ts | 5 +- .../preferences/browser/settingsTree.ts | 34 +- .../test/common/smartSnippetInserter.test.ts | 4 +- .../contrib/remote/browser/remote.ts | 3 +- .../contrib/remote/browser/tunnelView.ts | 6 +- .../workbench/contrib/scm/browser/mainPane.ts | 6 +- .../contrib/scm/browser/repositoryPane.ts | 4 +- .../contrib/scm/browser/scmViewlet.ts | 3 +- .../search/browser/search.contribution.ts | 16 +- .../contrib/search/browser/searchView.ts | 44 +- .../contrib/search/browser/searchWidget.ts | 30 +- .../contrib/search/common/searchModel.ts | 15 +- .../contrib/searchEditor/browser/constants.ts | 2 +- .../browser/searchEditor.contribution.ts | 34 +- .../searchEditor/browser/searchEditor.ts | 17 +- .../browser/searchEditorActions.ts | 8 +- .../test/browser/snippetsService.test.ts | 42 +- .../tasks/browser/abstractTaskService.ts | 126 ++---- .../tasks/browser/providerProgressManager.ts | 61 --- .../tasks/browser/terminalTaskSystem.ts | 1 + .../contrib/tasks/common/taskConfiguration.ts | 22 +- .../contrib/tasks/common/taskService.ts | 2 + .../contrib/tasks/common/taskSystem.ts | 2 +- .../workbench/contrib/tasks/common/tasks.ts | 2 +- .../contrib/terminal/browser/terminalView.ts | 2 +- .../contrib/timeline/browser/timelinePane.ts | 4 +- .../browser/userDataSync.contribution.ts | 10 +- .../userDataSync/browser/userDataSync.ts | 280 ++++++++---- .../electron-browser/desktop.contribution.ts | 4 + .../backupFileService.test.ts | 11 +- .../bulkEdit/browser/bulkEditService.ts | 62 ++- .../services/editor/browser/editorService.ts | 74 +--- .../test/browser/editorGroupsService.test.ts | 132 ++---- .../editor/test/browser/editorService.test.ts | 215 ++++------ .../test/browser/editorsObserver.test.ts | 128 ++---- .../history/test/browser/history.test.ts | 103 +---- .../services/search/common/search.ts | 10 +- .../common/textFileSaveParticipant.ts | 2 +- .../test/browser/textFileEditorModel.test.ts | 59 +-- .../textFileEditorModelManager.test.ts | 17 +- .../test/browser/textFileService.test.ts | 29 +- .../textFileService.io.test.ts | 65 +-- .../browser/textModelResolverService.test.ts | 23 +- .../common/untitledTextEditorModel.ts | 31 +- .../test/browser/untitledTextEditor.test.ts | 23 +- .../userDataSync/common/userDataSyncUtil.ts | 8 +- .../electron-browser/userDataSyncService.ts | 4 + .../browser/workingCopyFileService.test.ts | 18 +- .../browser/api/extHostApiCommands.test.ts | 4 +- .../api/extHostLanguageFeatures.test.ts | 13 +- .../test/browser/api/extHostWebview.test.ts | 32 +- ...mainThreadDocumentContentProviders.test.ts | 4 +- .../browser/api/mainThreadDocuments.test.ts | 10 +- .../api/mainThreadDocumentsAndEditors.test.ts | 7 +- .../browser/api/mainThreadEditors.test.ts | 13 +- .../browser/parts/editor/baseEditor.test.ts | 31 ++ .../test/browser/parts/editor/editor.test.ts | 31 +- .../parts/editor/editorDiffModel.test.ts | 19 +- .../browser/parts/editor/editorModel.test.ts | 11 +- .../parts/editor/rangeDecorations.test.ts | 3 +- .../parts/editor/resourceEditorInput.test.ts | 16 +- .../test/browser/workbenchTestServices.ts | 292 ++++++++++--- .../quickopen.perf.integrationTest.ts | 13 +- .../textsearch.perf.integrationTest.ts | 13 +- .../electron-browser/workbenchTestServices.ts | 53 ++- yarn.lock | 10 +- 250 files changed, 3724 insertions(+), 2756 deletions(-) create mode 100644 src/vs/platform/dialogs/test/common/testDialogService.ts rename src/vs/workbench/contrib/callHierarchy/{browser => common}/callHierarchy.ts (100%) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts delete mode 100644 src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts diff --git a/.eslintrc.json b/.eslintrc.json index b3d8a70e6c..13c995f300 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -727,6 +727,7 @@ "create", "delete", "dispose", + "edit", "end", "expand", "hide", diff --git a/build/package.json b/build/package.json index b8eb76f4e2..c7c88ebbad 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.1-rc", + "typescript": "3.8.2", "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 60d5a6dd62..3975ed3a34 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.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" + integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== + 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/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index be2d2e8eba..0defd3aee6 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -18,8 +18,8 @@ "watch": "gulp watch-extension:configuration-editing" }, "dependencies": { - "jsonc-parser": "^2.1.1", - "vscode-nls": "^4.0.0" + "jsonc-parser": "^2.2.1", + "vscode-nls": "^4.1.1" }, "contributes": { "languages": [ diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index d5aafed118..36aab5fd22 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -7,12 +7,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.7.tgz#57682a9771a3f7b09c2497f28129a0462966524a" integrity sha512-JNbGaHFCLwgHn/iCckiGSOZ1XYHsKFwREtzPwSGCVld1SGhOlmZw2D4ZI94HQCrBHbADzW9m4LER/8olJTRGHA== -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== -vscode-nls@^4.0.0: - version "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-nls@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 54edf5ccbd..fe4a7b44d2 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -19,10 +19,10 @@ "watch": "gulp watch-extension:extension-editing" }, "dependencies": { - "jsonc-parser": "^2.1.1", + "jsonc-parser": "^2.2.1", "markdown-it": "^8.3.1", "parse5": "^3.0.2", - "vscode-nls": "^4.0.0" + "vscode-nls": "^4.1.1" }, "contributes": { "jsonValidation": [ diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index d2f69a169d..50adf31c09 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -29,10 +29,10 @@ entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -jsonc-parser@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.1.1.tgz#83dc3d7a6e7186346b889b1280eefa04446c6d3e" - integrity sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== linkify-it@^2.0.0: version "2.0.3" @@ -74,7 +74,7 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI= -vscode-nls@^4.0.0: - version "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-nls@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c" + integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A== diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 3410b999d6..fb8a5b138c 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -27,11 +27,15 @@ export class PreviewManager implements vscode.WebviewCustomEditorProvider { private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public async resolveWebviewEditor( - resource: vscode.Uri, + public async provideWebviewCustomEditorDocument(resource: vscode.Uri) { + return vscode.window.createWebviewEditorCustomDocument(PreviewManager.viewType, resource, undefined, {}); + } + + public async resolveWebviewCustomEditor( + document: vscode.WebviewEditorCustomDocument, webviewEditor: vscode.WebviewPanel, ): Promise { - const preview = new Preview(this.extensionRoot, resource, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); + const preview = new Preview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); this._previews.add(preview); this.setActivePreview(preview); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index dfceebce8e..8a12ff009c 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -100,27 +100,29 @@ }, "configurationDefaults": { "[json]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" }, "[jsonc]": { - "editor.quickSuggestions": { - "strings": true - }, - "editor.suggest.insertMode": "replace" - } + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + } }, - "jsonValidation": [{ - "fileMatch": "*.schema.json", - "url": "http://json-schema.org/draft-07/schema#" - }] + "jsonValidation": [ + { + "fileMatch": "*.schema.json", + "url": "http://json-schema.org/draft-07/schema#" + } + ] }, "dependencies": { "request-light": "^0.2.5", "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.1", + "vscode-languageclient": "^6.1.1", "vscode-nls": "^4.1.1" }, "devDependencies": { diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index a21b05e4ab..ae5b4a9e07 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,10 +12,10 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.2.0", + "jsonc-parser": "^2.2.1", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.4.12", - "vscode-languageserver": "^6.0.1", + "vscode-json-languageservice": "^3.5.1", + "vscode-languageserver": "^6.1.1", "vscode-uri": "^2.1.1" }, "devDependencies": { diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 0fd5bee766..1fa6e91420 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -450,7 +450,7 @@ connection.onDocumentRangeFormatting((formatParams, token) => { const edits = languageService.format(document, formatParams.range, formatParams.options); if (edits.length > formatterMaxNumberOfEdits) { const newText = TextDocument.applyEdits(document, edits); - return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(document.getText().length - 1)), newText)]; + return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(document.getText().length)), newText)]; } return edits; } diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 9c72cea8b0..f6b78986f8 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -61,10 +61,10 @@ https-proxy-agent@^2.2.3: agent-base "^4.3.0" debug "^3.1.0" -jsonc-parser@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" - integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== +jsonc-parser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== ms@2.0.0: version "2.0.0" @@ -80,14 +80,14 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.4.12: - version "3.4.12" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.12.tgz#e7c96a1824896a624cc7bb14f46fbf9cb7e6c5a3" - integrity sha512-+tA0KPVM1pDfORZqsQen7bY5buBpQGDTVYEobm5MoGtXNeZY2Kn0iy5wIQqXveb28LRv/I5xKE87dmNJTEaijQ== +vscode-json-languageservice@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.5.1.tgz#75779d466107cbc8c4cc9828df100df71c1870f8" + integrity sha512-F8jPqcAC1mbQOMKvGYS4dGEw9JCZxVEi7tc5ASZLfcfwKq2URZKB4fOtdy1GEsTLsrW11tVrBjEPntpXzqp/NA== dependencies: - jsonc-parser "^2.2.0" - vscode-languageserver-textdocument "^1.0.1-next.1" - vscode-languageserver-types "^3.15.0" + jsonc-parser "^2.2.1" + vscode-languageserver-textdocument "^1.0.1" + vscode-languageserver-types "^3.15.1" vscode-nls "^4.1.1" vscode-uri "^2.1.1" @@ -96,30 +96,30 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageserver-protocol@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz#7555e595f0058b9a166f14605ad039e97fab320a" - integrity sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.0" + vscode-languageserver-types "3.15.1" -vscode-languageserver-textdocument@^1.0.1-next.1: - version "1.0.1-next.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1-next.1.tgz#c8f2f792c7c88d33ea8441ca04bfb8376896b671" - integrity sha512-Cmt0KsNxouns+d7/Kw/jWtWU9Z3h56z1qAA8utjDOEqrDcrTs2rDXv3EJRa99nuKM3wVf6DbWym1VqL9q71XPA== +vscode-languageserver-textdocument@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.1.tgz#178168e87efad6171b372add1dea34f53e5d330f" + integrity sha512-UIcJDjX7IFkck7cSkNNyzIz5FyvpQfY7sdzVy+wkKN/BLaD4DQ0ppXQrKePomCxTS7RrolK1I0pey0bG9eh8dA== -vscode-languageserver-types@3.15.0, vscode-languageserver-types@^3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a" - integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw== +vscode-languageserver-types@3.15.1, vscode-languageserver-types@^3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== -vscode-languageserver@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.0.1.tgz#4f499d245f1baf83bd607dd79c4c3fd19e8cefc0" - integrity sha512-Wk4I/Dn5KNARWockdCrYuuImJz6bpYG8n2G3Kk5AU6Xy9nWNHD6YjB9/Rd99p4goViZOyETM+hYE81LnEzQZUA== +vscode-languageserver@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz#d76afc68172c27d4327ee74332b468fbc740d762" + integrity sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ== dependencies: - vscode-languageserver-protocol "^3.15.1" + vscode-languageserver-protocol "^3.15.3" vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 608f3eaed5..cbda171084 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -125,26 +125,26 @@ vscode-jsonrpc@^5.0.1: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== -vscode-languageclient@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.0.1.tgz#acd138e0a19a40c5788365e882ae11c164d9a460" - integrity sha512-7yZaSHichTJEyOJykI2RLQEECf9MqNLoklzC/1OVi/M8ioIsWQ1+lkN1nTsUhd6+F7p9ar9dNmPiEhL0i5uUBA== +vscode-languageclient@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.1.tgz#91b62e416c5abbf2013ae3726f314a19c22a8457" + integrity sha512-mB6d8Tg+82l8EFUfR+SBu0+lCshyKVgC5E5+MQ0/BJa+9AgeBjtG5npoGaCo4/VvWzK0ZRGm85zU5iRp1RYPIA== dependencies: semver "^6.3.0" - vscode-languageserver-protocol "^3.15.1" + vscode-languageserver-protocol "^3.15.3" -vscode-languageserver-protocol@^3.15.1: - version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.1.tgz#7555e595f0058b9a166f14605ad039e97fab320a" - integrity sha512-wJAo06VM9ZBnRqslplDjfz6Tdive0O7z44yNxBFA3x0/YZkXBIL6I+9rwQ/9Y//0X0eCh12FQrj+KmEXf2L5eA== +vscode-languageserver-protocol@^3.15.3: + version "3.15.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" - vscode-languageserver-types "3.15.0" + vscode-languageserver-types "3.15.1" -vscode-languageserver-types@3.15.0: - version "3.15.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0.tgz#c45a23308ec0967135c483b759dfaf97978d9e0a" - integrity sha512-AXteNagMhBWnZ6gNN0UB4HTiD/7TajgfHl6jaM6O7qz3zDJw0H3Jf83w05phihnBRCML+K6Ockh8f8bL0OObPw== +vscode-languageserver-types@3.15.1: + version "3.15.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== vscode-nls@^4.1.1: version "4.1.1" diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index 52a19ca992..632e1936ca 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -148,12 +148,16 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this.registerDynamicPreview(preview); } - public async resolveWebviewEditor( - resource: vscode.Uri, + public async provideWebviewCustomEditorDocument(resource: vscode.Uri) { + return vscode.window.createWebviewEditorCustomDocument('vscode.markdown.preview.editor', resource, undefined, {}); + } + + public async resolveWebviewCustomEditor( + document: vscode.WebviewEditorCustomDocument, webview: vscode.WebviewPanel ): Promise { const preview = DynamicMarkdownPreview.revive( - { resource, locked: false, resourceColumn: vscode.ViewColumn.One }, + { resource: document.uri, locked: false, resourceColumn: vscode.ViewColumn.One }, webview, this._contentProvider, this._previewConfigurations, diff --git a/extensions/package.json b/extensions/package.json index df820979f1..b8e304b234 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.8.1-rc" + "typescript": "3.8.2" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index da71634c2e..d99e050d30 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -17,7 +17,7 @@ "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" }, "dependencies": { - "jsonc-parser": "2.2.0" + "jsonc-parser": "2.2.1" }, "devDependencies": { "@types/node": "^12.11.7", diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock index c6b3fdb431..b8a66d65ef 100644 --- a/extensions/vscode-colorize-tests/yarn.lock +++ b/extensions/vscode-colorize-tests/yarn.lock @@ -1042,10 +1042,10 @@ json3@3.3.2: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= -jsonc-parser@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.0.tgz#f206f87f9d49d644b7502052c04e82dd6392e9ef" - integrity sha512-4fLQxW1j/5fWj6p78vAlAafoCKtuBm6ghv+Ij5W2DrDx0qE+ZdEl2c6Ko1mgJNF5ftX1iEWQQ4Ap7+3GlhjkOA== +jsonc-parser@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.2.1.tgz#db73cd59d78cce28723199466b2a03d1be1df2bc" + integrity sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w== jsonify@~0.0.0: version "0.0.0" diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 1d6447cfe6..43a70c058c 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -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== +typescript@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" + integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== diff --git a/package.json b/package.json index 9e97c0bb2a..4a180c28ee 100644 --- a/package.json +++ b/package.json @@ -175,7 +175,7 @@ "temp-write": "^3.4.0", "ts-loader": "^4.4.2", "typemoq": "^0.3.2", - "typescript": "^3.8.1-rc", + "typescript": "3.8.2", "typescript-formatter": "7.1.0", "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", diff --git a/src/main.js b/src/main.js index e5f8e9f548..36c4cb378a 100644 --- a/src/main.js +++ b/src/main.js @@ -138,8 +138,12 @@ function configureCommandlineSwitchesSync(cliArgs) { 'disable-hardware-acceleration', // provided by Electron - 'disable-color-correct-rendering' + 'disable-color-correct-rendering', + + // override for the color profile to use + 'force-color-profile' ]; + if (process.platform === 'linux') { SUPPORTED_ELECTRON_SWITCHES.push('force-renderer-accessibility'); } @@ -154,7 +158,16 @@ function configureCommandlineSwitchesSync(cliArgs) { } const argvValue = argvConfig[argvKey]; - if (argvValue === true || argvValue === 'true') { + + // Color profile + if (argvKey === 'force-color-profile') { + if (argvValue) { + app.commandLine.appendSwitch(argvKey, argvValue); + } + } + + // Others + else if (argvValue === true || argvValue === 'true') { if (argvKey === 'disable-hardware-acceleration') { app.disableHardwareAcceleration(); // needs to be called explicitly } else { diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts index 1a42f4a865..c8623a8bb7 100644 --- a/src/sql/workbench/browser/modal/optionsDialog.ts +++ b/src/sql/workbench/browser/modal/optionsDialog.ts @@ -37,6 +37,7 @@ import { attachModalDialogStyler, attachPanelStyler } from 'sql/workbench/common import { IViewDescriptorService } from 'vs/workbench/common/views'; import { ServiceOptionType } from 'sql/platform/connection/common/interfaces'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class CategoryView extends ViewPane { @@ -51,9 +52,10 @@ export class CategoryView extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService protected openerService: IOpenerService, - @IThemeService protected themeService: IThemeService + @IThemeService protected themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService, telemetryService); } // we want a fixed size, so when we render to will measure our content and set that to be our diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index dbf8a95a09..a28851d9e9 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -50,6 +50,7 @@ import { NodeContextKey } from 'sql/workbench/browser/parts/views/nodeContext'; import { UserCancelledConnectionError } from 'sql/base/common/errors'; import { firstIndex } from 'vs/base/common/arrays'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class CustomTreeViewPanel extends ViewPane { @@ -65,9 +66,10 @@ export class CustomTreeViewPanel extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService protected openerService: IOpenerService, - @IThemeService protected themeService: IThemeService + @IThemeService protected themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView as ITreeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); diff --git a/src/sql/workbench/contrib/accounts/test/browser/accountDialogController.test.ts b/src/sql/workbench/contrib/accounts/test/browser/accountDialogController.test.ts index 50ec51fc49..7424924e24 100644 --- a/src/sql/workbench/contrib/accounts/test/browser/accountDialogController.test.ts +++ b/src/sql/workbench/contrib/accounts/test/browser/accountDialogController.test.ts @@ -86,7 +86,7 @@ function createInstantiationService(addAccountFailureEmitter?: Emitter): .returns(() => undefined); // Create a mock account dialog - let accountDialog = new AccountDialog(undefined!, undefined!, instantiationService.object, undefined!, undefined!, undefined!, undefined!, new MockContextKeyService(), undefined!, undefined!, undefined!, undefined!, undefined!); + let accountDialog = new AccountDialog(undefined!, undefined!, instantiationService.object, undefined!, undefined!, undefined!, undefined!, new MockContextKeyService(), undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); let mockAccountDialog = TypeMoq.Mock.ofInstance(accountDialog); mockAccountDialog.setup(x => x.onAddAccountErrorEvent) .returns(() => { return addAccountFailureEmitter ? addAccountFailureEmitter.event : mockEvent.event; }); 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 4c18353b7a..85bc3133d4 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, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEditorService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { TestQueryModelService } from 'sql/workbench/services/query/test/common/testQueryModelService'; import { Event } from 'vs/base/common/event'; @@ -33,6 +33,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te import { isUndefinedOrNull } from 'vs/base/common/types'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; class TestParsedArgs implements ParsedArgs { [arg: string]: any; diff --git a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts index 29af49de6e..7eb21f0112 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts @@ -24,6 +24,7 @@ import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILogService } from 'vs/platform/log/common/log'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class ConnectionViewletPanel extends ViewPane { @@ -46,9 +47,10 @@ export class ConnectionViewletPanel extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService protected openerService: IOpenerService, @IThemeService protected themeService: IThemeService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService, telemetryService); this._addServerAction = this.instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL); diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index 2454dc0129..d75c87a591 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -47,6 +47,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { GridPanelState, GridTableState } from 'sql/workbench/common/editor/query/gridPanelState'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer'; +import { Progress } from 'vs/platform/progress/common/progress'; const ROW_HEIGHT = 29; const HEADER_HEIGHT = 26; @@ -580,7 +581,7 @@ export abstract class GridTableBase extends Disposable implements IView { let content = value.displayValue; const input = this.untitledEditorService.create({ mode: column.isXml ? 'xml' : 'json', initialValue: content }); - await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, input.textEditorModel, FormattingMode.Explicit, CancellationToken.None); + await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, input.textEditorModel, FormattingMode.Explicit, Progress.None, CancellationToken.None); return this.editorService.openEditor(input); }); } diff --git a/src/sql/workbench/services/accountManagement/browser/accountDialog.ts b/src/sql/workbench/services/accountManagement/browser/accountDialog.ts index f33c74279a..e19f956d3b 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountDialog.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountDialog.ts @@ -39,6 +39,7 @@ import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/vie import { attachModalDialogStyler, attachPanelStyler } from 'sql/workbench/common/styler'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; class AccountPanel extends ViewPane { public index: number; @@ -54,8 +55,9 @@ class AccountPanel extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { @@ -133,7 +135,8 @@ export class AccountDialog extends Modal { @ILogService logService: ILogService, @IViewDescriptorService private viewDescriptorService: IViewDescriptorService, @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService, - @IOpenerService protected readonly openerService: IOpenerService + @IOpenerService protected readonly openerService: IOpenerService, + @ITelemetryService private readonly vstelemetryService: ITelemetryService, ) { super( localize('linkedAccounts', "Linked accounts"), @@ -305,7 +308,8 @@ export class AccountDialog extends Modal { this.contextKeyService, this._instantiationService, this.viewDescriptorService, - this.openerService + this.openerService, + this.vstelemetryService ); attachPanelStyler(providerView, this._themeService); diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts index d055417808..5bd76afcd5 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts @@ -46,6 +46,7 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie import { attachPanelStyler, attachModalDialogStyler } from 'sql/workbench/common/styler'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const labelDisplay = nls.localize("insights.item", "Item"); const valueDisplay = nls.localize("insights.value", "Value"); @@ -70,8 +71,9 @@ class InsightTableView extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, opener, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 1aa143a93c..a64ad4df57 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -283,3 +283,20 @@ export function isRootOrDriveLetter(path: string): boolean { return pathNormalized === posix.sep; } + +export function indexOfPath(path: string, candidate: string, ignoreCase: boolean): number { + if (candidate.length > path.length) { + return -1; + } + + if (path === candidate) { + return 0; + } + + if (ignoreCase) { + path = path.toLowerCase(); + candidate = candidate.toLowerCase(); + } + + return path.indexOf(candidate); +} diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 24f4747bd4..ebb26d44a2 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { URI, UriComponents } from 'vs/base/common/uri'; + const _typeof = { number: 'number', string: 'string', @@ -262,3 +264,21 @@ export type AddFirstParameterToFunctions = { [K in keyof T]: T[K] extends URI + ? UriComponents + : UriDto }; + +/** + * Mapped-type that replaces all occurrences of URI with UriComponents and + * drops all functions. + * todo@joh use toJSON-results + */ +export type Dto = { [K in keyof T]: T[K] extends URI + ? UriComponents + : T[K] extends Function + ? never + : UriDto }; diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 27169f6f7f..15932a96c9 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -575,10 +575,6 @@ class QuickPick extends QuickInput implements IQuickPi return this.visible ? this.ui.inputBox.hasFocus() : false; } - public focusOnInput() { - this.ui.inputBox.setFocus(); - } - onDidChangeSelection = this.onDidChangeSelectionEmitter.event; onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts index 7228ca0649..e0abaa442e 100644 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ b/src/vs/base/parts/quickinput/common/quickInput.ts @@ -209,8 +209,6 @@ export interface IQuickPick extends IQuickInput { validationMessage: string | undefined; inputHasFocus(): boolean; - - focusOnInput(): void; } export interface IInputBox extends IQuickInput { diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index a5d9da6a22..2b5be267ff 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -24,7 +24,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; const CORE_WEIGHT = KeybindingWeight.EditorCore; @@ -1530,6 +1530,26 @@ export namespace CoreNavigationCommands { }); } +const columnSelectionCondition = ContextKeyExpr.and( + EditorContextKeys.textInputFocus, + EditorContextKeys.columnSelection +); +function registerColumnSelection(id: string, keybinding: number): void { + KeybindingsRegistry.registerKeybindingRule({ + id: id, + primary: keybinding, + when: columnSelectionCondition, + weight: CORE_WEIGHT + 1 + }); +} + +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectLeft.id, KeyMod.Shift | KeyCode.LeftArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectRight.id, KeyMod.Shift | KeyCode.RightArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectUp.id, KeyMod.Shift | KeyCode.UpArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectPageUp.id, KeyMod.Shift | KeyCode.PageUp); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectDown.id, KeyMod.Shift | KeyCode.DownArrow); +registerColumnSelection(CoreNavigationCommands.CursorColumnSelectPageDown.id, KeyMod.Shift | KeyCode.PageDown); + /** * A command that will: * 1. invoke a command on the focused editor. diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index b501b22230..244da5f662 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -352,6 +352,7 @@ class MouseDownOperation extends Disposable { if (!options.get(EditorOption.readOnly) && options.get(EditorOption.dragAndDrop) + && !options.get(EditorOption.columnSelection) && !this._mouseState.altKey // we don't support multiple mouse && e.detail < 2 // only single click on a selection can work && !this._isActive // the mouse is not down yet diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index e6f7f4ca0e..8600c09421 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -133,7 +133,9 @@ export class ViewController { } public dispatchMouse(data: IMouseDispatchData): void { - const selectionClipboardIsOn = (platform.isLinux && this.configuration.options.get(EditorOption.selectionClipboard)); + const options = this.configuration.options; + const selectionClipboardIsOn = (platform.isLinux && options.get(EditorOption.selectionClipboard)); + const columnSelection = options.get(EditorOption.columnSelection); if (data.middleButton && !selectionClipboardIsOn) { this._columnSelect(data.position, data.mouseColumn, data.inSelectionMode); } else if (data.startedOnLineNumbers) { @@ -196,7 +198,11 @@ export class ViewController { if (data.altKey) { this._columnSelect(data.position, data.mouseColumn, true); } else { - this._moveToSelect(data.position); + if (columnSelection) { + this._columnSelect(data.position, data.mouseColumn, true); + } else { + this._moveToSelect(data.position); + } } } else { this.moveTo(data.position); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index fc1df3a2f1..2e5782a7c0 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1666,6 +1666,7 @@ class EditorContextKeysManager extends Disposable { private readonly _editorTextFocus: IContextKey; private readonly _editorTabMovesFocus: IContextKey; private readonly _editorReadonly: IContextKey; + private readonly _editorColumnSelection: IContextKey; private readonly _hasMultipleSelections: IContextKey; private readonly _hasNonEmptySelection: IContextKey; private readonly _canUndo: IContextKey; @@ -1687,6 +1688,7 @@ class EditorContextKeysManager extends Disposable { this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService); this._editorTabMovesFocus = EditorContextKeys.tabMovesFocus.bindTo(contextKeyService); this._editorReadonly = EditorContextKeys.readOnly.bindTo(contextKeyService); + this._editorColumnSelection = EditorContextKeys.columnSelection.bindTo(contextKeyService); this._hasMultipleSelections = EditorContextKeys.hasMultipleSelections.bindTo(contextKeyService); this._hasNonEmptySelection = EditorContextKeys.hasNonEmptySelection.bindTo(contextKeyService); this._canUndo = EditorContextKeys.canUndo.bindTo(contextKeyService); @@ -1714,6 +1716,7 @@ class EditorContextKeysManager extends Disposable { this._editorTabMovesFocus.set(options.get(EditorOption.tabFocusMode)); this._editorReadonly.set(options.get(EditorOption.readOnly)); + this._editorColumnSelection.set(options.get(EditorOption.columnSelection)); } private _updateFromSelection(): void { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index ca5f4e7293..e73889de35 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -324,6 +324,11 @@ export interface IEditorOptions { * Defaults to true. */ scrollPredominantAxis?: boolean; + /** + * Enable that the selection with the mouse and keys is doing column selection. + * Defaults to false. + */ + columnSelection?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -3302,6 +3307,7 @@ export const enum EditorOption { autoSurround, codeLens, colorDecorators, + columnSelection, comments, contextmenu, copyWithSyntaxHighlighting, @@ -3526,6 +3532,10 @@ export const EditorOptions = { EditorOption.colorDecorators, 'colorDecorators', true, { description: nls.localize('colorDecorators', "Controls whether the editor should render the inline color decorators and color picker.") } )), + columnSelection: register(new EditorBooleanOption( + EditorOption.columnSelection, 'columnSelection', false, + { description: nls.localize('columnSelection', "Enable that the selection with the mouse and keys is doing column selection.") } + )), comments: register(new EditorComments()), contextmenu: register(new EditorBooleanOption( EditorOption.contextmenu, 'contextmenu', true, diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 4f9fccb873..c70afc3272 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -23,6 +23,7 @@ export namespace EditorContextKeys { export const textInputFocus = new RawContextKey('textInputFocus', false); export const readOnly = new RawContextKey('editorReadonly', false); + export const columnSelection = new RawContextKey('editorColumnSelection', false); export const writable: ContextKeyExpr = readOnly.toNegated(); export const hasNonEmptySelection = new RawContextKey('editorHasSelection', false); export const hasOnlyEmptySelection: ContextKeyExpr = hasNonEmptySelection.toNegated(); diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index d5230f5ed5..bcee301f41 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1077,7 +1077,7 @@ export interface ITextModel { * @param cursorStateComputer A callback that can compute the resulting cursors state after the edit operations have been executed. * @return The cursor state returned by the `cursorStateComputer`. */ - pushEditOperations(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; + pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; /** * Change the end of line sequence. This is the preferred way of @@ -1093,11 +1093,6 @@ export interface ITextModel { */ applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[]; - /** - * @internal - */ - _applyEdits(edits: IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[]; - /** * Change the end of line sequence without recording in the undo stack. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way. @@ -1107,7 +1102,7 @@ export interface ITextModel { /** * @internal */ - _setEOL(eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void; + _applyUndoRedoEdits(edits: IValidEditOperations[], eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[]; /** * Undo edit operations until the first previous stop point created by `pushStackElement`. diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index 23af15498a..fa076bb026 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -8,41 +8,50 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Selection } from 'vs/editor/common/core/selection'; import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel, IValidEditOperations } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { IUndoRedoService, IUndoRedoElement, IUndoRedoContext } from 'vs/platform/undoRedo/common/undoRedo'; +import { IUndoRedoService, IResourceUndoRedoElement, UndoRedoElementType, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; +import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; -class EditStackElement implements IUndoRedoElement { +export class EditStackElement implements IResourceUndoRedoElement { + public readonly type = UndoRedoElementType.Resource; public readonly label: string; private _isOpen: boolean; - private readonly _model: TextModel; + public readonly model: ITextModel; private readonly _beforeVersionId: number; - private readonly _beforeCursorState: Selection[]; + private readonly _beforeEOL: EndOfLineSequence; + private readonly _beforeCursorState: Selection[] | null; private _afterVersionId: number; + private _afterEOL: EndOfLineSequence; private _afterCursorState: Selection[] | null; private _edits: IValidEditOperations[]; - public get resources(): readonly URI[] { - return [this._model.uri]; + public get resource(): URI { + return this.model.uri; } - constructor(model: TextModel, beforeVersionId: number, beforeCursorState: Selection[], afterVersionId: number, afterCursorState: Selection[] | null, operations: IValidEditOperation[]) { + constructor(model: ITextModel, beforeCursorState: Selection[] | null) { this.label = nls.localize('edit', "Typing"); this._isOpen = true; - this._model = model; - this._beforeVersionId = beforeVersionId; + this.model = model; + this._beforeVersionId = this.model.getAlternativeVersionId(); + this._beforeEOL = getModelEOL(this.model); this._beforeCursorState = beforeCursorState; - this._afterVersionId = afterVersionId; - this._afterCursorState = afterCursorState; - this._edits = [{ operations: operations }]; + this._afterVersionId = this._beforeVersionId; + this._afterEOL = this._beforeEOL; + this._afterCursorState = this._beforeCursorState; + this._edits = []; } - public isOpen(): boolean { - return this._isOpen; + public canAppend(model: ITextModel): boolean { + return (this._isOpen && this.model === model); } - public append(operations: IValidEditOperation[], afterVersionId: number, afterCursorState: Selection[] | null): void { - this._edits.push({ operations: operations }); + public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + if (operations.length > 0) { + this._edits.push({ operations: operations }); + } + this._afterEOL = afterEOL; this._afterVersionId = afterVersionId; this._afterCursorState = afterCursorState; } @@ -51,20 +60,83 @@ class EditStackElement implements IUndoRedoElement { this._isOpen = false; } - undo(ctx: IUndoRedoContext): void { + public undo(): void { this._isOpen = false; this._edits.reverse(); - this._edits = this._model._applyEdits(this._edits, true, false, this._beforeVersionId, this._beforeCursorState); + this._edits = this.model._applyUndoRedoEdits(this._edits, this._beforeEOL, true, false, this._beforeVersionId, this._beforeCursorState); } - redo(ctx: IUndoRedoContext): void { - this._isOpen = false; + public redo(): void { this._edits.reverse(); - this._edits = this._model._applyEdits(this._edits, false, true, this._afterVersionId, this._afterCursorState); + this._edits = this.model._applyUndoRedoEdits(this._edits, this._afterEOL, false, true, this._afterVersionId, this._afterCursorState); + } +} + +export class MultiModelEditStackElement implements IWorkspaceUndoRedoElement { + + public readonly type = UndoRedoElementType.Workspace; + public readonly label: string; + private _isOpen: boolean; + + private readonly _editStackElementsArr: EditStackElement[]; + private readonly _editStackElementsMap: Map; + + public get resources(): readonly URI[] { + return this._editStackElementsArr.map(editStackElement => editStackElement.model.uri); } - invalidate(resource: URI): void { - // nothing to do + constructor( + label: string, + editStackElements: EditStackElement[] + ) { + this.label = label; + this._isOpen = true; + this._editStackElementsArr = editStackElements.slice(0); + this._editStackElementsMap = new Map(); + for (const editStackElement of this._editStackElementsArr) { + const key = uriGetComparisonKey(editStackElement.model.uri); + this._editStackElementsMap.set(key, editStackElement); + } + } + + public canAppend(model: ITextModel): boolean { + if (!this._isOpen) { + return false; + } + const key = uriGetComparisonKey(model.uri); + if (this._editStackElementsMap.has(key)) { + const editStackElement = this._editStackElementsMap.get(key)!; + return editStackElement.canAppend(model); + } + return false; + } + + public append(model: ITextModel, operations: IValidEditOperation[], afterEOL: EndOfLineSequence, afterVersionId: number, afterCursorState: Selection[] | null): void { + const key = uriGetComparisonKey(model.uri); + const editStackElement = this._editStackElementsMap.get(key)!; + editStackElement.append(model, operations, afterEOL, afterVersionId, afterCursorState); + } + + public close(): void { + this._isOpen = false; + } + + public undo(): void { + this._isOpen = false; + + for (const editStackElement of this._editStackElementsArr) { + editStackElement.undo(); + } + } + + public redo(): void { + for (const editStackElement of this._editStackElementsArr) { + editStackElement.redo(); + } + } + + public split(): IResourceUndoRedoElement[] { + return this._editStackElementsArr; } } @@ -77,46 +149,11 @@ function getModelEOL(model: ITextModel): EndOfLineSequence { } } -class EOLStackElement implements IUndoRedoElement { - - public readonly label: string; - private readonly _model: TextModel; - private readonly _beforeVersionId: number; - private readonly _afterVersionId: number; - private _eol: EndOfLineSequence; - - public get resources(): readonly URI[] { - return [this._model.uri]; +function isKnownStackElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null): element is EditStackElement | MultiModelEditStackElement { + if (!element) { + return false; } - - constructor(model: TextModel, beforeVersionId: number, afterVersionId: number, eol: EndOfLineSequence) { - this.label = nls.localize('eol', "Change End Of Line Sequence"); - this._model = model; - this._beforeVersionId = beforeVersionId; - this._afterVersionId = afterVersionId; - this._eol = eol; - } - - undo(ctx: IUndoRedoContext): void { - const redoEOL = getModelEOL(this._model); - this._model._setEOL(this._eol, true, false, this._beforeVersionId, null); - this._eol = redoEOL; - } - - redo(ctx: IUndoRedoContext): void { - const undoEOL = getModelEOL(this._model); - this._model._setEOL(this._eol, false, true, this._afterVersionId, null); - this._eol = undoEOL; - } - - invalidate(resource: URI): void { - // nothing to do - } -} - -export interface IUndoRedoResult { - selections: Selection[] | null; - recordedVersionId: number; + return ((element instanceof EditStackElement) || (element instanceof MultiModelEditStackElement)); } export class EditStack { @@ -131,7 +168,7 @@ export class EditStack { public pushStackElement(): void { const lastElement = this._undoRedoService.getLastElement(this._model.uri); - if (lastElement && lastElement instanceof EditStackElement) { + if (isKnownStackElement(lastElement)) { lastElement.close(); } } @@ -140,32 +177,27 @@ export class EditStack { this._undoRedoService.removeElements(this._model.uri); } - public pushEOL(eol: EndOfLineSequence): void { - const beforeVersionId = this._model.getAlternativeVersionId(); - const inverseEOL = getModelEOL(this._model); - this._model.setEOL(eol); - const afterVersionId = this._model.getAlternativeVersionId(); - + private _getOrCreateEditStackElement(beforeCursorState: Selection[] | null): EditStackElement | MultiModelEditStackElement { const lastElement = this._undoRedoService.getLastElement(this._model.uri); - if (lastElement && lastElement instanceof EditStackElement) { - lastElement.close(); + if (isKnownStackElement(lastElement) && lastElement.canAppend(this._model)) { + return lastElement; } - this._undoRedoService.pushElement(new EOLStackElement(this._model, inverseEOL, beforeVersionId, afterVersionId)); + const newElement = new EditStackElement(this._model, beforeCursorState); + this._undoRedoService.pushElement(newElement); + return newElement; } - public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { - const beforeVersionId = this._model.getAlternativeVersionId(); + public pushEOL(eol: EndOfLineSequence): void { + const editStackElement = this._getOrCreateEditStackElement(null); + this._model.setEOL(eol); + editStackElement.append(this._model, [], getModelEOL(this._model), this._model.getAlternativeVersionId(), null); + } + + public pushEditOperation(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { + const editStackElement = this._getOrCreateEditStackElement(beforeCursorState); const inverseEditOperations = this._model.applyEdits(editOperations); - const afterVersionId = this._model.getAlternativeVersionId(); const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations); - - const lastElement = this._undoRedoService.getLastElement(this._model.uri); - if (lastElement && lastElement instanceof EditStackElement && lastElement.isOpen()) { - lastElement.append(inverseEditOperations, afterVersionId, afterCursorState); - } else { - this._undoRedoService.pushElement(new EditStackElement(this._model, beforeVersionId, beforeCursorState, afterVersionId, afterCursorState, inverseEditOperations)); - } - + editStackElement.append(this._model, inverseEditOperations, getModelEOL(this._model), this._model.getAlternativeVersionId(), afterCursorState); return afterCursorState; } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index b8fdb912a9..0407f8d341 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -37,7 +37,6 @@ import { Color } from 'vs/base/common/color'; import { Constants } from 'vs/base/common/uint'; import { EditorTheme } from 'vs/editor/common/view/viewContext'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; -import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -189,10 +188,6 @@ export class TextModel extends Disposable implements model.ITextModel { largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations, }; - public static createFromString(text: string, options: model.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel { - return new TextModel(text, options, languageIdentifier, uri, new UndoRedoService()); - } - public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions { if (options.detectIndentation) { const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); @@ -494,21 +489,6 @@ export class TextModel extends Disposable implements model.ITextModel { ); } - _setEOL(eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void { - try { - this._onDidChangeDecorations.beginDeferredEmit(); - this._eventEmitter.beginDeferredEmit(); - this._isUndoing = isUndoing; - this._isRedoing = isRedoing; - this.setEOL(eol); - this._overwriteAlternativeVersionId(resultingAlternativeVersionId); - } finally { - this._isUndoing = false; - this._eventEmitter.endDeferredEmit(resultingSelection); - this._onDidChangeDecorations.endDeferredEmit(); - } - } - private _onBeforeEOLChange(): void { // Ensure all decorations get their `range` set. const versionId = this.getVersionId(); @@ -1202,7 +1182,7 @@ export class TextModel extends Disposable implements model.ITextModel { return result; } - public pushEditOperations(beforeCursorState: Selection[], editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + public pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.IIdentifiedSingleEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); @@ -1213,7 +1193,7 @@ export class TextModel extends Disposable implements model.ITextModel { } } - private _pushEditOperations(beforeCursorState: Selection[], editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { + private _pushEditOperations(beforeCursorState: Selection[] | null, editOperations: model.ValidAnnotatedEditOperation[], cursorStateComputer: model.ICursorStateComputer | null): Selection[] | null { if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { // Go through each saved line number and insert a trim whitespace edit // if it is safe to do so (no conflicts with other edits). @@ -1228,22 +1208,24 @@ export class TextModel extends Disposable implements model.ITextModel { // Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor // We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace let editsAreNearCursors = true; - for (let i = 0, len = beforeCursorState.length; i < len; i++) { - let sel = beforeCursorState[i]; - let foundEditNearSel = false; - for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { - let editRange = incomingEdits[j].range; - let selIsAbove = editRange.startLineNumber > sel.endLineNumber; - let selIsBelow = sel.startLineNumber > editRange.endLineNumber; - if (!selIsAbove && !selIsBelow) { - foundEditNearSel = true; + if (beforeCursorState) { + for (let i = 0, len = beforeCursorState.length; i < len; i++) { + let sel = beforeCursorState[i]; + let foundEditNearSel = false; + for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { + let editRange = incomingEdits[j].range; + let selIsAbove = editRange.startLineNumber > sel.endLineNumber; + let selIsBelow = sel.startLineNumber > editRange.endLineNumber; + if (!selIsAbove && !selIsBelow) { + foundEditNearSel = true; + break; + } + } + if (!foundEditNearSel) { + editsAreNearCursors = false; break; } } - if (!foundEditNearSel) { - editsAreNearCursors = false; - break; - } } if (editsAreNearCursors) { @@ -1298,7 +1280,7 @@ export class TextModel extends Disposable implements model.ITextModel { return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); } - _applyEdits(edits: model.IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] { + _applyUndoRedoEdits(edits: model.IValidEditOperations[], eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] { try { this._onDidChangeDecorations.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit(); @@ -1308,10 +1290,12 @@ export class TextModel extends Disposable implements model.ITextModel { for (let i = 0, len = edits.length; i < len; i++) { reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) }; } + this.setEOL(eol); this._overwriteAlternativeVersionId(resultingAlternativeVersionId); return reverseEdits; } finally { this._isUndoing = false; + this._isRedoing = false; this._eventEmitter.endDeferredEmit(resultingSelection); this._onDidChangeDecorations.endDeferredEmit(); } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 2e50a75ebe..d95d64b0d7 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -635,6 +635,9 @@ export interface CodeActionList extends IDisposable { * @internal */ export interface CodeActionProvider { + + displayName?: string + /** * Provide commands for the given document and range. */ diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index b0669a3810..37c0ae3f98 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -178,105 +178,106 @@ export enum EditorOption { autoSurround = 10, codeLens = 11, colorDecorators = 12, - comments = 13, - contextmenu = 14, - copyWithSyntaxHighlighting = 15, - cursorBlinking = 16, - cursorSmoothCaretAnimation = 17, - cursorStyle = 18, - cursorSurroundingLines = 19, - cursorSurroundingLinesStyle = 20, - cursorWidth = 21, - disableLayerHinting = 22, - disableMonospaceOptimizations = 23, - dragAndDrop = 24, - emptySelectionClipboard = 25, - extraEditorClassName = 26, - fastScrollSensitivity = 27, - find = 28, - fixedOverflowWidgets = 29, - folding = 30, - foldingStrategy = 31, - foldingHighlight = 32, - fontFamily = 33, - fontInfo = 34, - fontLigatures = 35, - fontSize = 36, - fontWeight = 37, - formatOnPaste = 38, - formatOnType = 39, - glyphMargin = 40, - gotoLocation = 41, - hideCursorInOverviewRuler = 42, - highlightActiveIndentGuide = 43, - hover = 44, - inDiffEditor = 45, - letterSpacing = 46, - lightbulb = 47, - lineDecorationsWidth = 48, - lineHeight = 49, - lineNumbers = 50, - lineNumbersMinChars = 51, - links = 52, - matchBrackets = 53, - minimap = 54, - mouseStyle = 55, - mouseWheelScrollSensitivity = 56, - mouseWheelZoom = 57, - multiCursorMergeOverlapping = 58, - multiCursorModifier = 59, - multiCursorPaste = 60, - occurrencesHighlight = 61, - overviewRulerBorder = 62, - overviewRulerLanes = 63, - padding = 64, - parameterHints = 65, - peekWidgetDefaultFocus = 66, - definitionLinkOpensInPeek = 67, - quickSuggestions = 68, - quickSuggestionsDelay = 69, - readOnly = 70, - renderControlCharacters = 71, - renderIndentGuides = 72, - renderFinalNewline = 73, - renderLineHighlight = 74, - renderValidationDecorations = 75, - renderWhitespace = 76, - revealHorizontalRightPadding = 77, - roundedSelection = 78, - rulers = 79, - scrollbar = 80, - scrollBeyondLastColumn = 81, - scrollBeyondLastLine = 82, - scrollPredominantAxis = 83, - selectionClipboard = 84, - selectionHighlight = 85, - selectOnLineNumbers = 86, - showFoldingControls = 87, - showUnused = 88, - snippetSuggestions = 89, - smoothScrolling = 90, - stopRenderingLineAfter = 91, - suggest = 92, - suggestFontSize = 93, - suggestLineHeight = 94, - suggestOnTriggerCharacters = 95, - suggestSelection = 96, - tabCompletion = 97, - useTabStops = 98, - wordSeparators = 99, - wordWrap = 100, - wordWrapBreakAfterCharacters = 101, - wordWrapBreakBeforeCharacters = 102, - wordWrapColumn = 103, - wordWrapMinified = 104, - wrappingIndent = 105, - wrappingStrategy = 106, - editorClassName = 107, - pixelRatio = 108, - tabFocusMode = 109, - layoutInfo = 110, - wrappingInfo = 111 + columnSelection = 13, + comments = 14, + contextmenu = 15, + copyWithSyntaxHighlighting = 16, + cursorBlinking = 17, + cursorSmoothCaretAnimation = 18, + cursorStyle = 19, + cursorSurroundingLines = 20, + cursorSurroundingLinesStyle = 21, + cursorWidth = 22, + disableLayerHinting = 23, + disableMonospaceOptimizations = 24, + dragAndDrop = 25, + emptySelectionClipboard = 26, + extraEditorClassName = 27, + fastScrollSensitivity = 28, + find = 29, + fixedOverflowWidgets = 30, + folding = 31, + foldingStrategy = 32, + foldingHighlight = 33, + fontFamily = 34, + fontInfo = 35, + fontLigatures = 36, + fontSize = 37, + fontWeight = 38, + formatOnPaste = 39, + formatOnType = 40, + glyphMargin = 41, + gotoLocation = 42, + hideCursorInOverviewRuler = 43, + highlightActiveIndentGuide = 44, + hover = 45, + inDiffEditor = 46, + letterSpacing = 47, + lightbulb = 48, + lineDecorationsWidth = 49, + lineHeight = 50, + lineNumbers = 51, + lineNumbersMinChars = 52, + links = 53, + matchBrackets = 54, + minimap = 55, + mouseStyle = 56, + mouseWheelScrollSensitivity = 57, + mouseWheelZoom = 58, + multiCursorMergeOverlapping = 59, + multiCursorModifier = 60, + multiCursorPaste = 61, + occurrencesHighlight = 62, + overviewRulerBorder = 63, + overviewRulerLanes = 64, + padding = 65, + parameterHints = 66, + peekWidgetDefaultFocus = 67, + definitionLinkOpensInPeek = 68, + quickSuggestions = 69, + quickSuggestionsDelay = 70, + readOnly = 71, + renderControlCharacters = 72, + renderIndentGuides = 73, + renderFinalNewline = 74, + renderLineHighlight = 75, + renderValidationDecorations = 76, + renderWhitespace = 77, + revealHorizontalRightPadding = 78, + roundedSelection = 79, + rulers = 80, + scrollbar = 81, + scrollBeyondLastColumn = 82, + scrollBeyondLastLine = 83, + scrollPredominantAxis = 84, + selectionClipboard = 85, + selectionHighlight = 86, + selectOnLineNumbers = 87, + showFoldingControls = 88, + showUnused = 89, + snippetSuggestions = 90, + smoothScrolling = 91, + stopRenderingLineAfter = 92, + suggest = 93, + suggestFontSize = 94, + suggestLineHeight = 95, + suggestOnTriggerCharacters = 96, + suggestSelection = 97, + tabCompletion = 98, + useTabStops = 99, + wordSeparators = 100, + wordWrap = 101, + wordWrapBreakAfterCharacters = 102, + wordWrapBreakBeforeCharacters = 103, + wordWrapColumn = 104, + wordWrapMinified = 105, + wrappingIndent = 106, + wrappingStrategy = 107, + editorClassName = 108, + pixelRatio = 109, + tabFocusMode = 110, + layoutInfo = 111, + wrappingInfo = 112 } /** diff --git a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts index 4a5152ad04..188058756e 100644 --- a/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts +++ b/src/vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { BracketMatchingController } from 'vs/editor/contrib/bracketMatching/bracketMatching'; @@ -31,7 +31,7 @@ suite('bracket matching', () => { test('issue #183: jump to matching bracket position', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier()); + let model = createTextModel('var x = (3 + (5-7)) + ((5+3)+5);', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); @@ -63,7 +63,7 @@ suite('bracket matching', () => { test('Jump to next bracket', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); + let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); @@ -100,7 +100,7 @@ suite('bracket matching', () => { test('Select to next bracket', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); + let model = createTextModel('var x = (3 + (5-7)); y();', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); @@ -152,7 +152,7 @@ suite('bracket matching', () => { '};', ].join('\n'); const mode = new BracketMode(); - const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier()); + const model = createTextModel(text, undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); @@ -177,7 +177,7 @@ suite('bracket matching', () => { '};', ].join('\n'); const mode = new BracketMode(); - const model = TextModel.createFromString(text, undefined, mode.getLanguageIdentifier()); + const model = createTextModel(text, undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { const bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); @@ -195,7 +195,7 @@ suite('bracket matching', () => { test('issue #45369: Select to Bracket with multicursor', () => { let mode = new BracketMode(); - let model = TextModel.createFromString('{ } { } { }', undefined, mode.getLanguageIdentifier()); + let model = createTextModel('{ } { } { }', undefined, mode.getLanguageIdentifier()); withTestCodeEditor(null, { model: model }, (editor, cursor) => { let bracketMatchingController = editor.registerAndInstantiateContribution(BracketMatchingController.ID, BracketMatchingController); diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index d4aa4d0fd1..81a4e18aed 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -16,6 +16,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './types'; +import { IProgress, Progress } from 'vs/platform/progress/common/progress'; export const codeActionCommandId = 'editor.action.codeAction'; export const refactorCommandId = 'editor.action.refactor'; @@ -70,7 +71,8 @@ export function getCodeActions( model: ITextModel, rangeOrSelection: Range | Selection, trigger: CodeActionTrigger, - token: CancellationToken + progress: IProgress, + token: CancellationToken, ): Promise { const filter = trigger.filter || {}; @@ -85,6 +87,7 @@ export function getCodeActions( const disposables = new DisposableStore(); const promises = providers.map(async provider => { try { + progress.report(provider); const providedCodeActions = await provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token); if (providedCodeActions) { disposables.add(providedCodeActions); @@ -210,6 +213,7 @@ registerLanguageCommand('_executeCodeActionProvider', async function (accessor, model, validatedRangeOrSelection, { type: modes.CodeActionTriggerType.Manual, filter: { includeSourceActions: true, include: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + Progress.None, CancellationToken.None); setTimeout(() => codeActionSet.dispose(), 100); diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 705a4944a6..40cb996b40 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -14,7 +14,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { CodeActionProviderRegistry, CodeActionTriggerType } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IEditorProgressService } from 'vs/platform/progress/common/progress'; +import { IEditorProgressService, Progress } from 'vs/platform/progress/common/progress'; import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './types'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -213,7 +213,7 @@ export class CodeActionModel extends Disposable { return; } - const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, token)); + const actions = createCancelablePromise(token => getCodeActions(model, trigger.selection, trigger.trigger, Progress.None, token)); if (this._progressService && trigger.trigger.type === CodeActionTriggerType.Manual) { this._progressService.showWhile(actions, 250); } diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 46a5f1c614..d1f18fd77a 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -12,6 +12,8 @@ import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/types'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Progress } from 'vs/platform/progress/common/progress'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; function staticCodeActionProvider(...actions: modes.CodeAction[]): modes.CodeActionProvider { return new class implements modes.CodeActionProvider { @@ -92,7 +94,7 @@ suite('CodeAction', () => { setup(function () { disposables.clear(); - model = TextModel.createFromString('test1\ntest2\ntest3', undefined, langId, uri); + model = createTextModel('test1\ntest2\ntest3', undefined, langId, uri); disposables.add(model); }); @@ -125,7 +127,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -140,20 +142,20 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a.b.c') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -172,7 +174,7 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: new CodeActionKind('a') } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -186,13 +188,13 @@ suite('CodeAction', () => { disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider)); { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: modes.CodeActionTriggerType.Auto, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } @@ -214,7 +216,7 @@ suite('CodeAction', () => { excludes: [CodeActionKind.Source], includeSourceActions: true, } - }, CancellationToken.None); + }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } @@ -250,7 +252,7 @@ suite('CodeAction', () => { include: baseType, excludes: [subType], } - }, CancellationToken.None); + }, Progress.None, CancellationToken.None); assert.strictEqual(didInvoke, false); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); @@ -275,7 +277,7 @@ suite('CodeAction', () => { filter: { include: CodeActionKind.QuickFix } - }, CancellationToken.None); + }, Progress.None, CancellationToken.None); assert.strictEqual(actions.length, 0); assert.strictEqual(wasInvoked, false); }); diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index 38f6fd9c82..2341e90071 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -15,6 +15,7 @@ import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; const testProvider = { provideCodeActions(): modes.CodeActionList { @@ -38,7 +39,7 @@ suite('CodeActionModel', () => { setup(() => { disposables.clear(); markerService = new MarkerService(); - model = TextModel.createFromString('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri); + model = createTextModel('foobar foo bar\nfarboo far boo', undefined, languageIdentifier, uri); editor = createTestCodeEditor({ model: model }); editor.setPosition({ lineNumber: 1, column: 1 }); }); diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 7e86fa07bc..f7fc0f3f26 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -68,7 +68,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu } private onEditorKeyDown(e: IKeyboardEvent): void { - if (!this._editor.getOption(EditorOption.dragAndDrop)) { + if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) { return; } @@ -84,7 +84,7 @@ export class DragAndDropController extends Disposable implements IEditorContribu } private onEditorKeyUp(e: IKeyboardEvent): void { - if (!this._editor.getOption(EditorOption.dragAndDrop)) { + if (!this._editor.getOption(EditorOption.dragAndDrop) || this._editor.getOption(EditorOption.columnSelection)) { return; } diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index 05503e1042..75654befd5 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -8,7 +8,7 @@ import { OutlineElement, OutlineGroup, OutlineModel } from '../outlineModel'; import { SymbolKind, DocumentSymbol, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { Range } from 'vs/editor/common/core/range'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { URI } from 'vs/base/common/uri'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -16,7 +16,7 @@ suite('OutlineModel', function () { test('OutlineModel#create, cached', async function () { - let model = TextModel.createFromString('foo', undefined, undefined, URI.file('/fome/path.foo')); + let model = createTextModel('foo', undefined, undefined, URI.file('/fome/path.foo')); let count = 0; let reg = DocumentSymbolProviderRegistry.register({ pattern: '**/path.foo' }, { provideDocumentSymbols() { @@ -42,7 +42,7 @@ suite('OutlineModel', function () { test('OutlineModel#create, cached/cancel', async function () { - let model = TextModel.createFromString('foo', undefined, undefined, URI.file('/fome/path.foo')); + let model = createTextModel('foo', undefined, undefined, URI.file('/fome/path.foo')); let isCancelled = false; let reg = DocumentSymbolProviderRegistry.register({ pattern: '**/path.foo' }, { diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index 9c13307f92..7eb4d94f95 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -16,6 +16,8 @@ import { FindModelBoundToEditorModel } from 'vs/editor/contrib/find/findModel'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; suite('FindModel', () => { @@ -45,7 +47,7 @@ suite('FindModel', () => { const factory = ptBuilder.finish(); withTestCodeEditor([], { - model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null, new UndoRedoService()) + model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null, new UndoRedoService(new TestDialogService(), new TestNotificationService())) }, (editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor) ); diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index cfcaf71125..6786a9c18c 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { FoldingModel, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines, setCollapseStateUp } from 'vs/editor/contrib/folding/foldingModel'; -import { TextModel, ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { TrackedRangeStickiness, IModelDeltaDecoration, ITextModel, IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; import { EditOperation } from 'vs/editor/common/core/editOperation'; @@ -92,7 +93,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -131,7 +132,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -177,7 +178,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -217,7 +218,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -254,7 +255,7 @@ suite('Folding Model', () => { /* 7*/ ' }', /* 8*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -295,7 +296,7 @@ suite('Folding Model', () => { /* 11*/ ' }', /* 12*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -346,7 +347,7 @@ suite('Folding Model', () => { /* 10*/ '//#endregion', /* 11*/ '']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -392,7 +393,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -448,7 +449,7 @@ suite('Folding Model', () => { /* 15*/ ' //#endregion', /* 16*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -504,7 +505,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -556,7 +557,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -603,7 +604,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); @@ -648,7 +649,7 @@ suite('Folding Model', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); diff --git a/src/vs/editor/contrib/folding/test/foldingRanges.test.ts b/src/vs/editor/contrib/folding/test/foldingRanges.test.ts index 455553cd92..93dd786f91 100644 --- a/src/vs/editor/contrib/folding/test/foldingRanges.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingRanges.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; import { MAX_FOLDING_REGIONS } from 'vs/editor/contrib/folding/foldingRanges'; @@ -26,7 +26,7 @@ suite('FoldingRanges', () => { for (let i = 0; i < nRegions; i++) { lines.push('#endregion'); } - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, false, markers, MAX_FOLDING_REGIONS); assert.equal(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { @@ -53,7 +53,7 @@ suite('FoldingRanges', () => { /* 12*/ ' }', /* 13*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); try { let actual = computeRanges(textModel, false, markers); // let r0 = r(1, 2); @@ -91,7 +91,7 @@ suite('FoldingRanges', () => { for (let i = 0; i < nRegions; i++) { lines.push('#endregion'); } - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, false, markers, MAX_FOLDING_REGIONS); assert.equal(actual.length, nRegions, 'len'); for (let i = 0; i < nRegions; i++) { diff --git a/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts b/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts index e2548fd165..6774bf6aa1 100644 --- a/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts +++ b/src/vs/editor/contrib/folding/test/hiddenRangeModel.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { FoldingModel } from 'vs/editor/contrib/folding/foldingModel'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { TestDecorationProvider } from './foldingModel.test'; import { HiddenRangeModel } from 'vs/editor/contrib/folding/hiddenRangeModel'; @@ -38,7 +38,7 @@ suite('Hidden Range Model', () => { /* 9*/ ' }', /* 10*/ '}']; - let textModel = TextModel.createFromString(lines.join('\n')); + let textModel = createTextModel(lines.join('\n')); let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); let hiddenRangeModel = new HiddenRangeModel(foldingModel); diff --git a/src/vs/editor/contrib/folding/test/indentFold.test.ts b/src/vs/editor/contrib/folding/test/indentFold.test.ts index 9ab4dfdcd8..a37bc714ff 100644 --- a/src/vs/editor/contrib/folding/test/indentFold.test.ts +++ b/src/vs/editor/contrib/folding/test/indentFold.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; interface IndentRange { start: number; @@ -47,7 +47,7 @@ suite('Indentation Folding', () => { let r8 = r(13, 14);//4 let r9 = r(15, 16);//0 - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); function assertLimit(maxEntries: number, expectedRanges: IndentRange[], message: string) { let indentRanges = computeRanges(model, true, undefined, maxEntries); diff --git a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts index 0a1779aea9..0709a25afc 100644 --- a/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts +++ b/src/vs/editor/contrib/folding/test/indentRangeProvider.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration'; @@ -15,7 +15,7 @@ interface ExpectedIndentRange { } function assertRanges(lines: string[], expected: ExpectedIndentRange[], offside: boolean, markers?: FoldingMarkers): void { - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let actual = computeRanges(model, offside, markers); let actualRanges: ExpectedIndentRange[] = []; diff --git a/src/vs/editor/contrib/folding/test/syntaxFold.test.ts b/src/vs/editor/contrib/folding/test/syntaxFold.test.ts index 0dcde4af0d..25a7e9dc94 100644 --- a/src/vs/editor/contrib/folding/test/syntaxFold.test.ts +++ b/src/vs/editor/contrib/folding/test/syntaxFold.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { SyntaxRangeProvider } from 'vs/editor/contrib/folding/syntaxRangeProvider'; import { FoldingRangeProvider, FoldingRange, FoldingContext, ProviderResult } from 'vs/editor/common/modes'; import { ITextModel } from 'vs/editor/common/model'; @@ -69,7 +69,7 @@ suite('Syntax folding', () => { let r8 = r(14, 15); //6 let r9 = r(22, 23); //0 - let model = TextModel.createFromString(lines.join('\n')); + let model = createTextModel(lines.join('\n')); let ranges = [r1, r2, r3, r4, r5, r6, r7, r8, r9]; let providers = [new TestFoldingRangeProvider(model, ranges)]; diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index fa7492a850..e9372dbfe6 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -27,6 +27,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { assertType } from 'vs/base/common/types'; +import { IProgress } from 'vs/platform/progress/common/progress'; export function alertFormattingEdits(edits: ISingleEditOperation[]): void { @@ -209,6 +210,7 @@ export async function formatDocumentWithSelectedProvider( accessor: ServicesAccessor, editorOrModel: ITextModel | IActiveCodeEditor, mode: FormattingMode, + progress: IProgress, token: CancellationToken ): Promise { @@ -217,6 +219,7 @@ export async function formatDocumentWithSelectedProvider( const provider = getRealAndSyntheticDocumentFormattersOrdered(model); const selected = await FormattingConflicts.select(provider, model, mode); if (selected) { + progress.report(selected); await instaService.invokeFunction(formatDocumentWithProvider, selected, editorOrModel, mode, token); } } diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index cb5281b24f..c6d3ced423 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -25,6 +25,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { Progress } from 'vs/platform/progress/common/progress'; class FormatOnType implements IEditorContribution { @@ -230,7 +231,7 @@ class FormatDocumentAction extends EditorAction { async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { if (editor.hasModel()) { const instaService = accessor.get(IInstantiationService); - await instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, CancellationToken.None); + await instaService.invokeFunction(formatDocumentWithSelectedProvider, editor, FormattingMode.Explicit, Progress.None, CancellationToken.None); } } } diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 6248016047..c4586e7803 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -33,6 +33,7 @@ import * as peekView from 'vs/editor/contrib/peekView/peekView'; import { FileReferences, OneReference, ReferencesModel } from '../referencesModel'; import { FuzzyScore } from 'vs/base/common/filters'; import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; class DecorationsManager implements IDisposable { @@ -215,7 +216,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget { @ITextModelService private readonly _textModelResolverService: ITextModelService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @peekView.IPeekViewService private readonly _peekViewService: peekView.IPeekViewService, - @ILabelService private readonly _uriLabel: ILabelService + @ILabelService private readonly _uriLabel: ILabelService, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, ) { super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }); @@ -304,7 +306,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { }; this._preview = this._instantiationService.createInstance(EmbeddedCodeEditorWidget, this._previewContainer, options, this.editor); dom.hide(this._previewContainer); - this._previewNotAvailableMessage = TextModel.createFromString(nls.localize('missingPreviewMessage', "no preview available")); + this._previewNotAvailableMessage = new TextModel(nls.localize('missingPreviewMessage', "no preview available"), TextModel.DEFAULT_CREATION_OPTIONS, null, null, this._undoRedoService); // tree this._treeContainer = dom.append(containerElement, dom.$('div.ref-tree.inline')); diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 43af6f2c8c..2185b04e82 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -40,6 +40,7 @@ import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Progress } from 'vs/platform/progress/common/progress'; const $ = dom.$; @@ -626,6 +627,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), markerCodeActionTrigger, + Progress.None, cancellationToken); }); } diff --git a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts index ffa91b3cc0..49abcecad5 100644 --- a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { Handler } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import * as modes from 'vs/editor/common/modes'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -49,7 +49,7 @@ suite('ParameterHintsModel', () => { }); function createMockEditor(fileContents: string) { - const textModel = TextModel.createFromString(fileContents, undefined, undefined, mockFile); + const textModel = createTextModel(fileContents, undefined, undefined, mockFile); const editor = createTestCodeEditor({ model: textModel, serviceCollection: new ServiceCollection( diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index 97cd7763c7..93c51d7861 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -20,6 +20,8 @@ import { TestTextResourcePropertiesService } from 'vs/editor/test/common/service import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; class MockJSMode extends MockMode { @@ -48,7 +50,7 @@ suite('SmartSelect', () => { setup(() => { const configurationService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService()); + modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService())); mode = new MockJSMode(); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 4020ee6f7a..6515aafed6 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -12,6 +12,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { NullLogService } from 'vs/platform/log/common/log'; import { Handler } from 'vs/editor/common/editorCommon'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('SnippetController2', function () { @@ -36,7 +37,7 @@ suite('SnippetController2', function () { setup(function () { contextKeys = new MockContextKeyService(); - model = TextModel.createFromString('if\n $state\nfi'); + model = createTextModel('if\n $state\nfi'); editor = createTestCodeEditor({ model: model }); editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]); assert.equal(model.getEOL(), '\n'); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index 4b6b41a7e7..e21ec394e7 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -11,6 +11,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { SnippetSession } from 'vs/editor/contrib/snippet/snippetSession'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('SnippetSession', function () { @@ -26,7 +27,7 @@ suite('SnippetSession', function () { } setup(function () { - model = TextModel.createFromString('function foo() {\n console.log(a);\n}'); + model = createTextModel('function foo() {\n console.log(a);\n}'); editor = createTestCodeEditor({ model: model }) as IActiveCodeEditor; editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]); assert.equal(model.getEOL(), '\n'); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 33b3946439..f6c0ea5795 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -12,6 +12,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; import { mock } from 'vs/editor/contrib/suggest/test/suggestModel.test'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Snippet Variables Resolver', function () { @@ -25,7 +26,7 @@ suite('Snippet Variables Resolver', function () { let resolver: VariableResolver; setup(function () { - model = TextModel.createFromString([ + model = createTextModel([ 'this is line one', 'this is line two', ' this is line three' @@ -67,7 +68,7 @@ suite('Snippet Variables Resolver', function () { resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) + createTextModel('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) ); assertVariableResolve(resolver, 'TM_FILENAME', 'ghi'); if (!isWindows) { @@ -77,7 +78,7 @@ suite('Snippet Variables Resolver', function () { resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('mem:fff.ts')) + createTextModel('', undefined, undefined, URI.parse('mem:fff.ts')) ); assertVariableResolve(resolver, 'TM_DIRECTORY', ''); assertVariableResolve(resolver, 'TM_FILEPATH', 'fff.ts'); @@ -92,7 +93,7 @@ suite('Snippet Variables Resolver', function () { } }; - const model = TextModel.createFromString([].join('\n'), undefined, undefined, URI.parse('foo:///foo/files/text.txt')); + const model = createTextModel([].join('\n'), undefined, undefined, URI.parse('foo:///foo/files/text.txt')); const resolver = new CompositeSnippetVariableResolver([new ModelBasedVariableResolver(labelService, model)]); @@ -144,19 +145,19 @@ suite('Snippet Variables Resolver', function () { resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) + createTextModel('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'ghi'); resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('mem:.git')) + createTextModel('', undefined, undefined, URI.parse('mem:.git')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', '.git'); resolver = new ModelBasedVariableResolver( labelService, - TextModel.createFromString('', undefined, undefined, URI.parse('mem:foo.')) + createTextModel('', undefined, undefined, URI.parse('mem:foo.')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'foo'); }); diff --git a/src/vs/editor/contrib/suggest/test/suggest.test.ts b/src/vs/editor/contrib/suggest/test/suggest.test.ts index 8254342a86..1037cb3176 100644 --- a/src/vs/editor/contrib/suggest/test/suggest.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggest.test.ts @@ -10,6 +10,7 @@ import { provideSuggestionItems, SnippetSortOrder, CompletionOptions } from 'vs/ import { Position } from 'vs/editor/common/core/position'; import { TextModel } from 'vs/editor/common/model/textModel'; import { Range } from 'vs/editor/common/core/range'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Suggest', function () { @@ -19,7 +20,7 @@ suite('Suggest', function () { setup(function () { - model = TextModel.createFromString('FOO\nbar\BAR\nfoo', undefined, undefined, URI.parse('foo:bar/path')); + model = createTextModel('FOO\nbar\BAR\nfoo', undefined, undefined, URI.parse('foo:bar/path')); registration = CompletionProviderRegistry.register({ pattern: 'bar/path', scheme: 'foo' }, { provideCompletionItems(_doc, pos) { return { diff --git a/src/vs/editor/contrib/suggest/test/suggestController.test.ts b/src/vs/editor/contrib/suggest/test/suggestController.test.ts index c793a23737..2e6293eaaf 100644 --- a/src/vs/editor/contrib/suggest/test/suggestController.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestController.test.ts @@ -23,6 +23,7 @@ import { CompletionProviderRegistry, CompletionItemKind, CompletionItemInsertTex import { Event } from 'vs/base/common/event'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { IMenuService, IMenu } from 'vs/platform/actions/common/actions'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('SuggestController', function () { @@ -57,7 +58,7 @@ suite('SuggestController', function () { }] ); - model = TextModel.createFromString('', undefined, undefined, URI.from({ scheme: 'test-ctrl', path: '/path.tst' })); + model = createTextModel('', undefined, undefined, URI.from({ scheme: 'test-ctrl', path: '/path.tst' })); editor = createTestCodeEditor({ model, serviceCollection, diff --git a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts index e0693a20cb..194dd8445c 100644 --- a/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestMemory.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { LRUMemory, NoMemory, PrefixMemory, Memory } from 'vs/editor/contrib/suggest/suggestMemory'; import { ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { createSuggestItem } from 'vs/editor/contrib/suggest/test/completionModel.test'; import { IPosition } from 'vs/editor/common/core/position'; import { CompletionItem } from 'vs/editor/contrib/suggest/suggest'; @@ -19,7 +19,7 @@ suite('SuggestMemories', function () { setup(function () { pos = { lineNumber: 1, column: 1 }; - buffer = TextModel.createFromString('This is some text.\nthis.\nfoo: ,'); + buffer = createTextModel('This is some text.\nthis.\nfoo: ,'); items = [ createSuggestItem('foo', 0), createSuggestItem('bar', 0) diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index c57516c4c2..182889b50c 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -32,6 +32,7 @@ import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; import { ITextModel } from 'vs/editor/common/model'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export interface Ctor { new(): T; @@ -124,7 +125,7 @@ suite('SuggestModel - Context', function () { }); test('Context - shouldAutoTrigger', function () { - const model = TextModel.createFromString('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?'); + const model = createTextModel('Das Pferd frisst keinen Gurkensalat - Philipp Reis 1861.\nWer hat\'s erfunden?'); disposables.push(model); assertAutoTrigger(model, 3, true, 'end of word, Das|'); @@ -138,7 +139,7 @@ suite('SuggestModel - Context', function () { const innerMode = new InnerMode(); disposables.push(outerMode, innerMode); - const model = TextModel.createFromString('aa', undefined, outerMode.getLanguageIdentifier()); + const model = createTextModel('aa', undefined, outerMode.getLanguageIdentifier()); disposables.push(model); assertAutoTrigger(model, 1, true, 'a| new NullLogService()); - export const undoRedoService = define(IUndoRedoService, () => new UndoRedoService()); + export const undoRedoService = define(IUndoRedoService, (o) => new UndoRedoService(dialogService.get(o), notificationService.get(o))); export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o))); diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts index f6358d5732..616ef310dd 100644 --- a/src/vs/editor/test/browser/commands/sideEditing.test.ts +++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts @@ -10,7 +10,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; @@ -199,7 +199,7 @@ suite('SideEditing', () => { ]; function _runTest(selection: Selection, editRange: Range, editText: string, editForceMoveMarkers: boolean, expected: Selection, msg: string): void { - const model = TextModel.createFromString(LINES.join('\n')); + const model = createTextModel(LINES.join('\n')); const config = new TestConfiguration({}); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(config.options); const viewModel = new ViewModel(0, config, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 6ddc576806..7473a3894e 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -5551,4 +5551,28 @@ suite('Undo stops', () => { }); }); + test('can undo typing and EOL change in one undo stop', () => { + let model = createTextModel( + [ + 'A line', + 'Another line', + ].join('\n') + ); + + withTestCodeEditor(null, { model: model }, (editor, cursor) => { + cursor.setSelections('test', [new Selection(1, 3, 1, 3)]); + cursorCommand(cursor, H.Type, { text: 'first' }, 'keyboard'); + assert.equal(model.getValue(), 'A first line\nAnother line'); + assertCursor(cursor, new Selection(1, 8, 1, 8)); + + model.pushEOL(EndOfLineSequence.CRLF); + assert.equal(model.getValue(), 'A first line\r\nAnother line'); + assertCursor(cursor, new Selection(1, 8, 1, 8)); + + CoreEditingCommands.Undo.runEditorCommand(null, editor, null); + assert.equal(model.getValue(), 'A line\nAnother line'); + assertCursor(cursor, new Selection(1, 3, 1, 3)); + }); + }); + }); diff --git a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts index 9499089ce2..73b08d8cee 100644 --- a/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts +++ b/src/vs/editor/test/browser/controller/cursorMoveCommand.test.ts @@ -14,6 +14,7 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Cursor move command test', () => { @@ -31,7 +32,7 @@ suite('Cursor move command test', () => { '1' ].join('\n'); - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); thisConfiguration = new TestConfiguration({}); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(thisConfiguration.options); thisViewModel = new ViewModel(0, thisConfiguration, thisModel, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 2a696d9786..d56654fc10 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ITextAreaWrapper, PagedScreenReaderStrategy, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export class MockTextAreaWrapper extends Disposable implements ITextAreaWrapper { @@ -506,7 +506,7 @@ suite('TextAreaState', () => { suite('PagedScreenReaderStrategy', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { - const model = TextModel.createFromString(lines.join('\n')); + const model = createTextModel(lines.join('\n')); const actual = PagedScreenReaderStrategy.fromEditorSelection(TextAreaState.EMPTY, model, selection, 10, true); assert.ok(equalsTextAreaState(actual, expected)); model.dispose(); diff --git a/src/vs/editor/test/browser/testCodeEditor.ts b/src/vs/editor/test/browser/testCodeEditor.ts index 2a3768c8a2..aa7deeb72a 100644 --- a/src/vs/editor/test/browser/testCodeEditor.ts +++ b/src/vs/editor/test/browser/testCodeEditor.ts @@ -11,7 +11,7 @@ import * as editorOptions from 'vs/editor/common/config/editorOptions'; import { Cursor } from 'vs/editor/common/controller/cursor'; import { IConfiguration, IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestCodeEditorService, TestCommandService } from 'vs/editor/test/browser/editorTestServices'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; @@ -77,9 +77,9 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test // create a model if necessary and remember it in order to dispose it. if (!options.model) { if (typeof text === 'string') { - options.model = TextModel.createFromString(text); + options.model = createTextModel(text); } else if (text) { - options.model = TextModel.createFromString(text.join('\n')); + options.model = createTextModel(text.join('\n')); } } diff --git a/src/vs/editor/test/browser/testCommand.ts b/src/vs/editor/test/browser/testCommand.ts index 85bf50c4e9..03957abbba 100644 --- a/src/vs/editor/test/browser/testCommand.ts +++ b/src/vs/editor/test/browser/testCommand.ts @@ -8,7 +8,7 @@ import { IRange } from 'vs/editor/common/core/range'; import { Selection, ISelection } from 'vs/editor/common/core/selection'; import { ICommand, Handler, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { LanguageIdentifier } from 'vs/editor/common/modes'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; @@ -21,7 +21,7 @@ export function testCommand( expectedSelection: Selection, forceTokenization?: boolean ): void { - let model = TextModel.createFromString(lines.join('\n'), undefined, languageIdentifier); + let model = createTextModel(lines.join('\n'), undefined, languageIdentifier); withTestCodeEditor('', { model: model }, (_editor, cursor) => { if (!cursor) { return; diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index ee03ebb368..a13be76f4d 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -7,9 +7,12 @@ import { URI } from 'vs/base/common/uri'; import { DefaultEndOfLine, ITextModelCreationOptions } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier } from 'vs/editor/common/modes'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; export function withEditorModel(text: string[], callback: (model: TextModel) => void): void { - let model = TextModel.createFromString(text.join('\n')); + let model = createTextModel(text.join('\n')); callback(model); model.dispose(); } @@ -36,5 +39,8 @@ export function createTextModel(text: string, _options: IRelaxedTextModelCreatio isForSimpleWidget: (typeof _options.isForSimpleWidget === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.isForSimpleWidget : _options.isForSimpleWidget), largeFileOptimizations: (typeof _options.largeFileOptimizations === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.largeFileOptimizations : _options.largeFileOptimizations), }; - return TextModel.createFromString(text, options, languageIdentifier, uri); + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); + return new TextModel(text, options, languageIdentifier, uri, undoRedoService); } diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index a791fce633..92161da0b1 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -10,9 +10,10 @@ import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; function createEditableTextModelFromString(text: string): TextModel { - return TextModel.createFromString(text, TextModel.DEFAULT_CREATION_OPTIONS, null); + return createTextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null); } suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () => { diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index 79b3fb5286..de89611906 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -9,6 +9,7 @@ import { EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation import { MirrorTextModel } from 'vs/editor/common/model/mirrorTextModel'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export function testApplyEditsWithSyncedModels(original: string[], edits: IIdentifiedSingleEditOperation[], expected: string[], inputEditsAreInvalid: boolean = false): void { let originalStr = original.join('\n'); @@ -88,7 +89,7 @@ function assertLineMapping(model: TextModel, msg: string): void { export function assertSyncedModels(text: string, callback: (model: TextModel, assertMirrorModels: () => void) => void, setup: ((model: TextModel) => void) | null = null): void { - let model = TextModel.createFromString(text, TextModel.DEFAULT_CREATION_OPTIONS, null); + let model = createTextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null); model.setEOL(EndOfLineSequence.LF); assertLineMapping(model, 'model'); diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index b5803ec183..aaa007435d 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -9,6 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { LanguageIdentifier, MetadataConsts } from 'vs/editor/common/modes'; import { ViewLineToken, ViewLineTokenFactory } from 'vs/editor/test/common/core/viewLineToken'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; interface ILineEdit { startColumn: number; @@ -106,7 +107,7 @@ suite('ModelLinesTokens', () => { function testApplyEdits(initial: IBufferLineState[], edits: IEdit[], expected: IBufferLineState[]): void { const initialText = initial.map(el => el.text).join('\n'); - const model = TextModel.createFromString(initialText, TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); + const model = createTextModel(initialText, TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); for (let lineIndex = 0; lineIndex < initial.length; lineIndex++) { const lineTokens = initial[lineIndex].tokens; const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1; @@ -442,7 +443,7 @@ suite('ModelLinesTokens', () => { } test('insertion on empty line', () => { - const model = TextModel.createFromString('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); + const model = createTextModel('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); const tokens = TestToken.toTokens([new TestToken(0, 1)]); LineTokens.convertToEndOffset(tokens, model.getLineMaxColumn(1) - 1); model.setLineTokens(1, tokens); diff --git a/src/vs/editor/test/common/model/model.modes.test.ts b/src/vs/editor/test/common/model/model.modes.test.ts index a80302c304..d09be2ab9b 100644 --- a/src/vs/editor/test/common/model/model.modes.test.ts +++ b/src/vs/editor/test/common/model/model.modes.test.ts @@ -12,6 +12,7 @@ import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { TextModel } from 'vs/editor/common/model/textModel'; import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- utils @@ -46,7 +47,7 @@ suite('Editor Model - Model Modes 1', () => { const LANGUAGE_ID = 'modelModeTest1'; calledFor = []; languageRegistration = modes.TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); - thisModel = TextModel.createFromString(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); + thisModel = createTextModel(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); }); teardown(() => { @@ -199,7 +200,7 @@ suite('Editor Model - Model Modes 2', () => { 'Line5'; const LANGUAGE_ID = 'modelModeTest2'; languageRegistration = modes.TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); - thisModel = TextModel.createFromString(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); + thisModel = createTextModel(TEXT, undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); }); teardown(() => { diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 636e9a7ca3..352e7b78b8 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -15,6 +15,7 @@ import { IState, LanguageIdentifier, MetadataConsts, TokenizationRegistry } from import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- utils @@ -35,7 +36,7 @@ suite('Editor Model - Model', () => { LINE3 + '\n' + LINE4 + '\r\n' + LINE5; - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); }); teardown(() => { @@ -349,7 +350,7 @@ suite('Editor Model - Model Line Separators', () => { LINE3 + '\u2028' + LINE4 + '\r\n' + LINE5; - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); }); teardown(() => { @@ -365,7 +366,7 @@ suite('Editor Model - Model Line Separators', () => { }); test('Bug 13333:Model should line break on lonely CR too', () => { - let model = TextModel.createFromString('Hello\rWorld!\r\nAnother line'); + let model = createTextModel('Hello\rWorld!\r\nAnother line'); assert.equal(model.getLineCount(), 3); assert.equal(model.getValue(), 'Hello\r\nWorld!\r\nAnother line'); model.dispose(); @@ -430,7 +431,7 @@ suite('Editor Model - Words', () => { test('Get word at position', () => { const text = ['This text has some words. ']; - const thisModel = TextModel.createFromString(text.join('\n')); + const thisModel = createTextModel(text.join('\n')); disposables.push(thisModel); assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: 'This', startColumn: 1, endColumn: 5 }); @@ -451,7 +452,7 @@ suite('Editor Model - Words', () => { const innerMode = new InnerMode(); disposables.push(outerMode, innerMode); - const model = TextModel.createFromString('abab', undefined, outerMode.getLanguageIdentifier()); + const model = createTextModel('abab', undefined, outerMode.getLanguageIdentifier()); disposables.push(model); assert.deepEqual(model.getWordAtPosition(new Position(1, 1)), { word: 'ab', startColumn: 1, endColumn: 3 }); @@ -476,7 +477,7 @@ suite('Editor Model - Words', () => { }; disposables.push(mode); - const thisModel = TextModel.createFromString('.🐷-a-b', undefined, MODE_ID); + const thisModel = createTextModel('.🐷-a-b', undefined, MODE_ID); disposables.push(thisModel); assert.deepEqual(thisModel.getWordAtPosition(new Position(1, 1)), { word: '.', startColumn: 1, endColumn: 2 }); diff --git a/src/vs/editor/test/common/model/modelDecorations.test.ts b/src/vs/editor/test/common/model/modelDecorations.test.ts index 2cac668ebf..9a761d197a 100644 --- a/src/vs/editor/test/common/model/modelDecorations.test.ts +++ b/src/vs/editor/test/common/model/modelDecorations.test.ts @@ -9,6 +9,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- utils @@ -92,7 +93,7 @@ suite('Editor Model - Model Decorations', () => { LINE3 + '\n' + LINE4 + '\r\n' + LINE5; - thisModel = TextModel.createFromString(text); + thisModel = createTextModel(text); }); teardown(() => { @@ -400,7 +401,7 @@ suite('Editor Model - Model Decorations', () => { }); test('removeAllDecorationsWithOwnerId can be called after model dispose', () => { - let model = TextModel.createFromString('asd'); + let model = createTextModel('asd'); model.dispose(); model.removeAllDecorationsWithOwnerId(1); }); @@ -415,7 +416,7 @@ suite('Editor Model - Model Decorations', () => { suite('Decorations and editing', () => { function _runTest(decRange: Range, stickiness: TrackedRangeStickiness, editRange: Range, editText: string, editForceMoveMarkers: boolean, expectedDecRange: Range, msg: string): void { - let model = TextModel.createFromString([ + let model = createTextModel([ 'My First Line', 'My Second Line', 'Third Line' @@ -1148,7 +1149,7 @@ suite('deltaDecorations', () => { function testDeltaDecorations(text: string[], decorations: ILightWeightDecoration[], newDecorations: ILightWeightDecoration[]): void { - let model = TextModel.createFromString(text.join('\n')); + let model = createTextModel(text.join('\n')); // Add initial decorations & assert they are added let initialIds = model.deltaDecorations([], decorations.map(toModelDeltaDecoration)); @@ -1177,7 +1178,7 @@ suite('deltaDecorations', () => { } test('result respects input', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'Hello world,', 'How are you?' ].join('\n')); @@ -1265,7 +1266,7 @@ suite('deltaDecorations', () => { test('issue #4317: editor.setDecorations doesn\'t update the hover message', () => { - let model = TextModel.createFromString('Hello world!'); + let model = createTextModel('Hello world!'); let ids = model.deltaDecorations([], [{ range: { @@ -1299,7 +1300,7 @@ suite('deltaDecorations', () => { }); test('model doesn\'t get confused with individual tracked ranges', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'Hello world,', 'How are you?' ].join('\n')); @@ -1340,7 +1341,7 @@ suite('deltaDecorations', () => { }); test('issue #16922: Clicking on link doesn\'t seem to do anything', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'Hello world,', 'How are you?', 'Fine.', @@ -1371,7 +1372,7 @@ suite('deltaDecorations', () => { test('issue #41492: URL highlighting persists after pasting over url', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'My First Line' ].join('\n')); diff --git a/src/vs/editor/test/common/model/modelEditOperation.test.ts b/src/vs/editor/test/common/model/modelEditOperation.test.ts index 6b7ffc4192..c25d520ef9 100644 --- a/src/vs/editor/test/common/model/modelEditOperation.test.ts +++ b/src/vs/editor/test/common/model/modelEditOperation.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Editor Model - Model Edit Operation', () => { const LINE1 = 'My First Line'; @@ -23,7 +24,7 @@ suite('Editor Model - Model Edit Operation', () => { LINE3 + '\n' + LINE4 + '\r\n' + LINE5; - model = TextModel.createFromString(text); + model = createTextModel(text); }); teardown(() => { diff --git a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts index 3bcaf6c275..d4ea32fd29 100644 --- a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts @@ -12,7 +12,7 @@ import { PieceTreeBase } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceT import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n'; @@ -1761,7 +1761,7 @@ function getValueInSnapshot(snapshot: ITextSnapshot) { } suite('snapshot', () => { test('bug #45564, piece tree pieces should be immutable', () => { - const model = TextModel.createFromString('\n'); + const model = createTextModel('\n'); model.applyEdits([ { range: new Range(2, 1, 2, 1), @@ -1789,7 +1789,7 @@ suite('snapshot', () => { }); test('immutable snapshot 1', () => { - const model = TextModel.createFromString('abc\ndef'); + const model = createTextModel('abc\ndef'); const snapshot = model.createSnapshot(); model.applyEdits([ { @@ -1809,7 +1809,7 @@ suite('snapshot', () => { }); test('immutable snapshot 2', () => { - const model = TextModel.createFromString('abc\ndef'); + const model = createTextModel('abc\ndef'); const snapshot = model.createSnapshot(); model.applyEdits([ { @@ -1829,7 +1829,7 @@ suite('snapshot', () => { }); test('immutable snapshot 3', () => { - const model = TextModel.createFromString('abc\ndef'); + const model = createTextModel('abc\ndef'); model.applyEdits([ { range: new Range(2, 4, 2, 4), @@ -1896,4 +1896,4 @@ suite('chunk based search', () => { assert.equal(ret.length, 1); assert.deepEqual(ret[0].range, new Range(2, 2, 2, 3)); }); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/test/common/model/textModel.test.ts b/src/vs/editor/test/common/model/textModel.test.ts index c5b459bbcb..4b8995aa31 100644 --- a/src/vs/editor/test/common/model/textModel.test.ts +++ b/src/vs/editor/test/common/model/textModel.test.ts @@ -163,7 +163,7 @@ suite('Editor Model - TextModel', () => { test('getValueLengthInRange', () => { - let m = TextModel.createFromString('My First Line\r\nMy Second Line\r\nMy Third Line'); + let m = createTextModel('My First Line\r\nMy Second Line\r\nMy Third Line'); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); assert.equal(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); @@ -176,7 +176,7 @@ suite('Editor Model - TextModel', () => { assert.equal(m.getValueLengthInRange(new Range(1, 2, 3, 1000)), 'y First Line\r\nMy Second Line\r\nMy Third Line'.length); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1000, 1000)), 'My First Line\r\nMy Second Line\r\nMy Third Line'.length); - m = TextModel.createFromString('My First Line\nMy Second Line\nMy Third Line'); + m = createTextModel('My First Line\nMy Second Line\nMy Third Line'); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 1)), ''.length); assert.equal(m.getValueLengthInRange(new Range(1, 1, 1, 2)), 'M'.length); assert.equal(m.getValueLengthInRange(new Range(1, 2, 1, 3)), 'y'.length); @@ -662,7 +662,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); @@ -691,7 +691,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition around high-low surrogate pairs 1', () => { - let m = TextModel.createFromString('a📚b'); + let m = createTextModel('a📚b'); assert.deepEqual(m.validatePosition(new Position(0, 0)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(0, 1)), new Position(1, 1)); @@ -718,7 +718,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition around high-low surrogate pairs 2', () => { - let m = TextModel.createFromString('a📚📚b'); + let m = createTextModel('a📚📚b'); assert.deepEqual(m.validatePosition(new Position(1, 1)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(1, 2)), new Position(1, 2)); @@ -732,7 +732,7 @@ suite('Editor Model - TextModel', () => { test('validatePosition handle NaN.', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validatePosition(new Position(NaN, 1)), new Position(1, 1)); assert.deepEqual(m.validatePosition(new Position(1, NaN)), new Position(1, 1)); @@ -743,7 +743,7 @@ suite('Editor Model - TextModel', () => { }); test('issue #71480: validatePosition handle floats', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validatePosition(new Position(0.2, 1)), new Position(1, 1), 'a'); assert.deepEqual(m.validatePosition(new Position(1.2, 1)), new Position(1, 1), 'b'); @@ -756,7 +756,7 @@ suite('Editor Model - TextModel', () => { }); test('issue #71480: validateRange handle floats', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.validateRange(new Range(0.2, 1.5, 0.8, 2.5)), new Range(1, 1, 1, 1)); assert.deepEqual(m.validateRange(new Range(1.2, 1.7, 1.8, 2.2)), new Range(1, 1, 1, 2)); @@ -764,7 +764,7 @@ suite('Editor Model - TextModel', () => { test('validateRange around high-low surrogate pairs 1', () => { - let m = TextModel.createFromString('a📚b'); + let m = createTextModel('a📚b'); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); @@ -792,7 +792,7 @@ suite('Editor Model - TextModel', () => { test('validateRange around high-low surrogate pairs 2', () => { - let m = TextModel.createFromString('a📚📚b'); + let m = createTextModel('a📚📚b'); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 1)), new Range(1, 1, 1, 1)); assert.deepEqual(m.validateRange(new Range(0, 0, 0, 7)), new Range(1, 1, 1, 1)); @@ -835,7 +835,7 @@ suite('Editor Model - TextModel', () => { test('modifyPosition', () => { - let m = TextModel.createFromString('line one\nline two'); + let m = createTextModel('line one\nline two'); assert.deepEqual(m.modifyPosition(new Position(1, 1), 0), new Position(1, 1)); assert.deepEqual(m.modifyPosition(new Position(0, 0), 0), new Position(1, 1)); assert.deepEqual(m.modifyPosition(new Position(30, 1), 0), new Position(2, 9)); @@ -913,7 +913,7 @@ suite('Editor Model - TextModel', () => { }); test('getLineFirstNonWhitespaceColumn', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'asd', ' asd', '\tasd', @@ -943,7 +943,7 @@ suite('Editor Model - TextModel', () => { }); test('getLineLastNonWhitespaceColumn', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'asd', 'asd ', 'asd\t', @@ -973,7 +973,7 @@ suite('Editor Model - TextModel', () => { }); test('#50471. getValueInRange with invalid range', () => { - let m = TextModel.createFromString('My First Line\r\nMy Second Line\r\nMy Third Line'); + let m = createTextModel('My First Line\r\nMy Second Line\r\nMy Third Line'); assert.equal(m.getValueInRange(new Range(1, NaN, 1, 3)), 'My'); assert.equal(m.getValueInRange(new Range(NaN, NaN, NaN, NaN)), ''); }); @@ -982,24 +982,24 @@ suite('Editor Model - TextModel', () => { suite('TextModel.mightContainRTL', () => { test('nope', () => { - let model = TextModel.createFromString('hello world!'); + let model = createTextModel('hello world!'); assert.equal(model.mightContainRTL(), false); }); test('yes', () => { - let model = TextModel.createFromString('Hello,\nזוהי עובדה מבוססת שדעתו'); + let model = createTextModel('Hello,\nזוהי עובדה מבוססת שדעתו'); assert.equal(model.mightContainRTL(), true); }); test('setValue resets 1', () => { - let model = TextModel.createFromString('hello world!'); + let model = createTextModel('hello world!'); assert.equal(model.mightContainRTL(), false); model.setValue('Hello,\nזוהי עובדה מבוססת שדעתו'); assert.equal(model.mightContainRTL(), true); }); test('setValue resets 2', () => { - let model = TextModel.createFromString('Hello,\nهناك حقيقة مثبتة منذ زمن طويل'); + let model = createTextModel('Hello,\nهناك حقيقة مثبتة منذ زمن طويل'); assert.equal(model.mightContainRTL(), true); model.setValue('hello world!'); assert.equal(model.mightContainRTL(), false); @@ -1010,14 +1010,14 @@ suite('TextModel.mightContainRTL', () => { suite('TextModel.createSnapshot', () => { test('empty file', () => { - let model = TextModel.createFromString(''); + let model = createTextModel(''); let snapshot = model.createSnapshot(); assert.equal(snapshot.read(), null); model.dispose(); }); test('file with BOM', () => { - let model = TextModel.createFromString(UTF8_BOM_CHARACTER + 'Hello'); + let model = createTextModel(UTF8_BOM_CHARACTER + 'Hello'); assert.equal(model.getLineContent(1), 'Hello'); let snapshot = model.createSnapshot(true); assert.equal(snapshot.read(), UTF8_BOM_CHARACTER + 'Hello'); @@ -1026,7 +1026,7 @@ suite('TextModel.createSnapshot', () => { }); test('regular file', () => { - let model = TextModel.createFromString('My First Line\n\t\tMy Second Line\n Third Line\n\n1'); + let model = createTextModel('My First Line\n\t\tMy Second Line\n Third Line\n\n1'); let snapshot = model.createSnapshot(); assert.equal(snapshot.read(), 'My First Line\n\t\tMy Second Line\n Third Line\n\n1'); assert.equal(snapshot.read(), null); @@ -1040,7 +1040,7 @@ suite('TextModel.createSnapshot', () => { } const text = lines.join('\n'); - let model = TextModel.createFromString(text); + let model = createTextModel(text); let snapshot = model.createSnapshot(); let actual = ''; diff --git a/src/vs/editor/test/common/model/textModelSearch.test.ts b/src/vs/editor/test/common/model/textModelSearch.test.ts index bfd0260195..18caa18361 100644 --- a/src/vs/editor/test/common/model/textModelSearch.test.ts +++ b/src/vs/editor/test/common/model/textModelSearch.test.ts @@ -11,6 +11,7 @@ import { EndOfLineSequence, FindMatch } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { SearchData, SearchParams, TextModelSearch, isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; // --------- Find suite('TextModelSearch', () => { @@ -51,12 +52,12 @@ suite('TextModelSearch', () => { let expectedMatches = expectedRanges.map(entry => new FindMatch(entry, null)); let searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators); - let model = TextModel.createFromString(text); + let model = createTextModel(text); _assertFindMatches(model, searchParams, expectedMatches); model.dispose(); - let model2 = TextModel.createFromString(text); + let model2 = createTextModel(text); model2.setEOL(EndOfLineSequence.CRLF); _assertFindMatches(model2, searchParams, expectedMatches); model2.dispose(); @@ -380,7 +381,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch without regex', () => { - let model = TextModel.createFromString('line line one\nline two\nthree'); + let model = createTextModel('line line one\nline two\nthree'); let searchParams = new SearchParams('line', false, false, null); @@ -403,7 +404,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with beginning boundary regex', () => { - let model = TextModel.createFromString('line one\nline two\nthree'); + let model = createTextModel('line one\nline two\nthree'); let searchParams = new SearchParams('^line', true, false, null); @@ -423,7 +424,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => { - let model = TextModel.createFromString('line line one\nline two\nthree'); + let model = createTextModel('line line one\nline two\nthree'); let searchParams = new SearchParams('^line', true, false, null); @@ -443,7 +444,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => { - let model = TextModel.createFromString('line line one\nline two\nline three\nline four'); + let model = createTextModel('line line one\nline two\nline three\nline four'); let searchParams = new SearchParams('^line.*\\nline', true, false, null); @@ -460,7 +461,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with ending boundary regex', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('line$', true, false, null); @@ -480,7 +481,7 @@ suite('TextModelSearch', () => { }); test('findMatches with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)', true, false, null); @@ -495,7 +496,7 @@ suite('TextModelSearch', () => { }); test('findMatches multiline with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); @@ -509,7 +510,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)', true, false, null); @@ -520,7 +521,7 @@ suite('TextModelSearch', () => { }); test('findNextMatch multiline with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); @@ -531,7 +532,7 @@ suite('TextModelSearch', () => { }); test('findPreviousMatch with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)', true, false, null); @@ -542,7 +543,7 @@ suite('TextModelSearch', () => { }); test('findPreviousMatch multiline with capturing matches', () => { - let model = TextModel.createFromString('one line line\ntwo line\nthree'); + let model = createTextModel('one line line\ntwo line\nthree'); let searchParams = new SearchParams('(l(in)e)\\n', true, false, null); @@ -553,7 +554,7 @@ suite('TextModelSearch', () => { }); test('\\n matches \\r\\n', () => { - let model = TextModel.createFromString('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); + let model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); assert.equal(model.getEOL(), '\r\n'); @@ -576,7 +577,7 @@ suite('TextModelSearch', () => { }); test('\\r can never be found', () => { - let model = TextModel.createFromString('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); + let model = createTextModel('a\r\nb\r\nc\r\nd\r\ne\r\nf\r\ng\r\nh\r\ni'); assert.equal(model.getEOL(), '\r\n'); @@ -763,7 +764,7 @@ suite('TextModelSearch', () => { }); test('issue #74715. \\d* finds empty string and stops searching.', () => { - let model = TextModel.createFromString('10.243.30.10'); + let model = createTextModel('10.243.30.10'); let searchParams = new SearchParams('\\d*', true, false, null); diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index b7f8fa1e57..cf90c9a60f 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -15,6 +15,7 @@ import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { ViewLineToken } from 'vs/editor/test/common/core/viewLineToken'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('TextModelWithTokens', () => { @@ -72,7 +73,7 @@ suite('TextModelWithTokens', () => { brackets: brackets }); - let model = TextModel.createFromString( + let model = createTextModel( contents.join('\n'), TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier @@ -178,7 +179,7 @@ suite('TextModelWithTokens - bracket matching', () => { let text = ')]}{[(' + '\n' + ')]}{[('; - let model = TextModel.createFromString(text, undefined, languageIdentifier); + let model = createTextModel(text, undefined, languageIdentifier); assertIsNotBracket(model, 1, 1); assertIsNotBracket(model, 1, 2); @@ -206,7 +207,7 @@ suite('TextModelWithTokens - bracket matching', () => { '}, bar: {hallo: [{' + '\n' + '}, {' + '\n' + '}]}}'; - let model = TextModel.createFromString(text, undefined, languageIdentifier); + let model = createTextModel(text, undefined, languageIdentifier); let brackets: [Position, Range, Range][] = [ [new Position(1, 11), new Range(1, 11, 1, 12), new Range(5, 4, 5, 5)], @@ -284,7 +285,7 @@ suite('TextModelWithTokens', () => { 'end;', ].join('\n'); - const model = TextModel.createFromString(text, undefined, languageIdentifier); + const model = createTextModel(text, undefined, languageIdentifier); // ... is not matched assertIsNotBracket(model, 10, 9); @@ -322,7 +323,7 @@ suite('TextModelWithTokens', () => { 'endrecord', ].join('\n'); - const model = TextModel.createFromString(text, undefined, languageIdentifier); + const model = createTextModel(text, undefined, languageIdentifier); // ... is matched assertIsBracket(model, new Position(1, 1), [new Range(1, 1, 1, 12), new Range(4, 1, 4, 10)]); @@ -388,7 +389,7 @@ suite('TextModelWithTokens', () => { ], }); - const model = TextModel.createFromString([ + const model = createTextModel([ 'function hello() {', ' console.log(`${100}`);', '}' @@ -459,7 +460,7 @@ suite('TextModelWithTokens regression tests', () => { let registration1 = TokenizationRegistry.register(LANG_ID1, tokenizationSupport); let registration2 = TokenizationRegistry.register(LANG_ID2, tokenizationSupport); - let model = TextModel.createFromString('A model with\ntwo lines'); + let model = createTextModel('A model with\ntwo lines'); assertViewLineTokens(model, 1, true, [createViewLineToken(12, 1)]); assertViewLineTokens(model, 2, true, [createViewLineToken(9, 1)]); @@ -498,7 +499,7 @@ suite('TextModelWithTokens regression tests', () => { ] }); - let model = TextModel.createFromString([ + let model = createTextModel([ 'Imports System', 'Imports System.Collections.Generic', '', @@ -528,7 +529,7 @@ suite('TextModelWithTokens regression tests', () => { ] }); - let model = TextModel.createFromString([ + let model = createTextModel([ 'sequence "outer"', ' sequence "inner"', ' endsequence', @@ -561,7 +562,7 @@ suite('TextModelWithTokens regression tests', () => { let registration = TokenizationRegistry.register(outerMode.language, tokenizationSupport); - let model = TextModel.createFromString('A model with one line', undefined, outerMode); + let model = createTextModel('A model with one line', undefined, outerMode); model.forceTokenization(1); assert.equal(model.getLanguageIdAtPosition(1, 1), innerMode.id); @@ -574,7 +575,7 @@ suite('TextModelWithTokens regression tests', () => { suite('TextModel.getLineIndentGuide', () => { function assertIndentGuides(lines: [number, number, number, number, string][], tabSize: number): void { let text = lines.map(l => l[4]).join('\n'); - let model = TextModel.createFromString(text); + let model = createTextModel(text); model.updateOptions({ tabSize: tabSize }); let actualIndents = model.getLinesIndentGuides(1, model.getLineCount()); @@ -747,7 +748,7 @@ suite('TextModel.getLineIndentGuide', () => { }); test('issue #49173', () => { - let model = TextModel.createFromString([ + let model = createTextModel([ 'class A {', ' public m1(): void {', ' }', diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts index 11518d259b..f5efc2dec3 100644 --- a/src/vs/editor/test/common/model/tokensStore.test.ts +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -9,6 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('TokensStore', () => { @@ -96,7 +97,7 @@ suite('TokensStore', () => { function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) { const initialState = parseTokensState(rawInitialState); - const model = TextModel.createFromString(initialState.text); + const model = createTextModel(initialState.text); model.setSemanticTokens([initialState.tokens]); model.applyEdits(edits); diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index 6ebbd61c78..519f7c4637 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -11,7 +11,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { createStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel'; +import { createTextBuffer } from 'vs/editor/common/model/textModel'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,6 +19,9 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; const GENERATE_TESTS = false; @@ -30,7 +33,7 @@ suite('ModelService', () => { configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService()); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService(new TestDialogService(), new TestNotificationService())); }); teardown(() => { @@ -49,7 +52,7 @@ suite('ModelService', () => { test('_computeEdits no change', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -75,7 +78,7 @@ suite('ModelService', () => { test('_computeEdits first line changed', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -103,7 +106,7 @@ suite('ModelService', () => { test('_computeEdits EOL changed', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -129,7 +132,7 @@ suite('ModelService', () => { test('_computeEdits EOL and other change 1', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'This is line one', //16 'and this is line number two', //27 @@ -165,7 +168,7 @@ suite('ModelService', () => { test('_computeEdits EOL and other change 2', function () { - const model = TextModel.createFromString( + const model = createTextModel( [ 'package main', // 1 'func foo() {', // 2 @@ -307,7 +310,7 @@ suite('ModelService', () => { }); function assertComputeEdits(lines1: string[], lines2: string[]): void { - const model = TextModel.createFromString(lines1.join('\n')); + const model = createTextModel(lines1.join('\n')); const textBuffer = createTextBuffer(lines2.join('\n'), DefaultEndOfLine.LF); // compute required edits diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index fef72a09ad..b164a6fac9 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -18,6 +18,7 @@ import { LineBreakData, ISimpleModel, SplitLine, SplitLinesCollection } from 'vs import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { @@ -96,7 +97,7 @@ suite('Editor ViewModel - SplitLinesCollection', () => { const lineBreaksComputerFactory = new MonospaceLineBreaksComputerFactory(wordWrapBreakBeforeCharacters, wordWrapBreakAfterCharacters); - const model = TextModel.createFromString([ + const model = createTextModel([ 'int main() {', '\tprintf("Hello world!");', '}', @@ -347,7 +348,7 @@ suite('SplitLinesCollection', () => { }; const LANGUAGE_ID = 'modelModeTest1'; languageRegistration = modes.TokenizationRegistry.register(LANGUAGE_ID, tokenizationSupport); - model = TextModel.createFromString(_text.join('\n'), undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); + model = createTextModel(_text.join('\n'), undefined, new modes.LanguageIdentifier(LANGUAGE_ID, 0)); // force tokenization model.forceTokenization(model.getLineCount()); }); diff --git a/src/vs/editor/test/common/viewModel/testViewModel.ts b/src/vs/editor/test/common/viewModel/testViewModel.ts index f6867e1d99..40aeda747c 100644 --- a/src/vs/editor/test/common/viewModel/testViewModel.ts +++ b/src/vs/editor/test/common/viewModel/testViewModel.ts @@ -8,12 +8,13 @@ import { TextModel } from 'vs/editor/common/model/textModel'; import { ViewModel } from 'vs/editor/common/viewModel/viewModelImpl'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; import { MonospaceLineBreaksComputerFactory } from 'vs/editor/common/viewModel/monospaceLineBreaksComputer'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; export function testViewModel(text: string[], options: IEditorOptions, callback: (viewModel: ViewModel, model: TextModel) => void): void { const EDITOR_ID = 1; const configuration = new TestConfiguration(options); - const model = TextModel.createFromString(text.join('\n')); + const model = createTextModel(text.join('\n')); const monospaceLineBreaksComputerFactory = MonospaceLineBreaksComputerFactory.create(configuration.options); const viewModel = new ViewModel(EDITOR_ID, configuration, model, monospaceLineBreaksComputerFactory, monospaceLineBreaksComputerFactory, null!); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 927d28f9af..832a37e3a9 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1884,7 +1884,7 @@ declare namespace monaco.editor { * @param cursorStateComputer A callback that can compute the resulting cursors state after the edit operations have been executed. * @return The cursor state returned by the `cursorStateComputer`. */ - pushEditOperations(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; + pushEditOperations(beforeCursorState: Selection[] | null, editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer): Selection[] | null; /** * Change the end of line sequence. This is the preferred way of * changing the eol sequence. This will land on the undo stack. @@ -2849,6 +2849,11 @@ declare namespace monaco.editor { * Defaults to true. */ scrollPredominantAxis?: boolean; + /** + * Enable that the selection with the mouse and keys is doing column selection. + * Defaults to false. + */ + columnSelection?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -3798,105 +3803,106 @@ declare namespace monaco.editor { autoSurround = 10, codeLens = 11, colorDecorators = 12, - comments = 13, - contextmenu = 14, - copyWithSyntaxHighlighting = 15, - cursorBlinking = 16, - cursorSmoothCaretAnimation = 17, - cursorStyle = 18, - cursorSurroundingLines = 19, - cursorSurroundingLinesStyle = 20, - cursorWidth = 21, - disableLayerHinting = 22, - disableMonospaceOptimizations = 23, - dragAndDrop = 24, - emptySelectionClipboard = 25, - extraEditorClassName = 26, - fastScrollSensitivity = 27, - find = 28, - fixedOverflowWidgets = 29, - folding = 30, - foldingStrategy = 31, - foldingHighlight = 32, - fontFamily = 33, - fontInfo = 34, - fontLigatures = 35, - fontSize = 36, - fontWeight = 37, - formatOnPaste = 38, - formatOnType = 39, - glyphMargin = 40, - gotoLocation = 41, - hideCursorInOverviewRuler = 42, - highlightActiveIndentGuide = 43, - hover = 44, - inDiffEditor = 45, - letterSpacing = 46, - lightbulb = 47, - lineDecorationsWidth = 48, - lineHeight = 49, - lineNumbers = 50, - lineNumbersMinChars = 51, - links = 52, - matchBrackets = 53, - minimap = 54, - mouseStyle = 55, - mouseWheelScrollSensitivity = 56, - mouseWheelZoom = 57, - multiCursorMergeOverlapping = 58, - multiCursorModifier = 59, - multiCursorPaste = 60, - occurrencesHighlight = 61, - overviewRulerBorder = 62, - overviewRulerLanes = 63, - padding = 64, - parameterHints = 65, - peekWidgetDefaultFocus = 66, - definitionLinkOpensInPeek = 67, - quickSuggestions = 68, - quickSuggestionsDelay = 69, - readOnly = 70, - renderControlCharacters = 71, - renderIndentGuides = 72, - renderFinalNewline = 73, - renderLineHighlight = 74, - renderValidationDecorations = 75, - renderWhitespace = 76, - revealHorizontalRightPadding = 77, - roundedSelection = 78, - rulers = 79, - scrollbar = 80, - scrollBeyondLastColumn = 81, - scrollBeyondLastLine = 82, - scrollPredominantAxis = 83, - selectionClipboard = 84, - selectionHighlight = 85, - selectOnLineNumbers = 86, - showFoldingControls = 87, - showUnused = 88, - snippetSuggestions = 89, - smoothScrolling = 90, - stopRenderingLineAfter = 91, - suggest = 92, - suggestFontSize = 93, - suggestLineHeight = 94, - suggestOnTriggerCharacters = 95, - suggestSelection = 96, - tabCompletion = 97, - useTabStops = 98, - wordSeparators = 99, - wordWrap = 100, - wordWrapBreakAfterCharacters = 101, - wordWrapBreakBeforeCharacters = 102, - wordWrapColumn = 103, - wordWrapMinified = 104, - wrappingIndent = 105, - wrappingStrategy = 106, - editorClassName = 107, - pixelRatio = 108, - tabFocusMode = 109, - layoutInfo = 110, - wrappingInfo = 111 + columnSelection = 13, + comments = 14, + contextmenu = 15, + copyWithSyntaxHighlighting = 16, + cursorBlinking = 17, + cursorSmoothCaretAnimation = 18, + cursorStyle = 19, + cursorSurroundingLines = 20, + cursorSurroundingLinesStyle = 21, + cursorWidth = 22, + disableLayerHinting = 23, + disableMonospaceOptimizations = 24, + dragAndDrop = 25, + emptySelectionClipboard = 26, + extraEditorClassName = 27, + fastScrollSensitivity = 28, + find = 29, + fixedOverflowWidgets = 30, + folding = 31, + foldingStrategy = 32, + foldingHighlight = 33, + fontFamily = 34, + fontInfo = 35, + fontLigatures = 36, + fontSize = 37, + fontWeight = 38, + formatOnPaste = 39, + formatOnType = 40, + glyphMargin = 41, + gotoLocation = 42, + hideCursorInOverviewRuler = 43, + highlightActiveIndentGuide = 44, + hover = 45, + inDiffEditor = 46, + letterSpacing = 47, + lightbulb = 48, + lineDecorationsWidth = 49, + lineHeight = 50, + lineNumbers = 51, + lineNumbersMinChars = 52, + links = 53, + matchBrackets = 54, + minimap = 55, + mouseStyle = 56, + mouseWheelScrollSensitivity = 57, + mouseWheelZoom = 58, + multiCursorMergeOverlapping = 59, + multiCursorModifier = 60, + multiCursorPaste = 61, + occurrencesHighlight = 62, + overviewRulerBorder = 63, + overviewRulerLanes = 64, + padding = 65, + parameterHints = 66, + peekWidgetDefaultFocus = 67, + definitionLinkOpensInPeek = 68, + quickSuggestions = 69, + quickSuggestionsDelay = 70, + readOnly = 71, + renderControlCharacters = 72, + renderIndentGuides = 73, + renderFinalNewline = 74, + renderLineHighlight = 75, + renderValidationDecorations = 76, + renderWhitespace = 77, + revealHorizontalRightPadding = 78, + roundedSelection = 79, + rulers = 80, + scrollbar = 81, + scrollBeyondLastColumn = 82, + scrollBeyondLastLine = 83, + scrollPredominantAxis = 84, + selectionClipboard = 85, + selectionHighlight = 86, + selectOnLineNumbers = 87, + showFoldingControls = 88, + showUnused = 89, + snippetSuggestions = 90, + smoothScrolling = 91, + stopRenderingLineAfter = 92, + suggest = 93, + suggestFontSize = 94, + suggestLineHeight = 95, + suggestOnTriggerCharacters = 96, + suggestSelection = 97, + tabCompletion = 98, + useTabStops = 99, + wordSeparators = 100, + wordWrap = 101, + wordWrapBreakAfterCharacters = 102, + wordWrapBreakBeforeCharacters = 103, + wordWrapColumn = 104, + wordWrapMinified = 105, + wrappingIndent = 106, + wrappingStrategy = 107, + editorClassName = 108, + pixelRatio = 109, + tabFocusMode = 110, + layoutInfo = 111, + wrappingInfo = 112 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3912,6 +3918,7 @@ declare namespace monaco.editor { autoSurround: IEditorOption; codeLens: IEditorOption; colorDecorators: IEditorOption; + columnSelection: IEditorOption; comments: IEditorOption; contextmenu: IEditorOption; copyWithSyntaxHighlighting: IEditorOption; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e340bd814e..da07bf33ee 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -11,8 +11,9 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { ICommandService, CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { UriDto } from 'vs/base/common/types'; export interface ILocalizedString { value: string; @@ -28,9 +29,7 @@ export interface ICommandAction { toggled?: ContextKeyExpr; } -type Serialized = { [K in keyof T]: T[K] extends URI ? UriComponents : Serialized }; - -export type ISerializableCommandAction = Serialized; +export type ISerializableCommandAction = UriDto; export interface IMenuItem { command: ICommandAction; diff --git a/src/vs/platform/dialogs/test/common/testDialogService.ts b/src/vs/platform/dialogs/test/common/testDialogService.ts new file mode 100644 index 0000000000..589a42f76e --- /dev/null +++ b/src/vs/platform/dialogs/test/common/testDialogService.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import Severity from 'vs/base/common/severity'; +import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs'; + +export class TestDialogService implements IDialogService { + + _serviceBrand: undefined; + + confirm(_confirmation: IConfirmation): Promise { return Promise.resolve({ confirmed: false }); } + show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } + about(): Promise { return Promise.resolve(); } +} diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 3ac1450c35..a0310c0c14 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -157,7 +157,7 @@ export class InstantiationService implements IInstantiationService { graph.lookupOrInsertNode(item); // a weak but working heuristic for cycle checks - if (cycleCount++ > 150) { + if (cycleCount++ > 200) { throw new CyclicDependencyError(graph); } diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 15de3a5024..ab92509f84 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -86,8 +86,6 @@ export interface IProgressRunner { done(): void; } -export const emptyProgress: IProgress = { report: () => { } }; - export const emptyProgressRunner: IProgressRunner = Object.freeze({ total() { }, worked() { }, @@ -100,6 +98,8 @@ export interface IProgress { export class Progress implements IProgress { + static readonly None: IProgress = Object.freeze({ report() { } }); + private _value?: T; get value(): T | undefined { return this._value; } diff --git a/src/vs/platform/undoRedo/common/undoRedo.ts b/src/vs/platform/undoRedo/common/undoRedo.ts index abaa2ed79c..3422c3de81 100644 --- a/src/vs/platform/undoRedo/common/undoRedo.ts +++ b/src/vs/platform/undoRedo/common/undoRedo.ts @@ -8,42 +8,26 @@ import { URI } from 'vs/base/common/uri'; export const IUndoRedoService = createDecorator('undoRedoService'); -export interface IUndoRedoContext { - replaceCurrentElement(others: IUndoRedoElement[]): void; +export const enum UndoRedoElementType { + Resource, + Workspace } -export interface IUndoRedoElement { - /** - * None, one or multiple resources that this undo/redo element impacts. - */ - readonly resources: readonly URI[]; - - /** - * The label of the undo/redo element. - */ +export interface IResourceUndoRedoElement { + readonly type: UndoRedoElementType.Resource; + readonly resource: URI; readonly label: string; + undo(): Promise | void; + redo(): Promise | void; +} - /** - * Undo. - * Will always be called before `redo`. - * Can be called multiple times. - * e.g. `undo` -> `redo` -> `undo` -> `redo` - */ - undo(ctx: IUndoRedoContext): void; - - /** - * Redo. - * Will always be called after `undo`. - * Can be called multiple times. - * e.g. `undo` -> `redo` -> `undo` -> `redo` - */ - redo(ctx: IUndoRedoContext): void; - - /** - * Invalidate the edits concerning `resource`. - * i.e. the undo/redo stack for that particular resource has been destroyed. - */ - invalidate(resource: URI): void; +export interface IWorkspaceUndoRedoElement { + readonly type: UndoRedoElementType.Workspace; + readonly resources: readonly URI[]; + readonly label: string; + undo(): Promise | void; + redo(): Promise | void; + split(): IResourceUndoRedoElement[]; } export interface IUndoRedoService { @@ -53,12 +37,12 @@ export interface IUndoRedoService { * Add a new element to the `undo` stack. * This will destroy the `redo` stack. */ - pushElement(element: IUndoRedoElement): void; + pushElement(element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void; /** * Get the last pushed element. If the last pushed element has been undone, returns null. */ - getLastElement(resource: URI): IUndoRedoElement | null; + getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null; /** * Remove elements that target `resource`. @@ -66,8 +50,8 @@ export interface IUndoRedoService { removeElements(resource: URI): void; canUndo(resource: URI): boolean; - undo(resource: URI): void; + undo(resource: URI): Promise | void; - redo(resource: URI): void; canRedo(resource: URI): boolean; + redo(resource: URI): Promise | void; } diff --git a/src/vs/platform/undoRedo/common/undoRedoService.ts b/src/vs/platform/undoRedo/common/undoRedoService.ts index 924eff3a7a..77e5315055 100644 --- a/src/vs/platform/undoRedo/common/undoRedoService.ts +++ b/src/vs/platform/undoRedo/common/undoRedoService.ts @@ -3,31 +3,88 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; +import * as nls from 'vs/nls'; +import { IUndoRedoService, IResourceUndoRedoElement, IWorkspaceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import Severity from 'vs/base/common/severity'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; -class StackElement { - public readonly actual: IUndoRedoElement; +class ResourceStackElement { + public readonly type = UndoRedoElementType.Resource; + public readonly actual: IResourceUndoRedoElement; public readonly label: string; - public readonly resources: readonly URI[]; + + public readonly resource: URI; + public readonly strResource: string; + public readonly resources: URI[]; public readonly strResources: string[]; - constructor(actual: IUndoRedoElement) { + constructor(actual: IResourceUndoRedoElement) { this.actual = actual; this.label = actual.label; - this.resources = actual.resources; + this.resource = actual.resource; + this.strResource = uriGetComparisonKey(this.resource); + this.resources = [this.resource]; + this.strResources = [this.strResource]; + } +} + +const enum RemovedResourceReason { + ExternalRemoval = 0, + NoParallelUniverses = 1 +} + +class RemovedResources { + public readonly set: Set = new Set(); + public readonly reason: [URI[], URI[]] = [[], []]; + + public createMessage(): string { + let messages: string[] = []; + if (this.reason[RemovedResourceReason.ExternalRemoval].length > 0) { + const paths = this.reason[RemovedResourceReason.ExternalRemoval].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path); + messages.push(nls.localize('externalRemoval', "The following files have been closed: {0}.", paths.join(', '))); + } + if (this.reason[RemovedResourceReason.NoParallelUniverses].length > 0) { + const paths = this.reason[RemovedResourceReason.NoParallelUniverses].map(uri => uri.scheme === Schemas.file ? uri.fsPath : uri.path); + messages.push(nls.localize('noParallelUniverses', "The following files have been modified in an incompatible way: {0}.", paths.join(', '))); + } + return messages.join('\n'); + } +} + +class WorkspaceStackElement { + public readonly type = UndoRedoElementType.Workspace; + public readonly actual: IWorkspaceUndoRedoElement; + public readonly label: string; + + public readonly resources: URI[]; + public readonly strResources: string[]; + public removedResources: RemovedResources | null; + + constructor(actual: IWorkspaceUndoRedoElement) { + this.actual = actual; + this.label = actual.label; + this.resources = actual.resources.slice(0); this.strResources = this.resources.map(resource => uriGetComparisonKey(resource)); + this.removedResources = null; } - public invalidate(resource: URI): void { - if (this.resources.length > 1) { - this.actual.invalidate(resource); + public removeResource(resource: URI, strResource: string, reason: RemovedResourceReason): void { + if (!this.removedResources) { + this.removedResources = new RemovedResources(); + } + if (!this.removedResources.set.has(strResource)) { + this.removedResources.set.add(strResource); + this.removedResources.reason[reason].push(resource); } } } +type StackElement = ResourceStackElement | WorkspaceStackElement; class ResourceEditStack { public resource: URI; @@ -46,12 +103,15 @@ export class UndoRedoService implements IUndoRedoService { private readonly _editStacks: Map; - constructor() { + constructor( + @IDialogService private readonly _dialogService: IDialogService, + @INotificationService private readonly _notificationService: INotificationService, + ) { this._editStacks = new Map(); } - public pushElement(_element: IUndoRedoElement): void { - const element = new StackElement(_element); + public pushElement(_element: IResourceUndoRedoElement | IWorkspaceUndoRedoElement): void { + const element: StackElement = (_element.type === UndoRedoElementType.Resource ? new ResourceStackElement(_element) : new WorkspaceStackElement(_element)); for (let i = 0, len = element.resources.length; i < len; i++) { const resource = element.resources[i]; const strResource = element.strResources[i]; @@ -66,14 +126,16 @@ export class UndoRedoService implements IUndoRedoService { // remove the future for (const futureElement of editStack.future) { - futureElement.invalidate(resource); + if (futureElement.type === UndoRedoElementType.Workspace) { + futureElement.removeResource(resource, strResource, RemovedResourceReason.NoParallelUniverses); + } } editStack.future = []; editStack.past.push(element); } } - public getLastElement(resource: URI): IUndoRedoElement | null { + public getLastElement(resource: URI): IResourceUndoRedoElement | IWorkspaceUndoRedoElement | null { const strResource = uriGetComparisonKey(resource); if (this._editStacks.has(strResource)) { const editStack = this._editStacks.get(strResource)!; @@ -88,15 +150,75 @@ export class UndoRedoService implements IUndoRedoService { return null; } + private _splitPastWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set | null): void { + const individualArr = toRemove.actual.split(); + const individualMap = new Map(); + for (const _element of individualArr) { + const element = new ResourceStackElement(_element); + individualMap.set(element.strResource, element); + } + + for (const strResource of toRemove.strResources) { + if (ignoreResources && ignoreResources.has(strResource)) { + continue; + } + const editStack = this._editStacks.get(strResource)!; + for (let j = editStack.past.length - 1; j >= 0; j--) { + if (editStack.past[j] === toRemove) { + if (individualMap.has(strResource)) { + // gets replaced + editStack.past[j] = individualMap.get(strResource)!; + } else { + // gets deleted + editStack.past.splice(j, 1); + } + break; + } + } + } + } + + private _splitFutureWorkspaceElement(toRemove: WorkspaceStackElement, ignoreResources: Set | null): void { + const individualArr = toRemove.actual.split(); + const individualMap = new Map(); + for (const _element of individualArr) { + const element = new ResourceStackElement(_element); + individualMap.set(element.strResource, element); + } + + for (const strResource of toRemove.strResources) { + if (ignoreResources && ignoreResources.has(strResource)) { + continue; + } + const editStack = this._editStacks.get(strResource)!; + for (let j = editStack.future.length - 1; j >= 0; j--) { + if (editStack.future[j] === toRemove) { + if (individualMap.has(strResource)) { + // gets replaced + editStack.future[j] = individualMap.get(strResource)!; + } else { + // gets deleted + editStack.future.splice(j, 1); + } + break; + } + } + } + } + public removeElements(resource: URI): void { const strResource = uriGetComparisonKey(resource); if (this._editStacks.has(strResource)) { const editStack = this._editStacks.get(strResource)!; - for (const pastElement of editStack.past) { - pastElement.invalidate(resource); + for (const element of editStack.past) { + if (element.type === UndoRedoElementType.Workspace) { + element.removeResource(resource, strResource, RemovedResourceReason.ExternalRemoval); + } } - for (const futureElement of editStack.future) { - futureElement.invalidate(resource); + for (const element of editStack.future) { + if (element.type === UndoRedoElementType.Workspace) { + element.removeResource(resource, strResource, RemovedResourceReason.ExternalRemoval); + } } this._editStacks.delete(strResource); } @@ -111,7 +233,85 @@ export class UndoRedoService implements IUndoRedoService { return false; } - public undo(resource: URI): void { + private _onError(err: Error, element: StackElement): void { + onUnexpectedError(err); + // An error occured while undoing or redoing => drop the undo/redo stack for all affected resources + for (const resource of element.resources) { + this.removeElements(resource); + } + this._notificationService.error(err); + } + + private _safeInvoke(element: StackElement, invoke: () => Promise | void): Promise | void { + let result: Promise | void; + try { + result = invoke(); + } catch (err) { + return this._onError(err, element); + } + + if (result) { + return result.then(undefined, (err) => this._onError(err, element)); + } + } + + private _workspaceUndo(resource: URI, element: WorkspaceStackElement): Promise | void { + if (element.removedResources) { + this._splitPastWorkspaceElement(element, element.removedResources.set); + const message = nls.localize('cannotWorkspaceUndo', "Could not undo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()); + this._notificationService.info(message); + return; + } + + // this must be the last past element in all the impacted resources! + let affectedEditStacks: ResourceEditStack[] = []; + for (const strResource of element.strResources) { + affectedEditStacks.push(this._editStacks.get(strResource)!); + } + + let cannotUndoDueToResources: URI[] = []; + for (const editStack of affectedEditStacks) { + if (editStack.past.length === 0 || editStack.past[editStack.past.length - 1] !== element) { + cannotUndoDueToResources.push(editStack.resource); + } + } + + if (cannotUndoDueToResources.length > 0) { + this._splitPastWorkspaceElement(element, null); + const paths = cannotUndoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path); + const message = nls.localize('cannotWorkspaceUndoDueToChanges', "Could not undo '{0}' across all files because changes were made to {1}", element.label, paths.join(', ')); + this._notificationService.info(message); + return; + } + + return this._dialogService.show( + Severity.Info, + nls.localize('confirmWorkspace', "Would you like to undo '{0}' across all files?", element.label), + [ + nls.localize('ok', "Yes, change {0} files.", affectedEditStacks.length), + nls.localize('nok', "No, change only this file.") + ] + ).then((result) => { + if (result.choice === 0) { + for (const editStack of affectedEditStacks) { + editStack.past.pop(); + editStack.future.push(element); + } + return this._safeInvoke(element, () => element.actual.undo()); + } else { + this._splitPastWorkspaceElement(element, null); + return this.undo(resource); + } + }); + } + + private _resourceUndo(editStack: ResourceEditStack, element: ResourceStackElement): Promise | void { + editStack.past.pop(); + editStack.future.push(element); + return this._safeInvoke(element, () => element.actual.undo()); + } + + public undo(resource: URI): Promise | void { const strResource = uriGetComparisonKey(resource); if (!this._editStacks.has(strResource)) { return; @@ -123,51 +323,10 @@ export class UndoRedoService implements IUndoRedoService { } const element = editStack.past[editStack.past.length - 1]; - - let replaceCurrentElement: IUndoRedoElement[] | null = null as IUndoRedoElement[] | null; - try { - element.actual.undo({ - replaceCurrentElement: (others: IUndoRedoElement[]): void => { - replaceCurrentElement = others; - } - }); - } catch (e) { - onUnexpectedError(e); - editStack.past.pop(); - editStack.future.push(element); - return; - } - - if (replaceCurrentElement === null) { - // regular case - editStack.past.pop(); - editStack.future.push(element); - return; - } - - const replaceCurrentElementMap = new Map(); - for (const _replace of replaceCurrentElement) { - const replace = new StackElement(_replace); - for (const strResource of replace.strResources) { - replaceCurrentElementMap.set(strResource, replace); - } - } - - for (let i = 0, len = element.strResources.length; i < len; i++) { - const strResource = element.strResources[i]; - if (this._editStacks.has(strResource)) { - const editStack = this._editStacks.get(strResource)!; - for (let j = editStack.past.length - 1; j >= 0; j--) { - if (editStack.past[j] === element) { - if (replaceCurrentElementMap.has(strResource)) { - editStack.past[j] = replaceCurrentElementMap.get(strResource)!; - } else { - editStack.past.splice(j, 1); - } - break; - } - } - } + if (element.type === UndoRedoElementType.Workspace) { + return this._workspaceUndo(resource, element); + } else { + return this._resourceUndo(editStack, element); } } @@ -180,7 +339,49 @@ export class UndoRedoService implements IUndoRedoService { return false; } - public redo(resource: URI): void { + private _workspaceRedo(resource: URI, element: WorkspaceStackElement): Promise | void { + if (element.removedResources) { + this._splitFutureWorkspaceElement(element, element.removedResources.set); + const message = nls.localize('cannotWorkspaceRedo', "Could not redo '{0}' across all files. {1}", element.label, element.removedResources.createMessage()); + this._notificationService.info(message); + return; + } + + // this must be the last future element in all the impacted resources! + let affectedEditStacks: ResourceEditStack[] = []; + for (const strResource of element.strResources) { + affectedEditStacks.push(this._editStacks.get(strResource)!); + } + + let cannotRedoDueToResources: URI[] = []; + for (const editStack of affectedEditStacks) { + if (editStack.future.length === 0 || editStack.future[editStack.future.length - 1] !== element) { + cannotRedoDueToResources.push(editStack.resource); + } + } + + if (cannotRedoDueToResources.length > 0) { + this._splitFutureWorkspaceElement(element, null); + const paths = cannotRedoDueToResources.map(r => r.scheme === Schemas.file ? r.fsPath : r.path); + const message = nls.localize('cannotWorkspaceRedoDueToChanges', "Could not redo '{0}' across all files because changes were made to {1}", element.label, paths.join(', ')); + this._notificationService.info(message); + return; + } + + for (const editStack of affectedEditStacks) { + editStack.future.pop(); + editStack.past.push(element); + } + return this._safeInvoke(element, () => element.actual.redo()); + } + + private _resourceRedo(editStack: ResourceEditStack, element: ResourceStackElement): Promise | void { + editStack.future.pop(); + editStack.past.push(element); + return this._safeInvoke(element, () => element.actual.redo()); + } + + public redo(resource: URI): Promise | void { const strResource = uriGetComparisonKey(resource); if (!this._editStacks.has(strResource)) { return; @@ -192,51 +393,10 @@ export class UndoRedoService implements IUndoRedoService { } const element = editStack.future[editStack.future.length - 1]; - - let replaceCurrentElement: IUndoRedoElement[] | null = null as IUndoRedoElement[] | null; - try { - element.actual.redo({ - replaceCurrentElement: (others: IUndoRedoElement[]): void => { - replaceCurrentElement = others; - } - }); - } catch (e) { - onUnexpectedError(e); - editStack.future.pop(); - editStack.past.push(element); - return; - } - - if (replaceCurrentElement === null) { - // regular case - editStack.future.pop(); - editStack.past.push(element); - return; - } - - const replaceCurrentElementMap = new Map(); - for (const _replace of replaceCurrentElement) { - const replace = new StackElement(_replace); - for (const strResource of replace.strResources) { - replaceCurrentElementMap.set(strResource, replace); - } - } - - for (let i = 0, len = element.strResources.length; i < len; i++) { - const strResource = element.strResources[i]; - if (this._editStacks.has(strResource)) { - const editStack = this._editStacks.get(strResource)!; - for (let j = editStack.future.length - 1; j >= 0; j--) { - if (editStack.future[j] === element) { - if (replaceCurrentElementMap.has(strResource)) { - editStack.future[j] = replaceCurrentElementMap.get(strResource)!; - } else { - editStack.future.splice(j, 1); - } - break; - } - } - } + if (element.type === UndoRedoElementType.Workspace) { + return this._workspaceRedo(resource, element); + } else { + return this._resourceRedo(editStack, element); } } } diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 3ff32322b0..9fee079ba7 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -18,8 +18,7 @@ import { ParseError, parse } from 'vs/base/common/json'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { IStringDictionary } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; - -const BACK_UP_MAX_AGE = 1000 * 60 * 60 * 24 * 30; /* 30 days */ +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; type SyncSourceClassification = { source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; @@ -65,6 +64,7 @@ export abstract class AbstractSynchroniser extends Disposable { @IUserDataSyncEnablementService protected readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IUserDataSyncLogService protected readonly logService: IUserDataSyncLogService, + @IConfigurationService protected readonly configurationService: IConfigurationService, ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); @@ -91,7 +91,7 @@ export abstract class AbstractSynchroniser extends Disposable { protected get enabled(): boolean { return this.userDataSyncEnablementService.isResourceEnabled(this.resourceKey); } - async sync(ref?: string): Promise { + async sync(ref?: string, donotUseLastSyncUserData?: boolean): Promise { if (!this.enabled) { this.logService.info(`${this.source}: Skipped synchronizing ${this.source.toLowerCase()} as it is disabled.`); return; @@ -108,7 +108,7 @@ export abstract class AbstractSynchroniser extends Disposable { this.logService.trace(`${this.source}: Started synchronizing ${this.source.toLowerCase()}...`); this.setStatus(SyncStatus.Syncing); - const lastSyncUserData = await this.getLastSyncUserData(); + const lastSyncUserData = donotUseLastSyncUserData ? null : await this.getLastSyncUserData(); const remoteUserData = ref && lastSyncUserData && lastSyncUserData.ref === ref ? lastSyncUserData : await this.getRemoteUserData(lastSyncUserData); if (remoteUserData.syncData && remoteUserData.syncData.version > this.version) { @@ -117,7 +117,19 @@ export abstract class AbstractSynchroniser extends Disposable { throw new UserDataSyncError(localize('incompatible', "Cannot sync {0} as its version {1} is not compatible with cloud {2}", this.source, this.version, remoteUserData.syncData.version), UserDataSyncErrorCode.Incompatible, this.source); } - return this.doSync(remoteUserData, lastSyncUserData); + try { + await this.doSync(remoteUserData, lastSyncUserData); + } catch (e) { + if (e instanceof UserDataSyncError) { + switch (e.code) { + case UserDataSyncErrorCode.RemotePreconditionFailed: + // Rejected as there is a new remote version. Syncing again, + this.logService.info(`${this.source}: Failed to synchronize as there is a new remote version available. Synchronizing again...`); + return this.sync(undefined, true); + } + } + throw e; + } } async hasPreviouslySynced(): Promise { @@ -191,15 +203,21 @@ export abstract class AbstractSynchroniser extends Disposable { protected async backupLocal(content: VSBuffer): Promise { const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')); - await this.fileService.writeFile(resource, content); + try { + await this.fileService.writeFile(resource, content); + } catch (e) { + this.logService.error(e); + } this.cleanUpDelayer.trigger(() => this.cleanUpBackup()); } private async cleanUpBackup(): Promise { - const stat = await this.fileService.resolve(this.syncFolder); - if (stat.children) { - const toDelete = stat.children.filter(stat => { - if (stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)) { + try { + const stat = await this.fileService.resolve(this.syncFolder); + if (stat.children) { + const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort(); + const backUpMaxAge = 1000 * 60 * 60 * 24 * (this.configurationService.getValue('sync.localBackupDuration') || 30 /* Default 30 days */); + let toDelete = all.filter(stat => { const ctime = stat.ctime || new Date( parseInt(stat.name.substring(0, 4)), parseInt(stat.name.substring(4, 6)) - 1, @@ -208,14 +226,19 @@ export abstract class AbstractSynchroniser extends Disposable { parseInt(stat.name.substring(11, 13)), parseInt(stat.name.substring(13, 15)) ).getTime(); - return Date.now() - ctime > BACK_UP_MAX_AGE; + return Date.now() - ctime > backUpMaxAge; + }); + const remaining = all.length - toDelete.length; + if (remaining < 10) { + toDelete = toDelete.slice(10 - remaining); } - return false; - }); - await Promise.all(toDelete.map(stat => { - this.logService.info('Deleting from backup', stat.resource.path); - this.fileService.del(stat.resource); - })); + await Promise.all(toDelete.map(stat => { + this.logService.info('Deleting from backup', stat.resource.path); + this.fileService.del(stat.resource); + })); + } + } catch (e) { + this.logService.error(e); } } @@ -247,8 +270,9 @@ export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); + super(source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(file))); this._register(this.fileService.onDidFilesChange(e => this.onFileChanges(e))); } @@ -346,8 +370,9 @@ export abstract class AbstractJsonFileSynchroniser extends AbstractFileSynchroni @ITelemetryService telemetryService: ITelemetryService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService protected readonly userDataSyncUtilService: IUserDataSyncUtilService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); + super(file, source, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); } protected hasErrors(content: string): boolean { diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 5d20c71fbd..a5f4ff944b 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -46,11 +46,11 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); + super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register( Event.debounce( Event.any( @@ -154,11 +154,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply(previewResult); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Extensions: Failed to synchronize extensions as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } throw e; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index cc38484663..3f4910fbf1 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser, ResourceKey, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -15,6 +15,7 @@ import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { parse } from 'vs/base/common/json'; import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const argvProperties: string[] = ['locale']; @@ -38,8 +39,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, + @IConfigurationService configurationService: IConfigurationService, ) { - super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService); + super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } @@ -129,11 +131,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.trace('UI State: Finished synchronizing ui state.'); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.RemotePreconditionFailed) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('UI State: Failed to synchronize ui state as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } throw e; } finally { this.setStatus(SyncStatus.Idle); diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index e0d400083b..bab4ddc7d0 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -36,14 +36,14 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem constructor( @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, @ITelemetryService telemetryService: ITelemetryService, ) { - super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService); + super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } async pull(): Promise { @@ -180,10 +180,6 @@ export class KeybindingsSynchroniser extends AbstractJsonFileSynchroniser implem this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncError) { switch (e.code) { - case UserDataSyncErrorCode.RemotePreconditionFailed: - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new remote version available. Synchronizing again...'); - return this.sync(); case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. this.logService.info('Keybindings: Failed to synchronize keybindings as there is a new local version available. Synchronizing again...'); diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index 25eecaa2c0..c0787da608 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -10,8 +10,8 @@ import { values } from 'vs/base/common/map'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions, Edit, getEOL } from 'vs/base/common/jsonFormatter'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; -import { IConflictSetting, CONFIGURATION_SYNC_STORE_KEY } from 'vs/platform/userDataSync/common/userDataSync'; -import { firstIndex } from 'vs/base/common/arrays'; +import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; +import { firstIndex, distinct } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; @@ -22,7 +22,7 @@ export interface IMergeResult { conflictsSettings: IConflictSetting[]; } -export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] { +export function getIgnoredSettings(defaultIgnoredSettings: string[], configurationService: IConfigurationService, settingsContent?: string): string[] { let value: string[] = []; if (settingsContent) { const setting = parse(settingsContent); @@ -42,7 +42,7 @@ export function getIgnoredSettings(configurationService: IConfigurationService, } } } - return [CONFIGURATION_SYNC_STORE_KEY, ...added].filter(setting => removed.indexOf(setting) === -1); + return distinct([...defaultIgnoredSettings, ...added,].filter(setting => removed.indexOf(setting) === -1)); } diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index 29f80eb848..2bc8f38fa6 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -21,6 +21,7 @@ import { edit } from 'vs/platform/userDataSync/common/content'; import { IFileSyncPreviewResult, AbstractJsonFileSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { URI } from 'vs/base/common/uri'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; interface ISettingsSyncContent { settings: string; @@ -51,11 +52,12 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncUtilService userDataSyncUtilService: IUserDataSyncUtilService, - @IConfigurationService private readonly configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService, @ITelemetryService telemetryService: ITelemetryService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, ) { - super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService); + super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService, userDataSyncEnablementService, telemetryService, logService, userDataSyncUtilService, configurationService); } protected setStatus(status: SyncStatus): void { @@ -94,7 +96,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const fileContent = await this.getLocalFileContent(); const formatUtils = await this.getFormattingOptions(); // Update ignored settings from local file content - const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + const content = updateIgnoredSettings(remoteSettingsSyncContent.settings, fileContent ? fileContent.value.toString() : '{}', ignoredSettings, formatUtils); this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, remoteUserData, @@ -136,7 +139,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (fileContent !== null) { const formatUtils = await this.getFormattingOptions(); // Remove ignored settings - const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + const content = updateIgnoredSettings(fileContent.value.toString(), '{}', ignoredSettings, formatUtils); const lastSyncUserData = await this.getLastSyncUserData(); const remoteUserData = await this.getRemoteUserData(lastSyncUserData); @@ -192,7 +196,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (preview && content !== null) { const formatUtils = await this.getFormattingOptions(); // remove ignored settings from the remote content for preview - content = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + content = updateIgnoredSettings(content, '{}', ignoredSettings, formatUtils); } return content; } @@ -203,7 +208,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.cancel(); const formatUtils = await this.getFormattingOptions(); // Add ignored settings from local file content - content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', getIgnoredSettings(this.configurationService), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(); + content = updateIgnoredSettings(content, preview.fileContent ? preview.fileContent.value.toString() : '{}', ignoredSettings, formatUtils); this.syncPreviewResultPromise = createCancelablePromise(async () => ({ ...preview, content })); await this.apply(true); this.setStatus(SyncStatus.Idle); @@ -237,10 +243,6 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncError) { switch (e.code) { - case UserDataSyncErrorCode.RemotePreconditionFailed: - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Settings: Failed to synchronize settings as there is a new remote version available. Synchronizing again...'); - return this.sync(); case UserDataSyncErrorCode.LocalPreconditionFailed: // Rejected as there is a new local version. Syncing again. this.logService.info('Settings: Failed to synchronize settings as there is a new local version available. Synchronizing again...'); @@ -271,7 +273,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const formatUtils = await this.getFormattingOptions(); // Update ignored settings from remote const remoteSettingsSyncContent = this.getSettingsSyncContent(remoteUserData); - content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', getIgnoredSettings(this.configurationService, content), formatUtils); + const ignoredSettings = await this.getIgnoredSettings(content); + content = updateIgnoredSettings(content, remoteSettingsSyncContent ? remoteSettingsSyncContent.settings : '{}', ignoredSettings, formatUtils); this.logService.trace('Settings: Updating remote settings...'); remoteUserData = await this.updateRemoteUserData(JSON.stringify({ settings: content }), forcePush ? null : remoteUserData.ref); this.logService.info('Settings: Updated remote settings'); @@ -317,7 +320,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement const localContent: string = fileContent ? fileContent.value.toString() : '{}'; this.validateContent(localContent); this.logService.trace('Settings: Merging remote settings with local settings...'); - const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions); + const ignoredSettings = await this.getIgnoredSettings(); + const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, ignoredSettings, resolvedConflicts, formattingOptions); content = result.localContent || result.remoteContent; hasLocalChanged = result.localContent !== null; hasRemoteChanged = result.remoteContent !== null; @@ -334,7 +338,8 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement if (content && !token.isCancellationRequested) { // Remove the ignored settings from the preview. - const previewContent = updateIgnoredSettings(content, '{}', getIgnoredSettings(this.configurationService), formattingOptions); + const ignoredSettings = await this.getIgnoredSettings(); + const previewContent = updateIgnoredSettings(content, '{}', ignoredSettings, formattingOptions); await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); } @@ -356,6 +361,21 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement return null; } + private _defaultIgnoredSettings: Promise | undefined = undefined; + protected async getIgnoredSettings(content?: string): Promise { + if (!this._defaultIgnoredSettings) { + this._defaultIgnoredSettings = this.userDataSyncUtilService.resolveDefaultIgnoredSettings(); + const disposable = Event.any( + Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)), + Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)))(() => { + disposable.dispose(); + this._defaultIgnoredSettings = undefined; + }); + } + const defaultIgnoredSettings = await this._defaultIgnoredSettings; + return getIgnoredSettings(defaultIgnoredSettings, this.configurationService, content); + } + private validateContent(content: string): void { if (this.hasErrors(content)) { throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index cd4bc1e9fb..49a65c8531 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -36,6 +36,12 @@ export interface ISyncConfiguration { } } +export function getDefaultIgnoredSettings(): string[] { + const allSettings = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + const machineSettings = Object.keys(allSettings).filter(setting => allSettings[setting].scope === ConfigurationScope.MACHINE || allSettings[setting].scope === ConfigurationScope.MACHINE_OVERRIDABLE); + return [CONFIGURATION_SYNC_STORE_KEY, ...machineSettings]; +} + export function registerConfiguration(): IDisposable { const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; const ignoredExtensionsSchemaId = 'vscode://schemas/ignoredExtensions'; @@ -105,11 +111,12 @@ export function registerConfiguration(): IDisposable { }); const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); const registerIgnoredSettingsSchema = () => { + const defaultIgnoreSettings = getDefaultIgnoredSettings().filter(s => s !== CONFIGURATION_SYNC_STORE_KEY); const ignoredSettingsSchema: IJSONSchema = { items: { type: 'string', - enum: Object.keys(allSettings.properties) - } + enum: [...Object.keys(allSettings.properties).filter(setting => defaultIgnoreSettings.indexOf(setting) === -1), ...defaultIgnoreSettings.map(setting => `-${setting}`)] + }, }; jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema); }; @@ -180,6 +187,7 @@ export enum UserDataSyncErrorCode { // Local Errors LocalPreconditionFailed = 'LocalPreconditionFailed', LocalInvalidContent = 'LocalInvalidContent', + LocalError = 'LocalError', Incompatible = 'Incompatible', Unknown = 'Unknown', @@ -286,6 +294,7 @@ export interface IUserDataSyncService { readonly onDidChangeConflicts: Event; readonly onDidChangeLocal: Event; + readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]>; readonly lastSyncTime: number | undefined; readonly onDidChangeLastSyncTime: Event; @@ -313,6 +322,7 @@ export interface IUserDataSyncUtilService { _serviceBrand: undefined; resolveUserBindings(userbindings: string[]): Promise>; resolveFormattingOptions(resource: URI): Promise; + resolveDefaultIgnoredSettings(): Promise; } export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index a50e4817ad..e32aa9c2a4 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -20,6 +20,7 @@ export class UserDataSyncChannel implements IServerChannel { case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; case 'onDidChangeLocal': return this.service.onDidChangeLocal; case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime; + case 'onSyncErrors': return this.service.onSyncErrors; } throw new Error(`Event not found: ${event}`); } @@ -101,6 +102,7 @@ export class UserDataSycnUtilServiceChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { + case 'resolveDefaultIgnoredSettings': return this.service.resolveDefaultIgnoredSettings(); case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]); case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0])); } @@ -115,6 +117,10 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService { constructor(private readonly channel: IChannel) { } + async resolveDefaultIgnoredSettings(): Promise { + return this.channel.call('resolveDefaultIgnoredSettings'); + } + async resolveUserBindings(userbindings: string[]): Promise> { return this.channel.call('resolveUserKeybindings', [userbindings]); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 2c0752ea82..f4f8d568e6 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -41,6 +41,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _syncErrors: [SyncSource, UserDataSyncError][] = []; + private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + private _lastSyncTime: number | undefined = undefined; get lastSyncTime(): number | undefined { return this._lastSyncTime; } private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); @@ -82,6 +86,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.handleSyncError(e, synchroniser.source); } } + this.updateLastSyncTime(); } async push(): Promise { @@ -93,12 +98,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.handleSyncError(e, synchroniser.source); } } + this.updateLastSyncTime(); } async sync(): Promise { await this.checkEnablement(); const startTime = new Date().getTime(); + this._syncErrors = []; try { this.logService.trace('Sync started.'); if (this.status !== SyncStatus.HasConflicts) { @@ -124,6 +131,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await synchroniser.sync(manifest && manifest.latest ? manifest.latest[synchroniser.resourceKey] : undefined); } catch (e) { this.handleSyncError(e, synchroniser.source); + this._syncErrors.push([synchroniser.source, UserDataSyncError.toUserDataSyncError(e)]); } } @@ -138,9 +146,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } this.logService.info(`Sync done. Took ${new Date().getTime() - startTime}ms`); + this.updateLastSyncTime(); } finally { this.updateStatus(); + this._onSyncErrors.fire(this._syncErrors); } } @@ -239,8 +249,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (this._status !== status) { this._status = status; this._onDidChangeStatus.fire(status); - if (oldStatus !== SyncStatus.Uninitialized && this.status === SyncStatus.Idle) { - this.updateLastSyncTime(new Date().getTime()); + if (oldStatus === SyncStatus.HasConflicts) { + this.updateLastSyncTime(); } } } @@ -268,11 +278,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return SyncStatus.Idle; } - private updateLastSyncTime(lastSyncTime: number): void { - if (this._lastSyncTime !== lastSyncTime) { - this._lastSyncTime = lastSyncTime; - this.storageService.store(LAST_SYNC_TIME_KEY, lastSyncTime, StorageScope.GLOBAL); - this._onDidChangeLastSyncTime.fire(lastSyncTime); + private updateLastSyncTime(): void { + if (this.status === SyncStatus.Idle) { + this._lastSyncTime = new Date().getTime(); + this.storageService.store(LAST_SYNC_TIME_KEY, this._lastSyncTime, StorageScope.GLOBAL); + this._onDidChangeLastSyncTime.fire(this._lastSyncTime); } } diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index d53ae0d4e0..04dc0c85a4 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -6,7 +6,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; import { generateUuid } from 'vs/base/common/uuid'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -231,6 +231,10 @@ export class TestUserDataSyncUtilService implements IUserDataSyncUtilService { _serviceBrand: any; + async resolveDefaultIgnoredSettings(): Promise { + return getDefaultIgnoredSettings(); + } + async resolveUserBindings(userbindings: string[]): Promise> { const keys: IStringDictionary = {}; for (const keybinding of userbindings) { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a1732a6c74..3972f967d1 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1187,65 +1187,77 @@ declare module 'vscode' { //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 + // TODO: + // - Naming! + // - Think about where a rename would live. + // - Think about handling go to line? + // - Should we expose edits? + // - More properties from `TextDocument`? + /** - * Defines the editing functionality of a webview editor. This allows the webview editor to hook into standard + * Defines the capabilities of a custom webview editor. + */ + interface WebviewCustomEditorCapabilities { + /** + * Defines the editing capability of a custom webview document. + * + * When not provided, the document is considered readonly. + */ + readonly editing?: WebviewCustomEditorEditingCapability; + } + + /** + * Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard * editor events such as `undo` or `save`. * * @param EditType Type of edits. */ - interface WebviewCustomEditorEditingDelegate { + interface WebviewCustomEditorEditingCapability { /** - * Save a resource. - * - * @param resource Resource being saved. + * Save the resource. * * @return Thenable signaling that the save has completed. */ - save(resource: Uri): Thenable; + save(): Thenable; /** - * Save an existing resource at a new path. + * Save the existing resource at a new path. * - * @param resource Resource being saved. * @param targetResource Location to save to. * * @return Thenable signaling that the save has completed. */ - saveAs(resource: Uri, targetResource: Uri): Thenable; + saveAs(targetResource: Uri): Thenable; /** * Event triggered by extensions to signal to VS Code that an edit has occurred. */ - // TODO@matt - // eslint-disable-next-line vscode-dts-event-naming - readonly onEdit: Event<{ readonly resource: Uri, readonly edit: EditType }>; + readonly onDidEdit: Event; /** * Apply a set of edits. * - * Note that is not invoked when `onEdit` is called as `onEdit` implies also updating the view to reflect the edit. + * Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit. * - * @param resource Resource being edited. * @param edit Array of edits. Sorted from oldest to most recent. * * @return Thenable signaling that the change has completed. */ - applyEdits(resource: Uri, edits: readonly EditType[]): Thenable; + applyEdits(edits: readonly EditType[]): Thenable; /** * Undo a set of edits. * * This is triggered when a user undoes an edit or when revert is called on a file. * - * @param resource Resource being edited. * @param edit Array of edits. Sorted from most recent to oldest. * * @return Thenable signaling that the change has completed. */ - undoEdits(resource: Uri, edits: readonly EditType[]): Thenable; + undoEdits(edits: readonly EditType[]): Thenable; /** - * Back up `resource` in its current state. + * Back up the resource in its current state. * * Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in * its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in @@ -1257,57 +1269,159 @@ declare module 'vscode' { * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when * `auto save` is enabled (since auto save already persists resource ). * - * @param resource The resource to back up. * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your * extension to decided how to respond to cancellation. If for example your extension is backing up a large file * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather * than cancelling it to ensure that VS Code has some valid backup. */ - backup?(resource: Uri, cancellation: CancellationToken): Thenable; + backup(cancellation: CancellationToken): Thenable; } + /** + * Represents a custom document for a custom webview editor. + * + * Custom documents are only used within a given `WebviewCustomEditorProvider`. The lifecycle of a + * `WebviewEditorCustomDocument` is managed by VS Code. When more more references remain to a given `WebviewEditorCustomDocument` + * then it is disposed of. + * + * @param UserDataType Type of custom object that extensions can store on the document. + */ + interface WebviewEditorCustomDocument { + /** + * The associated viewType for this document. + */ + readonly viewType: string; + + /** + * The associated uri for this document. + */ + readonly uri: Uri; + + /** + * Event fired when there are no more references to the `WebviewEditorCustomDocument`. + */ + readonly onDidDispose: Event; + + /** + * Custom data that an extension can store on the document. + */ + readonly userData: UserDataType; + + // TODO: Should we expose edits here? + // This could be helpful for tracking the life cycle of edits + } + + /** + * Provider for webview editors that use a custom data model. + * + * Custom webview editors use [`WebviewEditorCustomDocument`](#WebviewEditorCustomDocument) as their data model. + * This gives extensions full control over actions such as edit, save, and backup. + * + * You should use custom text based editors when dealing with binary files or more complex scenarios. For simple text + * based documents, use [`WebviewTextEditorProvider`](#WebviewTextEditorProvider) instead. + */ export interface WebviewCustomEditorProvider { + /** + * Create the model for a given + * + * @param document Resource being resolved. + */ + provideWebviewCustomEditorDocument(resource: Uri): Thenable; + /** * Resolve a webview editor for a given resource. * * To resolve a webview editor, a provider must fill in its initial html content and hook up all * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. * - * @param resource Resource being resolved. + * @param document Document for resource being resolved. * @param webview Webview being resolved. The provider should take ownership of this webview. * * @return Thenable indicating that the webview editor has been resolved. */ - resolveWebviewEditor( - resource: Uri, - webview: WebviewPanel, - ): Thenable; + resolveWebviewCustomEditor(document: WebviewEditorCustomDocument, webview: WebviewPanel): Thenable; + } + /** + * Provider for text based webview editors. + * + * Text based webview editors use a [`TextDocument`](#TextDocument) as their data model. This considerably simplifies + * implementing a webview editor as it allows VS Code to handle many common operations such as + * undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`. + * + * You should use text based webview editors when dealing with text based file formats, such as `xml` or `json`. + * For binary files or more specialized use cases, see [WebviewCustomEditorProvider](#WebviewCustomEditorProvider). + */ + export interface WebviewTextEditorProvider { /** - * Controls the editing functionality of a webview editor. This allows the webview editor to hook into standard - * editor events such as `undo` or `save`. + * Resolve a webview editor for a given resource. * - * WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact - * with readonly editors, but these editors will not integrate with VS Code's standard editor functionality. + * To resolve a webview editor, the provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider should also take ownership of the passed in `WebviewPanel`. + * + * @param document Resource being resolved. + * @param webview Webview being resolved. The provider should take ownership of this webview. + * + * @return Thenable indicating that the webview editor has been resolved. */ - readonly editingDelegate?: WebviewCustomEditorEditingDelegate; + resolveWebviewTextEditor(document: TextDocument, webview: WebviewPanel): Thenable; } namespace window { /** - * Register a new provider for webview editors of a given type. + * Register a new provider for text based webview editors. * - * @param viewType Type of the webview editor provider. - * @param provider Resolves webview editors. - * @param options Content settings for a webview panels the provider is given. + * @param viewType Type of the webview editor provider. This should match the `viewType` from the + * `package.json` contributions + * @param provider Provider that resolves webview editors. + * @param webviewOptions Content settings for the webview panels that provider is given. + * + * @return Disposable that unregisters the `WebviewTextEditorProvider`. + */ + export function registerWebviewTextEditorProvider( + viewType: string, + provider: WebviewTextEditorProvider, + webviewOptions?: WebviewPanelOptions, + ): Disposable; + + /** + * Register a new provider for custom webview editors. + * + * @param viewType Type of the webview editor provider. This should match the `viewType` from the + * `package.json` contributions. + * @param provider Provider that resolves webview editors. + * @param webviewOptions Content settings for the webview panels that provider is given. * * @return Disposable that unregisters the `WebviewCustomEditorProvider`. */ export function registerWebviewCustomEditorProvider( viewType: string, provider: WebviewCustomEditorProvider, - options?: WebviewPanelOptions, + webviewOptions?: WebviewPanelOptions, ): Disposable; + + /** + * Create a new `WebviewEditorCustomDocument`. + * + * Note that this method only creates a custom document object. To have it be registered with VS Code, you + * must return the document from `WebviewCustomEditorProvider.provideWebviewCustomEditorDocument`. + * + * @param viewType Type of the webview editor provider. This should match the `viewType` from the + * `package.json` contributions. + * @param uri The document's resource. + * @param userData Custom data attached to the document. + * @param capabilities Controls the editing functionality of a webview editor. This allows the webview + * editor to hook into standard editor events such as `undo` or `save`. + * + * WebviewEditors that do not have `editingCapability` are considered to be readonly. Users can still interact + * with readonly editors, but these editors will not integrate with VS Code's standard editor functionality. + */ + export function createWebviewEditorCustomDocument( + viewType: string, + uri: Uri, + userData: UserDataType, + capabilities: WebviewCustomEditorCapabilities + ): WebviewEditorCustomDocument; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index aa56ff1725..22898f0f97 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -19,7 +19,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import * as callh from 'vs/workbench/contrib/callHierarchy/browser/callHierarchy'; +import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { mixin } from 'vs/base/common/objects'; import { decodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens'; @@ -273,7 +273,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- quick fix - $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void { + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void { this._registrations.set(handle, modes.CodeActionProviderRegistry.register(selector, { provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token); @@ -290,7 +290,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; }, providedCodeActionKinds: metadata.providedKinds, - documentation: metadata.documentation + documentation: metadata.documentation, + displayName })); } diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 069644a341..00fbba0cb6 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -512,7 +512,7 @@ export class MainThreadTask implements MainThreadTaskShape { public $executeTask(value: TaskHandleDTO | TaskDTO): Promise { return new Promise((resolve, reject) => { if (TaskHandleDTO.is(value)) { - const workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); + const workspaceFolder = typeof value.workspaceFolder === 'string' ? value.workspaceFolder : this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); if (workspaceFolder) { this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task | undefined) => { if (!task) { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 580f2fe2b9..a2d20294ea 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -21,7 +21,7 @@ import * as extHostProtocol from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CustomEditorInput, ModelType } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewExtensionDescription } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; @@ -97,7 +97,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma private readonly _webviewInputs = new WebviewInputStore(); private readonly _revivers = new Map(); private readonly _editorProviders = new Map(); - private readonly _customEditorModels = new Map(); + private readonly _customEditorModels = new Map(); constructor( context: extHostProtocol.IExtHostContext, @@ -121,7 +121,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma // This should trigger the real reviver to be registered from the extension host side. this._register(_webviewWorkbenchService.registerResolver({ canResolve: (webview: WebviewInput) => { - if (webview instanceof CustomFileEditorInput) { + if (webview instanceof CustomEditorInput) { extensionService.activateByEvent(`onWebviewEditor:${webview.viewType}`); return false; } @@ -256,7 +256,20 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._revivers.delete(viewType); } - public $registerEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): void { + public $registerTextEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { + return this.registerEditorProvider(ModelType.Text, extensionData, viewType, options); + } + + public $registerCustomEditorProvider(extensionData: extHostProtocol.WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void { + return this.registerEditorProvider(ModelType.Custom, extensionData, viewType, options); + } + + public registerEditorProvider( + modelType: ModelType, + extensionData: extHostProtocol.WebviewExtensionDescription, + viewType: string, + options: modes.IWebviewPanelOptions, + ): void { if (this._editorProviders.has(viewType)) { throw new Error(`Provider for ${viewType} already registered`); } @@ -265,21 +278,25 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.set(viewType, this._webviewWorkbenchService.registerResolver({ canResolve: (webviewInput) => { - return webviewInput instanceof CustomFileEditorInput && webviewInput.viewType === viewType; + return webviewInput instanceof CustomEditorInput && webviewInput.viewType === viewType; }, - resolveWebview: async (webviewInput: CustomFileEditorInput) => { + resolveWebview: async (webviewInput: CustomEditorInput) => { const handle = webviewInput.id; this._webviewInputs.add(handle, webviewInput); this.hookupWebviewEventDelegate(handle, webviewInput); webviewInput.webview.options = options; webviewInput.webview.extension = extension; + webviewInput.modelType = modelType; + const resource = webviewInput.resource; - const model = await this.retainCustomEditorModel(webviewInput, resource, viewType, capabilities); - webviewInput.onDisposeWebview(() => { - this.releaseCustomEditorModel(model); - }); + if (modelType === ModelType.Custom) { + const model = await this.retainCustomEditorModel(webviewInput, resource, viewType); + webviewInput.onDisposeWebview(() => { + this.releaseCustomEditorModel(model); + }); + } try { await this._proxy.$resolveWebviewEditor( @@ -311,33 +328,26 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._customEditorService.models.disposeAllModelsForView(viewType); } - private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) { + private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string) { const model = await this._customEditorService.models.resolve(webviewInput.resource, webviewInput.viewType); - const existingEntry = this._customEditorModels.get(model); + const key = viewType + resource.toString(); + const existingEntry = this._customEditorModels.get(key); if (existingEntry) { ++existingEntry.referenceCount; // no need to hook up listeners again return model; } + this._customEditorModels.set(key, { referenceCount: 1 }); + const { editable } = await this._proxy.$createWebviewCustomEditorDocument(resource, viewType); - this._customEditorModels.set(model, { referenceCount: 1 }); - - const capabilitiesSet = new Set(capabilities); - const isEditable = capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable); - if (isEditable) { - model.onUndo(e => { - this._proxy.$undoEdits(resource, viewType, e.edits); + if (editable) { + model.onUndo(() => { + this._proxy.$undo(resource, viewType); }); - model.onDisposeEdits(e => { - this._proxy.$disposeEdits(e.edits); - }); - - model.onApplyEdit(e => { - if (e.trigger !== model) { - this._proxy.$applyEdits(resource, viewType, e.edits); - } + model.onRedo(() => { + this._proxy.$redo(resource, viewType); }); model.onWillSave(e => { @@ -347,7 +357,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma // Save as should always be implemented even if the model is readonly model.onWillSaveAs(e => { - if (isEditable) { + if (editable) { e.waitUntil(this._proxy.$onSaveAs(e.resource.toJSON(), viewType, e.targetResource.toJSON())); } else { // Since the editor is readonly, just copy the file over @@ -355,36 +365,37 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma } }); - if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.SupportsHotExit)) { - model.onBackup(() => { - return createCancelablePromise(token => - this._proxy.$backup(model.resource.toJSON(), viewType, token)); - }); - } + model.onBackup(() => { + return createCancelablePromise(token => + this._proxy.$backup(model.resource.toJSON(), viewType, token)); + }); return model; } private async releaseCustomEditorModel(model: ICustomEditorModel) { - const entry = this._customEditorModels.get(model); + const key = model.viewType + model.resource; + const entry = this._customEditorModels.get(key); if (!entry) { - return; + throw new Error('Model not found'); } --entry.referenceCount; if (entry.referenceCount <= 0) { + this._proxy.$disposeWebviewCustomEditorDocument(model.resource, model.viewType); this._customEditorService.models.disposeModel(model); - this._customEditorModels.delete(model); + this._customEditorModels.delete(key); } } - public $onEdit(resource: UriComponents, viewType: string, editId: number): void { + + + public $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }) { const model = this._customEditorService.models.get(URI.revive(resource), viewType); if (!model) { throw new Error('Could not find model for webview editor'); } - - model.pushEdit(editId, model); + model.setDirty(state.dirty); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 7571da0d4b..06243a4814 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -114,7 +114,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService)); // manually create and register addressable instances - const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService)); const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); @@ -135,6 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostTheming = rpcProtocol.set(ExtHostContext.ExtHostTheming, new ExtHostTheming(rpcProtocol)); const extHostAuthentication = rpcProtocol.set(ExtHostContext.ExtHostAuthentication, new ExtHostAuthentication(rpcProtocol)); const extHostTimeline = rpcProtocol.set(ExtHostContext.ExtHostTimeline, new ExtHostTimeline(rpcProtocol, extHostCommands)); + const extHostWebviews = rpcProtocol.set(ExtHostContext.ExtHostWebviews, new ExtHostWebviews(rpcProtocol, initData.environment, extHostWorkspace, extHostLogService, extHostApiDeprecation, extHostDocuments)); // Check that no named customers are missing // {{SQL CARBON EDIT}} filter out the services we don't expose @@ -564,10 +564,18 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => { return extHostWebviews.registerWebviewPanelSerializer(extension, viewType, serializer); }, + registerWebviewTextEditorProvider: (viewType: string, provider: vscode.WebviewTextEditorProvider, options?: vscode.WebviewPanelOptions) => { + checkProposedApiEnabled(extension); + return extHostWebviews.registerWebviewTextEditorProvider(extension, viewType, provider, options); + }, registerWebviewCustomEditorProvider: (viewType: string, provider: vscode.WebviewCustomEditorProvider, options?: vscode.WebviewPanelOptions) => { checkProposedApiEnabled(extension); return extHostWebviews.registerWebviewCustomEditorProvider(extension, viewType, provider, options); }, + createWebviewEditorCustomDocument: (viewType: string, resource: vscode.Uri, userData: UserDataType, capabilities: vscode.WebviewCustomEditorCapabilities) => { + checkProposedApiEnabled(extension); + return extHostWebviews.createWebviewEditorCustomDocument(viewType, resource, userData, capabilities); + }, registerDecorationProvider(provider: vscode.DecorationProvider) { checkProposedApiEnabled(extension); return extHostDecorations.registerDecorationProvider(provider, extension.identifier); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index e134b58a52..7f4fc54d88 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,6 +51,8 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineCursor, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; +import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; +import { Dto } from 'vs/base/common/types'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -363,7 +365,7 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerEvaluatableExpressionProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerDocumentHighlightProvider(handle: number, selector: IDocumentFilterDto[]): void; $registerReferenceSupport(handle: number, selector: IDocumentFilterDto[]): void; - $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto): void; + $registerQuickFixSupport(handle: number, selector: IDocumentFilterDto[], metadata: ICodeActionProviderMetadataDto, displayName: string): void; $registerDocumentFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerRangeFormattingSupport(handle: number, selector: IDocumentFilterDto[], extensionId: ExtensionIdentifier, displayName: string): void; $registerOnTypeFormattingSupport(handle: number, selector: IDocumentFilterDto[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; @@ -574,11 +576,6 @@ export interface WebviewExtensionDescription { readonly location: UriComponents; } -export enum WebviewEditorCapabilities { - Editable, - SupportsHotExit, -} - export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; @@ -594,10 +591,11 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; - $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly WebviewEditorCapabilities[]): void; + $registerTextEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; + $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; - $onEdit(resource: UriComponents, viewType: string, editId: number): void; + $onDidChangeCustomDocumentState(resource: UriComponents, viewType: string, state: { dirty: boolean }): void; } export interface WebviewPanelViewStateData { @@ -615,12 +613,14 @@ export interface ExtHostWebviewsShape { $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise<{ editable: boolean }>; + $disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise; - $undoEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void; - $applyEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void; - $disposeEdits(editIds: readonly number[]): void; - + $undo(resource: UriComponents, viewType: string): void; + $redo(resource: UriComponents, viewType: string): void; + $revert(resource: UriComponents, viewType: string): void; $onSave(resource: UriComponents, viewType: string): Promise; $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; @@ -1194,16 +1194,7 @@ export interface ICodeLensDto { command?: ICommandDto; } -export interface ICallHierarchyItemDto { - _sessionId: string; - _itemId: string; - kind: modes.SymbolKind; - name: string; - detail?: string; - uri: UriComponents; - range: IRange; - selectionRange: IRange; -} +export type ICallHierarchyItemDto = Dto; export interface IIncomingCallDto { from: ICallHierarchyItemDto; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index b5acd16a40..8b5cf49a53 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1617,7 +1617,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF kind: x.kind.value, command: this._commands.converter.toInternal(x.command, store), })) - }); + }, ExtHostLanguageFeatures._extLabel(extension)); store.add(this._createDisposable(handle)); return store; } diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index ca5c8a8cdf..47f1b98917 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -26,6 +26,7 @@ import { Schemas } from 'vs/base/common/network'; import * as Platform from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; export interface IExtHostTask extends ExtHostTaskShape { @@ -192,9 +193,11 @@ export namespace CustomExecutionDTO { export namespace TaskHandleDTO { export function from(value: types.Task): tasks.TaskHandleDTO { - let folder: UriComponents | undefined; + let folder: UriComponents | string; if (value.scope !== undefined && typeof value.scope !== 'number') { folder = value.scope.uri; + } else if (value.scope !== undefined && typeof value.scope === 'number') { + folder = USER_TASKS_GROUP_KEY; } return { id: value._id!, diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 163eebdfe6..69eab38329 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -10,15 +11,15 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import type * as vscode from 'vscode'; -import { Cache } from './cache'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewExtensionDescription, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; -import { CancellationToken } from 'vs/base/common/cancellation'; type IconPath = URI | { light: URI, dark: URI }; @@ -37,7 +38,7 @@ export class ExtHostWebview implements vscode.Webview { private readonly _initData: WebviewInitData, private readonly _workspace: IExtHostWorkspace | undefined, private readonly _extension: IExtensionDescription, - private readonly _logService: ILogService, + private readonly _deprecationService: IExtHostApiDeprecationService, ) { } public dispose() { @@ -63,11 +64,10 @@ export class ExtHostWebview implements vscode.Webview { this.assertNotDisposed(); if (this._html !== value) { this._html = value; - if (this._initData.isExtensionDevelopmentDebug && !this._hasCalledAsWebviewUri) { - if (/(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { - this._hasCalledAsWebviewUri = true; - this._logService.warn(`${this._extension.identifier.value} created a webview that appears to use the vscode-resource scheme directly. Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); - } + if (!this._hasCalledAsWebviewUri && /(["'])vscode-resource:([^\s'"]+?)(["'])/i.test(value)) { + this._hasCalledAsWebviewUri = true; + this._deprecationService.report('Webview vscode-resource: uris', this._extension, + `Please migrate to use the 'webview.asWebviewUri' api instead: https://aka.ms/vscode-webview-use-aswebviewuri`); } this._proxy.$setHtml(this._handle, value); } @@ -245,6 +245,193 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } } +type EditType = unknown; + +class WebviewEditorCustomDocument extends Disposable implements vscode.WebviewEditorCustomDocument { + private _currentEditIndex: number = -1; + private _savePoint: number = -1; + private readonly _edits: Array = []; + + constructor( + private readonly _proxy: MainThreadWebviewsShape, + public readonly viewType: string, + public readonly uri: vscode.Uri, + public readonly userData: unknown, + public readonly _capabilities: vscode.WebviewCustomEditorCapabilities, + ) { + super(); + + // Hook up events + _capabilities.editing?.onDidEdit(edit => { + this.pushEdit(edit, this); + }); + } + + //#region Public API + + #_onDidDispose = this._register(new Emitter()); + public readonly onDidDispose = this.#_onDidDispose.event; + + //#endregion + + dispose() { + this.#_onDidDispose.fire(); + super.dispose(); + } + + private pushEdit(edit: EditType, trigger: any) { + this.spliceEdits(edit); + + this._currentEditIndex = this._edits.length - 1; + this.updateState(); + // this._onApplyEdit.fire({ edits: [edit], trigger }); + } + + private updateState() { + const dirty = this._edits.length > 0 && this._savePoint !== this._currentEditIndex; + this._proxy.$onDidChangeCustomDocumentState(this.uri, this.viewType, { dirty }); + } + + private spliceEdits(editToInsert?: EditType) { + const start = this._currentEditIndex + 1; + const toRemove = this._edits.length - this._currentEditIndex; + + editToInsert + ? this._edits.splice(start, toRemove, editToInsert) + : this._edits.splice(start, toRemove); + } + + revert() { + const editing = this.getEditingCapability(); + if (this._currentEditIndex === this._savePoint) { + return true; + } + + if (this._currentEditIndex >= this._savePoint) { + const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex); + editing.undoEdits(editsToUndo.reverse()); + } else if (this._currentEditIndex < this._savePoint) { + const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); + editing.applyEdits(editsToRedo); + } + + this._currentEditIndex = this._savePoint; + this.spliceEdits(); + + this.updateState(); + return true; + } + + undo() { + const editing = this.getEditingCapability(); + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } + + const undoneEdit = this._edits[this._currentEditIndex]; + --this._currentEditIndex; + editing.undoEdits([undoneEdit]); + this.updateState(); + } + + redo() { + const editing = this.getEditingCapability(); + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + ++this._currentEditIndex; + const redoneEdit = this._edits[this._currentEditIndex]; + editing.applyEdits([redoneEdit]); + this.updateState(); + } + + save() { + return this.getEditingCapability().save(); + } + + saveAs(target: vscode.Uri) { + return this.getEditingCapability().saveAs(target); + } + + backup(cancellation: CancellationToken): boolean | PromiseLike { + throw new Error('Method not implemented.'); + } + + private getEditingCapability(): vscode.WebviewCustomEditorEditingCapability { + if (!this._capabilities.editing) { + throw new Error('Document is not editable'); + } + return this._capabilities.editing; + } +} + +class WebviewDocumentStore { + private readonly _documents = new Map(); + + public get(viewType: string, resource: vscode.Uri): WebviewEditorCustomDocument | undefined { + return this._documents.get(this.key(viewType, resource)); + } + + public add(document: WebviewEditorCustomDocument) { + const key = this.key(document.viewType, document.uri); + if (this._documents.has(key)) { + throw new Error(`Document already exists for viewType:${document.viewType} resource:${document.uri}`); + } + this._documents.set(key, document); + } + + public delete(document: WebviewEditorCustomDocument) { + const key = this.key(document.viewType, document.uri); + this._documents.delete(key); + } + + private key(viewType: string, resource: vscode.Uri): string { + return `${viewType}@@@${resource.toString}`; + } +} + +const enum WebviewEditorType { + Text, + Custom +} + +type ProviderEntry = { + readonly extension: IExtensionDescription; + readonly type: WebviewEditorType.Text; + readonly provider: vscode.WebviewTextEditorProvider; +} | { + readonly extension: IExtensionDescription; + readonly type: WebviewEditorType.Custom; + readonly provider: vscode.WebviewCustomEditorProvider; +}; + +class EditorProviderStore { + private readonly _providers = new Map(); + + public addTextProvider(viewType: string, extension: IExtensionDescription, provider: vscode.WebviewTextEditorProvider): vscode.Disposable { + return this.add(WebviewEditorType.Text, viewType, extension, provider); + } + + public addCustomProvider(viewType: string, extension: IExtensionDescription, provider: vscode.WebviewCustomEditorProvider): vscode.Disposable { + return this.add(WebviewEditorType.Custom, viewType, extension, provider); + } + + public get(viewType: string): ProviderEntry | undefined { + return this._providers.get(viewType); + } + + private add(type: WebviewEditorType, viewType: string, extension: IExtensionDescription, provider: vscode.WebviewTextEditorProvider | vscode.WebviewCustomEditorProvider): vscode.Disposable { + if (this._providers.has(viewType)) { + throw new Error(`Provider for viewType:${viewType} already registered`); + } + this._providers.set(viewType, { type, extension, provider } as ProviderEntry); + return new VSCodeDisposable(() => this._providers.delete(viewType)); + } +} + export class ExtHostWebviews implements ExtHostWebviewsShape { private static newHandle(): WebviewPanelHandle { @@ -259,18 +446,17 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { readonly extension: IExtensionDescription; }>(); - private readonly _editorProviders = new Map(); + private readonly _editorProviders = new EditorProviderStore(); - private readonly _edits = new Cache('edits'); + private readonly _documents = new WebviewDocumentStore(); constructor( mainContext: IMainContext, private readonly initData: WebviewInitData, private readonly workspace: IExtHostWorkspace | undefined, private readonly _logService: ILogService, + private readonly _deprecationService: IExtHostApiDeprecationService, + private readonly _extHostDocuments: ExtHostDocuments, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } @@ -289,9 +475,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }; const handle = ExtHostWebviews.newHandle(); - this._proxy.$createWebviewPanel({ id: extension.identifier, location: extension.extensionLocation }, handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); + this._proxy.$createWebviewPanel(toExtensionData(extension), handle, viewType, title, webviewShowOptions, convertWebviewOptions(extension, this.workspace, options)); - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const panel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, viewColumn, options, webview); this._webviewPanels.set(handle, panel); return panel; @@ -315,31 +501,45 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { }); } + public registerWebviewTextEditorProvider( + extension: IExtensionDescription, + viewType: string, + provider: vscode.WebviewTextEditorProvider, + options: vscode.WebviewPanelOptions | undefined = {} + ): vscode.Disposable { + const unregisterProvider = this._editorProviders.addTextProvider(viewType, extension, provider); + this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options); + + return new VSCodeDisposable(() => { + unregisterProvider.dispose(); + this._proxy.$unregisterEditorProvider(viewType); + }); + } + public registerWebviewCustomEditorProvider( extension: IExtensionDescription, viewType: string, provider: vscode.WebviewCustomEditorProvider, - options?: vscode.WebviewPanelOptions, + options: vscode.WebviewPanelOptions | undefined = {}, ): vscode.Disposable { - if (this._editorProviders.has(viewType)) { - throw new Error(`Editor provider for '${viewType}' already registered`); - } - this._editorProviders.set(viewType, { extension, provider, }); - - this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}, this.getCapabilites(provider)); - - // Hook up events - provider?.editingDelegate?.onEdit(({ edit, resource }) => { - const id = this._edits.add([edit]); - this._proxy.$onEdit(resource, viewType, id); - }); + const unregisterProvider = this._editorProviders.addCustomProvider(viewType, extension, provider); + this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); return new VSCodeDisposable(() => { - this._editorProviders.delete(viewType); + unregisterProvider.dispose(); this._proxy.$unregisterEditorProvider(viewType); }); } + public createWebviewEditorCustomDocument( + viewType: string, + resource: vscode.Uri, + userData: UserDataType, + capabilities: vscode.WebviewCustomEditorCapabilities, + ): vscode.WebviewEditorCustomDocument { + return Object.seal(new WebviewEditorCustomDocument(this._proxy, viewType, resource, userData, capabilities) as vscode.WebviewEditorCustomDocument); + } + public $onMessage( handle: WebviewPanelHandle, message: any @@ -414,12 +614,46 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const { serializer, extension } = entry; - const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(webviewHandle, this._proxy, options, this.initData, this.workspace, extension, this._deprecationService); const revivedPanel = new ExtHostWebviewEditor(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); await serializer.deserializeWebviewPanel(revivedPanel, state); } + async $createWebviewCustomEditorDocument(resource: UriComponents, viewType: string) { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (entry.type !== WebviewEditorType.Custom) { + throw new Error(`Invalid provide type for '${viewType}'`); + } + + const revivedResource = URI.revive(resource); + const document = await entry.provider.provideWebviewCustomEditorDocument(revivedResource) as WebviewEditorCustomDocument; + this._documents.add(document); + return { + editable: !!document._capabilities.editing + }; + } + + async $disposeWebviewCustomEditorDocument(resource: UriComponents, viewType: string): Promise { + const entry = this._editorProviders.get(viewType); + if (!entry) { + throw new Error(`No provider found for '${viewType}'`); + } + + if (entry.type !== WebviewEditorType.Custom) { + throw new Error(`Invalid provider type for '${viewType}'`); + } + + const revivedResource = URI.revive(resource); + const document = this.getDocument(viewType, revivedResource); + this._documents.delete(document); + document.dispose(); + } + async $resolveWebviewEditor( resource: UriComponents, handle: WebviewPanelHandle, @@ -430,81 +664,79 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { ): Promise { const entry = this._editorProviders.get(viewType); if (!entry) { - return Promise.reject(new Error(`No provider found for '${viewType}'`)); + throw new Error(`No provider found for '${viewType}'`); } - const { provider, extension } = entry; - const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, extension, this._logService); + const webview = new ExtHostWebview(handle, this._proxy, options, this.initData, this.workspace, entry.extension, this._deprecationService); const revivedPanel = new ExtHostWebviewEditor(handle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(handle, revivedPanel); + const revivedResource = URI.revive(resource); - await provider.resolveWebviewEditor(revivedResource, revivedPanel); - } - $undoEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void { - const provider = this.getEditorProvider(viewType); - if (!provider?.editingDelegate) { - return; - } - - const resource = URI.revive(resourceComponents); - const edits = editIds.map(id => this._edits.get(id, 0)); - provider.editingDelegate.undoEdits(resource, edits); - } - - $applyEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void { - const provider = this.getEditorProvider(viewType); - if (!provider?.editingDelegate) { - return; - } - - const resource = URI.revive(resourceComponents); - const edits = editIds.map(id => this._edits.get(id, 0)); - provider.editingDelegate.applyEdits(resource, edits); - } - - $disposeEdits(editIds: readonly number[]): void { - for (const edit of editIds) { - this._edits.delete(edit); + switch (entry.type) { + case WebviewEditorType.Custom: + { + const document = this.getDocument(viewType, revivedResource); + return entry.provider.resolveWebviewCustomEditor(document, revivedPanel); + } + case WebviewEditorType.Text: + { + await this._extHostDocuments.ensureDocumentData(revivedResource); + const document = this._extHostDocuments.getDocument(revivedResource); + return entry.provider.resolveWebviewTextEditor(document, revivedPanel); + } + default: + { + throw new Error('Unknown webview provider type'); + } } } - async $onSave(resource: UriComponents, viewType: string): Promise { - const provider = this.getEditorProvider(viewType); - return provider?.editingDelegate?.save(URI.revive(resource)); + async $undo(resourceComponents: UriComponents, viewType: string): Promise { + const document = this.getDocument(viewType, resourceComponents); + document.undo(); } - async $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise { - const provider = this.getEditorProvider(viewType); - return provider?.editingDelegate?.saveAs(URI.revive(resource), URI.revive(targetResource)); + async $redo(resourceComponents: UriComponents, viewType: string): Promise { + const document = this.getDocument(viewType, resourceComponents); + document.redo(); } - async $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise { - const provider = this.getEditorProvider(viewType); - if (!provider?.editingDelegate?.backup) { - return false; - } - return provider.editingDelegate.backup(URI.revive(resource), cancellation); + async $revert(resourceComponents: UriComponents, viewType: string): Promise { + const document = this.getDocument(viewType, resourceComponents); + document.revert(); + } + + async $onSave(resourceComponents: UriComponents, viewType: string): Promise { + const document = this.getDocument(viewType, resourceComponents); + document.save(); + } + + async $onSaveAs(resourceComponents: UriComponents, viewType: string, targetResource: UriComponents): Promise { + const document = this.getDocument(viewType, resourceComponents); + return document.saveAs(URI.revive(targetResource)); + } + + async $backup(resourceComponents: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const document = this.getDocument(viewType, resourceComponents); + return document.backup(cancellation); } private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } - private getEditorProvider(viewType: string): vscode.WebviewCustomEditorProvider | undefined { - return this._editorProviders.get(viewType)?.provider; + private getDocument(viewType: string, resource: UriComponents): WebviewEditorCustomDocument { + const document = this._documents.get(viewType, URI.revive(resource)); + if (!document) { + throw new Error('No webview editor custom document found'); + } + return document; } +} - private getCapabilites(capabilities: vscode.WebviewCustomEditorProvider) { - const declaredCapabilites: WebviewEditorCapabilities[] = []; - if (capabilities.editingDelegate) { - declaredCapabilites.push(WebviewEditorCapabilities.Editable); - } - if (capabilities.editingDelegate?.backup) { - declaredCapabilites.push(WebviewEditorCapabilities.SupportsHotExit); - } - return declaredCapabilites; - } +function toExtensionData(extension: IExtensionDescription): WebviewExtensionDescription { + return { id: extension.identifier, location: extension.extensionLocation }; } function convertWebviewOptions( diff --git a/src/vs/workbench/api/common/shared/tasks.ts b/src/vs/workbench/api/common/shared/tasks.ts index 57052f322a..fc913edd5c 100644 --- a/src/vs/workbench/api/common/shared/tasks.ts +++ b/src/vs/workbench/api/common/shared/tasks.ts @@ -78,7 +78,7 @@ export interface TaskSourceDTO { export interface TaskHandleDTO { id: string; - workspaceFolder: UriComponents; + workspaceFolder: UriComponents | string; } export interface TaskDTO { diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index cfdc31402c..0e227d2bef 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; +import { posix } from 'vs/base/common/path'; +import { dirname, isEqual, basenameOrAuthority } from 'vs/base/common/resources'; import { IconLabel, IIconLabelValueOptions, IIconLabelCreationOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -323,7 +324,7 @@ class ResourceLabelWidget extends IconLabel { } notifyUntitledLabelChange(resource: URI): void { - if (resources.isEqual(resource, this.label?.resource)) { + if (isEqual(resource, this.label?.resource)) { this.render(false); } } @@ -340,19 +341,19 @@ class ResourceLabelWidget extends IconLabel { } if (!name) { - name = resources.basenameOrAuthority(resource); + name = basenameOrAuthority(resource); } } let description: string | undefined; if (!options?.hidePath) { - description = this.labelService.getUriLabel(resources.dirname(resource), { relative: true }); + description = this.labelService.getUriLabel(dirname(resource), { relative: true }); } this.setResource({ resource, name, description }, options); } - setResource(label: IResourceLabelProps, options?: IResourceLabelOptions): void { + setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void { if (label.resource?.scheme === Schemas.untitled) { // Untitled labels are very dynamic because they may change // whenever the content changes (unless a path is associated). @@ -368,17 +369,20 @@ class ResourceLabelWidget extends IconLabel { } if (typeof label.description === 'string') { - let untitledDescription: string; - if (untitledModel.hasAssociatedFilePath) { - untitledDescription = this.labelService.getUriLabel(resources.dirname(untitledModel.resource), { relative: true }); - } else { - untitledDescription = untitledModel.resource.path; - } - + let untitledDescription = untitledModel.resource.path; if (label.name !== untitledDescription) { label.description = untitledDescription; + } else if (label.description === posix.sep) { + label.description = undefined; // unset showing just "/" for untitled without associated resource } } + + let untitledTitle = untitledModel.resource.path; + if (untitledModel.name !== untitledTitle) { + options.title = `${untitledModel.name} • ${untitledTitle}`; + } else { + options.title = untitledTitle; + } } } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index ad445d4650..86aefd03c8 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -59,8 +59,16 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { } else { this.moveComposite(dragData.id, targetCompositeId); } + } else { + const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors; + if (draggedViews.length === 1 && draggedViews[0].canMoveView) { + dragData.type = 'view'; + dragData.id = draggedViews[0].id; + } } - } else { + } + + if (dragData.type === 'view') { const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id); if (viewDescriptor && viewDescriptor.canMoveView) { if (targetCompositeId) { @@ -105,7 +113,21 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop { // ... across view containers but without a destination composite if (!targetCompositeId) { - return false; + const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors; + if (draggedViews.some(vd => !vd.canMoveView)) { + return false; + } + + if (draggedViews.length !== 1) { + return false; + } + + const defaultLocation = viewContainerRegistry.getViewContainerLocation(this.viewDescriptorService.getDefaultContainer(draggedViews[0].id)!); + if (this.targetContainerLocation === ViewContainerLocation.Sidebar && this.targetContainerLocation !== defaultLocation) { + return false; + } + + return true; } // ... from panel to the sidebar @@ -241,10 +263,7 @@ export class CompositeBar extends Widget implements ICompositeBar { const draggedCompositeId = data[0].id; this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); - const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1]; - if (targetItem && targetItem.id !== draggedCompositeId) { - this.move(draggedCompositeId, targetItem.id); - } + this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e); } } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index b53f8b3543..e972355616 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -59,6 +59,7 @@ export abstract class CompositePart extends Part { protected readonly onDidCompositeClose = this._register(new Emitter()); protected toolBar: ToolBar | undefined; + protected titleLabelElement: HTMLElement | undefined; private mapCompositeToCompositeContainer = new Map(); private mapActionsBindingToComposite = new Map void>(); @@ -402,6 +403,7 @@ export abstract class CompositePart extends Part { protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel { const titleContainer = append(parent, $('.title-label')); const titleLabel = append(titleContainer, $('h2')); + this.titleLabelElement = titleLabel; const $this = this; return { diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 2fbecf1ba4..631c1361c9 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -16,6 +16,9 @@ import { Event } from 'vs/base/common/event'; import { isEmptyObject } from 'vs/base/common/types'; import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { MementoObject } from 'vs/workbench/common/memento'; +import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { isLinux } from 'vs/base/common/platform'; +import { indexOfPath } from 'vs/base/common/extpath'; /** * The base class of editors in the workbench. Editors register themselves for specific editor inputs. @@ -249,6 +252,34 @@ export class EditorMemento implements IEditorMemento { } } + moveEditorState(source: URI, target: URI): void { + const cache = this.doLoad(); + + const cacheKeys = cache.keys(); + for (const cacheKey of cacheKeys) { + const resource = URI.parse(cacheKey); + + if (!isEqualOrParent(resource, source)) { + continue; // not matching our resource + } + + // Determine new resulting target resource + let targetResource: URI; + if (source.toString() === resource.toString()) { + targetResource = target; // file got moved + } else { + const index = indexOfPath(resource.path, source.path, !isLinux); + targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved + } + + const value = cache.get(cacheKey); + if (value) { + cache.delete(cacheKey); + cache.set(targetResource.toString(), value); + } + } + } + private doGetResource(resourceOrEditor: URI | EditorInput): URI | undefined { if (resourceOrEditor instanceof EditorInput) { return resourceOrEditor.resource; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index f232e58312..0e70ae4c85 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -1227,7 +1227,9 @@ export class ChangeEOLAction extends Action { const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget); if (activeCodeEditor?.hasModel() && !this.editorService.activeEditor?.isReadonly()) { textModel = activeCodeEditor.getModel(); + textModel.pushStackElement(); textModel.pushEOL(eol.eol); + textModel.pushStackElement(); } } } diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 1d387d9245..9168bb92b6 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -204,9 +204,6 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return this.editorControl; } - /** - * Saves the text editor view state for the given resource. - */ protected saveTextEditorViewState(resource: URI): void { const editorViewState = this.retrieveTextEditorViewState(resource); if (!editorViewState || !this.group) { @@ -248,22 +245,20 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { return control.saveViewState(); } - /** - * Clears the text editor view state for the given resources. - */ + protected loadTextEditorViewState(resource: URI): IEditorViewState | undefined { + return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined; + } + + protected moveTextEditorViewState(source: URI, target: URI): void { + return this.editorMemento.moveEditorState(source, target); + } + protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void { resources.forEach(resource => { this.editorMemento.clearEditorState(resource, group); }); } - /** - * Loads the text editor view state for the given resource and returns it. - */ - protected loadTextEditorViewState(resource: URI): IEditorViewState | undefined { - return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined; - } - private updateEditorConfiguration(configuration?: IEditorConfiguration): void { if (!configuration) { const resource = this.getActiveResource(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 5cd63a457a..d5216e5f98 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -11,7 +11,7 @@ import { INotificationsModel, INotificationChangeEvent, NotificationChangeType } import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Emitter } from 'vs/base/common/event'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { NotificationsCenterVisibleContext, INotificationsCenterController } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { addClass, removeClass, isAncestor, Dimension } from 'vs/base/browser/dom'; @@ -24,7 +24,7 @@ import { IAction } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; -export class NotificationsCenter extends Themable { +export class NotificationsCenter extends Themable implements INotificationsCenterController { private static readonly MAX_DIMENSIONS = new Dimension(450, 400); @@ -284,8 +284,10 @@ export class NotificationsCenter extends Themable { this.hide(); // Close all - while (this.model.notifications.length) { - this.model.notifications[0].close(); + for (const notification of this.model.notifications) { + if (!notification.hasProgress) { + notification.close(); + } } } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index bcd1001d51..b80dceec9d 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -107,7 +107,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl }, handler: (accessor, args?: any) => { const notification = getNotificationFromContext(accessor.get(IListService), args); - if (notification) { + if (notification && !notification.hasProgress) { notification.close(); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts index 73ecb863b3..e0894579a1 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsStatus.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications'; +import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, IStatusMessageChangeEvent, StatusMessageChangeType, IStatusMessageViewItem } from 'vs/workbench/common/notifications'; import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; @@ -12,11 +12,12 @@ import { localize } from 'vs/nls'; export class NotificationsStatus extends Disposable { private notificationsCenterStatusItem: IStatusbarEntryAccessor | undefined; - private currentNotifications = new Set(); + private newNotificationsCount = 0; private currentStatusMessage: [IStatusMessageViewItem, IDisposable] | undefined; - private isNotificationsCenterVisible: boolean | undefined; + private isNotificationsCenterVisible: boolean = false; + private isNotificationsToastsVisible: boolean = false; constructor( private model: INotificationsModel, @@ -39,39 +40,56 @@ export class NotificationsStatus extends Disposable { } private onDidChangeNotification(e: INotificationChangeEvent): void { - if (this.isNotificationsCenterVisible) { - return; // no change if notification center is visible - } - - // Notification got Added - if (e.kind === NotificationChangeType.ADD) { - this.currentNotifications.add(e.item); - } - - // Notification got Removed - else if (e.kind === NotificationChangeType.REMOVE) { - this.currentNotifications.delete(e.item); + + // Consider a notification as unread as long as it only + // appeared as toast and not in the notification center + if (!this.isNotificationsCenterVisible) { + if (e.kind === NotificationChangeType.ADD) { + this.newNotificationsCount++; + } else if (e.kind === NotificationChangeType.REMOVE) { + this.newNotificationsCount--; + } } + // Update in status bar this.updateNotificationsCenterStatusItem(); } private updateNotificationsCenterStatusItem(): void { + + // Figure out how many notifications have progress only if neither + // toasts are visible nor center is visible. In that case we still + // want to give a hint to the user that something is running. + let notificationsInProgress = 0; + if (!this.isNotificationsCenterVisible && !this.isNotificationsToastsVisible) { + for (const notification of this.model.notifications) { + if (notification.hasProgress) { + notificationsInProgress++; + } + } + } + const statusProperties: IStatusbarEntry = { - text: this.currentNotifications.size === 0 ? '$(bell)' : `$(bell) ${this.currentNotifications.size}`, + text: `${this.newNotificationsCount === 0 ? '$(bell)' : '$(bell-dot)'}${notificationsInProgress > 0 ? ' $(sync~spin)' : ''}`, command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : SHOW_NOTIFICATIONS_CENTER, - tooltip: this.getTooltip(), + tooltip: this.getTooltip(notificationsInProgress), showBeak: this.isNotificationsCenterVisible }; if (!this.notificationsCenterStatusItem) { - this.notificationsCenterStatusItem = this.statusbarService.addEntry(statusProperties, 'status.notifications', localize('status.notifications', "Notifications"), StatusbarAlignment.RIGHT, -Number.MAX_VALUE /* towards the far end of the right hand side */); + this.notificationsCenterStatusItem = this.statusbarService.addEntry( + statusProperties, + 'status.notifications', + localize('status.notifications', "Notifications"), + StatusbarAlignment.RIGHT, + -Number.MAX_VALUE /* towards the far end of the right hand side */ + ); } else { this.notificationsCenterStatusItem.update(statusProperties); } } - private getTooltip(): string { + private getTooltip(notificationsInProgress: number): string { if (this.isNotificationsCenterVisible) { return localize('hideNotifications', "Hide Notifications"); } @@ -80,23 +98,45 @@ export class NotificationsStatus extends Disposable { return localize('zeroNotifications', "No Notifications"); } - if (this.currentNotifications.size === 0) { - return localize('noNotifications', "No New Notifications"); + if (notificationsInProgress === 0) { + if (this.newNotificationsCount === 0) { + return localize('noNotifications', "No New Notifications"); + } + + if (this.newNotificationsCount === 1) { + return localize('oneNotification', "1 New Notification"); + } + + return localize('notifications', "{0} New Notifications", this.newNotificationsCount); } - if (this.currentNotifications.size === 1) { - return localize('oneNotification', "1 New Notification"); + if (this.newNotificationsCount === 0) { + return localize('noNotificationsWithProgress', "No New Notifications ({0} in progress)", notificationsInProgress); } - return localize('notifications', "{0} New Notifications", this.currentNotifications.size); + if (this.newNotificationsCount === 1) { + return localize('oneNotificationWithProgress', "1 New Notification ({0} in progress)", notificationsInProgress); + } + + return localize('notificationsWithProgress', "{0} New Notifications ({0} in progress)", this.newNotificationsCount, notificationsInProgress); } - update(isCenterVisible: boolean): void { + update(isCenterVisible: boolean, isToastsVisible: boolean): void { + let updateNotificationsCenterStatusItem = false; + if (this.isNotificationsCenterVisible !== isCenterVisible) { this.isNotificationsCenterVisible = isCenterVisible; + this.newNotificationsCount = 0; // Showing the notification center resets the unread counter to 0 + updateNotificationsCenterStatusItem = true; + } - // Showing the notification center resets the counter to 0 - this.currentNotifications.clear(); + if (this.isNotificationsToastsVisible !== isToastsVisible) { + this.isNotificationsToastsVisible = isToastsVisible; + updateNotificationsCenterStatusItem = true; + } + + // Update in status bar as needed + if (updateNotificationsCenterStatusItem) { this.updateNotificationsCenterStatusItem(); } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 70021862a1..c824d17a22 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -9,13 +9,13 @@ import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/com import { addClass, removeClass, isAncestor, addDisposableListener, EventType, Dimension } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Themable, NOTIFICATIONS_TOAST_BORDER, NOTIFICATIONS_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { NotificationsToastsVisibleContext, INotificationsToastController } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; @@ -39,7 +39,7 @@ enum ToastVisibility { VISIBLE } -export class NotificationsToasts extends Themable { +export class NotificationsToasts extends Themable implements INotificationsToastController { private static readonly MAX_WIDTH = 450; private static readonly MAX_NOTIFICATIONS = 3; @@ -53,6 +53,12 @@ export class NotificationsToasts extends Themable { return intervals; })(); + private readonly _onDidChangeVisibility = this._register(new Emitter()); + readonly onDidChangeVisibility = this._onDidChangeVisibility.event; + + private _isVisible = false; + get isVisible(): boolean { return !!this._isVisible; } + private notificationsToastsContainer: HTMLElement | undefined; private workbenchDimensions: Dimension | undefined; private isNotificationsCenterVisible: boolean | undefined; @@ -125,11 +131,11 @@ export class NotificationsToasts extends Themable { private addToast(item: INotificationViewItem): void { if (this.isNotificationsCenterVisible) { - return; // do not show toasts while notification center is visibles + return; // do not show toasts while notification center is visible } if (item.silent) { - return; // do not show toats for silenced notifications + return; // do not show toasts for silenced notifications } // Lazily create toasts containers @@ -174,7 +180,7 @@ export class NotificationsToasts extends Themable { this.mapNotificationToToast.set(item, toast); itemDisposables.add(toDisposable(() => { - if (this.isVisible(toast) && notificationsToastsContainer) { + if (this.isToastVisible(toast) && notificationsToastsContainer) { notificationsToastsContainer.removeChild(toast.container); } })); @@ -229,6 +235,12 @@ export class NotificationsToasts extends Themable { removeClass(notificationToast, 'notification-fade-in'); addClass(notificationToast, 'notification-fade-in-done'); })); + + // Events + if (!this._isVisible) { + this._isVisible = true; + this._onDidChangeVisibility.fire(); + } } private purgeNotification(item: INotificationViewItem, notificationToastContainer: HTMLElement, notificationList: NotificationsList, disposables: DisposableStore): void { @@ -325,6 +337,12 @@ export class NotificationsToasts extends Themable { // Context Key this.notificationsToastsVisibleContextKey.set(false); + + // Events + if (this._isVisible) { + this._isVisible = false; + this._onDidChangeVisibility.fire(); + } } hide(): void { @@ -441,12 +459,12 @@ export class NotificationsToasts extends Themable { notificationToasts.push(toast); break; case ToastVisibility.HIDDEN: - if (!this.isVisible(toast)) { + if (!this.isToastVisible(toast)) { notificationToasts.push(toast); } break; case ToastVisibility.VISIBLE: - if (this.isVisible(toast)) { + if (this.isToastVisible(toast)) { notificationToasts.push(toast); } break; @@ -534,7 +552,7 @@ export class NotificationsToasts extends Themable { } private setVisibility(toast: INotificationToast, visible: boolean): void { - if (this.isVisible(toast) === visible) { + if (this.isToastVisible(toast) === visible) { return; } @@ -546,7 +564,7 @@ export class NotificationsToasts extends Themable { } } - private isVisible(toast: INotificationToast): boolean { + private isToastVisible(toast: INotificationToast): boolean { return !!toast.container.parentElement; } } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 40f582bead..743a3f3271 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -306,7 +306,7 @@ export class NotificationTemplateRenderer extends Disposable { // Container toggleClass(this.template.container, 'expanded', notification.expanded); this.inputDisposables.add(addDisposableListener(this.template.container, EventType.MOUSE_UP, e => { - if (e.button === 1 /* Middle Button */) { + if (!notification.hasProgress && e.button === 1 /* Middle Button */) { EventHelper.stop(e); notification.close(); @@ -403,8 +403,10 @@ export class NotificationTemplateRenderer extends Disposable { actions.push(notification.expanded ? NotificationTemplateRenderer.collapseNotificationAction : NotificationTemplateRenderer.expandNotificationAction); } - // Close - actions.push(NotificationTemplateRenderer.closeNotificationAction); + // Close (unless progress is showing) + if (!notification.hasProgress) { + actions.push(NotificationTemplateRenderer.closeNotificationAction); + } this.template.toolbar.clear(); this.template.toolbar.context = notification; @@ -453,7 +455,7 @@ export class NotificationTemplateRenderer extends Disposable { private renderProgress(notification: INotificationViewItem): void { // Return early if the item has no progress - if (!notification.hasProgress()) { + if (!notification.hasProgress) { this.template.progress.stop().hide(); return; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 420aa4b62e..0a9ec3030a 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -33,6 +33,9 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; import { assertIsDefined } from 'vs/base/common/types'; +import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; +import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions'; export class SidebarPart extends CompositePart implements IViewletService { @@ -163,6 +166,29 @@ export class SidebarPart extends CompositePart implements IViewletServi this.onTitleAreaContextMenu(new StandardMouseEvent(e)); })); + this.titleLabelElement!.draggable = true; + this._register(addDisposableListener(this.titleLabelElement!, EventType.DRAG_START, e => { + const activeViewlet = this.getActiveViewlet(); + if (activeViewlet) { + const visibleViews = activeViewlet.getViewPaneContainer().views.filter(v => v.isVisible()); + if (visibleViews.length === 1) { + LocalSelectionTransfer.getInstance().setData([new DraggedViewIdentifier(visibleViews[0].id)], DraggedViewIdentifier.prototype); + } else { + LocalSelectionTransfer.getInstance().setData([new DraggedCompositeIdentifier(activeViewlet.getId())], DraggedCompositeIdentifier.prototype); + } + } + })); + + this._register(addDisposableListener(this.titleLabelElement!, EventType.DRAG_END, e => { + if (LocalSelectionTransfer.getInstance().hasData(DraggedViewIdentifier.prototype)) { + LocalSelectionTransfer.getInstance().clearData(DraggedViewIdentifier.prototype); + } + + if (LocalSelectionTransfer.getInstance().hasData(DraggedCompositeIdentifier.prototype)) { + LocalSelectionTransfer.getInstance().clearData(DraggedCompositeIdentifier.prototype); + } + })); + return titleArea; } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 7b2590bc13..3ba9d3e41d 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -43,6 +43,7 @@ import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class CustomTreeViewPane extends ViewPane { @@ -59,8 +60,9 @@ export class CustomTreeViewPane extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index cad87a45b6..a533f8af2d 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -66,6 +66,10 @@ export class DraggedViewIdentifier { } } +type WelcomeActionClassification = { + viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; +}; const viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -194,11 +198,12 @@ export abstract class ViewPane extends Pane implements IView { @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService protected contextKeyService: IContextKeyService, @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, @IInstantiationService protected instantiationService: IInstantiationService, @IOpenerService protected openerService: IOpenerService, @IThemeService protected themeService: IThemeService, + @ITelemetryService protected telemetryService: ITelemetryService, ) { super(options); @@ -397,7 +402,9 @@ export abstract class ViewPane extends Pane implements IView { addClass(this.bodyContainer, 'welcome'); this.viewWelcomeContainer.innerHTML = ''; - for (const { content } of contents) { + let buttonIndex = 0; + + for (const { content, preconditions } of contents) { const lines = content.split('\n'); for (let line of lines) { @@ -416,9 +423,28 @@ export abstract class ViewPane extends Pane implements IView { } else if (linkedText.nodes.length === 1) { const button = new Button(p, { title: node.title }); button.label = node.label; - button.onDidClick(_ => this.openerService.open(node.href), null, disposables); + button.onDidClick(_ => { + this.telemetryService.publicLog2<{ viewId: string, uri: string }, WelcomeActionClassification>('views.welcomeAction', { viewId: this.id, uri: node.href }); + this.openerService.open(node.href); + }, null, disposables); disposables.add(button); disposables.add(attachButtonStyler(button, this.themeService)); + + if (preconditions) { + const precondition = preconditions[buttonIndex]; + + if (precondition) { + const updateEnablement = () => button.enabled = this.contextKeyService.contextMatchesRules(precondition); + updateEnablement(); + + const keys = new Set(); + precondition.keys().forEach(key => keys.add(key)); + const onDidChangeContext = Event.filter(this.contextKeyService.onDidChangeContext, e => e.affectsSome(keys)); + onDidChangeContext(updateEnablement, null, disposables); + } + } + + buttonIndex++; } else { const link = this.instantiationService.createInstance(Link, node); append(p, link.el); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 3dbc206834..351f862d99 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -42,6 +42,19 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio key: 'tabDescription' }, "Controls the format of the label for an editor."), }, + 'workbench.editor.untitled.labelFormat': { + 'type': 'string', + 'enum': ['content', 'name'], + 'enumDescriptions': [ + nls.localize('workbench.editor.untitled.labelFormat.content', "The name of the untitled file is derived from the contents of its first line unless it has an associated file path. It will fallback to the name in case the line is empty or contains no word characters."), + nls.localize('workbench.editor.untitled.labelFormat.name', "The name of the untitled file is not derived from the contents of the file."), + ], + 'default': 'content', + 'description': nls.localize({ + comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], + key: 'untitledLabelFormat' + }, "Controls the format of the label for an untitled editor."), + }, 'workbench.editor.tabCloseButton': { 'type': 'string', 'enum': ['left', 'right', 'off'], diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index b2b7cd680b..1624e591b0 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -386,10 +386,14 @@ export class Workbench extends Layout { // Visibility this._register(notificationsCenter.onDidChangeVisibility(() => { - notificationsStatus.update(notificationsCenter.isVisible); + notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible); notificationsToasts.update(notificationsCenter.isVisible); })); + this._register(notificationsToasts.onDidChangeVisibility(() => { + notificationsStatus.update(notificationsCenter.isVisible, notificationsToasts.isVisible); + })); + // Register Commands registerNotificationCommands(notificationsCenter, notificationsToasts); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 530ca44116..6c604cb71f 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -346,7 +346,7 @@ export interface IRevertOptions { } export interface IMoveResult { - editor: IEditorInput | IResourceEditor; + editor: EditorInput | IResourceEditor; options?: IEditorOptions; } @@ -1374,6 +1374,8 @@ export interface IEditorMemento { clearEditorState(resource: URI, group?: IEditorGroup): void; clearEditorState(editor: EditorInput, group?: IEditorGroup): void; + + moveEditorState(source: URI, target: URI): void; } class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 88d64a952d..596c57e207 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -261,6 +261,7 @@ export interface INotificationViewItem { readonly expanded: boolean; readonly canCollapse: boolean; + readonly hasProgress: boolean; readonly onDidChangeExpansion: Event; readonly onDidClose: Event; @@ -270,9 +271,6 @@ export interface INotificationViewItem { collapse(skipEvents?: boolean): void; toggle(): void; - hasProgress(): boolean; - hasPrompt(): boolean; - updateSeverity(severity: Severity): void; updateMessage(message: NotificationMessage): void; updateActions(actions?: INotificationActions): void; @@ -495,7 +493,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } get canCollapse(): boolean { - return !this.hasPrompt(); + return !this.hasPrompt; } get expanded(): boolean { @@ -511,7 +509,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return true; // explicitly sticky } - const hasPrompt = this.hasPrompt(); + const hasPrompt = this.hasPrompt; if ( (hasPrompt && this._severity === Severity.Error) || // notification errors with actions are sticky (!hasPrompt && this._expanded) || // notifications that got expanded are sticky @@ -527,7 +525,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return !!this._silent; } - hasPrompt(): boolean { + private get hasPrompt(): boolean { if (!this._actions) { return false; } @@ -539,7 +537,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie return this._actions.primary.length > 0; } - hasProgress(): boolean { + get hasProgress(): boolean { return !!this._progress; } @@ -621,7 +619,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie } equals(other: INotificationViewItem): boolean { - if (this.hasProgress() || other.hasProgress()) { + if (this.hasProgress || other.hasProgress) { return false; } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 44c2fa5c8f..912f55f313 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -214,6 +214,11 @@ export interface IViewDescriptorCollection extends IDisposable { export interface IViewContentDescriptor { readonly content: string; readonly when?: ContextKeyExpr | 'default'; + + /** + * ordered preconditions for each button in the content + */ + readonly preconditions?: (ContextKeyExpr | undefined)[]; } export interface IViewsRegistry { diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts index 911024914e..5833314aa1 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupRestorer.test.ts @@ -12,12 +12,10 @@ import { URI } from 'vs/base/common/uri'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker'; -import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -33,6 +31,8 @@ import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/ele import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isEqual } from 'vs/base/common/resources'; +import { TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; const userdataDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backuprestorer'); const backupHome = path.join(userdataDir, 'Backups'); @@ -51,15 +51,8 @@ class TestBackupRestorer extends BackupRestorer { } } -class ServiceAccessor { - constructor( - @ITextFileService public textFileService: TestTextFileService - ) { - } -} - suite.skip('BackupRestorer', () => { // {{SQL CARBON EDIT}} TODO @anthonydresser these tests are failing due to tabColorMode, should investigate and fix - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let disposables: IDisposable[] = []; @@ -105,7 +98,7 @@ suite.skip('BackupRestorer', () => { // {{SQL CARBON EDIT}} TODO @anthonydresser const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); await part.whenRestored; diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index 86ae339ee9..02967986c4 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -10,10 +10,8 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { hashPath } from 'vs/workbench/services/backup/node/backupFileService'; import { NativeBackupTracker } from 'vs/workbench/contrib/backup/electron-browser/backupTracker'; -import { TestLifecycleService, TestFilesConfigurationService, TestContextService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; @@ -32,16 +30,14 @@ import { toResource } from 'vs/base/test/common/utils'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILogService } from 'vs/platform/log/common/log'; -import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; +import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ShutdownReason, ILifecycleService, BeforeShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/common/workspace'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { BackupTracker } from 'vs/workbench/contrib/backup/common/backupTracker'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { TestTextFileService, TestElectronService, workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; @@ -52,23 +48,6 @@ const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); -class ServiceAccessor { - constructor( - @ILifecycleService public lifecycleService: TestLifecycleService, - @ITextFileService public textFileService: TestTextFileService, - @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, - @IWorkspaceContextService public contextService: TestContextService, - @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService, - @IElectronService public electronService: TestElectronService, - @IFileDialogService public fileDialogService: TestFileDialogService, - @IBackupFileService public backupFileService: NodeTestBackupFileService, - @IWorkingCopyService public workingCopyService: IWorkingCopyService, - @IEditorService public editorService: IEditorService - ) { - } -} - class TestBackupTracker extends NativeBackupTracker { constructor( @@ -102,12 +81,12 @@ class BeforeShutdownEventImpl implements BeforeShutdownEvent { } suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let disposables: IDisposable[] = []; setup(async () => { const instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( EditorDescriptor.create( @@ -134,7 +113,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); }); - async function createTracker(): Promise<[ServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { + async function createTracker(): Promise<[TestServiceAccessor, EditorPart, BackupTracker, IInstantiationService]> { const backupFileService = new NodeTestBackupFileService(workspaceBackupPath); const instantiationService = workbenchInstantiationService(); instantiationService.stub(IBackupFileService, backupFileService); @@ -148,7 +127,7 @@ suite.skip('BackupTracker', () => { // {{SQL CARBON EDIT}} skip failing tests const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); await part.whenRestored; diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index f31353062f..2b1cc251b6 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -38,6 +38,7 @@ import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataT import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const enum State { Data = 'data', @@ -86,10 +87,11 @@ export class BulkEditPane extends ViewPane { @IConfigurationService configurationService: IConfigurationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { super( { ...options, titleMenuId: MenuId.BulkEditTitle }, - keybindingService, contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instaService, openerService, themeService + keybindingService, contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instaService, openerService, themeService, telemetryService ); this.element.classList.add('bulk-edit-panel', 'show-file-icons'); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index dffcd241b2..57a9a98b1a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -26,6 +26,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { WorkspaceFileEdit } from 'vs/editor/common/modes'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; // --- VIEW MODEL @@ -174,7 +175,10 @@ export class BulkEditDataSource implements IAsyncDataSource(provider => { + progress.report({ + message: localize( + 'formatting', + "Running '{0}' Formatter ([configure](command:workbench.action.openSettings?%5B%22editor.formatOnSave%22%5D)).", + provider.displayName || provider.extensionId && provider.extensionId.value || '???' + ) + }); + }); const editorOrModel = findEditor(model, this.codeEditorService) || model; - await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token); + await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, nestedProgress, token); } } @@ -287,14 +295,37 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { .map(x => new CodeActionKind(x)); progress.report({ message: localize('codeaction', "Quick Fixes") }); - await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token); + await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, progress, token); } - private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { + private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken): Promise { + + const getActionProgress = new class implements IProgress { + private _names: string[] = []; + private _report(): void { + progress.report({ + message: localize( + 'codeaction.get', + "Getting code actions from '{0}' ([configure](command:workbench.action.openSettings?%5B%22editor.codeActionsOnSave%22%5D)).", + this._names.map(name => `'${name}'`).join(', ') + ) + }); + } + report(provider: CodeActionProvider) { + if (provider.displayName) { + this._names.push(provider.displayName); + this._report(); + } + } + }; + for (const codeActionKind of codeActionsOnSave) { - const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, token); + const actionsToRun = await this.getActionsToRun(model, codeActionKind, excludes, getActionProgress, token); try { - await this.applyCodeActions(actionsToRun.validActions); + for (const action of actionsToRun.validActions) { + progress.report({ message: localize('codeAction.apply', "Applying code action '{0}'.", action.title) }); + await this.instantiationService.invokeFunction(applyCodeAction, action); + } } catch { // Failure to apply a code action should not block other on save actions } finally { @@ -303,17 +334,11 @@ class CodeActionOnSaveParticipant implements ITextFileSaveParticipant { } } - private async applyCodeActions(actionsToRun: readonly CodeAction[]) { - for (const action of actionsToRun) { - await this.instantiationService.invokeFunction(applyCodeAction, action); - } - } - - private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], token: CancellationToken) { + private getActionsToRun(model: ITextModel, codeActionKind: CodeActionKind, excludes: readonly CodeActionKind[], progress: IProgress, token: CancellationToken) { return getCodeActions(model, model.getFullModelRange(), { type: CodeActionTriggerType.Auto, filter: { include: codeActionKind, excludes: excludes, includeSourceActions: true }, - }, token); + }, progress, token); } } diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts new file mode 100644 index 0000000000..0af7824e8c --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleColumnSelection.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; + +export class ToggleColumnSelectionAction extends Action { + public static readonly ID = 'editor.action.toggleColumnSelection'; + public static readonly LABEL = nls.localize('toggleColumnSelection', "Toggle Column Selection Mode"); + + constructor( + id: string, + label: string, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(id, label); + } + + public run(): Promise { + const newValue = !this._configurationService.getValue('editor.columnSelection'); + return this._configurationService.updateValue('editor.columnSelection', newValue, ConfigurationTarget.USER); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleColumnSelectionAction, ToggleColumnSelectionAction.ID, ToggleColumnSelectionAction.LABEL), 'View: Toggle Column Selection Mode', nls.localize('view', "View")); + +MenuRegistry.appendMenuItem(MenuId.MenubarSelectionMenu, { + group: '3_multi', + command: { + id: ToggleColumnSelectionAction.ID, + title: nls.localize({ key: 'miColumnSelection', comment: ['&& denotes a mnemonic'] }, "Column &&Selection Mode"), + toggled: ContextKeyExpr.equals('config.editor.columnSelection', true) + }, + order: 1.5 +}); diff --git a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts index be095eb104..1490dad73a 100644 --- a/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts +++ b/src/vs/workbench/contrib/codeEditor/test/browser/saveParticipant.test.ts @@ -7,29 +7,23 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { FinalNewLineParticipant, TrimFinalNewLinesParticipant } from 'vs/workbench/contrib/codeEditor/browser/saveParticipants'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { SaveReason } from 'vs/workbench/common/editor'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -class ServiceAccessor { - constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService) { - } -} - suite('MainThreadSaveParticipant', function () { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index c50f0b18fc..43de2a6d9a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -27,6 +27,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class CommentsPanel extends ViewPane { @@ -51,9 +52,10 @@ export class CommentsPanel extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, - @ICommentService private readonly commentService: ICommentService + @ICommentService private readonly commentService: ICommentService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), id: COMMENTS_VIEW_ID, ariaHeaderLabel: COMMENTS_VIEW_TITLE }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), id: COMMENTS_VIEW_ID, ariaHeaderLabel: COMMENTS_VIEW_TITLE }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } public renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index fcc60a30eb..0c2ebfd7fa 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -15,7 +15,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; import { IEditorCommandsContext } from 'vs/workbench/common/editor'; -import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { defaultEditorId } from 'vs/workbench/contrib/customEditor/browser/customEditors'; import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -114,19 +114,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { } public runCommand(accessor: ServicesAccessor): void { - const customEditorService = accessor.get(ICustomEditorService); - - const activeCustomEditor = customEditorService.activeCustomEditor; - if (!activeCustomEditor) { - return; + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeControl?.input; + if (activeInput instanceof CustomEditorInput) { + activeInput.undo(); } - - const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); - if (!model) { - return; - } - - model.undo(); } }).register(); @@ -149,19 +141,11 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { } public runCommand(accessor: ServicesAccessor): void { - const customEditorService = accessor.get(ICustomEditorService); - - const activeCustomEditor = customEditorService.activeCustomEditor; - if (!activeCustomEditor) { - return; + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeControl?.input; + if (activeInput instanceof CustomEditorInput) { + activeInput.redo(); } - - const model = customEditorService.models.get(activeCustomEditor.resource, activeCustomEditor.viewType); - if (!model) { - return; - } - - model.redo(); } }).register(); @@ -193,7 +177,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { const customEditorService = accessor.get(ICustomEditorService); let toggleView = defaultEditorId; - if (!(activeEditor instanceof CustomFileEditorInput)) { + if (!(activeEditor instanceof CustomEditorInput)) { const bestAvailableEditor = customEditorService.getContributedCustomEditors(targetResource).bestAvailableEditor; if (bestAvailableEditor) { toggleView = bestAvailableEditor.id; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 79b96a8f96..ae6b755bd2 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -16,20 +16,26 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILabelService } from 'vs/platform/label/common/label'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { WebviewEditorOverlay, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; +import { IWebviewService, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; -import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; + +export const enum ModelType { + Custom = 'custom', + Text = 'text', +} + +export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { -export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { public static typeId = 'workbench.editors.webviewEditor'; private readonly _editorResource: URI; get resource() { return this._editorResource; } - private _model?: ICustomEditorModel; - + private _model?: { readonly type: ModelType.Custom, readonly model: ICustomEditorModel } | { readonly type: ModelType.Text }; constructor( resource: URI, @@ -43,14 +49,18 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @ICustomEditorService private readonly customEditorService: ICustomEditorService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @ITextFileService private readonly textFileService: ITextFileService, + ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; } + public modelType?: ModelType; + public getTypeId(): string { - return CustomFileEditorInput.typeId; + return CustomEditorInput.typeId; } public supportsSplitEditor() { @@ -62,13 +72,8 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return basename(this.labelService.getUriLabel(this.resource)); } - @memoize - getDescription(): string | undefined { - return super.getDescription(); - } - matches(other: IEditorInput): boolean { - return this === other || (other instanceof CustomFileEditorInput + return this === other || (other instanceof CustomEditorInput && this.viewType === other.viewType && isEqual(this.resource, other.resource)); } @@ -101,11 +106,24 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } public isReadonly(): boolean { - return false; + return false; // TODO } public isDirty(): boolean { - return this._model ? this._model.isDirty() : false; + if (!this._model) { + return false; + } + + switch (this._model.type) { + case ModelType.Text: + return this.textFileService.isDirty(this.resource); + + case ModelType.Custom: + return this._model.model.isDirty(); + + default: + throw new Error('Unknown model type'); + } } public isSaving(): boolean { @@ -125,12 +143,20 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return undefined; } - const result = await this._model.save(options); - if (!result) { - return undefined; + switch (this._model.type) { + case ModelType.Text: + { + const result = await this.textFileService.save(this.resource, options); + return result ? this : undefined; + } + case ModelType.Custom: + { + const result = await this._model.model.save(options); + return result ? this : undefined; + } + default: + throw new Error('Unknown model type'); } - - return this; } public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { @@ -144,31 +170,81 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return undefined; // save cancelled } - if (!await this._model.saveAs(this._editorResource, target, options)) { - return undefined; + switch (this._model.type) { + case ModelType.Text: + if (!await this.textFileService.saveAs(this.resource, target, options)) { + return undefined; + } + break; + + case ModelType.Custom: + if (!await this._model.model.saveAs(this._editorResource, target, options)) { + return undefined; + } + break; + + default: + throw new Error('Unknown model type'); } return this.handleMove(groupId, target) || this.editorService.createInput({ resource: target, forceFile: true }); } - public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - return this._model ? this._model.revert(options) : Promise.resolve(false); + public async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + if (!this._model) { + return false; + } + + switch (this._model.type) { + case ModelType.Text: + return this.textFileService.revert(this.resource, options); + + case ModelType.Custom: + return this._model.model.revert(options); + + default: + throw new Error('Unknown model type'); + } } public async resolve(): Promise { - this._model = await this.customEditorService.models.resolve(this.resource, this.viewType); - this._register(this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + const editorModel = await super.resolve(); + if (!this._model) { + switch (this.modelType) { + case ModelType.Custom: + const model = await this.customEditorService.models.resolve(this.resource, this.viewType); + this._model = { type: ModelType.Custom, model }; + this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + + break; + + case ModelType.Text: + this._model = { type: ModelType.Text, }; + this.textFileService.files.onDidChangeDirty(e => { + if (isEqual(this.resource, e.resource)) { + this._onDidChangeDirty.fire(); + } + }); + + break; + + default: + throw new Error('Unknown model type'); + } + } + if (this.isDirty()) { this._onDidChangeDirty.fire(); } - return await super.resolve(); + + return editorModel; } public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { const webview = assertIsDefined(this.takeOwnershipOfWebview()); - const newInput = this.instantiationService.createInstance(CustomFileEditorInput, + const newInput = this.instantiationService.createInstance(CustomEditorInput, uri, this.viewType, generateUuid(), @@ -178,4 +254,42 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } return undefined; } + + public undo(): void { + if (!this._model) { + return; + } + + switch (this._model.type) { + case ModelType.Custom: + this._model.model.undo(); + return; + + case ModelType.Text: + this.textFileService.files.get(this.resource)?.textEditorModel?.undo(); + return; + + default: + throw new Error('Unknown model type'); + } + } + + public redo(): void { + if (!this._model) { + return; + } + + switch (this._model.type) { + case ModelType.Custom: + this._model.model.redo(); + return; + + case ModelType.Text: + this.textFileService.files.get(this.resource)?.textEditorModel?.redo(); + return; + + default: + throw new Error('Unknown model type'); + } + } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index 1efaa6c366..bbb003b38a 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -6,14 +6,14 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { CustomEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/webviewEditorInputFactory'; import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { Lazy } from 'vs/base/common/lazy'; export class CustomEditorInputFactory extends WebviewEditorInputFactory { - public static readonly ID = CustomFileEditorInput.typeId; + public static readonly ID = CustomEditorInput.typeId; public constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -22,10 +22,11 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { super(webviewWorkbenchService); } - public serialize(input: CustomFileEditorInput): string | undefined { + public serialize(input: CustomEditorInput): string | undefined { const data = { ...this.toJson(input), - editorResource: input.resource.toJSON() + editorResource: input.resource.toJSON(), + modelType: input.modelType }; try { @@ -38,7 +39,7 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { public deserialize( _instantiationService: IInstantiationService, serializedEditorInput: string - ): CustomFileEditorInput { + ): CustomEditorInput { const data = this.fromJson(serializedEditorInput); const id = data.id || generateUuid(); @@ -50,10 +51,13 @@ export class CustomEditorInputFactory extends WebviewEditorInputFactory { return webviewInput.webview; }); - const customInput = this._instantiationService.createInstance(CustomFileEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); + const customInput = this._instantiationService.createInstance(CustomEditorInput, URI.from((data as any).editorResource), data.viewType, id, webview); if (typeof data.group === 'number') { customInput.updateGroup(data.group); } + if ((data as any).modelType) { + customInput.modelType = (data as any).modelType; + } return customInput; } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 24be9ba01f..d02db01be9 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -7,7 +7,7 @@ import { coalesce } from 'vs/base/common/arrays'; import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; -import { basename, isEqual, extname } from 'vs/base/common/resources'; +import { basename, extname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as nls from 'vs/nls'; @@ -31,7 +31,7 @@ import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { CustomFileEditorInput } from './customEditorInput'; +import { CustomEditorInput } from './customEditorInput'; export const defaultEditorId = 'default'; @@ -138,7 +138,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ public get activeCustomEditor(): ICustomEditor | undefined { const activeInput = this.editorService.activeControl?.input; - if (!(activeInput instanceof CustomFileEditorInput)) { + if (!(activeInput instanceof CustomEditorInput)) { return undefined; } const resource = activeInput.resource; @@ -175,7 +175,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ let currentlyOpenedEditorType: undefined | string; for (const editor of group ? group.editors : []) { if (editor.resource && isEqual(editor.resource, resource)) { - currentlyOpenedEditorType = editor instanceof CustomFileEditorInput ? editor.viewType : defaultEditorId; + currentlyOpenedEditorType = editor instanceof CustomEditorInput ? editor.viewType : defaultEditorId; break; } } @@ -271,7 +271,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ const webview = new Lazy(() => { return this.webviewService.createWebviewEditorOverlay(id, { customClasses: options?.customClasses }, {}); }); - const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, webview); + const input = this.instantiationService.createInstance(CustomEditorInput, resource, viewType, id, webview); if (group) { input.updateGroup(group.id); } @@ -297,7 +297,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ options: options ? EditorOptions.create(options) : undefined, }], targetGroup); - if (existing instanceof CustomFileEditorInput) { + if (existing instanceof CustomEditorInput) { existing.dispose(); } } @@ -321,14 +321,14 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ ...this.getUserConfiguredCustomEditors(resource).allEditors, ]; this._hasCustomEditor.set(possibleEditors.length > 0); - this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomFileEditorInput); + this._focusedCustomEditorIsEditable.set(activeControl?.input instanceof CustomEditorInput); this._webviewHasOwnEditFunctions.set(possibleEditors.length > 0); } private handleMovedFileInOpenedFileEditors(oldResource: URI, newResource: URI): void { for (const group of this.editorGroupService.groups) { for (const editor of group.editors) { - if (!(editor instanceof CustomFileEditorInput)) { + if (!(editor instanceof CustomEditorInput)) { continue; } @@ -371,7 +371,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo })); this._register(this.editorService.onDidCloseEditor(({ editor }) => { - if (!(editor instanceof CustomFileEditorInput)) { + if (!(editor instanceof CustomEditorInput)) { return; } @@ -386,7 +386,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo options: ITextEditorOptions | undefined, group: IEditorGroup ): IOpenEditorOverride | undefined { - if (editor instanceof CustomFileEditorInput) { + if (editor instanceof CustomEditorInput) { if (editor.group === group.id) { // No need to do anything return undefined; @@ -483,7 +483,7 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo group: IEditorGroup ): IOpenEditorOverride | undefined { const getCustomEditorOverrideForSubInput = (subInput: IEditorInput, customClasses: string): EditorInput | undefined => { - if (subInput instanceof CustomFileEditorInput) { + if (subInput instanceof CustomEditorInput) { return undefined; } const resource = subInput.resource; diff --git a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts index be9c891c72..39449d386c 100644 --- a/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts +++ b/src/vs/workbench/contrib/customEditor/browser/webviewEditor.contribution.ts @@ -17,7 +17,7 @@ import { CustomEditorInputFactory } from 'vs/workbench/contrib/customEditor/brow import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEditor'; import './commands'; -import { CustomFileEditorInput } from './customEditorInput'; +import { CustomEditorInput } from './customEditorInput'; import { CustomEditorContribution, customEditorsAssociationsKey, CustomEditorService } from './customEditors'; registerSingleton(ICustomEditorService, CustomEditorService); @@ -31,7 +31,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( WebviewEditor.ID, 'Webview Editor', ), [ - new SyncDescriptor(CustomFileEditorInput) + new SyncDescriptor(CustomEditorInput) ]); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 657960b00e..cf35eefe17 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -43,8 +43,6 @@ export interface ICustomEditorService { promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } -export type CustomEditorEdit = number; - export interface ICustomEditorModelManager { get(resource: URI, viewType: string): ICustomEditorModel | undefined; @@ -69,23 +67,22 @@ export interface CustomEditorSaveAsEvent { export interface ICustomEditorModel extends IWorkingCopy { readonly viewType: string; - readonly onUndo: Event<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>; - readonly onApplyEdit: Event<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>; - readonly onDisposeEdits: Event<{ edits: readonly CustomEditorEdit[] }>; + readonly onUndo: Event; + readonly onRedo: Event; + readonly onRevert: Event; readonly onWillSave: Event; readonly onWillSaveAs: Event; onBackup(f: () => CancelablePromise): void; + setDirty(dirty: boolean): void; undo(): void; redo(): void; revert(options?: IRevertOptions): Promise; save(options?: ISaveOptions): Promise; saveAs(resource: URI, targetResource: URI, currentOptions?: ISaveOptions): Promise; - - pushEdit(edit: CustomEditorEdit, trigger: any): void; } export const enum CustomEditorPriority { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts index fb8c7e8fc7..520f7c0b00 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; -import { CustomEditorEdit, CustomEditorSaveAsEvent, CustomEditorSaveEvent, ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { CustomEditorSaveAsEvent, CustomEditorSaveEvent, ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILabelService } from 'vs/platform/label/common/label'; import { basename } from 'vs/base/common/path'; @@ -38,10 +38,8 @@ namespace HotExitState { export class CustomEditorModel extends Disposable implements ICustomEditorModel { - private _currentEditIndex: number = -1; - private _savePoint: number = -1; - private readonly _edits: Array = []; private _hotExitState: HotExitState.State = HotExitState.NotSupported; + private _dirty = false; constructor( public readonly viewType: string, @@ -51,11 +49,6 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel super(); } - dispose() { - this._onDisposeEdits.fire({ edits: this._edits }); - super.dispose(); - } - //#region IWorkingCopy public get resource() { @@ -71,31 +64,31 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel } public isDirty(): boolean { - return this._edits.length > 0 && this._savePoint !== this._currentEditIndex; + return this._dirty; } - protected readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); + private readonly _onDidChangeDirty: Emitter = this._register(new Emitter()); readonly onDidChangeDirty: Event = this._onDidChangeDirty.event; - protected readonly _onDidChangeContent: Emitter = this._register(new Emitter()); + private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); readonly onDidChangeContent: Event = this._onDidChangeContent.event; //#endregion - protected readonly _onUndo = this._register(new Emitter<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>()); - readonly onUndo = this._onUndo.event; + private readonly _onUndo = this._register(new Emitter()); + public readonly onUndo = this._onUndo.event; - protected readonly _onApplyEdit = this._register(new Emitter<{ edits: readonly CustomEditorEdit[], trigger: any | undefined }>()); - readonly onApplyEdit = this._onApplyEdit.event; + private readonly _onRedo = this._register(new Emitter()); + public readonly onRedo = this._onRedo.event; - protected readonly _onDisposeEdits = this._register(new Emitter<{ edits: readonly CustomEditorEdit[] }>()); - readonly onDisposeEdits = this._onDisposeEdits.event; + private readonly _onRevert = this._register(new Emitter()); + public readonly onRevert = this._onRevert.event; - protected readonly _onWillSave = this._register(new Emitter()); - readonly onWillSave = this._onWillSave.event; + private readonly _onWillSave = this._register(new Emitter()); + public readonly onWillSave = this._onWillSave.event; - protected readonly _onWillSaveAs = this._register(new Emitter()); - readonly onWillSaveAs = this._onWillSaveAs.event; + private readonly _onWillSaveAs = this._register(new Emitter()); + public readonly onWillSaveAs = this._onWillSaveAs.event; private _onBackup: undefined | (() => CancelablePromise); @@ -110,38 +103,30 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel } } - public pushEdit(edit: CustomEditorEdit, trigger: any) { - this.spliceEdits(edit); + public setDirty(dirty: boolean): void { + this._onDidChangeContent.fire(); - this._currentEditIndex = this._edits.length - 1; - this.updateDirty(); - this._onApplyEdit.fire({ edits: [edit], trigger }); - this.updateContentChanged(); - } - - private spliceEdits(editToInsert?: CustomEditorEdit) { - const start = this._currentEditIndex + 1; - const toRemove = this._edits.length - this._currentEditIndex; - - const removedEdits = editToInsert - ? this._edits.splice(start, toRemove, editToInsert) - : this._edits.splice(start, toRemove); - - if (removedEdits.length) { - this._onDisposeEdits.fire({ edits: removedEdits }); + if (this._dirty !== dirty) { + this._dirty = dirty; + this._onDidChangeDirty.fire(); } } - private updateDirty() { - // TODO@matt this should to be more fine grained and avoid - // emitting events if there was no change actually - this._onDidChangeDirty.fire(); + public async revert(_options?: IRevertOptions) { + if (!this._dirty) { + return true; + } + + this._onRevert.fire(); + return true; } - private updateContentChanged() { - // TODO@matt revisit that this method is being called correctly - // on each case of content change within the custom editor - this._onDidChangeContent.fire(); + public undo() { + this._onUndo.fire(); + } + + public redo() { + this._onRedo.fire(); } public async save(_options?: ISaveOptions): Promise { @@ -158,8 +143,7 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel return false; } - this._savePoint = this._currentEditIndex; - this.updateDirty(); + this.setDirty(false); return true; } @@ -179,62 +163,11 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel return false; } - this._savePoint = this._currentEditIndex; - this.updateDirty(); + this.setDirty(false); return true; } - public async revert(_options?: IRevertOptions) { - if (this._currentEditIndex === this._savePoint) { - return true; - } - - if (this._currentEditIndex >= this._savePoint) { - const editsToUndo = this._edits.slice(this._savePoint, this._currentEditIndex); - this._onUndo.fire({ edits: editsToUndo.reverse(), trigger: undefined }); - } else if (this._currentEditIndex < this._savePoint) { - const editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); - this._onApplyEdit.fire({ edits: editsToRedo, trigger: undefined }); - } - - this._currentEditIndex = this._savePoint; - this.spliceEdits(); - - this.updateDirty(); - this.updateContentChanged(); - return true; - } - - public undo() { - if (this._currentEditIndex < 0) { - // nothing to undo - return; - } - - const undoneEdit = this._edits[this._currentEditIndex]; - --this._currentEditIndex; - this._onUndo.fire({ edits: [undoneEdit], trigger: undefined }); - - this.updateDirty(); - this.updateContentChanged(); - } - - public redo() { - if (this._currentEditIndex >= this._edits.length - 1) { - // nothing to redo - return; - } - - ++this._currentEditIndex; - const redoneEdit = this._edits[this._currentEditIndex]; - - this._onApplyEdit.fire({ edits: [redoneEdit], trigger: undefined }); - - this.updateDirty(); - this.updateContentChanged(); - } - public async backup(): Promise { if (this._hotExitState === HotExitState.NotSupported) { throw new Error('Not supported'); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index e16cf3c112..22fd758daa 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -35,6 +35,7 @@ import { Gesture } from 'vs/base/browser/touch'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; @@ -71,8 +72,9 @@ export class BreakpointsView extends ViewPane { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel()); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 6e95e3adef..2de025ea2e 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -37,6 +37,7 @@ import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; @@ -101,8 +102,9 @@ export class CallStackView extends ViewPane { @IContextKeyService readonly contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 4bd1f73d30..9fa3385cde 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -11,7 +11,6 @@ import * as objects from 'vs/base/common/objects'; import { URI as uri } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -434,15 +433,8 @@ export class ConfigurationManager implements IConfigurationManager { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } - getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[] { - if (isCodeEditor(editor)) { - const model = editor.getModel(); - const language = model ? model.getLanguageIdentifier().language : undefined; - - return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).map(d => d.label); - } - - return []; + isDebuggerInterestedInLanguage(language: string): boolean { + return this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0).length > 0; } async guessDebugger(type?: string): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 70660403b7..bce0fb1c11 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -840,7 +840,10 @@ export class DebugSession implements IDebugSession { } if (event.body.group === 'end') { this.repl.endGroup(); - // Do not return, the end event can have additional output in it + if (!event.body.output) { + // Only return if the end event does not have additional output in it + return; + } } if (event.body.variablesReference) { diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 48497841be..724871d9f4 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -39,6 +39,7 @@ import type { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTr import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const NEW_STYLE_COMPRESS = true; @@ -427,8 +428,9 @@ export class LoadedScriptsView extends ViewPane { @ILabelService private readonly labelService: ILabelService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 3f3db40d10..2ff5b70b59 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -58,6 +58,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; @@ -100,7 +101,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IStorageService private readonly storageService: IStorageService, @IThemeService themeService: IThemeService, @IModelService private readonly modelService: IModelService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextMenuService contextMenuService: IContextMenuService, @@ -110,8 +111,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget { @IEditorService private readonly editorService: IEditorService, @IKeybindingService keybindingService: IKeybindingService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index f36a7d22ce..b3f91b9c5b 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -21,14 +21,20 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; import { OpenFolderAction, OpenFileAction, OpenFileFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { isMacintosh } from 'vs/base/common/platform'; +import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -const CONTEXT_DEBUGGER_INTERESTED = new RawContextKey('debuggerInterested', false); +const debugStartLanguageKey = 'debugStartLanguage'; +const CONTEXT_DEBUG_START_LANGUAGE = new RawContextKey(debugStartLanguageKey, undefined); +const CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR = new RawContextKey('debuggerInterestedInActiveEditor', false); export class StartView extends ViewPane { static ID = 'workbench.debug.startView'; static LABEL = localize('start', "Start"); + private debugStartLanguageContext: IContextKey; private debuggerInterestedContext: IContextKey; constructor( @@ -43,40 +49,61 @@ export class StartView extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService openerService: IOpenerService, + @IStorageService storageSevice: IStorageService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); + + this.debugStartLanguageContext = CONTEXT_DEBUG_START_LANGUAGE.bindTo(contextKeyService); + this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.bindTo(contextKeyService); + const lastSetLanguage = storageSevice.get(debugStartLanguageKey, StorageScope.WORKSPACE); + this.debugStartLanguageContext.set(lastSetLanguage); - this.debuggerInterestedContext = CONTEXT_DEBUGGER_INTERESTED.bindTo(contextKeyService); const setContextKey = () => { - const activeEditor = this.editorService.activeTextEditorWidget; - const debuggerLabels = this.debugService.getConfigurationManager().getDebuggerLabelsForEditor(activeEditor); - this.debuggerInterestedContext.set(debuggerLabels.length > 0); + const editor = this.editorService.activeTextEditorWidget; + if (isCodeEditor(editor)) { + const model = editor.getModel(); + const language = model ? model.getLanguageIdentifier().language : undefined; + if (language && this.debugService.getConfigurationManager().isDebuggerInterestedInLanguage(language)) { + this.debugStartLanguageContext.set(language); + this.debuggerInterestedContext.set(true); + storageSevice.store(debugStartLanguageKey, language, StorageScope.WORKSPACE); + return; + } + } + this.debuggerInterestedContext.set(false); }; this._register(editorService.onDidActiveEditorChange(setContextKey)); this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(setContextKey)); + this.registerViews(); } shouldShowWelcome(): boolean { return true; } + + private registerViews(): void { + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), + when: CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR.toNegated() + }); + + const debugKeybinding = this.keybindingService.lookupKeybinding(StartAction.ID); + const debugKeybindingLabel = debugKeybinding ? ` (${debugKeybinding.getLabel()})` : ''; + viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('runAndDebugAction', "[Run and Debug{0}](command:{1})", debugKeybindingLabel, StartAction.ID), + preconditions: [CONTEXT_DEBUGGER_INTERESTED_IN_ACTIVE_EDITOR] + }); + + viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), + when: WorkbenchStateContext.notEqualsTo('empty') + }); + + viewsRegistry.registerViewWelcomeContent(StartView.ID, { + content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), + when: WorkbenchStateContext.isEqualTo('empty') + }); + } } - -const viewsRegistry = Registry.as(Extensions.ViewsRegistry); -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('openAFileWhichCanBeDebugged', "[Open a file](command:{0}) which can be debugged or run.", isMacintosh ? OpenFileFolderAction.ID : OpenFileAction.ID), - when: CONTEXT_DEBUGGER_INTERESTED.toNegated() -}); - -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('runAndDebugAction', "[Run and Debug](command:{0})", StartAction.ID) -}); - -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('customizeRunAndDebug', "To customize Run and Debug [create a launch.json file](command:{0}).", ConfigureAction.ID), - when: WorkbenchStateContext.notEqualsTo('empty') -}); - -viewsRegistry.registerViewWelcomeContent(StartView.ID, { - content: localize('customizeRunAndDebugOpenFolder', "To customize Run and Debug, [open a folder](command:{0}) and create a launch.json file.", isMacintosh ? OpenFileFolderAction.ID : OpenFolderAction.ID), - when: WorkbenchStateContext.isEqualTo('empty') -}); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index da068eed1c..3c3e74938b 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -33,6 +33,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; let forgetScopes = true; @@ -58,8 +59,9 @@ export class VariablesView extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 6e1a5150a6..ddbea95bd1 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -33,6 +33,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreVariableSetEmitter = false; @@ -55,8 +56,9 @@ export class WatchExpressionsView extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 7009d43ae4..73812cd376 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -647,7 +647,7 @@ export interface IConfigurationManager { activateDebuggers(activationEvent: string, debugType?: string): Promise; - getDebuggerLabelsForEditor(editor: editorCommon.IEditor | undefined): string[]; + isDebuggerInterestedInLanguage(language: string): boolean; hasDebugConfigurationProvider(debugType: string): boolean; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; diff --git a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts index af4199e5ad..754f0d359b 100644 --- a/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts @@ -17,6 +17,7 @@ import { LanguageIdentifier, LanguageId } from 'vs/editor/common/modes'; import { createBreakpointDecorations } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import { OverviewRulerLane } from 'vs/editor/common/model'; import { MarkdownString } from 'vs/base/common/htmlContent'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService, undefined!, undefined!); @@ -319,7 +320,7 @@ suite('Debug - Breakpoints', () => { test('decorations', () => { const modelUri = uri.file('/myfolder/my file first.js'); const languageIdentifier = new LanguageIdentifier('testMode', LanguageId.PlainText); - const textModel = TextModel.createFromString( + const textModel = createTextModel( ['this is line one', 'this is line two', ' this is line three it has whitespace at start', 'this is line four', 'this is line five'].join('\n'), TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index ddce32c7ad..b9062e0599 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -102,19 +102,19 @@ export class ExtensionsListView extends ViewPane { @IExtensionsWorkbenchService protected extensionsWorkbenchService: IExtensionsWorkbenchService, @IEditorService private readonly editorService: IEditorService, @IExtensionTipsService protected tipsService: IExtensionTipsService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IExperimentService private readonly experimentService: IExperimentService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService protected readonly productService: IProductService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IMenuService private readonly menuService: IMenuService, @IOpenerService openerService: IOpenerService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.server = options.server; } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index efeea54114..c3199aeaf3 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -16,7 +16,7 @@ import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbenc import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService, FileOperationEvent, FileOperation } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -61,16 +61,25 @@ export class TextFileEditor extends BaseTextEditor { this.updateRestoreViewStateConfiguration(); // Clear view state for deleted files - this._register(this.fileService.onDidFilesChange(e => this.onFilesChanged(e))); + this._register(this.fileService.onDidFilesChange(e => this.onDidFilesChange(e))); + + // Move view state for moved files + this._register(this.fileService.onDidRunOperation(e => this.onDidRunOperation(e))); } - private onFilesChanged(e: FileChangesEvent): void { + private onDidFilesChange(e: FileChangesEvent): void { const deleted = e.getDeleted(); if (deleted?.length) { this.clearTextEditorViewState(deleted.map(d => d.resource)); } } + private onDidRunOperation(e: FileOperationEvent): void { + if (e.operation === FileOperation.MOVE && e.target) { + this.moveTextEditorViewState(e.resource, e.target.resource); + } + } + protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void { super.handleConfigurationChangeEvent(configuration); diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index fac178b5a7..12d25ecaa3 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -19,6 +19,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class EmptyView extends ViewPane { @@ -36,9 +37,10 @@ export class EmptyView extends ViewPane { @IConfigurationService configurationService: IConfigurationService, @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService, - @IOpenerService openerService: IOpenerService + @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.refreshTitle())); this._register(this.labelService.onDidChangeFormatters(() => this.refreshTitle())); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 15fcd92353..db3c30eb33 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -159,20 +159,20 @@ export class ExplorerView extends ViewPane { @IEditorService private readonly editorService: IEditorService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @IDecorationsService private readonly decorationService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, @IThemeService protected themeService: IWorkbenchThemeService, @IMenuService private readonly menuService: IMenuService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IExplorerService private readonly explorerService: IExplorerService, @IStorageService private readonly storageService: IStorageService, @IClipboardService private clipboardService: IClipboardService, @IFileService private readonly fileService: IFileService, @IOpenerService openerService: IOpenerService, ) { - super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: {0}", labelService.getWorkspaceLabel(contextService.getWorkspace())) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: {0}", labelService.getWorkspaceLabel(contextService.getWorkspace())) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index cba0aa7b2b..1966127c53 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -76,9 +76,9 @@ export class OpenEditorsView extends ViewPane { @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IMenuService private readonly menuService: IMenuService, @IWorkingCopyService private readonly workingCopyService: IWorkingCopyService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @@ -87,7 +87,7 @@ export class OpenEditorsView extends ViewPane { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), - }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index e5a6c56b96..71ec019490 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput, GroupIdentifier, IMoveResult } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, Verbosity, TextResourceEditorInput, GroupIdentifier, IMoveResult, isTextEditor } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -19,6 +19,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { isEqual } from 'vs/base/common/resources'; import { Event } from 'vs/base/common/event'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; const enum ForceOpenAs { None, @@ -280,11 +281,26 @@ export class FileEditorInput extends TextResourceEditorInput implements IFileEdi return { editor: { resource: target, - encoding: this.getEncoding() + encoding: this.getEncoding(), + options: { + viewState: this.getViewStateFor(group) + } } }; } + private getViewStateFor(group: GroupIdentifier): IEditorViewState | undefined { + for (const editor of this.editorService.visibleControls) { + if (editor.group.id === group && isEqual(editor.input.resource, this.resource)) { + if (isTextEditor(editor)) { + return editor.getViewState(); + } + } + } + + return undefined; + } + matches(otherInput: unknown): boolean { if (super.matches(otherInput) === true) { return true; diff --git a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts index 863b68c4a6..f8093a347a 100644 --- a/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts @@ -7,9 +7,8 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TestFileService, TestFilesConfigurationService, TestEnvironmentService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ITextFileService, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; +import { TestFilesConfigurationService, TestEnvironmentService, workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -28,17 +27,6 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -class ServiceAccessor { - constructor( - @IEditorService public editorService: IEditorService, - @IEditorGroupsService public editorGroupService: IEditorGroupsService, - @ITextFileService public textFileService: ITextFileService, - @IFileService public fileService: TestFileService, - @IConfigurationService public configurationService: TestConfigurationService - ) { - } -} - suite('EditorAutoSave', () => { let disposables: IDisposable[] = []; @@ -81,7 +69,7 @@ suite('EditorAutoSave', () => { const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - const accessor = instantiationService.createInstance(ServiceAccessor); + const accessor = instantiationService.createInstance(TestServiceAccessor); const editorAutoSave = instantiationService.createInstance(EditorAutoSave); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 84772f989f..d5b28bdb75 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -7,34 +7,23 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { toResource } from 'vs/base/test/common/utils'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode, Verbosity } from 'vs/workbench/common/editor'; -import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { DisposableStore } from 'vs/base/common/lifecycle'; -class ServiceAccessor { - constructor( - @IEditorService public editorService: IEditorService, - @ITextFileService public textFileService: TestTextFileService, - @IModelService public modelService: IModelService - ) { - } -} - suite('Files - FileEditorInput', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test.skip('Basics', async function () { // {{SQL CARBON EDIT}} skip test diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 9996d2fcb9..a0ea4fabde 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -8,9 +8,9 @@ import { Event } from 'vs/base/common/event'; import { TextFileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/textFileEditorTracker'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -26,16 +26,6 @@ import { EditorService } from 'vs/workbench/services/editor/browser/editorServic import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -class ServiceAccessor { - constructor( - @IEditorService public editorService: IEditorService, - @IEditorGroupsService public editorGroupService: IEditorGroupsService, - @ITextFileService public textFileService: TestTextFileService, - @IFileService public fileService: TestFileService - ) { - } -} - suite('Files - TextFileEditorTracker', () => { let disposables: IDisposable[] = []; @@ -58,7 +48,7 @@ suite('Files - TextFileEditorTracker', () => { test.skip('file change event updates model', async function () { // {{SQL CARBON EDIT}} tabcolormode failure const instantiationService = workbenchInstantiationService(); - const accessor = instantiationService.createInstance(ServiceAccessor); + const accessor = instantiationService.createInstance(TestServiceAccessor); const tracker = instantiationService.createInstance(TextFileEditorTracker); @@ -82,7 +72,7 @@ suite('Files - TextFileEditorTracker', () => { (accessor.textFileService.files).dispose(); }); - async function createTracker(): Promise<[EditorPart, ServiceAccessor, TextFileEditorTracker, IInstantiationService, IEditorService]> { + async function createTracker(): Promise<[EditorPart, TestServiceAccessor, TextFileEditorTracker, IInstantiationService, IEditorService]> { const instantiationService = workbenchInstantiationService(); const part = instantiationService.createInstance(EditorPart); @@ -94,7 +84,7 @@ suite('Files - TextFileEditorTracker', () => { const editorService: EditorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - const accessor = instantiationService.createInstance(ServiceAccessor); + const accessor = instantiationService.createInstance(TestServiceAccessor); await part.whenRestored; diff --git a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts index e937b4dce2..43bdca8214 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileOnDiskProvider.test.ts @@ -5,27 +5,19 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileContentProvider } from 'vs/workbench/contrib/files/common/files'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; - -class ServiceAccessor { - constructor( - @IFileService public fileService: TestFileService - ) { - } -} suite('Files - FileOnDiskContentProvider', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('provideTextContent', async () => { diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index b33f641168..6a77a2ca7e 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -51,6 +51,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { Progress } from 'vs/platform/progress/common/progress'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -642,7 +643,7 @@ export class MarkerViewModel extends Disposable { this.codeActionsPromise = createCancelablePromise(cancellationToken => { return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: CodeActionTriggerType.Manual, filter: { include: CodeActionKind.QuickFix } - }, cancellationToken).then(actions => { + }, Progress.None, cancellationToken).then(actions => { return this._register(actions); }); }); diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index 9814a2a734..b6a8109a97 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -97,7 +97,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, - @ITelemetryService private readonly telemetryService: ITelemetryService, + @ITelemetryService telemetryService: ITelemetryService, @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @@ -108,7 +108,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, ) { - super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.panelFoucusContextKey = Constants.MarkerViewFocusContextKey.bindTo(contextKeyService); this.panelState = new Memento(Constants.MARKERS_VIEW_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 3da91d71e2..19a0b5e58b 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -50,6 +50,7 @@ import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; class RequestState { @@ -270,8 +271,9 @@ export class OutlinePane extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index adb58aee7f..da31ecb799 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -43,14 +43,15 @@ export class OutputViewPane extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, @IOutputService private readonly outputService: IOutputService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.editor = instantiationService.createInstance(OutputEditor); this._register(this.editor.onTitleAreaUpdate(() => { this.updateTitle(this.editor.getTitle()); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index e242804055..c0d8c1942b 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -49,7 +49,7 @@ import { ExcludeSettingWidget, IListChangeEvent, IListDataItem, ListSettingWidge import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; -import { IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncEnablementService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; const $ = DOM.$; @@ -1238,7 +1238,7 @@ export class SettingTreeRenderers { private getActionsForSetting(setting: ISetting): IAction[] { const enableSync = this._userDataSyncEnablementService.isEnabled(); return enableSync && !setting.disallowSyncIgnore ? - [this._instantiationService.createInstance(StopSyncingSettingAction, setting)] : + [this._instantiationService.createInstance(SyncSettingAction, setting)] : []; } @@ -1622,7 +1622,7 @@ class CopySettingAsJSONAction extends Action { } } -class StopSyncingSettingAction extends Action { +class SyncSettingAction extends Action { static readonly ID = 'settings.stopSyncingSetting'; static readonly LABEL = localize('stopSyncingSetting', "Sync This Setting"); @@ -1630,24 +1630,38 @@ class StopSyncingSettingAction extends Action { private readonly setting: ISetting, @IConfigurationService private readonly configService: IConfigurationService, ) { - super(StopSyncingSettingAction.ID, StopSyncingSettingAction.LABEL); + super(SyncSettingAction.ID, SyncSettingAction.LABEL); + this._register(Event.filter(configService.onDidChangeConfiguration, e => e.affectsConfiguration('sync.ignoredSettings'))(() => this.update())); this.update(); } - update() { - const ignoredSettings = getIgnoredSettings(this.configService); + async update() { + const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), this.configService); this.checked = !ignoredSettings.includes(this.setting.key); } async run(): Promise { + // first remove the current setting completely from ignored settings let currentValue = [...this.configService.getValue('sync.ignoredSettings')]; - if (this.checked) { - currentValue.push(this.setting.key); - } else { - currentValue = currentValue.filter(v => v !== this.setting.key); + currentValue = currentValue.filter(v => v !== this.setting.key && v !== `-${this.setting.key}`); + + const defaultIgnoredSettings = getDefaultIgnoredSettings(); + const isDefaultIgnored = defaultIgnoredSettings.includes(this.setting.key); + const askedToSync = !this.checked; + + // If asked to sync, then add only if it is ignored by default + if (askedToSync && isDefaultIgnored) { + currentValue.push(`-${this.setting.key}`); } + + // If asked not to sync, then add only if it is not ignored by default + if (!askedToSync && !isDefaultIgnored) { + currentValue.push(this.setting.key); + } + this.configService.updateValue('sync.ignoredSettings', currentValue.length ? currentValue : undefined, ConfigurationTarget.USER); return Promise.resolve(undefined); } + } diff --git a/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts b/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts index 9e1557a81e..80c2abbda9 100644 --- a/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts +++ b/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { Position } from 'vs/editor/common/core/position'; suite('SmartSnippetInserter', () => { function testSmartSnippetInserter(text: string[], runner: (assert: (desiredPos: Position, pos: Position, prepend: string, append: string) => void) => void): void { - let model = TextModel.createFromString(text.join('\n')); + let model = createTextModel(text.join('\n')); runner((desiredPos, pos, prepend, append) => { let actual = SmartSnippetInserter.insertSnippet(model, desiredPos); let expected = { diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index d66c9cd168..93bdc66a06 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -374,8 +374,9 @@ class HelpPanel extends ViewPane { @IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 4503ecbf5d..c60b4eac52 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -40,6 +40,7 @@ import { URI } from 'vs/base/common/uri'; import { RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export const forwardedPortsViewEnabled = new RawContextKey('forwardedPortsViewEnabled', false); @@ -457,9 +458,10 @@ export class TunnelPanel extends ViewPane { @INotificationService private readonly notificationService: INotificationService, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService themeService: IThemeService, - @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index 82951159b2..d9cdb14cbb 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -33,6 +33,7 @@ import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/vie import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface ISpliceEvent { index: number; @@ -185,13 +186,14 @@ export class MainPane extends ViewPane { @IContextMenuService protected contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, @IConfigurationService configurationService: IConfigurationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index 2b0c2d1ba3..6d7ad578e1 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -68,6 +68,7 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2 import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; type TreeElement = ISCMResourceGroup | IResourceNode | ISCMResource; @@ -640,8 +641,9 @@ export class RepositoryPane extends ViewPane { @IStorageService private storageService: IStorageService, @IModelService private modelService: IModelService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); this._register(this.menus); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index d82db1bb98..2008b3a8d2 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -56,8 +56,9 @@ export class EmptyPane extends ViewPane { @IInstantiationService instantiationService: IInstantiationService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); } shouldShowWelcome(): boolean { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 381074ff9a..6520062069 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -54,7 +54,6 @@ import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/ex import { assertType, assertIsDefined } from 'vs/base/common/types'; import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import product from 'vs/platform/product/common/product'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); @@ -775,21 +774,16 @@ configurationRegistry.registerConfiguration({ default: 300, markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") }, - 'search.enableSearchEditorPreview': { - type: 'boolean', - default: product.quality !== 'stable', - description: nls.localize('search.enableSearchEditorPreview', "Experimental: When enabled, allows opening workspace search results in an editor.") - }, - 'search.searchEditorPreview.doubleClickBehaviour': { + 'search.searchEditor.doubleClickBehaviour': { type: 'string', enum: ['selectWord', 'goToLocation', 'openLocationToSide'], default: 'goToLocation', enumDescriptions: [ - nls.localize('search.searchEditorPreview.doubleClickBehaviour.selectWord', "Double clicking selects the word under the cursor."), - nls.localize('search.searchEditorPreview.doubleClickBehaviour.goToLocation', "Double clicking opens the result in the active editor group."), - nls.localize('search.searchEditorPreview.doubleClickBehaviour.openLocationToSide', "Double clicking opens the result in the editor group to the side, creating one if it does not yet exist."), + nls.localize('search.searchEditor.doubleClickBehaviour.selectWord', "Double clicking selects the word under the cursor."), + nls.localize('search.searchEditor.doubleClickBehaviour.goToLocation', "Double clicking opens the result in the active editor group."), + nls.localize('search.searchEditor.doubleClickBehaviour.openLocationToSide', "Double clicking opens the result in the editor group to the side, creating one if it does not yet exist."), ], - markdownDescription: nls.localize('search.searchEditorPreview.doubleClickBehaviour', "Configure effect of double clicking a result in a Search Editor.\n\n `#search.enableSearchEditorPreview#` must be enabled for this setting to have an effect.") + markdownDescription: nls.localize('search.searchEditor.doubleClickBehaviour', "Configure effect of double clicking a result in a search editor.") }, 'search.sortOrder': { 'type': 'string', diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 46f9d374a7..88505702d3 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -34,7 +34,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TreeResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -67,6 +67,7 @@ import { Color, RGBA } from 'vs/base/common/color'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { OpenSearchEditorAction, createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const $ = dom.$; @@ -173,9 +174,10 @@ export class SearchView extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this.container = dom.$('.search-view'); @@ -231,14 +233,9 @@ export class SearchView extends ViewPane { this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), + this._register(this.instantiationService.createInstance(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL)) ]; - if (this.searchConfig.enableSearchEditorPreview) { - this.actions.push( - this._register(this.instantiationService.createInstance(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL)) - ); - } - this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); this.toggleCollapseAction = this._register(this.instantiationService.createInstance(ToggleCollapseAndExpandAction, ToggleCollapseAndExpandAction.ID, ToggleCollapseAndExpandAction.LABEL, collapseDeepestExpandedLevelAction, expandAllAction)); @@ -877,7 +874,6 @@ export class SearchView extends ViewPane { this.searchWidget.setValue(selectedText); this.pauseSearching = false; updatedText = true; - if (this.searchConfig.searchOnType) { this.triggerQueryChange(); } } } @@ -1346,7 +1342,7 @@ export class SearchView extends ViewPane { this.inputPatternIncludes.onSearchSubmit(); }); - this.viewModel.cancelSearch(); + this.viewModel.cancelSearch(true); this.currentSearchQ = this.currentSearchQ .then(() => this.doSearch(query, excludePatternText, includePatternText, triggeredOnType)) @@ -1391,6 +1387,10 @@ export class SearchView extends ViewPane { this.updateActions(); const hasResults = !this.viewModel.searchResult.isEmpty(); + if (completed?.exit === SearchCompletionExitCode.NewSearchStarted) { + return; + } + if (completed && completed.limitHit) { this.searchWidget.searchInput.showMessage({ content: nls.localize('searchMaxResultsWarning', "The result set only contains a subset of all matches. Please be more specific in your search to narrow down the results."), @@ -1556,23 +1556,19 @@ export class SearchView extends ViewPane { resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled"); } - if (this.searchConfig.enableSearchEditorPreview) { - dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); - const span = dom.append(messageEl, $('span')); - const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); + dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); + const span = dom.append(messageEl, $('span')); + const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); - openInEditorLink.title = appendKeyBindingLabel( - nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), - this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); + openInEditorLink.title = appendKeyBindingLabel( + nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), + this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); - this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { - dom.EventHelper.stop(e, false); - this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue()); - })); + this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { + dom.EventHelper.stop(e, false); + this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue()); + })); - } else { - dom.append(messageEl, $('span', undefined, resultMsg)); - } this.reLayout(); } else if (!msgWasHidden) { dom.hide(this.messagesElement); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 6b4375ea34..5e9ffb6ca8 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -154,7 +154,6 @@ export class SearchWidget extends Widget { private readonly _onDidToggleContext = new Emitter(); readonly onDidToggleContext: Event = this._onDidToggleContext.event; - private temporarilySkipSearchOnChange = false; private showContextCheckbox!: Checkbox; private contextLinesInput!: InputBox; @@ -488,12 +487,10 @@ export class SearchWidget extends Widget { this.setReplaceAllActionState(false); if (this.searchConfiguration.searchOnType) { - if (!this.temporarilySkipSearchOnChange) { - this._onSearchCancel.fire({ focus: false }); - if (this.searchInput.getRegex()) { - try { - const regex = new RegExp(this.searchInput.getValue(), 'ug'); - const matchienessHeuristic = ` + if (this.searchInput.getRegex()) { + try { + const regex = new RegExp(this.searchInput.getValue(), 'ug'); + const matchienessHeuristic = ` ~!@#$%^&*()_+ \`1234567890-= qwertyuiop[]\\ @@ -503,18 +500,17 @@ export class SearchWidget extends Widget { zxcvbnm,./ ZXCVBNM<>? `.match(regex)?.length ?? 0; - const delayMultiplier = - matchienessHeuristic < 50 ? 1 : - matchienessHeuristic < 100 ? 5 : // expressions like `.` or `\w` - 10; // only things matching empty string + const delayMultiplier = + matchienessHeuristic < 50 ? 1 : + matchienessHeuristic < 100 ? 5 : // expressions like `.` or `\w` + 10; // only things matching empty string - this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); - } catch { - // pass - } - } else { - this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod); + this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod * delayMultiplier); + } catch { + // pass } + } else { + this.submitSearch(true, this.searchConfiguration.searchOnTypeDebouncePeriod); } } } diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index f8f973fd45..d63fc26960 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult, SearchSortOrder, SearchCompletionExitCode } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -968,6 +968,7 @@ export class SearchModel extends Disposable { readonly onReplaceTermChanged: Event = this._onReplaceTermChanged.event; private currentCancelTokenSource: CancellationTokenSource | null = null; + private searchCancelledForNewSearch: boolean = false; constructor( @ISearchService private readonly searchService: ISearchService, @@ -1016,7 +1017,7 @@ export class SearchModel extends Disposable { } search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { - this.cancelSearch(); + this.cancelSearch(true); this._searchQuery = query; @@ -1114,7 +1115,12 @@ export class SearchModel extends Disposable { private onSearchError(e: any, duration: number): void { if (errors.isPromiseCanceledError(e)) { - this.onSearchCompleted(null, duration); + this.onSearchCompleted( + this.searchCancelledForNewSearch + ? { exit: SearchCompletionExitCode.NewSearchStarted, results: [] } + : null, + duration); + this.searchCancelledForNewSearch = false; } } @@ -1133,8 +1139,9 @@ export class SearchModel extends Disposable { return this.configurationService.getValue('search'); } - cancelSearch(): boolean { + cancelSearch(cancelledForNewSearch = false): boolean { if (this.currentCancelTokenSource) { + this.searchCancelledForNewSearch = cancelledForNewSearch; this.currentCancelTokenSource.cancel(); return true; } diff --git a/src/vs/workbench/contrib/searchEditor/browser/constants.ts b/src/vs/workbench/contrib/searchEditor/browser/constants.ts index 4d3e23bd87..98d660959e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/constants.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/constants.ts @@ -12,8 +12,8 @@ export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseS export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; +export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; -export const EnableSearchEditorPreview = new RawContextKey('previewSearchEditor', false); export const InSearchEditor = new RawContextKey('inSearchEditor', false); export const SearchEditorScheme = 'search-editor'; diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index 57aa23a65a..dd288caea4 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -10,7 +10,6 @@ import { URI } from 'vs/base/common/uri'; import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding } from 'vs/editor/contrib/find/findModel'; import { localize } from 'vs/nls'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -29,7 +28,8 @@ import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEd import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; //#region Editor Descriptior Registry.as(EditorExtensions.Editors).registerEditor( @@ -51,16 +51,7 @@ class SearchEditorContribution implements IWorkbenchContribution { @IInstantiationService protected readonly instantiationService: IInstantiationService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @IContextKeyService protected readonly contextKeyService: IContextKeyService, - @IConfigurationService private readonly configurationService: IConfigurationService, ) { - const enableSearchEditorPreview = SearchEditorConstants.EnableSearchEditorPreview.bindTo(this.contextKeyService); - - enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); - configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('search.previewSearchEditor')) { - enableSearchEditorPreview.set(this.searchConfig.enableSearchEditorPreview); - } - }); this.editorService.overrideOpenEditor((editor, options, group) => { const resource = editor.resource; @@ -83,10 +74,6 @@ class SearchEditorContribution implements IWorkbenchContribution { return { override: Promise.resolve(opened) }; }); } - - private get searchConfig(): ISearchConfigurationProperties { - return this.configurationService.getValue('search'); - } } const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); @@ -158,6 +145,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Alt | KeyCode.KEY_L, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_L } }); + +CommandsRegistry.registerCommand( + SearchEditorConstants.RerunSearchEditorSearchCommandId, + (accessor: ServicesAccessor) => { + const activeControl = accessor.get(IEditorService).activeControl; + if (activeControl instanceof SearchEditor) { + activeControl.triggerSearch({ resetCursor: false }); + } + }); //#endregion //#region Actions @@ -167,12 +163,10 @@ const category = localize('search', "Search Editor"); registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, - ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey, SearchEditorConstants.EnableSearchEditorPreview)), - 'Search Editor: Open Results in Editor', category, - ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); + ContextKeyExpr.and(SearchConstants.HasSearchResults, SearchConstants.SearchViewFocusedKey)), + 'Search Editor: Open Results in Editor', category); registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenSearchEditorAction, OpenSearchEditorAction.ID, OpenSearchEditorAction.LABEL), - 'Search Editor: Open New Search Editor', category, - ContextKeyExpr.and(SearchEditorConstants.EnableSearchEditorPreview)); + 'Search Editor: Open New Search Editor', category); //#endregion diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 56e25a9b3b..f95363d5e1 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -77,6 +77,7 @@ export class SearchEditor extends BaseTextEditor { private searchHistoryDelayer: Delayer; private messageDisposables: IDisposable[] = []; private container: HTMLElement; + private searchModel: SearchModel; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -107,6 +108,8 @@ export class SearchEditor extends BaseTextEditor { this.inputFocusContextKey = InputBoxFocusedKey.bindTo(scopedContextKeyService); this.searchOperation = this._register(new LongRunningOperation(progressService)); this.searchHistoryDelayer = new Delayer(2000); + + this.searchModel = this._register(this.instantiationService.createInstance(SearchModel)); } createEditor(parent: HTMLElement) { @@ -200,7 +203,7 @@ export class SearchEditor extends BaseTextEditor { this.searchResultEditor = super.getControl() as CodeEditorWidget; this.searchResultEditor.onMouseUp(e => { if (e.event.detail === 2) { - const behaviour = this.configurationService.getValue('search').searchEditorPreview.doubleClickBehaviour; + const behaviour = this.configurationService.getValue('search').searchEditor.doubleClickBehaviour; const position = e.target.position; if (position && behaviour !== 'selectWord') { const line = this.searchResultEditor.getModel()?.getLineContent(position.lineNumber) ?? ''; @@ -326,6 +329,8 @@ export class SearchEditor extends BaseTextEditor { } private async doRunSearch() { + this.searchModel.cancelSearch(true); + const startInput = this.getInput(); this.searchHistoryDelayer.trigger(() => { @@ -372,30 +377,26 @@ export class SearchEditor extends BaseTextEditor { catch (err) { return; } - const searchModel = this.instantiationService.createInstance(SearchModel); + this.searchOperation.start(500); - await searchModel.search(query).finally(() => this.searchOperation.stop()); + await this.searchModel.search(query).finally(() => this.searchOperation.stop()); const input = this.getInput(); if (!input || input !== startInput || JSON.stringify(config) !== JSON.stringify(this.readConfigFromWidget())) { - - searchModel.dispose(); return; } const controller = ReferencesController.get(this.searchResultEditor); controller.closeWidget(false); const labelFormatter = (uri: URI): string => this.labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false); + const results = serializeSearchResultForEditor(this.searchModel.searchResult, config.includes, config.excludes, config.contextLines, labelFormatter, false); const { header, body } = await input.getModels(); this.modelService.updateModel(body, results.text); header.setValue(serializeSearchConfiguration(config)); input.setDirty(input.resource.scheme !== 'search-editor'); input.setMatchRanges(results.matchRanges); - - searchModel.dispose(); } layout(dimension: DOM.Dimension) { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index b3ed3e1384..3eacf6e293 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -61,7 +61,6 @@ export class OpenSearchEditorAction extends Action { static readonly LABEL = localize('search.openNewEditor', "Open New Search Editor"); constructor(id: string, label: string, - @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label, 'codicon-new-file'); @@ -76,9 +75,7 @@ export class OpenSearchEditorAction extends Action { } async run() { - if (this.configurationService.getValue('search').enableSearchEditorPreview) { - await this.instantiationService.invokeFunction(openNewSearchEditor); - } + await this.instantiationService.invokeFunction(openNewSearchEditor); } } @@ -89,7 +86,6 @@ export class OpenResultsInEditorAction extends Action { constructor(id: string, label: string, @IViewsService private viewsService: IViewsService, - @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(id, label, 'codicon-go-to-file'); @@ -106,7 +102,7 @@ export class OpenResultsInEditorAction extends Action { async run() { const searchView = getSearchView(this.viewsService); - if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { + if (searchView) { await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); } } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 28151ae90d..f980ee2c32 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -8,7 +8,7 @@ import { SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser import { Position } from 'vs/editor/common/core/position'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; @@ -67,7 +67,7 @@ suite('SnippetsService', function () { test('snippet completions - simple', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - const model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); + const model = createTextModel('', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.incomplete, undefined); @@ -78,7 +78,7 @@ suite('SnippetsService', function () { test('snippet completions - with prefix', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - const model = TextModel.createFromString('bar', undefined, modeService.getLanguageIdentifier('fooLang')); + const model = createTextModel('bar', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => { assert.equal(result.incomplete, undefined); @@ -113,7 +113,7 @@ suite('SnippetsService', function () { )]); const provider = new SnippetCompletionProvider(modeService, snippetService); - const model = TextModel.createFromString('bar-bar', undefined, modeService.getLanguageIdentifier('fooLang')); + const model = createTextModel('bar-bar', undefined, modeService.getLanguageIdentifier('fooLang')); await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => { assert.equal(result.incomplete, undefined); @@ -174,19 +174,19 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('\t { assert.equal(result.suggestions.length, 1); model.dispose(); - model = TextModel.createFromString('\t { assert.equal(result.suggestions.length, 1); assert.equal((result.suggestions[0].range as any).insert.startColumn, 2); model.dispose(); - model = TextModel.createFromString('a { assert.equal(result.suggestions.length, 1); @@ -209,7 +209,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.suggestions.length, 1); return provider.provideCompletionItems(model, new Position(2, 2), context)!; @@ -239,7 +239,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('', undefined, modeService.getLanguageIdentifier('fooLang')); return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.suggestions.length, 2); let [first, second] = result.suggestions; @@ -266,7 +266,7 @@ suite('SnippetsService', function () { )]); const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('p-', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('p-', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); @@ -291,7 +291,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; assert.equal(result.suggestions.length, 1); @@ -310,7 +310,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString(':', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel(':', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 0); @@ -329,7 +329,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('template', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('template', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; assert.equal(result.suggestions.length, 1); @@ -352,7 +352,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; assert.equal(result.suggestions.length, 1); @@ -374,7 +374,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('.🐷-a-b', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('.🐷-a-b', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!; assert.equal(result.suggestions.length, 1); @@ -395,7 +395,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('a ', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('a ', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); @@ -422,14 +422,14 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString(' <', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel(' <', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); let [first] = result.suggestions; assert.equal((first.range as any).insert.startColumn, 2); - model = TextModel.createFromString('1', undefined, modeService.getLanguageIdentifier('fooLang')); + model = createTextModel('1', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); @@ -450,7 +450,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); - let model = TextModel.createFromString('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + let model = createTextModel('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); @@ -458,7 +458,7 @@ suite('SnippetsService', function () { assert.equal((first.range as any).insert.endColumn, 3); assert.equal((first.range as any).replace.endColumn, 9); - model = TextModel.createFromString('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); + model = createTextModel('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); @@ -466,7 +466,7 @@ suite('SnippetsService', function () { assert.equal((first.range as any).insert.endColumn, 3); assert.equal((first.range as any).replace.endColumn, 3); - model = TextModel.createFromString('not word', undefined, modeService.getLanguageIdentifier('fooLang')); + model = createTextModel('not word', undefined, modeService.getLanguageIdentifier('fooLang')); result = await provider.provideCompletionItems(model, new Position(1, 1), context)!; assert.equal(result.suggestions.length, 1); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 0a8a16db3d..509b931cb3 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -57,13 +57,13 @@ import { TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource, KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult, USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; import * as TaskConfig from '../common/taskConfiguration'; import { TerminalTaskSystem } from './terminalTaskSystem'; -import { IQuickInputService, IQuickPickItem, QuickPickInput, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -80,15 +80,12 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { find } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IViewsService } from 'vs/workbench/common/views'; -import { ProviderProgressMananger } from 'vs/workbench/contrib/tasks/browser/providerProgressManager'; const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history'; const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt'; const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; -const SETTINGS_GROUP_KEY = 'settings'; - export namespace ConfigureTaskAction { export const ID = 'workbench.action.tasks.configureTaskRunner'; export const TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task"); @@ -221,7 +218,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _providers: Map; private _providerTypes: Map; protected _taskSystemInfos: Map; - private _providerProgressManager: ProviderProgressMananger | undefined; protected _workspaceTasksPromise?: Promise>; protected _areJsonTasksSupportedPromise: Promise = Promise.resolve(false); @@ -553,7 +549,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return this.getGroupedTasks().then((map) => { let values = map.get(folder); - values = values.concat(map.get(SETTINGS_GROUP_KEY)); + values = values.concat(map.get(USER_TASKS_GROUP_KEY)); if (!values) { return undefined; @@ -1171,8 +1167,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); let resolver: ITaskResolver = { - resolve: (uri: URI, alias: string) => { - let data = resolverData.get(uri.toString()); + resolve: (uri: URI | string, alias: string) => { + let data = resolverData.get(typeof uri === 'string' ? uri : uri.toString()); if (!data) { return undefined; } @@ -1238,8 +1234,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }); return { - resolve: (uri: URI, identifier: string | TaskIdentifier | undefined) => { - let data = uri ? resolverData.get(uri.toString()) : undefined; + resolve: (uri: URI | string, identifier: string | TaskIdentifier | undefined) => { + let data = resolverData.get(typeof uri === 'string' ? uri : uri.toString()); if (!data || !identifier) { return undefined; } @@ -1349,24 +1345,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected abstract getTaskSystem(): ITaskSystem; - private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { + private async provideTasksWithWarning(provider: ITaskProvider, type: string, validTypes: IStringDictionary): Promise { return new Promise(async (resolve, reject) => { - let isDone = false; - let disposable: IDisposable | undefined; - const providePromise = provider.provideTasks(validTypes); - this._providerProgressManager?.addProvider(type, providePromise); - disposable = this._providerProgressManager?.canceled.token.onCancellationRequested(() => { - if (!isDone) { - resolve(); - } - }); - providePromise.then((value) => { - isDone = true; - disposable?.dispose(); + provider.provideTasks(validTypes).then((value) => { resolve(value); }, (e) => { - isDone = true; - disposable?.dispose(); reject(e); }); }); @@ -1378,11 +1361,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); validTypes['shell'] = true; validTypes['process'] = true; - this._providerProgressManager = new ProviderProgressMananger(); return new Promise(resolve => { let result: TaskSet[] = []; let counter: number = 0; - let done = (value: TaskSet | undefined) => { + let done = (value: TaskSet) => { if (value) { result.push(value); } @@ -1600,7 +1582,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const userTasks = await this.computeUserTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); if (userTasks) { - result.set(SETTINGS_GROUP_KEY, userTasks); + result.set(USER_TASKS_GROUP_KEY, userTasks); } const workspaceFileTasks = await this.computeWorkspaceFileTasks(this.workspaceFolders[0], runSource).then((value) => value, () => undefined); if (workspaceFileTasks && this._workspace && this._workspace.configuration) { @@ -2078,69 +2060,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } return entries; }); - - const picker: IQuickPick = this.quickInputService.createQuickPick(); - picker.placeholder = placeHolder; - picker.matchOnDescription = true; - picker.ignoreFocusOut = true; - - picker.onDidTriggerItemButton(context => { - let task = context.item.task; - this.quickInputService.cancel(); - if (ContributedTask.is(task)) { - this.customize(task, undefined, true); - } else if (CustomTask.is(task)) { - this.openConfig(task); - } - }); - picker.busy = true; - const progressManager = this._providerProgressManager; - const progressTimeout = setTimeout(() => { - if (progressManager) { - progressManager.showProgress = (stillProviding, total) => { - let message = undefined; - if (stillProviding.length > 0) { - message = nls.localize('pickProgressManager.description', 'Detecting tasks ({0} of {1}): {2} in progress', total - stillProviding.length, total, stillProviding.join(', ')); - } - picker.description = message; - }; - progressManager.addOnDoneListener(() => { - picker.focusOnInput(); - picker.customButton = false; - }); - if (!progressManager.isDone) { - picker.customLabel = nls.localize('taskQuickPick.cancel', "Stop detecting"); - picker.onDidCustom(() => { - this._providerProgressManager?.cancel(); - }); - picker.customButton = true; + return this.quickInputService.pick(pickEntries, { + placeHolder, + matchOnDescription: true, + onDidTriggerItemButton: context => { + let task = context.item.task; + this.quickInputService.cancel(); + if (ContributedTask.is(task)) { + this.customize(task, undefined, true); + } else if (CustomTask.is(task)) { + this.openConfig(task); } } - }, 1000); - pickEntries.then(entries => { - clearTimeout(progressTimeout); - progressManager?.dispose(); - picker.busy = false; - picker.items = entries; - }); - picker.show(); - - return new Promise(resolve => { - this._register(picker.onDidAccept(async () => { - let selection = picker.selectedItems ? picker.selectedItems[0] : undefined; - if (cancellationToken.isCancellationRequested) { - // canceled when there's only one task - const task = (await pickEntries)[0]; - if ((task).task) { - selection = task; - } + }, cancellationToken).then(async (selection) => { + if (cancellationToken.isCancellationRequested) { + // canceled when there's only one task + const task = (await pickEntries)[0]; + if ((task).task) { + selection = task; } - picker.dispose(); - if (!selection) { - resolve(); - } - resolve(selection); - })); + } + if (!selection) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + return selection; }); } @@ -2173,9 +2116,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (identifier !== undefined) { this.getGroupedTasks().then((grouped) => { let resolver = this.createResolver(grouped); - let folders = this.contextService.getWorkspace().folders; + let folders: (IWorkspaceFolder | string)[] = this.contextService.getWorkspace().folders; + folders = folders.concat([USER_TASKS_GROUP_KEY]); for (let folder of folders) { - let task = resolver.resolve(folder.uri, identifier); + let task = resolver.resolve(typeof folder === 'string' ? folder : folder.uri, identifier); if (task) { this.run(task).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here diff --git a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts b/src/vs/workbench/contrib/tasks/browser/providerProgressManager.ts deleted file mode 100644 index 2eb181665a..0000000000 --- a/src/vs/workbench/contrib/tasks/browser/providerProgressManager.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. - *--------------------------------------------------------------------------------------------*/ - -import { TaskSet } from 'vs/workbench/contrib/tasks/common/tasks'; -import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; - -export class ProviderProgressMananger extends Disposable { - private _onProviderComplete: Emitter = new Emitter(); - private _stillProviding: Set = new Set(); - private _totalProviders: number = 0; - private _onDone: Emitter = new Emitter(); - private _isDone: boolean = false; - private _showProgress: ((remaining: string[], total: number) => void) | undefined; - public canceled: CancellationTokenSource = new CancellationTokenSource(); - - constructor() { - super(); - this._register(this._onProviderComplete.event(taskType => { - this._stillProviding.delete(taskType); - if (this._stillProviding.size === 0) { - this._isDone = true; - this._onDone.fire(); - } - if (this._showProgress) { - this._showProgress(Array.from(this._stillProviding), this._totalProviders); - } - })); - } - - public addProvider(taskType: string, provider: Promise) { - this._totalProviders++; - this._stillProviding.add(taskType); - provider.then(() => this._onProviderComplete.fire(taskType)); - } - - public addOnDoneListener(onDoneListener: () => void) { - this._register(this._onDone.event(onDoneListener)); - } - - set showProgress(progressDisplayFunction: (remaining: string[], total: number) => void) { - this._showProgress = progressDisplayFunction; - this._showProgress(Array.from(this._stillProviding), this._totalProviders); - } - - get isDone(): boolean { - return this._isDone; - } - - public cancel() { - this._isDone = true; - if (this._showProgress) { - this._showProgress([], 0); - } - this._onDone.fire(); - this.canceled.cancel(); - } -} diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 0e11cb6927..fb27b01e37 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -438,6 +438,7 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.DependsOnStarted, task)); promise = this.executeTask(dependencyTask, resolver, trigger, alreadyResolved); } + promises.push(promise); if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { const promiseResult = await promise; if (promiseResult.exitCode === 0) { diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index a7b11a1333..b159f3250b 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -22,6 +22,8 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import * as Tasks from './tasks'; import { TaskDefinitionRegistry } from './taskDefinitionRegistry'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { URI } from 'vs/base/common/uri'; +import { USER_TASKS_GROUP_KEY } from 'vs/workbench/contrib/tasks/common/taskService'; export const enum ShellQuoting { @@ -1231,9 +1233,15 @@ namespace GroupKind { } namespace TaskDependency { - export function from(this: void, external: string | TaskIdentifier, context: ParseContext): Tasks.TaskDependency | undefined { + export function from(this: void, external: string | TaskIdentifier, context: ParseContext, source: TaskConfigSource): Tasks.TaskDependency | undefined { if (Types.isString(external)) { - return { uri: context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri, task: external }; + let uri: URI | string; + if (source === TaskConfigSource.User) { + uri = USER_TASKS_GROUP_KEY; + } else { + uri = context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri; + } + return { uri, task: external }; } else if (TaskIdentifier.is(external)) { return { uri: context.workspace && context.workspace.configuration ? context.workspace.configuration : context.workspaceFolder.uri, @@ -1267,7 +1275,7 @@ namespace ConfigurationProperties { { property: 'options' } ]; - export function from(this: void, external: ConfigurationProperties & { [key: string]: any; }, context: ParseContext, includeCommandOptions: boolean, properties?: IJSONSchemaMap): Tasks.ConfigurationProperties | undefined { + export function from(this: void, external: ConfigurationProperties & { [key: string]: any; }, context: ParseContext, includeCommandOptions: boolean, source: TaskConfigSource, properties?: IJSONSchemaMap): Tasks.ConfigurationProperties | undefined { if (!external) { return undefined; } @@ -1311,14 +1319,14 @@ namespace ConfigurationProperties { if (external.dependsOn !== undefined) { if (Types.isArray(external.dependsOn)) { result.dependsOn = external.dependsOn.reduce((dependencies: Tasks.TaskDependency[], item): Tasks.TaskDependency[] => { - const dependency = TaskDependency.from(item, context); + const dependency = TaskDependency.from(item, context, source); if (dependency) { dependencies.push(dependency); } return dependencies; }, []); } else { - const dependsOnValue = TaskDependency.from(external.dependsOn, context); + const dependsOnValue = TaskDependency.from(external.dependsOn, context, source); result.dependsOn = dependsOnValue ? [dependsOnValue] : undefined; } } @@ -1435,7 +1443,7 @@ namespace ConfiguringTask { RunOptions.fromConfiguration(external.runOptions), {} ); - let configuration = ConfigurationProperties.from(external, context, true, typeDeclaration.properties); + let configuration = ConfigurationProperties.from(external, context, true, source, typeDeclaration.properties); if (configuration) { result.configurationProperties = Objects.assign(result.configurationProperties, configuration); if (result.configurationProperties.name) { @@ -1512,7 +1520,7 @@ namespace CustomTask { identifier: taskName, } ); - let configuration = ConfigurationProperties.from(external, context, false); + let configuration = ConfigurationProperties.from(external, context, false, source); if (configuration) { result.configurationProperties = Objects.assign(result.configurationProperties, configuration); } diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index cc4dc5233b..b364b14866 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -50,6 +50,8 @@ export interface WorkspaceFolderTaskResult extends WorkspaceTaskResult { workspaceFolder: IWorkspaceFolder; } +export const USER_TASKS_GROUP_KEY = 'settings'; + export interface ITaskService { _serviceBrand: undefined; onDidStateChange: Event; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index 34b72fe50c..e908ff67d7 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -93,7 +93,7 @@ export interface ITaskExecuteResult { } export interface ITaskResolver { - resolve(uri: URI, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined; + resolve(uri: URI | string, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined; } export interface TaskTerminateResponse extends TerminateResponse { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 2a04ca3458..68f0a85816 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -438,7 +438,7 @@ export interface KeyedTaskIdentifier extends TaskIdentifier { } export interface TaskDependency { - uri: URI; + uri: URI | string; task: string | KeyedTaskIdentifier | undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 22368eb002..fd0b32657e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -57,7 +57,7 @@ export class TerminalViewPane extends ViewPane { @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, ) { - super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService); + super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index e1efc70983..95a7596b74 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -36,6 +36,7 @@ import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuItemAction, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { fromNow } from 'vs/base/common/date'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; // TODO[ECA]: Localize all the strings @@ -78,8 +79,9 @@ export class TimelinePane extends ViewPane { @ITimelineService protected timelineService: ITimelineService, @IOpenerService openerService: IOpenerService, @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, ) { - super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...options, titleMenuId: MenuId.TimelineTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this._menus = this._register(this.instantiationService.createInstance(TimelineMenus, this.id)); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 5e8ecf29bf..3cd5d37a10 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -37,11 +37,11 @@ class UserDataSyncSettingsMigrationContribution implements IWorkbenchContributio } private async removeFromConfiguration(): Promise { - await this.configurationService.updateValue('sync.enable', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableSettings', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableKeybindings', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableUIState', undefined, ConfigurationTarget.USER); - await this.configurationService.updateValue('sync.enableExtensions', undefined, ConfigurationTarget.USER); + await this.configurationService.updateValue('sync.enable', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableSettings', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableKeybindings', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableUIState', undefined, {}, ConfigurationTarget.USER, true); + await this.configurationService.updateValue('sync.enableExtensions', undefined, {}, ConfigurationTarget.USER, true); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 8728349221..2a3454705a 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -28,7 +28,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CONTEXT_SYNC_STATE, getSyncSourceFromRemoteContentResource, getUserDataSyncStore, ISyncConfiguration, IUserDataAutoSyncService, IUserDataSyncService, IUserDataSyncStore, registerConfiguration, SyncSource, SyncStatus, toRemoteContentResource, UserDataSyncError, UserDataSyncErrorCode, USER_DATA_SYNC_SCHEME, IUserDataSyncEnablementService, ResourceKey, getSyncSourceFromPreviewResource, CONTEXT_SYNC_ENABLEMENT } from 'vs/platform/userDataSync/common/userDataSync'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; @@ -77,13 +77,29 @@ type FirstTimeSyncClassification = { action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; }; +const getActivityTitle = (label: string, userDataSyncService: IUserDataSyncService): string => { + if (userDataSyncService.status === SyncStatus.Syncing) { + return localize('sync is on with syncing', "{0} (syncing)", label); + } + if (userDataSyncService.lastSyncTime) { + return localize('sync is on with time', "{0} (synced {1})", label, fromNow(userDataSyncService.lastSyncTime, true)); + } + return label; +}; +const getIdentityTitle = (label: string, account?: AuthenticationSession): string => { + return account ? `${label} (${account.accountName})` : label; +}; const turnOnSyncCommand = { id: 'workbench.userData.actions.syncStart', title: localize('turn on sync with category', "Sync: Turn on Sync") }; const signInCommand = { id: 'workbench.userData.actions.signin', title: localize('sign in', "Sync: Sign in to sync") }; -const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Sync: Turn off Sync") }; +const stopSyncCommand = { id: 'workbench.userData.actions.stopSync', title(account?: AuthenticationSession) { return getIdentityTitle(localize('stop sync', "Sync: Turn off Sync"), account); } }; const resolveSettingsConflictsCommand = { id: 'workbench.userData.actions.resolveSettingsConflicts', title: localize('showConflicts', "Sync: Show Settings Conflicts") }; const resolveKeybindingsConflictsCommand = { id: 'workbench.userData.actions.resolveKeybindingsConflicts', title: localize('showKeybindingsConflicts', "Sync: Show Keybindings Conflicts") }; const configureSyncCommand = { id: 'workbench.userData.actions.configureSync', title: localize('configure sync', "Sync: Configure") }; -const showSyncActivityCommand = { id: 'workbench.userData.actions.showSyncActivity', title: localize('show sync log', "Sync: Show Activity") }; +const showSyncActivityCommand = { + id: 'workbench.userData.actions.showSyncActivity', title(userDataSyncService: IUserDataSyncService): string { + return getActivityTitle(localize('show sync log', "Sync: Show Activity"), userDataSyncService); + } +}; const showSyncSettingsCommand = { id: 'workbench.userData.actions.syncSettings', title: localize('sync settings', "Sync: Settings"), }; export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { @@ -93,13 +109,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private readonly syncStatusContext: IContextKey; private readonly authenticationState: IContextKey; private readonly conflictsSources: IContextKey; - private readonly conflictsDisposables = new Map(); + private readonly badgeDisposable = this._register(new MutableDisposable()); private readonly signInNotificationDisposable = this._register(new MutableDisposable()); private _activeAccount: AuthenticationSession | undefined; - private readonly syncStatusAction = this._register(new MutableDisposable()); - constructor( @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @@ -135,6 +149,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.onDidChangeEnablement(this.userDataSyncEnablementService.isEnabled()); this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(() => this.onDidChangeSyncStatus(this.userDataSyncService.status))); this._register(userDataSyncService.onDidChangeConflicts(() => this.onDidChangeConflicts(this.userDataSyncService.conflictsSources))); + this._register(userDataSyncService.onSyncErrors(errors => this.onSyncErrors(errors))); this._register(this.authTokenService.onTokenFailed(_ => this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId))); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(enabled => this.onDidChangeEnablement(enabled))); this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); @@ -220,6 +235,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } this.updateBadge(); + this.registerSyncStatusAction(); } private async onDidChangeSessions(providerId: string): Promise { @@ -253,6 +269,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this.updateBadge(); } + private readonly conflictsDisposables = new Map(); private onDidChangeConflicts(conflicts: SyncSource[]) { this.updateBadge(); if (conflicts.length) { @@ -377,20 +394,21 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo case UserDataSyncErrorCode.SessionExpired: this.notificationService.notify({ severity: Severity.Info, - message: localize('turned off', "Turned off sync because it was turned off from other device."), + message: localize('turned off', "Sync was turned off from another device."), actions: { - primary: [new Action('turn on sync', localize('Turn on sync', "Turn on Sync"), undefined, true, () => this.turnOn())] + primary: [new Action('turn on sync', localize('Turn sync back on', "Turn Sync Back On"), undefined, true, () => this.turnOn())] } }); return; case UserDataSyncErrorCode.TooLarge: if (error.source === SyncSource.Keybindings || error.source === SyncSource.Settings) { + this.disableSync(error.source); const sourceArea = getSyncAreaLabel(error.source); this.notificationService.notify({ severity: Severity.Error, - message: localize('too large', "Disabled sync {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'), + message: localize('too large', "Disabled syncing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'), actions: { - primary: [new Action('open sync file', localize('open file', "Show {0} file", sourceArea), undefined, true, + primary: [new Action('open sync file', localize('open file', "Open {0} file", sourceArea), undefined, true, () => error.source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] } }); @@ -406,6 +424,47 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private readonly invalidContentErrorDisposables = new Map(); + private onSyncErrors(errors: [SyncSource, UserDataSyncError][]): void { + if (errors.length) { + for (const [source, error] of errors) { + switch (error.code) { + case UserDataSyncErrorCode.LocalInvalidContent: + this.handleInvalidContentError(source); + break; + default: + const disposable = this.invalidContentErrorDisposables.get(source); + if (disposable) { + disposable.dispose(); + this.invalidContentErrorDisposables.delete(source); + } + } + } + } else { + this.invalidContentErrorDisposables.forEach(disposable => disposable.dispose()); + this.invalidContentErrorDisposables.clear(); + } + } + + private handleInvalidContentError(source: SyncSource): void { + if (!this.invalidContentErrorDisposables.has(source)) { + const errorArea = getSyncAreaLabel(source); + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('errorInvalidConfiguration', "Unable to sync {0} because there are some errors/warnings in the file. Please open the file to correct errors/warnings in it.", errorArea), + actions: { + primary: [new Action('open sync file', localize('open file', "Open {0} file", errorArea), undefined, true, + () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + this.invalidContentErrorDisposables.set(source, toDisposable(() => { + // close the error warning notification + handle.close(); + this.invalidContentErrorDisposables.delete(source); + })); + } + } + private async updateBadge(): Promise { this.badgeDisposable.clear(); @@ -463,7 +522,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } await this.handleFirstTimeSync(); this.userDataSyncEnablementService.setEnablement(true); - this.notificationService.info(localize('sync turned on', "Sync is turned on and from now on sycing will be done automatically.")); + this.notificationService.info(localize('sync turned on', "Sync will happen automatically from now on.")); } private getConfigureSyncQuickPickItems(): ConfigureSyncQuickPickItem[] { @@ -640,69 +699,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private registerActions(): void { this.registerTurnOnSyncAction(); - this.registerTurnOffSyncAction(); - - this.registerSyncStatusAction(); this.registerSignInAction(); this.registerShowSettingsConflictsAction(); this.registerShowKeybindingsConflictsAction(); + this.registerSyncStatusAction(); + this.registerTurnOffSyncAction(); this.registerConfigureSyncAction(); this.registerShowActivityAction(); this.registerShowSettingsAction(); } - private registerSyncStatusAction(): void { - const that = this; - this.syncStatusAction.value = registerAction2(class SyncStatusAction extends Action2 { - constructor() { - super({ - id: 'workbench.userData.actions.syncStatus', - get title() { - if (that.userDataSyncService.status === SyncStatus.Syncing) { - return localize('sync is on with syncing', "Sync is on (syncing)"); - } - if (that.userDataSyncService.lastSyncTime) { - return localize('sync is on with time', "Sync is on (synced {0})", fromNow(that.userDataSyncService.lastSyncTime, true)); - } - return localize('sync is on', "Sync is on"); - }, - menu: { - id: MenuId.GlobalActivity, - group: '5_sync', - when: ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)) - }, - }); - } - run(accessor: ServicesAccessor): any { - return new Promise((c, e) => { - const quickInputService = accessor.get(IQuickInputService); - const commandService = accessor.get(ICommandService); - const quickPick = quickInputService.createQuickPick(); - quickPick.items = [ - { id: configureSyncCommand.id, label: configureSyncCommand.title }, - { id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }, - { id: showSyncActivityCommand.id, label: showSyncActivityCommand.title }, - { type: 'separator' }, - { id: stopSyncCommand.id, label: stopSyncCommand.title } - ]; - const disposables = new DisposableStore(); - disposables.add(quickPick.onDidAccept(() => { - if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { - commandService.executeCommand(quickPick.selectedItems[0].id); - } - quickPick.hide(); - })); - disposables.add(quickPick.onDidHide(() => { - disposables.dispose(); - c(); - })); - quickPick.show(); - }); - } - }); - } - private registerTurnOnSyncAction(): void { const turnOnSyncWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT.toNegated(), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthStatus.Initializing)); CommandsRegistry.registerCommand(turnOnSyncCommand.id, async () => { @@ -721,6 +728,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('global activity turn on sync', "Turn on Sync...") }, when: turnOnSyncWhenContext, + order: 1 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: turnOnSyncCommand, @@ -736,31 +744,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); } - private registerTurnOffSyncAction(): void { - const that = this; - registerAction2(class StopSyncAction extends Action2 { - constructor() { - super({ - id: stopSyncCommand.id, - title: stopSyncCommand.title, - menu: { - id: MenuId.CommandPalette, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), - }, - }); - } - async run(accessor: ServicesAccessor): Promise { - try { - await that.turnOff(); - } catch (e) { - if (!isPromiseCanceledError(e)) { - that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); - } - } - } - }); - } - private registerSignInAction(): void { const that = this; registerAction2(class StopSyncAction extends Action2 { @@ -772,6 +755,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo group: '5_sync', id: MenuId.GlobalActivity, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedOut)), + order: 2 }, }); } @@ -795,6 +779,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), }, when: resolveSettingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveSettingsConflictsCommand.id, + title: localize('resolveConflicts_global', "Sync: Show Settings Conflicts (1)"), + }, + when: resolveSettingsConflictsWhenContext, + order: 2 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: resolveSettingsConflictsCommand, @@ -812,6 +806,16 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), }, when: resolveKeybindingsConflictsWhenContext, + order: 2 + }); + MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + group: '5_sync', + command: { + id: resolveKeybindingsConflictsCommand.id, + title: localize('resolveKeybindingsConflicts_global', "Sync: Show Keybindings Conflicts (1)"), + }, + when: resolveKeybindingsConflictsWhenContext, + order: 2 }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: resolveKeybindingsConflictsCommand, @@ -820,6 +824,100 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } + private readonly _syncStatusActionDisposable = this._register(new MutableDisposable()); + private registerSyncStatusAction(): void { + const that = this; + const when = ContextKeyExpr.and(CONTEXT_SYNC_ENABLEMENT, CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthStatus.SignedIn), CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)); + this._syncStatusActionDisposable.value = registerAction2(class SyncStatusAction extends Action2 { + constructor() { + super({ + id: 'workbench.userData.actions.syncStatus', + get title() { + return getIdentityTitle(localize('sync is on', "Sync is on"), that.activeAccount); + }, + menu: [ + { + id: MenuId.GlobalActivity, + group: '5_sync', + when, + order: 3 + }, + { + id: MenuId.MenubarPreferencesMenu, + group: '5_sync', + when, + order: 3, + } + ], + }); + } + run(accessor: ServicesAccessor): any { + return new Promise((c, e) => { + const quickInputService = accessor.get(IQuickInputService); + const commandService = accessor.get(ICommandService); + const quickPick = quickInputService.createQuickPick(); + const items: Array = []; + if (that.userDataSyncService.conflictsSources.length) { + for (const source of that.userDataSyncService.conflictsSources) { + switch (source) { + case SyncSource.Settings: + items.push({ id: resolveSettingsConflictsCommand.id, label: resolveSettingsConflictsCommand.title }); + break; + case SyncSource.Keybindings: + items.push({ id: resolveKeybindingsConflictsCommand.id, label: resolveKeybindingsConflictsCommand.title }); + break; + } + } + items.push({ type: 'separator' }); + } + items.push({ id: configureSyncCommand.id, label: configureSyncCommand.title }); + items.push({ id: showSyncSettingsCommand.id, label: showSyncSettingsCommand.title }); + items.push({ id: showSyncActivityCommand.id, label: showSyncActivityCommand.title(that.userDataSyncService) }); + items.push({ type: 'separator' }); + items.push({ id: stopSyncCommand.id, label: stopSyncCommand.title(that.activeAccount), }); + quickPick.items = items; + const disposables = new DisposableStore(); + disposables.add(quickPick.onDidAccept(() => { + if (quickPick.selectedItems[0] && quickPick.selectedItems[0].id) { + commandService.executeCommand(quickPick.selectedItems[0].id); + } + quickPick.hide(); + })); + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + quickPick.show(); + }); + } + }); + } + + private registerTurnOffSyncAction(): void { + const that = this; + registerAction2(class StopSyncAction extends Action2 { + constructor() { + super({ + id: stopSyncCommand.id, + title: stopSyncCommand.title(that.activeAccount), + menu: { + id: MenuId.CommandPalette, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), CONTEXT_SYNC_ENABLEMENT), + }, + }); + } + async run(): Promise { + try { + await that.turnOff(); + } catch (e) { + if (!isPromiseCanceledError(e)) { + that.notificationService.error(localize('turn off failed', "Error while turning off sync: {0}", toErrorMessage(e))); + } + } + } + }); + } + private registerConfigureSyncAction(): void { const that = this; registerAction2(class ShowSyncActivityAction extends Action2 { @@ -843,7 +941,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo constructor() { super({ id: showSyncActivityCommand.id, - title: showSyncActivityCommand.title, + get title() { return showSyncActivityCommand.title(that.userDataSyncService); }, menu: { id: MenuId.CommandPalette, when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized)), diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 2c9163e741..4ffbcf5ce9 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -353,6 +353,10 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten 'disable-color-correct-rendering': { type: 'boolean', description: nls.localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.') + }, + 'force-color-profile': { + type: 'string', + markdownDescription: nls.localize('argv.forceColorProfile', 'Allows to override the color profile to use. If you experience colors appear badly, try to set this to `srgb` and restart.') } } }; diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index f85abde87e..465d986550 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -12,7 +12,8 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { BackupFilesModel } from 'vs/workbench/services/backup/common/backupFileService'; -import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; @@ -205,7 +206,7 @@ suite('BackupFileService', () => { }); test('text file (ITextSnapshot)', async () => { - const model = TextModel.createFromString('test'); + const model = createTextModel('test'); await service.backup(fooFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); @@ -217,7 +218,7 @@ suite('BackupFileService', () => { }); test('untitled file (ITextSnapshot)', async () => { - const model = TextModel.createFromString('test'); + const model = createTextModel('test'); await service.backup(untitledFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); @@ -229,7 +230,7 @@ suite('BackupFileService', () => { test('text file (large file, ITextSnapshot)', async () => { const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = TextModel.createFromString(largeString); + const model = createTextModel(largeString); await service.backup(fooFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); @@ -242,7 +243,7 @@ suite('BackupFileService', () => { test('untitled file (large file, ITextSnapshot)', async () => { const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = TextModel.createFromString(largeString); + const model = createTextModel(largeString); await service.backup(untitledFile, model.createSnapshot()); assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); diff --git a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index 3bed0f5baa..f063bda5d4 100644 --- a/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -10,6 +10,7 @@ import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditOptions, IBulkEditResult, IBulkEditService, IBulkEditPreviewHandler } from 'vs/editor/browser/services/bulkEditService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { WorkspaceFileEdit, WorkspaceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -18,7 +19,7 @@ import { localize } from 'vs/nls'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgress, IProgressStep, emptyProgress } from 'vs/platform/progress/common/progress'; +import { IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -26,20 +27,21 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; - +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { EditStackElement, MultiModelEditStackElement } from 'vs/editor/common/model/editStack'; type ValidationResult = { canApply: true } | { canApply: false, reason: URI }; class ModelEditTask implements IDisposable { - private readonly _model: ITextModel; + public readonly model: ITextModel; protected _edits: IIdentifiedSingleEditOperation[]; private _expectedModelVersionId: number | undefined; protected _newEol: EndOfLineSequence | undefined; constructor(private readonly _modelReference: IReference) { - this._model = this._modelReference.object.textEditorModel; + this.model = this._modelReference.object.textEditorModel; this._edits = []; } @@ -67,7 +69,7 @@ class ModelEditTask implements IDisposable { // create edit operation let range: Range; if (!edit.range) { - range = this._model.getFullModelRange(); + range = this.model.getFullModelRange(); } else { range = Range.lift(edit.range); } @@ -75,23 +77,23 @@ class ModelEditTask implements IDisposable { } validate(): ValidationResult { - if (typeof this._expectedModelVersionId === 'undefined' || this._model.getVersionId() === this._expectedModelVersionId) { + if (typeof this._expectedModelVersionId === 'undefined' || this.model.getVersionId() === this._expectedModelVersionId) { return { canApply: true }; } - return { canApply: false, reason: this._model.uri }; + return { canApply: false, reason: this.model.uri }; + } + + getBeforeCursorState(): Selection[] | null { + return null; } apply(): void { if (this._edits.length > 0) { this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - this._model.pushStackElement(); - this._model.pushEditOperations([], this._edits, () => []); - this._model.pushStackElement(); + this.model.pushEditOperations(null, this._edits, () => null); } if (this._newEol !== undefined) { - this._model.pushStackElement(); - this._model.pushEOL(this._newEol); - this._model.pushStackElement(); + this.model.pushEOL(this._newEol); } } } @@ -105,18 +107,18 @@ class EditorEditTask extends ModelEditTask { this._editor = editor; } + getBeforeCursorState(): Selection[] | null { + return this._editor.getSelections(); + } + apply(): void { if (this._edits.length > 0) { this._edits = mergeSort(this._edits, (a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - this._editor.pushUndoStop(); this._editor.executeEdits('', this._edits); - this._editor.pushUndoStop(); } if (this._newEol !== undefined) { if (this._editor.hasModel()) { - this._editor.pushUndoStop(); this._editor.getModel().pushEOL(this._newEol); - this._editor.pushUndoStop(); } } } @@ -133,6 +135,7 @@ class BulkEditModel implements IDisposable { edits: WorkspaceTextEdit[], @IEditorWorkerService private readonly _editorWorker: IEditorWorkerService, @ITextModelService private readonly _textModelResolverService: ITextModelService, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService ) { edits.forEach(this._addEdit, this); } @@ -215,10 +218,31 @@ class BulkEditModel implements IDisposable { } apply(): void { - for (const task of this._tasks!) { + const tasks = this._tasks!; + + if (tasks.length === 1) { + // This edit touches a single model => keep things simple + for (const task of tasks) { + task.model.pushStackElement(); + task.apply(); + task.model.pushStackElement(); + this._progress.report(undefined); + } + return; + } + + const multiModelEditStackElement = new MultiModelEditStackElement( + localize('workspaceEdit', "Workspace Edit"), + tasks.map(t => new EditStackElement(t.model, t.getBeforeCursorState())) + ); + this._undoRedoService.pushElement(multiModelEditStackElement); + + for (const task of tasks) { task.apply(); this._progress.report(undefined); } + + multiModelEditStackElement.close(); } } @@ -242,7 +266,7 @@ class BulkEdit { @IConfigurationService private readonly _configurationService: IConfigurationService ) { this._editor = editor; - this._progress = progress || emptyProgress; + this._progress = progress || Progress.None; this._edits = edits; } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 8aff05cb49..402b972dcf 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -14,7 +14,7 @@ import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, File import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename, isEqualOrParent, isEqual, joinPath } from 'vs/base/common/resources'; +import { basename, isEqualOrParent, joinPath } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IResourceEditor, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ISaveEditorsOptions, ISaveAllEditorsOptions, IRevertAllEditorsOptions, IBaseSaveRevertAllEditorOptions } from 'vs/workbench/services/editor/common/editorService'; @@ -33,6 +33,7 @@ import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/u import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { timeout } from 'vs/base/common/async'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { indexOfPath } from 'vs/base/common/extpath'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput | UntitledTextEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -206,7 +207,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#endregion - //#region File Changes: Move editors when detecting file move operations + //#region File Changes: Move & Deletes to move or close opend editors private onDidRunFileOperation(e: FileOperationEvent): void { @@ -221,24 +222,30 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - private handleMovedFile(oldResource: URI, newResource: URI): void { + private onDidFilesChange(e: FileChangesEvent): void { + if (e.gotDeleted()) { + this.handleDeletedFile(e, true); + } + } + + private handleMovedFile(source: URI, target: URI): void { for (const group of this.editorGroupService.groups) { let replacements: (IResourceEditorReplacement | IEditorReplacement)[] = []; for (const editor of group.editors) { const resource = editor.resource; - if (!resource || !isEqualOrParent(resource, oldResource)) { + if (!resource || !isEqualOrParent(resource, source)) { continue; // not matching our resource } // Determine new resulting target resource let targetResource: URI; - if (oldResource.toString() === resource.toString()) { - targetResource = newResource; // file got moved + if (source.toString() === resource.toString()) { + targetResource = target; // file got moved } else { const ignoreCase = !this.fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive); - const index = this.getIndexOfPath(resource.path, oldResource.path, ignoreCase); - targetResource = joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved + const index = indexOfPath(resource.path, source.path, ignoreCase); + targetResource = joinPath(target, resource.path.substr(index + source.path.length + 1)); // parent folder got moved } // Delegate move() to editor instance @@ -247,12 +254,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return; // not target - ignore } - const extraOptions = { + const optionOverrides = { preserveFocus: true, pinned: group.isPinned(editor), index: group.getIndexOfEditor(editor), - inactive: !group.isActive(editor), - viewState: this.getViewStateFor(oldResource, group) + inactive: !group.isActive(editor) }; // Construct a replacement with our extra options mixed in @@ -262,7 +268,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { replacement: moveResult.editor, options: { ...moveResult.options, - ...extraOptions + ...optionOverrides } }); } else { @@ -271,8 +277,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { replacement: { ...moveResult.editor, options: { - ...(moveResult.editor as IResourceEditor /* TS fail */).options, - ...extraOptions + ...moveResult.editor.options, + ...optionOverrides } } }); @@ -286,40 +292,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - private getIndexOfPath(path: string, candidate: string, ignoreCase: boolean): number { - if (candidate.length > path.length) { - return -1; - } - - if (path === candidate) { - return 0; - } - - if (ignoreCase) { - path = path.toLowerCase(); - candidate = candidate.toLowerCase(); - } - - return path.indexOf(candidate); - } - - private getViewStateFor(resource: URI, group: IEditorGroup): IEditorViewState | undefined { - for (const editor of this.visibleControls) { - if (isEqual(editor.input.resource, resource) && editor.group === group) { - const control = editor.getControl(); - if (isCodeEditor(control)) { - return withNullAsUndefined(control.saveViewState()); - } - } - } - - return undefined; - } - - //#endregion - - //#region File Changes: Close editors of deleted files unless configured otherwise - private closeOnFileDelete: boolean = false; private fileInputFactory = Registry.as(EditorExtensions.EditorInputFactories).getFileInputFactory(); private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { @@ -330,12 +302,6 @@ export class EditorService extends Disposable implements EditorServiceImpl { } } - private onDidFilesChange(e: FileChangesEvent): void { - if (e.gotDeleted()) { - this.handleDeletedFile(e, true); - } - } - private handleDeletedFile(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void { for (const editor of this.getAllNonDirtyEditors({ includeUntitled: false, supportSideBySide: true })) { (async () => { diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 1a014a00a6..65aa59d685 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -5,89 +5,23 @@ import * as assert from 'assert'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IFileEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorOptions, CloseDirection, IEditorPartOptions, EditorsOrder } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; const TEST_EDITOR_ID = 'MyFileEditorForEditorGroupService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorGroupService'; -class TestEditorControl extends BaseEditor { - - constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return Promise.resolve(null); } - matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - setForceOpenAsBinary(): void { } - isResolved(): boolean { return false; } -} - suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite let disposables: IDisposable[] = []; setup(() => { - interface ISerializedTestEditorInput { - resource: string; - } - - class TestEditorInputFactory implements IEditorInputFactory { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - const testEditorInput = editorInput; - const testInput: ISerializedTestEditorInput = { - resource: testEditorInput.resource.toString() - }; - - return JSON.stringify(testInput); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - const testInput: ISerializedTestEditorInput = JSON.parse(serializedEditorInput); - - return new TestEditorInput(URI.parse(testInput.resource)); - } - } - - disposables.push((Registry.as(EditorExtensions.EditorInputFactories)).registerEditorInputFactory(TEST_EDITOR_INPUT_ID, TestEditorInputFactory)); - disposables.push((Registry.as(Extensions.Editors)).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test File Editor'), [new SyncDescriptor(TestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_EDITOR_INPUT_ID)); }); teardown(() => { @@ -344,17 +278,17 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite rootGroupDisposed = true; }); - const input = new TestEditorInput(URI.file('foo/bar')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input, EditorOptions.create({ pinned: true })); const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true }); const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN); assert.equal(groupAddedCounter, 2); assert.equal(downGroup.count, 1); - assert.ok(downGroup.activeEditor instanceof TestEditorInput); + assert.ok(downGroup.activeEditor instanceof TestFileEditorInput); part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.COPY_EDITORS }); assert.equal(rightGroup.count, 1); - assert.ok(rightGroup.activeEditor instanceof TestEditorInput); + assert.ok(rightGroup.activeEditor instanceof TestFileEditorInput); part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.MOVE_EDITORS }); assert.equal(rootGroup.count, 0); part.mergeGroup(rootGroup, downGroup); @@ -437,8 +371,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite editorWillCloseCounter++; }); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input, EditorOptions.create({ pinned: true })); await group.openEditor(inputInactive, EditorOptions.create({ inactive: true })); @@ -465,7 +399,7 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite assert.ok(!group.previewEditor); assert.equal(group.activeEditor, input); - assert.ok(group.activeControl instanceof TestEditorControl); + assert.equal(group.activeControl?.getId(), TEST_EDITOR_ID); assert.equal(group.count, 2); const mru = group.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE); @@ -498,8 +432,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -516,9 +450,9 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -537,9 +471,9 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -557,9 +491,9 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -579,9 +513,9 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input1 = new TestEditorInput(URI.file('foo/bar1')); - const input2 = new TestEditorInput(URI.file('foo/bar2')); - const input3 = new TestEditorInput(URI.file('foo/bar3')); + const input1 = new TestFileEditorInput(URI.file('foo/bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.file('foo/bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.file('foo/bar3'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); assert.equal(group.count, 3); @@ -601,8 +535,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -619,8 +553,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); let editorMoveCounter = 0; const editorGroupChangeListener = group.onDidGroupChange(e => { @@ -649,8 +583,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -671,8 +605,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const rightGroup = part.addGroup(group, GroupDirection.RIGHT); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); assert.equal(group.count, 2); @@ -692,8 +626,8 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite const group = part.activeGroup; assert.equal(group.isEmpty, true); - const input = new TestEditorInput(URI.file('foo/bar')); - const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); + const input = new TestFileEditorInput(URI.file('foo/bar'), TEST_EDITOR_INPUT_ID); + const inputInactive = new TestFileEditorInput(URI.file('foo/bar/inactive'), TEST_EDITOR_INPUT_ID); await group.openEditor(input); assert.equal(group.count, 1); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 951b1cc6ce..7b906172b4 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -4,21 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; +import { EditorActivation } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IEditorInput, IMoveResult } from 'vs/workbench/common/editor'; -import { workbenchInstantiationService, TestStorageService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { EditorInput, EditorsOrder } from 'vs/workbench/common/editor'; +import { workbenchInstantiationService, TestStorageService, TestServiceAccessor, registerTestEditor, TestFileEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorGroup, IEditorGroupsService, GroupDirection, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { Registry } from 'vs/platform/registry/common/platform'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { timeout } from 'vs/base/common/async'; @@ -28,85 +26,10 @@ import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; import { NullFileSystemProvider } from 'vs/platform/files/test/common/nullFileSystemProvider'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const TEST_EDITOR_ID = 'MyTestEditorForEditorService'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorService'; -class ServicesAccessor { - constructor( - @IFileService public fileService: TestFileService - ) { } -} - -class TestEditorControl extends BaseEditor { - - constructor(@ITelemetryService telemetryService: ITelemetryService) { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - gotDisposed = false; - gotSaved = false; - gotSavedAs = false; - gotReverted = false; - dirty = false; - private fails = false; - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } - matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - setForceOpenAsBinary(): void { } - setFailToOpen(): void { - this.fails = true; - } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.gotSaved = true; - return this; - } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.gotSavedAs = true; - return this; - } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - this.gotReverted = true; - this.gotSaved = false; - this.gotSavedAs = false; - return true; - } - isDirty(): boolean { - return this.dirty; - } - isReadonly(): boolean { - return false; - } - isResolved(): boolean { return false; } - dispose(): void { - super.dispose(); - this.gotDisposed = true; - } - movedEditor: IMoveResult | undefined = undefined; - move(): IMoveResult | undefined { return this.movedEditor; } -} - class FileServiceProvider extends Disposable { constructor(scheme: string, @IFileService fileService: IFileService) { super(); @@ -120,7 +43,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite let disposables: IDisposable[] = []; setup(() => { - disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Next Editor Service'), [new SyncDescriptor(TestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_EDITOR_INPUT_ID)); }); teardown(() => { @@ -128,7 +51,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite disposables = []; }); - function createEditorService(): [EditorPart, EditorService, IInstantiationService, ServicesAccessor] { + function createEditorService(): [EditorPart, EditorService, TestServiceAccessor] { const instantiationService = workbenchInstantiationService(); const part = instantiationService.createInstance(EditorPart); @@ -140,14 +63,14 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite const editorService = instantiationService.createInstance(EditorService); instantiationService.stub(IEditorService, editorService); - return [part, editorService, instantiationService, instantiationService.createInstance(ServicesAccessor)]; + return [part, editorService, instantiationService.createInstance(TestServiceAccessor)]; } test('basics', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); - let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); + let input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + let otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventCounter = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -169,7 +92,7 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite // Open input let editor = await service.openEditor(input, { pinned: true }); - assert.ok(editor instanceof TestEditorControl); + assert.equal(editor?.getId(), TEST_EDITOR_ID); assert.equal(editor, service.activeControl); assert.equal(1, service.count); assert.equal(input, service.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE)[0].editor); @@ -200,8 +123,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assert.equal(0, service.count); // Open again 2 inputs (recreate because disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-basics')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-basics')); + input = new TestFileEditorInput(URI.parse('my://resource-basics'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-basics'), TEST_EDITOR_INPUT_ID); await service.openEditor(input, { pinned: true }); editor = await service.openEditor(otherInput, { pinned: true }); @@ -226,11 +149,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('openEditors() / replaceEditors()', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-openEditors')); - const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openEditors')); - const replaceInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-openEditors')); + const input = new TestFileEditorInput(URI.parse('my://resource-openEditors'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-openEditors'), TEST_EDITOR_INPUT_ID); + const replaceInput = new TestFileEditorInput(URI.parse('my://resource3-openEditors'), TEST_EDITOR_INPUT_ID); await part.whenRestored; @@ -401,9 +324,9 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('close editor does not dispose when editor opened in other group', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-close1')); + const input = new TestFileEditorInput(URI.parse('my://resource-close1'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -430,10 +353,10 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('open to the side', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -456,10 +379,10 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('editor group activation', async () => { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -491,10 +414,10 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('active editor change / visible editor change events', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - let otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + let otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEventFired = false; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -546,8 +469,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 2.) open, open same (forced open) (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -559,8 +482,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite await closeEditorAndWaitForNextToOpen(group, input); // 3.) open, open inactive, close (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -574,8 +497,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 4.) open, open inactive, close inactive (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -593,8 +516,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 5.) add group, remove group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -616,8 +539,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 6.) open editor in inactive group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -639,8 +562,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 7.) activate group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -666,8 +589,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 8.) move editor (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -685,8 +608,8 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite assertVisibleEditorsChangedEvent(true); // 9.) close editor in inactive group (recreate inputs that got disposed) - input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-active')); + input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + otherInput = new TestFileEditorInput(URI.parse('my://resource2-active'), TEST_EDITOR_INPUT_ID); editor = await service.openEditor(input, { pinned: true }); assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -711,9 +634,9 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('two active editor change events when opening editor to the side', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - let input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); + let input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); let activeEditorChangeEvents = 0; const activeEditorChangeListener = service.onDidActiveEditorChange(() => { @@ -763,11 +686,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('openEditor returns NULL when opening fails or is inactive', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource-active')); - const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-inactive')); - const failingInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-failing')); + const input = new TestFileEditorInput(URI.parse('my://resource-active'), TEST_EDITOR_INPUT_ID); + const otherInput = new TestFileEditorInput(URI.parse('my://resource2-inactive'), TEST_EDITOR_INPUT_ID); + const failingInput = new TestFileEditorInput(URI.parse('my://resource3-failing'), TEST_EDITOR_INPUT_ID); failingInput.setFailToOpen(); await part.whenRestored; @@ -785,11 +708,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); test('save, saveAll, revertAll', async function () { - const [part, service, testInstantiationService] = createEditorService(); + const [part, service] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); input1.dirty = true; - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); input2.dirty = true; const rootGroup = part.activeGroup; @@ -828,11 +751,11 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite }); async function testFileDeleteEditorClose(dirty: boolean): Promise { - const [part, service, testInstantiationService, accessor] = createEditorService(); + const [part, service, accessor] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); input1.dirty = dirty; - const input2 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input2 = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); input2.dirty = dirty; const rootGroup = part.activeGroup; @@ -860,10 +783,10 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite } test('file move asks input to move', async function () { - const [part, service, testInstantiationService, accessor] = createEditorService(); + const [part, service, accessor] = createEditorService(); - const input1 = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource1-openside')); - const movedInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openside')); + const input1 = new TestFileEditorInput(URI.parse('my://resource1-openside'), TEST_EDITOR_INPUT_ID); + const movedInput = new TestFileEditorInput(URI.parse('my://resource2-openside'), TEST_EDITOR_INPUT_ID); input1.movedEditor = { editor: movedInput }; const rootGroup = part.activeGroup; @@ -896,4 +819,26 @@ suite.skip('EditorService', () => { // {{SQL CARBON EDIT}} skip suite Event.once(editorService.onDidActiveEditorChange)(c); }); } + + test('file watcher gets installed for out of workspace files', async function () { + const [part, service, accessor] = createEditorService(); + + const input1 = new TestFileEditorInput(URI.parse('file://resource1-openside'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('file://resource2-openside'), TEST_EDITOR_INPUT_ID); + + await part.whenRestored; + + await service.openEditor(input1, { pinned: true }); + assert.equal(accessor.fileService.watches.length, 1); + assert.equal(accessor.fileService.watches[0].toString(), input1.resource.toString()); + + const editor = await service.openEditor(input2, { pinned: true }); + assert.equal(accessor.fileService.watches.length, 1); + assert.equal(accessor.fileService.watches[0].toString(), input2.resource.toString()); + + await editor?.group?.closeAllEditors(); + assert.equal(accessor.fileService.watches.length, 0); + + part.dispose(); + }); }); diff --git a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts index 4532fe8681..742a962880 100644 --- a/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorsObserver.test.ts @@ -4,21 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; +import { EditorOptions, IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestStorageService, TestFileEditorInput, registerTestEditor } from 'vs/workbench/test/browser/workbenchTestServices'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { EditorActivation } from 'vs/platform/editor/common/editor'; import { WillSaveStateReason } from 'vs/platform/storage/common/storage'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; import { timeout } from 'vs/base/common/async'; @@ -27,78 +21,12 @@ const TEST_EDITOR_ID = 'MyTestEditorForEditorsObserver'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForEditorsObserver'; const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForEditorsObserver'; -class TestEditorControl extends BaseEditor { - - constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - - private dirty = false; - - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return Promise.resolve(null); } - matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - setForceOpenAsBinary(): void { } - isDirty(): boolean { return this.dirty; } - setDirty(): void { this.dirty = true; } - isResolved(): boolean { return false; } -} - -class EditorsObserverTestEditorInput extends TestEditorInput { - getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } -} - -interface ISerializedTestInput { - resource: string; -} - -class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - let testEditorInput = editorInput; - let testInput: ISerializedTestInput = { - resource: testEditorInput.resource.toString() - }; - - return JSON.stringify(testInput); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); - - return new EditorsObserverTestEditorInput(URI.parse(testInput.resource)); - } -} - suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failing tests due to tabcolormode let disposables: IDisposable[] = []; setup(() => { - disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, EditorsObserverTestEditorInputFactory)); - disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For Editors Observer'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(EditorsObserverTestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)], TEST_SERIALIZABLE_EDITOR_INPUT_ID)); }); teardown(() => { @@ -139,7 +67,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU.length, 0); assert.equal(observerChangeListenerCalled, false); - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); @@ -149,8 +77,8 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(currentEditorsMRU[0].editor, input1); assert.equal(observerChangeListenerCalled, true); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input3, EditorOptions.create({ pinned: true })); @@ -204,7 +132,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); await sideGroup.openEditor(input1, EditorOptions.create({ pinned: true, activation: EditorActivation.ACTIVATE })); @@ -227,7 +155,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin // Opening an editor inactive should not change // the most recent editor, but rather put it behind - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await rootGroup.openEditor(input2, EditorOptions.create({ inactive: true })); @@ -258,9 +186,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin test('copy group', async () => { const [part, observer] = await createEditorObserver(); - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); const rootGroup = part.activeGroup; @@ -303,9 +231,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin const rootGroup = part.activeGroup; - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -346,9 +274,9 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin const rootGroup = part.activeGroup; - const input1 = new EditorsObserverTestEditorInput(URI.parse('foo://bar1')); - const input2 = new EditorsObserverTestEditorInput(URI.parse('foo://bar2')); - const input3 = new EditorsObserverTestEditorInput(URI.parse('foo://bar3')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_SERIALIZABLE_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -391,7 +319,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin const rootGroup = part.activeGroup; - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); @@ -425,10 +353,10 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin const rootGroup = part.activeGroup; const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); @@ -452,7 +380,7 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin assert.equal(rootGroup.isOpened(input3), false); assert.equal(rootGroup.isOpened(input4), true); - const input5 = new TestEditorInput(URI.parse('foo://bar5')); + const input5 = new TestFileEditorInput(URI.parse('foo://bar5'), TEST_EDITOR_INPUT_ID); await sideGroup.openEditor(input5, EditorOptions.create({ pinned: true })); assert.equal(rootGroup.count, 1); @@ -477,10 +405,10 @@ suite.skip('EditorsObserver', function () { //{{SQL CARBON EDIT}} disable failin const rootGroup = part.activeGroup; const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID); await rootGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await rootGroup.openEditor(input2, EditorOptions.create({ pinned: true })); diff --git a/src/vs/workbench/services/history/test/browser/history.test.ts b/src/vs/workbench/services/history/test/browser/history.test.ts index b57d785601..4fb4ae1bd4 100644 --- a/src/vs/workbench/services/history/test/browser/history.test.ts +++ b/src/vs/workbench/services/history/test/browser/history.test.ts @@ -4,21 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorOptions, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IEditorInputFactory, IFileEditorInput } from 'vs/workbench/common/editor'; +import { EditorOptions } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { workbenchInstantiationService, TestFileEditorInput, registerTestEditor } from 'vs/workbench/test/browser/workbenchTestServices'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IEditorGroupsService, GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { HistoryService } from 'vs/workbench/services/history/browser/history'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -27,68 +19,6 @@ import { timeout } from 'vs/base/common/async'; const TEST_EDITOR_ID = 'MyTestEditorForEditorHistory'; const TEST_EDITOR_INPUT_ID = 'testEditorInputForHistoyService'; -const TEST_SERIALIZABLE_EDITOR_INPUT_ID = 'testSerializableEditorInputForHistoyService'; - -class TestEditorControl extends BaseEditor { - - constructor() { super(TEST_EDITOR_ID, NullTelemetryService, new TestThemeService(), new TestStorageService()); } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - super.setInput(input, options, token); - - await input.resolve(); - } - - getId(): string { return TEST_EDITOR_ID; } - layout(): void { } - createEditor(): any { } -} - -class TestEditorInput extends EditorInput implements IFileEditorInput { - - constructor(public resource: URI) { super(); } - - getTypeId() { return TEST_EDITOR_INPUT_ID; } - resolve(): Promise { return Promise.resolve(null); } - matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } - setEncoding(encoding: string) { } - getEncoding() { return undefined; } - setPreferredEncoding(encoding: string) { } - setMode(mode: string) { } - setPreferredMode(mode: string) { } - setForceOpenAsBinary(): void { } - isResolved(): boolean { return false; } -} - -class HistoryTestEditorInput extends TestEditorInput { - getTypeId() { return TEST_SERIALIZABLE_EDITOR_INPUT_ID; } -} - -interface ISerializedTestInput { - resource: string; -} - -class HistoryTestEditorInputFactory implements IEditorInputFactory { - - canSerialize(editorInput: EditorInput): boolean { - return true; - } - - serialize(editorInput: EditorInput): string { - let testEditorInput = editorInput; - let testInput: ISerializedTestInput = { - resource: testEditorInput.resource.toString() - }; - - return JSON.stringify(testInput); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { - let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); - - return new HistoryTestEditorInput(URI.parse(testInput.resource)); - } -} async function createServices(): Promise<[EditorPart, HistoryService, EditorService]> { const instantiationService = workbenchInstantiationService(); @@ -115,8 +45,7 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd let disposables: IDisposable[] = []; setup(() => { - disposables.push(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(TEST_SERIALIZABLE_EDITOR_INPUT_ID, HistoryTestEditorInputFactory)); - disposables.push(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, TEST_EDITOR_ID, 'My Test Editor For History Editor Service'), [new SyncDescriptor(TestEditorInput), new SyncDescriptor(HistoryTestEditorInput)])); + disposables.push(registerTestEditor(TEST_EDITOR_ID, [new SyncDescriptor(TestFileEditorInput)])); }); teardown(() => { @@ -127,11 +56,11 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd test('back / forward', async () => { const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); assert.equal(part.activeGroup.activeEditor, input1); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); assert.equal(part.activeGroup.activeEditor, input2); @@ -150,10 +79,10 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd let history = historyService.getHistory(); assert.equal(history.length, 0); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); history = historyService.getHistory(); @@ -172,7 +101,7 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd assert.ok(!historyService.getLastActiveFile('foo')); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); assert.equal(historyService.getLastActiveFile('foo')?.toString(), input1.resource.toString()); @@ -183,8 +112,8 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd test('open next/previous recently used editor (single group)', async () => { const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); assert.equal(part.activeGroup.activeEditor, input1); @@ -211,8 +140,8 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd const [part, historyService] = await createServices(); const rootGroup = part.activeGroup; - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); const sideGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); @@ -233,10 +162,10 @@ suite.skip('HistoryService', function () { // {{SQL CARBON EDIT}} TODO @anthonyd test('open next/previous recently is reset when other input opens', async () => { const [part, historyService] = await createServices(); - const input1 = new TestEditorInput(URI.parse('foo://bar1')); - const input2 = new TestEditorInput(URI.parse('foo://bar2')); - const input3 = new TestEditorInput(URI.parse('foo://bar3')); - const input4 = new TestEditorInput(URI.parse('foo://bar4')); + const input1 = new TestFileEditorInput(URI.parse('foo://bar1'), TEST_EDITOR_INPUT_ID); + const input2 = new TestFileEditorInput(URI.parse('foo://bar2'), TEST_EDITOR_INPUT_ID); + const input3 = new TestFileEditorInput(URI.parse('foo://bar3'), TEST_EDITOR_INPUT_ID); + const input4 = new TestFileEditorInput(URI.parse('foo://bar4'), TEST_EDITOR_INPUT_ID); await part.activeGroup.openEditor(input1, EditorOptions.create({ pinned: true })); await part.activeGroup.openEditor(input2, EditorOptions.create({ pinned: true })); diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index bdd6b4c8d9..69c5f32a93 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -198,6 +198,12 @@ export interface ISearchCompleteStats { export interface ISearchComplete extends ISearchCompleteStats { results: IFileMatch[]; + exit?: SearchCompletionExitCode +} + +export const enum SearchCompletionExitCode { + Normal, + NewSearchStarted } export interface ITextSearchStats { @@ -334,9 +340,7 @@ export interface ISearchConfigurationProperties { collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand'; searchOnType: boolean; searchOnTypeDebouncePeriod: number; - enableSearchEditorPreview: boolean; - searchEditorPreview: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide' }; - searchEditorPreviewForceAbsolutePaths: boolean; + searchEditor: { doubleClickBehaviour: 'selectWord' | 'goToLocation' | 'openLocationToSide' }; sortOrder: SearchSortOrder; } diff --git a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts index abcbfdcb43..f248ba3f4b 100644 --- a/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts +++ b/src/vs/workbench/services/textfile/common/textFileSaveParticipant.ts @@ -33,7 +33,7 @@ export class TextFileSaveParticipant extends Disposable { const cts = new CancellationTokenSource(token); return this.progressService.withProgress({ - title: localize('saveParticipants', "Running Save Participants for '{0}'", model.name), + title: localize('saveParticipants', "Saving '{0}'", model.name), location: ProgressLocation.Notification, cancellable: true, delay: model.isDirty() ? 3000 : 5000 diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 753e39f799..a689c35763 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -7,50 +7,31 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { createFileInput, TestFileService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TextFileEditorModelState, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { createFileInput, workbenchInstantiationService, TestServiceAccessor, TestReadonlyTextFileEditorModel } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { FileOperationResult, FileOperationError, IFileService } from 'vs/platform/files/common/files'; -import { IModelService } from 'vs/editor/common/services/modelService'; +import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { timeout } from 'vs/base/common/async'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { assertIsDefined } from 'vs/base/common/types'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -class ServiceAccessor { - constructor( - @ITextFileService public readonly textFileService: TestTextFileService, - @IModelService public readonly modelService: IModelService, - @IFileService public readonly fileService: TestFileService, - @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService - ) { - } -} - function getLastModifiedTime(model: TextFileEditorModel): number { const stat = model.getStat(); return stat ? stat.mtime : -1; } -class TestTextFileEditorModel extends TextFileEditorModel { - - isReadonly(): boolean { - return true; - } -} - suite('Files - TextFileEditorModel', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let content: string; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); content = accessor.fileService.getContent(); }); @@ -107,7 +88,7 @@ suite('Files - TextFileEditorModel', () => { assert.equal(accessor.workingCopyService.isDirty(model.resource), true); let savedEvent = false; - model.onDidSave(e => savedEvent = true); + model.onDidSave(() => savedEvent = true); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -139,7 +120,7 @@ suite('Files - TextFileEditorModel', () => { await model.load(); let savedEvent = false; - model.onDidSave(e => savedEvent = true); + model.onDidSave(() => savedEvent = true); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -165,7 +146,7 @@ suite('Files - TextFileEditorModel', () => { model.updateTextEditorModel(createTextBufferFactory('bar')); let saveErrorEvent = false; - model.onDidSaveError(e => saveErrorEvent = true); + model.onDidSaveError(() => saveErrorEvent = true); accessor.fileService.writeShouldThrowError = new Error('failed to write'); try { @@ -195,7 +176,7 @@ suite('Files - TextFileEditorModel', () => { model.updateTextEditorModel(createTextBufferFactory('bar')); let saveErrorEvent = false; - model.onDidSaveError(e => saveErrorEvent = true); + model.onDidSaveError(() => saveErrorEvent = true); accessor.fileService.writeShouldThrowError = new FileOperationError('save conflict', FileOperationResult.FILE_MODIFIED_SINCE); try { @@ -221,7 +202,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); let encodingEvent = false; - model.onDidChangeEncoding(e => encodingEvent = true); + model.onDidChangeEncoding(() => encodingEvent = true); model.setEncoding('utf8', EncodingMode.Encode); // no-op assert.equal(getLastModifiedTime(model), -1); @@ -276,8 +257,8 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); assert.ok(model.hasState(TextFileEditorModelState.SAVED)); - model.onDidSave(e => assert.fail()); - model.onDidChangeDirty(e => assert.fail()); + model.onDidSave(() => assert.fail()); + model.onDidChangeDirty(() => assert.fail()); await model.load(); assert.ok(model.isResolved()); @@ -303,7 +284,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidRevert(e => eventCounter++); + model.onDidRevert(() => eventCounter++); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -336,7 +317,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidRevert(e => eventCounter++); + model.onDidRevert(() => eventCounter++); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -402,7 +383,7 @@ suite('Files - TextFileEditorModel', () => { await model.revert({ soft: true }); assert.ok(!model.isDirty()); - model.onDidChangeDirty(e => eventCounter++); + model.onDidChangeDirty(() => eventCounter++); let workingCopyEvent = false; accessor.workingCopyService.onDidChangeDirty(e => { @@ -431,7 +412,7 @@ suite('Files - TextFileEditorModel', () => { } }); - const model = instantiationService.createInstance(TestTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + const model = instantiationService.createInstance(TestReadonlyTextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); model.updateTextEditorModel(createTextBufferFactory('foo')); @@ -507,7 +488,7 @@ suite('Files - TextFileEditorModel', () => { let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidSave(e => { + model.onDidSave(() => { assert.equal(snapshotToString(model.createSnapshot()!), eventCounter === 1 ? 'bar' : 'foobar'); assert.ok(!model.isDirty()); eventCounter++; @@ -544,7 +525,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async model => { + participate: async () => { eventCounter++; } }); @@ -563,7 +544,7 @@ suite('Files - TextFileEditorModel', () => { let eventCounter = 0; const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - model.onDidSave(e => { + model.onDidSave(() => { assert.ok(!model.isDirty()); eventCounter++; }); @@ -595,7 +576,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async model => { + participate: async () => { new Error('boom'); } }); diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts index 16131020eb..b48bf014d8 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModelManager.test.ts @@ -7,31 +7,22 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; -import { IModelService } from 'vs/editor/common/services/modelService'; +import { FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { toResource } from 'vs/base/test/common/utils'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; -class ServiceAccessor { - constructor( - @IFileService public fileService: TestFileService, - @IModelService public modelService: IModelService - ) { - } -} - suite('Files - TextFileEditorModelManager', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('add, remove, clear, get, getAll', function () { diff --git a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts index c5311065d3..877579abb3 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileService.test.ts @@ -4,44 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { TestLifecycleService, TestContextService, TestFileService, TestFilesConfigurationService, TestFileDialogService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; - -class ServiceAccessor { - constructor( - @ILifecycleService public lifecycleService: TestLifecycleService, - @ITextFileService public textFileService: TestTextFileService, - @IWorkingCopyFileService public workingCopyFileService: IWorkingCopyFileService, - @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, - @IWorkspaceContextService public contextService: TestContextService, - @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService, - @IFileDialogService public fileDialogService: TestFileDialogService - ) { - } -} suite('Files - TextFileService', () => { let instantiationService: IInstantiationService; let model: TextFileEditorModel; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { diff --git a/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts index 9a7c66e1eb..9efa1e70fa 100644 --- a/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/textFileService.io.test.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; @@ -20,57 +21,23 @@ import { generateUuid } from 'vs/base/common/uuid'; import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; -import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { isWindows } from 'vs/base/common/platform'; import { readFileSync, statSync } from 'fs'; import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; - -class ServiceAccessor { - constructor( - @ITextFileService public textFileService: TestTextFileService - ) { - } -} - -class TestNativeTextFileService extends NativeTextFileService { - - private _testEncoding: TestEncodingOracle | undefined; - get encoding(): TestEncodingOracle { - if (!this._testEncoding) { - this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); - } - - return this._testEncoding; - } -} - -class TestEncodingOracle extends EncodingOracle { - - protected get encodingOverrides(): IEncodingOverride[] { - return [ - { extension: 'utf16le', encoding: UTF16le }, - { extension: 'utf16be', encoding: UTF16be }, - { extension: 'utf8bom', encoding: UTF8_with_bom } - ]; - } - - protected set encodingOverrides(overrides: IEncodingOverride[]) { } -} +import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices'; suite('Files - TextFileService i/o', () => { const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice'); - let accessor: ServiceAccessor; const disposables = new DisposableStore(); + let service: ITextFileService; let testDir: string; setup(async () => { const instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); const logService = new NullLogService(); const fileService = new FileService(logService); @@ -82,7 +49,7 @@ suite('Files - TextFileService i/o', () => { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - service = instantiationService.createChild(collection).createInstance(TestNativeTextFileService); + service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides); const id = generateUuid(); testDir = join(parentDir, id); @@ -92,7 +59,7 @@ suite('Files - TextFileService i/o', () => { }); teardown(async () => { - (accessor.textFileService.files).dispose(); + (service.files).dispose(); disposables.clear(); @@ -185,7 +152,7 @@ suite('Files - TextFileService i/o', () => { test('create - UTF 8 BOM - empty content - snapshot', async () => { const resource = URI.file(join(testDir, 'small_new.utf8bom')); - await service.create(resource, TextModel.createFromString('').createSnapshot()); + await service.create(resource, createTextModel('').createSnapshot()); assert.equal(await exists(resource.fsPath), true); @@ -196,7 +163,7 @@ suite('Files - TextFileService i/o', () => { test('create - UTF 8 BOM - content provided - snapshot', async () => { const resource = URI.file(join(testDir, 'small_new.utf8bom')); - await service.create(resource, TextModel.createFromString('Hello World').createSnapshot()); + await service.create(resource, createTextModel('Hello World').createSnapshot()); assert.equal(await exists(resource.fsPath), true); @@ -209,7 +176,7 @@ suite('Files - TextFileService i/o', () => { }); test('write - use encoding (UTF 16 BE) - small content as snapshot', async () => { - await testEncoding(URI.file(join(testDir, 'small.txt')), UTF16be, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); + await testEncoding(URI.file(join(testDir, 'small.txt')), UTF16be, createTextModel('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); }); test('write - use encoding (UTF 16 BE) - large content as string', async () => { @@ -217,7 +184,7 @@ suite('Files - TextFileService i/o', () => { }); test('write - use encoding (UTF 16 BE) - large content as snapshot', async () => { - await testEncoding(URI.file(join(testDir, 'lorem.txt')), UTF16be, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); + await testEncoding(URI.file(join(testDir, 'lorem.txt')), UTF16be, createTextModel('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); }); async function testEncoding(resource: URI, encoding: string, content: string | ITextSnapshot, expectedContent: string) { @@ -265,7 +232,7 @@ suite('Files - TextFileService i/o', () => { resolved = await service.readStream(resource, { encoding }); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content); - await service.write(resource, TextModel.createFromString(content).createSnapshot(), { encoding }); + await service.write(resource, createTextModel(content).createSnapshot(), { encoding }); resolved = await service.readStream(resource, { encoding }); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content); @@ -287,7 +254,7 @@ suite('Files - TextFileService i/o', () => { const content = (await readFile(resource.fsPath)).toString(); - await service.write(resource, TextModel.createFromString(content).createSnapshot()); + await service.write(resource, createTextModel(content).createSnapshot()); const resolved = await service.readStream(resource); assert.equal(resolved.value.getFirstLineText(999999), content); @@ -308,7 +275,7 @@ suite('Files - TextFileService i/o', () => { const resolved = await service.readStream(resource); assert.equal(resolved.encoding, UTF16le); - await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); + await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, createTextModel('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); }); test('write - UTF8 variations - content as string', async () => { @@ -345,7 +312,7 @@ suite('Files - TextFileService i/o', () => { let detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.equal(detectedEncoding, null); - const model = TextModel.createFromString((await readFile(resource.fsPath)).toString() + 'updates'); + const model = createTextModel((await readFile(resource.fsPath)).toString() + 'updates'); await service.write(resource, model.createSnapshot(), { encoding: UTF8_with_bom }); detectedEncoding = await detectEncodingByBOM(resource.fsPath); @@ -390,7 +357,7 @@ suite('Files - TextFileService i/o', () => { test('write - ensure BOM in empty file - content as snapshot', async () => { const resource = URI.file(join(testDir, 'small.txt')); - await service.write(resource, TextModel.createFromString('').createSnapshot(), { encoding: UTF8_with_bom }); + await service.write(resource, createTextModel('').createSnapshot(), { encoding: UTF8_with_bom }); let detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.equal(detectedEncoding, UTF8_with_bom); @@ -413,7 +380,7 @@ suite('Files - TextFileService i/o', () => { assert.equal(result.name, basename(resource.fsPath)); assert.equal(result.size, statSync(resource.fsPath).size); - assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), snapshotToString(TextModel.createFromString(readFileSync(resource.fsPath).toString()).createSnapshot(false))); + assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), snapshotToString(createTextModel(readFileSync(resource.fsPath).toString()).createSnapshot(false))); } test('read - small text', async () => { diff --git a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts index e82d51fcb9..405b96f799 100644 --- a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts @@ -9,39 +9,24 @@ import { URI } from 'vs/base/common/uri'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Event } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -class ServiceAccessor { - constructor( - @ITextModelService public textModelResolverService: ITextModelService, - @IModelService public modelService: IModelService, - @IModeService public modeService: IModeService, - @ITextFileService public textFileService: ITextFileService, - @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService - ) { - } -} - suite('Workbench - TextModelResolverService', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; let model: TextFileEditorModel; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 9af8fcb058..5d4e1cf6c1 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -92,7 +92,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt // Take name from first line if present and only if // we have no associated file path. In that case we // prefer the file name as title. - if (!this.hasAssociatedFilePath && this.cachedModelFirstLineWords) { + if (this.configuredLabelFormat === 'content' && !this.hasAssociatedFilePath && this.cachedModelFirstLineWords) { return this.cachedModelFirstLineWords; } @@ -104,7 +104,9 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private ignoreDirtyOnModelContentChange = false; private versionId = 0; + private configuredEncoding: string | undefined; + private configuredLabelFormat: 'content' | 'name' = 'content'; constructor( public readonly resource: URI, @@ -130,25 +132,39 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this.setMode(preferredMode); } + // Fetch config + this.onConfigurationChange(false); + this.registerListeners(); } private registerListeners(): void { // Config Changes - this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.onConfigurationChange())); + this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.onConfigurationChange(true))); } - private onConfigurationChange(): void { - const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); + private onConfigurationChange(fromEvent: boolean): void { + // Encoding + const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); if (this.configuredEncoding !== configuredEncoding) { this.configuredEncoding = configuredEncoding; - if (!this.preferredEncoding) { + if (fromEvent && !this.preferredEncoding) { this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set } } + + // Label Format + const configuredLabelFormat = this.textResourceConfigurationService.getValue(this.resource, 'workbench.editor.untitled.labelFormat'); + if (this.configuredLabelFormat !== configuredLabelFormat && (configuredLabelFormat === 'content' || configuredLabelFormat === 'name')) { + this.configuredLabelFormat = configuredLabelFormat; + + if (fromEvent) { + this._onDidChangeName.fire(); + } + } } getVersionId(): number { @@ -283,13 +299,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this.updateTextEditorModel(undefined, this.preferredMode); } - // Figure out encoding now that model is present - this.configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); - // Listen to text model events const textEditorModel = assertIsDefined(this.textEditorModel); this._register(textEditorModel.onDidChangeContent(e => this.onModelContentChanged(textEditorModel, e))); - this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config + this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange(true))); // mode change can have impact on config // Only adjust name and dirty state etc. if we // actually created the untitled model diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index d9997f668c..ff499e7631 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -8,37 +8,22 @@ import * as assert from 'assert'; import { join } from 'vs/base/common/path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { workbenchInstantiationService, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -class ServiceAccessor { - constructor( - @IUntitledTextEditorService public readonly untitledTextEditorService: IUntitledTextEditorService, - @IEditorService public readonly editorService: TestEditorService, - @IWorkingCopyService public readonly workingCopyService: IWorkingCopyService, - @IModeService public readonly modeService: ModeServiceImpl, - @IConfigurationService public readonly testConfigurationService: TestConfigurationService - ) { } -} - suite('Untitled text editors', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index 16c316da3b..f012f722f6 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncUtilService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -23,7 +23,11 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, ) { } - public async resolveUserBindings(userBindings: string[]): Promise> { + async resolveDefaultIgnoredSettings(): Promise { + return getDefaultIgnoredSettings(); + } + + async resolveUserBindings(userBindings: string[]): Promise> { const keys: IStringDictionary = {}; for (const userbinding of userBindings) { keys[userbinding] = this.keybindingsService.resolveUserBinding(userbinding).map(part => part.getUserSettingsLabel()).join(' '); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index dd40eca4f9..70fc9339b8 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -34,6 +34,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _onDidChangeLastSyncTime: Emitter = this._register(new Emitter()); readonly onDidChangeLastSyncTime: Event = this._onDidChangeLastSyncTime.event; + private _onSyncErrors: Emitter<[SyncSource, UserDataSyncError][]> = this._register(new Emitter<[SyncSource, UserDataSyncError][]>()); + readonly onSyncErrors: Event<[SyncSource, UserDataSyncError][]> = this._onSyncErrors.event; + constructor( @ISharedProcessService sharedProcessService: ISharedProcessService ) { @@ -58,6 +61,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this._register(this.channel.listen('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime))); }); this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + this._register(this.channel.listen<[SyncSource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)]))))); } pull(): Promise { diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index 69a26af409..bc1b8b4af9 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -8,31 +8,19 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { toResource } from 'vs/base/test/common/utils'; -import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { FileOperation } from 'vs/platform/files/common/files'; -class ServiceAccessor { - constructor( - @ITextFileService public textFileService: TestTextFileService, - @IWorkingCopyFileService public workingCopyFileService: IWorkingCopyFileService, - @IWorkingCopyService public workingCopyService: IWorkingCopyService - ) { - } -} - suite('WorkingCopyFileService', () => { let instantiationService: IInstantiationService; let model: TextFileEditorModel; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { diff --git a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts index 6bd4e41097..0116ad65ec 100644 --- a/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/browser/api/extHostApiCommands.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { TestRPCProtocol } from './testRPCProtocol'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; @@ -49,7 +49,7 @@ import 'vs/editor/contrib/smartSelect/smartSelect'; import 'vs/editor/contrib/suggest/suggest'; const defaultSelector = { scheme: 'far' }; -const model: ITextModel = TextModel.createFromString( +const model: ITextModel = createTextModel( [ 'This is the first line', 'This is the second line', diff --git a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts index 8e2b73df57..d040470332 100644 --- a/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/browser/api/extHostLanguageFeatures.test.ts @@ -8,7 +8,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { setUnexpectedErrorHandler, errorHandler } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import * as types from 'vs/workbench/api/common/extHostTypes'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { Position as EditorPosition, Position } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; import { TestRPCProtocol } from './testRPCProtocol'; @@ -48,9 +48,10 @@ import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerServ import { dispose } from 'vs/base/common/lifecycle'; import { withNullAsUndefined } from 'vs/base/common/types'; import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { Progress } from 'vs/platform/progress/common/progress'; const defaultSelector = { scheme: 'far' }; -const model: ITextModel = TextModel.createFromString( +const model: ITextModel = createTextModel( [ 'This is the first line', 'This is the second line', @@ -590,7 +591,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 2); const [first, second] = actions; assert.equal(first.title, 'Testing1'); @@ -614,7 +615,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); const [first] = actions; assert.equal(first.title, 'Testing1'); @@ -637,7 +638,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); }); @@ -655,7 +656,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, CancellationToken.None); + const { validActions: actions } = await getCodeActions(model, model.getFullModelRange(), { type: modes.CodeActionTriggerType.Manual }, Progress.None, CancellationToken.None); assert.equal(actions.length, 1); }); diff --git a/src/vs/workbench/test/browser/api/extHostWebview.test.ts b/src/vs/workbench/test/browser/api/extHostWebview.test.ts index 9cc8a07eb2..d8bd5fe99f 100644 --- a/src/vs/workbench/test/browser/api/extHostWebview.test.ts +++ b/src/vs/workbench/test/browser/api/extHostWebview.test.ts @@ -13,18 +13,33 @@ import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { mock } from 'vs/workbench/test/browser/api/mock'; import { SingleProxyRPCProtocol } from './testRPCProtocol'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; +import { NullApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; suite('ExtHostWebview', () => { + let rpcProtocol: (IExtHostRpcService & IExtHostContext) | undefined; + let extHostDocuments: ExtHostDocuments | undefined; + + setup(() => { + const shape = createNoopMainThreadWebviews(); + rpcProtocol = SingleProxyRPCProtocol(shape); + + const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()); + extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + }); + test('Cannot register multiple serializers for the same view type', async () => { const viewType = 'view.type'; - const shape = createNoopMainThreadWebviews(); - const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { webviewCspSource: '', webviewResourceRoot: '', isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService, extHostDocuments!); let lastInvokedDeserializer: vscode.WebviewPanelSerializer | undefined = undefined; @@ -57,12 +72,11 @@ suite('ExtHostWebview', () => { }); test('asWebviewUri for desktop vscode-resource scheme', () => { - const shape = createNoopMainThreadWebviews(); - const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { webviewCspSource: '', webviewResourceRoot: 'vscode-resource://{{resource}}', isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService, extHostDocuments!); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); assert.strictEqual( @@ -97,13 +111,11 @@ suite('ExtHostWebview', () => { }); test('asWebviewUri for web endpoint', () => { - const shape = createNoopMainThreadWebviews(); - - const extHostWebviews = new ExtHostWebviews(SingleProxyRPCProtocol(shape), { + const extHostWebviews = new ExtHostWebviews(rpcProtocol!, { webviewCspSource: '', webviewResourceRoot: `https://{{uuid}}.webview.contoso.com/commit/{{resource}}`, isExtensionDevelopmentDebug: false, - }, undefined, new NullLogService()); + }, undefined, new NullLogService(), NullApiDeprecationService, extHostDocuments!); const webview = extHostWebviews.createWebviewPanel({} as any, 'type', 'title', 1, {}); function stripEndpointUuid(input: string) { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts index 65c284af0d..9469168d05 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentContentProviders.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { MainThreadDocumentContentProviders } from 'vs/workbench/api/browser/mainThreadDocumentContentProviders'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { mock } from 'vs/workbench/test/browser/api/mock'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; @@ -18,7 +18,7 @@ suite('MainThreadDocumentContentProviders', function () { test('events are processed properly', function () { let uri = URI.parse('test:uri'); - let model = TextModel.createFromString('1', undefined, undefined, uri); + let model = createTextModel('1', undefined, undefined, uri); let providers = new MainThreadDocumentContentProviders(new TestRPCProtocol(), null!, null!, new class extends mock() { diff --git a/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts index ae544bb26b..b65ad2c5eb 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocuments.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { BoundModelReferenceCollection } from 'vs/workbench/api/browser/mainThreadDocuments'; -import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; import { timeout } from 'vs/base/common/async'; suite('BoundModelReferenceCollection', () => { @@ -21,7 +21,7 @@ suite('BoundModelReferenceCollection', () => { let didDispose = false; col.add({ - object: { textEditorModel: TextModel.createFromString('farboo') }, + object: { textEditorModel: createTextModel('farboo') }, dispose() { didDispose = true; } @@ -36,20 +36,20 @@ suite('BoundModelReferenceCollection', () => { let disposed: number[] = []; col.add({ - object: { textEditorModel: TextModel.createFromString('farboo') }, + object: { textEditorModel: createTextModel('farboo') }, dispose() { disposed.push(0); } }); col.add({ - object: { textEditorModel: TextModel.createFromString('boofar') }, + object: { textEditorModel: createTextModel('boofar') }, dispose() { disposed.push(1); } }); col.add({ - object: { textEditorModel: TextModel.createFromString(new Array(71).join('x')) }, + object: { textEditorModel: createTextModel(new Array(71).join('x')) }, dispose() { disposed.push(2); } diff --git a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts index 8735f74342..e31238e32e 100644 --- a/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadDocumentsAndEditors.test.ts @@ -23,6 +23,8 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; suite('MainThreadDocumentsAndEditors', () => { @@ -45,7 +47,10 @@ suite('MainThreadDocumentsAndEditors', () => { deltas.length = 0; const configService = new TestConfigurationService(); configService.setUserConfiguration('editor', { 'detectIndentation': false }); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService()); + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); codeEditorService = new TestCodeEditorService(); textFileService = new class extends mock() { isDirty() { return false; } diff --git a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts index 3bf7415bf3..6e6aeac0d9 100644 --- a/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/browser/api/mainThreadEditors.test.ts @@ -42,6 +42,11 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; suite('MainThreadEditors', () => { @@ -64,7 +69,10 @@ suite('MainThreadEditors', () => { const configService = new TestConfigurationService(); - modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService()); + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); + modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), undoRedoService); const services = new ServiceCollection(); @@ -74,6 +82,9 @@ suite('MainThreadEditors', () => { services.set(IWorkspaceContextService, new TestContextService()); services.set(IWorkbenchEnvironmentService, TestEnvironmentService); services.set(IConfigurationService, configService); + services.set(IDialogService, dialogService); + services.set(INotificationService, notificationService); + services.set(IUndoRedoService, undoRedoService); services.set(IModelService, modelService); services.set(ICodeEditorService, new TestCodeEditorService()); services.set(IFileService, new TestFileService()); diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 147b0e3813..952864a70e 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -254,6 +254,37 @@ suite('Workbench base editor', () => { assert.ok(!memento.loadEditorState(testGroup0, URI.file('/E'))); }); + test('EditorMemento - move', function () { + const testGroup0 = new TestEditorGroupView(0); + + const editorGroupService = new TestEditorGroupsService([testGroup0]); + + interface TestViewState { line: number; } + + const rawMemento = Object.create(null); + let memento = new EditorMemento('id', 'key', rawMemento, 3, editorGroupService); + + memento.saveEditorState(testGroup0, URI.file('/some/folder/file-1.txt'), { line: 1 }); + memento.saveEditorState(testGroup0, URI.file('/some/folder/file-2.txt'), { line: 2 }); + memento.saveEditorState(testGroup0, URI.file('/some/other/file.txt'), { line: 3 }); + + memento.moveEditorState(URI.file('/some/folder/file-1.txt'), URI.file('/some/folder/file-moved.txt')); + + let res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-1.txt')); + assert.ok(!res); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder/file-moved.txt')); + assert.equal(res?.line, 1); + + memento.moveEditorState(URI.file('/some/folder'), URI.file('/some/folder-moved')); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-moved.txt')); + assert.equal(res?.line, 1); + + res = memento.loadEditorState(testGroup0, URI.file('/some/folder-moved/file-2.txt')); + assert.equal(res?.line, 2); + }); + test('EditoMemento - use with editor input', function () { const testGroup0 = new TestEditorGroupView(0); diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index 728e154877..a1507d42ba 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -4,43 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; -import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; import { Schemas } from 'vs/base/common/network'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; -class ServiceAccessor { - constructor(@IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService) { } -} - -class FileEditorInput extends EditorInput { - - constructor(public resource: URI) { - super(); - } - - getTypeId(): string { - return 'editorResourceFileTest'; - } - - resolve(): Promise { - return Promise.resolve(null); - } -} - suite('Workbench editor', () => { let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); teardown(() => { @@ -60,7 +39,7 @@ suite('Workbench editor', () => { assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); assert.ok(!toResource(untitled, { filterByScheme: Schemas.file })); - const file = new FileEditorInput(URI.file('/some/path.txt')); + const file = new TestEditorInput(URI.file('/some/path.txt'), 'editorResourceFileTest'); assert.equal(toResource(file)!.toString(), file.resource.toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); diff --git a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts index 71b3d7f6a4..257fff36b2 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorDiffModel.test.ts @@ -6,31 +6,20 @@ import * as assert from 'assert'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { URI } from 'vs/base/common/uri'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -class ServiceAccessor { - constructor( - @ITextModelService public textModelResolverService: ITextModelService, - @IModelService public modelService: IModelService, - @IModeService public modeService: IModeService, - ) { - } -} - suite('Workbench editor model', () => { + let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('TextDiffEditorModel', async () => { diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index 3e0b4fd898..b3793de4a3 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -20,6 +20,10 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { TestTextResourcePropertiesService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { @@ -72,9 +76,14 @@ suite('Workbench editor model', () => { }); function stubModelService(instantiationService: TestInstantiationService): IModelService { + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); - instantiationService.stub(IUndoRedoService, new UndoRedoService()); + instantiationService.stub(IDialogService, dialogService); + instantiationService.stub(INotificationService, notificationService); + instantiationService.stub(IUndoRedoService, undoRedoService); return instantiationService.createInstance(ModelServiceImpl); } }); diff --git a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts index 592d0125e8..ba34af1f56 100644 --- a/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts @@ -21,6 +21,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; suite('Editor - Range decorations', () => { @@ -136,7 +137,7 @@ suite('Editor - Range decorations', () => { } function aModel(resource: URI, content: string = text): TextModel { - let model = TextModel.createFromString(content, TextModel.DEFAULT_CREATION_OPTIONS, null, resource); + let model = createTextModel(content, TextModel.DEFAULT_CREATION_OPTIONS, null, resource); modelsToDispose.push(model); return model; } diff --git a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts index 6d2c25e792..91d11eef05 100644 --- a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts @@ -8,26 +8,18 @@ import { URI } from 'vs/base/common/uri'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -class ServiceAccessor { - constructor( - @IModelService public modelService: IModelService, - @IModeService public modeService: IModeService - ) { } -} - suite('Resource text editors', () => { + let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let accessor: TestServiceAccessor; setup(() => { instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + accessor = instantiationService.createInstance(TestServiceAccessor); }); test('basics', async () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 9a73263b7d..12facadaa3 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -10,16 +10,15 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorInput, EditorOptions, EditorsOrder, IFileEditorInput, IEditorInputFactoryRegistry, IEditorInputFactory, Extensions as EditorExtensions, ISaveOptions, IMoveResult } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, IEditorGroupsAccessor } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; -import Severity from 'vs/base/common/severity'; import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, IResourceInput, IEditorModel } from 'vs/platform/editor/common/editor'; import { IUntitledTextEditorService, UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; @@ -45,17 +44,18 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; -import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; -import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor, ISaveEditorsOptions, IRevertAllEditorsOptions, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { Dimension } from 'vs/base/browser/dom'; @@ -89,10 +89,16 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { Direction } from 'vs/base/browser/ui/grid/grid'; -import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, emptyProgress } from 'vs/platform/progress/common/progress'; +import { IProgressService, IProgressOptions, IProgressWindowOptions, IProgressNotificationOptions, IProgressCompositeOptions, IProgress, IProgressStep, Progress } from 'vs/platform/progress/common/progress'; import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; export import TestTextResourcePropertiesService = CommonWorkbenchTestServices.TestTextResourcePropertiesService; export import TestContextService = CommonWorkbenchTestServices.TestContextService; @@ -107,6 +113,78 @@ export interface ITestInstantiationService extends IInstantiationService { stub(service: ServiceIdentifier, ctor: any): T; } +export function workbenchInstantiationService(overrides?: { textFileService?: (instantiationService: IInstantiationService) => ITextFileService }): ITestInstantiationService { + const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); + + instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); + instantiationService.stub(IEnvironmentService, TestEnvironmentService); + const contextKeyService = instantiationService.createInstance(MockContextKeyService); + instantiationService.stub(IContextKeyService, contextKeyService); + instantiationService.stub(IProgressService, new TestProgressService()); + const workspaceContextService = new TestContextService(TestWorkspace); + instantiationService.stub(IWorkspaceContextService, workspaceContextService); + const configService = new TestConfigurationService(); + instantiationService.stub(IConfigurationService, configService); + instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); + instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); + instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); + instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); + instantiationService.stub(IFileDialogService, new TestFileDialogService()); + instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); + instantiationService.stub(IHistoryService, new TestHistoryService()); + instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); + instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); + instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); + instantiationService.stub(IFileService, new TestFileService()); + instantiationService.stub(IBackupFileService, new TestBackupFileService()); + instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(INotificationService, new TestNotificationService()); + instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); + instantiationService.stub(IMenuService, new TestMenuService()); + instantiationService.stub(IKeybindingService, new MockKeybindingService()); + instantiationService.stub(IDecorationsService, new TestDecorationsService()); + instantiationService.stub(IExtensionService, new TestExtensionService()); + instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService)); + instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); + instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); + instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); + instantiationService.stub(IThemeService, new TestThemeService()); + instantiationService.stub(ILogService, new NullLogService()); + const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); + instantiationService.stub(IEditorGroupsService, editorGroupService); + instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); + const editorService = new TestEditorService(editorGroupService); + instantiationService.stub(IEditorService, editorService); + instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); + instantiationService.stub(IViewletService, new TestViewletService()); + + return instantiationService; +} + +export class TestServiceAccessor { + constructor( + @ILifecycleService public lifecycleService: TestLifecycleService, + @ITextFileService public textFileService: TestTextFileService, + @IWorkingCopyFileService public workingCopyFileService: IWorkingCopyFileService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IWorkspaceContextService public contextService: TestContextService, + @IModelService public modelService: ModelServiceImpl, + @IFileService public fileService: TestFileService, + @IFileDialogService public fileDialogService: TestFileDialogService, + @IWorkingCopyService public workingCopyService: IWorkingCopyService, + @IEditorService public editorService: TestEditorService, + @IEditorGroupsService public editorGroupService: IEditorGroupsService, + @IModeService public modeService: IModeService, + @ITextModelService public textModelResolverService: ITextModelService, + @IUntitledTextEditorService public untitledTextEditorService: UntitledTextEditorService, + @IConfigurationService public testConfigurationService: TestConfigurationService, + @IBackupFileService public backupFileService: TestBackupFileService + ) { } +} + export class TestTextFileService extends BrowserTextFileService { private resolveTextContentError!: FileOperationError | null; @@ -173,57 +251,6 @@ export class TestTextFileService extends BrowserTextFileService { export const TestEnvironmentService = new BrowserWorkbenchEnvironmentService(Object.create(null)); -export function workbenchInstantiationService(overrides?: { textFileService?: (instantiationService: IInstantiationService) => ITextFileService }): ITestInstantiationService { - const instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); - - instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); - instantiationService.stub(IEnvironmentService, TestEnvironmentService); - const contextKeyService = instantiationService.createInstance(MockContextKeyService); - instantiationService.stub(IContextKeyService, contextKeyService); - instantiationService.stub(IProgressService, new TestProgressService()); - const workspaceContextService = new TestContextService(TestWorkspace); - instantiationService.stub(IWorkspaceContextService, workspaceContextService); - const configService = new TestConfigurationService(); - instantiationService.stub(IConfigurationService, configService); - instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService(contextKeyService, configService, TestEnvironmentService)); - instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); - instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); - instantiationService.stub(IDialogService, new TestDialogService()); - instantiationService.stub(IAccessibilityService, new TestAccessibilityService()); - instantiationService.stub(IFileDialogService, new TestFileDialogService()); - instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); - instantiationService.stub(IHistoryService, new TestHistoryService()); - instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); - instantiationService.stub(IUndoRedoService, instantiationService.createInstance(UndoRedoService)); - instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); - instantiationService.stub(IFileService, new TestFileService()); - instantiationService.stub(IBackupFileService, new TestBackupFileService()); - instantiationService.stub(ITelemetryService, NullTelemetryService); - instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); - instantiationService.stub(IMenuService, new TestMenuService()); - instantiationService.stub(IKeybindingService, new MockKeybindingService()); - instantiationService.stub(IDecorationsService, new TestDecorationsService()); - instantiationService.stub(IExtensionService, new TestExtensionService()); - instantiationService.stub(IWorkingCopyFileService, instantiationService.createInstance(WorkingCopyFileService)); - instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : instantiationService.createInstance(TestTextFileService)); - instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); - instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - instantiationService.stub(IThemeService, new TestThemeService()); - instantiationService.stub(ILogService, new NullLogService()); - const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); - instantiationService.stub(IEditorGroupsService, editorGroupService); - instantiationService.stub(ILabelService, instantiationService.createInstance(LabelService)); - const editorService = new TestEditorService(editorGroupService); - instantiationService.stub(IEditorService, editorService); - instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); - instantiationService.stub(IViewletService, new TestViewletService()); - - return instantiationService; -} - export class TestProgressService implements IProgressService { _serviceBrand: undefined; @@ -233,7 +260,7 @@ export class TestProgressService implements IProgressService { task: (progress: IProgress) => Promise, onDidCancel?: ((choice?: number | undefined) => void) | undefined ): Promise { - return task(emptyProgress); + return task(Progress.None); } } @@ -295,14 +322,7 @@ export class TestHistoryService implements IHistoryService { openLastEditLocation(): void { } } -export class TestDialogService implements IDialogService { - _serviceBrand: undefined; - - confirm(_confirmation: IConfirmation): Promise { return Promise.resolve({ confirmed: false }); } - show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } - about(): Promise { return Promise.resolve(); } -} export class TestFileDialogService implements IFileDialogService { @@ -739,7 +759,14 @@ export class TestFileService implements IFileService { } del(_resource: URI, _options?: { useTrash?: boolean, recursive?: boolean }): Promise { return Promise.resolve(); } - watch(_resource: URI): IDisposable { return Disposable.None; } + + readonly watches: URI[] = []; + watch(_resource: URI): IDisposable { + this.watches.push(_resource); + + return toDisposable(() => this.watches.splice(this.watches.indexOf(_resource), 1)); + } + getWriteEncoding(_resource: URI): IResourceEncoding { return { encoding: 'utf8', hasBOM: false }; } dispose(): void { } } @@ -899,3 +926,136 @@ export class TestFilesConfigurationService extends FilesConfigurationService { super.onFilesConfigurationChange(configuration); } } + +export class TestReadonlyTextFileEditorModel extends TextFileEditorModel { + + isReadonly(): boolean { + return true; + } +} + +export class TestEditorInput extends EditorInput { + + constructor(public resource: URI, private typeId: string) { + super(); + } + + getTypeId(): string { + return this.typeId; + } + + resolve(): Promise { + return Promise.resolve(null); + } +} + +export function registerTestEditor(id: string, inputs: SyncDescriptor[], factoryInputId?: string): IDisposable { + class TestEditorControl extends BaseEditor { + + constructor() { super(id, NullTelemetryService, new TestThemeService(), new TestStorageService()); } + + async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + super.setInput(input, options, token); + + await input.resolve(); + } + + getId(): string { return id; } + layout(): void { } + createEditor(): any { } + } + + const disposables = new DisposableStore(); + + disposables.add(Registry.as(Extensions.Editors).registerEditor(EditorDescriptor.create(TestEditorControl, id, 'Test Editor Control'), inputs)); + + if (factoryInputId) { + + interface ISerializedTestInput { + resource: string; + } + + class EditorsObserverTestEditorInputFactory implements IEditorInputFactory { + + canSerialize(editorInput: EditorInput): boolean { + return true; + } + + serialize(editorInput: EditorInput): string { + let testEditorInput = editorInput; + let testInput: ISerializedTestInput = { + resource: testEditorInput.resource.toString() + }; + + return JSON.stringify(testInput); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + let testInput: ISerializedTestInput = JSON.parse(serializedEditorInput); + + return new TestFileEditorInput(URI.parse(testInput.resource), factoryInputId!); + } + } + + disposables.add(Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(factoryInputId, EditorsObserverTestEditorInputFactory)); + } + + return disposables; +} + +export class TestFileEditorInput extends EditorInput implements IFileEditorInput { + gotDisposed = false; + gotSaved = false; + gotSavedAs = false; + gotReverted = false; + dirty = false; + private fails = false; + + constructor( + public resource: URI, + private typeId: string + ) { + super(); + } + + getTypeId() { return this.typeId; } + resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } + matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestFileEditorInput && other.getTypeId() === this.typeId; } + setEncoding(encoding: string) { } + getEncoding() { return undefined; } + setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } + setForceOpenAsBinary(): void { } + setFailToOpen(): void { + this.fails = true; + } + async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSaved = true; + return this; + } + async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + this.gotSavedAs = true; + return this; + } + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + this.gotReverted = true; + this.gotSaved = false; + this.gotSavedAs = false; + return true; + } + setDirty(): void { this.dirty = true; } + isDirty(): boolean { + return this.dirty; + } + isReadonly(): boolean { + return false; + } + isResolved(): boolean { return false; } + dispose(): void { + super.dispose(); + this.gotDisposed = true; + } + movedEditor: IMoveResult | undefined = undefined; + move(): IMoveResult | undefined { return this.movedEditor; } +} diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index 0fdca7b10f..8d8e16c727 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -34,6 +34,11 @@ import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/pla import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { NullLogService } from 'vs/platform/log/common/log'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; namespace Timer { export interface ITimerEvent { @@ -73,11 +78,17 @@ suite.skip('QuickOpen performance (integration)', () => { const telemetryService = new TestTelemetryService(); const configurationService = new TestConfigurationService(); const textResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService(), new UndoRedoService())], + [IDialogService, dialogService], + [INotificationService, notificationService], + [IUndoRedoService, undoRedoService], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), new NullLogService(), undoRedoService)], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index c159788224..495db72a6a 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -37,6 +37,11 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService'; +import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; // declare var __dirname: string; @@ -63,11 +68,17 @@ suite.skip('TextSearch performance (integration)', () => { const configurationService = new TestConfigurationService(); const textResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); const logService = new NullLogService(); + const dialogService = new TestDialogService(); + const notificationService = new TestNotificationService(); + const undoRedoService = new UndoRedoService(dialogService, notificationService); const instantiationService = new InstantiationService(new ServiceCollection( [ITelemetryService, telemetryService], [IConfigurationService, configurationService], [ITextResourcePropertiesService, textResourcePropertiesService], - [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, new UndoRedoService())], + [IDialogService, dialogService], + [INotificationService, notificationService], + [IUndoRedoService, undoRedoService], + [IModelService, new ModelServiceImpl(configurationService, textResourcePropertiesService, new TestThemeService(), logService, undoRedoService)], [IWorkspaceContextService, new TestContextService(testWorkspace(URI.file(testWorkspacePath)))], [IEditorService, new TestEditorService()], [IEditorGroupsService, new TestEditorGroupsService()], diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index b5b8090112..008199045c 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestContextService, TestFileService, TestFileDialogService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; -import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; +import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { FileOperationError, IFileService } from 'vs/platform/files/common/files'; @@ -30,6 +30,13 @@ import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { LogLevel } from 'vs/platform/log/common/log'; import { IRemotePathService } from 'vs/workbench/services/path/common/remotePathService'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { UTF16le, UTF16be, UTF8_with_bom } from 'vs/base/node/encoding'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { NodeTestBackupFileService } from 'vs/workbench/services/backup/test/electron-browser/backupFileService.test'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export const TestWindowConfiguration: IWindowConfiguration = { windowId: 0, @@ -110,6 +117,31 @@ export class TestTextFileService extends NativeTextFileService { } } +export class TestNativeTextFileServiceWithEncodingOverrides extends NativeTextFileService { + + private _testEncoding: TestEncodingOracle | undefined; + get encoding(): TestEncodingOracle { + if (!this._testEncoding) { + this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); + } + + return this._testEncoding; + } +} + +class TestEncodingOracle extends EncodingOracle { + + protected get encodingOverrides(): IEncodingOverride[] { + return [ + { extension: 'utf16le', encoding: UTF16le }, + { extension: 'utf16be', encoding: UTF16be }, + { extension: 'utf8bom', encoding: UTF8_with_bom } + ]; + } + + protected set encodingOverrides(overrides: IEncodingOverride[]) { } +} + export class TestSharedProcessService implements ISharedProcessService { _serviceBrand: undefined; @@ -187,3 +219,20 @@ export function workbenchInstantiationService(): ITestInstantiationService { return instantiationService; } + +export class TestServiceAccessor { + constructor( + @ILifecycleService public lifecycleService: TestLifecycleService, + @ITextFileService public textFileService: TestTextFileService, + @IFilesConfigurationService public filesConfigurationService: TestFilesConfigurationService, + @IWorkspaceContextService public contextService: TestContextService, + @IModelService public modelService: ModelServiceImpl, + @IFileService public fileService: TestFileService, + @IElectronService public electronService: TestElectronService, + @IFileDialogService public fileDialogService: TestFileDialogService, + @IBackupFileService public backupFileService: NodeTestBackupFileService, + @IWorkingCopyService public workingCopyService: IWorkingCopyService, + @IEditorService public editorService: IEditorService + ) { + } +} diff --git a/yarn.lock b/yarn.lock index ee2053a77a..a7d885ed99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9389,16 +9389,16 @@ typescript-formatter@7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" +typescript@3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a" + integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ== + typescript@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -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== - uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192"