diff --git a/.vscode/settings.json b/.vscode/settings.json index eea3f45f764..3abb868dc0c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,7 +12,8 @@ } }, "files.associations": { - "cglicenses.json": "jsonc" + "cglicenses.json": "jsonc", + "*.tst": "typescript" }, "search.exclude": { "**/node_modules": true, @@ -27,7 +28,8 @@ "test/automation/out/**": true, "test/integration/browser/out/**": true, "src/vs/base/test/node/uri.test.data.txt": true, - "src/vs/workbench/api/test/browser/extHostDocumentData.test.perf-data.ts": true + "src/vs/workbench/api/test/browser/extHostDocumentData.test.perf-data.ts": true, + "src/vs/editor/test/node/diffing/fixtures/**": true, }, "lcov.path": [ "./.build/coverage/lcov.info", diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 87b7f72f166..69122f64711 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -104,7 +104,7 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName) { // check for a webpack configuration files, then invoke webpack // and merge its output with the files stream. const webpackConfigLocations = glob.sync(path.join(extensionPath, '**', webpackConfigFileName), { ignore: ['**/node_modules'] }); - const webpackStreams = webpackConfigLocations.map(webpackConfigPath => { + const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { const webpackDone = (err, stats) => { fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); if (err) { @@ -118,27 +118,30 @@ function fromLocalWebpack(extensionPath, webpackConfigFileName) { result.emit('error', compilation.warnings.join('\n')); } }; - const webpackConfig = { - ...require(webpackConfigPath), - ...{ mode: 'production' } - }; - const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); - return webpackGulp(webpackConfig, webpack, webpackDone) - .pipe(es.through(function (data) { - data.stat = data.stat || {}; - data.base = extensionPath; - this.emit('data', data); - })) - .pipe(es.through(function (data) { - // source map handling: - // * rewrite sourceMappingURL - // * save to disk so that upload-task picks this up - const contents = data.contents.toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); - this.emit('data', data); - })); + const exportedConfig = require(webpackConfigPath); + return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { + const webpackConfig = { + ...config, + ...{ mode: 'production' } + }; + const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); + return webpackGulp(webpackConfig, webpack, webpackDone) + .pipe(es.through(function (data) { + data.stat = data.stat || {}; + data.base = extensionPath; + this.emit('data', data); + })) + .pipe(es.through(function (data) { + // source map handling: + // * rewrite sourceMappingURL + // * save to disk so that upload-task picks this up + const contents = data.contents.toString('utf8'); + data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { + return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; + }), 'utf8'); + this.emit('data', data); + })); + }); }); es.merge(...webpackStreams, es.readArray(files)) // .pipe(es.through(function (data) { diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 71513ae1f58..56a47071b5a 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -121,7 +121,7 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string): { ignore: ['**/node_modules'] } )); - const webpackStreams = webpackConfigLocations.map(webpackConfigPath => { + const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { const webpackDone = (err: any, stats: any) => { fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); @@ -137,29 +137,32 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string): } }; - const webpackConfig = { - ...require(webpackConfigPath), - ...{ mode: 'production' } - }; - const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); + const exportedConfig = require(webpackConfigPath); + return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { + const webpackConfig = { + ...config, + ...{ mode: 'production' } + }; + const relativeOutputPath = path.relative(extensionPath, webpackConfig.output.path); - return webpackGulp(webpackConfig, webpack, webpackDone) - .pipe(es.through(function (data) { - data.stat = data.stat || {}; - data.base = extensionPath; - this.emit('data', data); - })) - .pipe(es.through(function (data: File) { - // source map handling: - // * rewrite sourceMappingURL - // * save to disk so that upload-task picks this up - const contents = (data.contents).toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); + return webpackGulp(webpackConfig, webpack, webpackDone) + .pipe(es.through(function (data) { + data.stat = data.stat || {}; + data.base = extensionPath; + this.emit('data', data); + })) + .pipe(es.through(function (data: File) { + // source map handling: + // * rewrite sourceMappingURL + // * save to disk so that upload-task picks this up + const contents = (data.contents).toString('utf8'); + data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { + return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; + }), 'utf8'); - this.emit('data', data); - })); + this.emit('data', data); + })); + }); }); es.merge(...webpackStreams, es.readArray(files)) @@ -506,7 +509,7 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp for (const { configPath, outputRoot } of webpackConfigLocations) { const configOrFnOrArray = require(configPath); - function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown,args: unknown) => webpack.Configuration) | webpack.Configuration[]) { + function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown, args: unknown) => webpack.Configuration) | webpack.Configuration[]) { for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; if (outputRoot) { diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index f48942bf3e5..930691f1a6f 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -73,6 +73,8 @@ export interface ICommandHandler { #include(vs/editor/common/core/wordHelper): IWordAtPosition #includeAll(vs/editor/common/model): IScrollEvent #include(vs/editor/common/diff/smartLinesDiffComputer): IChange, ICharChange, ILineChange +#include(vs/editor/common/diff/documentDiffProvider): IDocumentDiffProvider, IDocumentDiffProviderOptions, IDocumentDiff +#include(vs/editor/common/diff/linesDiffComputer): LineRangeMapping, LineRange, RangeMapping #include(vs/editor/common/core/dimension): IDimension #includeAll(vs/editor/common/editorCommon): IScrollEvent #includeAll(vs/editor/common/textModelEvents): diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index a226de6c552..dc58bafec5f 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -14,11 +14,11 @@ "out/**/*.js" ], "dependencies": { + "@vscode/l10n": "^0.0.10", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.5", "vscode-languageserver-types": "^3.17.1", - "vscode-markdown-languageservice": "^0.2.0", - "vscode-nls": "^5.2.0", + "vscode-markdown-languageservice": "^0.3.0-alpha.2", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/package/README.md b/extensions/markdown-language-features/server/package/README.md deleted file mode 100644 index 1fd38302195..00000000000 --- a/extensions/markdown-language-features/server/package/README.md +++ /dev/null @@ -1,135 +0,0 @@ -# Markdown Language Server - -> **❗ Import** This is still in development. While the language server is being used by VS Code, it has not yet been tested with other clients. - -The Markdown language server powers VS Code's built-in markdown support, providing tools for writing and browsing Markdown files. It runs as a separate executable and implements the [language server protocol](https://microsoft.github.io/language-server-protocol/overview). - -This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server. - - -## Server capabilities - -- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links. - -- [Folding](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) of Markdown regions, block elements, and header sections. - -- [Smart selection](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) for inline elements, block elements, and header sections. - -- [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to headers in a document. - -- [Workspace Symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) for quick navigation to headers in the workspace - -- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) for making Markdown links in a document clickable. - -- [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace. - -- [Go to definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) from links to headers or link definitions. - -- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace. - -- Find all references to a file. Uses a custom `markdown/getReferencesToFileInWorkspace` message. - -- [Code Actions](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction) - - - Organize link definitions source action. - - Extract link to definition refactoring. - -- Updating links when a file is moved / renamed. Uses a custom `markdown/getEditForFileRenames` message. - -- [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links. - - -## Client requirements - -### Initialization options - -The client can send the following initialization options to the server: - -- `markdownFileExtensions` Array file extensions that should be considered as Markdown. These should not include the leading `.`. For example: `['md', 'mdown', 'markdown']`. - -### Settings - -Clients may send a `workspace/didChangeConfiguration` notification to notify the server of settings changes. -The server supports the following settings: - -- `markdown` - - `suggest` - - `paths` - - `enabled` — Enable/disable path suggestions. - - - `occurrencesHighlight` - - `enabled` — Enable/disable highlighting of link occurrences. - - - `validate` - - `enabled` — Enable/disable all validation. - - `referenceLinks` - - `enabled` — Enable/disable validation of reference links: `[text][ref]` - - `fragmentLinks` - - `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)` - - `fileLinks` - - `enabled` — Enable/disable validation of links to file in the workspace. - - `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files. Use `inherit` to inherit the `fragmentLinks` setting. - - `ignoredLinks` — Array of glob patterns for files that should not be validated. - - `unusedLinkDefinitions` - - `enabled` — Enable/disable validation of unused link definitions. - - `duplicateLinkDefinitions` - - `enabled` — Enable/disable validation of duplicated link definitions. - -### Custom requests - -To support all of the features of the language server, the client needs to implement a few custom request types. The definitions of these request types can be found in [`protocol.ts`](./src/protocol.ts) - -#### `markdown/parse` - -Get the tokens for a Markdown file. Clients are expected to use [Markdown-it](https://github.com/markdown-it/markdown-it) for this. - -We require that clients bring their own version of Markdown-it so that they can customize/extend Markdown-it. - -#### `markdown/fs/readFile` - -Read the contents of a file in the workspace. - -#### `markdown/fs/readDirectory` - -Read the contents of a directory in the workspace. - -#### `markdown/fs/stat` - -Check if a given file/directory exists in the workspace. - -#### `markdown/fs/watcher/create` - -Create a file watcher. This is needed for diagnostics support. - -#### `markdown/fs/watcher/delete` - -Delete a previously created file watcher. - -#### `markdown/findMarkdownFilesInWorkspace` - -Get a list of all markdown files in the workspace. - - -## Contribute - -The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server). - -File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source. - -Most of the functionality of the server is located in libraries: - -- [vscode-markdown-languageservice](https://github.com/microsoft/vscode-markdown-languageservice) contains the implementation of all features as a reusable library. -- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS. - -Help on any of these projects is very welcome. - -## Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - -## License - -Copyright (c) Microsoft Corporation. All rights reserved. - -Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License. - diff --git a/extensions/markdown-language-features/server/package/package.json b/extensions/markdown-language-features/server/package/package.json deleted file mode 100644 index c7540437c7d..00000000000 --- a/extensions/markdown-language-features/server/package/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "vscode-markdown-languageserver", - "description": "Markdown language server", - "version": "0.2.0-alpha.4", - "author": "Microsoft Corporation", - "license": "MIT", - "engines": { - "node": "*" - }, - "main": "./out/node/main", - "browser": "./dist/browser/main", - "files": [ - "dist/**/*.js", - "out/**/*.js" - ], - "dependencies": { - "vscode-languageserver": "^8.0.2", - "vscode-languageserver-textdocument": "^1.0.5", - "vscode-languageserver-types": "^3.17.1", - "vscode-markdown-languageservice": "^0.2.0-alpha.4", - "vscode-nls": "^5.2.0", - "vscode-uri": "^3.0.3" - }, - "devDependencies": { - "@types/node": "16.x" - }, - "scripts": { - "compile": "gulp compile-extension:markdown-language-features-server", - "prepublishOnly": "npm run compile", - "watch": "gulp watch-extension:markdown-language-features-server" - } -} diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts index a9dbfe940f3..3711832b1b6 100644 --- a/extensions/markdown-language-features/server/src/server.ts +++ b/extensions/markdown-language-features/server/src/server.ts @@ -7,7 +7,6 @@ import { CancellationToken, Connection, InitializeParams, InitializeResult, Note import { TextDocument } from 'vscode-languageserver-textdocument'; import * as lsp from 'vscode-languageserver-types'; import * as md from 'vscode-markdown-languageservice'; -import * as nls from 'vscode-nls'; import { URI } from 'vscode-uri'; import { getLsConfiguration, LsConfiguration } from './config'; import { ConfigurationManager } from './configuration'; @@ -16,8 +15,7 @@ import { LogFunctionLogger } from './logging'; import * as protocol from './protocol'; import { IDisposable } from './util/dispose'; import { VsCodeClientWorkspace } from './workspace'; - -const localize = nls.loadMessageBundle(); +import * as l10n from '@vscode/l10n'; interface MdServerInitializationOptions extends LsConfiguration { } @@ -204,7 +202,7 @@ export async function startServer(connection: Connection, serverConfig: { if (params.context.only?.some(kind => kind === 'source' || kind.startsWith('source.'))) { const action: lsp.CodeAction = { - title: localize('organizeLinkDefAction.title', "Organize link definitions"), + title: l10n.t("Organize link definitions"), kind: organizeLinkDefKind, data: { uri: document.uri } }; diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index 11e2e969f7e..3c867b8ff50 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -3,9 +3,14 @@ "@types/node@16.x": - version "16.11.59" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.59.tgz#823f238b9063ccc3b3b7f13186f143a57926c4f6" - integrity sha512-6u+36Dj3aDzhfBVUf/mfmc92OEdzQ2kx2jcXGdigfl70E/neV21ZHE6UCz4MDzTRcVqGAM27fk+DLXvyDsn3Jw== + version "16.18.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" + integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg== + +"@vscode/l10n@^0.0.10": + version "0.0.10" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0" + integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ== picomatch@^2.3.1: version "2.3.1" @@ -42,23 +47,18 @@ vscode-languageserver@^8.0.2: dependencies: vscode-languageserver-protocol "3.17.2" -vscode-markdown-languageservice@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.2.0.tgz#93e230a1ed826786792e820a6e993e50139a0119" - integrity sha512-3Jh7/eN6zEPqfkT6cjL+AwGoQ7euL8RtW3FYf24IfPksz4nAZJcRibRtpSdlCaOKpwEoy/f5Axh94cmWPIGBMw== +vscode-markdown-languageservice@^0.3.0-alpha.2: + version "0.3.0-alpha.2" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.3.0-alpha.2.tgz#7fe1b053001f4829586a7c15c08ce5bd2a3067d1" + integrity sha512-O8kcUV/jhUeafjSCMM3ZOGBnX8ro+GzQ+/Pj2iC8JDAlhEldG3cXtexkRtx5EBO9DrkfY80ORXWf1VUXjXrhwA== dependencies: + "@vscode/l10n" "^0.0.10" picomatch "^2.3.1" vscode-languageserver-textdocument "^1.0.5" vscode-languageserver-types "^3.17.1" - vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-nls@^5.0.1, vscode-nls@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.2.0.tgz#3cb6893dd9bd695244d8a024bdf746eea665cc3f" - integrity sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng== - vscode-uri@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" - integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== + version "3.0.6" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.6.tgz#5e6e2e1a4170543af30151b561a41f71db1d6f91" + integrity sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ== diff --git a/extensions/package.json b/extensions/package.json index ab0a515dacf..b9739529518 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -10,8 +10,8 @@ "postinstall": "node ./postinstall.mjs" }, "devDependencies": { - "@parcel/watcher": "2.0.5", - "esbuild": "^0.11.12", + "@parcel/watcher": "2.0.7", + "esbuild": "^0.15.14", "vscode-grammar-updater": "^1.1.0" } } diff --git a/extensions/typescript-language-features/extension-browser.webpack.config.js b/extensions/typescript-language-features/extension-browser.webpack.config.js index 9cbf6903725..c931b906965 100644 --- a/extensions/typescript-language-features/extension-browser.webpack.config.js +++ b/extensions/typescript-language-features/extension-browser.webpack.config.js @@ -63,7 +63,10 @@ module.exports = [withBrowserDefaults({ entry: { 'typescript/tsserver.web': './web/webServer.ts' }, - ignoreWarnings: [/Critical dependency: the request of a dependency is an expression/], + module: { + exprContextCritical: false, + }, + ignoreWarnings: [/Critical dependency: the request of a dependency is an expression/], output: { // all output goes into `dist`. // packaging depends on that and this must always be like it diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 5fd6f84c662..2ddaf3d6a1c 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -36,7 +36,7 @@ ], "dependencies": { "@vscode/extension-telemetry": "0.7.1-preview", - "jsonc-parser": "^2.2.1", + "jsonc-parser": "^3.2.0", "semver": "5.5.1", "vscode-tas-client": "^0.1.63", "vscode-uri": "^3.0.3" diff --git a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts index 81700b0efe4..0be4cb767b7 100644 --- a/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/typescript-language-features/src/languageFeatures/diagnostics.ts @@ -34,6 +34,7 @@ export const enum DiagnosticKind { } class FileDiagnostics { + private readonly _diagnostics = new Map>(); constructor( @@ -61,7 +62,7 @@ class FileDiagnostics { return true; } - public getDiagnostics(settings: DiagnosticSettings): vscode.Diagnostic[] { + public getAllDiagnostics(settings: DiagnosticSettings): vscode.Diagnostic[] { if (!settings.getValidate(this.language)) { return []; } @@ -73,6 +74,12 @@ class FileDiagnostics { ]; } + public delete(toDelete: vscode.Diagnostic): void { + for (const [type, diags] of this._diagnostics) { + this._diagnostics.set(type, diags.filter(diag => !diagnosticsEquals(diag, toDelete))); + } + } + private getSuggestionDiagnostics(settings: DiagnosticSettings) { const enableSuggestions = settings.getEnableSuggestions(this.language); return this.get(DiagnosticKind.Suggestion).filter(x => { @@ -177,14 +184,14 @@ export class DiagnosticsManager extends Disposable { public setValidate(language: DiagnosticLanguage, value: boolean) { const didUpdate = this._settings.setValidate(language, value); if (didUpdate) { - this.rebuild(); + this.rebuildAll(); } } public setEnableSuggestions(language: DiagnosticLanguage, value: boolean) { const didUpdate = this._settings.setEnableSuggestions(language, value); if (didUpdate) { - this.rebuild(); + this.rebuildAll(); } } @@ -217,11 +224,19 @@ export class DiagnosticsManager extends Disposable { this._currentDiagnostics.set(file, diagnostics); } - public delete(resource: vscode.Uri): void { + public deleteAllDiagnosticsInFile(resource: vscode.Uri): void { this._currentDiagnostics.delete(resource); this._diagnostics.delete(resource); } + public deleteDiagnostic(resource: vscode.Uri, diagnostic: vscode.Diagnostic): void { + const fileDiagnostics = this._diagnostics.get(resource); + if (fileDiagnostics) { + fileDiagnostics.delete(diagnostic); + this.rebuildFile(fileDiagnostics); + } + } + public getDiagnostics(file: vscode.Uri): ReadonlyArray { return this._currentDiagnostics.get(file) || []; } @@ -239,13 +254,17 @@ export class DiagnosticsManager extends Disposable { } const fileDiagnostics = this._diagnostics.get(file); - this._currentDiagnostics.set(file, fileDiagnostics ? fileDiagnostics.getDiagnostics(this._settings) : []); + this._currentDiagnostics.set(file, fileDiagnostics ? fileDiagnostics.getAllDiagnostics(this._settings) : []); } - private rebuild(): void { + private rebuildAll(): void { this._currentDiagnostics.clear(); for (const fileDiagnostic of this._diagnostics.values) { - this._currentDiagnostics.set(fileDiagnostic.file, fileDiagnostic.getDiagnostics(this._settings)); + this.rebuildFile(fileDiagnostic); } } + + private rebuildFile(fileDiagnostic: FileDiagnostics) { + this._currentDiagnostics.set(fileDiagnostic.file, fileDiagnostic.getAllDiagnostics(this._settings)); + } } diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index fd3f84963e6..9986e0fb004 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -77,24 +77,27 @@ export default class FileConfigurationManager extends Disposable { const cachedOptions = this.formatOptions.get(document.uri); if (cachedOptions) { const cachedOptionsValue = await cachedOptions; + if (token.isCancellationRequested) { + return; + } + if (cachedOptionsValue && areFileConfigurationsEqual(cachedOptionsValue, currentOptions)) { return; } } - let resolve: (x: FileConfiguration | undefined) => void; - this.formatOptions.set(document.uri, new Promise(r => resolve = r)); + const task = (async () => { + try { + const response = await this.client.execute('configure', { file, ...currentOptions }, token); + return response.type === 'response' ? currentOptions : undefined; + } catch { + return undefined; + } + })(); - const args: Proto.ConfigureRequestArguments = { - file, - ...currentOptions, - }; - try { - const response = await this.client.execute('configure', args, token); - resolve!(response.type === 'response' ? currentOptions : undefined); - } finally { - resolve!(undefined); - } + this.formatOptions.set(document.uri, task); + + await task; } public async setGlobalConfigurationFromDocument( diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index a2391e28e07..decd9c912a6 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -20,6 +20,11 @@ import * as typeConverters from '../utils/typeConverters'; import { DiagnosticsManager } from './diagnostics'; import FileConfigurationManager from './fileConfigurationManager'; +type ApplyCodeActionCommand_args = { + readonly resource: vscode.Uri; + readonly diagnostic: vscode.Diagnostic; + readonly action: Proto.CodeFixAction; +}; class ApplyCodeActionCommand implements Command { public static readonly ID = '_typescript.applyCodeActionCommand'; @@ -27,12 +32,11 @@ class ApplyCodeActionCommand implements Command { constructor( private readonly client: ITypeScriptServiceClient, + private readonly diagnosticManager: DiagnosticsManager, private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute( - action: Proto.CodeFixAction - ): Promise { + public async execute({ resource, action, diagnostic }: ApplyCodeActionCommand_args): Promise { /* __GDPR__ "quickFix.execute" : { "owner": "mjbvz", @@ -46,6 +50,7 @@ class ApplyCodeActionCommand implements Command { fixName: action.fixName }); + this.diagnosticManager.deleteDiagnostic(resource, diagnostic); return applyCodeActionCommands(this.client, action.commands, nulToken); } } @@ -212,7 +217,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + ): Promise { const file = this.client.toOpenedFilePath(document); if (!file) { - return []; + return; } const fixableDiagnostics = await this.supportedCodeActionProvider.getFixableDiagnosticsForContext(context); - if (!fixableDiagnostics.size) { - return []; + if (!fixableDiagnostics.size || token.isCancellationRequested) { + return; } if (this.client.bufferSyncSupport.hasPendingDiagnostics(document.uri)) { - return []; + return; } await this.formattingConfigurationManager.ensureConfigurationForDocument(document, token); + if (token.isCancellationRequested) { + return; + } const results = new CodeActionSet(); for (const diagnostic of fixableDiagnostics.values) { await this.getFixesForDiagnostic(document, file, diagnostic, results, token); + if (token.isCancellationRequested) { + return; + } } const allActions = Array.from(results.values); @@ -303,12 +314,13 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider{ action: tsAction, diagnostic, resource }], title: '' }; return codeAction; @@ -325,7 +337,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + if (!this.diagnosticsManager.getDiagnostics(resource).some(x => { if (x === diagnostic) { return false; } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 99b263365ec..89aeb3f8359 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -178,7 +178,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.diagnosticsManager = new DiagnosticsManager('typescript', onCaseInsenitiveFileSystem); this.bufferSyncSupport.onDelete(resource => { this.cancelInflightRequestsForResource(resource); - this.diagnosticsManager.delete(resource); + this.diagnosticsManager.deleteAllDiagnosticsInFile(resource); }, null, this._disposables); this.bufferSyncSupport.onWillChange(resource => { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index ad0e264a688..c765571b9fa 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -320,10 +320,10 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -jsonc-parser@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342" - integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== mime-db@1.52.0: version "1.52.0" diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index f1b76a3e515..5632c7e96d3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -897,6 +897,7 @@ suite('vscode API - workspace', () => { async function test77735(withOpenedEditor: boolean): Promise { const docUriOriginal = await createRandomFile(); const docUriMoved = docUriOriginal.with({ path: `${docUriOriginal.path}.moved` }); + await deleteFile(docUriMoved); // ensure target does not exist if (withOpenedEditor) { const document = await vscode.workspace.openTextDocument(docUriOriginal); diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 9d6fa08317e..8cd7aaf26af 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,10 +2,20 @@ # yarn lockfile v1 -"@parcel/watcher@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.5.tgz#f913a54e1601b0aac972803829b0eece48de215b" - integrity sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw== +"@esbuild/android-arm@0.15.14": + version "0.15.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.14.tgz#5d0027f920eeeac313c01fd6ecb8af50c306a466" + integrity sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA== + +"@esbuild/linux-loong64@0.15.14": + version "0.15.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz#1221684955c44385f8af34f7240088b7dc08d19d" + integrity sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg== + +"@parcel/watcher@2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.7.tgz#c95fe1370e8c6237cb9729c9c075264acc7e21a5" + integrity sha512-gc3hoS6e+2XdIQ4HHljDB1l0Yx2EWh/sBBtCEFNKGSMlwASWeAQsOY/fPbxOBcZ/pg0jBh4Ga+4xHlZc4faAEQ== dependencies: node-addon-api "^3.2.1" node-gyp-build "^4.3.0" @@ -22,10 +32,133 @@ cson-parser@^4.0.9: dependencies: coffeescript "1.12.7" -esbuild@^0.11.12: - version "0.11.23" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8" - integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q== +esbuild-android-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz#114e55b0d58fb7b45d7fa3d93516bd13fc8869cc" + integrity sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg== + +esbuild-android-arm64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz#8541f38a9aacf88e574fb13f5ad4ca51a04c12bb" + integrity sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg== + +esbuild-darwin-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz#b40b334db81ff1e3677a6712b23761748a157c57" + integrity sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg== + +esbuild-darwin-arm64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz#44b5c1477bb7bdb852dd905e906f68765e2828bc" + integrity sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A== + +esbuild-freebsd-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz#8c57315d238690f34b6ed0c94e5cfc04c858247a" + integrity sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA== + +esbuild-freebsd-arm64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz#2e92acca09258daa849e635565f52469266f0b7b" + integrity sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg== + +esbuild-linux-32@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz#ca5ed3e9dff82df486ddde362d7e00775a597dfd" + integrity sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA== + +esbuild-linux-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz#42952e1d08a299d5f573c567639fb37b033befbf" + integrity sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw== + +esbuild-linux-arm64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz#0c0d788099703327ec0ae70758cb2639ef6c5d88" + integrity sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg== + +esbuild-linux-arm@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz#751a5ca5042cd60f669b07c3bcec3dd6c4f8151c" + integrity sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q== + +esbuild-linux-mips64le@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz#da8ac35f2704de0b52bf53a99c12f604fbe9b916" + integrity sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA== + +esbuild-linux-ppc64le@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz#a315b5016917429080c3d32e03319f1ff876ac55" + integrity sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w== + +esbuild-linux-riscv64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz#9f2e0a935e5086d398fc19c7ff5d217bfefe3e12" + integrity sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q== + +esbuild-linux-s390x@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz#53108112faff5a4e1bad17f7b0b0ffa1df4b7efb" + integrity sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw== + +esbuild-netbsd-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz#5330efc41fe4f1c2bab5462bcfe7a4ffce7ba00a" + integrity sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ== + +esbuild-openbsd-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz#ee64944d863e937611fc31adf349e9bb4f5f7eac" + integrity sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA== + +esbuild-sunos-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz#29b0b20de6fe6ef50f9fbe533ec20dc4b595f9aa" + integrity sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q== + +esbuild-windows-32@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz#05e9b159d664809f7a4a8a68ed048d193457b27d" + integrity sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg== + +esbuild-windows-64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz#d5ae086728ab30b72969e40ed0a7a0d9082f2cdd" + integrity sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog== + +esbuild-windows-arm64@0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz#8eb50ab9a0ecaf058593fbad17502749306f801d" + integrity sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw== + +esbuild@^0.15.14: + version "0.15.14" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.14.tgz#09202b811f1710363d5088a3401a351351c79875" + integrity sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ== + optionalDependencies: + "@esbuild/android-arm" "0.15.14" + "@esbuild/linux-loong64" "0.15.14" + esbuild-android-64 "0.15.14" + esbuild-android-arm64 "0.15.14" + esbuild-darwin-64 "0.15.14" + esbuild-darwin-arm64 "0.15.14" + esbuild-freebsd-64 "0.15.14" + esbuild-freebsd-arm64 "0.15.14" + esbuild-linux-32 "0.15.14" + esbuild-linux-64 "0.15.14" + esbuild-linux-arm "0.15.14" + esbuild-linux-arm64 "0.15.14" + esbuild-linux-mips64le "0.15.14" + esbuild-linux-ppc64le "0.15.14" + esbuild-linux-riscv64 "0.15.14" + esbuild-linux-s390x "0.15.14" + esbuild-netbsd-64 "0.15.14" + esbuild-openbsd-64 "0.15.14" + esbuild-sunos-64 "0.15.14" + esbuild-windows-32 "0.15.14" + esbuild-windows-64 "0.15.14" + esbuild-windows-arm64 "0.15.14" fast-plist@0.1.2: version "0.1.2" diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 0a5039769f5..d226e583080 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -10,7 +10,7 @@ import { isEqualOrParent } from 'vs/base/common/extpath'; import { LRUCache } from 'vs/base/common/map'; import { basename, extname, posix, sep } from 'vs/base/common/path'; import { isLinux } from 'vs/base/common/platform'; -import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { escapeRegExpCharacters, ltrim } from 'vs/base/common/strings'; export interface IRelativePattern { @@ -367,7 +367,12 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | // Given we have checked `base` being a parent of `path`, // we can now remove the `base` portion of the `path` // and only match on the remaining path components - return parsedPattern(path.substr(arg2.base.length + 1), basename); + // For that we try to extract the portion of the `path` + // that comes after the `base` portion. We have to account + // for the fact that `base` might end in a path separator + // (https://github.com/microsoft/vscode/issues/162498) + + return parsedPattern(ltrim(path.substr(arg2.base.length), sep), basename); }; // Make sure to preserve associated metadata diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index 679a168332b..bbabd7faf91 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -1091,6 +1091,22 @@ suite('Glob', () => { } }); + test('relative pattern - trailing slash / backslash (#162498)', function () { + if (isWindows) { + let p: glob.IRelativePattern = { base: 'C:\\', pattern: 'foo.cs' }; + assertGlobMatch(p, 'C:\\foo.cs'); + + p = { base: 'C:\\bar\\', pattern: 'foo.cs' }; + assertGlobMatch(p, 'C:\\bar\\foo.cs'); + } else { + let p: glob.IRelativePattern = { base: '/', pattern: 'foo.cs' }; + assertGlobMatch(p, '/foo.cs'); + + p = { base: '/bar/', pattern: 'foo.cs' }; + assertGlobMatch(p, '/bar/foo.cs'); + } + }); + test('pattern with "base" does not explode - #36081', function () { assert.ok(glob.match({ 'base': true }, 'base')); }); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 8c904e25308..da9ec060539 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -80,8 +80,8 @@ import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyn import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; -import { UserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService'; -import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { UserDataProfileStorageService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; @@ -361,7 +361,7 @@ class SharedProcessMain extends Disposable { services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService, undefined, false /* Eagerly cleans up old backups */)); services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService, undefined, true)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService, undefined, false /* Initializes the Sync State */)); - services.set(IUserDataSyncProfilesStorageService, new SyncDescriptor(UserDataSyncProfilesStorageService, undefined, true)); + services.set(IUserDataProfileStorageService, new SyncDescriptor(UserDataProfileStorageService, undefined, true)); services.set(IUserDataSyncResourceProviderService, new SyncDescriptor(UserDataSyncResourceProviderService, undefined, true)); // Terminal diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 63d0b154039..be68de2916f 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -107,7 +107,7 @@ import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } fro import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { UserDataTransientProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataTransientProfilesHandler'; -import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc'; +import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc'; import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; /** diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 7dfaee53aef..3cb36cdcfc1 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -30,9 +30,24 @@ export type ServicesAccessor = InstantiationServicesAccessor; export type IEditorContributionCtor = IConstructorSignature; export type IDiffEditorContributionCtor = IConstructorSignature; +export enum EditorContributionInstantiation { + /** + * The contribution is created eagerly when the {@linkcode ICodeEditor} is instantiated. + */ + Eager, + + /** + * The contribution is created on idle (or when explicitly requested). + * + * Idle contributions cannot participate in saving or restoring of view states. + */ + Idle, +} + export interface IEditorContributionDescription { - id: string; - ctor: IEditorContributionCtor; + readonly id: string; + readonly ctor: IEditorContributionCtor; + readonly instantiation: EditorContributionInstantiation; } export interface IDiffEditorContributionDescription { @@ -481,8 +496,8 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction); } -export function registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void { - EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor); +export function registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }, instantiation = EditorContributionInstantiation.Eager): void { + EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor, instantiation); } export function registerDiffEditorContribution(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void { @@ -533,8 +548,8 @@ class EditorContributionRegistry { this.editorCommands = Object.create(null); } - public registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void { - this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor }); + public registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }, instantiation = EditorContributionInstantiation.Eager): void { + this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor, instantiation }); } public getEditorContributions(): IEditorContributionDescription[] { diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index 2ca1ded563b..48625e8c0a2 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -14,7 +14,7 @@ import { ITextModel } from 'vs/editor/common/model'; import * as languages from 'vs/editor/common/languages'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; -import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { DiffAlgorithmName, IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { IModelService } from 'vs/editor/common/services/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { regExpFlags } from 'vs/base/common/strings'; @@ -26,7 +26,8 @@ import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeText import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; -import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { LineRangeMapping, LineRange, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; /** * Stop syncing a model to the worker if it was not needed for 1 min. @@ -95,8 +96,31 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range)); } - public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise { - return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options)); + public async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { + const result = await this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options, algorithm)); + if (!result) { + return null; + } + // Convert from space efficient JSON data to rich objects. + const diff: IDocumentDiff = { + identical: result.identical, + quitEarly: result.quitEarly, + changes: result.changes.map( + (c) => + new LineRangeMapping( + new LineRange(c[0], c[1]), + new LineRange(c[2], c[3]), + c[4]?.map( + (c) => + new RangeMapping( + new Range(c[0], c[1], c[2], c[3]), + new Range(c[4], c[5], c[6], c[7]) + ) + ) + ) + ), + }; + return diff; } public canComputeDirtyDiff(original: URI, modified: URI): boolean { @@ -492,9 +516,9 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien }); } - public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise { + public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { return this._withSyncedResources([original, modified], /* forceLargeModels */true).then(proxy => { - return proxy.computeDiff(original.toString(), modified.toString(), options); + return proxy.computeDiff(original.toString(), modified.toString(), options, algorithm); }); } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 65ae5f61c9f..53135b6f615 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -18,7 +18,7 @@ import { Disposable, IDisposable, dispose, DisposableStore, DisposableMap } from import { Schemas } from 'vs/base/common/network'; import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; +import { EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view'; @@ -60,6 +60,7 @@ import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; import { IDimension } from 'vs/editor/common/core/dimension'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IdleValue } from 'vs/base/common/async'; let EDITOR_ID = 0; @@ -241,7 +242,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _id: number; private readonly _configuration: IEditorConfiguration; - protected readonly _contributions = new DisposableMap(); + protected readonly _contributions = new DisposableMap>(); protected readonly _actions = new Map(); // --- Members logically associated to a model @@ -338,8 +339,25 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE continue; } try { - const contribution = this._instantiationService.createInstance(desc.ctor, this); - this._contributions.set(desc.id, contribution); + if (desc.instantiation === EditorContributionInstantiation.Idle) { + const contribution = new IdleValue(() => { + try { + // Replace the original entry in _contributions with the resolved contribution + const instance = this._instantiationService.createInstance(desc.ctor, this); + this._contributions.set(desc.id, instance); + return instance; + } catch (err) { + // In case of an exception, we delete the idle value from _contributions + onUnexpectedError(err); + this._contributions.deleteAndDispose(desc.id); + return null; + } + }); + this._contributions.set(desc.id, contribution); + } else { + const contribution = this._instantiationService.createInstance(desc.ctor, this); + this._contributions.set(desc.id, contribution); + } } catch (err) { onUnexpectedError(err); } @@ -993,7 +1011,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const contributionsState: { [key: string]: any } = {}; for (const [id, contribution] of this._contributions) { - if (typeof contribution.saveViewState === 'function') { + if (!(contribution instanceof IdleValue) && typeof contribution.saveViewState === 'function') { contributionsState[id] = contribution.saveViewState(); } } @@ -1025,7 +1043,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const contributionsState = codeEditorState.contributionsState || {}; for (const [id, contribution] of this._contributions) { - if (typeof contribution.restoreViewState === 'function') { + if (!(contribution instanceof IdleValue) && typeof contribution.restoreViewState === 'function') { contribution.restoreViewState(contributionsState[id]); } } @@ -1045,7 +1063,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public getContribution(id: string): T | null { - return (this._contributions.get(id) || null); + const entry = this._contributions.get(id); + if (!entry) { + return null; + } + + return (entry instanceof IdleValue ? entry.value : entry) as T | null; } public getActions(): editorCommon.IEditorAction[] { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index ed85bb8681f..216e31ab49e 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -3,61 +3,60 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/diffEditor'; -import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; +import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode'; +import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; +import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; import * as assert from 'vs/base/common/assert'; -import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState, Orientation } from 'vs/base/browser/ui/sash/sash'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { Codicon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; +import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { Constants } from 'vs/base/common/uint'; import { URI } from 'vs/base/common/uri'; +import 'vs/css!./media/diffEditor'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; +import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; +import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffReview } from 'vs/editor/browser/widget/diffReview'; -import { IDiffEditorOptions, EditorLayoutInfo, EditorOption, EditorOptions, EditorFontLigatures, stringSet as validateStringSetOption, boolean as validateBooleanOption, ValidDiffEditorBaseOptions, clampedInt } from 'vs/editor/common/config/editorOptions'; +import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; +import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider'; +import { boolean as validateBooleanOption, clampedInt, EditorFontLigatures, EditorLayoutInfo, EditorOption, EditorOptions, IDiffEditorOptions, stringSet as validateStringSetOption, ValidDiffEditorBaseOptions } from 'vs/editor/common/config/editorOptions'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { IDimension } from 'vs/editor/common/core/dimension'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { StringBuilder } from 'vs/editor/common/core/stringBuilder'; +import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager'; +import { ILineBreaksComputer } from 'vs/editor/common/modelLineProjectionData'; +import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { IEditorWhitespace, InlineDecoration, InlineDecorationType, IViewModel, ViewLineRenderingData } from 'vs/editor/common/viewModel'; +import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager'; +import * as nls from 'vs/nls'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffDiagonalFill, diffInsertedLineGutter, diffRemovedLineGutter, diffInsertedLine, diffRemovedLine, diffOverviewRulerInserted, diffOverviewRulerRemoved } from 'vs/platform/theme/common/colorRegistry'; -import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { Constants } from 'vs/base/common/uint'; -import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; -import { onUnexpectedError } from 'vs/base/common/errors'; import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; -import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; -import { Codicon } from 'vs/base/common/codicons'; -import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor'; -import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; -import { FontInfo } from 'vs/editor/common/config/fontInfo'; +import { defaultInsertColor, defaultRemoveColor, diffBorder, diffDiagonalFill, diffInserted, diffInsertedLine, diffInsertedLineGutter, diffInsertedOutline, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved, diffRemovedLine, diffRemovedLineGutter, diffRemovedOutline, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -import { ILineBreaksComputer } from 'vs/editor/common/modelLineProjectionData'; -import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; -import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; -import { IDimension } from 'vs/editor/common/core/dimension'; import { isHighContrast } from 'vs/platform/theme/common/theme'; -import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider'; -import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider'; +import { getThemeTypeSelector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; export interface IDiffCodeEditorWidgetOptions { originalEditor?: ICodeEditorWidgetOptions; @@ -227,7 +226,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly _updateDecorationsRunner: RunOnceScheduler; - private readonly _documentDiffProvider: IDocumentDiffProvider; + private readonly _documentDiffProvider: WorkerBasedDocumentDiffProvider; private readonly _contextKeyService: IContextKeyService; private readonly _instantiationService: IInstantiationService; private readonly _codeEditorService: ICodeEditorService; @@ -251,7 +250,9 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE ) { super(); - this._documentDiffProvider = instantiationService.createInstance(WorkerBasedDocumentDiffProvider); + this._documentDiffProvider = this._register(instantiationService.createInstance(WorkerBasedDocumentDiffProvider, options)); + this._register(this._documentDiffProvider.onDidChange(e => this._beginUpdateDecorationsSoon())); + this._codeEditorService = codeEditorService; this._contextKeyService = this._register(contextKeyService.createScoped(domElement)); this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])); @@ -762,7 +763,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._options = newOptions; const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators || changed.renderMarginRevertIcon); - const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize || changed.diffAlgorithm)); + const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize)); + this._documentDiffProvider.setOptions(newOptions); if (beginUpdateDecorations) { this._beginUpdateDecorations(); @@ -1092,7 +1094,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } private _beginUpdateDecorations(): void { - this._beginUpdateDecorationsTimeout = -1; + if (this._beginUpdateDecorationsTimeout !== -1) { + // Cancel any pending requests in case this method is called directly + window.clearTimeout(this._beginUpdateDecorationsTimeout); + this._beginUpdateDecorationsTimeout = -1; + } const currentOriginalModel = this._originalEditor.getModel(); const currentModifiedModel = this._modifiedEditor.getModel(); if (!currentOriginalModel || !currentModifiedModel) { @@ -1126,8 +1132,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._setState(editorBrowser.DiffEditorState.ComputingDiff); this._documentDiffProvider.computeDiff(currentOriginalModel, currentModifiedModel, { ignoreTrimWhitespace: this._options.ignoreTrimWhitespace, - maxComputationTime: this._options.maxComputationTime, - diffAlgorithm: this._options.diffAlgorithm, + maxComputationTimeMs: this._options.maxComputationTime, }).then(result => { if (currentToken === this._diffComputationToken && currentOriginalModel === this._originalEditor.getModel() diff --git a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts index a05bdb7224b..3f837f75657 100644 --- a/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts +++ b/src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts @@ -3,43 +3,63 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { LineRange, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; -import { Range } from 'vs/editor/common/core/range'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { IDocumentDiff, IDocumentDiffProvider, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { ITextModel } from 'vs/editor/common/model'; +import { DiffAlgorithmName, IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; + +export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, IDisposable { + private onDidChangeEventEmitter = new Emitter(); + public readonly onDidChange: Event = this.onDidChangeEventEmitter.event; + + private diffAlgorithm: DiffAlgorithmName | IDocumentDiffProvider = 'smart'; + private diffAlgorithmOnDidChangeSubscription: IDisposable | undefined = undefined; -export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider { constructor( + options: IWorkerBasedDocumentDiffProviderOptions, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, ) { + this.setOptions(options); + } + + public dispose(): void { + this.diffAlgorithmOnDidChangeSubscription?.dispose(); } async computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions): Promise { - const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options); + if (typeof this.diffAlgorithm !== 'string') { + return this.diffAlgorithm.computeDiff(original, modified, options); + } + + const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options, this.diffAlgorithm); if (!result) { throw new Error('no diff result available'); } - // Convert from space efficient JSON data to rich objects. - const diff: IDocumentDiff = { - identical: result.identical, - quitEarly: result.quitEarly, - changes: result.changes.map( - (c) => - new LineRangeMapping( - new LineRange(c[0], c[1]), - new LineRange(c[2], c[3]), - c[4]?.map( - (c) => - new RangeMapping( - new Range(c[0], c[1], c[2], c[3]), - new Range(c[4], c[5], c[6], c[7]) - ) - ) - ) - ), - }; - return diff; + return result; + } + + public setOptions(newOptions: IWorkerBasedDocumentDiffProviderOptions): void { + let didChange = false; + if (newOptions.diffAlgorithm) { + if (this.diffAlgorithm !== newOptions.diffAlgorithm) { + this.diffAlgorithmOnDidChangeSubscription?.dispose(); + this.diffAlgorithmOnDidChangeSubscription = undefined; + + this.diffAlgorithm = newOptions.diffAlgorithm; + if (typeof newOptions.diffAlgorithm !== 'string') { + this.diffAlgorithmOnDidChangeSubscription = newOptions.diffAlgorithm.onDidChange(() => this.onDidChangeEventEmitter.fire()); + } + didChange = true; + } + } + if (didChange) { + this.onDidChangeEventEmitter.fire(); + } } } + +interface IWorkerBasedDocumentDiffProviderOptions { + readonly diffAlgorithm?: 'smart' | 'experimental' | IDocumentDiffProvider; +} diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2629ba6ef14..f4e6eb27297 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -15,6 +15,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults'; +import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider'; //#region typed options @@ -739,7 +740,7 @@ export interface IDiffEditorBaseOptions { /** * Diff Algorithm */ - diffAlgorithm?: 'smart' | 'experimental'; + diffAlgorithm?: 'smart' | 'experimental' | IDocumentDiffProvider; } /** diff --git a/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts b/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts index ae370b5a693..81d9c9acd06 100644 --- a/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts +++ b/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts @@ -85,11 +85,11 @@ export function shiftSequenceDiffs(sequence1: ISequence, sequence2: ISequence, s const diff = sequenceDiffs[i]; if (diff.seq1Range.isEmpty) { const seq2PrevEndExclusive = (i > 0 ? sequenceDiffs[i - 1].seq2Range.endExclusive : -1); - const seq2NextStart = (i + 1 < sequenceDiffs.length ? sequenceDiffs[i + 1].seq2Range.start : sequence2.length + 1); + const seq2NextStart = (i + 1 < sequenceDiffs.length ? sequenceDiffs[i + 1].seq2Range.start : sequence2.length); sequenceDiffs[i] = shiftDiffToBetterPosition(diff, sequence1, sequence2, seq2NextStart, seq2PrevEndExclusive); } else if (diff.seq2Range.isEmpty) { const seq1PrevEndExclusive = (i > 0 ? sequenceDiffs[i - 1].seq1Range.endExclusive : -1); - const seq1NextStart = (i + 1 < sequenceDiffs.length ? sequenceDiffs[i + 1].seq1Range.start : sequence1.length + 1); + const seq1NextStart = (i + 1 < sequenceDiffs.length ? sequenceDiffs[i + 1].seq1Range.start : sequence1.length); sequenceDiffs[i] = shiftDiffToBetterPosition(diff.reverse(), sequence2, sequence1, seq1NextStart, seq1PrevEndExclusive).reverse(); } } @@ -102,7 +102,7 @@ function shiftDiffToBetterPosition(diff: SequenceDiff, sequence1: ISequence, seq // don't touch previous or next! let deltaBefore = 1; - while (diff.seq1Range.start - deltaBefore > seq2PrevEndExclusive && + while (diff.seq2Range.start - deltaBefore > seq2PrevEndExclusive && sequence2.getElement(diff.seq2Range.start - deltaBefore) === sequence2.getElement(diff.seq2Range.endExclusive - deltaBefore) && deltaBefore < maxShiftLimit) { deltaBefore++; @@ -110,7 +110,7 @@ function shiftDiffToBetterPosition(diff: SequenceDiff, sequence1: ISequence, seq deltaBefore--; let deltaAfter = 1; - while (diff.seq1Range.start + deltaAfter < seq2NextStart && + while (diff.seq2Range.start + deltaAfter < seq2NextStart && sequence2.getElement(diff.seq2Range.start + deltaAfter) === sequence2.getElement(diff.seq2Range.endExclusive + deltaAfter) && deltaAfter < maxShiftLimit) { deltaAfter++; diff --git a/src/vs/editor/common/diff/documentDiffProvider.ts b/src/vs/editor/common/diff/documentDiffProvider.ts index 32d289ec399..c1023cadeec 100644 --- a/src/vs/editor/common/diff/documentDiffProvider.ts +++ b/src/vs/editor/common/diff/documentDiffProvider.ts @@ -3,21 +3,57 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; import { LineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; import { ITextModel } from 'vs/editor/common/model'; +/** + * A document diff provider computes the diff between two text models. + */ export interface IDocumentDiffProvider { + /** + * Computes the diff between the text models `original` and `modified`. + */ computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions): Promise; + + /** + * Is fired when settings of the diff algorithm change that could alter the result of the diffing computation. + * Any user of this provider should recompute the diff when this event is fired. + */ + onDidChange: Event; } +/** + * Options for the diff computation. + */ export interface IDocumentDiffProviderOptions { + /** + * When set to true, the diff should ignore whitespace changes.i + */ ignoreTrimWhitespace: boolean; - maxComputationTime: number; - diffAlgorithm: 'smart' | 'experimental'; + + /** + * A diff computation should throw if it takes longer than this value. + */ + maxComputationTimeMs: number; } +/** + * Represents a diff between two text models. + */ export interface IDocumentDiff { + /** + * If true, both text models are identical (byte-wise). + */ readonly identical: boolean; + + /** + * If true, the diff computation timed out and the diff might not be accurate. + */ readonly quitEarly: boolean; + + /** + * Maps all modified line ranges in the original to the corresponding line ranges in the modified text model. + */ readonly changes: LineRangeMapping[]; } diff --git a/src/vs/editor/common/diff/linesDiffComputer.ts b/src/vs/editor/common/diff/linesDiffComputer.ts index 7d8677e29d5..9a9961f77cd 100644 --- a/src/vs/editor/common/diff/linesDiffComputer.ts +++ b/src/vs/editor/common/diff/linesDiffComputer.ts @@ -10,8 +10,8 @@ export interface ILinesDiffComputer { } export interface ILinesDiffComputerOptions { - ignoreTrimWhitespace: boolean; - maxComputationTime: number; + readonly ignoreTrimWhitespace: boolean; + readonly maxComputationTimeMs: number; } export interface ILinesDiff { @@ -19,58 +19,125 @@ export interface ILinesDiff { readonly changes: LineRangeMapping[]; } +/** + * Maps a line range in the original text model to a line range in the modified text model. + */ export class LineRangeMapping { - constructor( - readonly originalRange: LineRange, - readonly modifiedRange: LineRange, - /** - * Meaning of `undefined` unclear. - */ - readonly innerChanges: RangeMapping[] | undefined, - ) { } + /** + * The line range in the original text model. + */ + public readonly originalRange: LineRange; - toString(): string { - return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`; + /** + * The line range in the modified text model. + */ + public readonly modifiedRange: LineRange; + + /** + * If inner changes have not been computed, this is set to undefined. + * Otherwise, it represents the character-level diff in this line range. + * The original range of each range mapping should be contained in the original line range (same for modified). + * Must not be an empty array. + */ + public readonly innerChanges: RangeMapping[] | undefined; + + constructor( + originalRange: LineRange, + modifiedRange: LineRange, + innerChanges: RangeMapping[] | undefined, + ) { + this.originalRange = originalRange; + this.modifiedRange = modifiedRange; + this.innerChanges = innerChanges; } -} -export class RangeMapping { - constructor( - readonly originalRange: Range, - readonly modifiedRange: Range, - ) { } - - toString(): string { + public toString(): string { return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`; } } /** - * 1-based. -*/ -export class LineRange { - constructor(public readonly startLineNumber: number, public readonly endLineNumberExclusive: number) { } + * Maps a range in the original text model to a range in the modified text model. + */ +export class RangeMapping { + /** + * The original range. + */ + readonly originalRange: Range; + /** + * The modified range. + */ + readonly modifiedRange: Range; + + constructor( + originalRange: Range, + + modifiedRange: Range, + ) { + this.originalRange = originalRange; + this.modifiedRange = modifiedRange; + } + + public toString(): string { + return `{${this.originalRange.toString()}->${this.modifiedRange.toString()}}`; + } +} + +/** + * A range of lines (1-based). + */ +export class LineRange { + /** + * The start line number. + */ + public readonly startLineNumber: number; + + /** + * The end line number (exclusive). + */ + public readonly endLineNumberExclusive: number; + + constructor( + startLineNumber: number, + endLineNumberExclusive: number, + ) { + this.startLineNumber = startLineNumber; + this.endLineNumberExclusive = endLineNumberExclusive; + } + + /** + * Indicates if this line range is empty. + */ get isEmpty(): boolean { return this.startLineNumber === this.endLineNumberExclusive; } + /** + * Moves this line range by the given offset of line numbers. + */ public delta(offset: number): LineRange { return new LineRange(this.startLineNumber + offset, this.endLineNumberExclusive + offset); } + /** + * The number of lines this line range spans. + */ public get length(): number { return this.endLineNumberExclusive - this.startLineNumber; } - toString(): string { - return `[${this.startLineNumber},${this.endLineNumberExclusive})`; - } - + /** + * Creates a line range that combines this and the given line range. + */ public join(other: LineRange): LineRange { return new LineRange( Math.min(this.startLineNumber, other.startLineNumber), Math.max(this.endLineNumberExclusive, other.endLineNumberExclusive) ); } + + public toString(): string { + return `[${this.startLineNumber},${this.endLineNumberExclusive})`; + } } diff --git a/src/vs/editor/common/diff/smartLinesDiffComputer.ts b/src/vs/editor/common/diff/smartLinesDiffComputer.ts index ecfadac0dad..e511d507cf7 100644 --- a/src/vs/editor/common/diff/smartLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/smartLinesDiffComputer.ts @@ -15,7 +15,7 @@ const MINIMUM_MATCHING_CHARACTER_LENGTH = 3; export class SmartLinesDiffComputer implements ILinesDiffComputer { computeDiff(originalLines: string[], modifiedLines: string[], options: ILinesDiffComputerOptions): ILinesDiff { const diffComputer = new DiffComputer(originalLines, modifiedLines, { - maxComputationTime: options.maxComputationTime, + maxComputationTime: options.maxComputationTimeMs, shouldIgnoreTrimWhitespace: options.ignoreTrimWhitespace, shouldComputeCharChanges: true, shouldMakePrettyDiff: true, diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts index e8dfd6f8f9f..cbabd157db0 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/ast.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BugIndicatingError } from 'vs/base/common/errors'; import { CursorColumns } from 'vs/editor/common/core/cursorColumns'; import { BracketKind } from 'vs/editor/common/languages/supports/languageBracketsConfiguration'; import { ITextModel } from 'vs/editor/common/model'; @@ -125,7 +126,7 @@ export class PairAstNode extends BaseAstNode { * Avoid using this property, it allocates an array! */ public get children() { - const result = new Array(); + const result: AstNode[] = []; result.push(this.openingBracket); if (this.child) { result.push(this.child); @@ -295,10 +296,19 @@ export abstract class ListAstNode extends BaseAstNode { return false; } + if (this.childrenLength === 0) { + // Don't reuse empty lists. + return false; + } + let lastChild: ListAstNode = this; - let lastLength: number; - while (lastChild.kind === AstNodeKind.List && (lastLength = lastChild.childrenLength) > 0) { - lastChild = lastChild.getChild(lastLength! - 1) as ListAstNode; + while (lastChild.kind === AstNodeKind.List) { + const lastLength = lastChild.childrenLength; + if (lastLength === 0) { + // Empty lists should never be contained in other lists. + throw new BugIndicatingError(); + } + lastChild = lastChild.getChild(lastLength - 1) as ListAstNode; } return lastChild.canBeReused(openBracketIds); @@ -324,7 +334,7 @@ export abstract class ListAstNode extends BaseAstNode { } public flattenLists(): ListAstNode { - const items = new Array(); + const items: AstNode[] = []; for (const c of this.children) { const normalized = c.flattenLists(); if (normalized.kind === AstNodeKind.List) { diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts index 7d9e19c05d8..162509bed20 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper.ts @@ -26,7 +26,6 @@ export class BeforeEditPositionMapper { */ constructor( edits: readonly TextEditInfo[], - private readonly documentLength: Length, ) { this.edits = edits.map(edit => TextEditInfoCache.from(edit)); } @@ -41,12 +40,16 @@ export class BeforeEditPositionMapper { /** * @param offset Must be equal to or greater than the last offset this method has been called with. + * Returns null if there is no edit anymore. */ - getDistanceToNextChange(offset: Length): Length { + getDistanceToNextChange(offset: Length): Length | null { this.adjustNextEdit(offset); const nextEdit = this.edits[this.nextEditIdx]; - const nextChangeOffset = nextEdit ? this.translateOldToCur(nextEdit.offsetObj) : this.documentLength; + const nextChangeOffset = nextEdit ? this.translateOldToCur(nextEdit.offsetObj) : null; + if (nextChangeOffset === null) { + return null; + } return lengthDiffNonNegative(offset, nextChangeOffset); } diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts index 62d82fdd711..1a85bec9ec6 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts @@ -21,6 +21,7 @@ import { FastTokenizer, TextBufferTokenizer } from './tokenizer'; import { BackgroundTokenizationState } from 'vs/editor/common/tokenizationTextModelPart'; import { Position } from 'vs/editor/common/core/position'; import { CallbackIterable } from 'vs/base/common/arrays'; +import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos'; export class BracketPairsTree extends Disposable { private readonly didChangeEmitter = new Emitter(); @@ -45,6 +46,8 @@ export class BracketPairsTree extends Disposable { } public readonly onDidChange = this.didChangeEmitter.event; + private queuedTextEditsForInitialAstWithoutTokens: TextEditInfo[] = []; + private queuedTextEdits: TextEditInfo[] = []; public constructor( private readonly textModel: TextModel, @@ -90,7 +93,9 @@ export class BracketPairsTree extends Disposable { toLength(r.toLineNumber - r.fromLineNumber + 1, 0) ) ); - this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false); + + this.handleEdits(edits, true); + if (!this.initialAstWithoutTokens) { this.didChangeEmitter.fire(); } @@ -106,14 +111,34 @@ export class BracketPairsTree extends Disposable { ); }).reverse(); - this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens, false); - if (this.initialAstWithoutTokens) { - this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(edits, this.initialAstWithoutTokens, false); + this.handleEdits(edits, false); + } + + private handleEdits(edits: TextEditInfo[], tokenChange: boolean): void { + // Lazily queue the edits and only apply them when the tree is accessed. + const result = combineTextEditInfos(this.queuedTextEdits, edits); + + this.queuedTextEdits = result; + if (this.initialAstWithoutTokens && !tokenChange) { + this.queuedTextEditsForInitialAstWithoutTokens = combineTextEditInfos(this.queuedTextEditsForInitialAstWithoutTokens, edits); } } //#endregion + private flushQueue() { + if (this.queuedTextEdits.length > 0) { + this.astWithTokens = this.parseDocumentFromTextBuffer(this.queuedTextEdits, this.astWithTokens, false); + this.queuedTextEdits = []; + } + if (this.queuedTextEditsForInitialAstWithoutTokens.length > 0) { + if (this.initialAstWithoutTokens) { + this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(this.queuedTextEditsForInitialAstWithoutTokens, this.initialAstWithoutTokens, false); + } + this.queuedTextEditsForInitialAstWithoutTokens = []; + } + } + /** * @pure (only if isPure = true) */ @@ -127,6 +152,8 @@ export class BracketPairsTree extends Disposable { } public getBracketsInRange(range: Range): CallbackIterable { + this.flushQueue(); + const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1); const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1); return new CallbackIterable(cb => { @@ -136,6 +163,8 @@ export class BracketPairsTree extends Disposable { } public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): CallbackIterable { + this.flushQueue(); + const startLength = positionToLength(range.getStartPosition()); const endLength = positionToLength(range.getEndPosition()); @@ -147,11 +176,15 @@ export class BracketPairsTree extends Disposable { } public getFirstBracketAfter(position: Position): IFoundBracket | null { + this.flushQueue(); + const node = this.initialAstWithoutTokens || this.astWithTokens!; return getFirstBracketAfter(node, lengthZero, node.length, positionToLength(position)); } public getFirstBracketBefore(position: Position): IFoundBracket | null { + this.flushQueue(); + const node = this.initialAstWithoutTokens || this.astWithTokens!; return getFirstBracketBefore(node, lengthZero, node.length, positionToLength(position)); } diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos.ts new file mode 100644 index 00000000000..218ff36b6a3 --- /dev/null +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ArrayQueue } from 'vs/base/common/arrays'; +import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; +import { Length, lengthAdd, lengthDiffNonNegative, lengthEquals, lengthIsZero, lengthLessThanEqual, lengthZero, sumLengths } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; + +export function combineTextEditInfos(textEditInfoFirst: TextEditInfo[], textEditInfoSecond: TextEditInfo[]): TextEditInfo[] { + if (textEditInfoFirst.length === 0) { + return textEditInfoSecond; + } + + // s0: State before any edits + const firstMap = new ArrayQueue(toTextMap(textEditInfoFirst)); + // s1: State after first edit, but before second edit + const secondMap = toTextMap(textEditInfoSecond); + // s2: State after both edits + + // If set, we are in an edit + let remainingS0Length: Length | undefined = undefined; + let remainingS1Length: Length = lengthZero; + + /** + * @param s1Length Use undefined for length "infinity" + */ + function readPartialS0Map(s1Length: Length | undefined): TextMapping[] { + const result: TextMapping[] = []; + + while (true) { + if ((remainingS0Length !== undefined && !lengthIsZero(remainingS0Length)) || !lengthIsZero(remainingS1Length)) { + let readS1Length: Length; + if (s1Length !== undefined && lengthLessThanEqual(s1Length, remainingS1Length)) { + // remaining satisfies request + readS1Length = s1Length; + remainingS1Length = lengthDiffNonNegative(s1Length, remainingS1Length); + s1Length = lengthZero; + } else { + // Read all of remaining, potentially even more + readS1Length = remainingS1Length; + if (s1Length !== undefined) { + s1Length = lengthDiffNonNegative(remainingS1Length, s1Length); + } + remainingS1Length = lengthZero; + } + + if (remainingS0Length === undefined) { + // unchanged area + result.push({ + oldLength: readS1Length, + newLength: undefined + }); + } else { + // We eagerly consume all of the old length, even if + // we are in an edit and only consume it partially. + result.push({ + oldLength: remainingS0Length, + newLength: readS1Length + }); + remainingS0Length = lengthZero; + } + } + + if (s1Length !== undefined && lengthIsZero(s1Length)) { + break; + } + + const item = firstMap.dequeue(); + if (!item) { + if (s1Length !== undefined) { + result.push({ + oldLength: s1Length, + newLength: undefined, + }); + } + break; + } + if (item.newLength === undefined) { + remainingS1Length = item.oldLength; + remainingS0Length = undefined; + } else { + remainingS0Length = item.oldLength; + remainingS1Length = item.newLength; + } + } + + return result; + } + + const result: TextEditInfo[] = []; + + function push(startOffset: Length, endOffset: Length, newLength: Length) { + if (result.length > 0 && lengthEquals(result[result.length - 1].endOffset, startOffset)) { + const lastResult = result[result.length - 1]; + result[result.length - 1] = new TextEditInfo(lastResult.startOffset, endOffset, lengthAdd(lastResult.newLength, newLength)); + } else { + result.push({ startOffset, endOffset, newLength }); + } + } + + let s0offset = lengthZero; + for (const s2 of secondMap) { + const s0ToS1Map = readPartialS0Map(s2.oldLength); + if (s2.newLength !== undefined) { + // This is an edit + const s0Length = sumLengths(s0ToS1Map, s => s.oldLength); + const s0EndOffset = lengthAdd(s0offset, s0Length); + push(s0offset, s0EndOffset, s2.newLength); + s0offset = s0EndOffset; + } else { + // We are in an unchanged area + for (const s1 of s0ToS1Map) { + const s0startOffset = s0offset; + s0offset = lengthAdd(s0offset, s1.oldLength); + + if (s1.newLength !== undefined) { + push(s0startOffset, s0offset, s1.newLength); + } + } + } + } + + const s0ToS1Map = readPartialS0Map(undefined); + for (const s1 of s0ToS1Map) { + const s0startOffset = s0offset; + s0offset = lengthAdd(s0offset, s1.oldLength); + + if (s1.newLength !== undefined) { + push(s0startOffset, s0offset, s1.newLength); + } + } + + return result; +} + +interface TextMapping { + oldLength: Length; + + /** + * If set, this mapping represents an edit. + * If not set, this mapping represents an unchanged region (for which the new length equals the old length). + */ + newLength?: Length; +} + +function toTextMap(textEditInfos: TextEditInfo[]): TextMapping[] { + const result: TextMapping[] = []; + let lastOffset = lengthZero; + for (const textEditInfo of textEditInfos) { + const spaceLength = lengthDiffNonNegative(lastOffset, textEditInfo.startOffset); + if (!lengthIsZero(spaceLength)) { + result.push({ oldLength: spaceLength }); + } + + const oldLength = lengthDiffNonNegative(textEditInfo.startOffset, textEditInfo.endOffset); + result.push({ oldLength, newLength: textEditInfo.newLength }); + lastOffset = textEditInfo.endOffset; + } + return result; +} diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees.ts index 98ae08e64c3..aab40426436 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/concat23Trees.ts @@ -108,7 +108,7 @@ function concat(node1: AstNode, node2: AstNode): AstNode { function append(list: ListAstNode, nodeToAppend: AstNode): AstNode { list = list.toMutable() as ListAstNode; let curNode: AstNode = list; - const parents = new Array(); + const parents: ListAstNode[] = []; let nodeToAppendOfCorrectHeight: AstNode | undefined; while (true) { // assert nodeToInsert.listHeight <= curNode.listHeight @@ -157,7 +157,7 @@ function append(list: ListAstNode, nodeToAppend: AstNode): AstNode { function prepend(list: ListAstNode, nodeToAppend: AstNode): AstNode { list = list.toMutable() as ListAstNode; let curNode: AstNode = list; - const parents = new Array(); + const parents: ListAstNode[] = []; // assert nodeToInsert.listHeight <= curNode.listHeight while (nodeToAppend.listHeight !== curNode.listHeight) { // assert 0 <= nodeToInsert.listHeight < curNode.listHeight diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts index ef87ef77d68..0b72d9dbf31 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts @@ -100,10 +100,12 @@ export function lengthIsZero(length: Length): boolean { /* * We have 52 bits available in a JS number. * We use the upper 26 bits to store the line and the lower 26 bits to store the column. - * - * Set boolean to `true` when debugging, so that debugging is easier. */ -const factor = /* is debug: */ false ? 100000 : 2 ** 26; +///* +const factor = 2 ** 26; +/*/ +const factor = 1000000; +// */ export function toLength(lineCount: number, columnCount: number): Length { // llllllllllllllllllllllllllcccccccccccccccccccccccccc (52 bits) @@ -138,9 +140,17 @@ export function lengthGetColumnCountIfZeroLineCount(length: Length): number { // [10 lines, 5 cols] + [20 lines, 3 cols] = [30 lines, 3 cols] export function lengthAdd(length1: Length, length2: Length): Length; export function lengthAdd(l1: any, l2: any): Length { - return ((l2 < factor) - ? (l1 + l2) // l2 is the amount of columns (zero line count). Keep the column count from l1. - : (l1 - (l1 % factor) + l2)); // l1 - (l1 % factor) equals toLength(l1.lineCount, 0) + let r = l1 + l2; + if (l2 >= factor) { r = r - (l1 % factor); } + return r; +} + +export function sumLengths(items: readonly T[], lengthFn: (item: T) => Length): Length { + return items.reduce((a, b) => lengthAdd(a, lengthFn(b)), lengthZero); +} + +export function lengthEquals(length1: Length, length2: Length): boolean { + return length1 === length2; } /** diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts index fb4edcfb866..8758dfb3ea0 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/parser.ts @@ -53,7 +53,7 @@ class Parser { } this.oldNodeReader = oldNode ? new NodeReader(oldNode) : undefined; - this.positionMapper = new BeforeEditPositionMapper(edits, tokenizer.length); + this.positionMapper = new BeforeEditPositionMapper(edits); } parseDocument(): AstNode { @@ -71,19 +71,24 @@ class Parser { private parseList( openedBracketIds: SmallImmutableSet, ): AstNode | null { - const items = new Array(); + const items: AstNode[] = []; while (true) { - const token = this.tokenizer.peek(); - if ( - !token || - (token.kind === TokenKind.ClosingBracket && - token.bracketIds.intersects(openedBracketIds)) - ) { - break; + let child = this.tryReadChildFromCache(openedBracketIds); + + if (!child) { + const token = this.tokenizer.peek(); + if ( + !token || + (token.kind === TokenKind.ClosingBracket && + token.bracketIds.intersects(openedBracketIds)) + ) { + break; + } + + child = this.parseChild(openedBracketIds); } - const child = this.parseChild(openedBracketIds); if (child.kind === AstNodeKind.List && child.childrenLength === 0) { continue; } @@ -96,14 +101,14 @@ class Parser { return result; } - private parseChild( - openedBracketIds: SmallImmutableSet, - ): AstNode { + private tryReadChildFromCache(openedBracketIds: SmallImmutableSet): AstNode | undefined { if (this.oldNodeReader) { const maxCacheableLength = this.positionMapper.getDistanceToNextChange(this.tokenizer.offset); - if (!lengthIsZero(maxCacheableLength)) { + if (maxCacheableLength === null || !lengthIsZero(maxCacheableLength)) { const cachedNode = this.oldNodeReader.readLongestNodeAt(this.positionMapper.getOffsetBeforeChange(this.tokenizer.offset), curNode => { - if (!lengthLessThan(curNode.length, maxCacheableLength)) { + // The edit could extend the ending token, thus we cannot re-use nodes that touch the edit. + // If there is no edit anymore, we can re-use the node in any case. + if (maxCacheableLength !== null && !lengthLessThan(curNode.length, maxCacheableLength)) { // Either the node contains edited text or touches edited text. // In the latter case, brackets might have been extended (`end` -> `ending`), so even touching nodes cannot be reused. return false; @@ -119,7 +124,12 @@ class Parser { } } } + return undefined; + } + private parseChild( + openedBracketIds: SmallImmutableSet, + ): AstNode { this._itemsConstructed++; const token = this.tokenizer.read()!; diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts index a0d4261e9f7..c324c97f0a7 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const emptyArr = new Array(); +const emptyArr: number[] = []; /** * Represents an immutable set that works best for a small number of elements (less than 32). @@ -86,7 +86,7 @@ export class SmallImmutableSet { } // This can be optimized, but it's not a common case - const newItems = new Array(); + const newItems: number[] = []; for (let i = 0; i < Math.max(this.additionalItems.length, other.additionalItems.length); i++) { const item1 = this.additionalItems[i] || 0; const item2 = other.additionalItems[i] || 0; diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts index 7cfe9631770..c5d8898ded7 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/tokenizer.ts @@ -81,7 +81,7 @@ export class TextBufferTokenizer implements Tokenizer { } get length() { - return toLength(this.textBufferLineCount, this.textBufferLastLineLength); + return toLength(this.textBufferLineCount - 1, this.textBufferLastLineLength); } getText() { @@ -143,7 +143,9 @@ class NonPeekableTextBufferTokenizer { // We must not jump into a token! if (lineIdx === this.lineIdx) { this.lineCharOffset = column; - this.lineTokenOffset = this.lineCharOffset === 0 ? 0 : this.lineTokens!.findTokenIndexAtOffset(this.lineCharOffset); + if (this.line !== null) { + this.lineTokenOffset = this.lineCharOffset === 0 ? 0 : this.lineTokens!.findTokenIndexAtOffset(this.lineCharOffset); + } } else { this.lineIdx = lineIdx; this.lineCharOffset = column; @@ -291,7 +293,7 @@ export class FastTokenizer implements Tokenizer { let lastTokenEndOffset = 0; let lastTokenEndLine = 0; - const smallTextTokens0Line = new Array(); + const smallTextTokens0Line: Token[] = []; for (let i = 0; i < 60; i++) { smallTextTokens0Line.push( new Token( @@ -301,7 +303,7 @@ export class FastTokenizer implements Tokenizer { ); } - const smallTextTokens1Line = new Array(); + const smallTextTokens1Line: Token[] = []; for (let i = 0; i < 60; i++) { smallTextTokens1Line.push( new Token( diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 899050d252c..8864a766527 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -16,7 +16,7 @@ import { ensureValidWordDefinition, getWordAtText, IWordAtPosition } from 'vs/ed import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/languages'; import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/languages/linkComputer'; import { BasicInplaceReplace } from 'vs/editor/common/languages/supports/inplaceReplaceSupport'; -import { IDiffComputationResult, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { DiffAlgorithmName, IDiffComputationResult, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { createMonacoBaseAPI } from 'vs/editor/common/services/editorBaseApi'; import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -384,18 +384,18 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable { // ---- BEGIN diff -------------------------------------------------------------------------- - public async computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions): Promise { + public async computeDiff(originalUrl: string, modifiedUrl: string, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { const original = this._getModel(originalUrl); const modified = this._getModel(modifiedUrl); if (!original || !modified) { return null; } - return EditorSimpleWorker.computeDiff(original, modified, options); + return EditorSimpleWorker.computeDiff(original, modified, options, algorithm); } - private static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, options: IDocumentDiffProviderOptions): IDiffComputationResult { - const diffAlgorithm: ILinesDiffComputer = options.diffAlgorithm === 'experimental' ? linesDiffComputers.experimental : linesDiffComputers.smart; + private static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): IDiffComputationResult { + const diffAlgorithm: ILinesDiffComputer = algorithm === 'experimental' ? linesDiffComputers.experimental : linesDiffComputers.smart; const originalLines = originalTextModel.getLinesContent(); const modifiedLines = modifiedTextModel.getLinesContent(); diff --git a/src/vs/editor/common/services/editorWorker.ts b/src/vs/editor/common/services/editorWorker.ts index 36ff24d2908..8f5129b9751 100644 --- a/src/vs/editor/common/services/editorWorker.ts +++ b/src/vs/editor/common/services/editorWorker.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; -import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; import { IInplaceReplaceSupportResult, TextEdit } from 'vs/editor/common/languages'; import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter'; @@ -14,6 +14,8 @@ import type { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleW export const IEditorWorkerService = createDecorator('editorWorkerService'); +export type DiffAlgorithmName = 'smart' | 'experimental'; + export interface IEditorWorkerService { readonly _serviceBrand: undefined; @@ -21,7 +23,7 @@ export interface IEditorWorkerService { computedUnicodeHighlights(uri: URI, options: UnicodeHighlighterOptions, range?: IRange): Promise; /** Implementation in {@link EditorSimpleWorker.computeDiff} */ - computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise; + computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise; canComputeDirtyDiff(original: URI, modified: URI): boolean; computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise; diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts index 57ca5c49f8d..11bc19a66db 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts @@ -27,7 +27,6 @@ import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel'; import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types'; -import { IdleValue } from 'vs/base/common/async'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -91,7 +90,7 @@ export class CodeActionController extends Disposable implements IEditorContribut } private readonly _editor: ICodeEditor; - private readonly _model: IdleValue; + private readonly _model: CodeActionModel; private readonly _ui: Lazy; constructor( @@ -106,13 +105,9 @@ export class CodeActionController extends Disposable implements IEditorContribut this._editor = editor; - this._model = this._register(new IdleValue(() => { - const model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService)); + this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService)); - this._register(model.onDidChangeState(newState => this.update(newState))); - - return model; - })); + this._register(this._model.onDidChangeState(newState => this.update(newState))); this._ui = new Lazy(() => this._register(_instantiationService.createInstance(CodeActionUi, editor, QuickFixAction.Id, AutoFixAction.Id, { @@ -154,7 +149,7 @@ export class CodeActionController extends Disposable implements IEditorContribut } private _trigger(trigger: CodeActionTrigger) { - return this._model.value.trigger(trigger); + return this._model.trigger(trigger); } private _applyCodeAction(action: CodeActionItem, preview: boolean): Promise { diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts index 74ea6a68806..24b8b5b6bed 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema'; import { AutoFixAction, CodeActionCommand, CodeActionController, FixAllAction, OrganizeImportsAction, QuickFixAction, RefactorAction, RefactorPreview, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands'; import * as nls from 'vs/nls'; import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -registerEditorContribution(CodeActionController.ID, CodeActionController); +registerEditorContribution(CodeActionController.ID, CodeActionController, EditorContributionInstantiation.Idle); registerEditorAction(QuickFixAction); registerEditorAction(RefactorAction); registerEditorAction(RefactorPreview); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenuItems.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenuItems.ts index 8e3bad65bcf..18f495dc5d8 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionMenuItems.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenuItems.ts @@ -5,10 +5,10 @@ import 'vs/base/browser/ui/codicons/codiconStyles'; // The codicon symbol styles are defined here and must be loaded import { Codicon } from 'vs/base/common/codicons'; -import { ActionListItemKind, IListMenuItem } from 'vs/platform/actionWidget/browser/actionWidget'; import { CodeActionItem, CodeActionKind } from 'vs/editor/contrib/codeAction/common/types'; import 'vs/editor/contrib/symbolIcons/browser/symbolIcons'; // The codicon symbol colors are defined here and must be loaded to get colors import { localize } from 'vs/nls'; +import { ActionListItemKind, IListMenuItem } from 'vs/platform/actionWidget/browser/actionList'; export interface ActionGroup { readonly kind: CodeActionKind; diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts index 07b285e6121..c4b31f6a167 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleValue } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorCommand, EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import * as languages from 'vs/editor/common/languages'; @@ -30,7 +29,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution } private readonly editor: ICodeEditor; - private readonly model: IdleValue; + private readonly model: ParameterHintsModel; private readonly widget: Lazy; constructor( @@ -42,26 +41,22 @@ class ParameterHintsController extends Disposable implements IEditorContribution this.editor = editor; - this.model = this._register(new IdleValue(() => { - const model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider)); + this.model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider)); - this._register(model.onChangedHints(newParameterHints => { - if (newParameterHints) { - this.widget.getValue().show(); - this.widget.getValue().render(newParameterHints); - } else { - this.widget.rawValue?.hide(); - } - })); - - return model; + this._register(this.model.onChangedHints(newParameterHints => { + if (newParameterHints) { + this.widget.getValue().show(); + this.widget.getValue().render(newParameterHints); + } else { + this.widget.rawValue?.hide(); + } })); - this.widget = new Lazy(() => this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor, this.model.value))); + this.widget = new Lazy(() => this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor, this.model))); } cancel(): void { - this.model.value.cancel(); + this.model.cancel(); } previous(): void { @@ -73,7 +68,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution } trigger(context: TriggerContext): void { - this.model.value.trigger(context, 0); + this.model.trigger(context, 0); } } @@ -101,7 +96,7 @@ export class TriggerParameterHintsAction extends EditorAction { } } -registerEditorContribution(ParameterHintsController.ID, ParameterHintsController); +registerEditorContribution(ParameterHintsController.ID, ParameterHintsController, EditorContributionInstantiation.Idle); registerEditorAction(TriggerParameterHintsAction); const weight = KeybindingWeight.EditorContrib + 75; diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index e5a5fc8e4e1..e58b089edd9 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { alert } from 'vs/base/browser/ui/aria/aria'; -import { IdleValue, raceCancellation } from 'vs/base/common/async'; +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -13,7 +13,7 @@ import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorCommand, EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IPosition, Position } from 'vs/editor/common/core/position'; @@ -131,7 +131,7 @@ class RenameController implements IEditorContribution { return editor.getContribution(RenameController.ID); } - private readonly _renameInputField: IdleValue; + private readonly _renameInputField: RenameInputField; private readonly _disposableStore = new DisposableStore(); private _cts: CancellationTokenSource = new CancellationTokenSource(); @@ -145,7 +145,7 @@ class RenameController implements IEditorContribution { @ITextResourceConfigurationService private readonly _configService: ITextResourceConfigurationService, @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, ) { - this._renameInputField = this._disposableStore.add(new IdleValue(() => this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview'])))); + this._renameInputField = this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview'])); } dispose(): void { @@ -207,7 +207,7 @@ class RenameController implements IEditorContribution { } const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue(this.editor.getModel().uri, 'editor.rename.enablePreview'); - const inputFieldResult = await this._renameInputField.value.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token); + const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token); // no result, only hint to focus the editor or not if (typeof inputFieldResult === 'boolean') { @@ -260,11 +260,11 @@ class RenameController implements IEditorContribution { } acceptRenameInput(wantsPreview: boolean): void { - this._renameInputField.value.acceptInput(wantsPreview); + this._renameInputField.acceptInput(wantsPreview); } cancelRenameInput(): void { - this._renameInputField.value.cancelInput(true); + this._renameInputField.cancelInput(true); } } @@ -319,7 +319,7 @@ export class RenameAction extends EditorAction { } } -registerEditorContribution(RenameController.ID, RenameController); +registerEditorContribution(RenameController.ID, RenameController, EditorContributionInstantiation.Idle); registerEditorAction(RenameAction); const RenameCommand = EditorCommand.bindToContribution(RenameController.get); diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 79eac4ae03f..fe0a4c939e4 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -34,6 +34,7 @@ import { EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensi import { IMenuItem, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'; +import { LineRange, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; /** * Create a new editor under `domElement`. @@ -503,6 +504,9 @@ export function createMonacoEditorAPI(): typeof monaco.editor { TextModelResolvedOptions: TextModelResolvedOptions, FindMatch: FindMatch, ApplyUpdateResult: ApplyUpdateResult, + LineRange: LineRange, + LineRangeMapping: LineRangeMapping, + RangeMapping: RangeMapping, // vars EditorType: EditorType, diff --git a/src/vs/editor/test/common/diff/standardLinesDiffCompute.test.ts b/src/vs/editor/test/common/diff/standardLinesDiffCompute.test.ts index ba6609756ae..c2f6e8afe6e 100644 --- a/src/vs/editor/test/common/diff/standardLinesDiffCompute.test.ts +++ b/src/vs/editor/test/common/diff/standardLinesDiffCompute.test.ts @@ -76,7 +76,7 @@ suite('standardLinesDiffCompute', () => { } `.split('\n'); - const diff = c.computeDiff(lines1, lines2, { maxComputationTime: 1000, ignoreTrimWhitespace: false }); + const diff = c.computeDiff(lines1, lines2, { maxComputationTimeMs: 1000, ignoreTrimWhitespace: false }); // TODO this diff should only have one inner, not two. assert.deepStrictEqual( diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts index 2fc81e3e176..609866a8bf5 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/beforeEditPositionMapper.test.ts @@ -27,8 +27,8 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '0 0 0 0 0 0 0 0 0 0 ', // the old line numbers '0 1 2 3 4 5 7 8 9 10 ', // the old columns - '0 0 0 0 0 0 0 0 0 0 ', // line count until next change - '4 3 2 1 0 0 3 2 1 0 ', // column count until next change + '0 0 0 0 0 0 ∞ ∞ ∞ ∞ ', // line count until next change + '4 3 2 1 0 0 ∞ ∞ ∞ ∞ ', // column count until next change ] ); }); @@ -50,8 +50,8 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ', '0 1 2 3 4 5 4 5 6 7 6 7 8 9 10 ', - '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ', - '2 1 0 0 0 0 2 1 0 0 4 3 2 1 0 ', + '0 0 0 0 0 0 0 0 0 0 ∞ ∞ ∞ ∞ ∞ ', + '2 1 0 0 0 0 2 1 0 0 ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -75,16 +75,16 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '0 0 0 0 0 1 1 1 1 1 1 1 1 ', '0 1 2 3 4 3 4 5 6 7 8 9 10 ', - '0 0 0 0 0 1 1 1 1 1 1 1 1 ', - '3 2 1 0 0 10 10 10 10 10 10 10 10 ', + "0 0 0 0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ", + '3 2 1 0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', // ------------------ '⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ', '2 2 2 2 2 2 2 2 2 2 2 ', '0 1 2 3 4 5 6 7 8 9 10 ', - '0 0 0 0 0 0 0 0 0 0 0 ', - '10 9 8 7 6 5 4 3 2 1 0 ', + '∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', + '∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -109,16 +109,16 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '0 0 0 0 0 1 1 1 1 1 1 1 1 1 ', '0 1 2 3 4 0 1 2 3 4 5 7 8 9 ', - '0 0 0 0 0 0 0 0 0 0 0 1 1 1 ', - '3 2 1 0 0 5 4 3 2 1 0 10 10 10 ', + '0 0 0 0 0 0 0 0 0 0 0 ∞ ∞ ∞ ', + '3 2 1 0 0 5 4 3 2 1 0 ∞ ∞ ∞ ', // ------------------ '⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ', '2 2 2 2 2 2 2 2 2 2 2 ', '0 1 2 3 4 5 6 7 8 9 10 ', - '0 0 0 0 0 0 0 0 0 0 0 ', - '10 9 8 7 6 5 4 3 2 1 0 ', + '∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', + '∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -144,8 +144,8 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 ', '0 1 2 3 4 0 1 2 3 4 5 7 8 4 5 6 7 8 9 10 ', - '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ', - '3 2 1 0 0 5 4 3 2 1 0 1 0 6 5 4 3 2 1 0 ', + '0 0 0 0 0 0 0 0 0 0 0 0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', + '3 2 1 0 0 5 4 3 2 1 0 1 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -175,8 +175,8 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '1 0 0 0 0 0 ', '0 5 6 7 8 9 ', - '0 0 0 0 0 0 ', - '0 4 3 2 1 0 ', + '0 ∞ ∞ ∞ ∞ ∞ ', + '0 ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -215,8 +215,8 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '1 0 0 ', '0 8 9 ', - '0 0 0 ', - '0 1 0 ', + '0 ∞ ∞ ', + '0 ∞ ∞ ', ] ); }); @@ -247,16 +247,16 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '1 1 1 1 1 1 1 1 1 1 1 1 ', '0 1 2 1 2 3 4 5 6 7 8 9 ', - '0 0 0 1 1 1 1 1 1 1 1 1 ', - '0 0 0 10 10 10 10 10 10 10 10 10 ', + '0 0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', + '0 0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', // ------------------ '⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ', '2 2 2 2 2 2 2 2 2 2 2 ', '0 1 2 3 4 5 6 7 8 9 10 ', - '0 0 0 0 0 0 0 0 0 0 0 ', - '10 9 8 7 6 5 4 3 2 1 0 ', + '∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', + '∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -306,8 +306,8 @@ suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => { '2 2 2 2 2 2 2 2 ', '0 4 5 6 7 8 9 10 ', - '0 0 0 0 0 0 0 0 ', - '0 6 5 4 3 2 1 0 ', + '0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', + '0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ', ] ); }); @@ -320,7 +320,7 @@ function compute(inputArr: string[], edits: TextEdit[]): string[] { range: Range.fromPositions(lengthToPosition(e.startOffset), lengthToPosition(e.endOffset)) })))); - const mapper = new BeforeEditPositionMapper(edits, lengthOfString(newLines.join('\n'))); + const mapper = new BeforeEditPositionMapper(edits); const result = new Array(); @@ -342,9 +342,15 @@ function compute(inputArr: string[], edits: TextEdit[]): string[] { lineLine += rightPad('' + beforeObj.lineCount, 3); colLine += rightPad('' + beforeObj.columnCount, 3); - const dist = lengthToObj(mapper.getDistanceToNextChange(toLength(lineIdx, colIdx))); - lineDist += rightPad('' + dist.lineCount, 3); - colDist += rightPad('' + dist.columnCount, 3); + const distLen = mapper.getDistanceToNextChange(toLength(lineIdx, colIdx)); + if (distLen === null) { + lineDist += '∞ '; + colDist += '∞ '; + } else { + const dist = lengthToObj(distLen); + lineDist += rightPad('' + dist.lineCount, 3); + colDist += rightPad('' + dist.columnCount, 3); + } } result.push(lineStr); diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts new file mode 100644 index 00000000000..7e81dbfbcf4 --- /dev/null +++ b/src/vs/editor/test/common/model/bracketPairColorizer/combineTextEditInfos.test.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; +import { Range } from 'vs/editor/common/core/range'; +import { TextEditInfo } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/beforeEditPositionMapper'; +import { combineTextEditInfos } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/combineTextEditInfos'; +import { lengthAdd, lengthToObj, lengthToPosition, positionToLength, toLength } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { createTextModel } from 'vs/editor/test/common/testTextModel'; + +suite('combineTextEditInfos', () => { + for (let seed = 0; seed < 50; seed++) { + test('test' + seed, () => { + runTest(seed); + }); + } +}); + +function runTest(seed: number) { + const rng = new MersenneTwister(seed); + + const str = 'abcde\nfghij\nklmno\npqrst\n'; + const textModelS0 = createTextModel(str); + + const edits1 = getRandomEditInfos(textModelS0, rng.nextIntRange(1, 4), rng); + const textModelS1 = createTextModel(textModelS0.getValue()); + textModelS1.applyEdits(edits1.map(e => toEdit(e))); + + const edits2 = getRandomEditInfos(textModelS1, rng.nextIntRange(1, 4), rng); + const textModelS2 = createTextModel(textModelS1.getValue()); + textModelS2.applyEdits(edits2.map(e => toEdit(e))); + + const combinedEdits = combineTextEditInfos(edits1, edits2); + for (const edit of combinedEdits) { + const range = Range.fromPositions(lengthToPosition(edit.startOffset), lengthToPosition(lengthAdd(edit.startOffset, edit.newLength))); + const value = textModelS2.getValueInRange(range); + if (!value.match(/^(L|C|\n)*$/)) { + throw new Error('Invalid edit: ' + value); + } + textModelS2.applyEdits([{ + range, + text: textModelS0.getValueInRange(Range.fromPositions(lengthToPosition(edit.startOffset), lengthToPosition(edit.endOffset))), + }]); + } + + assert.deepStrictEqual(textModelS2.getValue(), textModelS0.getValue()); + + textModelS0.dispose(); + textModelS1.dispose(); + textModelS2.dispose(); +} + +function getRandomEditInfos(textModel: TextModel, count: number, rng: MersenneTwister): TextEditInfo[] { + const edits: TextEditInfo[] = []; + let i = 0; + for (let j = 0; j < count; j++) { + edits.push(getRandomEdit(textModel, i, rng)); + i = textModel.getOffsetAt(lengthToPosition(edits[j].endOffset)); + } + return edits; +} + +function getRandomEdit(textModel: TextModel, rangeOffsetStart: number, rng: MersenneTwister): TextEditInfo { + const textModelLength = textModel.getValueLength(); + const offsetStart = rng.nextIntRange(rangeOffsetStart, textModelLength); + const offsetEnd = rng.nextIntRange(offsetStart, textModelLength); + + const lineCount = rng.nextIntRange(0, 3); + const columnCount = rng.nextIntRange(0, 5); + + return { + startOffset: positionToLength(textModel.getPositionAt(offsetStart)), + endOffset: positionToLength(textModel.getPositionAt(offsetEnd)), + newLength: toLength(lineCount, columnCount) + }; +} + +function toEdit(editInfo: TextEditInfo): ISingleEditOperation { + const l = lengthToObj(editInfo.newLength); + let text = ''; + + for (let i = 0; i < l.lineCount; i++) { + text += 'LLL\n'; + } + for (let i = 0; i < l.columnCount; i++) { + text += 'C'; + } + + return { + range: Range.fromPositions( + lengthToPosition(editInfo.startOffset), + lengthToPosition(editInfo.endOffset) + ), + text + }; +} + +// Generated by copilot +class MersenneTwister { + private readonly mt = new Array(624); + private index = 0; + + constructor(seed: number) { + this.mt[0] = seed >>> 0; + for (let i = 1; i < 624; i++) { + const s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); + this.mt[i] = (((((s & 0xffff0000) >>> 16) * 0x6c078965) << 16) + (s & 0x0000ffff) * 0x6c078965 + i) >>> 0; + } + } + + public nextInt() { + if (this.index === 0) { + this.generateNumbers(); + } + + let y = this.mt[this.index]; + y = y ^ (y >>> 11); + y = y ^ ((y << 7) & 0x9d2c5680); + y = y ^ ((y << 15) & 0xefc60000); + y = y ^ (y >>> 18); + + this.index = (this.index + 1) % 624; + + return y >>> 0; + } + + public nextIntRange(start: number, endExclusive: number) { + const range = endExclusive - start; + return Math.floor(this.nextInt() / (0x100000000 / range)) + start; + } + + private generateNumbers() { + for (let i = 0; i < 624; i++) { + const y = (this.mt[i] & 0x80000000) + (this.mt[(i + 1) % 624] & 0x7fffffff); + this.mt[i] = this.mt[(i + 397) % 624] ^ (y >>> 1); + if ((y % 2) !== 0) { + this.mt[i] = this.mt[i] ^ 0x9908b0df; + } + } + } +} diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts index c938b730a46..2b05427a4ba 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts @@ -11,6 +11,10 @@ import { BracketPairInfo } from 'vs/editor/common/textModelBracketPairs'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { TokenInfo, TokenizedDocument } from 'vs/editor/test/common/model/bracketPairColorizer/tokenizer.test'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { StandardTokenType } from 'vs/editor/common/encodedTokenAttributes'; +import { TokenizationRegistry } from 'vs/editor/common/languages'; suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { @@ -18,6 +22,16 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { const languageId = 'testLanguage'; const instantiationService = createModelServices(store); const languageConfigurationService = instantiationService.get(ILanguageConfigurationService); + const languageService = instantiationService.get(ILanguageService); + store.add(languageService.registerLanguage({ + id: languageId, + })); + + const encodedMode1 = languageService.languageIdCodec.encodeLanguageId(languageId); + const document = new TokenizedDocument([ + new TokenInfo(text, encodedMode1, StandardTokenType.Other, true) + ]); + store.add(TokenizationRegistry.register(languageId, document.getTokenizationSupport())); store.add(languageConfigurationService.register(languageId, { colorizedBracketPairs: [ @@ -26,13 +40,15 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { ['(', ')'], ] })); - return store.add(instantiateTextModel(instantiationService, text, languageId)); + const textModel = store.add(instantiateTextModel(instantiationService, text, languageId)); + return textModel; } test('Basic 1', () => { disposeOnReturn(store => { const doc = new AnnotatedDocument(`{ ( [] ¹ ) [ ² { } ] () } []`); const model = createTextModelWithColorizedBracketPairs(store, doc.text); + model.tokenization.getLineTokens(1).getLanguageId(0); assert.deepStrictEqual( model.bracketPairs .getBracketPairsInRange(doc.range(1, 2)) diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts index d1800b09f0d..ca5535c0eb5 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/tokenizer.test.ts @@ -124,7 +124,7 @@ function tokenToObj(token: Token, offset: Length, model: TextModel, keyProvider: }; } -class TokenizedDocument { +export class TokenizedDocument { private readonly tokensByLine: readonly TokenInfo[][]; constructor(tokens: TokenInfo[]) { const tokensByLine = new Array(); @@ -189,7 +189,7 @@ class TokenizedDocument { } } -class TokenInfo { +export class TokenInfo { constructor( public readonly text: string, public readonly languageId: LanguageId, diff --git a/src/vs/editor/test/common/services/testEditorWorkerService.ts b/src/vs/editor/test/common/services/testEditorWorkerService.ts index 9c5c6519088..a0fb8772d7f 100644 --- a/src/vs/editor/test/common/services/testEditorWorkerService.ts +++ b/src/vs/editor/test/common/services/testEditorWorkerService.ts @@ -5,9 +5,9 @@ import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; -import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { DiffAlgorithmName, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/languages'; -import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; export class TestEditorWorkerService implements IEditorWorkerService { @@ -16,7 +16,7 @@ export class TestEditorWorkerService implements IEditorWorkerService { canComputeUnicodeHighlights(uri: URI): boolean { return false; } async computedUnicodeHighlights(uri: URI): Promise { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; } - async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise { return null; } + async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise { return null; } canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; } async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return null; } async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined): Promise { return undefined; } diff --git a/src/vs/editor/test/node/diffing/README.md b/src/vs/editor/test/node/diffing/README.md new file mode 100644 index 00000000000..0865d7b3eca --- /dev/null +++ b/src/vs/editor/test/node/diffing/README.md @@ -0,0 +1,10 @@ +# Diffing Fixture Tests + +Every folder in `fixtures` represents a test. +The file that starts with `1.` is diffed against the file that starts with `2.`. Use `tst` instead of `ts` to avoid compiler/linter errors for typescript diff files. + +* Missing `*.expected.diff.json` are created automatically (as well as an `*.invalid.diff.json` file). +* If the actual diff does not equal the expected diff, the expected file is updated automatically. The previous value of the expected file is written to `*.invalid.diff.json`. +* The test will fail if there are any `*.invalid.diff.json` files. This makes sure that the test keeps failing even if it is run a second time. + +When changing the diffing algorithm, run the fixture tests, review the diff of the `*.expected.diff.json` files and delete all `*.invalid.diff.json` files. diff --git a/src/vs/editor/test/node/diffing/diffing.test.ts b/src/vs/editor/test/node/diffing/diffing.test.ts new file mode 100644 index 00000000000..fdccaf91fc2 --- /dev/null +++ b/src/vs/editor/test/node/diffing/diffing.test.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert = require('assert'); +import { readdirSync, readFileSync, existsSync, writeFileSync } from 'fs'; +import { join, resolve } from 'path'; +import { FileAccess } from 'vs/base/common/network'; +import { SmartLinesDiffComputer } from 'vs/editor/common/diff/smartLinesDiffComputer'; +import { StandardLinesDiffComputer } from 'vs/editor/common/diff/standardLinesDiffComputer'; + +suite('diff fixtures', () => { + const fixturesOutDir = FileAccess.asFileUri('vs/editor/test/node/diffing/fixtures').fsPath; + // We want the dir in src, so we can directly update the source files if they disagree and create invalid files to capture the previous state. + // This makes it very easy to update the fixtures. + const fixturesSrcDir = resolve(fixturesOutDir).replaceAll('\\', '/').replace('/out/vs/editor/', '/src/vs/editor/'); + const folders = readdirSync(fixturesSrcDir); + + for (const folder of folders) { + for (const diffingAlgoName of ['smart', 'experimental']) { + test(`${folder}-${diffingAlgoName}`, () => { + const folderPath = join(fixturesSrcDir, folder); + const files = readdirSync(folderPath); + + const firstFileName = files.find(f => f.startsWith('1.'))!; + const secondFileName = files.find(f => f.startsWith('2.'))!; + + const firstContentLines = readFileSync(join(folderPath, firstFileName), 'utf8').split(/\r\n|\r|\n/); + const secondContentLines = readFileSync(join(folderPath, secondFileName), 'utf8').split(/\r\n|\r|\n/); + + const diffingAlgo = diffingAlgoName === 'smart' ? new SmartLinesDiffComputer() : new StandardLinesDiffComputer(); + + const diff = diffingAlgo.computeDiff(firstContentLines, secondContentLines, { ignoreTrimWhitespace: false, maxComputationTime: Number.MAX_SAFE_INTEGER }); + + const actualDiffingResult: DiffingResult = { + originalFileName: `./${firstFileName}`, + modifiedFileName: `./${secondFileName}`, + diffs: diff.changes.map(c => ({ + originalRange: c.originalRange.toString(), + modifiedRange: c.modifiedRange.toString(), + innerChanges: c.innerChanges?.map(c => ({ + originalRange: c.originalRange.toString(), + modifiedRange: c.modifiedRange.toString(), + })) || null + })) + }; + + const expectedFilePath = join(folderPath, `${diffingAlgoName}.expected.diff.json`); + const invalidFilePath = join(folderPath, `${diffingAlgoName}.invalid.diff.json`); + + const expectedFileContentFromActual = JSON.stringify(actualDiffingResult, null, '\t'); + + const invalidExists = existsSync(invalidFilePath); + + if (!existsSync(expectedFilePath)) { + writeFileSync(expectedFilePath, expectedFileContentFromActual); + writeFileSync(invalidFilePath, ''); + throw new Error('No expected file! Expected and invalid files were written. Delete the invalid file to make the test pass.'); + } else { + const expectedFileContent = readFileSync(invalidExists ? invalidFilePath : expectedFilePath, 'utf8'); + const expectedFileDiffResult: DiffingResult = JSON.parse(expectedFileContent); + + try { + assert.deepStrictEqual(actualDiffingResult, expectedFileDiffResult); + } catch (e) { + if (!invalidExists) { + writeFileSync(invalidFilePath, expectedFileContent); + } + writeFileSync(expectedFilePath, expectedFileContentFromActual); + throw e; + } + } + + if (invalidExists) { + throw new Error('Invalid file exists and agrees with expected file! Delete the invalid file to make the test pass.'); + } + }); + } + } +}); + +interface DiffingResult { + originalFileName: string; + modifiedFileName: string; + + diffs: IDetailedDiff[]; +} + +interface IDetailedDiff { + originalRange: string; // [startLineNumber, endLineNumberExclusive) + modifiedRange: string; // [startLineNumber, endLineNumberExclusive) + innerChanges: IDiff[] | null; +} + +interface IDiff { + originalRange: string; // [1,18 -> 1,19] + modifiedRange: string; // [1,18 -> 1,19] +} diff --git a/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/1.tst b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/1.tst new file mode 100644 index 00000000000..e1557eb0c9a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/1.tst @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CompareResult } from 'vs/base/common/arrays'; +import { autorun, derived } from 'vs/base/common/observable'; +import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; +import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; +import { CodeEditorView } from './codeEditorView'; + +export class ResultCodeEditorView extends CodeEditorView { + private readonly decorations = derived('result.decorations', reader => { + const viewModel = this.viewModel.read(reader); + if (!viewModel) { + return []; + } + const model = viewModel.model; + const result = new Array(); + + const baseRangeWithStoreAndTouchingDiffs = join( + model.modifiedBaseRanges.read(reader), + model.resultDiffs.read(reader), + (baseRange, diff) => baseRange.baseRange.touches(diff.inputRange) + ? CompareResult.neitherLessOrGreaterThan + : LineRange.compareByStart( + baseRange.baseRange, + diff.inputRange + ) + ); + + const activeModifiedBaseRange = viewModel.activeModifiedBaseRange.read(reader); + + for (const m of baseRangeWithStoreAndTouchingDiffs) { + const modifiedBaseRange = m.left; + + if (modifiedBaseRange) { + const range = model.getRangeInResult(modifiedBaseRange.baseRange, reader).toInclusiveRange(); + if (range) { + const blockClassNames = ['merge-editor-block']; + const isHandled = model.isHandled(modifiedBaseRange).read(reader); + if (isHandled) { + blockClassNames.push('handled'); + } + if (modifiedBaseRange === activeModifiedBaseRange) { + blockClassNames.push('focused'); + } + blockClassNames.push('result'); + + result.push({ + range, + options: { + isWholeLine: true, + blockClassName: blockClassNames.join(' '), + description: 'Result Diff', + minimap: { + position: MinimapPosition.Gutter, + color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, + }, + overviewRuler: { + position: OverviewRulerLane.Center, + color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, + } + } + }); + } + } + + for (const diff of m.rights) { + const range = diff.outputRange.toInclusiveRange(); + if (range) { + result.push({ + range, + options: { + className: `merge-editor-diff result`, + description: 'Merge Editor', + isWholeLine: true, + } + }); + } + + if (diff.rangeMappings) { + for (const d of diff.rangeMappings) { + result.push({ + range: d.outputRange, + options: { + className: `merge-editor-diff-word result`, + description: 'Merge Editor' + } + }); + } + } + } + } + return result; + }); + + constructor( + @IInstantiationService instantiationService: IInstantiationService + ) { + super(instantiationService); + + this._register(applyObservableDecorations(this.editor, this.decorations)); + + + this._register(autorun('update remainingConflicts label', reader => { + const model = this.model.read(reader); + if (!model) { + return; + } + const count = model.unhandledConflictsCount.read(reader); + + this.htmlElements.detail.innerText = count === 1 + ? localize( + 'mergeEditor.remainingConflicts', + '{0} Conflict Remaining', + count + ) + : localize( + 'mergeEditor.remainingConflict', + '{0} Conflicts Remaining ', + count + ); + + })); + } +} diff --git a/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/2.tst b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/2.tst new file mode 100644 index 00000000000..f775a30f62a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/2.tst @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CompareResult } from 'vs/base/common/arrays'; +import { autorun, derived } from 'vs/base/common/observable'; +import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; +import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; +import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; +import { CodeEditorView } from './codeEditorView'; + +export class ResultCodeEditorView extends CodeEditorView { + private readonly decorations = derived('result.decorations', reader => { + const viewModel = this.viewModel.read(reader); + if (!viewModel) { + return []; + } + const model = viewModel.model; + const result = new Array(); + + const baseRangeWithStoreAndTouchingDiffs = join( + model.modifiedBaseRanges.read(reader), + model.resultDiffs.read(reader), + (baseRange, diff) => baseRange.baseRange.touches(diff.inputRange) + ? CompareResult.neitherLessOrGreaterThan + : LineRange.compareByStart( + baseRange.baseRange, + diff.inputRange + ) + ); + + const activeModifiedBaseRange = viewModel.activeModifiedBaseRange.read(reader); + + for (const m of baseRangeWithStoreAndTouchingDiffs) { + const modifiedBaseRange = m.left; + + if (modifiedBaseRange) { + const range = model.getRangeInResult(modifiedBaseRange.baseRange, reader).toInclusiveRange(); + if (range) { + const blockClassNames = ['merge-editor-block']; + const isHandled = model.isHandled(modifiedBaseRange).read(reader); + if (isHandled) { + blockClassNames.push('handled'); + } + if (modifiedBaseRange === activeModifiedBaseRange) { + blockClassNames.push('focused'); + } + blockClassNames.push('result'); + + result.push({ + range, + options: { + isWholeLine: true, + blockClassName: blockClassNames.join(' '), + description: 'Result Diff', + minimap: { + position: MinimapPosition.Gutter, + color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, + }, + overviewRuler: { + position: OverviewRulerLane.Center, + color: { id: isHandled ? handledConflictMinimapOverViewRulerColor : unhandledConflictMinimapOverViewRulerColor }, + } + } + }); + } + } + + + if (!modifiedBaseRange || modifiedBaseRange.isConflicting) { + for (const diff of m.rights) { + const range = diff.outputRange.toInclusiveRange(); + if (range) { + result.push({ + range, + options: { + className: `merge-editor-diff result`, + description: 'Merge Editor', + isWholeLine: true, + } + }); + } + + if (diff.rangeMappings) { + for (const d of diff.rangeMappings) { + result.push({ + range: d.outputRange, + options: { + className: `merge-editor-diff-word result`, + description: 'Merge Editor' + } + }); + } + } + } + } + } + return result; + }); + + constructor( + @IInstantiationService instantiationService: IInstantiationService + ) { + super(instantiationService); + + this._register(applyObservableDecorations(this.editor, this.decorations)); + + + this._register(autorun('update remainingConflicts label', reader => { + const model = this.model.read(reader); + if (!model) { + return; + } + const count = model.unhandledConflictsCount.read(reader); + + this.htmlElements.detail.innerText = count === 1 + ? localize( + 'mergeEditor.remainingConflicts', + '{0} Conflict Remaining', + count + ) + : localize( + 'mergeEditor.remainingConflict', + '{0} Conflicts Remaining ', + count + ); + + })); + } +} diff --git a/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/experimental.expected.diff.json new file mode 100644 index 00000000000..86b0dc30193 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/experimental.expected.diff.json @@ -0,0 +1,122 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[73,85)", + "modifiedRange": "[73,87)", + "innerChanges": [ + { + "originalRange": "[73,1 -> 73,1]", + "modifiedRange": "[73,1 -> 75,1]" + }, + { + "originalRange": "[73,1 -> 73,1]", + "modifiedRange": "[75,1 -> 75,2]" + }, + { + "originalRange": "[74,1 -> 74,1]", + "modifiedRange": "[76,1 -> 76,2]" + }, + { + "originalRange": "[75,1 -> 75,1]", + "modifiedRange": "[77,1 -> 77,2]" + }, + { + "originalRange": "[76,1 -> 76,1]", + "modifiedRange": "[78,1 -> 78,2]" + }, + { + "originalRange": "[77,1 -> 77,1]", + "modifiedRange": "[79,1 -> 79,2]" + }, + { + "originalRange": "[78,1 -> 78,1]", + "modifiedRange": "[80,1 -> 80,2]" + }, + { + "originalRange": "[79,1 -> 79,1]", + "modifiedRange": "[81,1 -> 81,2]" + }, + { + "originalRange": "[80,1 -> 80,1]", + "modifiedRange": "[82,1 -> 82,2]" + }, + { + "originalRange": "[81,1 -> 81,1]", + "modifiedRange": "[83,1 -> 83,2]" + }, + { + "originalRange": "[82,1 -> 82,1]", + "modifiedRange": "[84,1 -> 84,2]" + }, + { + "originalRange": "[83,1 -> 83,1]", + "modifiedRange": "[85,1 -> 85,2]" + }, + { + "originalRange": "[84,1 -> 84,1]", + "modifiedRange": "[86,1 -> 86,2]" + } + ] + }, + { + "originalRange": "[86,98)", + "modifiedRange": "[88,101)", + "innerChanges": [ + { + "originalRange": "[86,1 -> 86,1]", + "modifiedRange": "[88,1 -> 88,2]" + }, + { + "originalRange": "[87,1 -> 87,1]", + "modifiedRange": "[89,1 -> 89,2]" + }, + { + "originalRange": "[88,1 -> 88,1]", + "modifiedRange": "[90,1 -> 90,2]" + }, + { + "originalRange": "[89,1 -> 89,1]", + "modifiedRange": "[91,1 -> 91,2]" + }, + { + "originalRange": "[90,1 -> 90,1]", + "modifiedRange": "[92,1 -> 92,2]" + }, + { + "originalRange": "[91,1 -> 91,1]", + "modifiedRange": "[93,1 -> 93,2]" + }, + { + "originalRange": "[92,1 -> 92,1]", + "modifiedRange": "[94,1 -> 94,2]" + }, + { + "originalRange": "[93,1 -> 93,1]", + "modifiedRange": "[95,1 -> 95,2]" + }, + { + "originalRange": "[94,1 -> 94,1]", + "modifiedRange": "[96,1 -> 96,2]" + }, + { + "originalRange": "[95,1 -> 95,1]", + "modifiedRange": "[97,1 -> 97,2]" + }, + { + "originalRange": "[96,1 -> 96,1]", + "modifiedRange": "[98,1 -> 98,2]" + }, + { + "originalRange": "[97,1 -> 97,1]", + "modifiedRange": "[99,1 -> 99,2]" + }, + { + "originalRange": "[98,1 -> 98,1]", + "modifiedRange": "[100,1 -> 101,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/smart.expected.diff.json new file mode 100644 index 00000000000..17dd9daa899 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/bracket-aligning/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[73,85)", + "modifiedRange": "[73,87)", + "innerChanges": null + }, + { + "originalRange": "[86,95)", + "modifiedRange": "[88,98)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/1.txt b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/1.txt new file mode 100644 index 00000000000..8530beeba82 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/1.txt @@ -0,0 +1,8 @@ + +console.log(1) +console.log(2) +console.log(3) +console.log(4) +console.log(5) +console.log(6) +console.log(7) diff --git a/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/2.txt b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/2.txt new file mode 100644 index 00000000000..808189c8bec --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/2.txt @@ -0,0 +1,9 @@ +console.log(1); +console.log(2); +console.log(3); +console.log(4); + +console.log(5); +console.log(6); +console.log(7); + diff --git a/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/experimental.expected.diff.json new file mode 100644 index 00000000000..52c3f93400f --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/experimental.expected.diff.json @@ -0,0 +1,26 @@ +{ + "originalFileName": "./1.txt", + "modifiedFileName": "./2.txt", + "diffs": [ + { + "originalRange": "[1,1)", + "modifiedRange": "[1,9)", + "innerChanges": [ + { + "originalRange": "[1,1 -> 1,1]", + "modifiedRange": "[1,1 -> 9,1]" + } + ] + }, + { + "originalRange": "[2,9)", + "modifiedRange": "[10,10)", + "innerChanges": [ + { + "originalRange": "[2,1 -> 9,1]", + "modifiedRange": "[10,1 -> 10,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/smart.expected.diff.json new file mode 100644 index 00000000000..3a2b5cf1e02 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/fuzzy-matching/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.txt", + "modifiedFileName": "./2.txt", + "diffs": [ + { + "originalRange": "[1,1)", + "modifiedRange": "[1,9)", + "innerChanges": null + }, + { + "originalRange": "[2,9)", + "modifiedRange": "[10,10)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/indentation/1.tst b/src/vs/editor/test/node/diffing/fixtures/indentation/1.tst new file mode 100644 index 00000000000..f41bf28983f --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/indentation/1.tst @@ -0,0 +1,36 @@ +export function lineRangeMappingFromRangeMappings(alignments: RangeMapping[]): LineRangeMapping[] { + const changes: LineRangeMapping[] = []; + for (const g of group( + alignments, + (a1, a2) => + (a2.originalRange.startLineNumber - (a1.originalRange.endLineNumber - (a1.originalRange.endColumn > 1 ? 0 : 1)) <= 1) + || (a2.modifiedRange.startLineNumber - (a1.modifiedRange.endLineNumber - (a1.modifiedRange.endColumn > 1 ? 0 : 1)) <= 1) + )) { + const first = g[0]; + const last = g[g.length - 1]; + + changes.push(new LineRangeMapping( + new LineRange( + first.originalRange.startLineNumber, + last.originalRange.endLineNumber + (last.originalRange.endColumn > 1 || last.modifiedRange.endColumn > 1 ? 1 : 0) + ), + new LineRange( + first.modifiedRange.startLineNumber, + last.modifiedRange.endLineNumber + (last.originalRange.endColumn > 1 || last.modifiedRange.endColumn > 1 ? 1 : 0) + ), + g + )); + } + + assertFn(() => { + return checkAdjacentItems(changes, + (m1, m2) => m2.originalRange.startLineNumber - m1.originalRange.endLineNumberExclusive === m2.modifiedRange.startLineNumber - m1.modifiedRange.endLineNumberExclusive && + // There has to be an unchanged line in between (otherwise both diffs should have been joined) + m1.originalRange.endLineNumberExclusive < m2.originalRange.startLineNumber && + m1.modifiedRange.endLineNumberExclusive < m2.modifiedRange.startLineNumber, + ); + }); + + + return changes; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/indentation/2.tst b/src/vs/editor/test/node/diffing/fixtures/indentation/2.tst new file mode 100644 index 00000000000..30845a978ee --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/indentation/2.tst @@ -0,0 +1,38 @@ +export function lineRangeMappingFromRangeMappings(alignments: RangeMapping[]): LineRangeMapping[] { + const changes: LineRangeMapping[] = []; + for (const g of group( + alignments, + (a1, a2) => + (a2.originalRange.startLineNumber - (a1.originalRange.endLineNumber - (a1.originalRange.endColumn > 1 ? 0 : 1)) <= 1) + || (a2.modifiedRange.startLineNumber - (a1.modifiedRange.endLineNumber - (a1.modifiedRange.endColumn > 1 ? 0 : 1)) <= 1) + )) { + if (true) { + const first = g[0]; + const last = g[g.length - 1]; + + changes.push(new LineRangeMapping( + new LineRange( + first.originalRange.startLineNumber, + last.originalRange.endLineNumber + (last.originalRange.endColumn > 1 || last.modifiedRange.endColumn > 1 ? 1 : 0) + ), + new LineRange( + first.modifiedRange.startLineNumber, + last.modifiedRange.endLineNumber + (last.originalRange.endColumn > 1 || last.modifiedRange.endColumn > 1 ? 1 : 0) + ), + g + )); + } + } + + assertFn(() => { + return checkAdjacentItems(changes, + (m1, m2) => m2.originalRange.startLineNumber - m1.originalRange.endLineNumberExclusive === m2.modifiedRange.startLineNumber - m1.modifiedRange.endLineNumberExclusive && + // There has to be an unchanged line in between (otherwise both diffs should have been joined) + m1.originalRange.endLineNumberExclusive < m2.originalRange.startLineNumber && + m1.modifiedRange.endLineNumberExclusive < m2.modifiedRange.startLineNumber, + ); + }); + + + return changes; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/indentation/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/indentation/experimental.expected.diff.json new file mode 100644 index 00000000000..6e683c46f89 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/indentation/experimental.expected.diff.json @@ -0,0 +1,78 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,11)", + "modifiedRange": "[9,12)", + "innerChanges": [ + { + "originalRange": "[9,1 -> 9,1]", + "modifiedRange": "[9,1 -> 10,1]" + }, + { + "originalRange": "[9,1 -> 9,1]", + "modifiedRange": "[10,1 -> 10,2]" + }, + { + "originalRange": "[10,1 -> 10,1]", + "modifiedRange": "[11,1 -> 11,2]" + } + ] + }, + { + "originalRange": "[12,23)", + "modifiedRange": "[13,25)", + "innerChanges": [ + { + "originalRange": "[12,1 -> 12,1]", + "modifiedRange": "[13,1 -> 13,2]" + }, + { + "originalRange": "[13,1 -> 13,1]", + "modifiedRange": "[14,1 -> 14,2]" + }, + { + "originalRange": "[14,1 -> 14,1]", + "modifiedRange": "[15,1 -> 15,2]" + }, + { + "originalRange": "[15,1 -> 15,1]", + "modifiedRange": "[16,1 -> 16,2]" + }, + { + "originalRange": "[16,1 -> 16,1]", + "modifiedRange": "[17,1 -> 17,2]" + }, + { + "originalRange": "[17,1 -> 17,1]", + "modifiedRange": "[18,1 -> 18,2]" + }, + { + "originalRange": "[18,1 -> 18,1]", + "modifiedRange": "[19,1 -> 19,2]" + }, + { + "originalRange": "[19,1 -> 19,1]", + "modifiedRange": "[20,1 -> 20,2]" + }, + { + "originalRange": "[20,1 -> 20,1]", + "modifiedRange": "[21,1 -> 21,2]" + }, + { + "originalRange": "[21,1 -> 21,1]", + "modifiedRange": "[22,1 -> 22,2]" + }, + { + "originalRange": "[22,1 -> 22,1]", + "modifiedRange": "[23,1 -> 23,2]" + }, + { + "originalRange": "[23,1 -> 23,1]", + "modifiedRange": "[24,1 -> 25,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/indentation/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/indentation/smart.expected.diff.json new file mode 100644 index 00000000000..1673eac315e --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/indentation/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,11)", + "modifiedRange": "[9,12)", + "innerChanges": null + }, + { + "originalRange": "[12,23)", + "modifiedRange": "[13,25)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/1.txt b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/1.txt new file mode 100644 index 00000000000..5b1da6edfa1 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/1.txt @@ -0,0 +1,4 @@ +console.log(1); +console.log(2); +console.log(3); +console.log(4); diff --git a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/2.txt b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/2.txt new file mode 100644 index 00000000000..1def7d3472a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/2.txt @@ -0,0 +1,3 @@ +console.log(1) +console.log(2) +console.log(4) diff --git a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/experimental.expected.diff.json new file mode 100644 index 00000000000..7ac4028731d --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/experimental.expected.diff.json @@ -0,0 +1,24 @@ +{ + "originalFileName": "./1.txt", + "modifiedFileName": "./2.txt", + "diffs": [ + { + "originalRange": "[1,5)", + "modifiedRange": "[1,4)", + "innerChanges": [ + { + "originalRange": "[1,15 -> 1,16]", + "modifiedRange": "[1,15 -> 1,15]" + }, + { + "originalRange": "[2,15 -> 3,16]", + "modifiedRange": "[2,15 -> 2,15]" + }, + { + "originalRange": "[4,15 -> 4,16]", + "modifiedRange": "[3,15 -> 3,15]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/intra-block-align/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/smart.expected.diff.json new file mode 100644 index 00000000000..7ac4028731d --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/intra-block-align/smart.expected.diff.json @@ -0,0 +1,24 @@ +{ + "originalFileName": "./1.txt", + "modifiedFileName": "./2.txt", + "diffs": [ + { + "originalRange": "[1,5)", + "modifiedRange": "[1,4)", + "innerChanges": [ + { + "originalRange": "[1,15 -> 1,16]", + "modifiedRange": "[1,15 -> 1,15]" + }, + { + "originalRange": "[2,15 -> 3,16]", + "modifiedRange": "[2,15 -> 2,15]" + }, + { + "originalRange": "[4,15 -> 4,16]", + "modifiedRange": "[3,15 -> 3,15]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/json-brackets/1.json b/src/vs/editor/test/node/diffing/fixtures/json-brackets/1.json new file mode 100644 index 00000000000..7fa1a6519a7 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/json-brackets/1.json @@ -0,0 +1,478 @@ +{ + "editor": [ + { + "name": "vs/platform", + "project": "vscode-editor" + }, + { + "name": "vs/editor/contrib", + "project": "vscode-editor" + }, + { + "name": "vs/editor", + "project": "vscode-editor" + }, + { + "name": "vs/base", + "project": "vscode-editor" + } + ], + "workbench": [ + { + "name": "vs/code", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/api/common", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/bulkEdit", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/cli", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/codeEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/callHierarchy", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/typeHierarchy", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/codeActions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/comments", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/debug", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/dialogs", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/emmet", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/experiments", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/extensions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/externalTerminal", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/feedback", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/files", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/html", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/issue", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/inlayHints", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/interactive", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/languageStatus", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/keybindings", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/markers", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/mergeEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/localization", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/logs", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/output", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/performance", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/preferences", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/notebook", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/quickaccess", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/userData", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/remote", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/relauncher", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/sash", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/scm", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/search", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/searchEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/snippets", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/format", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/tags", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/surveys", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/tasks", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/testing", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/terminal", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/themes", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/trust", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/update", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/url", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/watermark", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/webview", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/webviewPanel", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/workspace", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/workspaces", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/customEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/externalUriOpener", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeGettingStarted", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeOverlay", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomePage", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeViews", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeWalkthrough", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/outline", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/userDataSync", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/editSessions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/views", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/languageDetection", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/audioCues", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/deprecatedExtensionMigrator", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/offline", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/actions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/authToken", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/backup", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/bulkEdit", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/clipboard", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/commands", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/configuration", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/configurationResolver", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/dialogs", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/editor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensionManagement", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/files", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/history", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/log", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/integrity", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/keybinding", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/lifecycle", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/language", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/progress", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/remote", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/search", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/textfile", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/themes", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/textMate", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/workingCopy", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/workspaces", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/decorations", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/label", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/preferences", + "project": "vscode-preferences" + }, + { + "name": "vs/workbench/services/notification", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/userData", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/userDataSync", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/editSessions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/views", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/timeline", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/localHistory", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/authentication", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensionRecommendations", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/gettingStarted", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/host", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/userDataProfile", + "project": "vscode-profiles" + }, + { + "name": "vs/workbench/services/userDataProfile", + "project": "vscode-profiles" + } + ] +} diff --git a/src/vs/editor/test/node/diffing/fixtures/json-brackets/2.json b/src/vs/editor/test/node/diffing/fixtures/json-brackets/2.json new file mode 100644 index 00000000000..dd949e618f2 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/json-brackets/2.json @@ -0,0 +1,482 @@ +{ + "editor": [ + { + "name": "vs/platform", + "project": "vscode-editor" + }, + { + "name": "vs/editor/contrib", + "project": "vscode-editor" + }, + { + "name": "vs/editor", + "project": "vscode-editor" + }, + { + "name": "vs/base", + "project": "vscode-editor" + } + ], + "workbench": [ + { + "name": "vs/code", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/api/common", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/bulkEdit", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/cli", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/codeEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/callHierarchy", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/typeHierarchy", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/codeActions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/comments", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/debug", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/dialogs", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/emmet", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/experiments", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/extensions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/externalTerminal", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/feedback", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/files", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/html", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/issue", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/inlayHints", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/interactive", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/languageStatus", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/keybindings", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/markers", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/mergeEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/localization", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/logs", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/output", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/performance", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/preferences", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/notebook", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/quickaccess", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/userData", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/remote", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/relauncher", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/sash", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/scm", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/search", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/searchEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/snippets", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/format", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/tags", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/surveys", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/tasks", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/testing", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/terminal", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/themes", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/trust", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/update", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/url", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/watermark", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/webview", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/webviewPanel", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/workspace", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/workspaces", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/customEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/externalUriOpener", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeGettingStarted", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeOverlay", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomePage", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeViews", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcomeWalkthrough", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/outline", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/userDataSync", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/editSessions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/views", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/languageDetection", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/audioCues", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/deprecatedExtensionMigrator", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/bracketPairColorizer2Telemetry", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/offline", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/actions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/authToken", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/backup", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/bulkEdit", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/clipboard", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/commands", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/configuration", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/configurationResolver", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/dialogs", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/editor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensionManagement", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/files", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/history", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/log", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/integrity", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/keybinding", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/lifecycle", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/language", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/progress", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/remote", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/search", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/textfile", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/themes", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/textMate", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/workingCopy", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/workspaces", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/decorations", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/label", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/preferences", + "project": "vscode-preferences" + }, + { + "name": "vs/workbench/services/notification", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/userData", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/userDataSync", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/editSessions", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/views", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/timeline", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/localHistory", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/authentication", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/extensionRecommendations", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/gettingStarted", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/host", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/userDataProfile", + "project": "vscode-profiles" + }, + { + "name": "vs/workbench/services/userDataProfile", + "project": "vscode-profiles" + } + ] +} diff --git a/src/vs/editor/test/node/diffing/fixtures/json-brackets/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/json-brackets/experimental.expected.diff.json new file mode 100644 index 00000000000..7e258364cfc --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/json-brackets/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.json", + "modifiedFileName": "./2.json", + "diffs": [ + { + "originalRange": "[301,301)", + "modifiedRange": "[301,305)", + "innerChanges": [ + { + "originalRange": "[301,1 -> 301,1]", + "modifiedRange": "[301,1 -> 305,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/json-brackets/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/json-brackets/smart.expected.diff.json new file mode 100644 index 00000000000..93b4f55206e --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/json-brackets/smart.expected.diff.json @@ -0,0 +1,11 @@ +{ + "originalFileName": "./1.json", + "modifiedFileName": "./2.json", + "diffs": [ + { + "originalRange": "[302,302)", + "modifiedRange": "[302,306)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/1.js b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/1.js new file mode 100644 index 00000000000..78f00467983 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/1.js @@ -0,0 +1 @@ +console.log('foo'); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/2.js b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/2.js new file mode 100644 index 00000000000..3c800d3a788 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/2.js @@ -0,0 +1 @@ +console.log('foo'); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/experimental.expected.diff.json new file mode 100644 index 00000000000..cf6f3ec5f25 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.js", + "modifiedFileName": "./2.js", + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,2)", + "innerChanges": [ + { + "originalRange": "[1,20 -> 1,21]", + "modifiedRange": "[1,20 -> 1,20]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/just-whitespace/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/smart.expected.diff.json new file mode 100644 index 00000000000..cf6f3ec5f25 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/just-whitespace/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.js", + "modifiedFileName": "./2.js", + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,2)", + "innerChanges": [ + { + "originalRange": "[1,20 -> 1,21]", + "modifiedRange": "[1,20 -> 1,20]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/method-splitting/1.tst b/src/vs/editor/test/node/diffing/fixtures/method-splitting/1.tst new file mode 100644 index 00000000000..41ec64f1b1e --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/method-splitting/1.tst @@ -0,0 +1,71 @@ +class Test { + public getDecorationsViewportData(viewRange: Range): IDecorationsViewportData { + return null!; + } + + public getInlineDecorationsOnLine(lineNumber: number): InlineDecoration[] { + const range = new Range(lineNumber, this._linesCollection.getViewLineMinColumn(lineNumber), lineNumber, this._linesCollection.getViewLineMaxColumn(lineNumber)); + return this._getDecorationsInRange(range).inlineDecorations[0]; + } + + private _getDecorationsInRange(viewRange: Range): IDecorationsViewportData { + const modelDecorations = this._linesCollection.getDecorationsInRange(viewRange, this.editorId, filterValidationDecorations(this.configuration.options)); + const startLineNumber = viewRange.startLineNumber; + const endLineNumber = viewRange.endLineNumber; + + const decorationsInViewport: ViewModelDecoration[] = []; + let decorationsInViewportLen = 0; + const inlineDecorations: InlineDecoration[][] = []; + for (let j = startLineNumber; j <= endLineNumber; j++) { + inlineDecorations[j - startLineNumber] = []; + } + + for (let i = 0, len = modelDecorations.length; i < len; i++) { + const modelDecoration = modelDecorations[i]; + const decorationOptions = modelDecoration.options; + + if (!isModelDecorationVisible(this.model, modelDecoration)) { + continue; + } + + const viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration); + const viewRange = viewModelDecoration.range; + + decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration; + + if (decorationOptions.inlineClassName) { + const inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular); + const intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber); + const intersectedEndLineNumber = Math.min(endLineNumber, viewRange.endLineNumber); + for (let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) { + inlineDecorations[j - startLineNumber].push(inlineDecoration); + } + } + if (decorationOptions.beforeContentClassName) { + if (startLineNumber <= viewRange.startLineNumber && viewRange.startLineNumber <= endLineNumber) { + const inlineDecoration = new InlineDecoration( + new Range(viewRange.startLineNumber, viewRange.startColumn, viewRange.startLineNumber, viewRange.startColumn), + decorationOptions.beforeContentClassName, + InlineDecorationType.Before + ); + inlineDecorations[viewRange.startLineNumber - startLineNumber].push(inlineDecoration); + } + } + if (decorationOptions.afterContentClassName) { + if (startLineNumber <= viewRange.endLineNumber && viewRange.endLineNumber <= endLineNumber) { + const inlineDecoration = new InlineDecoration( + new Range(viewRange.endLineNumber, viewRange.endColumn, viewRange.endLineNumber, viewRange.endColumn), + decorationOptions.afterContentClassName, + InlineDecorationType.After + ); + inlineDecorations[viewRange.endLineNumber - startLineNumber].push(inlineDecoration); + } + } + } + + return { + decorations: decorationsInViewport, + inlineDecorations: inlineDecorations + }; + } +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/method-splitting/2.tst b/src/vs/editor/test/node/diffing/fixtures/method-splitting/2.tst new file mode 100644 index 00000000000..f74aaa81fc2 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/method-splitting/2.tst @@ -0,0 +1,67 @@ +class Test { + public getDecorationsViewportData(viewRange: Range): IDecorationsViewportData { + return null!; + } + + private _getDecorationsViewportData(viewportRange: Range, onlyMinimapDecorations: boolean): IDecorationsViewportData { + const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, filterValidationDecorations(this.configuration.options), onlyMinimapDecorations); + + const startLineNumber = viewportRange.startLineNumber; + const endLineNumber = viewportRange.endLineNumber; + + const decorationsInViewport: ViewModelDecoration[] = []; + let decorationsInViewportLen = 0; + const inlineDecorations: InlineDecoration[][] = []; + for (let j = startLineNumber; j <= endLineNumber; j++) { + inlineDecorations[j - startLineNumber] = []; + } + + for (let i = 0, len = modelDecorations.length; i < len; i++) { + const modelDecoration = modelDecorations[i]; + const decorationOptions = modelDecoration.options; + + if (!isModelDecorationVisible(this.model, modelDecoration)) { + continue; + } + + const viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration); + const viewRange = viewModelDecoration.range; + + decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration; + + if (decorationOptions.inlineClassName) { + const inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular); + const intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber); + const intersectedEndLineNumber = Math.min(endLineNumber, viewRange.endLineNumber); + for (let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) { + inlineDecorations[j - startLineNumber].push(inlineDecoration); + } + } + if (decorationOptions.beforeContentClassName) { + if (startLineNumber <= viewRange.startLineNumber && viewRange.startLineNumber <= endLineNumber) { + const inlineDecoration = new InlineDecoration( + new Range(viewRange.startLineNumber, viewRange.startColumn, viewRange.startLineNumber, viewRange.startColumn), + decorationOptions.beforeContentClassName, + InlineDecorationType.Before + ); + inlineDecorations[viewRange.startLineNumber - startLineNumber].push(inlineDecoration); + } + } + if (decorationOptions.afterContentClassName) { + if (startLineNumber <= viewRange.endLineNumber && viewRange.endLineNumber <= endLineNumber) { + const inlineDecoration = new InlineDecoration( + new Range(viewRange.endLineNumber, viewRange.endColumn, viewRange.endLineNumber, viewRange.endColumn), + decorationOptions.afterContentClassName, + InlineDecorationType.After + ); + inlineDecorations[viewRange.endLineNumber - startLineNumber].push(inlineDecoration); + } + } + } + + return { + decorations: decorationsInViewport, + inlineDecorations: inlineDecorations + }; + } +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/method-splitting/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/method-splitting/experimental.expected.diff.json new file mode 100644 index 00000000000..1c1ebba93cd --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/method-splitting/experimental.expected.diff.json @@ -0,0 +1,278 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[6,10)", + "modifiedRange": "[6,8)", + "innerChanges": [ + { + "originalRange": "[6,3 -> 6,6]", + "modifiedRange": "[6,3 -> 6,4]" + }, + { + "originalRange": "[6,7 -> 6,8]", + "modifiedRange": "[6,5 -> 6,9]" + }, + { + "originalRange": "[6,9 -> 6,9]", + "modifiedRange": "[6,10 -> 6,11]" + }, + { + "originalRange": "[6,12 -> 6,18]", + "modifiedRange": "[6,14 -> 6,14]" + }, + { + "originalRange": "[6,29 -> 6,32]", + "modifiedRange": "[6,25 -> 6,26]" + }, + { + "originalRange": "[6,33 -> 6,34]", + "modifiedRange": "[6,27 -> 6,27]" + }, + { + "originalRange": "[6,35 -> 6,35]", + "modifiedRange": "[6,28 -> 6,37]" + }, + { + "originalRange": "[6,36 -> 6,37]", + "modifiedRange": "[6,38 -> 6,39]" + }, + { + "originalRange": "[6,38 -> 6,38]", + "modifiedRange": "[6,40 -> 6,48]" + }, + { + "originalRange": "[6,39 -> 6,39]", + "modifiedRange": "[6,49 -> 6,50]" + }, + { + "originalRange": "[6,40 -> 6,42]", + "modifiedRange": "[6,51 -> 6,68]" + }, + { + "originalRange": "[6,43 -> 6,44]", + "modifiedRange": "[6,69 -> 6,72]" + }, + { + "originalRange": "[6,45 -> 6,45]", + "modifiedRange": "[6,73 -> 6,75]" + }, + { + "originalRange": "[6,46 -> 6,46]", + "modifiedRange": "[6,76 -> 6,82]" + }, + { + "originalRange": "[6,48 -> 6,51]", + "modifiedRange": "[6,84 -> 6,84]" + }, + { + "originalRange": "[6,52 -> 6,52]", + "modifiedRange": "[6,85 -> 6,88]" + }, + { + "originalRange": "[6,53 -> 6,54]", + "modifiedRange": "[6,89 -> 6,91]" + }, + { + "originalRange": "[6,58 -> 6,63]", + "modifiedRange": "[6,95 -> 6,95]" + }, + { + "originalRange": "[6,73 -> 6,75]", + "modifiedRange": "[6,105 -> 6,118]" + }, + { + "originalRange": "[7,9 -> 7,13]", + "modifiedRange": "[7,9 -> 7,12]" + }, + { + "originalRange": "[7,14 -> 7,18]", + "modifiedRange": "[7,13 -> 7,15]" + }, + { + "originalRange": "[7,19 -> 7,22]", + "modifiedRange": "[7,16 -> 7,19]" + }, + { + "originalRange": "[7,23 -> 7,28]", + "modifiedRange": "[7,20 -> 7,21]" + }, + { + "originalRange": "[7,29 -> 7,29]", + "modifiedRange": "[7,22 -> 7,23]" + }, + { + "originalRange": "[7,30 -> 7,38]", + "modifiedRange": "[7,24 -> 7,27]" + }, + { + "originalRange": "[7,64 -> 7,66]", + "modifiedRange": "[7,53 -> 7,54]" + }, + { + "originalRange": "[7,67 -> 7,69]", + "modifiedRange": "[7,55 -> 7,60]" + }, + { + "originalRange": "[7,70 -> 7,70]", + "modifiedRange": "[7,61 -> 7,62]" + }, + { + "originalRange": "[7,71 -> 7,74]", + "modifiedRange": "[7,63 -> 7,65]" + }, + { + "originalRange": "[7,75 -> 7,80]", + "modifiedRange": "[7,66 -> 7,68]" + }, + { + "originalRange": "[7,81 -> 7,81]", + "modifiedRange": "[7,69 -> 7,71]" + }, + { + "originalRange": "[7,82 -> 7,83]", + "modifiedRange": "[7,72 -> 7,73]" + }, + { + "originalRange": "[7,84 -> 7,85]", + "modifiedRange": "[7,74 -> 7,74]" + }, + { + "originalRange": "[7,86 -> 7,91]", + "modifiedRange": "[7,75 -> 7,78]" + }, + { + "originalRange": "[7,92 -> 7,97]", + "modifiedRange": "[7,79 -> 7,82]" + }, + { + "originalRange": "[7,98 -> 7,98]", + "modifiedRange": "[7,83 -> 7,84]" + }, + { + "originalRange": "[7,99 -> 7,105]", + "modifiedRange": "[7,85 -> 7,85]" + }, + { + "originalRange": "[7,112 -> 7,116]", + "modifiedRange": "[7,92 -> 7,92]" + }, + { + "originalRange": "[7,117 -> 7,119]", + "modifiedRange": "[7,93 -> 7,96]" + }, + { + "originalRange": "[7,120 -> 7,120]", + "modifiedRange": "[7,97 -> 7,104]" + }, + { + "originalRange": "[7,121 -> 7,124]", + "modifiedRange": "[7,105 -> 7,105]" + }, + { + "originalRange": "[7,125 -> 7,130]", + "modifiedRange": "[7,106 -> 7,106]" + }, + { + "originalRange": "[7,131 -> 7,132]", + "modifiedRange": "[7,107 -> 7,108]" + }, + { + "originalRange": "[7,133 -> 7,133]", + "modifiedRange": "[7,109 -> 7,111]" + }, + { + "originalRange": "[7,134 -> 7,137]", + "modifiedRange": "[7,112 -> 7,115]" + }, + { + "originalRange": "[7,138 -> 7,138]", + "modifiedRange": "[7,116 -> 7,117]" + }, + { + "originalRange": "[7,139 -> 7,139]", + "modifiedRange": "[7,118 -> 7,119]" + }, + { + "originalRange": "[7,140 -> 7,141]", + "modifiedRange": "[7,120 -> 7,123]" + }, + { + "originalRange": "[7,142 -> 7,144]", + "modifiedRange": "[7,124 -> 7,126]" + }, + { + "originalRange": "[7,145 -> 7,148]", + "modifiedRange": "[7,127 -> 7,127]" + }, + { + "originalRange": "[7,149 -> 7,149]", + "modifiedRange": "[7,128 -> 7,129]" + }, + { + "originalRange": "[7,150 -> 8,5]", + "modifiedRange": "[7,130 -> 7,130]" + }, + { + "originalRange": "[8,6 -> 8,11]", + "modifiedRange": "[7,131 -> 7,131]" + }, + { + "originalRange": "[8,15 -> 8,21]", + "modifiedRange": "[7,135 -> 7,135]" + }, + { + "originalRange": "[8,23 -> 8,23]", + "modifiedRange": "[7,137 -> 7,142]" + }, + { + "originalRange": "[8,29 -> 8,44]", + "modifiedRange": "[7,148 -> 7,148]" + }, + { + "originalRange": "[8,45 -> 8,45]", + "modifiedRange": "[7,149 -> 7,152]" + }, + { + "originalRange": "[8,46 -> 8,46]", + "modifiedRange": "[7,153 -> 7,160]" + }, + { + "originalRange": "[8,48 -> 8,48]", + "modifiedRange": "[7,162 -> 7,164]" + }, + { + "originalRange": "[8,50 -> 8,51]", + "modifiedRange": "[7,166 -> 7,170]" + }, + { + "originalRange": "[8,62 -> 8,65]", + "modifiedRange": "[7,181 -> 7,182]" + }, + { + "originalRange": "[8,66 -> 9,3]", + "modifiedRange": "[7,183 -> 7,183]" + } + ] + }, + { + "originalRange": "[11,15)", + "modifiedRange": "[9,11)", + "innerChanges": [ + { + "originalRange": "[11,1 -> 13,1]", + "modifiedRange": "[9,1 -> 9,1]" + }, + { + "originalRange": "[13,31 -> 13,31]", + "modifiedRange": "[9,31 -> 9,35]" + }, + { + "originalRange": "[14,29 -> 14,29]", + "modifiedRange": "[10,29 -> 10,33]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/method-splitting/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/method-splitting/smart.expected.diff.json new file mode 100644 index 00000000000..d0b4abc03ed --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/method-splitting/smart.expected.diff.json @@ -0,0 +1,78 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[6,10)", + "modifiedRange": "[6,8)", + "innerChanges": [ + { + "originalRange": "[6,3 -> 6,9]", + "modifiedRange": "[6,3 -> 6,11]" + }, + { + "originalRange": "[6,12 -> 6,18]", + "modifiedRange": "[6,14 -> 6,14]" + }, + { + "originalRange": "[6,29 -> 6,54]", + "modifiedRange": "[6,25 -> 6,91]" + }, + { + "originalRange": "[6,58 -> 6,63]", + "modifiedRange": "[6,95 -> 6,95]" + }, + { + "originalRange": "[6,73 -> 6,75]", + "modifiedRange": "[6,105 -> 6,118]" + }, + { + "originalRange": "[7,9 -> 7,38]", + "modifiedRange": "[7,9 -> 7,27]" + }, + { + "originalRange": "[7,64 -> 7,105]", + "modifiedRange": "[7,53 -> 7,85]" + }, + { + "originalRange": "[7,112 -> 7,124]", + "modifiedRange": "[7,92 -> 7,114]" + }, + { + "originalRange": "[7,128 -> 8,10]", + "modifiedRange": "[7,118 -> 7,130]" + }, + { + "originalRange": "[8,15 -> 8,23]", + "modifiedRange": "[7,135 -> 7,142]" + }, + { + "originalRange": "[8,29 -> 8,51]", + "modifiedRange": "[7,148 -> 7,170]" + }, + { + "originalRange": "[8,62 -> 9,3]", + "modifiedRange": "[7,181 -> 7,183]" + } + ] + }, + { + "originalRange": "[11,15)", + "modifiedRange": "[9,11)", + "innerChanges": [ + { + "originalRange": "[11,1 -> 13,1]", + "modifiedRange": "[9,1 -> 9,1]" + }, + { + "originalRange": "[13,31 -> 13,31]", + "modifiedRange": "[9,31 -> 9,35]" + }, + { + "originalRange": "[14,29 -> 14,29]", + "modifiedRange": "[10,29 -> 10,33]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/1.tst b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/1.tst new file mode 100644 index 00000000000..b9fd35ebace --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/1.tst @@ -0,0 +1,5 @@ +import * as path from 'path'; +import { Command } from 'vscode'; +import * as nls from 'vscode-nls'; + +"()()()()()()()()()()()()()" \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/2.tst b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/2.tst new file mode 100644 index 00000000000..44b380e2bdb --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/2.tst @@ -0,0 +1,5 @@ +import * as path from 'path'; +import { Command, commands } from 'vscode'; +import * as nls from 'vscode-nls'; + +"Gallicum()est()divisa()in()partres()tres()quarum()unam()est()()()()()()()()()()()()" \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/experimental.expected.diff.json new file mode 100644 index 00000000000..a0aea479fa4 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/experimental.expected.diff.json @@ -0,0 +1,30 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,17 -> 2,17]", + "modifiedRange": "[2,17 -> 2,27]" + } + ] + }, + { + "originalRange": "[5,6)", + "modifiedRange": "[5,6)", + "innerChanges": [ + { + "originalRange": "[5,2 -> 5,2]", + "modifiedRange": "[5,2 -> 5,56]" + }, + { + "originalRange": "[5,4 -> 5,4]", + "modifiedRange": "[5,58 -> 5,61]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/smart.expected.diff.json new file mode 100644 index 00000000000..430db713be3 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/minimal-diff-character/smart.expected.diff.json @@ -0,0 +1,26 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,17 -> 2,17]", + "modifiedRange": "[2,17 -> 2,27]" + } + ] + }, + { + "originalRange": "[5,6)", + "modifiedRange": "[5,6)", + "innerChanges": [ + { + "originalRange": "[5,2 -> 5,4]", + "modifiedRange": "[5,2 -> 5,61]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/subword/1.tst b/src/vs/editor/test/node/diffing/fixtures/subword/1.tst new file mode 100644 index 00000000000..d4e043303e5 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/subword/1.tst @@ -0,0 +1,5 @@ +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { IModelService } from 'vs/editor/common/services/model'; + +let x: [IEditorWorkerService, EditorSimpleWorker, IModelService, IUnicodeHighlightsResult]; \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/subword/2.tst b/src/vs/editor/test/node/diffing/fixtures/subword/2.tst new file mode 100644 index 00000000000..94f016c889b --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/subword/2.tst @@ -0,0 +1,6 @@ +import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; +import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { IModelService } from 'vs/editor/common/services/model'; + +let x: [IEditorWorkerService, EditorSimpleWorker, IModelService, IUnicodeHighlightsResult]; +let y: IDiffComputationResult; \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/subword/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/subword/experimental.expected.diff.json new file mode 100644 index 00000000000..d1e2aa57bee --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/subword/experimental.expected.diff.json @@ -0,0 +1,26 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,9 -> 2,9]", + "modifiedRange": "[2,9 -> 2,33]" + } + ] + }, + { + "originalRange": "[6,6)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,1 -> 6,1]", + "modifiedRange": "[6,1 -> 7,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/subword/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/subword/smart.expected.diff.json new file mode 100644 index 00000000000..795a5a1d248 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/subword/smart.expected.diff.json @@ -0,0 +1,21 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,11 -> 2,11]", + "modifiedRange": "[2,11 -> 2,35]" + } + ] + }, + { + "originalRange": "[6,6)", + "modifiedRange": "[6,7)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/trivial/1.txt b/src/vs/editor/test/node/diffing/fixtures/trivial/1.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/vs/editor/test/node/diffing/fixtures/trivial/2.txt b/src/vs/editor/test/node/diffing/fixtures/trivial/2.txt new file mode 100644 index 00000000000..c1b0730e013 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/trivial/2.txt @@ -0,0 +1 @@ +x \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/trivial/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/trivial/experimental.expected.diff.json new file mode 100644 index 00000000000..82196225144 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/trivial/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.txt", + "modifiedFileName": "./2.txt", + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,2)", + "innerChanges": [ + { + "originalRange": "[1,1 -> 1,1]", + "modifiedRange": "[1,1 -> 1,2]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/trivial/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/trivial/smart.expected.diff.json new file mode 100644 index 00000000000..6068fbfd5b1 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/trivial/smart.expected.diff.json @@ -0,0 +1,11 @@ +{ + "originalFileName": "./1.txt", + "modifiedFileName": "./2.txt", + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,2)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-comments/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-comments/1.tst new file mode 100644 index 00000000000..cc22cad9aae --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-comments/1.tst @@ -0,0 +1,12 @@ +interface Test { + /** + * Render +/- indicators for added/deleted changes. + * Defaults to true. + */ + renderIndicators?: boolean; + /** + * Original model should be editable? + * Defaults to false. + */ + originalEditable?: boolean; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-comments/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-comments/2.tst new file mode 100644 index 00000000000..3650024093c --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-comments/2.tst @@ -0,0 +1,17 @@ +interface Test { + /** + * Render +/- indicators for added/deleted changes. + * Defaults to true. + */ + renderIndicators?: boolean; + /** + * Shows icons in the glyph margin to revert changes. + * Default to true. + */ + renderMarginRevertIcon?: boolean; + /** + * Original model should be editable? + * Defaults to false. + */ + originalEditable?: boolean; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-comments/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-comments/experimental.expected.diff.json new file mode 100644 index 00000000000..6bdd24212ff --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-comments/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[7,7)", + "modifiedRange": "[7,12)", + "innerChanges": [ + { + "originalRange": "[7,1 -> 7,1]", + "modifiedRange": "[7,1 -> 12,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-comments/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-comments/smart.expected.diff.json new file mode 100644 index 00000000000..e5fb2b96111 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-comments/smart.expected.diff.json @@ -0,0 +1,11 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[8,8)", + "modifiedRange": "[8,13)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/1.tst new file mode 100644 index 00000000000..d709bbb57e9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/1.tst @@ -0,0 +1,33 @@ +class Test { + // ---- BEGIN diff -------------------------------------------------------------------------- + + public async computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { + const original = this._getModel(originalUrl); + const modified = this._getModel(modifiedUrl); + if (!original || !modified) { + return null; + } + + return EditorSimpleWorker.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime); + } + + public static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, ignoreTrimWhitespace: boolean, maxComputationTime: number): IDiffComputationResult | null { + const originalLines = originalTextModel.getLinesContent(); + const modifiedLines = modifiedTextModel.getLinesContent(); + const diffComputer = new DiffComputer(originalLines, modifiedLines, { + shouldComputeCharChanges: true, + shouldPostProcessCharChanges: true, + shouldIgnoreTrimWhitespace: ignoreTrimWhitespace, + shouldMakePrettyDiff: true, + maxComputationTime: maxComputationTime + }); + + const diffResult = diffComputer.computeDiff(); + const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel)); + return { + quitEarly: diffResult.quitEarly, + identical: identical, + changes: diffResult.changes + }; + } +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/2.tst new file mode 100644 index 00000000000..7de284ec7a0 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/2.tst @@ -0,0 +1,40 @@ +class Test { + // ---- BEGIN diff -------------------------------------------------------------------------- + + public async computeDiff(originalUrl: string, modifiedUrl: string, options: ILinesDiffComputerOptions): Promise { + const original = this._getModel(originalUrl); + const modified = this._getModel(modifiedUrl); + if (!original || !modified) { + return null; + } + + return EditorSimpleWorker.computeDiff(original, modified, options); + } + + public static computeDiff(originalTextModel: ICommonModel | ITextModel, modifiedTextModel: ICommonModel | ITextModel, options: ILinesDiffComputerOptions): IDiffComputationResult { + + const diffAlgorithm: ILinesDiffComputer = options.diffAlgorithm === 'experimental' ? linesDiffComputers.experimental : linesDiffComputers.smart; + + const originalLines = originalTextModel.getLinesContent(); + const modifiedLines = modifiedTextModel.getLinesContent(); + + const result = diffAlgorithm.computeDiff(originalLines, modifiedLines, options); + + const identical = (result.changes.length > 0 ? false : this._modelsAreIdentical(originalTextModel, modifiedTextModel)); + + return { + identical, + quitEarly: result.quitEarly, + changes: result.changes.map(m => ([m.originalRange.startLineNumber, m.originalRange.endLineNumberExclusive, m.modifiedRange.startLineNumber, m.modifiedRange.endLineNumberExclusive, m.innerChanges?.map(m => [ + m.originalRange.startLineNumber, + m.originalRange.startColumn, + m.originalRange.endLineNumber, + m.originalRange.endColumn, + m.modifiedRange.startLineNumber, + m.modifiedRange.startColumn, + m.modifiedRange.endLineNumber, + m.modifiedRange.endColumn, + ])])) + }; + } +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/experimental.expected.diff.json new file mode 100644 index 00000000000..1fdc1077112 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/experimental.expected.diff.json @@ -0,0 +1,508 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,1 -> 2,5]", + "modifiedRange": "[2,1 -> 2,2]" + } + ] + }, + { + "originalRange": "[4,5)", + "modifiedRange": "[4,5)", + "innerChanges": [ + { + "originalRange": "[4,69 -> 4,69]", + "modifiedRange": "[4,69 -> 4,72]" + }, + { + "originalRange": "[4,70 -> 4,71]", + "modifiedRange": "[4,73 -> 4,74]" + }, + { + "originalRange": "[4,72 -> 4,84]", + "modifiedRange": "[4,75 -> 4,75]" + }, + { + "originalRange": "[4,85 -> 4,89]", + "modifiedRange": "[4,76 -> 4,76]" + }, + { + "originalRange": "[4,91 -> 4,95]", + "modifiedRange": "[4,78 -> 4,82]" + }, + { + "originalRange": "[4,96 -> 4,103]", + "modifiedRange": "[4,83 -> 4,88]" + }, + { + "originalRange": "[4,109 -> 4,110]", + "modifiedRange": "[4,94 -> 4,98]" + }, + { + "originalRange": "[4,114 -> 4,126]", + "modifiedRange": "[4,102 -> 4,103]" + } + ] + }, + { + "originalRange": "[11,12)", + "modifiedRange": "[11,12)", + "innerChanges": [ + { + "originalRange": "[11,61 -> 11,87]", + "modifiedRange": "[11,61 -> 11,61]" + }, + { + "originalRange": "[11,88 -> 11,89]", + "modifiedRange": "[11,62 -> 11,62]" + }, + { + "originalRange": "[11,90 -> 11,93]", + "modifiedRange": "[11,63 -> 11,63]" + }, + { + "originalRange": "[11,97 -> 11,101]", + "modifiedRange": "[11,67 -> 11,68]" + } + ] + }, + { + "originalRange": "[14,15)", + "modifiedRange": "[14,18)", + "innerChanges": [ + { + "originalRange": "[14,120 -> 14,120]", + "modifiedRange": "[14,120 -> 14,123]" + }, + { + "originalRange": "[14,121 -> 14,123]", + "modifiedRange": "[14,124 -> 14,124]" + }, + { + "originalRange": "[14,124 -> 14,125]", + "modifiedRange": "[14,125 -> 14,133]" + }, + { + "originalRange": "[14,126 -> 14,128]", + "modifiedRange": "[14,134 -> 14,136]" + }, + { + "originalRange": "[14,129 -> 14,129]", + "modifiedRange": "[14,137 -> 14,141]" + }, + { + "originalRange": "[14,130 -> 14,133]", + "modifiedRange": "[14,142 -> 14,144]" + }, + { + "originalRange": "[14,135 -> 14,136]", + "modifiedRange": "[14,146 -> 14,148]" + }, + { + "originalRange": "[14,137 -> 14,143]", + "modifiedRange": "[14,149 -> 14,151]" + }, + { + "originalRange": "[14,144 -> 14,148]", + "modifiedRange": "[14,152 -> 14,152]" + }, + { + "originalRange": "[14,149 -> 14,150]", + "modifiedRange": "[14,153 -> 14,156]" + }, + { + "originalRange": "[14,151 -> 14,154]", + "modifiedRange": "[14,157 -> 14,162]" + }, + { + "originalRange": "[14,165 -> 14,168]", + "modifiedRange": "[14,173 -> 14,174]" + }, + { + "originalRange": "[14,169 -> 14,170]", + "modifiedRange": "[14,175 -> 14,179]" + }, + { + "originalRange": "[14,171 -> 14,171]", + "modifiedRange": "[14,180 -> 16,5]" + }, + { + "originalRange": "[14,172 -> 14,176]", + "modifiedRange": "[16,6 -> 16,17]" + }, + { + "originalRange": "[14,177 -> 14,178]", + "modifiedRange": "[16,18 -> 16,22]" + }, + { + "originalRange": "[14,181 -> 14,181]", + "modifiedRange": "[16,25 -> 16,30]" + }, + { + "originalRange": "[14,191 -> 14,192]", + "modifiedRange": "[16,40 -> 16,47]" + }, + { + "originalRange": "[14,196 -> 14,198]", + "modifiedRange": "[16,51 -> 16,51]" + }, + { + "originalRange": "[14,199 -> 14,200]", + "modifiedRange": "[16,52 -> 16,58]" + }, + { + "originalRange": "[14,201 -> 14,201]", + "modifiedRange": "[16,59 -> 16,63]" + }, + { + "originalRange": "[14,202 -> 14,202]", + "modifiedRange": "[16,64 -> 16,66]" + }, + { + "originalRange": "[14,203 -> 14,204]", + "modifiedRange": "[16,67 -> 16,70]" + }, + { + "originalRange": "[14,205 -> 14,205]", + "modifiedRange": "[16,71 -> 16,80]" + }, + { + "originalRange": "[14,206 -> 14,207]", + "modifiedRange": "[16,81 -> 16,83]" + }, + { + "originalRange": "[14,209 -> 14,209]", + "modifiedRange": "[16,85 -> 16,119]" + }, + { + "originalRange": "[14,210 -> 14,211]", + "modifiedRange": "[16,120 -> 17,1]" + } + ] + }, + { + "originalRange": "[17,24)", + "modifiedRange": "[20,24)", + "innerChanges": [ + { + "originalRange": "[17,1 -> 17,1]", + "modifiedRange": "[20,1 -> 21,1]" + }, + { + "originalRange": "[17,8 -> 17,8]", + "modifiedRange": "[21,8 -> 21,17]" + }, + { + "originalRange": "[17,13 -> 17,14]", + "modifiedRange": "[21,22 -> 21,33]" + }, + { + "originalRange": "[17,20 -> 17,28]", + "modifiedRange": "[21,39 -> 21,39]" + }, + { + "originalRange": "[17,32 -> 17,40]", + "modifiedRange": "[21,43 -> 21,43]" + }, + { + "originalRange": "[17,71 -> 17,72]", + "modifiedRange": "[21,74 -> 22,1]" + }, + { + "originalRange": "[18,3 -> 18,4]", + "modifiedRange": "[23,3 -> 23,6]" + }, + { + "originalRange": "[18,5 -> 18,9]", + "modifiedRange": "[23,7 -> 23,10]" + }, + { + "originalRange": "[18,10 -> 18,15]", + "modifiedRange": "[23,11 -> 23,13]" + }, + { + "originalRange": "[18,16 -> 18,19]", + "modifiedRange": "[23,14 -> 23,16]" + }, + { + "originalRange": "[18,20 -> 18,29]", + "modifiedRange": "[23,17 -> 23,20]" + }, + { + "originalRange": "[18,30 -> 18,31]", + "modifiedRange": "[23,21 -> 23,22]" + }, + { + "originalRange": "[18,32 -> 18,33]", + "modifiedRange": "[23,23 -> 23,23]" + }, + { + "originalRange": "[18,34 -> 19,4]", + "modifiedRange": "[23,24 -> 23,24]" + }, + { + "originalRange": "[19,5 -> 19,7]", + "modifiedRange": "[23,25 -> 23,25]" + }, + { + "originalRange": "[19,10 -> 19,14]", + "modifiedRange": "[23,28 -> 23,28]" + }, + { + "originalRange": "[19,14 -> 19,17]", + "modifiedRange": "[23,28 -> 23,29]" + }, + { + "originalRange": "[19,18 -> 19,26]", + "modifiedRange": "[23,30 -> 23,30]" + }, + { + "originalRange": "[19,32 -> 19,32]", + "modifiedRange": "[23,36 -> 23,56]" + }, + { + "originalRange": "[19,35 -> 20,4]", + "modifiedRange": "[23,59 -> 23,61]" + }, + { + "originalRange": "[20,5 -> 20,6]", + "modifiedRange": "[23,62 -> 23,65]" + }, + { + "originalRange": "[20,7 -> 20,9]", + "modifiedRange": "[23,66 -> 23,66]" + }, + { + "originalRange": "[20,10 -> 20,15]", + "modifiedRange": "[23,67 -> 23,67]" + }, + { + "originalRange": "[20,16 -> 20,17]", + "modifiedRange": "[23,68 -> 23,71]" + }, + { + "originalRange": "[20,18 -> 20,24]", + "modifiedRange": "[23,72 -> 23,72]" + }, + { + "originalRange": "[20,25 -> 20,29]", + "modifiedRange": "[23,73 -> 23,75]" + }, + { + "originalRange": "[20,30 -> 20,32]", + "modifiedRange": "[23,76 -> 23,78]" + }, + { + "originalRange": "[20,33 -> 20,35]", + "modifiedRange": "[23,79 -> 23,83]" + }, + { + "originalRange": "[20,36 -> 20,39]", + "modifiedRange": "[23,84 -> 23,84]" + }, + { + "originalRange": "[20,41 -> 20,44]", + "modifiedRange": "[23,86 -> 23,87]" + }, + { + "originalRange": "[20,45 -> 20,49]", + "modifiedRange": "[23,88 -> 23,89]" + }, + { + "originalRange": "[20,50 -> 20,51]", + "modifiedRange": "[23,90 -> 23,92]" + }, + { + "originalRange": "[20,52 -> 21,6]", + "modifiedRange": "[23,93 -> 23,96]" + }, + { + "originalRange": "[21,7 -> 21,9]", + "modifiedRange": "[23,97 -> 23,97]" + }, + { + "originalRange": "[21,10 -> 21,13]", + "modifiedRange": "[23,98 -> 23,98]" + }, + { + "originalRange": "[21,14 -> 21,25]", + "modifiedRange": "[23,99 -> 23,101]" + }, + { + "originalRange": "[21,26 -> 22,4]", + "modifiedRange": "[23,102 -> 23,102]" + }, + { + "originalRange": "[22,5 -> 22,8]", + "modifiedRange": "[23,103 -> 23,103]" + }, + { + "originalRange": "[22,9 -> 22,15]", + "modifiedRange": "[23,104 -> 23,107]" + }, + { + "originalRange": "[22,16 -> 22,18]", + "modifiedRange": "[23,108 -> 23,110]" + }, + { + "originalRange": "[22,19 -> 22,21]", + "modifiedRange": "[23,111 -> 23,111]" + }, + { + "originalRange": "[22,22 -> 22,26]", + "modifiedRange": "[23,112 -> 23,112]" + }, + { + "originalRange": "[22,27 -> 22,32]", + "modifiedRange": "[23,113 -> 23,113]" + }, + { + "originalRange": "[22,33 -> 22,36]", + "modifiedRange": "[23,114 -> 23,115]" + }, + { + "originalRange": "[22,37 -> 22,41]", + "modifiedRange": "[23,116 -> 23,117]" + }, + { + "originalRange": "[22,42 -> 23,4]", + "modifiedRange": "[23,118 -> 23,120]" + } + ] + }, + { + "originalRange": "[25,27)", + "modifiedRange": "[25,25)", + "innerChanges": [ + { + "originalRange": "[25,1 -> 27,1]", + "modifiedRange": "[25,1 -> 25,1]" + } + ] + }, + { + "originalRange": "[28,31)", + "modifiedRange": "[26,38)", + "innerChanges": [ + { + "originalRange": "[28,4 -> 28,4]", + "modifiedRange": "[26,4 -> 27,4]" + }, + { + "originalRange": "[28,15 -> 28,20]", + "modifiedRange": "[27,15 -> 27,16]" + }, + { + "originalRange": "[29,4 -> 29,7]", + "modifiedRange": "[28,4 -> 28,7]" + }, + { + "originalRange": "[29,8 -> 29,8]", + "modifiedRange": "[28,8 -> 28,18]" + }, + { + "originalRange": "[29,9 -> 29,10]", + "modifiedRange": "[28,19 -> 28,20]" + }, + { + "originalRange": "[29,11 -> 29,11]", + "modifiedRange": "[28,21 -> 28,47]" + }, + { + "originalRange": "[29,13 -> 29,14]", + "modifiedRange": "[28,49 -> 28,71]" + }, + { + "originalRange": "[29,15 -> 29,15]", + "modifiedRange": "[28,72 -> 28,76]" + }, + { + "originalRange": "[29,16 -> 29,16]", + "modifiedRange": "[28,77 -> 28,90]" + }, + { + "originalRange": "[29,17 -> 29,17]", + "modifiedRange": "[28,91 -> 28,94]" + }, + { + "originalRange": "[29,18 -> 29,18]", + "modifiedRange": "[28,95 -> 28,124]" + }, + { + "originalRange": "[29,19 -> 29,19]", + "modifiedRange": "[28,125 -> 28,129]" + }, + { + "originalRange": "[29,20 -> 29,20]", + "modifiedRange": "[28,130 -> 28,134]" + }, + { + "originalRange": "[29,21 -> 29,21]", + "modifiedRange": "[28,135 -> 28,176]" + }, + { + "originalRange": "[29,22 -> 29,22]", + "modifiedRange": "[28,177 -> 29,13]" + }, + { + "originalRange": "[29,24 -> 29,24]", + "modifiedRange": "[29,15 -> 29,36]" + }, + { + "originalRange": "[30,4 -> 30,6]", + "modifiedRange": "[30,4 -> 30,16]" + }, + { + "originalRange": "[30,10 -> 30,10]", + "modifiedRange": "[30,20 -> 30,21]" + }, + { + "originalRange": "[30,11 -> 30,13]", + "modifiedRange": "[30,22 -> 31,23]" + }, + { + "originalRange": "[30,14 -> 30,14]", + "modifiedRange": "[31,24 -> 33,10]" + }, + { + "originalRange": "[30,16 -> 30,17]", + "modifiedRange": "[33,12 -> 33,15]" + }, + { + "originalRange": "[30,18 -> 30,18]", + "modifiedRange": "[33,16 -> 33,19]" + }, + { + "originalRange": "[30,19 -> 30,19]", + "modifiedRange": "[33,20 -> 33,21]" + }, + { + "originalRange": "[30,20 -> 30,22]", + "modifiedRange": "[33,22 -> 33,22]" + }, + { + "originalRange": "[30,23 -> 30,23]", + "modifiedRange": "[33,23 -> 34,6]" + }, + { + "originalRange": "[30,24 -> 30,26]", + "modifiedRange": "[34,7 -> 34,16]" + }, + { + "originalRange": "[30,30 -> 30,30]", + "modifiedRange": "[34,20 -> 34,21]" + }, + { + "originalRange": "[30,31 -> 30,31]", + "modifiedRange": "[34,22 -> 37,9]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/smart.expected.diff.json new file mode 100644 index 00000000000..a1e0ab440f9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing-2/smart.expected.diff.json @@ -0,0 +1,155 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,3)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,1 -> 2,5]", + "modifiedRange": "[2,1 -> 2,2]" + } + ] + }, + { + "originalRange": "[4,5)", + "modifiedRange": "[4,5)", + "innerChanges": [ + { + "originalRange": "[4,69 -> 4,103]", + "modifiedRange": "[4,69 -> 4,88]" + }, + { + "originalRange": "[4,109 -> 4,110]", + "modifiedRange": "[4,94 -> 4,98]" + }, + { + "originalRange": "[4,114 -> 4,126]", + "modifiedRange": "[4,102 -> 4,103]" + } + ] + }, + { + "originalRange": "[11,12)", + "modifiedRange": "[11,12)", + "innerChanges": [ + { + "originalRange": "[11,61 -> 11,93]", + "modifiedRange": "[11,61 -> 11,63]" + }, + { + "originalRange": "[11,97 -> 11,101]", + "modifiedRange": "[11,67 -> 11,68]" + } + ] + }, + { + "originalRange": "[14,15)", + "modifiedRange": "[14,18)", + "innerChanges": [ + { + "originalRange": "[14,120 -> 14,154]", + "modifiedRange": "[14,120 -> 14,162]" + }, + { + "originalRange": "[14,165 -> 14,178]", + "modifiedRange": "[14,173 -> 16,22]" + }, + { + "originalRange": "[14,181 -> 14,181]", + "modifiedRange": "[16,25 -> 16,30]" + }, + { + "originalRange": "[14,191 -> 14,192]", + "modifiedRange": "[16,40 -> 16,47]" + }, + { + "originalRange": "[14,196 -> 14,211]", + "modifiedRange": "[16,51 -> 17,1]" + } + ] + }, + { + "originalRange": "[17,24)", + "modifiedRange": "[20,24)", + "innerChanges": [ + { + "originalRange": "[17,1 -> 17,1]", + "modifiedRange": "[20,1 -> 21,1]" + }, + { + "originalRange": "[17,9 -> 17,21]", + "modifiedRange": "[21,9 -> 21,15]" + }, + { + "originalRange": "[17,24 -> 17,29]", + "modifiedRange": "[21,18 -> 21,19]" + }, + { + "originalRange": "[17,32 -> 17,33]", + "modifiedRange": "[21,22 -> 21,33]" + }, + { + "originalRange": "[17,39 -> 17,40]", + "modifiedRange": "[21,39 -> 21,43]" + }, + { + "originalRange": "[17,71 -> 17,72]", + "modifiedRange": "[21,74 -> 22,1]" + }, + { + "originalRange": "[18,3 -> 19,26]", + "modifiedRange": "[23,3 -> 23,30]" + }, + { + "originalRange": "[19,32 -> 19,32]", + "modifiedRange": "[23,36 -> 23,56]" + }, + { + "originalRange": "[19,35 -> 23,4]", + "modifiedRange": "[23,59 -> 23,120]" + } + ] + }, + { + "originalRange": "[25,27)", + "modifiedRange": "[25,25)", + "innerChanges": null + }, + { + "originalRange": "[28,31)", + "modifiedRange": "[26,38)", + "innerChanges": [ + { + "originalRange": "[28,1 -> 28,1]", + "modifiedRange": "[26,1 -> 27,1]" + }, + { + "originalRange": "[28,15 -> 28,20]", + "modifiedRange": "[27,15 -> 27,16]" + }, + { + "originalRange": "[29,4 -> 29,24]", + "modifiedRange": "[28,4 -> 32,30]" + }, + { + "originalRange": "[30,4 -> 30,6]", + "modifiedRange": "[33,4 -> 33,16]" + }, + { + "originalRange": "[30,10 -> 30,13]", + "modifiedRange": "[33,20 -> 34,9]" + }, + { + "originalRange": "[30,16 -> 30,26]", + "modifiedRange": "[34,12 -> 36,16]" + }, + { + "originalRange": "[30,30 -> 30,31]", + "modifiedRange": "[36,20 -> 37,9]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/1.tst new file mode 100644 index 00000000000..ae2fd98b2ca --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/1.tst @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IRange } from 'vs/editor/common/core/range'; +import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/languages'; +import { IChange, IDiffComputationResult } from 'vs/editor/common/diff/diffComputer'; + +export class TestEditorWorkerService implements IEditorWorkerService { + + declare readonly _serviceBrand: undefined; + + canComputeUnicodeHighlights(uri: URI): boolean { return false; } + async computedUnicodeHighlights(uri: URI): Promise { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; } + async computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise { return null; } + canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; } + async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return null; } + async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined): Promise { return undefined; } + canComputeWordRanges(resource: URI): boolean { return false; } + async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { return null; } + canNavigateValueSet(resource: URI): boolean { return false; } + async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { return null; } +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/2.tst new file mode 100644 index 00000000000..9c5c6519088 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/2.tst @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IRange } from 'vs/editor/common/core/range'; +import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker'; +import { TextEdit, IInplaceReplaceSupportResult } from 'vs/editor/common/languages'; +import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; +import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; + +export class TestEditorWorkerService implements IEditorWorkerService { + + declare readonly _serviceBrand: undefined; + + canComputeUnicodeHighlights(uri: URI): boolean { return false; } + async computedUnicodeHighlights(uri: URI): Promise { return { ranges: [], hasMore: false, ambiguousCharacterCount: 0, invisibleCharacterCount: 0, nonBasicAsciiCharacterCount: 0 }; } + async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise { return null; } + canComputeDirtyDiff(original: URI, modified: URI): boolean { return false; } + async computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise { return null; } + async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined): Promise { return undefined; } + canComputeWordRanges(resource: URI): boolean { return false; } + async computeWordRanges(resource: URI, range: IRange): Promise<{ [word: string]: IRange[] } | null> { return null; } + canNavigateValueSet(resource: URI): boolean { return false; } + async navigateValueSet(resource: URI, range: IRange, up: boolean): Promise { return null; } +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/experimental.expected.diff.json new file mode 100644 index 00000000000..ecd6a96cfcb --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/experimental.expected.diff.json @@ -0,0 +1,112 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[8,9)", + "modifiedRange": "[8,9)", + "innerChanges": [ + { + "originalRange": "[8,9 -> 8,9]", + "modifiedRange": "[8,9 -> 8,33]" + } + ] + }, + { + "originalRange": "[10,11)", + "modifiedRange": "[10,12)", + "innerChanges": [ + { + "originalRange": "[10,11 -> 10,14]", + "modifiedRange": "[10,11 -> 10,17]" + }, + { + "originalRange": "[10,15 -> 10,20]", + "modifiedRange": "[10,18 -> 10,19]" + }, + { + "originalRange": "[10,24 -> 10,25]", + "modifiedRange": "[10,23 -> 10,25]" + }, + { + "originalRange": "[10,26 -> 10,27]", + "modifiedRange": "[10,26 -> 10,32]" + }, + { + "originalRange": "[10,28 -> 10,31]", + "modifiedRange": "[10,33 -> 10,33]" + }, + { + "originalRange": "[10,35 -> 10,36]", + "modifiedRange": "[10,37 -> 10,50]" + }, + { + "originalRange": "[10,37 -> 10,38]", + "modifiedRange": "[10,51 -> 10,72]" + }, + { + "originalRange": "[10,39 -> 10,40]", + "modifiedRange": "[10,73 -> 11,6]" + }, + { + "originalRange": "[10,42 -> 10,42]", + "modifiedRange": "[11,8 -> 11,18]" + }, + { + "originalRange": "[10,72 -> 10,73]", + "modifiedRange": "[11,48 -> 11,59]" + } + ] + }, + { + "originalRange": "[18,19)", + "modifiedRange": "[19,20)", + "innerChanges": [ + { + "originalRange": "[18,50 -> 18,50]", + "modifiedRange": "[19,50 -> 19,53]" + }, + { + "originalRange": "[18,51 -> 18,52]", + "modifiedRange": "[19,54 -> 19,55]" + }, + { + "originalRange": "[18,53 -> 18,65]", + "modifiedRange": "[19,56 -> 19,56]" + }, + { + "originalRange": "[18,66 -> 18,70]", + "modifiedRange": "[19,57 -> 19,57]" + }, + { + "originalRange": "[18,72 -> 18,73]", + "modifiedRange": "[19,59 -> 19,61]" + }, + { + "originalRange": "[18,74 -> 18,76]", + "modifiedRange": "[19,62 -> 19,65]" + }, + { + "originalRange": "[18,77 -> 18,78]", + "modifiedRange": "[19,66 -> 19,66]" + }, + { + "originalRange": "[18,79 -> 18,85]", + "modifiedRange": "[19,67 -> 19,74]" + }, + { + "originalRange": "[18,86 -> 18,87]", + "modifiedRange": "[19,75 -> 19,81]" + }, + { + "originalRange": "[18,88 -> 18,91]", + "modifiedRange": "[19,82 -> 19,82]" + }, + { + "originalRange": "[18,95 -> 18,107]", + "modifiedRange": "[19,86 -> 19,87]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-confusing/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/smart.expected.diff.json new file mode 100644 index 00000000000..7ad5e5e6604 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-confusing/smart.expected.diff.json @@ -0,0 +1,48 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[8,9)", + "modifiedRange": "[8,9)", + "innerChanges": [ + { + "originalRange": "[8,11 -> 8,11]", + "modifiedRange": "[8,11 -> 8,35]" + } + ] + }, + { + "originalRange": "[10,11)", + "modifiedRange": "[10,12)", + "innerChanges": [ + { + "originalRange": "[10,11 -> 10,20]", + "modifiedRange": "[10,11 -> 10,77]" + }, + { + "originalRange": "[10,24 -> 10,41]", + "modifiedRange": "[10,81 -> 11,17]" + }, + { + "originalRange": "[10,72 -> 10,73]", + "modifiedRange": "[11,48 -> 11,59]" + } + ] + }, + { + "originalRange": "[18,19)", + "modifiedRange": "[19,20)", + "innerChanges": [ + { + "originalRange": "[18,50 -> 18,91]", + "modifiedRange": "[19,50 -> 19,82]" + }, + { + "originalRange": "[18,95 -> 18,107]", + "modifiedRange": "[19,86 -> 19,87]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/1.tst new file mode 100644 index 00000000000..0a612a235e2 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/1.tst @@ -0,0 +1,10 @@ +import { findLast } from 'vs/base/common/arrays'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ITransaction, observableValue, transaction } from 'vs/base/common/observable'; +import { Range } from 'vs/editor/common/core/range'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { IFooBar, IFoo } from 'foo'; + +console.log(observableValue); + +console.log(observableValue); diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/2.tst new file mode 100644 index 00000000000..968e08f00f8 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/2.tst @@ -0,0 +1,10 @@ +import { findLast } from 'vs/base/common/arrays'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { Range } from 'vs/editor/common/core/range'; +import { ScrollType } from 'vs/editor/common/editorCommon'; +import { IFooBar, IBar, IFoo } from 'foo'; + +console.log(observableFromEvent, observableValue); + +console.log(observableValue, observableFromEvent); diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/experimental.expected.diff.json new file mode 100644 index 00000000000..7a03e3e2009 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/experimental.expected.diff.json @@ -0,0 +1,46 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,23 -> 3,23]", + "modifiedRange": "[3,23 -> 3,44]" + } + ] + }, + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,18 -> 6,18]", + "modifiedRange": "[6,18 -> 6,24]" + } + ] + }, + { + "originalRange": "[8,9)", + "modifiedRange": "[8,9)", + "innerChanges": [ + { + "originalRange": "[8,13 -> 8,13]", + "modifiedRange": "[8,13 -> 8,34]" + } + ] + }, + { + "originalRange": "[10,11)", + "modifiedRange": "[10,11)", + "innerChanges": [ + { + "originalRange": "[10,28 -> 10,28]", + "modifiedRange": "[10,28 -> 10,49]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/smart.expected.diff.json new file mode 100644 index 00000000000..ac0383a456b --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-diff-word-split/smart.expected.diff.json @@ -0,0 +1,46 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,34 -> 3,34]", + "modifiedRange": "[3,34 -> 3,55]" + } + ] + }, + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,20 -> 6,20]", + "modifiedRange": "[6,20 -> 6,26]" + } + ] + }, + { + "originalRange": "[8,9)", + "modifiedRange": "[8,9)", + "innerChanges": [ + { + "originalRange": "[8,23 -> 8,23]", + "modifiedRange": "[8,23 -> 8,44]" + } + ] + }, + { + "originalRange": "[10,11)", + "modifiedRange": "[10,11)", + "innerChanges": [ + { + "originalRange": "[10,28 -> 10,28]", + "modifiedRange": "[10,28 -> 10,49]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example1/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-example1/1.tst new file mode 100644 index 00000000000..5615fb34001 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example1/1.tst @@ -0,0 +1,15 @@ +export class EditorWorkerServiceDiffComputer implements IDiffComputer { + constructor(@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService) { } + + async computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise { + const diffs = await this.editorWorkerService.computeDiff(textModel1.uri, textModel2.uri, false, 1000); + if (!diffs || diffs.quitEarly) { + return null; + } + return diffs.changes.map((c) => LineDiff.fromLineChange(c, textModel1, textModel2)); + } +} + +function wait(ms: number): Promise { + return new Promise(r => setTimeout(r, ms)); +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example1/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-example1/2.tst new file mode 100644 index 00000000000..e923d0ec4de --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example1/2.tst @@ -0,0 +1,80 @@ +export class EditorWorkerServiceDiffComputer implements IDiffComputer { + constructor(@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService) { } + + async computeDiff(textModel1: ITextModel, textModel2: ITextModel): Promise { + const diffs = await this.editorWorkerService.computeDiff(textModel1.uri, textModel2.uri, false, 1000); + if (!diffs || diffs.quitEarly) { + return null; + } + return EditorWorkerServiceDiffComputer.fromDiffComputationResult(diffs, textModel1, textModel2); + } + + public static fromDiffComputationResult(result: IDiffComputationResult, textModel1: ITextModel, textModel2: ITextModel): LineDiff[] { + return result.changes.map((c) => fromLineChange(c, textModel1, textModel2)); + } +} + +function fromLineChange(lineChange: ILineChange, originalTextModel: ITextModel, modifiedTextModel: ITextModel): LineDiff { + let originalRange: LineRange; + if (lineChange.originalEndLineNumber === 0) { + // Insertion + originalRange = new LineRange(lineChange.originalStartLineNumber + 1, 0); + } else { + originalRange = new LineRange(lineChange.originalStartLineNumber, lineChange.originalEndLineNumber - lineChange.originalStartLineNumber + 1); + } + + let modifiedRange: LineRange; + if (lineChange.modifiedEndLineNumber === 0) { + // Deletion + modifiedRange = new LineRange(lineChange.modifiedStartLineNumber + 1, 0); + } else { + modifiedRange = new LineRange(lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber - lineChange.modifiedStartLineNumber + 1); + } + + let innerDiffs = lineChange.charChanges?.map(c => fromCharChange(c)); + if (!innerDiffs) { + innerDiffs = [diffFromLineRanges(originalRange, modifiedRange)]; + } + + return new LineDiff( + originalTextModel, + originalRange, + modifiedTextModel, + modifiedRange, + innerDiffs + ); +} + +function diffFromLineRanges(originalRange: LineRange, modifiedRange: LineRange): Diff { + // [1,1) -> [100, 101) + + if (originalRange.startLineNumber !== 1 && modifiedRange.startLineNumber !== 1) { + + } + + let original = new Range( + originalRange.startLineNumber - 1, + Number.MAX_SAFE_INTEGER, + originalRange.endLineNumberExclusive - 1, + Number.MAX_SAFE_INTEGER, + ); + + let modified = new Range( + modifiedRange.startLineNumber - 1, + Number.MAX_SAFE_INTEGER, + modifiedRange.endLineNumberExclusive - 1, + Number.MAX_SAFE_INTEGER, + ); + + return new Diff( + original, + modified + ); +} + +function fromCharChange(charChange: ICharChange): Diff { + return new Diff( + new Range(charChange.originalStartLineNumber, charChange.originalStartColumn, charChange.originalEndLineNumber, charChange.originalEndColumn), + new Range(charChange.modifiedStartLineNumber, charChange.modifiedStartColumn, charChange.modifiedEndLineNumber, charChange.modifiedEndColumn) + ); +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example1/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-example1/experimental.expected.diff.json new file mode 100644 index 00000000000..dff793f0286 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example1/experimental.expected.diff.json @@ -0,0 +1,226 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,10)", + "modifiedRange": "[9,14)", + "innerChanges": [ + { + "originalRange": "[9,10 -> 9,10]", + "modifiedRange": "[9,10 -> 9,68]" + }, + { + "originalRange": "[9,15 -> 9,15]", + "modifiedRange": "[9,73 -> 13,16]" + }, + { + "originalRange": "[9,35 -> 9,44]", + "modifiedRange": "[13,36 -> 13,36]" + } + ] + }, + { + "originalRange": "[13,15)", + "modifiedRange": "[17,80)", + "innerChanges": [ + { + "originalRange": "[13,10 -> 13,11]", + "modifiedRange": "[17,10 -> 17,20]" + }, + { + "originalRange": "[13,12 -> 13,12]", + "modifiedRange": "[17,21 -> 17,26]" + }, + { + "originalRange": "[13,13 -> 13,13]", + "modifiedRange": "[17,27 -> 17,61]" + }, + { + "originalRange": "[13,14 -> 13,14]", + "modifiedRange": "[17,62 -> 19,5]" + }, + { + "originalRange": "[13,15 -> 13,15]", + "modifiedRange": "[19,6 -> 19,34]" + }, + { + "originalRange": "[13,18 -> 13,18]", + "modifiedRange": "[19,37 -> 20,10]" + }, + { + "originalRange": "[13,17 -> 13,18]", + "modifiedRange": "[20,9 -> 21,18]" + }, + { + "originalRange": "[13,20 -> 13,20]", + "modifiedRange": "[21,20 -> 21,62]" + }, + { + "originalRange": "[13,25 -> 13,25]", + "modifiedRange": "[21,67 -> 21,74]" + }, + { + "originalRange": "[13,26 -> 13,26]", + "modifiedRange": "[21,75 -> 26,19]" + }, + { + "originalRange": "[13,28 -> 13,29]", + "modifiedRange": "[26,21 -> 27,37]" + }, + { + "originalRange": "[13,30 -> 13,30]", + "modifiedRange": "[27,38 -> 28,12]" + }, + { + "originalRange": "[13,31 -> 13,31]", + "modifiedRange": "[28,13 -> 29,3]" + }, + { + "originalRange": "[13,32 -> 13,32]", + "modifiedRange": "[29,4 -> 29,6]" + }, + { + "originalRange": "[13,33 -> 13,33]", + "modifiedRange": "[29,7 -> 30,6]" + }, + { + "originalRange": "[13,35 -> 13,37]", + "modifiedRange": "[30,8 -> 31,4]" + }, + { + "originalRange": "[13,38 -> 13,38]", + "modifiedRange": "[31,5 -> 31,6]" + }, + { + "originalRange": "[13,39 -> 13,39]", + "modifiedRange": "[31,7 -> 31,10]" + }, + { + "originalRange": "[13,40 -> 13,40]", + "modifiedRange": "[31,11 -> 34,50]" + }, + { + "originalRange": "[13,41 -> 13,41]", + "modifiedRange": "[34,51 -> 35,18]" + }, + { + "originalRange": "[14,2 -> 14,2]", + "modifiedRange": "[36,2 -> 36,7]" + }, + { + "originalRange": "[14,3 -> 14,3]", + "modifiedRange": "[36,8 -> 39,3]" + }, + { + "originalRange": "[14,13 -> 14,14]", + "modifiedRange": "[39,13 -> 40,4]" + }, + { + "originalRange": "[14,15 -> 14,15]", + "modifiedRange": "[40,5 -> 40,16]" + }, + { + "originalRange": "[14,16 -> 14,16]", + "modifiedRange": "[40,17 -> 42,3]" + }, + { + "originalRange": "[14,17 -> 14,17]", + "modifiedRange": "[42,4 -> 42,6]" + }, + { + "originalRange": "[14,18 -> 14,18]", + "modifiedRange": "[42,7 -> 44,12]" + }, + { + "originalRange": "[14,19 -> 14,19]", + "modifiedRange": "[44,13 -> 48,21]" + }, + { + "originalRange": "[14,20 -> 14,20]", + "modifiedRange": "[48,22 -> 48,28]" + }, + { + "originalRange": "[14,21 -> 14,21]", + "modifiedRange": "[48,29 -> 48,30]" + }, + { + "originalRange": "[14,22 -> 14,22]", + "modifiedRange": "[48,31 -> 48,43]" + }, + { + "originalRange": "[14,23 -> 14,24]", + "modifiedRange": "[48,44 -> 49,12]" + }, + { + "originalRange": "[14,26 -> 14,26]", + "modifiedRange": "[49,14 -> 51,20]" + }, + { + "originalRange": "[14,27 -> 14,27]", + "modifiedRange": "[51,21 -> 51,28]" + }, + { + "originalRange": "[14,28 -> 14,28]", + "modifiedRange": "[51,29 -> 51,60]" + }, + { + "originalRange": "[14,29 -> 14,29]", + "modifiedRange": "[51,61 -> 57,21]" + }, + { + "originalRange": "[14,30 -> 14,30]", + "modifiedRange": "[57,22 -> 58,5]" + }, + { + "originalRange": "[14,31 -> 14,31]", + "modifiedRange": "[58,6 -> 58,26]" + }, + { + "originalRange": "[14,32 -> 14,32]", + "modifiedRange": "[58,27 -> 58,28]" + }, + { + "originalRange": "[14,33 -> 14,33]", + "modifiedRange": "[58,29 -> 62,7]" + }, + { + "originalRange": "[14,34 -> 14,34]", + "modifiedRange": "[62,8 -> 63,27]" + }, + { + "originalRange": "[14,35 -> 14,35]", + "modifiedRange": "[63,28 -> 69,4]" + }, + { + "originalRange": "[14,36 -> 14,36]", + "modifiedRange": "[69,5 -> 69,17]" + }, + { + "originalRange": "[14,37 -> 14,37]", + "modifiedRange": "[69,18 -> 70,4]" + }, + { + "originalRange": "[14,38 -> 14,38]", + "modifiedRange": "[70,5 -> 70,11]" + }, + { + "originalRange": "[14,39 -> 14,39]", + "modifiedRange": "[70,12 -> 75,9]" + }, + { + "originalRange": "[14,40 -> 14,40]", + "modifiedRange": "[75,10 -> 75,13]" + }, + { + "originalRange": "[14,41 -> 14,42]", + "modifiedRange": "[75,14 -> 75,48]" + }, + { + "originalRange": "[14,43 -> 14,43]", + "modifiedRange": "[75,49 -> 79,2]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example1/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-example1/smart.expected.diff.json new file mode 100644 index 00000000000..e865b3fa365 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example1/smart.expected.diff.json @@ -0,0 +1,38 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,10)", + "modifiedRange": "[9,53)", + "innerChanges": null + }, + { + "originalRange": "[11,11)", + "modifiedRange": "[54,73)", + "innerChanges": null + }, + { + "originalRange": "[13,15)", + "modifiedRange": "[75,80)", + "innerChanges": [ + { + "originalRange": "[13,10 -> 13,20]", + "modifiedRange": "[75,10 -> 77,42]" + }, + { + "originalRange": "[13,25 -> 14,9]", + "modifiedRange": "[77,47 -> 78,3]" + }, + { + "originalRange": "[14,13 -> 14,37]", + "modifiedRange": "[78,7 -> 78,112]" + }, + { + "originalRange": "[14,40 -> 14,43]", + "modifiedRange": "[78,115 -> 79,2]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/1.tst new file mode 100644 index 00000000000..a719676ac6a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/1.tst @@ -0,0 +1,8 @@ +function cloneTypeReference(source: TypeReference): TypeReference { + const type = createType(source.flags); + type.symbol = source.symbol; + type.objectFlags = source.objectFlags; + type.target = source.target; + type.typeArguments = source.typeArguments; + return type; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/2.tst new file mode 100644 index 00000000000..933eb428c05 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/2.tst @@ -0,0 +1,25 @@ +function cloneTypeReference(source: TypeReference): TypeReference { + const type = createType(source.flags); + type.symbol = source.symbol; + type.objectFlags = source.objectFlags; + type.target = source.target; + type.resolvedTypeArguments = source.resolvedTypeArguments; + return type; +} + +function createDeferredTypeReference(): DeferredTypeReference { + const aliasSymbol = getAliasSymbolForTypeNode(node); + const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); + type.target = target; + type.node = node; + type.mapper = mapper; + type.aliasSymbol = aliasSymbol; + return type; +} + +function getTypeArguments(type: TypeReference): ReadonlyArray { + if (!type.resolvedTypeArguments) { + const node = type.node; + } + return type.resolvedTypeArguments; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/experimental.expected.diff.json new file mode 100644 index 00000000000..12fb2fb68e1 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/experimental.expected.diff.json @@ -0,0 +1,38 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,17)", + "innerChanges": [ + { + "originalRange": "[6,10 -> 6,11]", + "modifiedRange": "[6,10 -> 6,19]" + }, + { + "originalRange": "[6,33 -> 6,33]", + "modifiedRange": "[6,41 -> 7,12]" + }, + { + "originalRange": "[6,37 -> 6,37]", + "modifiedRange": "[7,16 -> 12,39]" + }, + { + "originalRange": "[6,46 -> 6,46]", + "modifiedRange": "[12,48 -> 16,35]" + } + ] + }, + { + "originalRange": "[9,9)", + "modifiedRange": "[19,26)", + "innerChanges": [ + { + "originalRange": "[9,1 -> 9,1]", + "modifiedRange": "[19,1 -> 26,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/smart.expected.diff.json new file mode 100644 index 00000000000..9f38ce0b43c --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-example2-ts/smart.expected.diff.json @@ -0,0 +1,33 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,17)", + "innerChanges": [ + { + "originalRange": "[6,10 -> 6,11]", + "modifiedRange": "[6,10 -> 6,19]" + }, + { + "originalRange": "[6,33 -> 6,33]", + "modifiedRange": "[6,41 -> 7,12]" + }, + { + "originalRange": "[6,37 -> 6,37]", + "modifiedRange": "[7,16 -> 12,20]" + }, + { + "originalRange": "[6,46 -> 6,46]", + "modifiedRange": "[12,29 -> 16,35]" + } + ] + }, + { + "originalRange": "[9,9)", + "modifiedRange": "[19,26)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/1.tst new file mode 100644 index 00000000000..dd606465f8b --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/1.tst @@ -0,0 +1,8 @@ +function compileProgram(): ExitStatus { + // First get any syntactic errors. + var diagnostics = program.getSyntacticDiagnostics(); + reportDiagnostics(diagnostics); + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/2.tst new file mode 100644 index 00000000000..f001c8b1c9b --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/2.tst @@ -0,0 +1,9 @@ +function compileProgram(): ExitStatus { + let diagnostics: Diagnostic[]; + + // First get and report any syntactic errors. + diagnostics = program.getSyntacticDiagnostics(); + + // If we didn't have any syntactic errors, then also try getting the global and + // semantic errors. + if (diagnostics.length === 0) { \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/experimental.expected.diff.json new file mode 100644 index 00000000000..533acf07537 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/experimental.expected.diff.json @@ -0,0 +1,42 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,5)", + "modifiedRange": "[2,6)", + "innerChanges": [ + { + "originalRange": "[2,1 -> 2,1]", + "modifiedRange": "[2,1 -> 4,1]" + }, + { + "originalRange": "[2,17 -> 2,17]", + "modifiedRange": "[4,17 -> 4,28]" + }, + { + "originalRange": "[2,39 -> 2,40]", + "modifiedRange": "[4,50 -> 4,50]" + }, + { + "originalRange": "[3,5 -> 3,9]", + "modifiedRange": "[5,5 -> 5,5]" + }, + { + "originalRange": "[3,57 -> 4,36]", + "modifiedRange": "[5,53 -> 5,53]" + } + ] + }, + { + "originalRange": "[8,9)", + "modifiedRange": "[9,10)", + "innerChanges": [ + { + "originalRange": "[8,35 -> 8,35]", + "modifiedRange": "[9,35 -> 9,36]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/smart.expected.diff.json new file mode 100644 index 00000000000..ab6e1d63356 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-experimental-bug/smart.expected.diff.json @@ -0,0 +1,42 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,5)", + "modifiedRange": "[2,6)", + "innerChanges": [ + { + "originalRange": "[2,1 -> 2,1]", + "modifiedRange": "[2,1 -> 4,1]" + }, + { + "originalRange": "[2,20 -> 2,20]", + "modifiedRange": "[4,20 -> 4,31]" + }, + { + "originalRange": "[2,39 -> 2,40]", + "modifiedRange": "[4,50 -> 4,50]" + }, + { + "originalRange": "[3,5 -> 3,9]", + "modifiedRange": "[5,5 -> 5,5]" + }, + { + "originalRange": "[3,57 -> 4,36]", + "modifiedRange": "[5,53 -> 5,53]" + } + ] + }, + { + "originalRange": "[8,9)", + "modifiedRange": "[9,10)", + "innerChanges": [ + { + "originalRange": "[8,35 -> 8,35]", + "modifiedRange": "[9,35 -> 9,36]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/1.tst new file mode 100644 index 00000000000..541a9211b39 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/1.tst @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHistoryNavigationWidget } from 'vs/base/browser/history'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; +import { IReplaceInputOptions, ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; +import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { localize } from 'vs/nls'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; + +export const historyNavigationVisible = new RawContextKey('suggestWidgetVisible', false, localize('suggestWidgetVisible', "Whether suggestion are visible")); + +const HistoryNavigationWidgetFocusContext = 'historyNavigationWidgetFocus'; +const HistoryNavigationForwardsEnablementContext = 'historyNavigationForwardsEnabled'; +const HistoryNavigationBackwardsEnablementContext = 'historyNavigationBackwardsEnabled'; + +export interface IHistoryNavigationContext extends IDisposable { + scopedContextKeyService: IContextKeyService; + historyNavigationForwardsEnablement: IContextKey; + historyNavigationBackwardsEnablement: IContextKey; +} + +let lastFocusedWidget: IHistoryNavigationWidget | undefined = undefined; +const widgets: IHistoryNavigationWidget[] = []; + +export function registerAndCreateHistoryNavigationContext(contextKeyService: IContextKeyService, widget: IHistoryNavigationWidget): IHistoryNavigationContext { + if (widgets.includes(widget)) { + throw new Error('Cannot register the same widget multiple times'); + } + + widgets.push(widget); + const disposableStore = new DisposableStore(); + const scopedContextKeyService = disposableStore.add(contextKeyService.createScoped(widget.element)); + const historyNavigationWidgetFocus = new RawContextKey(HistoryNavigationWidgetFocusContext, false).bindTo(scopedContextKeyService); + const historyNavigationForwardsEnablement = new RawContextKey(HistoryNavigationForwardsEnablementContext, true).bindTo(scopedContextKeyService); + const historyNavigationBackwardsEnablement = new RawContextKey(HistoryNavigationBackwardsEnablementContext, true).bindTo(scopedContextKeyService); + + const onDidFocus = () => { + historyNavigationWidgetFocus.set(true); + lastFocusedWidget = widget; + }; + + const onDidBlur = () => { + historyNavigationWidgetFocus.set(false); + if (lastFocusedWidget === widget) { + lastFocusedWidget = undefined; + } + }; + + // Check for currently being focused + if (widget.element === document.activeElement) { + onDidFocus(); + } + + disposableStore.add(widget.onDidFocus(() => onDidFocus())); + disposableStore.add(widget.onDidBlur(() => onDidBlur())); + disposableStore.add(toDisposable(() => { + widgets.splice(widgets.indexOf(widget), 1); + onDidBlur(); + })); + + return { + scopedContextKeyService, + historyNavigationForwardsEnablement, + historyNavigationBackwardsEnablement, + dispose() { + disposableStore.dispose(); + } + }; +} + +export class ContextScopedHistoryInputBox extends HistoryInputBox { + + constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(container, contextViewProvider, options); + this._register(registerAndCreateHistoryNavigationContext(contextKeyService, this)); + } + +} + +export class ContextScopedFindInput extends FindInput { + + constructor(container: HTMLElement | null, contextViewProvider: IContextViewProvider, options: IFindInputOptions, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(container, contextViewProvider, options); + this._register(registerAndCreateHistoryNavigationContext(contextKeyService, this.inputBox)); + } +} + +export class ContextScopedReplaceInput extends ReplaceInput { + + constructor(container: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IReplaceInputOptions, + @IContextKeyService contextKeyService: IContextKeyService, showReplaceOptions: boolean = false + ) { + super(container, contextViewProvider, showReplaceOptions, options); + this._register(registerAndCreateHistoryNavigationContext(contextKeyService, this.inputBox)); + } + +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'history.showPrevious', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + ContextKeyExpr.has(HistoryNavigationWidgetFocusContext), + ContextKeyExpr.equals(HistoryNavigationBackwardsEnablementContext, true), + historyNavigationVisible.isEqualTo(false), + ), + primary: KeyCode.UpArrow, + secondary: [KeyMod.Alt | KeyCode.UpArrow], + handler: (accessor) => { + lastFocusedWidget?.showPreviousValue(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'history.showNext', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + ContextKeyExpr.has(HistoryNavigationWidgetFocusContext), + ContextKeyExpr.equals(HistoryNavigationForwardsEnablementContext, true), + historyNavigationVisible.isEqualTo(false), + ), + primary: KeyCode.DownArrow, + secondary: [KeyMod.Alt | KeyCode.DownArrow], + handler: (accessor) => { + lastFocusedWidget?.showNextValue(); + } +}); diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/2.tst new file mode 100644 index 00000000000..0278e0c0112 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/2.tst @@ -0,0 +1,138 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHistoryNavigationWidget } from 'vs/base/browser/history'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; +import { IReplaceInputOptions, ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; +import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { localize } from 'vs/nls'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; + +export const historyNavigationVisible = new RawContextKey('suggestWidgetVisible', false, localize('suggestWidgetVisible', "Whether suggestion are visible")); + +const HistoryNavigationWidgetFocusContext = 'historyNavigationWidgetFocus'; +const HistoryNavigationForwardsEnablementContext = 'historyNavigationForwardsEnabled'; +const HistoryNavigationBackwardsEnablementContext = 'historyNavigationBackwardsEnabled'; + +export interface IHistoryNavigationContext extends IDisposable { + historyNavigationForwardsEnablement: IContextKey; + historyNavigationBackwardsEnablement: IContextKey; +} + +let lastFocusedWidget: IHistoryNavigationWidget | undefined = undefined; +const widgets: IHistoryNavigationWidget[] = []; + +export function registerAndCreateHistoryNavigationContext(scopedContextKeyService: IContextKeyService, widget: IHistoryNavigationWidget): IHistoryNavigationContext { + if (widgets.includes(widget)) { + throw new Error('Cannot register the same widget multiple times'); + } + + widgets.push(widget); + const disposableStore = new DisposableStore(); + const historyNavigationWidgetFocus = new RawContextKey(HistoryNavigationWidgetFocusContext, false).bindTo(scopedContextKeyService); + const historyNavigationForwardsEnablement = new RawContextKey(HistoryNavigationForwardsEnablementContext, true).bindTo(scopedContextKeyService); + const historyNavigationBackwardsEnablement = new RawContextKey(HistoryNavigationBackwardsEnablementContext, true).bindTo(scopedContextKeyService); + + const onDidFocus = () => { + historyNavigationWidgetFocus.set(true); + lastFocusedWidget = widget; + }; + + const onDidBlur = () => { + historyNavigationWidgetFocus.set(false); + if (lastFocusedWidget === widget) { + lastFocusedWidget = undefined; + } + }; + + // Check for currently being focused + if (widget.element === document.activeElement) { + onDidFocus(); + } + + disposableStore.add(widget.onDidFocus(() => onDidFocus())); + disposableStore.add(widget.onDidBlur(() => onDidBlur())); + disposableStore.add(toDisposable(() => { + widgets.splice(widgets.indexOf(widget), 1); + onDidBlur(); + })); + + return { + historyNavigationForwardsEnablement, + historyNavigationBackwardsEnablement, + dispose() { + disposableStore.dispose(); + } + }; +} + +export class ContextScopedHistoryInputBox extends HistoryInputBox { + + constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IHistoryInputOptions, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(container, contextViewProvider, options); + const scopedContextKeyService = this._register(contextKeyService.createScoped(this.element)); + this._register(registerAndCreateHistoryNavigationContext(scopedContextKeyService, this)); + } + +} + +export class ContextScopedFindInput extends FindInput { + + constructor(container: HTMLElement | null, contextViewProvider: IContextViewProvider, options: IFindInputOptions, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(container, contextViewProvider, options); + const scopedContextKeyService = this._register(contextKeyService.createScoped(this.inputBox.element)); + this._register(registerAndCreateHistoryNavigationContext(scopedContextKeyService, this.inputBox)); + } +} + +export class ContextScopedReplaceInput extends ReplaceInput { + + constructor(container: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IReplaceInputOptions, + @IContextKeyService contextKeyService: IContextKeyService, showReplaceOptions: boolean = false + ) { + super(container, contextViewProvider, showReplaceOptions, options); + const scopedContextKeyService = this._register(contextKeyService.createScoped(this.inputBox.element)); + this._register(registerAndCreateHistoryNavigationContext(scopedContextKeyService, this.inputBox)); + } + +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'history.showPrevious', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + ContextKeyExpr.has(HistoryNavigationWidgetFocusContext), + ContextKeyExpr.equals(HistoryNavigationBackwardsEnablementContext, true), + historyNavigationVisible.isEqualTo(false), + ), + primary: KeyCode.UpArrow, + secondary: [KeyMod.Alt | KeyCode.UpArrow], + handler: (accessor) => { + lastFocusedWidget?.showPreviousValue(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'history.showNext', + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and( + ContextKeyExpr.has(HistoryNavigationWidgetFocusContext), + ContextKeyExpr.equals(HistoryNavigationForwardsEnablementContext, true), + historyNavigationVisible.isEqualTo(false), + ), + primary: KeyCode.DownArrow, + secondary: [KeyMod.Alt | KeyCode.DownArrow], + handler: (accessor) => { + lastFocusedWidget?.showNextValue(); + } +}); diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/experimental.expected.diff.json new file mode 100644 index 00000000000..63bf0be0819 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/experimental.expected.diff.json @@ -0,0 +1,104 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[24,25)", + "modifiedRange": "[24,24)", + "innerChanges": [ + { + "originalRange": "[24,1 -> 25,1]", + "modifiedRange": "[24,1 -> 24,1]" + } + ] + }, + { + "originalRange": "[32,33)", + "modifiedRange": "[31,32)", + "innerChanges": [ + { + "originalRange": "[32,59 -> 32,59]", + "modifiedRange": "[31,59 -> 31,60]" + }, + { + "originalRange": "[32,60 -> 32,60]", + "modifiedRange": "[31,61 -> 31,66]" + } + ] + }, + { + "originalRange": "[39,40)", + "modifiedRange": "[38,38)", + "innerChanges": [ + { + "originalRange": "[39,1 -> 40,1]", + "modifiedRange": "[38,1 -> 38,1]" + } + ] + }, + { + "originalRange": "[69,70)", + "modifiedRange": "[67,67)", + "innerChanges": [ + { + "originalRange": "[69,1 -> 70,1]", + "modifiedRange": "[67,1 -> 67,1]" + } + ] + }, + { + "originalRange": "[84,85)", + "modifiedRange": "[81,83)", + "innerChanges": [ + { + "originalRange": "[84,1 -> 84,1]", + "modifiedRange": "[81,1 -> 82,1]" + }, + { + "originalRange": "[84,60 -> 84,60]", + "modifiedRange": "[82,60 -> 82,61]" + }, + { + "originalRange": "[84,61 -> 84,61]", + "modifiedRange": "[82,62 -> 82,67]" + } + ] + }, + { + "originalRange": "[95,96)", + "modifiedRange": "[93,95)", + "innerChanges": [ + { + "originalRange": "[95,1 -> 95,1]", + "modifiedRange": "[93,1 -> 94,1]" + }, + { + "originalRange": "[95,60 -> 95,60]", + "modifiedRange": "[94,60 -> 94,61]" + }, + { + "originalRange": "[95,61 -> 95,61]", + "modifiedRange": "[94,62 -> 94,67]" + } + ] + }, + { + "originalRange": "[105,106)", + "modifiedRange": "[104,106)", + "innerChanges": [ + { + "originalRange": "[105,1 -> 105,1]", + "modifiedRange": "[104,1 -> 105,1]" + }, + { + "originalRange": "[105,60 -> 105,60]", + "modifiedRange": "[105,60 -> 105,61]" + }, + { + "originalRange": "[105,61 -> 105,61]", + "modifiedRange": "[105,62 -> 105,67]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/smart.expected.diff.json new file mode 100644 index 00000000000..239c6fd8899 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing/smart.expected.diff.json @@ -0,0 +1,73 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[24,25)", + "modifiedRange": "[24,24)", + "innerChanges": null + }, + { + "originalRange": "[32,33)", + "modifiedRange": "[31,32)", + "innerChanges": [ + { + "originalRange": "[32,59 -> 32,61]", + "modifiedRange": "[31,59 -> 31,67]" + } + ] + }, + { + "originalRange": "[39,40)", + "modifiedRange": "[38,38)", + "innerChanges": null + }, + { + "originalRange": "[69,70)", + "modifiedRange": "[67,67)", + "innerChanges": null + }, + { + "originalRange": "[84,85)", + "modifiedRange": "[81,83)", + "innerChanges": [ + { + "originalRange": "[84,1 -> 84,1]", + "modifiedRange": "[81,1 -> 82,1]" + }, + { + "originalRange": "[84,60 -> 84,62]", + "modifiedRange": "[82,60 -> 82,68]" + } + ] + }, + { + "originalRange": "[95,96)", + "modifiedRange": "[93,95)", + "innerChanges": [ + { + "originalRange": "[95,1 -> 95,1]", + "modifiedRange": "[93,1 -> 94,1]" + }, + { + "originalRange": "[95,60 -> 95,62]", + "modifiedRange": "[94,60 -> 94,68]" + } + ] + }, + { + "originalRange": "[105,106)", + "modifiedRange": "[104,106)", + "innerChanges": [ + { + "originalRange": "[105,1 -> 105,1]", + "modifiedRange": "[104,1 -> 105,1]" + }, + { + "originalRange": "[105,60 -> 105,62]", + "modifiedRange": "[105,60 -> 105,68]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/1.tst new file mode 100644 index 00000000000..7cdc8bb2e94 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/1.tst @@ -0,0 +1,6 @@ +import { assertIsDefined } from 'vs/base/common/types'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, Action2, IAction2Options, IMenuService, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/2.tst new file mode 100644 index 00000000000..22f196b132b --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/2.tst @@ -0,0 +1,6 @@ +import { assertIsDefined } from 'vs/base/common/types'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, Action2, IAction2Options, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/experimental.expected.diff.json new file mode 100644 index 00000000000..e77975d759a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,43 -> 3,57]", + "modifiedRange": "[3,43 -> 3,43]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/smart.expected.diff.json new file mode 100644 index 00000000000..396591fc4dc --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing2/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,44 -> 3,58]", + "modifiedRange": "[3,44 -> 3,44]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/1.tst new file mode 100644 index 00000000000..87b71c28531 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/1.tst @@ -0,0 +1,9 @@ +import { assertIsDefined } from 'vs/base/common/types'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, Action2, IAction2Options, IMenuServiceFooManager, ServiceManagerLocator } from 'vs/platform/actions/common/actions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +ISFM,SML,SFMKL + +console.log(MenuId, Action2, IAction2Options, IMenuServiceFooManager, ServiceManagerLocator); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/2.tst new file mode 100644 index 00000000000..97340f5c4ba --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/2.tst @@ -0,0 +1,9 @@ +import { assertIsDefined } from 'vs/base/common/types'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, Action2, IAction2Options, ServiceManagerLocator } from 'vs/platform/actions/common/actions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { parseLinkedText } from 'vs/base/common/linkedText'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +SML + +console.log(MenuId, Action2, IAction2Options, ServiceManagerLocator); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/experimental.expected.diff.json new file mode 100644 index 00000000000..af483c2217d --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/experimental.expected.diff.json @@ -0,0 +1,40 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,43 -> 3,67]", + "modifiedRange": "[3,43 -> 3,43]" + } + ] + }, + { + "originalRange": "[7,8)", + "modifiedRange": "[7,8)", + "innerChanges": [ + { + "originalRange": "[7,1 -> 7,6]", + "modifiedRange": "[7,1 -> 7,1]" + }, + { + "originalRange": "[7,9 -> 7,15]", + "modifiedRange": "[7,4 -> 7,4]" + } + ] + }, + { + "originalRange": "[9,10)", + "modifiedRange": "[9,10)", + "innerChanges": [ + { + "originalRange": "[9,46 -> 9,70]", + "modifiedRange": "[9,46 -> 9,46]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/smart.expected.diff.json new file mode 100644 index 00000000000..e8211fc3870 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-fragmented-eager-diffing3/smart.expected.diff.json @@ -0,0 +1,40 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,44 -> 3,68]", + "modifiedRange": "[3,44 -> 3,44]" + } + ] + }, + { + "originalRange": "[7,8)", + "modifiedRange": "[7,8)", + "innerChanges": [ + { + "originalRange": "[7,1 -> 7,6]", + "modifiedRange": "[7,1 -> 7,1]" + }, + { + "originalRange": "[7,9 -> 7,15]", + "modifiedRange": "[7,4 -> 7,4]" + } + ] + }, + { + "originalRange": "[9,10)", + "modifiedRange": "[9,10)", + "innerChanges": [ + { + "originalRange": "[9,47 -> 9,71]", + "modifiedRange": "[9,47 -> 9,47]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/1.tst new file mode 100644 index 00000000000..29c4af7cfa7 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/1.tst @@ -0,0 +1,3 @@ +import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; +import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter'; +import { CodeEditorView } from './codeEditorView'; diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/2.tst new file mode 100644 index 00000000000..154166e11fc --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/2.tst @@ -0,0 +1,3 @@ +import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; +import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter'; +import { CodeEditorView, TitleMenu } from './codeEditorView'; diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/experimental.expected.diff.json new file mode 100644 index 00000000000..322bb5fc3be --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,24 -> 3,24]", + "modifiedRange": "[3,24 -> 3,35]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/smart.expected.diff.json new file mode 100644 index 00000000000..322bb5fc3be --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-import-ws-affinity/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,4)", + "innerChanges": [ + { + "originalRange": "[3,24 -> 3,24]", + "modifiedRange": "[3,24 -> 3,35]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-insert/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-insert/1.tst new file mode 100644 index 00000000000..5b19ed203f4 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-insert/1.tst @@ -0,0 +1 @@ +const sequence2 = new SequenceFromIntArray(tgtDocLines); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-insert/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-insert/2.tst new file mode 100644 index 00000000000..7e50aad3276 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-insert/2.tst @@ -0,0 +1 @@ +const sequence2 = new LineSequence(tgtDocLines, modifiedLines); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-insert/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-insert/experimental.expected.diff.json new file mode 100644 index 00000000000..d7b3e05ef3a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-insert/experimental.expected.diff.json @@ -0,0 +1,24 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,2)", + "innerChanges": [ + { + "originalRange": "[1,23 -> 1,23]", + "modifiedRange": "[1,23 -> 1,27]" + }, + { + "originalRange": "[1,31 -> 1,43]", + "modifiedRange": "[1,35 -> 1,35]" + }, + { + "originalRange": "[1,55 -> 1,55]", + "modifiedRange": "[1,47 -> 1,62]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-insert/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-insert/smart.expected.diff.json new file mode 100644 index 00000000000..d7b3e05ef3a --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-insert/smart.expected.diff.json @@ -0,0 +1,24 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[1,2)", + "modifiedRange": "[1,2)", + "innerChanges": [ + { + "originalRange": "[1,23 -> 1,23]", + "modifiedRange": "[1,23 -> 1,27]" + }, + { + "originalRange": "[1,31 -> 1,43]", + "modifiedRange": "[1,35 -> 1,35]" + }, + { + "originalRange": "[1,55 -> 1,55]", + "modifiedRange": "[1,47 -> 1,62]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-methods/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-methods/1.tst new file mode 100644 index 00000000000..795319a6f1e --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-methods/1.tst @@ -0,0 +1,5 @@ +interface Test { + getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; + getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; + getViewLineData(lineNumber: number): ViewLineData; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-methods/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-methods/2.tst new file mode 100644 index 00000000000..0a28ea569d5 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-methods/2.tst @@ -0,0 +1,6 @@ +interface Test { + getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; + getViewportViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; + getViewLineRenderingData(lineNumber: number): ViewLineRenderingData; + getViewLineData(lineNumber: number): ViewLineData; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-methods/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-methods/experimental.expected.diff.json new file mode 100644 index 00000000000..e5e8876bd55 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-methods/experimental.expected.diff.json @@ -0,0 +1,24 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,5)", + "innerChanges": [ + { + "originalRange": "[3,1 -> 3,2]", + "modifiedRange": "[3,1 -> 3,5]" + }, + { + "originalRange": "[3,4 -> 3,4]", + "modifiedRange": "[3,7 -> 3,15]" + }, + { + "originalRange": "[3,91 -> 3,91]", + "modifiedRange": "[3,102 -> 4,73]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-methods/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-methods/smart.expected.diff.json new file mode 100644 index 00000000000..69e0286f34d --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-methods/smart.expected.diff.json @@ -0,0 +1,24 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[3,4)", + "modifiedRange": "[3,5)", + "innerChanges": [ + { + "originalRange": "[3,1 -> 3,2]", + "modifiedRange": "[3,1 -> 3,5]" + }, + { + "originalRange": "[3,9 -> 3,9]", + "modifiedRange": "[3,12 -> 3,20]" + }, + { + "originalRange": "[3,91 -> 3,91]", + "modifiedRange": "[3,102 -> 4,73]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/1.tst new file mode 100644 index 00000000000..3dc9ae9bef7 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/1.tst @@ -0,0 +1,12 @@ +const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset); +if (childEndsAfterEnd) { + // No child after this child in the requested window, don't recurse + node = child; + level++; + continue whileLoop; +} + +const shouldContinue = collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, push, level + 1, levelPerBracketType); +if (!shouldContinue) { + return false; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/2.tst new file mode 100644 index 00000000000..ab46b84fb0b --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/2.tst @@ -0,0 +1,12 @@ +const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset); +if (childEndsAfterEnd) { + // No child after this child in the requested window, don't recurse + node = child; + level++; + continue whileLoop; +} + +const shouldContinue = collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, push, level + 1, levelPerBracket + 1, levelPerBracketType); +if (!shouldContinue) { + return false; +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/experimental.expected.diff.json new file mode 100644 index 00000000000..807bbb2dcd9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,10)", + "modifiedRange": "[9,10)", + "innerChanges": [ + { + "originalRange": "[9,116 -> 9,116]", + "modifiedRange": "[9,116 -> 9,137]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/smart.expected.diff.json new file mode 100644 index 00000000000..e155ae69309 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shift-to-ws/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,10)", + "modifiedRange": "[9,10)", + "innerChanges": [ + { + "originalRange": "[9,135 -> 9,135]", + "modifiedRange": "[9,135 -> 9,156]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shifting/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/1.tst new file mode 100644 index 00000000000..868802afac9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/1.tst @@ -0,0 +1,28 @@ +[ + { + "identifier": { + "id": "pflannery.vscode-versionlens", + "uuid": "07fc4a0a-11fc-4121-ba9a-f0d534c729d8" + }, + "preRelease": false, + "version": "1.0.9", + "installed": true + }, + { + "identifier": { + "id": "sumneko.lua", + "uuid": "3a15b5a7-be12-47e3-8445-88ee3eabc8b2" + }, + "preRelease": false, + "version": "3.5.6", + "installed": true + }, + { + "identifier": { + "id": "vscode.bat", + "uuid": "5ef96c58-076f-4167-8e40-62c9deb00496" + }, + "preRelease": false, + "version": "1.0.0" + } +] \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shifting/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/2.tst new file mode 100644 index 00000000000..ef593d66917 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/2.tst @@ -0,0 +1,19 @@ +[ + { + "identifier": { + "id": "pflannery.vscode-versionlens", + "uuid": "07fc4a0a-11fc-4121-ba9a-f0d534c729d8" + }, + "preRelease": false, + "version": "1.0.9", + "installed": true + }, + { + "identifier": { + "id": "vscode.bat", + "uuid": "5ef96c58-076f-4167-8e40-62c9deb00496" + }, + "preRelease": false, + "version": "1.0.0" + } +] \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shifting/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/experimental.expected.diff.json new file mode 100644 index 00000000000..7cae68fdc45 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[11,20)", + "modifiedRange": "[11,11)", + "innerChanges": [ + { + "originalRange": "[11,1 -> 20,1]", + "modifiedRange": "[11,1 -> 11,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-shifting/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/smart.expected.diff.json new file mode 100644 index 00000000000..32fdb9dbdd9 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-shifting/smart.expected.diff.json @@ -0,0 +1,11 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[13,22)", + "modifiedRange": "[13,13)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-strings/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-strings/1.tst new file mode 100644 index 00000000000..8a2d25d99ac --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-strings/1.tst @@ -0,0 +1,7 @@ +interface Test { + /** + * Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter. + * Defaults to 'mouseover'. + */ + showFoldingControls?: 'always' | 'mouseover'; +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-strings/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-strings/2.tst new file mode 100644 index 00000000000..1a03e3ab270 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-strings/2.tst @@ -0,0 +1,7 @@ +interface Test { + /** + * Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter. + * Defaults to 'mouseover'. + */ + showFoldingControls?: 'always' | 'never' | 'mouseover'; +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-strings/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-strings/experimental.expected.diff.json new file mode 100644 index 00000000000..1781d792519 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-strings/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,35 -> 6,35]", + "modifiedRange": "[6,35 -> 6,45]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-strings/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-strings/smart.expected.diff.json new file mode 100644 index 00000000000..eccec9997d5 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-strings/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[6,7)", + "modifiedRange": "[6,7)", + "innerChanges": [ + { + "originalRange": "[6,39 -> 6,39]", + "modifiedRange": "[6,39 -> 6,49]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/1.tst new file mode 100644 index 00000000000..0436fa2afb8 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/1.tst @@ -0,0 +1,20 @@ +class Test { + protected readonly checkboxesVisible = observableFromEvent( + this.configurationService.onDidChangeConfiguration, + () => /** @description checkboxesVisible */ this.configurationService.getValue('mergeEditor.showCheckboxes') ?? false + ); + + protected readonly showDeletionMarkers = observableFromEvent( + this.configurationService.onDidChangeConfiguration, + () => /** @description showDeletionMarkers */ this.configurationService.getValue('mergeEditor.showDeletionMarkers') + ); + + public readonly editor = this.instantiationService.createInstance( + CodeEditorWidget, + this.htmlElements.editor, + {}, + { + contributions: this.getEditorContributions(), + } + ); +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/2.tst new file mode 100644 index 00000000000..554f356ffd5 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/2.tst @@ -0,0 +1,25 @@ +class Test { + protected readonly checkboxesVisible = observableFromEvent( + this.configurationService.onDidChangeConfiguration, + () => /** @description checkboxesVisible */ this.configurationService.getValue('mergeEditor.showCheckboxes') ?? false + ); + + protected readonly showDeletionMarkers = observableFromEvent( + this.configurationService.onDidChangeConfiguration, + () => /** @description showDeletionMarkers */ this.configurationService.getValue('mergeEditor.showDeletionMarkers') ?? true + ); + + protected readonly useSimplifiedDecorations = observableFromEvent( + this.configurationService.onDidChangeConfiguration, + () => /** @description useSimplifiedDecorations */ this.configurationService.getValue('mergeEditor.useSimplifiedDecorations') ?? false + ); + + public readonly editor = this.instantiationService.createInstance( + CodeEditorWidget, + this.htmlElements.editor, + {}, + { + contributions: this.getEditorContributions(), + } + ); +} diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/experimental.expected.diff.json new file mode 100644 index 00000000000..7edda326b75 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/experimental.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,10)", + "modifiedRange": "[9,15)", + "innerChanges": [ + { + "originalRange": "[9,124 -> 9,124]", + "modifiedRange": "[9,124 -> 14,143]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/smart.expected.diff.json new file mode 100644 index 00000000000..7edda326b75 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-too-much-minimization/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[9,10)", + "modifiedRange": "[9,15)", + "innerChanges": [ + { + "originalRange": "[9,124 -> 9,124]", + "modifiedRange": "[9,124 -> 14,143]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/1.tst new file mode 100644 index 00000000000..c7454793249 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/1.tst @@ -0,0 +1,14 @@ +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import * as languages from 'vs/editor/common/languages'; +import { TriggerContext } from 'vs/editor/contrib/parameterHints/browser/parameterHintsModel'; +import { Context } from 'vs/editor/contrib/parameterHints/browser/provideSignatureHelp'; +import * as nls from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ParameterHintsWidget } from './parameterHintsWidget'; diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/2.tst new file mode 100644 index 00000000000..f08a014aafe --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/2.tst @@ -0,0 +1,16 @@ +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import * as languages from 'vs/editor/common/languages'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/browser/parameterHintsModel'; +import { Context } from 'vs/editor/contrib/parameterHints/browser/provideSignatureHelp'; +import * as nls from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ParameterHintsWidget } from './parameterHintsWidget'; diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/experimental.expected.diff.json new file mode 100644 index 00000000000..5d7e0b3c24d --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/experimental.expected.diff.json @@ -0,0 +1,30 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,2)", + "modifiedRange": "[2,3)", + "innerChanges": [ + { + "originalRange": "[2,1 -> 2,1]", + "modifiedRange": "[2,1 -> 3,1]" + } + ] + }, + { + "originalRange": "[8,9)", + "modifiedRange": "[9,11)", + "innerChanges": [ + { + "originalRange": "[8,8 -> 8,8]", + "modifiedRange": "[9,8 -> 10,8]" + }, + { + "originalRange": "[8,9 -> 8,9]", + "modifiedRange": "[10,9 -> 10,30]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/smart.expected.diff.json new file mode 100644 index 00000000000..9f3b700c75e --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unfragmented-diffing/smart.expected.diff.json @@ -0,0 +1,21 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[2,2)", + "modifiedRange": "[2,3)", + "innerChanges": null + }, + { + "originalRange": "[8,9)", + "modifiedRange": "[9,11)", + "innerChanges": [ + { + "originalRange": "[8,10 -> 8,10]", + "modifiedRange": "[9,10 -> 10,31]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/1.tst b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/1.tst new file mode 100644 index 00000000000..d32a16fbbd3 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/1.tst @@ -0,0 +1,17 @@ +test(() => { + it(() => { + console.log(1); + }) + + it(() => { + }) +}); + +test(() => { + it(() => { + console.log(1); + }) + + it(() => { + }) +}); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/2.tst b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/2.tst new file mode 100644 index 00000000000..ded30de58ef --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/2.tst @@ -0,0 +1,26 @@ +test(() => { + it(() => { + console.log(1); + }) + + it(() => { +2 console.log(2); + }) + + it(() => { + + }) +}); + +test(() => { + it(() => { + console.log(1); + }) + + it(() => { + }) + + it(() => { + + }) +}); \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/experimental.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/experimental.expected.diff.json new file mode 100644 index 00000000000..a37e3ebe187 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/experimental.expected.diff.json @@ -0,0 +1,26 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[7,7)", + "modifiedRange": "[7,12)", + "innerChanges": [ + { + "originalRange": "[7,1 -> 7,1]", + "modifiedRange": "[7,1 -> 12,1]" + } + ] + }, + { + "originalRange": "[17,17)", + "modifiedRange": "[22,26)", + "innerChanges": [ + { + "originalRange": "[17,1 -> 17,1]", + "modifiedRange": "[22,1 -> 26,1]" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/smart.expected.diff.json b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/smart.expected.diff.json new file mode 100644 index 00000000000..4c25222b3b4 --- /dev/null +++ b/src/vs/editor/test/node/diffing/fixtures/ts-unit-test/smart.expected.diff.json @@ -0,0 +1,16 @@ +{ + "originalFileName": "./1.tst", + "modifiedFileName": "./2.tst", + "diffs": [ + { + "originalRange": "[7,7)", + "modifiedRange": "[7,12)", + "innerChanges": null + }, + { + "originalRange": "[17,17)", + "modifiedRange": "[22,26)", + "innerChanges": null + } + ] +} \ No newline at end of file diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index c4d3b9e072a..ee016a91bd3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2235,6 +2235,124 @@ declare namespace monaco.editor { export interface ILineChange extends IChange { readonly charChanges: ICharChange[] | undefined; } + + /** + * A document diff provider computes the diff between two text models. + */ + export interface IDocumentDiffProvider { + /** + * Computes the diff between the text models `original` and `modified`. + */ + computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions): Promise; + /** + * Is fired when settings of the diff algorithm change that could alter the result of the diffing computation. + * Any user of this provider should recompute the diff when this event is fired. + */ + onDidChange: IEvent; + } + + /** + * Options for the diff computation. + */ + export interface IDocumentDiffProviderOptions { + /** + * When set to true, the diff should ignore whitespace changes.i + */ + ignoreTrimWhitespace: boolean; + /** + * A diff computation should throw if it takes longer than this value. + */ + maxComputationTimeMs: number; + } + + /** + * Represents a diff between two text models. + */ + export interface IDocumentDiff { + /** + * If true, both text models are identical (byte-wise). + */ + readonly identical: boolean; + /** + * If true, the diff computation timed out and the diff might not be accurate. + */ + readonly quitEarly: boolean; + /** + * Maps all modified line ranges in the original to the corresponding line ranges in the modified text model. + */ + readonly changes: LineRangeMapping[]; + } + + /** + * Maps a line range in the original text model to a line range in the modified text model. + */ + export class LineRangeMapping { + /** + * The line range in the original text model. + */ + readonly originalRange: LineRange; + /** + * The line range in the modified text model. + */ + readonly modifiedRange: LineRange; + /** + * If inner changes have not been computed, this is set to undefined. + * Otherwise, it represents the character-level diff in this line range. + * The original range of each range mapping should be contained in the original line range (same for modified). + * Must not be an empty array. + */ + readonly innerChanges: RangeMapping[] | undefined; + constructor(originalRange: LineRange, modifiedRange: LineRange, innerChanges: RangeMapping[] | undefined); + toString(): string; + } + + /** + * A range of lines (1-based). + */ + export class LineRange { + /** + * The start line number. + */ + readonly startLineNumber: number; + /** + * The end line number (exclusive). + */ + readonly endLineNumberExclusive: number; + constructor(startLineNumber: number, endLineNumberExclusive: number); + /** + * Indicates if this line range is empty. + */ + get isEmpty(): boolean; + /** + * Moves this line range by the given offset of line numbers. + */ + delta(offset: number): LineRange; + /** + * The number of lines this line range spans. + */ + get length(): number; + /** + * Creates a line range that combines this and the given line range. + */ + join(other: LineRange): LineRange; + toString(): string; + } + + /** + * Maps a range in the original text model to a range in the modified text model. + */ + export class RangeMapping { + /** + * The original range. + */ + readonly originalRange: Range; + /** + * The modified range. + */ + readonly modifiedRange: Range; + constructor(originalRange: Range, modifiedRange: Range); + toString(): string; + } export interface IDimension { width: number; height: number; @@ -3574,7 +3692,7 @@ declare namespace monaco.editor { /** * Diff Algorithm */ - diffAlgorithm?: 'smart' | 'experimental'; + diffAlgorithm?: 'smart' | 'experimental' | IDocumentDiffProvider; } /** diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts new file mode 100644 index 00000000000..5dacc07d2f7 --- /dev/null +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -0,0 +1,286 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; +import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; +import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { List } from 'vs/base/browser/ui/list/listWidget'; +import { Codicon } from 'vs/base/common/codicons'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { OS } from 'vs/base/common/platform'; +import 'vs/css!./actionWidget'; +import { localize } from 'vs/nls'; +import { IActionItem, IActionKeybindingResolver } from 'vs/platform/actionWidget/common/actionWidget'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; + +export const acceptSelectedActionCommand = 'acceptSelectedCodeAction'; +export const previewSelectedActionCommand = 'previewSelectedCodeAction'; + +export interface IRenderDelegate { + onHide(didCancel?: boolean): void; + onSelect(action: IActionItem, preview?: boolean): Promise; +} + +export interface IListMenuItem { + item?: T; + kind: ActionListItemKind; + group?: { kind?: any; icon?: { codicon: Codicon; color?: string }; title: string }; + disabled?: boolean; + label?: string; +} + +interface IActionMenuTemplateData { + readonly container: HTMLElement; + readonly icon: HTMLElement; + readonly text: HTMLElement; + readonly keybinding: KeybindingLabel; +} + +export const enum ActionListItemKind { + Action = 'action', + Header = 'header' +} + +interface IHeaderTemplateData { + readonly container: HTMLElement; + readonly text: HTMLElement; +} + +class HeaderRenderer> implements IListRenderer { + + get templateId(): string { return ActionListItemKind.Header; } + + renderTemplate(container: HTMLElement): IHeaderTemplateData { + container.classList.add('group-header'); + + const text = document.createElement('span'); + container.append(text); + + return { container, text }; + } + + renderElement(element: IListMenuItem, _index: number, templateData: IHeaderTemplateData): void { + if (!element.group) { + return; + } + templateData.text.textContent = element.group?.title; + } + + disposeTemplate(_templateData: IHeaderTemplateData): void { + // noop + } +} + +class ActionItemRenderer> implements IListRenderer { + + get templateId(): string { return 'action'; } + + constructor( + private readonly _keybindingResolver: IActionKeybindingResolver | undefined, + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { } + + renderTemplate(container: HTMLElement): IActionMenuTemplateData { + container.classList.add(this.templateId); + + const icon = document.createElement('div'); + icon.className = 'icon'; + container.append(icon); + + const text = document.createElement('span'); + text.className = 'title'; + container.append(text); + + const keybinding = new KeybindingLabel(container, OS); + + return { container, icon, text, keybinding }; + } + + renderElement(element: T, _index: number, data: IActionMenuTemplateData): void { + if (element.group?.icon) { + data.icon.className = element.group.icon.codicon.classNames; + data.icon.style.color = element.group.icon.color ?? ''; + } else { + data.icon.className = Codicon.lightBulb.classNames; + data.icon.style.color = 'var(--vscode-editorLightBulb-foreground)'; + } + if (!element.item || !element.label) { + return; + } + data.text.textContent = stripNewlines(element.label); + const binding = this._keybindingResolver?.getResolver()(element.item); + if (binding) { + data.keybinding.set(binding); + } + + if (!binding) { + dom.hide(data.keybinding.element); + } else { + dom.show(data.keybinding.element); + } + + const actionTitle = this._keybindingService.lookupKeybinding(acceptSelectedActionCommand)?.getLabel(); + const previewTitle = this._keybindingService.lookupKeybinding(previewSelectedActionCommand)?.getLabel(); + data.container.classList.toggle('option-disabled', element.disabled); + if (element.disabled) { + data.container.title = element.label; + } else if (actionTitle && previewTitle) { + data.container.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", actionTitle, previewTitle); + } else { + data.container.title = ''; + } + } + + disposeTemplate(_templateData: IActionMenuTemplateData): void { + // noop + } +} + +export class ActionList extends Disposable { + + readonly domNode: HTMLElement; + private readonly _list: List>; + + private readonly _actionLineHeight = 24; + private readonly _headerLineHeight = 26; + + private readonly _allMenuItems: IListMenuItem[]; + + private focusCondition(element: IListMenuItem): boolean { + return !element.disabled && element.kind === ActionListItemKind.Action; + } + + constructor( + user: string, + items: readonly T[], + showHeaders: boolean, + private readonly _delegate: IRenderDelegate, + resolver: IActionKeybindingResolver | undefined, + toMenuItems: (inputActions: readonly T[], showHeaders: boolean) => IListMenuItem[], + @IContextViewService private readonly _contextViewService: IContextViewService, + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { + super(); + + this.domNode = document.createElement('div'); + this.domNode.classList.add('actionList'); + const virtualDelegate: IListVirtualDelegate> = { + getHeight: element => element.kind === 'header' ? this._headerLineHeight : this._actionLineHeight, + getTemplateId: element => element.kind + }; + this._list = new List(user, this.domNode, virtualDelegate, [new ActionItemRenderer>(resolver, this._keybindingService), new HeaderRenderer()], { + keyboardSupport: true, + accessibilityProvider: { + getAriaLabel: element => { + if (element.kind === 'action') { + let label = element.label ? stripNewlines(element?.label) : ''; + if (element.disabled) { + label = localize({ key: 'customQuickFixWidget.labels', comment: [`Action widget labels for accessibility.`] }, "{0}, Disabled Reason: {1}", label, element.disabled); + } + return label; + } + return null; + }, + getWidgetAriaLabel: () => localize({ key: 'customQuickFixWidget', comment: [`An action widget option`] }, "Action Widget"), + getRole: () => 'option', + getWidgetRole: () => user + }, + }); + + this._register(this._list.onMouseClick(e => this.onListClick(e))); + this._register(this._list.onMouseOver(e => this.onListHover(e))); + this._register(this._list.onDidChangeFocus(() => this._list.domFocus())); + this._register(this._list.onDidChangeSelection(e => this.onListSelection(e))); + + this._allMenuItems = toMenuItems(items, showHeaders); + this._list.splice(0, this._list.length, this._allMenuItems); + this.focusNext(); + } + + hide(didCancel?: boolean): void { + this._delegate.onHide(didCancel); + this._contextViewService.hideContextView(); + } + + layout(minWidth: number): number { + // Updating list height, depending on how many separators and headers there are. + const numHeaders = this._allMenuItems.filter(item => item.kind === 'header').length; + const height = this._allMenuItems.length * this._actionLineHeight; + const heightWithHeaders = height + numHeaders * this._headerLineHeight - numHeaders * this._actionLineHeight; + this._list.layout(heightWithHeaders); + + // For finding width dynamically (not using resize observer) + const itemWidths: number[] = this._allMenuItems.map((_, index): number => { + const element = document.getElementById(this._list.getElementID(index)); + if (element) { + element.style.width = 'auto'; + const width = element.getBoundingClientRect().width; + element.style.width = ''; + return width; + } + return 0; + }); + + // resize observer - can be used in the future since list widget supports dynamic height but not width + const width = Math.max(...itemWidths, minWidth); + this._list.layout(heightWithHeaders, width); + + this.domNode.style.height = `${heightWithHeaders}px`; + + this._list.domFocus(); + return width; + } + + focusPrevious() { + this._list.focusPrevious(1, true, undefined, this.focusCondition); + } + + focusNext() { + this._list.focusNext(1, true, undefined, this.focusCondition); + } + + acceptSelected(preview?: boolean) { + const focused = this._list.getFocus(); + if (focused.length === 0) { + return; + } + + const focusIndex = focused[0]; + const element = this._list.element(focusIndex); + if (!this.focusCondition(element)) { + return; + } + + const event = new UIEvent(preview ? 'previewSelectedCodeAction' : 'acceptSelectedCodeAction'); + this._list.setSelection([focusIndex], event); + } + + private onListSelection(e: IListEvent>): void { + if (!e.elements.length) { + return; + } + + const element = e.elements[0]; + if (element.item && this.focusCondition(element)) { + this._delegate.onSelect(element.item, e.browserEvent?.type === 'previewSelectedEventType'); + } else { + this._list.setSelection([]); + } + } + + private onListHover(e: IListMouseEvent>): void { + this._list.setFocus(typeof e.index === 'number' ? [e.index] : []); + } + + private onListClick(e: IListMouseEvent>): void { + if (e.element && this.focusCondition(e.element)) { + this._list.setFocus([]); + } + } +} + +function stripNewlines(str: string): string { + return str.replace(/\r\n|\r|\n/g, ' '); +} diff --git a/src/vs/platform/actionWidget/browser/actionWidget.ts b/src/vs/platform/actionWidget/browser/actionWidget.ts index 996a586bebb..41a7b0732a3 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.ts +++ b/src/vs/platform/actionWidget/browser/actionWidget.ts @@ -2,36 +2,29 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import * as dom from 'vs/base/browser/dom'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; -import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { List } from 'vs/base/browser/ui/list/listWidget'; import { IAction } from 'vs/base/common/actions'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { OS } from 'vs/base/common/platform'; +import 'vs/css!./actionWidget'; import { localize } from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { acceptSelectedActionCommand, ActionList, IListMenuItem, previewSelectedActionCommand } from 'vs/platform/actionWidget/browser/actionList'; +import { ActionSet, IActionItem, IActionKeybindingResolver } from 'vs/platform/actionWidget/common/actionWidget'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Codicon } from 'vs/base/common/codicons'; -import 'vs/css!./actionWidget'; -import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ActionSet, IActionItem, IActionKeybindingResolver } from 'vs/platform/actionWidget/common/actionWidget'; -export const acceptSelectedActionCommand = 'acceptSelectedCodeAction'; -export const previewSelectedActionCommand = 'previewSelectedCodeAction'; -export const ActionWidgetContextKeys = { +const ActionWidgetContextKeys = { Visible: new RawContextKey('actionWidgetVisible', false, localize('actionWidgetVisible', "Whether the action widget list is visible")) }; + export interface IRenderDelegate { onHide(didCancel?: boolean): void; onSelect(action: IActionItem, preview?: boolean): Promise; @@ -43,141 +36,24 @@ export interface IActionShowOptions { readonly showHeaders?: boolean; } -export interface IListMenuItem { - item?: T; - kind: ActionListItemKind; - group?: { kind?: any; icon?: { codicon: Codicon; color?: string }; title: string }; - disabled?: boolean; - label?: string; -} - -export interface IActionList extends IDisposable { - hide(didCancel?: boolean): void; - focusPrevious(): void; - focusNext(): void; - layout(minWidth: number): void; - acceptSelected(preview?: boolean): void; - readonly domNode: HTMLElement; -} - -export interface IActionMenuTemplateData { - readonly container: HTMLElement; - readonly icon: HTMLElement; - readonly text: HTMLElement; - readonly keybinding: KeybindingLabel; -} - -export const enum ActionListItemKind { - Action = 'action', - Header = 'header' -} - -interface IHeaderTemplateData { - readonly container: HTMLElement; - readonly text: HTMLElement; -} - -export class HeaderRenderer> implements IListRenderer { - - get templateId(): string { return ActionListItemKind.Header; } - - renderTemplate(container: HTMLElement): IHeaderTemplateData { - container.classList.add('group-header'); - - const text = document.createElement('span'); - container.append(text); - - return { container, text }; - } - - renderElement(element: IListMenuItem, _index: number, templateData: IHeaderTemplateData): void { - if (!element.group) { - return; - } - templateData.text.textContent = element.group?.title; - } - - disposeTemplate(_templateData: IHeaderTemplateData): void { - // noop - } -} - - -export class ActionItemRenderer> implements IListRenderer { - - get templateId(): string { return 'action'; } - - constructor(private readonly _keybindingResolver: IActionKeybindingResolver | undefined, @IKeybindingService private readonly _keybindingService: IKeybindingService) { - } - - renderTemplate(container: HTMLElement): IActionMenuTemplateData { - container.classList.add(this.templateId); - - const icon = document.createElement('div'); - icon.className = 'icon'; - container.append(icon); - - const text = document.createElement('span'); - text.className = 'title'; - container.append(text); - - const keybinding = new KeybindingLabel(container, OS); - - return { container, icon, text, keybinding }; - } - - renderElement(element: T, _index: number, data: IActionMenuTemplateData): void { - if (element.group?.icon) { - data.icon.className = element.group.icon.codicon.classNames; - data.icon.style.color = element.group.icon.color ?? ''; - } else { - data.icon.className = Codicon.lightBulb.classNames; - data.icon.style.color = 'var(--vscode-editorLightBulb-foreground)'; - } - if (!element.item || !element.label) { - return; - } - data.text.textContent = stripNewlines(element.label); - const binding = this._keybindingResolver?.getResolver()(element.item); - if (binding) { - data.keybinding.set(binding); - } - - if (!binding) { - dom.hide(data.keybinding.element); - } else { - dom.show(data.keybinding.element); - } - const actionTitle = this._keybindingService.lookupKeybinding(acceptSelectedActionCommand)?.getLabel(); - const previewTitle = this._keybindingService.lookupKeybinding(previewSelectedActionCommand)?.getLabel(); - data.container.classList.toggle('option-disabled', element.disabled); - if (element.disabled) { - data.container.title = element.label; - } else if (actionTitle && previewTitle) { - data.container.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", actionTitle, previewTitle); - } else { - data.container.title = ''; - } - } - disposeTemplate(_templateData: IActionMenuTemplateData): void { - // noop - } -} - export const IActionWidgetService = createDecorator('actionWidgetService'); + export interface IActionWidgetService { readonly _serviceBrand: undefined; + show(user: string, toMenuItems: (inputQuickFixes: readonly any[], showHeaders: boolean) => IListMenuItem[], delegate: IRenderDelegate, actions: ActionSet, anchor: IAnchor, container: HTMLElement | undefined, options: IActionShowOptions): Promise; hide(): void; - isVisible: boolean; - acceptSelected(preview?: boolean): void; - focusPrevious(): void; - focusNext(): void; + + readonly isVisible: boolean; } -export class ActionWidgetService extends Disposable implements IActionWidgetService { +class ActionWidgetService extends Disposable implements IActionWidgetService { declare readonly _serviceBrand: undefined; - get isVisible() { return ActionWidgetContextKeys.Visible.getValue(this._contextKeyService) || false; } + + get isVisible() { + return ActionWidgetContextKeys.Visible.getValue(this._contextKeyService) || false; + } + private _showDisabled = false; private _currentShowingContext?: { readonly user: string; @@ -189,21 +65,21 @@ export class ActionWidgetService extends Disposable implements IActionWidgetServ readonly delegate: IRenderDelegate; readonly resolver?: IActionKeybindingResolver; }; - private _list = this._register(new MutableDisposable>()); - constructor(@ICommandService readonly _commandService: ICommandService, - @IContextViewService readonly contextViewService: IContextViewService, - @IKeybindingService readonly keybindingService: IKeybindingService, - @ITelemetryService readonly _telemetryService: ITelemetryService, - @IContextKeyService readonly _contextKeyService: IContextKeyService, - @IInstantiationService readonly _instantiationService: IInstantiationService) { - super(); + private readonly _list = this._register(new MutableDisposable>()); + + constructor( + @ICommandService private readonly _commandService: ICommandService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + super(); } async show(user: string, toMenuItems: (inputQuickFixes: readonly IActionItem[], showHeaders: boolean) => IListMenuItem[], delegate: IRenderDelegate, actions: ActionSet, anchor: IAnchor, container: HTMLElement | undefined, options: IActionShowOptions, resolver?: IActionKeybindingResolver): Promise { this._currentShowingContext = undefined; const visibleContext = ActionWidgetContextKeys.Visible.bindTo(this._contextKeyService); - const list = this._instantiationService.createInstance(ActionList, user, actions.allActions, true, delegate, resolver, toMenuItems); const actionsToShow = options.includeDisabledActions && (this._showDisabled || actions.validActions.length === 0) ? actions.allActions : actions.validActions; if (!actionsToShow.length) { @@ -213,6 +89,7 @@ export class ActionWidgetService extends Disposable implements IActionWidgetServ this._currentShowingContext = { user, toMenuItems, delegate, actions, anchor, container, options, resolver }; + const list = this._instantiationService.createInstance(ActionList, user, actionsToShow, true, delegate, resolver, toMenuItems); this.contextViewService.showContextView({ getAnchor: () => anchor, render: (container: HTMLElement) => { @@ -306,6 +183,7 @@ export class ActionWidgetService extends Disposable implements IActionWidgetServ actionBar.push(actions, { icon: false, label: true }); return actionBar; } + private _getActionBarActions(actions: ActionSet, options: IActionShowOptions): IAction[] { const resultActions = actions.documentation.map((command): IAction => ({ id: command.id, @@ -333,8 +211,10 @@ export class ActionWidgetService extends Disposable implements IActionWidgetServ run: () => this._toggleShowDisabled(true) }); } + return resultActions; } + /** * Toggles whether the disabled actions in the action widget are visible or not. */ @@ -354,156 +234,10 @@ export class ActionWidgetService extends Disposable implements IActionWidgetServ this._currentShowingContext = undefined; this._list.value?.hide(didCancel); } - } + registerSingleton(IActionWidgetService, ActionWidgetService, InstantiationType.Delayed); -export class ActionList extends Disposable implements IActionList { - - readonly domNode: HTMLElement; - private readonly _list: List>; - - private readonly _actionLineHeight = 24; - private readonly _headerLineHeight = 26; - - private readonly _allMenuItems: IListMenuItem[]; - - private focusCondition(element: IListMenuItem): boolean { - return !element.disabled && element.kind === ActionListItemKind.Action; - } - - constructor( - user: string, - items: readonly T[], - showHeaders: boolean, - private readonly _delegate: IRenderDelegate, - resolver: IActionKeybindingResolver | undefined, - toMenuItems: (inputActions: readonly T[], showHeaders: boolean) => IListMenuItem[], - @IContextViewService private readonly _contextViewService: IContextViewService, - @IKeybindingService private readonly _keybindingService: IKeybindingService - ) { - super(); - this.domNode = document.createElement('div'); - this.domNode.classList.add('actionList'); - const virtualDelegate: IListVirtualDelegate> = { - getHeight: element => element.kind === 'header' ? this._headerLineHeight : this._actionLineHeight, - getTemplateId: element => element.kind - }; - this._list = new List(user, this.domNode, virtualDelegate, [new ActionItemRenderer>(resolver, this._keybindingService), new HeaderRenderer()], { - keyboardSupport: true, - accessibilityProvider: { - getAriaLabel: element => { - if (element.kind === 'action') { - let label = element.label ? stripNewlines(element?.label) : ''; - if (element.disabled) { - label = localize({ key: 'customQuickFixWidget.labels', comment: [`Action widget labels for accessibility.`] }, "{0}, Disabled Reason: {1}", label, element.disabled); - } - return label; - } - return null; - }, - getWidgetAriaLabel: () => localize({ key: 'customQuickFixWidget', comment: [`An action widget option`] }, "Action Widget"), - getRole: () => 'option', - getWidgetRole: () => user - }, - }); - - this._register(this._list.onMouseClick(e => this.onListClick(e))); - this._register(this._list.onMouseOver(e => this.onListHover(e))); - this._register(this._list.onDidChangeFocus(() => this._list.domFocus())); - this._register(this._list.onDidChangeSelection(e => this.onListSelection(e))); - - this._allMenuItems = toMenuItems(items, showHeaders); - this._list.splice(0, this._list.length, this._allMenuItems); - this.focusNext(); - } - - hide(didCancel?: boolean): void { - this._delegate.onHide(didCancel); - this._contextViewService.hideContextView(); - } - - layout(minWidth: number): number { - // Updating list height, depending on how many separators and headers there are. - const numHeaders = this._allMenuItems.filter(item => item.kind === 'header').length; - const height = this._allMenuItems.length * this._actionLineHeight; - const heightWithHeaders = height + numHeaders * this._headerLineHeight - numHeaders * this._actionLineHeight; - this._list.layout(heightWithHeaders); - - // For finding width dynamically (not using resize observer) - const itemWidths: number[] = this._allMenuItems.map((_, index): number => { - const element = document.getElementById(this._list.getElementID(index)); - if (element) { - element.style.width = 'auto'; - const width = element.getBoundingClientRect().width; - element.style.width = ''; - return width; - } - return 0; - }); - - // resize observer - can be used in the future since list widget supports dynamic height but not width - const width = Math.max(...itemWidths, minWidth); - this._list.layout(heightWithHeaders, width); - - this.domNode.style.height = `${heightWithHeaders}px`; - - this._list.domFocus(); - return width; - } - - focusPrevious() { - this._list.focusPrevious(1, true, undefined, this.focusCondition); - } - - focusNext() { - this._list.focusNext(1, true, undefined, this.focusCondition); - } - - acceptSelected(preview?: boolean) { - const focused = this._list.getFocus(); - if (focused.length === 0) { - return; - } - - const focusIndex = focused[0]; - const element = this._list.element(focusIndex); - if (!this.focusCondition(element)) { - return; - } - - const event = new UIEvent(preview ? 'previewSelectedCodeAction' : 'acceptSelectedCodeAction'); - this._list.setSelection([focusIndex], event); - } - - private onListSelection(e: IListEvent>): void { - if (!e.elements.length) { - return; - } - - const element = e.elements[0]; - if (element.item && this.focusCondition(element)) { - this._delegate.onSelect(element.item, e.browserEvent?.type === 'previewSelectedEventType'); - } else { - this._list.setSelection([]); - } - } - - private onListHover(e: IListMouseEvent>): void { - this._list.setFocus(typeof e.index === 'number' ? [e.index] : []); - } - - private onListClick(e: IListMouseEvent>): void { - if (e.element && this.focusCondition(e.element)) { - this._list.setFocus([]); - } - } -} - -export function stripNewlines(str: string): string { - return str.replace(/\r\n|\r|\n/g, ' '); -} - const weight = KeybindingWeight.EditorContrib + 1000; registerAction2(class extends Action2 { @@ -547,7 +281,10 @@ registerAction2(class extends Action2 { } run(accessor: ServicesAccessor): void { - accessor.get(IActionWidgetService).focusPrevious(); + const widgetService = accessor.get(IActionWidgetService); + if (widgetService instanceof ActionWidgetService) { + widgetService.focusPrevious(); + } } }); @@ -570,7 +307,10 @@ registerAction2(class extends Action2 { } run(accessor: ServicesAccessor): void { - accessor.get(IActionWidgetService).focusNext(); + const widgetService = accessor.get(IActionWidgetService); + if (widgetService instanceof ActionWidgetService) { + widgetService.focusNext(); + } } }); @@ -592,7 +332,10 @@ registerAction2(class extends Action2 { } run(accessor: ServicesAccessor): void { - accessor.get(IActionWidgetService).acceptSelected(); + const widgetService = accessor.get(IActionWidgetService); + if (widgetService instanceof ActionWidgetService) { + widgetService.acceptSelected(); + } } }); @@ -613,7 +356,9 @@ registerAction2(class extends Action2 { } run(accessor: ServicesAccessor): void { - accessor.get(IActionWidgetService).acceptSelected(true); + const widgetService = accessor.get(IActionWidgetService); + if (widgetService instanceof ActionWidgetService) { + widgetService.acceptSelected(true); + } } }); - diff --git a/src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts b/src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts similarity index 94% rename from src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts rename to src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts index d90616fef82..b5a25d3b1df 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncProfilesStorageService.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfileStorageService.ts @@ -25,8 +25,8 @@ export interface IStorageValue { readonly target: StorageTarget; } -export const IUserDataSyncProfilesStorageService = createDecorator('IUserDataSyncProfilesStorageService'); -export interface IUserDataSyncProfilesStorageService { +export const IUserDataProfileStorageService = createDecorator('IUserDataProfileStorageService'); +export interface IUserDataProfileStorageService { readonly _serviceBrand: undefined; /** @@ -54,7 +54,7 @@ export interface IUserDataSyncProfilesStorageService { withProfileScopedStorageService(profile: IUserDataProfile, fn: (storageService: IStorageService) => Promise): Promise; } -export abstract class AbstractUserDataSyncProfilesStorageService extends Disposable implements IUserDataSyncProfilesStorageService { +export abstract class AbstractUserDataProfileStorageService extends Disposable implements IUserDataProfileStorageService { _serviceBrand: undefined; diff --git a/src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts similarity index 98% rename from src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts rename to src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts index 28d292e1515..e3ac3124a10 100644 --- a/src/vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProfileStorageChanges, IProfileStorageValueChanges } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { IProfileStorageChanges, IProfileStorageValueChanges } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { loadKeyTargets, StorageScope, TARGET_KEY } from 'vs/platform/storage/common/storage'; import { IBaseSerializableStorageRequest } from 'vs/platform/storage/common/storageIpc'; import { IStorageMain } from 'vs/platform/storage/electron-main/storageMain'; diff --git a/src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts similarity index 89% rename from src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts rename to src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts index 1d0679a115b..b0d9c5082fd 100644 --- a/src/vs/platform/userDataSync/electron-sandbox/userDataSyncProfilesStorageService.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService.ts @@ -8,12 +8,12 @@ import { MutableDisposable } from 'vs/base/common/lifecycle'; import { IStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; -import { AbstractUserDataSyncProfilesStorageService, IProfileStorageChanges, IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { AbstractUserDataProfileStorageService, IProfileStorageChanges, IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { isProfileUsingDefaultStorage, IStorageService } from 'vs/platform/storage/common/storage'; import { ApplicationStorageDatabaseClient, ProfileStorageDatabaseClient } from 'vs/platform/storage/common/storageIpc'; import { IUserDataProfile, IUserDataProfilesService, reviveProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -export class UserDataSyncProfilesStorageService extends AbstractUserDataSyncProfilesStorageService implements IUserDataSyncProfilesStorageService { +export class UserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService { private readonly _onDidChange: Emitter; readonly onDidChange: Event; diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts similarity index 90% rename from src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts rename to src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts index a282d1e2d84..76075183f30 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileStorageService.test.ts @@ -8,7 +8,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { InMemoryStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage'; -import { AbstractUserDataSyncProfilesStorageService, IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { AbstractUserDataProfileStorageService, IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { InMemoryStorageService, loadKeyTargets, StorageTarget, TARGET_KEY } from 'vs/platform/storage/common/storage'; import { IUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; @@ -26,7 +26,7 @@ class TestStorageDatabase extends InMemoryStorageDatabase { } } -export class TestUserDataSyncProfilesStorageService extends AbstractUserDataSyncProfilesStorageService implements IUserDataSyncProfilesStorageService { +export class TestUserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService { readonly onDidChange = Event.None; private databases = new Map(); @@ -46,11 +46,11 @@ suite('ProfileStorageService', () => { const disposables = new DisposableStore(); const profile = toUserDataProfile('test', 'test', URI.file('foo')); - let testObject: TestUserDataSyncProfilesStorageService; + let testObject: TestUserDataProfileStorageService; let storage: Storage; setup(async () => { - testObject = disposables.add(new TestUserDataSyncProfilesStorageService(new InMemoryStorageService())); + testObject = disposables.add(new TestUserDataProfileStorageService(new InMemoryStorageService())); storage = new Storage(await testObject.createStorageDatabase(profile)); await storage.init(); }); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index c40c3a42a88..a98bbeff949 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -31,7 +31,7 @@ import { AbstractInitializer, AbstractSynchroniser, getSyncResourceLogLabel, IAc import { IMergeResult as IExtensionMergeResult, merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; import { Change, IRemoteUserData, ISyncData, ISyncExtension, ISyncExtensionWithVersion, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; -import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; type IExtensionResourceMergeResult = IAcceptResult & IExtensionMergeResult; @@ -128,7 +128,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse @ITelemetryService telemetryService: ITelemetryService, @IExtensionStorageService extensionStorageService: IExtensionStorageService, @IUriIdentityService uriIdentityService: IUriIdentityService, - @IUserDataSyncProfilesStorageService userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IUserDataProfileStorageService userDataProfileStorageService: IUserDataProfileStorageService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super({ syncResource: SyncResource.Extensions, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); @@ -137,7 +137,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse Event.any( Event.filter(this.extensionManagementService.onDidInstallExtensions, (e => e.some(({ local }) => !!local))), Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error)), - Event.filter(userDataSyncProfilesStorageService.onDidChange, e => e.valueChanges.some(({ profile, changes }) => this.syncResource.profile.id === profile.id && changes.some(change => change.key === DISABLED_EXTENSIONS_STORAGE_PATH))), + Event.filter(userDataProfileStorageService.onDidChange, e => e.valueChanges.some(({ profile, changes }) => this.syncResource.profile.id === profile.id && changes.some(change => change.key === DISABLED_EXTENSIONS_STORAGE_PATH))), extensionStorageService.onDidChangeExtensionStorageToSync)(() => this.triggerLocalChange())); } @@ -341,7 +341,7 @@ export class LocalExtensionsProvider { constructor( @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -510,7 +510,7 @@ export class LocalExtensionsProvider { } private async withProfileScopedServices(profile: IUserDataProfile, fn: (extensionEnablementService: IGlobalExtensionEnablementService, extensionStorageService: IExtensionStorageService) => Promise): Promise { - return this.userDataSyncProfilesStorageService.withProfileScopedStorageService(profile, + return this.userDataProfileStorageService.withProfileScopedStorageService(profile, async storageService => { const disposables = new DisposableStore(); const instantiationService = this.instantiationService.createChild(new ServiceCollection([IStorageService, storageService])); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 03ac74ee019..7b1ed5ca021 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -28,7 +28,7 @@ import { merge } from 'vs/platform/userDataSync/common/globalStateMerge'; import { ALL_SYNC_RESOURCES, Change, createSyncHeaders, getEnablementKey, IGlobalState, IRemoteUserData, IStorageValue, ISyncData, IUserData, IUserDataSyncBackupStoreService, IUserDataSynchroniser, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncStoreService, SyncResource, SYNC_SERVICE_URL_TYPE, UserDataSyncError, UserDataSyncErrorCode, UserDataSyncStoreType, USER_DATA_SYNC_SCHEME } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; const argvStoragePrefx = 'globalState.argv.'; @@ -80,7 +80,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs constructor( profile: IUserDataProfile, collection: string | undefined, - @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, @IFileService fileService: IFileService, @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService, @@ -100,7 +100,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs Event.any( /* Locale change */ Event.filter(fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource)), - Event.filter(userDataSyncProfilesStorageService.onDidChange, e => { + Event.filter(userDataProfileStorageService.onDidChange, e => { /* StorageTarget has changed in profile storage */ if (e.targetChanges.some(profile => this.syncResource.profile.id === profile.id)) { return true; @@ -282,7 +282,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } private async getStorageKeys(lastSyncGlobalState: IGlobalState | null): Promise { - const storageData = await this.userDataSyncProfilesStorageService.readStorageData(this.syncResource.profile); + const storageData = await this.userDataProfileStorageService.readStorageData(this.syncResource.profile); const user: string[] = [], machine: string[] = []; for (const [key, value] of storageData) { if (value.target === StorageTarget.USER) { @@ -309,7 +309,7 @@ export class LocalGlobalStateProvider { constructor( @IFileService private readonly fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUserDataSyncProfilesStorageService private readonly userDataSyncProfilesStorageService: IUserDataSyncProfilesStorageService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService ) { } @@ -324,7 +324,7 @@ export class LocalGlobalStateProvider { } } } - const storageData = await this.userDataSyncProfilesStorageService.readStorageData(profile); + const storageData = await this.userDataProfileStorageService.readStorageData(profile); for (const [key, value] of storageData) { if (value.value && value.target === StorageTarget.USER) { storage[key] = { version: 1, value: value.value }; @@ -349,7 +349,7 @@ export class LocalGlobalStateProvider { const syncResourceLogLabel = getSyncResourceLogLabel(SyncResource.GlobalState, profile); const argv: IStringDictionary = {}; const updatedStorage = new Map(); - const storageData = await this.userDataSyncProfilesStorageService.readStorageData(profile); + const storageData = await this.userDataProfileStorageService.readStorageData(profile); const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary): void => { for (const key of keys) { if (key.startsWith(argvStoragePrefx)) { @@ -389,7 +389,7 @@ export class LocalGlobalStateProvider { if (updatedStorage.size) { this.logService.trace(`${syncResourceLogLabel}: Updating global state...`); - await this.userDataSyncProfilesStorageService.updateStorageData(profile, updatedStorage, StorageTarget.USER); + await this.userDataProfileStorageService.updateStorageData(profile, updatedStorage, StorageTarget.USER); this.logService.info(`${syncResourceLogLabel}: Updated global state`, [...updatedStorage.keys()]); } } diff --git a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts index 110991b2d86..b7f6c141b02 100644 --- a/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts +++ b/src/vs/platform/userDataSync/test/common/globalStateSync.test.ts @@ -13,7 +13,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { IGlobalState, ISyncData, IUserDataSyncStoreService, SyncResource, SyncStatus } from 'vs/platform/userDataSync/common/userDataSync'; -import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient'; @@ -214,7 +214,7 @@ suite('GlobalStateSync', () => { await testClient.sync(); const syncedProfile = testClient.instantiationService.get(IUserDataProfilesService).profiles.find(p => p.id === profile.id)!; - const profileStorage = await testClient.instantiationService.get(IUserDataSyncProfilesStorageService).readStorageData(syncedProfile); + const profileStorage = await testClient.instantiationService.get(IUserDataProfileStorageService).readStorageData(syncedProfile); assert.strictEqual(profileStorage.get('a')?.value, 'value1'); assert.strictEqual(await readLocale(testClient), 'en'); @@ -241,7 +241,7 @@ suite('GlobalStateSync', () => { } async function updateUserStorageForProfile(key: string, value: string, profile: IUserDataProfile, client: UserDataSyncClient): Promise { - const storageService = client.instantiationService.get(IUserDataSyncProfilesStorageService); + const storageService = client.instantiationService.get(IUserDataProfileStorageService); const data = new Map(); data.set(key, value); await storageService.updateStorageData(profile, data, StorageTarget.USER); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index dbbccba5373..7626dd2527e 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -43,8 +43,8 @@ import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyn import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { InMemoryUserDataProfilesService, IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; -import { IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; -import { TestUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/test/common/userDataSyncProfilesStorageService.test'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { TestUserDataProfileStorageService } from 'vs/platform/userDataProfile/test/common/userDataProfileStorageService.test'; export class UserDataSyncClient extends Disposable { @@ -94,7 +94,7 @@ export class UserDataSyncClient extends Disposable { const storageService = new TestStorageService(userDataProfilesService.defaultProfile); this.instantiationService.stub(IStorageService, this._register(storageService)); - this.instantiationService.stub(IUserDataSyncProfilesStorageService, this._register(new TestUserDataSyncProfilesStorageService(storageService))); + this.instantiationService.stub(IUserDataProfileStorageService, this._register(new TestUserDataProfileStorageService(storageService))); const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index b6e15e9300d..e3ace7a45e9 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -216,7 +216,7 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { } break; case 'on': - text += '\n\n - ' + nls.localize('configuredOn', "The editor is configured to be permanently optimized for usage with a Screen Reader - you can change this by editing the setting `editor.accessibilitySupport`."); + text += '\n\n - ' + nls.localize('configuredOn', "The editor is configured to be permanently optimized for usage with a Screen Reader - you can change this via the command `Toggle Screen Reader Accessibility Mode` or by editing the setting `editor.accessibilitySupport`"); break; case 'off': text += '\n\n - ' + nls.localize('configuredOff', "The editor is configured to never be optimized for usage with a Screen Reader."); @@ -327,3 +327,22 @@ registerEditorCommand(new AccessibilityHelpCommand({ primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] } })); + +class ToggleScreenReaderMode extends Action2 { + + constructor() { + super({ + id: 'editor.action.toggleScreenReaderAccessibilityMode', + title: { value: nls.localize('toggleScreenReaderMode', "Toggle Screen Reader Accessibility Mode"), original: 'Toggle Screen Reader Accessibility Mode' }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + const value = configurationService.getValue('editor.accessibilitySupport'); + configurationService.updateValue('editor.accessibilitySupport', value === 'on' ? 'off' : 'on'); + } +} + +registerAction2(ToggleScreenReaderMode); diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index c94bdc405fa..14fb4f0b6fa 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { EditorAction, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; +import { EditorAction, EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -51,11 +51,11 @@ export class SimpleCommentEditor extends CodeEditorWidget { const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { isSimpleWidget: true, contributions: [ - { id: MenuPreventer.ID, ctor: MenuPreventer }, - { id: ContextMenuController.ID, ctor: ContextMenuController }, - { id: SuggestController.ID, ctor: SuggestController }, - { id: SnippetController2.ID, ctor: SnippetController2 }, - { id: TabCompletionController.ID, ctor: TabCompletionController }, + { id: MenuPreventer.ID, ctor: MenuPreventer, instantiation: EditorContributionInstantiation.Eager }, + { id: ContextMenuController.ID, ctor: ContextMenuController, instantiation: EditorContributionInstantiation.Eager }, + { id: SuggestController.ID, ctor: SuggestController, instantiation: EditorContributionInstantiation.Eager }, + { id: SnippetController2.ID, ctor: SnippetController2, instantiation: EditorContributionInstantiation.Eager }, + { id: TabCompletionController.ID, ctor: TabCompletionController, instantiation: EditorContributionInstantiation.Eager }, ] }; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts index 713b45c1503..c7243ad2bdb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInputModel.ts @@ -11,7 +11,6 @@ import { derived, IObservable, observableFromEvent, observableValue } from 'vs/b import { basename, isEqual } from 'vs/base/common/resources'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; -import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider'; import { IModelService } from 'vs/editor/common/services/model'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; @@ -103,15 +102,14 @@ export class TempFileMergeEditorModeFactory implements IMergeEditorInputModelFac ); store.add(temporaryResultModel); - const diffProvider = this._instantiationService.createInstance(WorkerBasedDocumentDiffProvider); + const mergeDiffComputer = this._instantiationService.createInstance(MergeDiffComputer); const model = this._instantiationService.createInstance( MergeEditorModel, base.object.textEditorModel, input1Data, input2Data, temporaryResultModel, - this._instantiationService.createInstance(MergeDiffComputer, diffProvider), - this._instantiationService.createInstance(MergeDiffComputer, diffProvider), + mergeDiffComputer, { resetResult: true, }, @@ -314,15 +312,15 @@ export class WorkspaceMergeEditorModeFactory implements IMergeEditorInputModelFa const hasConflictMarkers = lines.some(l => l.startsWith(conflictMarkers.start)); const resetResult = hasConflictMarkers; - const diffProvider = this._instantiationService.createInstance(WorkerBasedDocumentDiffProvider); + const mergeDiffComputer = this._instantiationService.createInstance(MergeDiffComputer); + const model = this._instantiationService.createInstance( MergeEditorModel, base.object.textEditorModel, input1Data, input2Data, result.object.textEditorModel, - this._instantiationService.createInstance(MergeDiffComputer, diffProvider), - this._instantiationService.createInstance(MergeDiffComputer, diffProvider), + mergeDiffComputer, { resetResult }, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts index d6fc05da492..6098b3cc3c0 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/diffComputer.ts @@ -7,9 +7,9 @@ import { assertFn, checkAdjacentItems } from 'vs/base/common/assert'; import { IReader, observableFromEvent } from 'vs/base/common/observable'; import { isDefined } from 'vs/base/common/types'; import { Range } from 'vs/editor/common/core/range'; -import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider'; import { LineRange as DiffLineRange, RangeMapping as DiffRangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; import { ITextModel } from 'vs/editor/common/model'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { DetailedLineRangeMapping, RangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; @@ -23,30 +23,33 @@ export interface IMergeDiffComputerResult { } export class MergeDiffComputer implements IMergeDiffComputer { - private readonly mergeAlgorithm = observableFromEvent( this.configurationService.onDidChangeConfiguration, () => /** @description config: mergeAlgorithm.diffAlgorithm */ this.configurationService.getValue<'smart' | 'experimental'>('mergeEditor.diffAlgorithm') ); constructor( - private readonly documentDiffProvider: IDocumentDiffProvider, + @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { } async computeDiff(textModel1: ITextModel, textModel2: ITextModel, reader: IReader): Promise { const diffAlgorithm = this.mergeAlgorithm.read(reader); - const result = await this.documentDiffProvider.computeDiff( - textModel1, - textModel2, + const result = await this.editorWorkerService.computeDiff( + textModel1.uri, + textModel2.uri, { ignoreTrimWhitespace: false, - maxComputationTime: 0, - diffAlgorithm, - } + maxComputationTimeMs: 0, + }, + diffAlgorithm, ); + if (!result) { + throw new Error('Diff computation failed'); + } + if (textModel1.isDisposed() || textModel2.isDisposed()) { return { diffs: null }; } @@ -76,15 +79,15 @@ export class MergeDiffComputer implements IMergeDiffComputer { } } -function toLineRange(range: DiffLineRange): LineRange { +export function toLineRange(range: DiffLineRange): LineRange { return new LineRange(range.startLineNumber, range.length); } -function toRangeMapping(mapping: DiffRangeMapping): RangeMapping { +export function toRangeMapping(mapping: DiffRangeMapping): RangeMapping { return new RangeMapping(mapping.originalRange, mapping.modifiedRange); } -function normalizeRangeMapping(rangeMapping: RangeMapping, inputTextModel: ITextModel, outputTextModel: ITextModel): RangeMapping | undefined { +export function normalizeRangeMapping(rangeMapping: RangeMapping, inputTextModel: ITextModel, outputTextModel: ITextModel): RangeMapping | undefined { const inputRangeEmpty = rangeMapping.inputRange.isEmpty(); const outputRangeEmpty = rangeMapping.outputRange.isEmpty(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 3829f745842..e3bbbd7c81f 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -29,7 +29,7 @@ export interface InputData { export class MergeEditorModel extends EditorModel { private readonly input1TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input1.textModel, this.diffComputer)); private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2.textModel, this.diffComputer)); - private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.resultTextModel, this.diffComputerConflictProjection)); + private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.resultTextModel, this.diffComputer)); public readonly modifiedBaseRanges = derived('modifiedBaseRanges', (reader) => { const input1Diffs = this.input1TextModelDiffs.diffs.read(reader); const input2Diffs = this.input2TextModelDiffs.diffs.read(reader); @@ -54,7 +54,6 @@ export class MergeEditorModel extends EditorModel { readonly input2: InputData, readonly resultTextModel: ITextModel, private readonly diffComputer: IMergeDiffComputer, - private readonly diffComputerConflictProjection: IMergeDiffComputer, private readonly options: { resetResult: boolean }, public readonly telemetry: MergeEditorTelemetry, @IModelService private readonly modelService: IModelService, @@ -442,14 +441,9 @@ export class MergeEditorModel extends EditorModel { } } - if (markInputAsHandled !== false) { - if (markInputAsHandled === true || markInputAsHandled === 1) { - existingState.handledInput1.set(true, transaction); - } - if (markInputAsHandled === true || markInputAsHandled === 2) { - existingState.handledInput2.set(true, transaction); - } - } + // always set conflict as handled + existingState.handledInput1.set(true, transaction); + existingState.handledInput2.set(true, transaction); } public resetDirtyConflictsToBase(): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/projectedDocumentDiffProvider.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/projectedDocumentDiffProvider.ts deleted file mode 100644 index 8d00c978af1..00000000000 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/projectedDocumentDiffProvider.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Position } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; -import { IDocumentDiff, IDocumentDiffProvider, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider'; -import { LineRange, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/model'; -import { TextModelProjection } from 'vs/workbench/contrib/mergeEditor/browser/model/textModelProjection'; - -export class ProjectedDiffComputer implements IDocumentDiffProvider { - private readonly projectedTextModel = new Map(); - - constructor( - private readonly underlyingDiffComputer: IDocumentDiffProvider, - @IModelService private readonly modelService: IModelService, - ) { - - } - - async computeDiff( - textModel1: ITextModel, - textModel2: ITextModel, - options: IDocumentDiffProviderOptions - ): Promise { - let proj = this.projectedTextModel.get(textModel2); - if (!proj) { - proj = TextModelProjection.create(textModel2, { - blockToRemoveStartLinePrefix: '<<<<<<<', - blockToRemoveEndLinePrefix: '>>>>>>>', - }, this.modelService); - this.projectedTextModel.set(textModel2, proj); - } - - const result = await this.underlyingDiffComputer.computeDiff(textModel1, proj.targetDocument, options); - - const transformer = proj.createMonotonousReverseTransformer(); - - return { - identical: result.identical, - quitEarly: result.quitEarly, - - changes: result.changes.map(d => { - const start = transformer.transform(new Position(d.modifiedRange.startLineNumber, 1)).lineNumber; - - const innerChanges = d.innerChanges?.map(m => { - const start = transformer.transform(m.modifiedRange.getStartPosition()); - const end = transformer.transform(m.modifiedRange.getEndPosition()); - return new RangeMapping(m.originalRange, Range.fromPositions(start, end)); - }); - - const end = transformer.transform(new Position(d.modifiedRange.endLineNumberExclusive, 1)).lineNumber; - - return new LineRangeMapping( - d.originalRange, - new LineRange(start, end), - innerChanges - ); - }) - }; - } -} diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts index a542fca11df..69af60c8f21 100644 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -5,7 +5,8 @@ import * as assert from 'assert'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { transaction } from 'vs/base/common/observable'; +import { IReader, transaction } from 'vs/base/common/observable'; +import { isDefined } from 'vs/base/common/types'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { linesDiffComputers } from 'vs/editor/common/diff/linesDiffComputers'; @@ -13,7 +14,8 @@ import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { MergeDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; +import { IMergeDiffComputer, IMergeDiffComputerResult, normalizeRangeMapping, toLineRange, toRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; +import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { MergeEditorTelemetry } from 'vs/workbench/contrib/mergeEditor/browser/telemetry'; @@ -260,23 +262,27 @@ class MergeModelInterface extends Disposable { const baseTextModel = this._register(createTextModel(options.base, options.languageId)); const resultTextModel = this._register(createTextModel(options.result, options.languageId)); - const diffComputer = instantiationService.createInstance(MergeDiffComputer, - { - // Don't go through the webworker to improve unit test performance & reduce dependencies - async computeDiff(textModel1, textModel2) { - const result = linesDiffComputers.smart.computeDiff( - textModel1.getLinesContent(), - textModel2.getLinesContent(), - { ignoreTrimWhitespace: false, maxComputationTime: 10000 } - ); - return { - changes: result.changes, - quitEarly: result.quitEarly, - identical: result.changes.length === 0 - }; - }, + const diffComputer: IMergeDiffComputer = { + async computeDiff(textModel1: ITextModel, textModel2: ITextModel, reader: IReader): Promise { + const result = await linesDiffComputers.smart.computeDiff( + textModel1.getLinesContent(), + textModel2.getLinesContent(), + { ignoreTrimWhitespace: false, maxComputationTimeMs: 10000 } + ); + const changes = result.changes.map(c => + new DetailedLineRangeMapping( + toLineRange(c.originalRange), + textModel1, + toLineRange(c.modifiedRange), + textModel2, + c.innerChanges?.map(ic => normalizeRangeMapping(toRangeMapping(ic), textModel1, textModel2)).filter(isDefined) + ) + ); + return { + diffs: changes + }; } - ); + }; this.mergeModel = this._register(instantiationService.createInstance(MergeEditorModel, baseTextModel, @@ -294,7 +300,6 @@ class MergeModelInterface extends Disposable { }, resultTextModel, diffComputer, - diffComputer, { resetResult: false }, diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 03a83291f0e..1e3f67378b1 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -51,91 +51,30 @@ function isInstallExtensionPick(item: QuickPickInput): item is I type KernelQuickPickItem = IQuickPickItem | InstallExtensionPick | KernelPick | SourcePick; const KERNEL_PICKER_UPDATE_DEBOUNCE = 200; -registerAction2(class extends Action2 { - constructor() { - super({ - id: SELECT_KERNEL_ID, - category: NOTEBOOK_ACTIONS_CATEGORY, - title: { value: localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, - // precondition: NOTEBOOK_IS_ACTIVE_EDITOR, - icon: selectKernelIcon, - f1: true, - menu: [{ - id: MenuId.EditorTitle, - when: ContextKeyExpr.and( - NOTEBOOK_IS_ACTIVE_EDITOR, - ContextKeyExpr.notEquals('config.notebook.globalToolbar', true) - ), - group: 'navigation', - order: -10 - }, { - id: MenuId.NotebookToolbar, - when: ContextKeyExpr.equals('config.notebook.globalToolbar', true), - group: 'status', - order: -10 - }, { - id: MenuId.InteractiveToolbar, - when: NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), - group: 'status', - order: -10 - }], - description: { - description: localize('notebookActions.selectKernel.args', "Notebook Kernel Args"), - args: [ - { - name: 'kernelInfo', - description: 'The kernel info', - schema: { - 'type': 'object', - 'required': ['id', 'extension'], - 'properties': { - 'id': { - 'type': 'string' - }, - 'extension': { - 'type': 'string' - }, - 'notebookEditorId': { - 'type': 'string' - } - } - } - } - ] - }, - }); - } +type KernelQuickPickContext = + { id: string; extension: string } | + { notebookEditorId: string } | + { id: string; extension: string; notebookEditorId: string } | + { ui?: boolean; notebookEditor?: NotebookEditorWidget }; - async run(accessor: ServicesAccessor, context?: - { id: string; extension: string } | - { notebookEditorId: string } | - { id: string; extension: string; notebookEditorId: string } | - { ui?: boolean; notebookEditor?: NotebookEditorWidget } | - undefined - ): Promise { - const notebookKernelService = accessor.get(INotebookKernelService); - const editorService = accessor.get(IEditorService); - const productService = accessor.get(IProductService); - const quickInputService = accessor.get(IQuickInputService); - const labelService = accessor.get(ILabelService); - const logService = accessor.get(ILogService); - const paneCompositeService = accessor.get(IPaneCompositePartService); - const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); - const extensionHostService = accessor.get(IExtensionService); +interface IKernelPickerStrategy { + showQuickPick(context?: KernelQuickPickContext): Promise; +} - let editor: INotebookEditor | undefined; - if (context !== undefined && 'notebookEditorId' in context) { - const editorId = context.notebookEditorId; - const matchingEditor = editorService.visibleEditorPanes.find((editorPane) => { - const notebookEditor = getNotebookEditorFromEditorPane(editorPane); - return notebookEditor?.getId() === editorId; - }); - editor = getNotebookEditorFromEditorPane(matchingEditor); - } else if (context !== undefined && 'notebookEditor' in context) { - editor = context?.notebookEditor; - } else { - editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - } +class KernelPickerFlatStrategy implements IKernelPickerStrategy { + constructor( + private readonly _notebookKernelService: INotebookKernelService, + private readonly _editorService: IEditorService, + private readonly _productService: IProductService, + private readonly _quickInputService: IQuickInputService, + private readonly _labelService: ILabelService, + private readonly _logService: ILogService, + private readonly _paneCompositePartService: IPaneCompositePartService, + private readonly _extensionWorkbenchService: IExtensionsWorkbenchService, + private readonly _extensionService: IExtensionService, + ) { } + async showQuickPick(context?: KernelQuickPickContext): Promise { + const editor = this._getEditorFromContext(context); if (!editor || !editor.hasModel()) { return false; @@ -151,7 +90,7 @@ registerAction2(class extends Action2 { const notebook = editor.textModel; const scopedContextKeyService = editor.scopedContextKeyService; - const matchResult = notebookKernelService.getMatchingKernel(notebook); + const matchResult = this._notebookKernelService.getMatchingKernel(notebook); const { selected, all } = matchResult; if (selected && controllerId && selected.id === controllerId && ExtensionIdentifier.equals(selected.extension, extensionId)) { @@ -169,41 +108,41 @@ registerAction2(class extends Action2 { } } if (!newKernel) { - logService.warn(`wanted kernel DOES NOT EXIST, wanted: ${wantedId}, all: ${all.map(k => k.id)}`); + this._logService.warn(`wanted kernel DOES NOT EXIST, wanted: ${wantedId}, all: ${all.map(k => k.id)}`); return false; } } if (newKernel) { - notebookKernelService.selectKernelForNotebook(newKernel, notebook); + this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); return true; } - const quickPick = quickInputService.createQuickPick(); - const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, notebookKernelService, scopedContextKeyService); + const quickPick = this._quickInputService.createQuickPick(); + const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); quickPick.items = quickPickItems; quickPick.canSelectMany = false; quickPick.placeholder = selected - ? localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) - : localize('prompt.placeholder.select', "Select kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })); + ? localize('prompt.placeholder.change', "Change kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true })) + : localize('prompt.placeholder.select', "Select kernel for '{0}'", this._labelService.getUriLabel(notebook.uri, { relative: true })); - quickPick.busy = notebookKernelService.getKernelDetectionTasks(notebook).length > 0; + quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0; - const kernelDetectionTaskListener = notebookKernelService.onDidChangeKernelDetectionTasks(() => { - quickPick.busy = notebookKernelService.getKernelDetectionTasks(notebook).length > 0; + const kernelDetectionTaskListener = this._notebookKernelService.onDidChangeKernelDetectionTasks(() => { + quickPick.busy = this._notebookKernelService.getKernelDetectionTasks(notebook).length > 0; }); // run extension recommendataion task if quickPickItems is empty const extensionRecommendataionPromise = quickPickItems.length === 0 - ? createCancelablePromise(token => this._showInstallKernelExtensionRecommendation(notebook, quickPick, extensionWorkbenchService, token)) + ? createCancelablePromise(token => this._showInstallKernelExtensionRecommendation(notebook, quickPick, this._extensionWorkbenchService, token)) : undefined; const kernelChangeEventListener = Event.debounce( Event.any( - notebookKernelService.onDidChangeSourceActions, - notebookKernelService.onDidAddKernel, - notebookKernelService.onDidRemoveKernel, - notebookKernelService.onDidChangeNotebookAffinity + this._notebookKernelService.onDidChangeSourceActions, + this._notebookKernelService.onDidAddKernel, + this._notebookKernelService.onDidRemoveKernel, + this._notebookKernelService.onDidChangeNotebookAffinity ), (last, _current) => last, KERNEL_PICKER_UPDATE_DEBOUNCE @@ -213,8 +152,8 @@ registerAction2(class extends Action2 { extensionRecommendataionPromise?.cancel(); const currentActiveItems = quickPick.activeItems; - const matchResult = notebookKernelService.getMatchingKernel(notebook); - const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, notebookKernelService, scopedContextKeyService); + const matchResult = this._notebookKernelService.getMatchingKernel(notebook); + const quickPickItems = this._getKernelPickerQuickPickItems(notebook, matchResult, this._notebookKernelService, scopedContextKeyService); quickPick.keepScrollPosition = true; // recalcuate active items @@ -262,27 +201,27 @@ registerAction2(class extends Action2 { if (pick) { if (isKernelPick(pick)) { newKernel = pick.kernel; - notebookKernelService.selectKernelForNotebook(newKernel, notebook); + this._notebookKernelService.selectKernelForNotebook(newKernel, notebook); return true; } // actions if (pick.id === 'install') { await this._showKernelExtension( - paneCompositeService, - extensionWorkbenchService, - extensionHostService, + this._paneCompositePartService, + this._extensionWorkbenchService, + this._extensionService, notebook.viewType ); // suggestedExtension must be defined for this option to be shown, but still check to make TS happy } else if (isInstallExtensionPick(pick)) { await this._showKernelExtension( - paneCompositeService, - extensionWorkbenchService, - extensionHostService, + this._paneCompositePartService, + this._extensionWorkbenchService, + this._extensionService, notebook.viewType, pick.extensionId, - productService.quality !== 'stable' + this._productService.quality !== 'stable' ); } else if (isSourcePick(pick)) { // selected explicilty, it should trigger the execution? @@ -293,6 +232,24 @@ registerAction2(class extends Action2 { return false; } + private _getEditorFromContext(context?: KernelQuickPickContext): INotebookEditor | undefined { + let editor: INotebookEditor | undefined; + if (context !== undefined && 'notebookEditorId' in context) { + const editorId = context.notebookEditorId; + const matchingEditor = this._editorService.visibleEditorPanes.find((editorPane) => { + const notebookEditor = getNotebookEditorFromEditorPane(editorPane); + return notebookEditor?.getId() === editorId; + }); + editor = getNotebookEditorFromEditorPane(matchingEditor); + } else if (context !== undefined && 'notebookEditor' in context) { + editor = context?.notebookEditor; + } else { + editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); + } + + return editor; + } + private _getKernelPickerQuickPickItems( notebookTextModel: NotebookTextModel, matchResult: INotebookKernelMatchResult, @@ -301,37 +258,14 @@ registerAction2(class extends Action2 { ): QuickPickInput[] { const { selected, all, suggestions, hidden } = matchResult; - function toQuickPick(kernel: INotebookKernel) { - const res = { - kernel, - picked: kernel.id === selected?.id, - label: kernel.label, - description: kernel.description, - detail: kernel.detail - }; - if (kernel.id === selected?.id) { - if (!res.description) { - res.description = localize('current1', "Currently Selected"); - } else { - res.description = localize('current2', "{0} - Currently Selected", res.description); - } - } - return res; - } const quickPickItems: QuickPickInput[] = []; if (all.length) { // Always display suggested kernels on the top. - if (suggestions.length) { - quickPickItems.push({ - type: 'separator', - label: localize('suggestedKernels', "Suggested") - }); - quickPickItems.push(...suggestions.map(toQuickPick)); - } + this._fillInSuggestions(quickPickItems, suggestions, selected); // Next display all of the kernels not marked as hidden grouped by categories or extensions. // If we don't have a kind, always display those at the bottom. - const picks = all.filter(item => (!suggestions.includes(item) && !hidden.includes(item))).map(toQuickPick); + const picks = all.filter(item => (!suggestions.includes(item) && !hidden.includes(item))).map(kernel => this._toQuickPick(kernel, selected)); const kernelsPerCategory = groupBy(picks, (a, b) => compareIgnoreCase(a.kernel.kind || 'z', b.kernel.kind || 'z')); kernelsPerCategory.forEach(items => { quickPickItems.push({ @@ -363,6 +297,47 @@ registerAction2(class extends Action2 { return quickPickItems; } + private _toQuickPick(kernel: INotebookKernel, selected: INotebookKernel | undefined) { + const res = { + kernel, + picked: kernel.id === selected?.id, + label: kernel.label, + description: kernel.description, + detail: kernel.detail + }; + if (kernel.id === selected?.id) { + if (!res.description) { + res.description = localize('current1', "Currently Selected"); + } else { + res.description = localize('current2', "{0} - Currently Selected", res.description); + } + } + return res; + } + + private _fillInSuggestions(quickPickItems: QuickPickInput[], suggestions: INotebookKernel[], selected: INotebookKernel | undefined) { + if (!suggestions.length) { + return; + } + + if (suggestions.length === 1 && suggestions[0].id === selected?.id) { + quickPickItems.push({ + type: 'separator', + label: localize('selectedKernels', "Selected") + }); + + // The title is already set to "Selected" so we don't need to set it again in description, thus passing in `undefined`. + quickPickItems.push(this._toQuickPick(suggestions[0], undefined)); + return; + } + + quickPickItems.push({ + type: 'separator', + label: localize('suggestedKernels', "Suggested") + }); + quickPickItems.push(...suggestions.map(kernel => this._toQuickPick(kernel, selected))); + } + private async _showInstallKernelExtensionRecommendation( notebookTextModel: NotebookTextModel, quickPick: IQuickPick, @@ -383,7 +358,6 @@ registerAction2(class extends Action2 { } } - private async _getKernelRecommendationsQuickPickItems( notebookTextModel: NotebookTextModel, extensionWorkbenchService: IExtensionsWorkbenchService, @@ -482,6 +456,86 @@ registerAction2(class extends Action2 { const pascalCased = viewType.split(/[^a-z0-9]/ig).map(uppercaseFirstLetter).join(''); view?.search(`@tag:notebookKernel${pascalCased}`); } +} + +registerAction2(class extends Action2 { + constructor() { + super({ + id: SELECT_KERNEL_ID, + category: NOTEBOOK_ACTIONS_CATEGORY, + title: { value: localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, + icon: selectKernelIcon, + f1: true, + menu: [{ + id: MenuId.EditorTitle, + when: ContextKeyExpr.and( + NOTEBOOK_IS_ACTIVE_EDITOR, + ContextKeyExpr.notEquals('config.notebook.globalToolbar', true) + ), + group: 'navigation', + order: -10 + }, { + id: MenuId.NotebookToolbar, + when: ContextKeyExpr.equals('config.notebook.globalToolbar', true), + group: 'status', + order: -10 + }, { + id: MenuId.InteractiveToolbar, + when: NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), + group: 'status', + order: -10 + }], + description: { + description: localize('notebookActions.selectKernel.args', "Notebook Kernel Args"), + args: [ + { + name: 'kernelInfo', + description: 'The kernel info', + schema: { + 'type': 'object', + 'required': ['id', 'extension'], + 'properties': { + 'id': { + 'type': 'string' + }, + 'extension': { + 'type': 'string' + }, + 'notebookEditorId': { + 'type': 'string' + } + } + } + } + ] + }, + }); + } + + async run(accessor: ServicesAccessor, context?: KernelQuickPickContext): Promise { + const notebookKernelService = accessor.get(INotebookKernelService); + const editorService = accessor.get(IEditorService); + const productService = accessor.get(IProductService); + const quickInputService = accessor.get(IQuickInputService); + const labelService = accessor.get(ILabelService); + const logService = accessor.get(ILogService); + const paneCompositeService = accessor.get(IPaneCompositePartService); + const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extensionHostService = accessor.get(IExtensionService); + + const strategy = new KernelPickerFlatStrategy( + notebookKernelService, + editorService, + productService, + quickInputService, + labelService, + logService, + paneCompositeService, + extensionWorkbenchService, + extensionHostService + ); + return await strategy.showQuickPick(context); + } }); export class NotebooKernelActionViewItem extends ActionViewItem { diff --git a/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts b/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts index 9cb088a6a72..45761718426 100644 --- a/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts +++ b/src/vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems.ts @@ -7,7 +7,7 @@ import { IAction } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/common/types'; import { localize } from 'vs/nls'; -import { IListMenuItem, ActionListItemKind } from 'vs/platform/actionWidget/browser/actionWidget'; +import { ActionListItemKind, IListMenuItem } from 'vs/platform/actionWidget/browser/actionList'; import { IActionItem } from 'vs/platform/actionWidget/common/actionWidget'; export class TerminalQuickFix implements IActionItem { diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts index 63bb85368e8..2694519e7d3 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/quickFixAddon.ts @@ -25,9 +25,10 @@ import { IExtensionTerminalQuickFix } from 'vs/platform/terminal/common/terminal import { URI } from 'vs/base/common/uri'; import { gitCreatePr, gitPushSetUpstream, gitSimilar } from 'vs/workbench/contrib/terminal/browser/terminalQuickFixBuiltinActions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IActionWidgetService, previewSelectedActionCommand } from 'vs/platform/actionWidget/browser/actionWidget'; +import { IActionWidgetService } from 'vs/platform/actionWidget/browser/actionWidget'; import { ActionSet } from 'vs/platform/actionWidget/common/actionWidget'; import { TerminalQuickFix, toMenuItems } from 'vs/workbench/contrib/terminal/browser/widgets/terminalQuickFixMenuItems'; +import { previewSelectedActionCommand } from 'vs/platform/actionWidget/browser/actionList'; const quickFixTelemetryTitle = 'terminal/quick-fix'; type QuickFixResultTelemetryEvent = { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index cb8cde452b4..5f8f9e3ba2d 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -34,21 +34,10 @@ import { loadLocalResource, WebviewResourceResponse } from 'vs/workbench/contrib import { WebviewThemeDataProvider } from 'vs/workbench/contrib/webview/browser/themeing'; import { areWebviewContentOptionsEqual, IWebview, WebviewContentOptions, WebviewExtensionDescription, WebviewInitInfo, WebviewMessageReceivedEvent, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/webview/browser/webviewFindWidget'; -import { FromWebviewMessage, ToWebviewMessage } from 'vs/workbench/contrib/webview/browser/webviewMessages'; +import { FromWebviewMessage, KeyEvent, ToWebviewMessage } from 'vs/workbench/contrib/webview/browser/webviewMessages'; import { decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/contrib/webview/common/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -interface IKeydownEvent { - key: string; - keyCode: number; - code: string; - shiftKey: boolean; - altKey: boolean; - ctrlKey: boolean; - metaKey: boolean; - repeat: boolean; -} - interface WebviewContent { readonly html: string; readonly options: WebviewContentOptions; @@ -77,8 +66,8 @@ namespace WebviewState { } interface WebviewActionContext { - webview?: string; - [key: string]: unknown; + readonly webview?: string; + readonly [key: string]: unknown; } const webviewIdContext = 'webviewId'; @@ -696,7 +685,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD } } - private handleKeyEvent(type: 'keydown' | 'keyup', event: IKeydownEvent) { + private handleKeyEvent(type: 'keydown' | 'keyup', event: KeyEvent) { // Create a fake KeyboardEvent from the data provided const emulatedKeyboardEvent = new KeyboardEvent(type, event); // Force override the target diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index ce9115547c6..0f8588cff72 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -89,7 +89,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private versionId = 0; private bufferSavedVersionId: number | undefined; + private ignoreDirtyOnModelContentChange = false; + private ignoreSaveFromSaveParticipants = false; private static readonly UNDO_REDO_SAVE_PARTICIPANTS_AUTO_SAVE_THROTTLE_THRESHOLD = 500; private lastModelContentChangeFromUndoRedo: number | undefined = undefined; @@ -738,6 +740,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil let versionId = this.versionId; this.trace(`doSave(${versionId}) - enter with versionId ${versionId}`); + // Return early if saved from within save participant to break recursion + // + // Scenario: a save participant triggers a save() on the model + if (this.ignoreSaveFromSaveParticipants) { + this.trace(`doSave(${versionId}) - exit - refusing to save() recursively from save participant`); + + return; + } + // Lookup any running pending save for this versionId and return it if found // // Scenario: user invoked the save action multiple times quickly for the same contents @@ -820,7 +831,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Run save participants unless save was cancelled meanwhile if (!saveCancellation.token.isCancellationRequested) { - await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + this.ignoreSaveFromSaveParticipants = true; + try { + await this.textFileService.files.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + } finally { + this.ignoreSaveFromSaveParticipants = false; + } } } catch (error) { this.logService.error(`[text file model] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString()); 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 a6fb673f437..ed1af3cf812 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -815,51 +815,81 @@ suite('Files - TextFileEditorModel', () => { participant.dispose(); }); - test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { + test('Save Participant, calling save from within is unsupported but does not explode (sync save, no model change)', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await testSaveFromSaveParticipant(model, false); + await testSaveFromSaveParticipant(model, false, false, false); model.dispose(); }); - test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () { + test('Save Participant, calling save from within is unsupported but does not explode (async save, no model change)', async function () { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - await testSaveFromSaveParticipant(model, true); + await testSaveFromSaveParticipant(model, true, false, false); model.dispose(); }); - async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean): Promise { + test('Save Participant, calling save from within is unsupported but does not explode (sync save, model change)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); - let breakLoop = false; + await testSaveFromSaveParticipant(model, false, true, false); - const participant = accessor.textFileService.files.addSaveParticipant({ - participate: async model => { - if (breakLoop) { - return; - } + model.dispose(); + }); - breakLoop = true; + test('Save Participant, calling save from within is unsupported but does not explode (async save, model change)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + await testSaveFromSaveParticipant(model, true, true, false); + + model.dispose(); + }); + + test('Save Participant, calling save from within is unsupported but does not explode (force)', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await testSaveFromSaveParticipant(model, false, false, true); + + model.dispose(); + }); + + async function testSaveFromSaveParticipant(model: TextFileEditorModel, async: boolean, modelChange: boolean, force: boolean): Promise { + + const disposable = accessor.textFileService.files.addSaveParticipant({ + participate: async () => { if (async) { await timeout(10); } - const newSavePromise = model.save(); - // assert that this is the same promise as the outer one - assert.strictEqual(savePromise, newSavePromise); + if (modelChange) { + model.updateTextEditorModel(createTextBufferFactory('bar')); + + const newSavePromise = model.save(force ? { force } : undefined); + + // assert that this is not the same promise as the outer one + assert.notStrictEqual(savePromise, newSavePromise); + + await newSavePromise; + } else { + const newSavePromise = model.save(force ? { force } : undefined); + + // assert that this is the same promise as the outer one + assert.strictEqual(savePromise, newSavePromise); + + await savePromise; + } } }); await model.resolve(); model.updateTextEditorModel(createTextBufferFactory('foo')); - const savePromise = model.save(); + const savePromise = model.save(force ? { force } : undefined); await savePromise; - participant.dispose(); + disposable.dispose(); } test('backup and restore (simple)', async function () { diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncProfilesStorageService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileStorageService.ts similarity index 85% rename from src/vs/workbench/services/userDataSync/browser/userDataSyncProfilesStorageService.ts rename to src/vs/workbench/services/userDataProfile/browser/userDataProfileStorageService.ts index 7cec5ba8d7d..8fe279fa9dd 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncProfilesStorageService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileStorageService.ts @@ -7,13 +7,13 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; -import { AbstractUserDataSyncProfilesStorageService, IProfileStorageChanges, IUserDataSyncProfilesStorageService } from 'vs/platform/userDataSync/common/userDataSyncProfilesStorageService'; +import { AbstractUserDataProfileStorageService, IProfileStorageChanges, IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; import { isProfileUsingDefaultStorage, IStorageService, IStorageValueChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IndexedDBStorageDatabase } from 'vs/workbench/services/storage/browser/storageService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -export class UserDataSyncProfilesStorageService extends AbstractUserDataSyncProfilesStorageService implements IUserDataSyncProfilesStorageService { +export class UserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService { private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; @@ -45,4 +45,4 @@ export class UserDataSyncProfilesStorageService extends AbstractUserDataSyncProf } } -registerSingleton(IUserDataSyncProfilesStorageService, UserDataSyncProfilesStorageService, InstantiationType.Delayed); +registerSingleton(IUserDataProfileStorageService, UserDataProfileStorageService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index af517b704ea..0f82e5ae635 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -781,6 +781,8 @@ export class StoredFileWorkingCopy extend private readonly saveSequentializer = new TaskSequentializer(); + private ignoreSaveFromSaveParticipants = false; + async save(options: IStoredFileWorkingCopySaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { return false; @@ -817,6 +819,15 @@ export class StoredFileWorkingCopy extend let versionId = this.versionId; this.trace(`doSave(${versionId}) - enter with versionId ${versionId}`); + // Return early if saved from within save participant to break recursion + // + // Scenario: a save participant triggers a save() on the working copy + if (this.ignoreSaveFromSaveParticipants) { + this.trace(`doSave(${versionId}) - exit - refusing to save() recursively from save participant`); + + return; + } + // Lookup any running pending save for this versionId and return it if found // // Scenario: user invoked the save action multiple times quickly for the same contents @@ -902,7 +913,12 @@ export class StoredFileWorkingCopy extend // Run save participants unless save was cancelled meanwhile if (!saveCancellation.token.isCancellationRequested) { - await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + this.ignoreSaveFromSaveParticipants = true; + try { + await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + } finally { + this.ignoreSaveFromSaveParticipants = false; + } } } catch (error) { this.logService.error(`[stored file working copy] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(), this.typeId); diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts index 4dc6c6e598b..eaba8825a8f 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts @@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { basename } from 'vs/base/common/resources'; import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; import { SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor'; -import { Promises } from 'vs/base/common/async'; +import { Promises, timeout } from 'vs/base/common/async'; import { consumeReadable, consumeStream, isReadableStream } from 'vs/base/common/stream'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; @@ -752,6 +752,39 @@ suite('StoredFileWorkingCopy', function () { assert.strictEqual(participationCounter, 1); }); + test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { + await workingCopy.resolve(); + + await testSaveFromSaveParticipant(workingCopy, false); + }); + + test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () { + await workingCopy.resolve(); + + await testSaveFromSaveParticipant(workingCopy, true); + }); + + async function testSaveFromSaveParticipant(workingCopy: StoredFileWorkingCopy, async: boolean): Promise { + + assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, false); + + const disposable = accessor.workingCopyFileService.addSaveParticipant({ + participate: async () => { + if (async) { + await timeout(10); + } + + await workingCopy.save({ force: true }); + } + }); + + assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, true); + + await workingCopy.save({ force: true }); + + disposable.dispose(); + } + test('revert', async () => { await workingCopy.resolve(); workingCopy.model?.updateContents('hello revert'); diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 39abd114941..d3710e7515d 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -61,7 +61,7 @@ import 'vs/workbench/services/tunnel/browser/tunnelService'; import 'vs/workbench/services/files/browser/elevatedFileService'; import 'vs/workbench/services/workingCopy/browser/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService'; -import 'vs/workbench/services/userDataSync/browser/userDataSyncProfilesStorageService'; +import 'vs/workbench/services/userDataProfile/browser/userDataProfileStorageService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import 'vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService'; diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 7399790e375..147f0b6902f 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -2066,7 +2066,9 @@ declare module 'vscode' { export class RelativePattern { /** - * A base file path to which this pattern will be matched against relatively. + * A base file path to which this pattern will be matched against relatively. The + * file path must be absolute, should not have any trailing path separators and + * not include any relative segments (`.` or `..`). */ baseUri: Uri;