diff --git a/.github/copycat.yml b/.github/copycat.yml index 192f225684f..690c803bd0a 100644 --- a/.github/copycat.yml +++ b/.github/copycat.yml @@ -1,5 +1,5 @@ { - perform: false, + perform: true, target_owner: 'chrmarti', target_repo: 'testissues' } diff --git a/.yarnrc b/.yarnrc index 85baaa63a78..2c769cfba18 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "6.1.6" +target "7.1.7" runtime "electron" diff --git a/build/package.json b/build/package.json index 69b98df35e4..33c254aafe1 100644 --- a/build/package.json +++ b/build/package.json @@ -43,7 +43,7 @@ "minimist": "^1.2.0", "request": "^2.85.0", "terser": "4.3.8", - "typescript": "^3.8.0-dev.20200104", + "typescript": "^3.8.0-dev.20200108", "vsce": "1.48.0", "vscode-telemetry-extractor": "^1.5.4", "xml2js": "^0.4.17" diff --git a/build/win32/i18n/messages.en.isl b/build/win32/i18n/messages.en.isl index 6535824eed6..12189c080d4 100644 --- a/build/win32/i18n/messages.en.isl +++ b/build/win32/i18n/messages.en.isl @@ -6,4 +6,4 @@ AddToPath=Add to PATH (requires shell restart) RunAfter=Run %1 after installation Other=Other: SourceFile=%1 Source File -OpenWithCodeContextMenu=Open with %1 \ No newline at end of file +OpenWithCodeContextMenu=Open w&ith %1 diff --git a/build/yarn.lock b/build/yarn.lock index 2e66b691a45..38c1cbec7d9 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -2458,10 +2458,10 @@ typescript@^3.0.1: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== -typescript@^3.8.0-dev.20200104: - version "3.8.0-dev.20200104" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649" - integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA== +typescript@^3.8.0-dev.20200108: + version "3.8.0-dev.20200108" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc" + integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w== typical@^4.0.0: version "4.0.0" diff --git a/cgmanifest.json b/cgmanifest.json index c102a04f705..e9dfd3d6ef3 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" + "commitHash": "e4745133a1d3745f066e068b8033c6a269b59caf" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "76.0.3809.146" + "version": "78.0.3904.130" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" + "commitHash": "787378879acfb212ed4ff824bf9f767a24a5cb43a" } }, "isOnlyProductionDependency": true, - "version": "12.4.0" + "version": "12.8.1" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "19c705ab80cd6fdccca3d65803ec2c4addb9540a" + "commitHash": "bef0dd868b7d6d32716c319664ed480f2ae17396" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "6.1.6" + "version": "7.1.7" }, { "component": { diff --git a/extensions/bat/language-configuration.json b/extensions/bat/language-configuration.json index 2fb5445a34a..e4639541142 100644 --- a/extensions/bat/language-configuration.json +++ b/extensions/bat/language-configuration.json @@ -11,7 +11,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/clojure/language-configuration.json b/extensions/clojure/language-configuration.json index 24f52480015..a62cb968ece 100644 --- a/extensions/clojure/language-configuration.json +++ b/extensions/clojure/language-configuration.json @@ -11,7 +11,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ "folding": { "offSide": true } -} \ No newline at end of file +} diff --git a/extensions/coffeescript/language-configuration.json b/extensions/coffeescript/language-configuration.json index fd4a1b88c87..c2b8662039d 100644 --- a/extensions/coffeescript/language-configuration.json +++ b/extensions/coffeescript/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 6342dba74f1..fc149b28e54 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -83,7 +83,7 @@ "type": [ "string", "null" - ], + ], "default": null, "description": "%emmetExtensionsPath%" }, @@ -453,7 +453,7 @@ "@emmetio/html-matcher": "^0.3.3", "@emmetio/math-expression": "^0.1.1", "image-size": "^0.5.2", - "vscode-emmet-helper": "^1.2.16", + "vscode-emmet-helper": "^1.2.17", "vscode-html-languageservice": "^3.0.3" } } diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index a801de886fd..37e06ecb522 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2469,10 +2469,10 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-emmet-helper@^1.2.16: - version "1.2.16" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.2.16.tgz#cfefb8b54c68178b4696d4abae806bb3a2b043e8" - integrity sha512-BuQK6fTV2w65Yd0/CJGj1EOvcJ9NHWfrMJ9nA8pjnu9jzAAnXLhnbviuGT9medMiPU0mp0tJqc/8Z0qlXcqdGw== +vscode-emmet-helper@^1.2.17: + version "1.2.17" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.2.17.tgz#f0c6bfcebc4285d081fb2618e6e5b9a08c567afa" + integrity sha512-X4pzcrJ8dE7M3ArFuySF5fgipKDd/EauXkiJwtjBIVRWpVNq0tF9+lNCyuC7iDUwP3Oq7ow/TGssD3GdG96Jow== dependencies: "@emmetio/extract-abbreviation" "0.1.6" jsonc-parser "^1.0.0" diff --git a/extensions/groovy/language-configuration.json b/extensions/groovy/language-configuration.json index e4656d0babb..a81a8864a51 100644 --- a/extensions/groovy/language-configuration.json +++ b/extensions/groovy/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts index fa615319354..7e4deafa262 100644 --- a/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts +++ b/extensions/html-language-features/server/src/modes/javascriptSemanticTokens.ts @@ -20,7 +20,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current if (node.kind === ts.SyntaxKind.Identifier) { const symbol = typeChecker.getSymbolAtLocation(node); if (symbol) { - const decl = symbol.valueDeclaration || symbol.declarations[0]; + const decl = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0]; if (decl) { let typeIdx = tokenFromDeclarationMapping[decl.kind]; let modifierSet = 0; diff --git a/extensions/ini/ini.language-configuration.json b/extensions/ini/ini.language-configuration.json index 93cfa1ce3b9..c688aee426d 100644 --- a/extensions/ini/ini.language-configuration.json +++ b/extensions/ini/ini.language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/ini/properties.language-configuration.json b/extensions/ini/properties.language-configuration.json index 85b401f0767..7dd2e4ebe2e 100644 --- a/extensions/ini/properties.language-configuration.json +++ b/extensions/ini/properties.language-configuration.json @@ -12,7 +12,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -21,4 +21,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/java/language-configuration.json b/extensions/java/language-configuration.json index a2909abb800..e19d2d749f8 100644 --- a/extensions/java/language-configuration.json +++ b/extensions/java/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, { "open": "/**", "close": " */", "notIn": ["string"] } ], "surroundingPairs": [ diff --git a/extensions/lua/language-configuration.json b/extensions/lua/language-configuration.json index 89e5c45b9ff..5e5e0ab4477 100644 --- a/extensions/lua/language-configuration.json +++ b/extensions/lua/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index c8a1eb85236..b9cedcc9a33 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -144,7 +144,7 @@ async function detectNpmScripts(): Promise { for (const folder of folders) { if (isAutoDetectionEnabled(folder)) { let relativePattern = new RelativePattern(folder, '**/package.json'); - let paths = await workspace.findFiles(relativePattern, '**/node_modules/**'); + let paths = await workspace.findFiles(relativePattern, '**/{node_modules,.vscode-test}/**'); for (const path of paths) { if (!isExcluded(folder, path) && !visitedPackageJsonFiles.has(path.fsPath)) { let tasks = await provideNpmScriptsForFolder(path); diff --git a/extensions/objective-c/language-configuration.json b/extensions/objective-c/language-configuration.json index e4656d0babb..a81a8864a51 100644 --- a/extensions/objective-c/language-configuration.json +++ b/extensions/objective-c/language-configuration.json @@ -12,8 +12,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -22,4 +22,4 @@ ["\"", "\""], ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/perl/perl.language-configuration.json b/extensions/perl/perl.language-configuration.json index c142f2d58f6..4d18f1fbaa4 100644 --- a/extensions/perl/perl.language-configuration.json +++ b/extensions/perl/perl.language-configuration.json @@ -11,9 +11,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -29,4 +29,4 @@ "end": "^(?:(?:=cut\\s*$)|(?:\\s*#endregion\\b))" } } -} \ No newline at end of file +} diff --git a/extensions/perl/perl6.language-configuration.json b/extensions/perl/perl6.language-configuration.json index 01b6a8a2823..be52105cbdf 100644 --- a/extensions/perl/perl6.language-configuration.json +++ b/extensions/perl/perl6.language-configuration.json @@ -11,9 +11,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -23,4 +23,4 @@ ["'", "'"], ["`", "`"] ] -} \ No newline at end of file +} diff --git a/extensions/pug/language-configuration.json b/extensions/pug/language-configuration.json index 00f4885d46e..98a2b77ca27 100644 --- a/extensions/pug/language-configuration.json +++ b/extensions/pug/language-configuration.json @@ -11,8 +11,8 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["'", "'"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -24,4 +24,4 @@ "folding": { "offSide": true } -} \ No newline at end of file +} diff --git a/extensions/r/language-configuration.json b/extensions/r/language-configuration.json index 6293d4d61a6..dd691e2a6d4 100644 --- a/extensions/r/language-configuration.json +++ b/extensions/r/language-configuration.json @@ -11,12 +11,14 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + ["\"", "\""], + ["'", "'"] ] -} \ No newline at end of file +} diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index 47c434deffa..81fdee540f2 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -12,15 +12,17 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], ["\"", "\""], - ["'", "'"] + ["'", "'"], + ["`", "`"] ], "indentationRules": { "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", diff --git a/extensions/rust/language-configuration.json b/extensions/rust/language-configuration.json index bd4cbb8d8f0..5d4f16d26a6 100644 --- a/extensions/rust/language-configuration.json +++ b/extensions/rust/language-configuration.json @@ -12,14 +12,13 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"] + ["\"", "\""] ], "indentationRules": { "increaseIndentPattern": "^.*\\{[^}\"']*$|^.*\\([^\\)\"']*$", diff --git a/extensions/shaderlab/language-configuration.json b/extensions/shaderlab/language-configuration.json index 8d91e91a89e..6af82376726 100644 --- a/extensions/shaderlab/language-configuration.json +++ b/extensions/shaderlab/language-configuration.json @@ -12,7 +12,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -20,4 +20,4 @@ ["(", ")"], ["\"", "\""] ] -} \ No newline at end of file +} diff --git a/extensions/shellscript/language-configuration.json b/extensions/shellscript/language-configuration.json index 964623ac4cc..8421a3817a2 100644 --- a/extensions/shellscript/language-configuration.json +++ b/extensions/shellscript/language-configuration.json @@ -11,9 +11,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/swift/language-configuration.json b/extensions/swift/language-configuration.json index 2bdc2752749..54095ef5212 100644 --- a/extensions/swift/language-configuration.json +++ b/extensions/swift/language-configuration.json @@ -12,9 +12,9 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["`", "`"] + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, + { "open": "`", "close": "`", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -24,4 +24,4 @@ ["'", "'"], ["`", "`"] ] -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/features/callHierarchy.ts b/extensions/typescript-language-features/src/features/callHierarchy.ts new file mode 100644 index 00000000000..b5b4501d2ad --- /dev/null +++ b/extensions/typescript-language-features/src/features/callHierarchy.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { ITypeScriptServiceClient } from '../typescriptService'; +import * as typeConverters from '../utils/typeConverters'; +import API from '../utils/api'; +import { VersionDependentRegistration } from '../utils/dependentRegistration'; +import * as Proto from '../protocol'; +import * as path from 'path'; +import * as PConst from '../protocol.const'; + +class TypeScriptCallHierarchySupport implements vscode.CallHierarchyProvider { + public static readonly minVersion = API.v380; + public constructor( + private readonly client: ITypeScriptServiceClient) { } + + public async prepareCallHierarchy( + document: vscode.TextDocument, + position: vscode.Position, + token: vscode.CancellationToken + ): Promise { + const filepath = this.client.toOpenedFilePath(document); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position); + const response = await this.client.execute('prepareCallHierarchy', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return Array.isArray(response.body) + ? response.body.map(fromProtocolCallHierarchyItem) + : fromProtocolCallHierarchyItem(response.body); + } + + public async provideCallHierarchyIncomingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const filepath = this.client.toPath(item.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start); + const response = await this.client.execute('provideCallHierarchyIncomingCalls', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return response.body.map(fromProtocolCallHierchyIncomingCall); + } + + public async provideCallHierarchyOutgoingCalls(item: vscode.CallHierarchyItem, token: vscode.CancellationToken): Promise { + const filepath = this.client.toPath(item.uri); + if (!filepath) { + return undefined; + } + + const args = typeConverters.Position.toFileLocationRequestArgs(filepath, item.selectionRange.start); + const response = await this.client.execute('provideCallHierarchyOutgoingCalls', args, token); + if (response.type !== 'response' || !response.body) { + return undefined; + } + + return response.body.map(fromProtocolCallHierchyOutgoingCall); + } +} + +function isSourceFileItem(item: Proto.CallHierarchyItem) { + return item.kind === PConst.Kind.script || item.kind === PConst.Kind.module && item.selectionSpan.start.line === 0 && item.selectionSpan.start.offset === 0; +} + +function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): vscode.CallHierarchyItem { + const useFileName = isSourceFileItem(item); + const name = useFileName ? path.basename(item.file) : item.name; + const detail = useFileName ? vscode.workspace.asRelativePath(path.dirname(item.file)) : ''; + return new vscode.CallHierarchyItem( + typeConverters.SymbolKind.fromProtocolScriptElementKind(item.kind), + name, + detail, + vscode.Uri.file(item.file), + typeConverters.Range.fromTextSpan(item.span), + typeConverters.Range.fromTextSpan(item.selectionSpan) + ); +} + +function fromProtocolCallHierchyIncomingCall(item: Proto.CallHierarchyIncomingCall): vscode.CallHierarchyIncomingCall { + return new vscode.CallHierarchyIncomingCall( + fromProtocolCallHierarchyItem(item.from), + item.fromSpans.map(typeConverters.Range.fromTextSpan) + ); +} + +function fromProtocolCallHierchyOutgoingCall(item: Proto.CallHierarchyOutgoingCall): vscode.CallHierarchyOutgoingCall { + return new vscode.CallHierarchyOutgoingCall( + fromProtocolCallHierarchyItem(item.to), + item.fromSpans.map(typeConverters.Range.fromTextSpan) + ); +} + +export function register( + selector: vscode.DocumentSelector, + client: ITypeScriptServiceClient +) { + return new VersionDependentRegistration(client, TypeScriptCallHierarchySupport.minVersion, + () => vscode.languages.registerCallHierarchyProvider(selector, + new TypeScriptCallHierarchySupport(client))); +} diff --git a/extensions/typescript-language-features/src/features/completions.ts b/extensions/typescript-language-features/src/features/completions.ts index 5fdefe1b2ad..cd887f1f4aa 100644 --- a/extensions/typescript-language-features/src/features/completions.ts +++ b/extensions/typescript-language-features/src/features/completions.ts @@ -16,7 +16,7 @@ import { ConfigurationDependentRegistration } from '../utils/dependentRegistrati import { memoize } from '../utils/memoize'; import * as Previewer from '../utils/previewer'; import { snippetForFunctionCall } from '../utils/snippetForFunctionCall'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; import TypingsStatus from '../utils/typingsStatus'; import FileConfigurationManager from './fileConfigurationManager'; @@ -405,15 +405,17 @@ class TypeScriptCompletionItemProvider implements vscode.CompletionItemProvider "duration" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "type" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "count" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, + "updateGraphDurationMs" : { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, "${include}": [ "${TypeScriptCommonProperties}" ] } */ this.telemetryReporter.logTelemetry('completions.execute', { - duration: duration + '', - type: response ? response.type : 'unknown', - count: (response && response.type === 'response' && response.body ? response.body.entries.length : 0) + '' + duration: duration, + type: response?.type ?? 'unknown', + count: response?.type === 'response' && response.body ? response.body.entries.length : 0, + updateGraphDurationMs: response?.type === 'response' ? response.performanceData?.updateGraphDurationMs : undefined, }); } diff --git a/extensions/typescript-language-features/src/features/organizeImports.ts b/extensions/typescript-language-features/src/features/organizeImports.ts index bb7880800a6..2e5871d1835 100644 --- a/extensions/typescript-language-features/src/features/organizeImports.ts +++ b/extensions/typescript-language-features/src/features/organizeImports.ts @@ -12,7 +12,7 @@ import { Command, CommandManager } from '../utils/commandManager'; import { VersionDependentRegistration } from '../utils/dependentRegistration'; import * as typeconverts from '../utils/typeConverters'; import FileConfigurationManager from './fileConfigurationManager'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import { nulToken } from '../utils/cancellation'; const localize = nls.loadMessageBundle(); diff --git a/extensions/typescript-language-features/src/features/quickFix.ts b/extensions/typescript-language-features/src/features/quickFix.ts index 1b1ad27da85..879abf07f8b 100644 --- a/extensions/typescript-language-features/src/features/quickFix.ts +++ b/extensions/typescript-language-features/src/features/quickFix.ts @@ -12,7 +12,7 @@ import { nulToken } from '../utils/cancellation'; import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction'; import { Command, CommandManager } from '../utils/commandManager'; import { memoize } from '../utils/memoize'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; import { DiagnosticsManager } from './diagnostics'; import FileConfigurationManager from './fileConfigurationManager'; diff --git a/extensions/typescript-language-features/src/features/refactor.ts b/extensions/typescript-language-features/src/features/refactor.ts index 3ccb5969913..017ea358b2a 100644 --- a/extensions/typescript-language-features/src/features/refactor.ts +++ b/extensions/typescript-language-features/src/features/refactor.ts @@ -11,7 +11,7 @@ import API from '../utils/api'; import { nulToken } from '../utils/cancellation'; import { Command, CommandManager } from '../utils/commandManager'; import { VersionDependentRegistration } from '../utils/dependentRegistration'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import * as typeConverters from '../utils/typeConverters'; import FormattingOptionsManager from './fileConfigurationManager'; import * as fileSchemes from '../utils/fileSchemes'; diff --git a/extensions/typescript-language-features/src/languageProvider.ts b/extensions/typescript-language-features/src/languageProvider.ts index 9d06ecdeddc..692cee35cbb 100644 --- a/extensions/typescript-language-features/src/languageProvider.ts +++ b/extensions/typescript-language-features/src/languageProvider.ts @@ -14,7 +14,7 @@ import { Disposable } from './utils/dispose'; import * as fileSchemes from './utils/fileSchemes'; import { LanguageDescription } from './utils/languageDescription'; import { memoize } from './utils/memoize'; -import TelemetryReporter from './utils/telemetry'; +import { TelemetryReporter } from './utils/telemetry'; import TypingsStatus from './utils/typingsStatus'; @@ -79,6 +79,7 @@ export default class LanguageProvider extends Disposable { import('./features/tagClosing').then(provider => this._register(provider.register(selector, this.description.id, this.client))), import('./features/typeDefinitions').then(provider => this._register(provider.register(selector, this.client))), import('./features/semanticColoring').then(provider => this._register(provider.register(selector, this.client))), + import('./features/callHierarchy').then(provider => this._register(provider.register(selector, this.client))), ]); } diff --git a/extensions/typescript-language-features/src/protocol.const.ts b/extensions/typescript-language-features/src/protocol.const.ts index 7a6a7d70235..a44c175f295 100644 --- a/extensions/typescript-language-features/src/protocol.const.ts +++ b/extensions/typescript-language-features/src/protocol.const.ts @@ -33,6 +33,7 @@ export class Kind { public static readonly warning = 'warning'; public static readonly string = 'string'; public static readonly parameter = 'parameter'; + public static readonly typeParameter = 'type parameter'; } diff --git a/extensions/typescript-language-features/src/protocol.d.ts b/extensions/typescript-language-features/src/protocol.d.ts index 6e926eb8d7e..62d6c4793ca 100644 --- a/extensions/typescript-language-features/src/protocol.d.ts +++ b/extensions/typescript-language-features/src/protocol.d.ts @@ -1,2 +1,61 @@ import * as Proto from 'typescript/lib/protocol'; export = Proto; + +declare module "typescript/lib/protocol" { + // TODO: Remove this hardcoded type once we update to TS 3.8+ that brings in the proper types + interface Response { + performanceData?: { + updateGraphDurationMs?: number; + } + } + + const enum CommandTypes { + PrepareCallHierarchy = "prepareCallHierarchy", + ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", + ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", + } + + interface CallHierarchyItem { + name: string; + kind: ScriptElementKind; + file: string; + span: TextSpan; + selectionSpan: TextSpan; + } + + interface CallHierarchyIncomingCall { + from: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + interface CallHierarchyOutgoingCall { + to: CallHierarchyItem; + fromSpans: TextSpan[]; + } + + interface PrepareCallHierarchyRequest extends FileLocationRequest { + command: CommandTypes.PrepareCallHierarchy; + } + + interface PrepareCallHierarchyResponse extends Response { + readonly body: CallHierarchyItem | CallHierarchyItem[]; + } + + interface ProvideCallHierarchyIncomingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyIncomingCalls; + kind: ScriptElementKind; + } + + interface ProvideCallHierarchyIncomingCallsResponse extends Response { + readonly body: CallHierarchyIncomingCall[]; + } + + interface ProvideCallHierarchyOutgoingCallsRequest extends FileLocationRequest { + command: CommandTypes.ProvideCallHierarchyOutgoingCalls; + kind: ScriptElementKind; + } + + interface ProvideCallHierarchyOutgoingCallsResponse extends Response { + readonly body: CallHierarchyOutgoingCall[]; + } +} diff --git a/extensions/typescript-language-features/src/test/server.test.ts b/extensions/typescript-language-features/src/test/server.test.ts index 651967fc12c..fd3210670ff 100644 --- a/extensions/typescript-language-features/src/test/server.test.ts +++ b/extensions/typescript-language-features/src/test/server.test.ts @@ -9,7 +9,7 @@ import * as stream from 'stream'; import { PipeRequestCanceller, TsServerProcess, ProcessBasedTsServer } from '../tsServer/server'; import { nulToken } from '../utils/cancellation'; import Logger from '../utils/logger'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import * as Proto from '../protocol'; diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 782c1c56f05..d23b4b13f83 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; import { ServerResponse, TypeScriptRequests } from '../typescriptService'; import { Disposable } from '../utils/dispose'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion } from '../utils/versionProvider'; import { Reader } from '../utils/wireProtocol'; diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 171f204703e..dfd71edfc51 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -15,7 +15,7 @@ import LogDirectoryProvider from '../utils/logDirectoryProvider'; import Logger from '../utils/logger'; import { TypeScriptPluginPathsProvider } from '../utils/pluginPathsProvider'; import { PluginManager } from '../utils/plugins'; -import TelemetryReporter from '../utils/telemetry'; +import { TelemetryReporter } from '../utils/telemetry'; import Tracer from '../utils/tracer'; import { TypeScriptVersion, TypeScriptVersionProvider } from '../utils/versionProvider'; import { ITypeScriptServer, PipeRequestCanceller, ProcessBasedTsServer, SyntaxRoutingTsServer, TsServerProcess, TsServerDelegate } from './server'; diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 3b5a139cd0a..043284c1244 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -58,6 +58,9 @@ interface StandardTsServerRequests { 'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse]; 'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse]; 'updateOpen': [Proto.UpdateOpenRequestArgs, Proto.Response]; + 'prepareCallHierarchy': [Proto.FileLocationRequestArgs, Proto.PrepareCallHierarchyResponse]; + 'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse]; + 'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse]; } interface NoResponseTsServerRequests { diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index e487ac835a7..bb041b7676e 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -22,7 +22,7 @@ import LogDirectoryProvider from './utils/logDirectoryProvider'; import Logger from './utils/logger'; import { TypeScriptPluginPathsProvider } from './utils/pluginPathsProvider'; import { PluginManager } from './utils/plugins'; -import TelemetryReporter, { VSCodeTelemetryReporter } from './utils/telemetry'; +import { TelemetryReporter, VSCodeTelemetryReporter, TelemetryProperties } from './utils/telemetry'; import Tracer from './utils/tracer'; import { inferredProjectCompilerOptions } from './utils/tsconfig'; import { TypeScriptVersionPicker } from './utils/versionPicker'; @@ -271,7 +271,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.logger.error(message, data); } - private logTelemetry(eventName: string, properties?: { readonly [prop: string]: string }) { + private logTelemetry(eventName: string, properties?: TelemetryProperties) { this.telemetryReporter.logTelemetry(eventName, properties); } @@ -711,7 +711,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType "${include}": [ "${TypeScriptCommonProperties}", "${TypeScriptRequestErrorProperties}" - ] + ], + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, } */ this.logTelemetry('fatalError', { command, ...(error instanceof TypeScriptServerError ? error.telemetry : {}) }); diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index c30bee16c7e..0fa41cb01a0 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -31,6 +31,7 @@ export default class API { public static readonly v340 = API.fromSimpleString('3.4.0'); public static readonly v345 = API.fromSimpleString('3.4.5'); public static readonly v350 = API.fromSimpleString('3.5.0'); + public static readonly v380 = API.fromSimpleString('3.8.0'); public static fromVersionString(versionString: string): API { let version = semver.valid(versionString); diff --git a/extensions/typescript-language-features/src/utils/projectStatus.ts b/extensions/typescript-language-features/src/utils/projectStatus.ts index 15aa72a80d0..b9a3b2328c7 100644 --- a/extensions/typescript-language-features/src/utils/projectStatus.ts +++ b/extensions/typescript-language-features/src/utils/projectStatus.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { loadMessageBundle } from 'vscode-nls'; import { ITypeScriptServiceClient } from '../typescriptService'; -import TelemetryReporter from './telemetry'; +import { TelemetryReporter } from './telemetry'; import { isImplicitProjectConfigFile, openOrCreateConfigFile } from './tsconfig'; const localize = loadMessageBundle(); diff --git a/extensions/typescript-language-features/src/utils/telemetry.ts b/extensions/typescript-language-features/src/utils/telemetry.ts index e17e0188e6d..296277ba028 100644 --- a/extensions/typescript-language-features/src/utils/telemetry.ts +++ b/extensions/typescript-language-features/src/utils/telemetry.ts @@ -13,8 +13,12 @@ interface PackageInfo { readonly aiKey: string; } -export default interface TelemetryReporter { - logTelemetry(eventName: string, properties?: { readonly [prop: string]: string }): void; +export interface TelemetryProperties { + readonly [prop: string]: string | number | undefined; +} + +export interface TelemetryReporter { + logTelemetry(eventName: string, properties?: TelemetryProperties): void; dispose(): void; } diff --git a/extensions/typescript-language-features/src/utils/typeConverters.ts b/extensions/typescript-language-features/src/utils/typeConverters.ts index 37947b38810..2ccf306885f 100644 --- a/extensions/typescript-language-features/src/utils/typeConverters.ts +++ b/extensions/typescript-language-features/src/utils/typeConverters.ts @@ -9,12 +9,18 @@ import * as vscode from 'vscode'; import * as Proto from '../protocol'; +import * as PConst from '../protocol.const'; import { ITypeScriptServiceClient } from '../typescriptService'; export namespace Range { export const fromTextSpan = (span: Proto.TextSpan): vscode.Range => fromLocations(span.start, span.end); + export const toTextSpan = (range: vscode.Range): Proto.TextSpan => ({ + start: Position.toLocation(range.start), + end: Position.toLocation(range.end) + }); + export const fromLocations = (start: Proto.Location, end: Proto.Location): vscode.Range => new vscode.Range( Math.max(0, start.line - 1), Math.max(start.offset - 1, 0), @@ -90,3 +96,33 @@ export namespace WorkspaceEdit { return workspaceEdit; } } + +export namespace SymbolKind { + export function fromProtocolScriptElementKind(kind: Proto.ScriptElementKind) { + switch (kind) { + case PConst.Kind.module: return vscode.SymbolKind.Module; + case PConst.Kind.class: return vscode.SymbolKind.Class; + case PConst.Kind.enum: return vscode.SymbolKind.Enum; + case PConst.Kind.enumMember: return vscode.SymbolKind.EnumMember; + case PConst.Kind.interface: return vscode.SymbolKind.Interface; + case PConst.Kind.indexSignature: return vscode.SymbolKind.Method; + case PConst.Kind.callSignature: return vscode.SymbolKind.Method; + case PConst.Kind.memberFunction: return vscode.SymbolKind.Method; + case PConst.Kind.memberVariable: return vscode.SymbolKind.Property; + case PConst.Kind.memberGetAccessor: return vscode.SymbolKind.Property; + case PConst.Kind.memberSetAccessor: return vscode.SymbolKind.Property; + case PConst.Kind.variable: return vscode.SymbolKind.Variable; + case PConst.Kind.let: return vscode.SymbolKind.Variable; + case PConst.Kind.const: return vscode.SymbolKind.Variable; + case PConst.Kind.localVariable: return vscode.SymbolKind.Variable; + case PConst.Kind.alias: return vscode.SymbolKind.Variable; + case PConst.Kind.function: return vscode.SymbolKind.Function; + case PConst.Kind.localFunction: return vscode.SymbolKind.Function; + case PConst.Kind.constructSignature: return vscode.SymbolKind.Constructor; + case PConst.Kind.constructorImplementation: return vscode.SymbolKind.Constructor; + case PConst.Kind.typeParameter: return vscode.SymbolKind.TypeParameter; + case PConst.Kind.string: return vscode.SymbolKind.String; + default: return vscode.SymbolKind.Variable; + } + } +} diff --git a/extensions/vb/language-configuration.json b/extensions/vb/language-configuration.json index d9a6b21014a..a31b67bec0f 100644 --- a/extensions/vb/language-configuration.json +++ b/extensions/vb/language-configuration.json @@ -12,7 +12,7 @@ ["{", "}"], ["[", "]"], ["(", ")"], - ["\"", "\""] + { "open": "\"", "close": "\"", "notIn": ["string"] } ], "surroundingPairs": [ ["{", "}"], @@ -27,4 +27,4 @@ "end": "^\\s*#End Region\\b" } } -} \ No newline at end of file +} diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index e785f1d4afb..e149da8995b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -13,7 +13,7 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -suite('Webview tests', () => { +suite.skip('Webview tests', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index c2ec4f13681..1e6d2f60669 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -145,17 +145,24 @@ suite('window namespace tests', () => { }); }); - test('active editor not always correct... #49125', async function () { + test.skip('active editor not always correct... #49125', async function () { + const randomFile1 = await createRandomFile(); + const randomFile2 = await createRandomFile(); + + console.log('Created random files: ' + randomFile1.toString() + ' and ' + randomFile2.toString()); + const [docA, docB] = await Promise.all([ - workspace.openTextDocument(await createRandomFile()), - workspace.openTextDocument(await createRandomFile()), + workspace.openTextDocument(randomFile1), + workspace.openTextDocument(randomFile2) ]); for (let c = 0; c < 4; c++) { let editorA = await window.showTextDocument(docA, ViewColumn.One); - assert(window.activeTextEditor === editorA); + console.log('Showing: ' + editorA.document.fileName + ' and active editor is: ' + window.activeTextEditor?.document.fileName); + assert.equal(window.activeTextEditor, editorA); let editorB = await window.showTextDocument(docB, ViewColumn.Two); - assert(window.activeTextEditor === editorB); + console.log('Showing: ' + editorB.document.fileName + ' and active editor is: ' + window.activeTextEditor?.document.fileName); + assert.equal(window.activeTextEditor, editorB); } }); diff --git a/extensions/xml/xml.language-configuration.json b/extensions/xml/xml.language-configuration.json index 702b6dc6eb7..15664cbb4f5 100644 --- a/extensions/xml/xml.language-configuration.json +++ b/extensions/xml/xml.language-configuration.json @@ -12,8 +12,8 @@ { "open": "{", "close": "}"}, { "open": "[", "close": "]"}, { "open": "(", "close": ")" }, - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "'", "close": "'", "notIn": ["string"] }, { "open": "", "notIn": [ "comment", "string" ]}, { "open": "", "notIn": [ "comment", "string" ]} ], diff --git a/package.json b/package.json index 989bc744f63..11d823edf02 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.42.0", - "distro": "aafa05cffde354e7f58dc9fd2dcfc9aa3e81ea77", + "distro": "683b7a48a0cab9871ae34d129cc31e1036746b37", "author": { "name": "Microsoft Corporation" }, @@ -91,7 +91,7 @@ "coveralls": "^2.11.11", "cson-parser": "^1.3.3", "debounce": "^1.0.0", - "electron": "6.1.6", + "electron": "7.1.7", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", @@ -144,7 +144,7 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "ts-loader": "^4.4.2", - "typescript": "^3.8.0-dev.20200104", + "typescript": "^3.8.0-dev.20200108", "typescript-formatter": "7.1.0", "underscore": "^1.8.2", "vinyl": "^2.0.0", diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index adf5e433633..ee7fab8ba62 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -50,10 +50,10 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataSyncChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; @@ -63,6 +63,7 @@ import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync'; +import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -186,6 +187,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter))); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); + services.set(ISettingsSyncService, new SyncDescriptor(SettingsSynchroniser)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); registerConfiguration(); @@ -209,6 +211,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const authTokenChannel = new AuthTokenChannel(authTokenService); server.registerChannel('authToken', authTokenChannel); + const settingsSyncService = accessor.get(ISettingsSyncService); + const settingsSyncChannel = new SettingsSyncChannel(settingsSyncService); + server.registerChannel('settingsSync', settingsSyncChannel); + const userDataSyncService = accessor.get(IUserDataSyncService); const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); server.registerChannel('userDataSync', userDataSyncChannel); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4d544450cc8..549c348240b 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -171,7 +171,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (_event: Event, contents) => { contents.on('will-attach-webview', (event: Event, webPreferences, params) => { - const isValidWebviewSource = (source: string): boolean => { + const isValidWebviewSource = (source: string | undefined): boolean => { if (!source) { return false; } @@ -191,11 +191,11 @@ export class CodeApplication extends Disposable { webPreferences.nodeIntegration = false; // Verify URLs being loaded - if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) { + if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preload)) { return; } - delete webPreferences.preloadUrl; + delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553 // Otherwise prevent loading this.logService.error('webContents#web-contents-created: Prevented webview attach'); @@ -497,27 +497,27 @@ export class CodeApplication extends Disposable { this.logService.info(`Tracing: waiting for windows to get ready...`); let recordingStopped = false; - const stopRecording = (timeout: boolean) => { + const stopRecording = async (timeout: boolean) => { if (recordingStopped) { return; } recordingStopped = true; // only once - contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { - if (!timeout) { - if (this.dialogMainService) { - this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); - } - } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + const path = await contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`)); + + if (!timeout) { + if (this.dialogMainService) { + this.dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "Ok")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); } - }); + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); + } }; // Wait up to 30s before creating the trace anyways diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index d0ea8752aae..fadf9730016 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -347,9 +347,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => { - const responseHeaders = details.responseHeaders as { [key: string]: string[] }; + const responseHeaders = details.responseHeaders as Record; - const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']); + const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { return callback({ cancel: true }); } @@ -441,7 +441,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => - this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } }))); + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as Record }))); } private onWindowError(error: WindowError): void { diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index 06a98c7918f..2937bca8f1f 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -83,6 +83,8 @@ export interface IConfigurationValue { readonly workspace?: { value?: T, override?: T }; readonly workspaceFolder?: { value?: T, override?: T }; readonly memory?: { value?: T, override?: T }; + + readonly overrideIdentifiers?: string[]; } export interface IConfigurationService { diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index 759376b9862..ee715c1631f 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -391,6 +391,7 @@ export class Configuration { const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined; const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key); const value = consolidateConfigurationModel.getValue(key); + const overrideIdentifiers: string[] = arrays.distinct(arrays.flatten(consolidateConfigurationModel.overrides.map(override => override.identifiers))).filter(overrideIdentifier => consolidateConfigurationModel.getOverrideValue(key, overrideIdentifier) !== undefined); return { defaultValue: defaultValue, @@ -409,6 +410,8 @@ export class Configuration { workspace: workspaceValue !== undefined ? { value: this._workspaceConfiguration.freeze().getValue(key), override: overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, workspaceFolder: workspaceFolderValue !== undefined ? { value: folderConfigurationModel?.freeze().getValue(key), override: overrides.overrideIdentifier ? folderConfigurationModel?.freeze().getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, memory: memoryValue !== undefined ? { value: memoryConfigurationModel.getValue(key), override: overrides.overrideIdentifier ? memoryConfigurationModel.getOverrideValue(key, overrides.overrideIdentifier) : undefined } : undefined, + + overrideIdentifiers: overrideIdentifiers.length ? overrideIdentifiers : undefined }; } diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index daf54b4b9f5..1ff040dce84 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -361,6 +361,17 @@ suite('CustomConfigurationModel', () => { suite('Configuration', () => { + test('Test inspect for overrideIdentifiers', () => { + const defaultConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 1 }, '[l2]': { 'b': 1 } }); + const userConfigurationModel = parseConfigurationModel({ '[l3]': { 'a': 2 } }); + const workspaceConfigurationModel = parseConfigurationModel({ '[l1]': { 'a': 3 }, '[l4]': { 'a': 3 } }); + const testObject: Configuration = new Configuration(defaultConfigurationModel, userConfigurationModel, new ConfigurationModel(), workspaceConfigurationModel); + + const { overrideIdentifiers } = testObject.inspect('a', {}, undefined); + + assert.deepEqual(overrideIdentifiers, ['l1', 'l3', 'l4']); + }); + test('Test update value', () => { const parser = new ConfigurationModelParser('test'); parser.parseContent(JSON.stringify({ 'a': 1 })); @@ -468,7 +479,7 @@ suite('Configuration', () => { }); - test('Test compare and deletre workspace folder configuration', () => { + test('Test compare and delete workspace folder configuration', () => { const testObject = new Configuration(new ConfigurationModel(), new ConfigurationModel()); testObject.updateFolderConfiguration(URI.file('file1'), toConfigurationModel({ 'editor.lineNumbers': 'off', @@ -484,6 +495,12 @@ suite('Configuration', () => { }); + function parseConfigurationModel(content: any): ConfigurationModel { + const parser = new ConfigurationModelParser('test'); + parser.parseContent(JSON.stringify(content)); + return parser.configurationModel; + } + }); suite('ConfigurationChangeEvent', () => { diff --git a/src/vs/platform/dialogs/electron-main/dialogs.ts b/src/vs/platform/dialogs/electron-main/dialogs.ts index 7b49ca50c2e..c694c003e9b 100644 --- a/src/vs/platform/dialogs/electron-main/dialogs.ts +++ b/src/vs/platform/dialogs/electron-main/dialogs.ts @@ -173,7 +173,7 @@ export class DialogMainService implements IDialogMainService { showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise { - function normalizePaths(paths: string[] | undefined): string[] | undefined { + function normalizePaths(paths: string[]): string[] { if (paths && paths.length > 0 && isMacintosh) { paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index e0beffc55ed..bae55607623 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -18,7 +18,6 @@ import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; -import { NativeImage } from 'electron'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; @@ -67,7 +66,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('Invalid window'); } const webContents = window.win.webContents; - const image = await new Promise(c => webContents.capturePage(c)); + const image = await webContents.capturePage(); return image.toPNG().toString('base64'); } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 24ef0a029ab..98b0e2a1f66 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -367,15 +367,13 @@ export class ElectronMainService implements IElectronMainService { //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { - return new Promise(resolve => { - const window = this.windowById(windowId); - const session = window?.win?.webContents?.session; - if (session) { - session.resolveProxy(url, proxy => resolve(proxy)); - } else { - resolve(); - } - }); + const window = this.windowById(windowId); + const session = window?.win?.webContents?.session; + if (session) { + return session.resolveProxy(url); + } else { + return undefined; + } } //#endregion diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index 560ed3de651..49c827f2bbc 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -57,6 +57,7 @@ export interface IProgressNotificationOptions extends IProgressOptions { readonly location: ProgressLocation.Notification; readonly primaryActions?: ReadonlyArray; readonly secondaryActions?: ReadonlyArray; + delay?: number; } export interface IProgressWindowOptions extends IProgressOptions { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 2f1d4020484..4e770b08026 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -17,8 +17,12 @@ export interface ResolvedOptions { readonly extensionHostEnv?: { [key: string]: string | null }; } +export interface TunnelDescription { + remoteAddress: { port: number, host: string }; + localAddress: string; +} export interface TunnelInformation { - environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[]; + environmentTunnels?: TunnelDescription[]; hideCandidatePorts?: boolean; } diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts deleted file mode 100644 index a5fa07ac174..00000000000 --- a/src/vs/platform/remote/common/tunnelService.ts +++ /dev/null @@ -1,26 +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 { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; - -export class NoOpTunnelService implements ITunnelService { - _serviceBrand: undefined; - - public readonly tunnels: Promise = Promise.resolve([]); - private _onTunnelOpened: Emitter = new Emitter(); - public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); - public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; - openTunnel(_remoteHost: string, _remotePort: number): Promise | undefined { - return undefined; - } - async closeTunnel(_remoteHost: string, _remotePort: number): Promise { - } - setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { - throw new Error('Method not implemented.'); - } -} diff --git a/src/vs/platform/userDataSync/common/settingsMerge.ts b/src/vs/platform/userDataSync/common/settingsMerge.ts index d7224e9f3a0..1b710e133bd 100644 --- a/src/vs/platform/userDataSync/common/settingsMerge.ts +++ b/src/vs/platform/userDataSync/common/settingsMerge.ts @@ -10,6 +10,7 @@ import { values } from 'vs/base/common/map'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import * as contentUtil from 'vs/platform/userDataSync/common/content'; +import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; export function computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string { if (ignoredSettings.length) { @@ -24,7 +25,7 @@ export function computeRemoteContent(localContent: string, remoteContent: string return localContent; } -export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } { +export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], resolvedConflicts: { key: string, value: any | undefined }[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, conflicts: IConflictSetting[] } { const local = parse(localContent); const remote = parse(remoteContent); const base = baseContent ? parse(baseContent) : null; @@ -33,30 +34,41 @@ export function merge(localContent: string, remoteContent: string, baseContent: const localToRemote = compare(local, remote, ignored); if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { // No changes found between local and remote. - return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + return { mergeContent: localContent, hasChanges: false, conflicts: [] }; } - const conflicts: Set = new Set(); + const conflicts: Map = new Map(); + const handledConflicts: Set = new Set(); const baseToLocal = base ? compare(base, local, ignored) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; const baseToRemote = base ? compare(base, remote, ignored) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; let mergeContent = localContent; + const handleConflict = (conflictKey: string): void => { + handledConflicts.add(conflictKey); + const resolvedConflict = resolvedConflicts.filter(({ key }) => key === conflictKey)[0]; + if (resolvedConflict) { + mergeContent = contentUtil.edit(mergeContent, [conflictKey], resolvedConflict.value, formattingOptions); + } else { + conflicts.set(conflictKey, { key: conflictKey, localValue: local[conflictKey], remoteValue: remote[conflictKey] }); + } + }; + // Removed settings in Local for (const key of values(baseToLocal.removed)) { // Got updated in remote if (baseToRemote.updated.has(key)) { - conflicts.add(key); + handleConflict(key); } } // Removed settings in Remote for (const key of values(baseToRemote.removed)) { - if (conflicts.has(key)) { + if (handledConflicts.has(key)) { continue; } // Got updated in local if (baseToLocal.updated.has(key)) { - conflicts.add(key); + handleConflict(key); } else { mergeContent = contentUtil.edit(mergeContent, [key], undefined, formattingOptions); } @@ -64,28 +76,28 @@ export function merge(localContent: string, remoteContent: string, baseContent: // Added settings in Local for (const key of values(baseToLocal.added)) { - if (conflicts.has(key)) { + if (handledConflicts.has(key)) { continue; } // Got added in remote if (baseToRemote.added.has(key)) { // Has different value if (localToRemote.updated.has(key)) { - conflicts.add(key); + handleConflict(key); } } } // Added settings in remote for (const key of values(baseToRemote.added)) { - if (conflicts.has(key)) { + if (handledConflicts.has(key)) { continue; } // Got added in local if (baseToLocal.added.has(key)) { // Has different value if (localToRemote.updated.has(key)) { - conflicts.add(key); + handleConflict(key); } } else { mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions); @@ -94,28 +106,28 @@ export function merge(localContent: string, remoteContent: string, baseContent: // Updated settings in Local for (const key of values(baseToLocal.updated)) { - if (conflicts.has(key)) { + if (handledConflicts.has(key)) { continue; } // Got updated in remote if (baseToRemote.updated.has(key)) { // Has different value if (localToRemote.updated.has(key)) { - conflicts.add(key); + handleConflict(key); } } } // Updated settings in Remote for (const key of values(baseToRemote.updated)) { - if (conflicts.has(key)) { + if (handledConflicts.has(key)) { continue; } // Got updated in local if (baseToLocal.updated.has(key)) { // Has different value if (localToRemote.updated.has(key)) { - conflicts.add(key); + handleConflict(key); } } else { mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions); @@ -126,7 +138,7 @@ export function merge(localContent: string, remoteContent: string, baseContent: const conflictNodes: { key: string, node: Node | undefined }[] = []; const tree = parseTree(mergeContent); const eol = formattingOptions.eol!; - for (const key of values(conflicts)) { + for (const { key } of values(conflicts)) { const node = findNodeAtLocation(tree, [key]); conflictNodes.push({ key, node }); } @@ -166,7 +178,7 @@ export function merge(localContent: string, remoteContent: string, baseContent: } } - return { mergeContent, hasChanges: true, hasConflicts: conflicts.size > 0 }; + return { mergeContent, hasChanges: true, conflicts: values(conflicts) }; } function compare(from: IStringDictionary, to: IStringDictionary, ignored: Set): { added: Set, removed: Set, updated: Set } { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index d65b456a539..09dd0a4647e 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -5,7 +5,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -19,16 +19,20 @@ import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; +import * as arrays from 'vs/base/common/arrays'; +import * as objects from 'vs/base/common/objects'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; readonly remoteUserData: IUserData; readonly hasLocalChanged: boolean; readonly hasRemoteChanged: boolean; - readonly hasConflicts: boolean; + readonly conflicts: IConflictSetting[]; } -export class SettingsSynchroniser extends Disposable implements ISynchroniser { +export class SettingsSynchroniser extends Disposable implements ISettingsSyncService { + + _serviceBrand: any; private static EXTERNAL_USER_DATA_SETTINGS_KEY: string = 'settings'; @@ -39,6 +43,11 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + private _conflicts: IConflictSetting[] = []; + get conflicts(): IConflictSetting[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -63,6 +72,18 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { this._status = status; this._onDidChangStatus.fire(status); } + if (this._status !== SyncStatus.HasConflicts) { + this.setConflicts([]); + } + } + + private setConflicts(conflicts: IConflictSetting[]): void { + if (!arrays.equals(this.conflicts, conflicts, + (a, b) => a.key === b.key && objects.equals(a.localValue, b.localValue) && objects.equals(a.remoteValue, b.remoteValue)) + ) { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } } async sync(_continue?: boolean): Promise { @@ -83,10 +104,31 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { this.logService.trace('Settings: Started synchronizing settings...'); this.setStatus(SyncStatus.Syncing); + return this.doSync([]); + } + stop(): void { + if (this.syncPreviewResultPromise) { + this.syncPreviewResultPromise.cancel(); + this.syncPreviewResultPromise = null; + this.logService.info('Settings: Stopped synchronizing settings.'); + } + this.fileService.del(this.environmentService.settingsSyncPreviewResource); + this.setStatus(SyncStatus.Idle); + } + + async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { + if (this.status === SyncStatus.HasConflicts) { + this.syncPreviewResultPromise!.cancel(); + this.syncPreviewResultPromise = null; + await this.doSync(resolvedConflicts); + } + } + + private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { try { - const result = await this.getPreview(); - if (result.hasConflicts) { + const result = await this.getPreview(resolvedConflicts); + if (result.conflicts.length) { this.logService.info('Settings: Detected conflicts while synchronizing settings.'); this.setStatus(SyncStatus.HasConflicts); return false; @@ -110,16 +152,6 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { } } - stop(): void { - if (this.syncPreviewResultPromise) { - this.syncPreviewResultPromise.cancel(); - this.syncPreviewResultPromise = null; - this.logService.info('Settings: Stopped synchronizing settings.'); - } - this.fileService.del(this.environmentService.settingsSyncPreviewResource); - this.setStatus(SyncStatus.Idle); - } - private async continueSync(): Promise { if (this.status === SyncStatus.HasConflicts) { await this.apply(); @@ -178,14 +210,14 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { return parseErrors.length > 0; } - private getPreview(): Promise { + private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise { if (!this.syncPreviewResultPromise) { - this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(token)); + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview(resolvedConflicts, token)); } return this.syncPreviewResultPromise; } - private async generatePreview(token: CancellationToken): Promise { + private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise { const lastSyncData = await this.getLastSyncUserData(); const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData); const remoteContent: string | null = remoteUserData.content; @@ -193,28 +225,29 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { const fileContent = await this.getLocalFileContent(); let hasLocalChanged: boolean = false; let hasRemoteChanged: boolean = false; - let hasConflicts: boolean = false; + let conflicts: IConflictSetting[] = []; let previewContent = null; if (remoteContent) { const localContent: string = fileContent ? fileContent.value.toString() : '{}'; + + // No action when there are errors if (this.hasErrors(localContent)) { this.logService.error('Settings: Unable to sync settings as there are errors/warning in settings file.'); - return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } - if (!lastSyncData // First time sync + else if (!lastSyncData // First time sync || lastSyncData.content !== localContent // Local has forwarded || lastSyncData.content !== remoteContent // Remote has forwarded ) { this.logService.trace('Settings: Merging remote settings with local settings...'); const formatUtils = await this.getFormattingOptions(); - const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), formatUtils); + const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), resolvedConflicts, formatUtils); // Sync only if there are changes if (result.hasChanges) { hasLocalChanged = result.mergeContent !== localContent; hasRemoteChanged = result.mergeContent !== remoteContent; - hasConflicts = result.hasConflicts; + conflicts = result.conflicts; previewContent = result.mergeContent; } } @@ -231,7 +264,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent)); } - return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + this.setConflicts(conflicts); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts }; } private _formattingOptions: Promise | undefined = undefined; diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index e273fdde569..ca76015a885 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -57,7 +57,7 @@ export function registerConfiguration(): IDisposable { }, 'sync.enableUIState': { type: 'boolean', - description: localize('sync.enableUIState', "Enable synchronizing UI state."), + description: localize('sync.enableUIState', "Enable synchronizing UI state (Only Display Language)."), default: true, scope: ConfigurationScope.APPLICATION, }, @@ -135,12 +135,9 @@ export function getUserDataSyncStore(configurationService: IConfigurationService } export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); - export interface IUserDataSyncStoreService { _serviceBrand: undefined; - readonly userDataSyncStore: IUserDataSyncStore | undefined; - read(key: string, oldValue: IUserData | null): Promise; write(key: string, content: string, ref: string | null): Promise; } @@ -170,40 +167,42 @@ export const enum SyncStatus { } export interface ISynchroniser { - readonly status: SyncStatus; readonly onDidChangeStatus: Event; readonly onDidChangeLocal: Event; - sync(_continue?: boolean): Promise; stop(): void; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); - export interface IUserDataSyncService extends ISynchroniser { _serviceBrand: any; readonly conflictsSource: SyncSource | null; - removeExtension(identifier: IExtensionIdentifier): Promise; } export const IUserDataSyncUtilService = createDecorator('IUserDataSyncUtilService'); - export interface IUserDataSyncUtilService { - _serviceBrand: undefined; - resolveUserBindings(userbindings: string[]): Promise>; - resolveFormattingOptions(resource: URI): Promise; - } export const IUserDataSyncLogService = createDecorator('IUserDataSyncLogService'); +export interface IUserDataSyncLogService extends ILogService { } -export interface IUserDataSyncLogService extends ILogService { +export interface IConflictSetting { + key: string; + localValue: any | undefined; + remoteValue: any | undefined; +} +export const ISettingsSyncService = createDecorator('ISettingsSyncService'); +export interface ISettingsSyncService extends ISynchroniser { + _serviceBrand: any; + readonly onDidChangeConflicts: Event; + readonly conflicts: IConflictSetting[]; + resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; } export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index b910d3f3b85..bcd53cae1b7 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -5,7 +5,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { IUserDataSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { URI } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; @@ -34,6 +34,31 @@ export class UserDataSyncChannel implements IServerChannel { } } +export class SettingsSyncChannel implements IServerChannel { + + constructor(private readonly service: ISettingsSyncService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChangeStatus': return this.service.onDidChangeStatus; + case 'onDidChangeLocal': return this.service.onDidChangeLocal; + case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; + } + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'sync': return this.service.sync(args[0]); + case '_getInitialStatus': return Promise.resolve(this.service.status); + case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); + case 'stop': this.service.stop(); return Promise.resolve(); + case 'resolveConflicts': return this.service.resolveConflicts(args[0]); + } + throw new Error('Invalid call'); + } +} + export class UserDataSycnUtilServiceChannel implements IServerChannel { constructor(private readonly service: IUserDataSyncUtilService) { } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 7619d616ddb..1bf9f49040d 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; @@ -30,7 +30,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _conflictsSource: SyncSource | null = null; get conflictsSource(): SyncSource | null { return this._conflictsSource; } - private readonly settingsSynchroniser: SettingsSynchroniser; private readonly keybindingsSynchroniser: KeybindingsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser; private readonly globalStateSynchroniser: GlobalStateSynchroniser; @@ -39,9 +38,9 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IAuthTokenService private readonly authTokenService: IAuthTokenService, + @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, ) { super(); - this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); this.globalStateSynchroniser = this._register(this.instantiationService.createInstance(GlobalStateSynchroniser)); this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 911e11c97b7..343c99b38d6 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { merge, computeRemoteContent } from 'vs/platform/userDataSync/common/settingsMerge'; +import { IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; const formattingOptions = { eol: '\n', insertSpaces: false, tabSize: 4 }; @@ -13,9 +14,9 @@ suite('SettingsMerge - No Conflicts', () => { test('merge when local and remote are same with one entry', async () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 1 }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -28,9 +29,9 @@ suite('SettingsMerge - No Conflicts', () => { 'a': 1, 'b': 2 }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -43,9 +44,9 @@ suite('SettingsMerge - No Conflicts', () => { 'a': 1, 'b': 2 }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -62,9 +63,9 @@ suite('SettingsMerge - No Conflicts', () => { 'a': 1, 'b': 2 }); - const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -76,9 +77,9 @@ suite('SettingsMerge - No Conflicts', () => { 'a': 1, 'b': 2 }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, remoteContent); }); @@ -96,9 +97,9 @@ suite('SettingsMerge - No Conflicts', () => { 'b': 2, 'c': 3, }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, expected); }); @@ -116,9 +117,9 @@ suite('SettingsMerge - No Conflicts', () => { 'b': 2, 'c': 3, }); - const actual = merge(localContent, remoteContent, localContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, expected); }); @@ -130,9 +131,9 @@ suite('SettingsMerge - No Conflicts', () => { const remoteContent = stringify({ 'a': 1, }); - const actual = merge(localContent, remoteContent, localContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, remoteContent); }); @@ -141,9 +142,9 @@ suite('SettingsMerge - No Conflicts', () => { 'a': 1, }); const remoteContent = stringify({}); - const actual = merge(localContent, remoteContent, localContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.deepEqual(JSON.parse(actual.mergeContent), {}); }); @@ -154,9 +155,9 @@ suite('SettingsMerge - No Conflicts', () => { const remoteContent = stringify({ 'a': 2 }); - const actual = merge(localContent, remoteContent, localContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, remoteContent); }); @@ -170,9 +171,9 @@ suite('SettingsMerge - No Conflicts', () => { 'c': 3, 'd': 4, }); - const actual = merge(localContent, remoteContent, localContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, remoteContent); }); @@ -186,9 +187,9 @@ suite('SettingsMerge - No Conflicts', () => { const remoteContent = stringify({ 'a': 1, }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -202,9 +203,9 @@ suite('SettingsMerge - No Conflicts', () => { const remoteContent = stringify({ 'a': 1, }); - const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -219,9 +220,9 @@ suite('SettingsMerge - No Conflicts', () => { 'c': 3, 'd': 4, }); - const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -234,9 +235,9 @@ suite('SettingsMerge - No Conflicts', () => { 'a': 2, 'c': 2, }); - const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -250,9 +251,9 @@ suite('SettingsMerge - No Conflicts', () => { const remoteContent = stringify({ 'a': 1, }); - const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions); + const actual = merge(localContent, remoteContent, remoteContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -267,9 +268,10 @@ suite('SettingsMerge - Conflicts', () => { const remoteContent = stringify({ 'a': 2 }); - const actual = merge(localContent, remoteContent, null, [], formattingOptions); + const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: 1, remoteValue: 2 }]; + const actual = merge(localContent, remoteContent, null, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); + assert.deepEqual(actual.conflicts, expectedConflicts); assert.equal(actual.mergeContent, `{ <<<<<<< local @@ -290,9 +292,10 @@ suite('SettingsMerge - Conflicts', () => { const remoteContent = stringify({ 'b': 2 }); - const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions); + const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: 2, remoteValue: undefined }]; + const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); + assert.deepEqual(actual.conflicts, expectedConflicts); assert.equal(actual.mergeContent, `{ <<<<<<< local @@ -311,9 +314,10 @@ suite('SettingsMerge - Conflicts', () => { const remoteContent = stringify({ 'a': 2 }); - const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions); + const expectedConflicts: IConflictSetting[] = [{ key: 'a', localValue: undefined, remoteValue: 2 }]; + const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); + assert.deepEqual(actual.conflicts, expectedConflicts); assert.equal(actual.mergeContent, `{ <<<<<<< local @@ -343,9 +347,15 @@ suite('SettingsMerge - Conflicts', () => { 'd': 6, 'e': 5, }); - const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions); + const expectedConflicts: IConflictSetting[] = [ + { key: 'b', localValue: undefined, remoteValue: 3 }, + { key: 'a', localValue: 2, remoteValue: undefined }, + { key: 'e', localValue: 4, remoteValue: 5 }, + { key: 'd', localValue: 5, remoteValue: 6 }, + ]; + const actual = merge(localContent, remoteContent, baseContent, [], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); + assert.deepEqual(actual.conflicts, expectedConflicts); assert.equal(actual.mergeContent, `{ <<<<<<< local @@ -371,6 +381,46 @@ suite('SettingsMerge - Conflicts', () => { }`); }); + test('resolve when local and remote has moved forwareded with conflicts', async () => { + const baseContent = stringify({ + 'a': 1, + 'b': 2, + 'c': 3, + 'd': 4, + }); + const localContent = stringify({ + 'a': 2, + 'c': 3, + 'd': 5, + 'e': 4, + 'f': 1, + }); + const remoteContent = stringify({ + 'b': 3, + 'c': 3, + 'd': 6, + 'e': 5, + }); + const expectedConflicts: IConflictSetting[] = [ + { key: 'd', localValue: 5, remoteValue: 6 }, + ]; + const actual = merge(localContent, remoteContent, baseContent, [], [{ key: 'a', value: 2 }, { key: 'b', value: undefined }, { key: 'e', value: 5 }], formattingOptions); + assert.ok(actual.hasChanges); + assert.deepEqual(actual.conflicts, expectedConflicts); + assert.equal(actual.mergeContent, + `{ + "a": 2, + "c": 3, +<<<<<<< local + "d": 5, +======= + "d": 6, +>>>>>>> remote + "e": 5, + "f": 1 +}`); + }); + }); suite('SettingsMerge - Ignored Settings', () => { @@ -378,9 +428,9 @@ suite('SettingsMerge - Ignored Settings', () => { test('ignored setting is not merged when changed in local and remote', async () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 2 }); - const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions); + const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -388,45 +438,45 @@ suite('SettingsMerge - Ignored Settings', () => { const baseContent = stringify({ 'a': 0 }); const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({ 'a': 2 }); - const actual = merge(localContent, remoteContent, baseContent, ['a'], formattingOptions); + const actual = merge(localContent, remoteContent, baseContent, ['a'], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); test('ignored setting is not merged when added in remote', async () => { const localContent = stringify({}); const remoteContent = stringify({ 'a': 1 }); - const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions); + const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); test('ignored setting is not merged when added in remote from base', async () => { const localContent = stringify({ 'b': 2 }); const remoteContent = stringify({ 'a': 1, 'b': 2 }); - const actual = merge(localContent, remoteContent, localContent, ['a'], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, ['a'], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); test('ignored setting is not merged when removed in remote', async () => { const localContent = stringify({ 'a': 1 }); const remoteContent = stringify({}); - const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions); + const actual = merge(localContent, remoteContent, null, ['a'], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); test('ignored setting is not merged when removed in remote from base', async () => { const localContent = stringify({ 'a': 2 }); const remoteContent = stringify({}); - const actual = merge(localContent, remoteContent, localContent, ['a'], formattingOptions); + const actual = merge(localContent, remoteContent, localContent, ['a'], [], formattingOptions); assert.ok(!actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, localContent); }); @@ -453,9 +503,9 @@ suite('SettingsMerge - Ignored Settings', () => { 'a': 1, 'b': 3, }); - const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions); + const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], [], formattingOptions); assert.ok(actual.hasChanges); - assert.ok(!actual.hasConflicts); + assert.equal(actual.conflicts.length, 0); assert.equal(actual.mergeContent, expectedContent); }); @@ -478,12 +528,14 @@ suite('SettingsMerge - Ignored Settings', () => { 'b': 3, 'e': 6, }); - const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions); - //'{\n\t"a": 1,\n\n<<<<<<< local\t"b": 4,\n=======\n\t"b": 3,\n>>>>>>> remote' - //'{\n\t"a": 1,\n<<<<<<< local\n\t"b": 4,\n=======\n\t"b": 3,\n>>>>>>> remote\n<<<<<<< local\n\t"d": 5\n=======\n>>>>>>> remote\n}' + const expectedConflicts: IConflictSetting[] = [ + { key: 'd', localValue: 5, remoteValue: undefined }, + { key: 'b', localValue: 4, remoteValue: 3 }, + ]; + const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], [], formattingOptions); assert.ok(actual.hasChanges); assert.ok(actual.hasChanges); - assert.ok(actual.hasConflicts); + assert.deepEqual(actual.conflicts, expectedConflicts); assert.equal(actual.mergeContent, `{ "a": 1, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 141ed91c941..15d667e14fc 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -40,10 +40,13 @@ declare module 'vscode' { label?: string; } - export interface Tunnel { + export interface TunnelDescription { remoteAddress: { port: number, host: string }; //The complete local address(ex. localhost:1234) localAddress: string; + } + + export interface Tunnel extends TunnelDescription { // Implementers of Tunnel should fire onDidDispose when dispose is called. onDidDispose: Event; dispose(): void; @@ -58,7 +61,7 @@ declare module 'vscode' { * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through * detected are read-only from the forwarded ports UI. */ - environmentTunnels?: { remoteAddress: { port: number, host: string }, localAddress: string }[]; + environmentTunnels?: TunnelDescription[]; hideCandidatePorts?: boolean; } @@ -1302,7 +1305,7 @@ declare module 'vscode' { //#region Language specific settings: https://github.com/microsoft/vscode/issues/26707 - export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { uri: Uri, languageId: string }; + export type ConfigurationScope = Uri | TextDocument | WorkspaceFolder | { uri?: Uri, languageId: string }; /** * An event describing the change in Configuration @@ -1428,6 +1431,8 @@ declare module 'vscode' { workspaceLanguageValue?: T; workspaceFolderLanguageValue?: T; + languages?: string[]; + } | undefined; /** diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 2b743a9484f..c54c2613028 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -227,12 +227,12 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise { - return this._untitledTextEditorService.loadOrCreate({ + return this._untitledTextEditorService.createOrGet({ resource, mode, initialValue, useResourcePath: Boolean(resource && resource.path) - }).then(model => { + }).resolve().then(model => { const resource = model.resource; if (!this._modelIsSynced.has(resource.toString())) { diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 62282e00c5f..64d9ae97df7 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -27,7 +27,7 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IProgressService, ProgressLocation, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ISaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -37,6 +37,8 @@ import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/sta import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { canceled, isPromiseCanceledError } from 'vs/base/common/errors'; export interface ICodeActionsOnSaveOptions { [kind: string]: boolean; @@ -48,8 +50,8 @@ class SaveParticipantError extends Error { } } -export interface ISaveParticipantParticipant extends ISaveParticipant { - // progressMessage: string; +export interface ISaveParticipantParticipant { + participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }, progress: IProgress, token: CancellationToken): Promise; } class TrimWhitespaceParticipant implements ISaveParticipantParticipant { @@ -92,7 +94,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { return; // Nothing to do } - model.pushEditOperations(prevSelection, ops, (edits) => prevSelection); + model.pushEditOperations(prevSelection, ops, (_edits) => prevSelection); } } @@ -123,7 +125,7 @@ export class FinalNewLineParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise { if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.resource })) { this.doInsertFinalNewLine(model.textEditorModel); } @@ -209,7 +211,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant return; } - model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], edits => prevSelection); + model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], _edits => prevSelection); if (editor) { editor.setSelections(prevSelection); @@ -227,7 +229,7 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { const model = editorModel.textEditorModel; const overrides = { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri }; @@ -236,26 +238,13 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { return undefined; } - return new Promise((resolve, reject) => { - const source = new CancellationTokenSource(); - const editorOrModel = findEditor(model, this._codeEditorService) || model; - const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', overrides); - const request = this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, source.token); - - setTimeout(() => { - reject(new SaveParticipantError( - localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout), - 'editor.formatOnSaveTimeout' - )); - source.cancel(); - }, timeout); - - request.then(resolve, reject); - }); + progress.report({ message: localize('formatting', "Formatting") }); + const editorOrModel = findEditor(model, this._codeEditorService) || model; + await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, editorOrModel, FormattingMode.Silent, token); } } -class CodeActionOnSaveParticipant implements ISaveParticipant { +class CodeActionOnSaveParticipant implements ISaveParticipantParticipant { constructor( @IBulkEditService private readonly _bulkEditService: IBulkEditService, @@ -264,7 +253,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress, token: CancellationToken): Promise { if (env.reason === SaveReason.AUTO) { return undefined; } @@ -300,23 +289,8 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { .filter(x => setting[x] === false) .map(x => new CodeActionKind(x)); - const tokenSource = new CancellationTokenSource(); - - const timeout = this._configurationService.getValue('editor.codeActionsOnSaveTimeout', settingsOverrides); - - return Promise.race([ - new Promise((_resolve, reject) => - setTimeout(() => { - tokenSource.cancel(); - reject(new SaveParticipantError( - localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout), - 'editor.codeActionsOnSaveTimeout' - )); - }, timeout)), - this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, tokenSource.token) - ]).finally(() => { - tokenSource.cancel(); - }); + progress.report({ message: localize('codeaction', "Quick Fixes") }); + await this.applyOnSaveActions(model, codeActionsOnSave, excludedActions, token); } private async applyOnSaveActions(model: ITextModel, codeActionsOnSave: readonly CodeActionKind[], excludes: readonly CodeActionKind[], token: CancellationToken): Promise { @@ -354,7 +328,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress, token: CancellationToken): Promise { if (!shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension @@ -363,6 +337,9 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant { } return new Promise((resolve, reject) => { + + token.onCancellationRequested(() => reject(canceled())); + setTimeout( () => reject(new SaveParticipantError(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))), 1750 @@ -388,7 +365,8 @@ export class SaveParticipant implements ISaveParticipant { @IInstantiationService instantiationService: IInstantiationService, @IProgressService private readonly _progressService: IProgressService, @IStatusbarService private readonly _statusbarService: IStatusbarService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @ILabelService private readonly _labelService: ILabelService, ) { this._saveParticipants = new IdleValue(() => [ instantiationService.createInstance(TrimWhitespaceParticipant), @@ -408,23 +386,41 @@ export class SaveParticipant implements ISaveParticipant { } async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { - return this._progressService.withProgress({ location: ProgressLocation.Window }, async progress => { - progress.report({ message: localize('saveParticipants', "Running Save Participants...") }); + + const cts = new CancellationTokenSource(); + + return this._progressService.withProgress({ + title: localize('saveParticipants', "Running Save Participants for '{0}'", this._labelService.getUriLabel(model.resource, { relative: true })), + location: ProgressLocation.Notification, + cancellable: true, + delay: model.isDirty() ? 3000 : 5000 + }, async progress => { let firstError: SaveParticipantError | undefined; - for (let p of this._saveParticipants.getValue()) { + + if (cts.token.isCancellationRequested) { + break; + } + try { - await p.participate(model, env); + await p.participate(model, env, progress, cts.token); + } catch (err) { - this._logService.warn(err); - firstError = !firstError && err instanceof SaveParticipantError ? err : firstError; + if (!isPromiseCanceledError(err)) { + this._logService.warn(err); + firstError = !firstError && err instanceof SaveParticipantError ? err : firstError; + } } } if (firstError) { this._showParticipantError(firstError); } + + }, () => { + // user cancel + cts.dispose(true); }); } diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 2319465e377..ad9446b7394 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -45,14 +45,9 @@ type ConfigurationInspect = { userLanguageValue?: T; workspaceLanguageValue?: T; workspaceFolderLanguageValue?: T; -}; -function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { - return thing - && thing.uri instanceof URI - && (!thing.name || typeof thing.name === 'string') - && (!thing.index || typeof thing.index === 'number'); -} + languages?: string[]; +}; function isUri(thing: any): thing is vscode.Uri { return thing instanceof URI; @@ -61,19 +56,35 @@ function isUri(thing: any): thing is vscode.Uri { function isResourceLanguage(thing: any): thing is { uri: URI, languageId: string } { return thing && thing.uri instanceof URI - && (!thing.languageId || typeof thing.languageId === 'string'); + && (thing.languageId && typeof thing.languageId === 'string'); +} + +function isLanguage(thing: any): thing is { languageId: string } { + return thing + && !thing.uri + && (thing.languageId && typeof thing.languageId === 'string'); +} + +function isWorkspaceFolder(thing: any): thing is vscode.WorkspaceFolder { + return thing + && thing.uri instanceof URI + && (!thing.name || typeof thing.name === 'string') + && (!thing.index || typeof thing.index === 'number'); } function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): IConfigurationOverrides | undefined { if (isUri(scope)) { return { resource: scope }; } - if (isWorkspaceFolder(scope)) { - return { resource: scope.uri }; - } if (isResourceLanguage(scope)) { return { resource: scope.uri, overrideIdentifier: scope.languageId }; } + if (isLanguage(scope)) { + return { overrideIdentifier: scope.languageId }; + } + if (isWorkspaceFolder(scope)) { + return { resource: scope.uri }; + } return undefined; } @@ -255,6 +266,8 @@ export class ExtHostConfigProvider { userLanguageValue: config.user?.override, workspaceLanguageValue: config.workspace?.override, workspaceFolderLanguageValue: config.workspaceFolder?.override, + + languages: config.overrideIdentifiers }; } return undefined; diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 446a36120d4..a3b0090c518 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -337,7 +337,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb } // make sure that only one factory for this type is registered - if (this.getAdapterFactoryByType(type)) { + if (this.getAdapterDescriptorFactoryByType(type)) { throw new Error(`a DebugAdapterDescriptorFactory can only be registered once per a type.`); } @@ -412,89 +412,92 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb const session = await this.getSession(sessionDto); - return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(daDescriptor => { + return this.getAdapterDescriptor(this.getAdapterDescriptorFactoryByType(session.type), session).then(daDescriptor => { - const adapter = this.convertToDto(daDescriptor); + if (!daDescriptor) { + throw new Error(`Couldn't find a debug adapter descriptor for debug type '${session.type}' (extension might have failed to activate)`); + } - const da: AbstractDebugAdapter | undefined = this.createDebugAdapter(adapter, session); + const adapterDescriptor = this.convertToDto(daDescriptor); + + const da = this.createDebugAdapter(adapterDescriptor, session); + if (!da) { + throw new Error(`Couldn't create a debug adapter for type '${session.type}'.`); + } const debugAdapter = da; - if (debugAdapter) { - this._debugAdapters.set(debugAdapterHandle, debugAdapter); + this._debugAdapters.set(debugAdapterHandle, debugAdapter); - return this.getDebugAdapterTrackers(session).then(tracker => { + return this.getDebugAdapterTrackers(session).then(tracker => { - if (tracker) { - this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); - } + if (tracker) { + this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); + } - debugAdapter.onMessage(async message => { + debugAdapter.onMessage(async message => { - if (message.type === 'request' && (message).command === 'handshake') { + if (message.type === 'request' && (message).command === 'handshake') { - const request = message; + const request = message; - const response: DebugProtocol.Response = { - type: 'response', - seq: 0, - command: request.command, - request_seq: request.seq, - success: true - }; + const response: DebugProtocol.Response = { + type: 'response', + seq: 0, + command: request.command, + request_seq: request.seq, + success: true + }; - if (!this._signService) { - this._signService = this.createSignService(); - } + if (!this._signService) { + this._signService = this.createSignService(); + } - try { - if (this._signService) { - const signature = await this._signService.sign(request.arguments.value); - response.body = { - signature: signature - }; - debugAdapter.sendResponse(response); - } else { - throw new Error('no signer'); - } - } catch (e) { - response.success = false; - response.message = e.message; + try { + if (this._signService) { + const signature = await this._signService.sign(request.arguments.value); + response.body = { + signature: signature + }; debugAdapter.sendResponse(response); + } else { + throw new Error('no signer'); } - } else { - if (tracker && tracker.onDidSendMessage) { - tracker.onDidSendMessage(message); - } - - // DA -> VS Code - message = convertToVSCPaths(message, true); - - mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); + } catch (e) { + response.success = false; + response.message = e.message; + debugAdapter.sendResponse(response); } - }); - debugAdapter.onError(err => { - if (tracker && tracker.onError) { - tracker.onError(err); + } else { + if (tracker && tracker.onDidSendMessage) { + tracker.onDidSendMessage(message); } - this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); - }); - debugAdapter.onExit((code: number | null) => { - if (tracker && tracker.onExit) { - tracker.onExit(withNullAsUndefined(code), undefined); - } - this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, withNullAsUndefined(code), undefined); - }); - if (tracker && tracker.onWillStartSession) { - tracker.onWillStartSession(); + // DA -> VS Code + message = convertToVSCPaths(message, true); + + mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); } - - return debugAdapter.startSession(); + }); + debugAdapter.onError(err => { + if (tracker && tracker.onError) { + tracker.onError(err); + } + this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); + }); + debugAdapter.onExit((code: number | null) => { + if (tracker && tracker.onExit) { + tracker.onExit(withNullAsUndefined(code), undefined); + } + this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, withNullAsUndefined(code), undefined); }); - } - return undefined; + if (tracker && tracker.onWillStartSession) { + tracker.onWillStartSession(); + } + + return debugAdapter.startSession(); + }); }); } @@ -663,13 +666,18 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb }); } - public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { - const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); - if (!adapterProvider) { - return Promise.reject(new Error('no handler found')); + public async $provideDebugAdapter(adapterFactoryHandle: number, sessionDto: IDebugSessionDto): Promise { + const adapterDescriptorFactory = this.getAdapterDescriptorFactoryByHandle(adapterFactoryHandle); + if (!adapterDescriptorFactory) { + return Promise.reject(new Error('no adapter descriptor factory found for handle')); } const session = await this.getSession(sessionDto); - return this.getAdapterDescriptor(adapterProvider, session).then(x => this.convertToDto(x)); + return this.getAdapterDescriptor(adapterDescriptorFactory, session).then(adapterDescriptor => { + if (!adapterDescriptor) { + throw new Error(`Couldn't find a debug adapter descriptor for debug type '${session.type}'`); + } + return this.convertToDto(adapterDescriptor); + }); } public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { @@ -709,7 +717,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb // private & dto helpers - private convertToDto(x: vscode.DebugAdapterDescriptor | undefined): IAdapterDescriptor { + private convertToDto(x: vscode.DebugAdapterDescriptor): IAdapterDescriptor { if (x instanceof DebugAdapterExecutable) { return { @@ -734,7 +742,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb } } - private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { + private getAdapterDescriptorFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { const results = this._adapterFactories.filter(p => p.type === type); if (results.length > 0) { return results[0].factory; @@ -742,7 +750,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb return undefined; } - private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { + private getAdapterDescriptorFactoryByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { const results = this._adapterFactories.filter(p => p.handle === handle); if (results.length > 0) { return results[0].factory; @@ -804,7 +812,7 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb }); } - private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { + private async getAdapterDescriptor(adapterDescriptorFactory: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { // a "debugServer" attribute in the launch config takes precedence const serverPort = session.configuration.debugServer; @@ -824,9 +832,9 @@ export class ExtHostDebugServiceBase implements IExtHostDebugService, ExtHostDeb }); } - if (adapterProvider) { + if (adapterDescriptorFactory) { const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { + return asPromise(() => adapterDescriptorFactory.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { if (daDescriptor) { return daDescriptor; } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index c9b4bd50a14..ef2679b4f9d 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -130,7 +130,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath); const rawCmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8'); const nullIndex = rawCmd.indexOf('\0'); - const cmd = rawCmd.substr(0, nullIndex > 0 ? nullIndex : rawCmd.length); + const cmd = rawCmd.substr(0, nullIndex > 0 ? nullIndex : rawCmd.length).trim(); processes.push({ pid, cwd, cmd }); } } catch (e) { @@ -183,7 +183,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe ip: this.parseIpAddress(address[0]), port: parseInt(address[1], 16) }; - }).map(port => [port.port, port]) + }).map(port => [port.ip + ':' + port.port, port]) ).values() ]; } diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 157494928b5..6d582d72c39 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -357,6 +357,8 @@ export class CustomTreeView extends Disposable implements ITreeView { // Pass Focus to Viewer this.tree.domFocus(); + } else if (this.tree) { + this.tree.domFocus(); } else { this.domNode.focus(); } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 3400b61747e..fc7f3528c07 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -316,7 +316,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } create(parent: HTMLElement): void { - // super.create(parent); this.paneview = this._register(new PaneView(parent, this.options)); this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane))); this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index 584291fb088..2f44e59ad78 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -67,13 +67,13 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private onUntitledModelCreated(resource: Uri): void { if (this.untitledTextEditorService.isDirty(resource)) { - this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); + this.untitledTextEditorService.createOrGet({ resource }).resolve().then(model => model.backup()); } } private onUntitledModelChanged(resource: Uri): void { if (this.untitledTextEditorService.isDirty(resource)) { - this.untitledTextEditorService.loadOrCreate({ resource }).then(model => model.backup()); + this.untitledTextEditorService.createOrGet({ resource }).resolve().then(model => model.backup()); } else { this.discardBackup(resource); } diff --git a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts index 53153e88dca..16c1f7aa131 100644 --- a/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts +++ b/src/vs/workbench/contrib/codeActions/common/codeActionsContribution.ts @@ -40,13 +40,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = { export const editorConfiguration = Object.freeze({ ...editorConfigurationBaseNode, properties: { - 'editor.codeActionsOnSave': codeActionsOnSaveSchema, - 'editor.codeActionsOnSaveTimeout': { - type: 'number', - default: 750, - description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled."), - scope: ConfigurationScope.RESOURCE_LANGUAGE, - }, + 'editor.codeActionsOnSave': codeActionsOnSaveSchema } }); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 0052f255b5b..83b983abb81 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -11,13 +11,12 @@ import severity from 'vs/base/common/severity'; import { IAction, Action } from 'vs/base/common/actions'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel, OverviewRulerLane, IModelDecorationOverviewRulerOptions } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, IBreakpointEditorContribution, IBreakpointUpdateData, IDebugConfiguration, State, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -145,7 +144,7 @@ async function createCandidateDecorations(model: ITextModel, breakpointDecoratio return result; } -class BreakpointEditorContribution implements IBreakpointEditorContribution { +export class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointHintDecoration: string[] = []; private breakpointWidget: BreakpointWidget | undefined; @@ -692,5 +691,3 @@ const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpoin const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, nls.localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); const debugIconBreakpointCurrentStackframeForeground = registerColor('debugIcon.breakpointCurrentStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, nls.localize('debugIcon.breakpointCurrentStackframeForeground', 'Icon color for the current breakpoint stack frame.')); const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, nls.localize('debugIcon.breakpointStackframeForeground', 'Icon color for all breakpoint stack frames.')); - -registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution); diff --git a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts index ad6a80d55db..fad30d12449 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackEditorContribution.ts @@ -13,7 +13,6 @@ import { localize } from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; @@ -82,7 +81,7 @@ export function createDecorationsForStackFrame(stackFrame: IStackFrame, topStack return result; } -class CallStackEditorContribution implements IEditorContribution { +export class CallStackEditorContribution implements IEditorContribution { private toDispose: IDisposable[] = []; private decorationIds: string[] = []; private topStackFrameRange: Range | undefined; @@ -147,5 +146,3 @@ registerThemingParticipant((theme, collector) => { const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); - -registerEditorContribution('editor.contrib.callStack', CallStackEditorContribution); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 18d812625a4..7314cb261a9 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -308,7 +308,13 @@ export class CallStackView extends ViewPane { this.ignoreSelectionChangedEvent = true; try { this.tree.setSelection([element]); - this.tree.reveal(element); + // If the element is outside of the screen bounds, + // position it in the middle + if (this.tree.getRelativeTop(element) === null) { + this.tree.reveal(element, 0.5); + } else { + this.tree.reveal(element); + } } catch (e) { } finally { this.ignoreSelectionChangedEvent = false; diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index fa2b03fb4e1..7626a1780ce 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView' import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -49,6 +49,9 @@ import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugCon import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { DebugViewPaneContainer } from 'vs/workbench/contrib/debug/browser/debugViewlet'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { CallStackEditorContribution } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; +import { BreakpointEditorContribution } from 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -336,6 +339,11 @@ registerDebugCallstackItem(TERMINATE_THREAD_ID, nls.localize('terminateThread', registerDebugCallstackItem(RESTART_FRAME_ID, nls.localize('restartFrame', "Restart Frame"), 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame'), CONTEXT_RESTART_FRAME_SUPPORTED)); registerDebugCallstackItem(COPY_STACK_TRACE_ID, nls.localize('copyStackTrace', "Copy Call Stack"), 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('stackFrame')); +// Editor contributions + +registerEditorContribution('editor.contrib.callStack', CallStackEditorContribution); +registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution); + // View menu MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 767b547b64f..2bdfc9c8bf9 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -161,9 +161,6 @@ export class StartDebugActionViewItem implements IActionViewItem { let lastGroup: string | undefined; const disabledIdxs: number[] = []; manager.getAllConfigurations().forEach(({ launch, name, presentation }) => { - if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { - this.selected = this.options.length; - } if (lastGroup !== presentation?.group) { lastGroup = presentation?.group; if (this.options.length) { @@ -171,6 +168,9 @@ export class StartDebugActionViewItem implements IActionViewItem { disabledIdxs.push(this.options.length - 1); } } + if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) { + this.selected = this.options.length; + } const label = inWorkspace ? `${name} (${launch.name})` : name; this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 230f047ad02..5f6e6681695 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -36,7 +36,7 @@ import { getHover } from 'vs/editor/contrib/hover/getHover'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; const HOVER_DELAY = 300; -const LAUNCH_JSON_REGEX = /launch\.json$/; +const LAUNCH_JSON_REGEX = /\.vscode\/launch\.json$/; const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration'; const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index b97556faaa9..0e2f8fd6df4 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -33,6 +33,8 @@ import { dispose } from 'vs/base/common/lifecycle'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; +let ignoreVariableSetEmitter = false; +let useCachedEvaluation = false; export class WatchExpressionsView extends ViewPane { @@ -90,7 +92,12 @@ export class WatchExpressionsView extends ViewPane { if (!this.isBodyVisible()) { this.needsRefresh = true; } else { + if (we && !we.name) { + // We are adding a new input box, no need to re-evaluate watch expressions + useCachedEvaluation = true; + } await this.tree.updateChildren(); + useCachedEvaluation = false; if (we instanceof Expression) { this.tree.reveal(we); } @@ -106,7 +113,11 @@ export class WatchExpressionsView extends ViewPane { this.onWatchExpressionsUpdatedScheduler.schedule(); } })); - this._register(variableSetEmitter.event(() => this.tree.updateChildren())); + this._register(variableSetEmitter.event(() => { + if (!ignoreVariableSetEmitter) { + this.tree.updateChildren(); + } + })); this._register(this.onDidChangeBodyVisibility(visible => { if (visible && this.needsRefresh) { @@ -221,7 +232,7 @@ class WatchExpressionsDataSource implements IAsyncDataSource !!we.name + return Promise.all(watchExpressions.map(we => !!we.name && !useCachedEvaluation ? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we) : Promise.resolve(we))); } @@ -259,7 +270,9 @@ export class WatchExpressionsRenderer extends AbstractExpressionsRenderer { onFinish: (value: string, success: boolean) => { if (success && value) { this.debugService.renameWatchExpression(expression.getId(), value); + ignoreVariableSetEmitter = true; variableSetEmitter.fire(); + ignoreVariableSetEmitter = false; } else if (!expression.name) { this.debugService.removeWatchExpressions(expression.getId()); } diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 24625cbbd51..14d4a539a74 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -3,29 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; -import Severity from 'vs/base/common/severity'; import { URI as uri } from 'vs/base/common/uri'; -import { IPosition, Position } from 'vs/editor/common/core/position'; +import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; -import { AdapterEndEvent, IBreakpoint, IBreakpointData, IBreakpointsChangeEvent, IBreakpointUpdateData, IConfig, IConfigurationManager, IDataBreakpoint, IDebugger, IDebugModel, IDebugService, IDebugSession, IDebugSessionOptions, IEvaluate, IExceptionBreakpoint, IExceptionInfo, IExpression, IFunctionBreakpoint, ILaunch, IRawModelUpdate, IReplElement, IReplElementSource, IStackFrame, IThread, IViewModel, LoadedSourceEvent, State } from 'vs/workbench/contrib/debug/common/debug'; +import { Position, IPosition } from 'vs/editor/common/core/position'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; - -const noopEvent = new Emitter().event; +import Severity from 'vs/base/common/severity'; +import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; export class MockDebugService implements IDebugService { public _serviceBrand: undefined; - private readonly _model: IDebugModel; - private readonly _viewModel: IViewModel; - - constructor() { - this._model = new MockDebugModel(); - this._viewModel = new MockDebugViewModel(); - } - public get state(): State { throw new Error('not implemented'); } @@ -42,7 +32,9 @@ export class MockDebugService implements IDebugService { throw new Error('not implemented'); } - public onDidChangeState: Event = noopEvent; + public get onDidChangeState(): Event { + throw new Error('not implemented'); + } public getConfigurationManager(): IConfigurationManager { throw new Error('not implemented'); @@ -124,11 +116,11 @@ export class MockDebugService implements IDebugService { } public getModel(): IDebugModel { - return this._model; + throw new Error('not implemented'); } public getViewModel(): IViewModel { - return this._viewModel; + throw new Error('not implemented'); } public logToRepl(session: IDebugSession, value: string): void { } @@ -536,97 +528,3 @@ export class MockDebugAdapter extends AbstractDebugAdapter { } } } - -class MockDebugModel implements IDebugModel { - onDidChangeBreakpoints: Event = noopEvent; - - get onDidChangeCallStack(): Event { - throw new Error('not implemented'); - } - - get onDidChangeWatchExpressions(): Event { - throw new Error('not implemented'); - } - - getSession(sessionId: string | undefined, includeInactive?: boolean | undefined): IDebugSession | undefined { - throw new Error('not implemented.'); - } - - getSessions(includeInactive?: boolean | undefined): IDebugSession[] { - return []; - } - - getBreakpoints(filter?: { uri?: uri | undefined; lineNumber?: number | undefined; column?: number | undefined; enabledOnly?: boolean | undefined; } | undefined): readonly IBreakpoint[] { - return []; - } - - areBreakpointsActivated(): boolean { - throw new Error('not implemented.'); - } - - getFunctionBreakpoints(): readonly IFunctionBreakpoint[] { - throw new Error('not implemented.'); - } - - getDataBreakpoints(): readonly IDataBreakpoint[] { - throw new Error('not implemented.'); - } - - getExceptionBreakpoints(): readonly IExceptionBreakpoint[] { - throw new Error('not implemented.'); - } - - getWatchExpressions(): readonly (IExpression & IEvaluate)[] { - throw new Error('not implemented.'); - } - - getId(): string { - throw new Error('not implemented.'); - } -} - -class MockDebugViewModel implements IViewModel { - get focusedSession(): IDebugSession | undefined { - throw new Error('not implemented'); - } - - get focusedThread(): IThread | undefined { - throw new Error('not implemented'); - } - - focusedStackFrame: IStackFrame | undefined = undefined; - - get onDidFocusSession(): Event { - throw new Error('not implemented'); - } - - onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; }> = noopEvent; - - get onDidSelectExpression(): Event { - throw new Error('not implemented'); - } - - getSelectedExpression(): IExpression | undefined { - throw new Error('Method not implemented.'); - } - - getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined { - throw new Error('Method not implemented.'); - } - - setSelectedExpression(expression: IExpression | undefined): void { - throw new Error('Method not implemented.'); - } - - setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void { - throw new Error('Method not implemented.'); - } - - isMultiSessionView(): boolean { - throw new Error('Method not implemented.'); - } - - getId(): string { - throw new Error('Method not implemented.'); - } -} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css index 2c4b2623d40..a2e6e0d7721 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionEditor.css @@ -6,6 +6,8 @@ .extension-editor { height: 100%; overflow: hidden; + display: flex; + flex-direction: column; } .extension-editor .clickable { @@ -188,7 +190,7 @@ } .extension-editor > .body { - height: calc(100% - 168px); + flex: 1; overflow: hidden; } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index e82b2a505d4..bebcb9fcf99 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -21,17 +21,16 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { timeout } from 'vs/base/common/async'; +import { timeout, RunOnceWorker } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { private readonly activeOutOfWorkspaceWatchers = new ResourceMap(); - private closeOnFileDelete: boolean = false; - constructor( @IEditorService private readonly editorService: IEditorService, @ITextFileService private readonly textFileService: ITextFileService, @@ -43,6 +42,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IHostService private readonly hostService: IHostService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService ) { super(); @@ -59,9 +59,10 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Update editors from disk changes this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); - // Ensure dirty text file models are always opened as editors + // Ensure dirty text file and untitled models are always opened as editors this._register(this.textFileService.models.onModelsDirty(e => this.ensureDirtyTextFilesAreOpened(e))); this._register(this.textFileService.models.onModelsSaveError(e => this.ensureDirtyTextFilesAreOpened(e))); + this._register(this.untitledTextEditorService.onDidChangeDirty(e => this.ensureDirtyUntitledTextFilesAreOpenedWorker.work(e))); // Out of workspace file watchers this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); @@ -175,7 +176,17 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region File Changes: Close editors of deleted files + //#region File Changes: Close editors of deleted files unless configured otherwise + + private closeOnFileDelete: boolean = false; + + private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { + if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { + this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; + } else { + this.closeOnFileDelete = false; // default + } + } private onFileChanges(e: FileChangesEvent): void { if (e.gotDeleted()) { @@ -264,16 +275,33 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region Text File: Ensure every dirty text file is opened in an editor + //#region Text File: Ensure every dirty text and untitled file is opened in an editor + + private readonly ensureDirtyUntitledTextFilesAreOpenedWorker = this._register(new RunOnceWorker(units => this.ensureDirtyUntitledTextFilesAreOpened(units), 250)); private ensureDirtyTextFilesAreOpened(events: ReadonlyArray): void { - this.editorService.openEditors(distinct(events.filter(({ resource }) => { + this.doEnsureDirtyFilesAreOpened(distinct(events.filter(({ resource }) => { const model = this.textFileService.models.get(resource); return model?.hasState(ModelState.DIRTY) && // model must be dirty !model.hasState(ModelState.PENDING_SAVE) && // model should not be saving currently !this.editorService.isOpen({ resource }); // model is not currently opened as editor - }).map(event => event.resource), resource => resource.toString()).map(resource => ({ + }).map(event => event.resource), resource => resource.toString())); + } + + private ensureDirtyUntitledTextFilesAreOpened(resources: URI[]): void { + this.doEnsureDirtyFilesAreOpened(distinct(resources.filter(resource => { + return this.untitledTextEditorService.isDirty(resource) && // untitled must be dirty + !this.editorService.isOpen({ resource }); // untitled is not currently opened as editor + }), resource => resource.toString())); + } + + private doEnsureDirtyFilesAreOpened(resources: URI[]): void { + if (!resources.length) { + return; + } + + this.editorService.openEditors(resources.map(resource => ({ resource, options: { inactive: true, pinned: true, preserveFocus: true } }))); @@ -352,18 +380,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region Configuration Change - - private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void { - if (typeof configuration.workbench?.editor?.closeOnFileDelete === 'boolean') { - this.closeOnFileDelete = configuration.workbench.editor.closeOnFileDelete; - } else { - this.closeOnFileDelete = false; // default - } - } - - //#endregion - dispose(): void { super.dispose(); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 3cdf705bf44..87a82bf8b7c 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -1099,6 +1099,7 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => { if (pasteShouldMove) { // Cut is done. Make sure to clear cut state. explorerService.setToCopy([], false); + pasteShouldMove = false; } if (stats.length >= 1) { const stat = stats[0]; diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index e78e63d4c3e..39744d2ae4a 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -344,12 +344,6 @@ configurationRegistry.registerConfiguration({ 'default': false, 'description': nls.localize('formatOnSave', "Format a file on save. A formatter must be available, the file must not be saved after delay, and the editor must not be shutting down."), scope: ConfigurationScope.RESOURCE_LANGUAGE, - }, - 'editor.formatOnSaveTimeout': { - 'type': 'number', - 'default': 750, - 'description': nls.localize('formatOnSaveTimeout', "Timeout in milliseconds after which the formatting that is run on file save is cancelled."), - scope: ConfigurationScope.RESOURCE_LANGUAGE, } } }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 15d60eb041d..8c06d19a919 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -4,37 +4,62 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; class ServiceAccessor { constructor( @IEditorService public editorService: IEditorService, @IEditorGroupsService public editorGroupService: IEditorGroupsService, @ITextFileService public textFileService: TestTextFileService, - @IFileService public fileService: TestFileService + @IFileService public fileService: TestFileService, + @IUntitledTextEditorService public untitledTextEditorService: IUntitledTextEditorService ) { } } suite('Files - FileEditorTracker', () => { - let instantiationService: IInstantiationService; - let accessor: ServiceAccessor; + let disposables: IDisposable[] = []; setup(() => { - instantiationService = workbenchInstantiationService(); - accessor = instantiationService.createInstance(ServiceAccessor); + disposables.push(Registry.as(EditorExtensions.Editors).registerEditor( + EditorDescriptor.create( + TextFileEditor, + TextFileEditor.ID, + 'Text File Editor' + ), + [new SyncDescriptor(FileEditorInput)] + )); + }); + + teardown(() => { + dispose(disposables); + disposables = []; }); test('file change event updates model', async function () { + const instantiationService = workbenchInstantiationService(); + const accessor = instantiationService.createInstance(ServiceAccessor); + const tracker = instantiationService.createInstance(FileEditorTracker); const resource = toResource.call(this, '/path/index.txt'); @@ -54,5 +79,77 @@ suite('Files - FileEditorTracker', () => { assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); tracker.dispose(); + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); }); + + test('dirty text file model opens as editor', async function () { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(ServiceAccessor); + + const tracker = instantiationService.createInstance(FileEditorTracker); + + const resource = toResource.call(this, '/path/index.txt'); + + assert.ok(!editorService.isOpen({ resource })); + + const model = await accessor.textFileService.models.loadOrCreate(resource) as IResolvedTextFileEditorModel; + + model.textEditorModel.setValue('Super Good'); + + await awaitEditorOpening(editorService); + assert.ok(editorService.isOpen({ resource })); + + part.dispose(); + tracker.dispose(); + (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); + }); + + test('dirty untitled text file model opens as editor', async function () { + const instantiationService = workbenchInstantiationService(); + + const part = instantiationService.createInstance(EditorPart); + part.create(document.createElement('div')); + part.layout(400, 300); + + instantiationService.stub(IEditorGroupsService, part); + + const editorService: EditorService = instantiationService.createInstance(EditorService); + instantiationService.stub(IEditorService, editorService); + + const accessor = instantiationService.createInstance(ServiceAccessor); + + const tracker = instantiationService.createInstance(FileEditorTracker); + + const untitledEditor = accessor.untitledTextEditorService.createOrGet(); + const model = await untitledEditor.resolve(); + + assert.ok(!editorService.isOpen(untitledEditor)); + + model.textEditorModel.setValue('Super Good'); + + await awaitEditorOpening(editorService); + assert.ok(editorService.isOpen(untitledEditor)); + + part.dispose(); + tracker.dispose(); + model.dispose(); + }); + + function awaitEditorOpening(editorService: IEditorService): Promise { + return new Promise(c => { + Event.once(editorService.onDidActiveEditorChange)(c); + }); + } }); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index 1b046e78169..ec5fedf10f9 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -18,7 +18,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Event, Emitter } from 'vs/base/common/event'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Disposable, IDisposable, toDisposable, MutableDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar, ActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -56,7 +56,7 @@ export interface ITunnelViewModel { readonly forwarded: TunnelItem[]; readonly detected: TunnelItem[]; readonly candidates: Promise; - readonly input: ITunnelItem | ITunnelGroup | undefined; + readonly input: TunnelItem; groups(): Promise; } @@ -64,16 +64,23 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { private _onForwardedPortsChanged: Emitter = new Emitter(); public onForwardedPortsChanged: Event = this._onForwardedPortsChanged.event; private model: TunnelModel; - private _input: ITunnelItem | ITunnelGroup | undefined; + private _input: TunnelItem; constructor( - @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService) { + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService) { super(); this.model = remoteExplorerService.tunnelModel; this._register(this.model.onForwardPort(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onClosePort(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onPortName(() => this._onForwardedPortsChanged.fire())); this._register(this.model.onCandidatesChanged(() => this._onForwardedPortsChanged.fire())); + this._input = { + label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), + tunnelType: TunnelType.Add, + remoteHost: 'localhost', + remotePort: 0, + description: '' + }; } async groups(): Promise { @@ -100,20 +107,20 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { items: candidates }); } - if (!this._input) { - this._input = { - label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), - tunnelType: TunnelType.Add, - }; + if (groups.length === 0) { + groups.push(this._input); } - groups.push(this._input); return groups; } get forwarded(): TunnelItem[] { - return Array.from(this.model.forwarded.values()).map(tunnel => { + const forwarded = Array.from(this.model.forwarded.values()).map(tunnel => { return new TunnelItem(TunnelType.Forwarded, tunnel.remoteHost, tunnel.remotePort, tunnel.localAddress, tunnel.closeable, tunnel.name, tunnel.description); }); + if (this.remoteExplorerService.getEditableData(undefined)) { + forwarded.push(this._input); + } + return forwarded; } get detected(): TunnelItem[] { @@ -135,7 +142,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { }); } - get input(): ITunnelItem | ITunnelGroup | undefined { + get input(): TunnelItem { return this._input; } @@ -357,9 +364,9 @@ class TunnelItem implements ITunnelItem { if (this.name) { return nls.localize('remote.tunnelsView.forwardedPortLabel0', "{0}", this.name); } else if (this.localAddress && (this.remoteHost !== 'localhost')) { - return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0}:{1} to {2}", this.remoteHost, this.remotePort, this.localAddress); + return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0}:{1} \u2192 {2}", this.remoteHost, this.remotePort, this.localAddress); } else if (this.localAddress) { - return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} to {1}", this.remotePort, this.localAddress); + return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} \u2192 {1}", this.remotePort, this.localAddress); } else if (this.remoteHost !== 'localhost') { return nls.localize('remote.tunnelsView.forwardedPortLabel4', "{0}:{1} not forwarded", this.remoteHost, this.remotePort); } else { @@ -460,6 +467,7 @@ export class TunnelPanel extends ViewPane { renderer.actionRunner = actionRunner; this._register(this.tree.onContextMenu(e => this.onContextMenu(e, actionRunner))); + this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e))); this.tree.setInput(this.viewModel); this._register(this.viewModel.onForwardedPortsChanged(() => { @@ -507,7 +515,7 @@ export class TunnelPanel extends ViewPane { } private onContextMenu(treeEvent: ITreeContextMenuEvent, actionRunner: ActionRunner): void { - if (!(treeEvent.element instanceof TunnelItem)) { + if ((treeEvent.element !== null) && !(treeEvent.element instanceof TunnelItem)) { return; } const node: ITunnelItem | null = treeEvent.element; @@ -516,9 +524,14 @@ export class TunnelPanel extends ViewPane { event.preventDefault(); event.stopPropagation(); - this.tree!.setFocus([node]); - this.tunnelTypeContext.set(node.tunnelType); - this.tunnelCloseableContext.set(!!node.closeable); + if (node) { + this.tree!.setFocus([node]); + this.tunnelTypeContext.set(node.tunnelType); + this.tunnelCloseableContext.set(!!node.closeable); + } else { + this.tunnelTypeContext.set(TunnelType.Add); + this.tunnelCloseableContext.set(false); + } const actions: IAction[] = []; this._register(createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService)); @@ -543,6 +556,12 @@ export class TunnelPanel extends ViewPane { }); } + private onMouseDblClick(e: ITreeMouseEvent): void { + if (!e.element) { + this.commandService.executeCommand(ForwardPortAction.INLINE_ID); + } + } + protected layoutBody(height: number, width: number): void { this.tree.layout(height, width); } @@ -793,7 +812,7 @@ MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ id: ForwardPortAction.INLINE_ID, title: ForwardPortAction.LABEL, }, - when: TunnelTypeContextKey.isEqualTo(TunnelType.Candidate) + when: ContextKeyExpr.or(TunnelTypeContextKey.isEqualTo(TunnelType.Candidate), TunnelTypeContextKey.isEqualTo(TunnelType.Add)) })); MenuRegistry.appendMenuItem(MenuId.TunnelContext, ({ group: '0_manage', diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index bd643628670..e5bdd22c73b 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -16,6 +16,7 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/s import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; +import { TunnelFactoryContribution } from 'vs/workbench/contrib/remote/common/tunnelFactory'; export const VIEWLET_ID = 'workbench.view.remote'; @@ -83,3 +84,4 @@ const workbenchContributionsRegistry = Registry.as | undefined => { + const tunnelPromise = workbenchEnvironmentService.options!.tunnelFactory!(tunnelOptions); + if (!tunnelPromise) { + return undefined; + } + return new Promise(resolve => { + tunnelPromise.then(tunnel => { + const remoteTunnel: RemoteTunnel = { + tunnelRemotePort: tunnel.remoteAddress.port, + tunnelRemoteHost: tunnel.remoteAddress.host, + localAddress: tunnel.localAddress, + dispose: tunnel.dispose + }; + resolve(remoteTunnel); + }); + }); + } + })); + } + } +} diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 69eac6711ac..77789580a70 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -214,7 +214,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo label: localize('user keybindings', "User Keybindings") }, { id: 'sync.enableUIState', - label: localize('ui state', "UI State") + label: localize('ui state', "UI State"), + description: localize('ui state description', "Display Language (Only)") }, { id: 'sync.enableExtensions', label: localize('extensions', "Extensions") diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 929b367097b..00aa8576189 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FindInPageOptions, OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebContents, WebviewTag } from 'electron'; +import { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron'; import { addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; @@ -65,8 +65,8 @@ class WebviewTagHandle extends Disposable { } } -type OnBeforeRequestDelegate = (details: OnBeforeRequestDetails) => Promise; -type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean; } | undefined; +type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise; +type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined; class WebviewSession extends Disposable { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 18166cdef37..8b2f1b17d6b 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, CrashReporterStartOptions, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -537,13 +537,13 @@ export class ElectronWindow extends Disposable { } // base options with product info - const options = { + const options: CrashReporterStartOptions = { companyName, productName, submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, extra: { vscode_version: product.version, - vscode_commit: product.commit + vscode_commit: product.commit || '' } }; diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index bd9070dd0b5..5d715082fc6 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -1094,7 +1094,7 @@ suite('WorkspaceConfigurationService - Folder', () => { test('no change event when there are no global tasks', async () => { const target = sinon.spy(); testObject.onDidChangeConfiguration(target); - await timeout(500); + await timeout(5); assert.ok(target.notCalled); }); diff --git a/src/vs/workbench/services/progress/browser/progressService.ts b/src/vs/workbench/services/progress/browser/progressService.ts index 562773832a5..4a5831b5667 100644 --- a/src/vs/workbench/services/progress/browser/progressService.ts +++ b/src/vs/workbench/services/progress/browser/progressService.ts @@ -146,10 +146,7 @@ export class ProgressService extends Disposable implements IProgressService { private withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: (choice?: number) => void): P { const toDispose = new DisposableStore(); - const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { - if (!message) { - return undefined; // we need a message at least - } + const createNotification = (message: string, increment?: number): INotificationHandle => { const primaryActions = options.primaryActions ? Array.from(options.primaryActions) : []; const secondaryActions = options.secondaryActions ? Array.from(options.secondaryActions) : []; @@ -222,21 +219,34 @@ export class ProgressService extends Disposable implements IProgressService { }; let handle: INotificationHandle | undefined; + let handleSoon: any | undefined; + + let titleAndMessage: string | undefined; // hoisted to make sure a delayed notification shows the most recent message + const updateNotification = (message?: string, increment?: number): void => { - if (!handle) { - handle = createNotification(message, increment); + + // full message (inital or update) + if (message && options.title) { + titleAndMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) } else { - if (typeof message === 'string') { - let newMessage: string; - if (typeof options.title === 'string') { - newMessage = `${options.title}: ${message}`; // always prefix with overall title if we have it (https://github.com/Microsoft/vscode/issues/50932) - } else { - newMessage = message; + titleAndMessage = options.title || message; + } + + if (!handle && titleAndMessage) { + // create notification now or after a delay + if (typeof options.delay === 'number' && options.delay > 0) { + if (typeof handleSoon !== 'number') { + handleSoon = setTimeout(() => handle = createNotification(titleAndMessage!, increment), options.delay); } - - handle.updateMessage(newMessage); + } else { + handle = createNotification(titleAndMessage, increment); } + } + if (handle) { + if (titleAndMessage) { + handle.updateMessage(titleAndMessage); + } if (typeof increment === 'number') { updateProgress(handle, increment); } @@ -244,7 +254,7 @@ export class ProgressService extends Disposable implements IProgressService { }; // Show initially - updateNotification(options.title); + updateNotification(); // Update based on progress const promise = callback({ @@ -255,6 +265,7 @@ export class ProgressService extends Disposable implements IProgressService { // Show progress for at least 800ms and then hide once done or canceled Promise.all([timeout(800), promise]).finally(() => { + clearTimeout(handleSoon); if (handle) { handle.close(); } diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 8a94eeccd97..cfc38fbed3b 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -11,7 +11,7 @@ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditableData } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { TunnelInformation } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { TunnelInformation, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver'; export const IRemoteExplorerService = createDecorator('remoteExplorerService'); export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType'; @@ -46,7 +46,7 @@ export interface Tunnel { } export function MakeAddress(host: string, port: number): string { - if (host = '127.0.0.1') { + if (host === '127.0.0.1') { host = 'localhost'; } return host + ':' + port; @@ -172,7 +172,7 @@ export class TunnelModel extends Disposable { return (this.forwarded.get(key) || this.detected.get(key))?.localAddress; } - addEnvironmentTunnels(tunnels: { remoteAddress: { port: number, host: string }, localAddress: string }[]): void { + addEnvironmentTunnels(tunnels: TunnelDescription[]): void { tunnels.forEach(tunnel => { this.detected.set(MakeAddress(tunnel.remoteAddress.host, tunnel.remoteAddress.port), { remoteHost: tunnel.remoteAddress.host, diff --git a/src/vs/workbench/services/remote/common/tunnelService.ts b/src/vs/workbench/services/remote/common/tunnelService.ts new file mode 100644 index 00000000000..967299787d8 --- /dev/null +++ b/src/vs/workbench/services/remote/common/tunnelService.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { ILogService } from 'vs/platform/log/common/log'; + +export abstract class AbstractTunnelService implements ITunnelService { + _serviceBrand: undefined; + + private _onTunnelOpened: Emitter = new Emitter(); + public onTunnelOpened: Event = this._onTunnelOpened.event; + private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); + public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; + protected readonly _tunnels = new Map }>>(); + protected _tunnelProvider: ITunnelProvider | undefined; + + public constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ILogService protected readonly logService: ILogService + ) { } + + setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { + if (!provider) { + return { + dispose: () => { } + }; + } + this._tunnelProvider = provider; + return { + dispose: () => { + this._tunnelProvider = undefined; + } + }; + } + + public get tunnels(): Promise { + const promises: Promise[] = []; + Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); + return Promise.all(promises); + } + + dispose(): void { + for (const portMap of this._tunnels.values()) { + for (const { value } of portMap.values()) { + value.then(tunnel => tunnel.dispose()); + } + portMap.clear(); + } + this._tunnels.clear(); + } + + openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (!remoteAuthority) { + return undefined; + } + + if (!remoteHost || (remoteHost === '127.0.0.1')) { + remoteHost = 'localhost'; + } + + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort); + if (!resolvedTunnel) { + return resolvedTunnel; + } + + return resolvedTunnel.then(tunnel => { + const newTunnel = this.makeTunnel(tunnel); + if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) { + this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.'); + } + this._onTunnelOpened.fire(newTunnel); + return newTunnel; + }); + } + + private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { + return { + tunnelRemotePort: tunnel.tunnelRemotePort, + tunnelRemoteHost: tunnel.tunnelRemoteHost, + tunnelLocalPort: tunnel.tunnelLocalPort, + localAddress: tunnel.localAddress, + dispose: () => { + const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); + if (existingHost) { + const existing = existingHost.get(tunnel.tunnelRemotePort); + if (existing) { + existing.refcount--; + this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); + } + } + } + }; + } + + private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { + if (tunnel.refcount <= 0) { + const disposePromise: Promise = tunnel.value.then(tunnel => { + tunnel.dispose(); + this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); + }); + if (this._tunnels.has(remoteHost)) { + this._tunnels.get(remoteHost)!.delete(remotePort); + } + return disposePromise; + } + } + + async closeTunnel(remoteHost: string, remotePort: number): Promise { + const portMap = this._tunnels.get(remoteHost); + if (portMap && portMap.has(remotePort)) { + const value = portMap.get(remotePort)!; + value.refcount = 0; + await this.tryDisposeTunnel(remoteHost, remotePort, value); + } + } + + protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { + if (!this._tunnels.has(remoteHost)) { + this._tunnels.set(remoteHost, new Map()); + } + this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); + } + + protected abstract retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined; +} + +export class TunnelService extends AbstractTunnelService { + protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise | undefined { + const portMap = this._tunnels.get(remoteHost); + const existing = portMap ? portMap.get(remotePort) : undefined; + if (existing) { + ++existing.refcount; + return existing.value; + } + + if (this._tunnelProvider) { + const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } }); + if (tunnel) { + this.addTunnelToMap(remoteHost, remotePort, tunnel); + } + return tunnel; + } + return undefined; + } +} diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 66e50c7e997..194f4edfe58 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -5,22 +5,22 @@ import * as net from 'net'; import { Barrier } from 'vs/base/common/async'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel'; +import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { findFreePort } from 'vs/base/node/ports'; -import { Event, Emitter } from 'vs/base/common/event'; +import { AbstractTunnelService } from 'vs/workbench/services/remote/common/tunnelService'; -export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { - const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort, tunnelLocalPort); +export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise { + const tunnel = new NodeRemoteTunnel(options, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort); return tunnel.waitForReady(); } @@ -28,7 +28,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { public readonly tunnelRemotePort: number; public tunnelLocalPort!: number; - public tunnelRemoteHost: string = 'localhost'; + public tunnelRemoteHost: string; public localAddress!: string; private readonly _options: IConnectionOptions; @@ -38,7 +38,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { private readonly _listeningListener: () => void; private readonly _connectionListener: (socket: net.Socket) => void; - constructor(options: IConnectionOptions, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) { + constructor(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) { super(); this._options = options; this._server = net.createServer(); @@ -51,7 +51,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { this._server.on('connection', this._connectionListener); this.tunnelRemotePort = tunnelRemotePort; - + this.tunnelRemoteHost = tunnelRemoteHost; } public dispose(): void { @@ -98,124 +98,17 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { } } -export class TunnelService implements ITunnelService { - _serviceBrand: undefined; - - private _onTunnelOpened: Emitter = new Emitter(); - public onTunnelOpened: Event = this._onTunnelOpened.event; - private _onTunnelClosed: Emitter<{ host: string, port: number }> = new Emitter(); - public onTunnelClosed: Event<{ host: string, port: number }> = this._onTunnelClosed.event; - private readonly _tunnels = new Map }>>(); - private _tunnelProvider: ITunnelProvider | undefined; - +export class TunnelService extends AbstractTunnelService { public constructor( - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILogService logService: ILogService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService private readonly signService: ISignService, - @ILogService private readonly logService: ILogService, - ) { } - - setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable { - if (!provider) { - return { - dispose: () => { } - }; - } - this._tunnelProvider = provider; - return { - dispose: () => { - this._tunnelProvider = undefined; - } - }; + ) { + super(environmentService, logService); } - public get tunnels(): Promise { - const promises: Promise[] = []; - Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value))); - return Promise.all(promises); - } - - dispose(): void { - for (const portMap of this._tunnels.values()) { - for (const { value } of portMap.values()) { - value.then(tunnel => tunnel.dispose()); - } - portMap.clear(); - } - this._tunnels.clear(); - } - - openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise | undefined { - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (!remoteAuthority) { - return undefined; - } - - if (!remoteHost || (remoteHost === '127.0.0.1')) { - remoteHost = 'localhost'; - } - - const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort); - if (!resolvedTunnel) { - return resolvedTunnel; - } - - return resolvedTunnel.then(tunnel => { - const newTunnel = this.makeTunnel(tunnel); - this._onTunnelOpened.fire(newTunnel); - return newTunnel; - }); - } - - private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel { - return { - tunnelRemotePort: tunnel.tunnelRemotePort, - tunnelRemoteHost: tunnel.tunnelRemoteHost, - tunnelLocalPort: tunnel.tunnelLocalPort, - localAddress: tunnel.localAddress, - dispose: () => { - const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost); - if (existingHost) { - const existing = existingHost.get(tunnel.tunnelRemotePort); - if (existing) { - existing.refcount--; - this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing); - } - } - } - }; - } - - private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise }): Promise { - if (tunnel.refcount <= 0) { - const disposePromise: Promise = tunnel.value.then(tunnel => { - tunnel.dispose(); - this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }); - }); - if (this._tunnels.has(remoteHost)) { - this._tunnels.get(remoteHost)!.delete(remotePort); - } - return disposePromise; - } - } - - async closeTunnel(remoteHost: string, remotePort: number): Promise { - const portMap = this._tunnels.get(remoteHost); - if (portMap && portMap.has(remotePort)) { - const value = portMap.get(remotePort)!; - value.refcount = 0; - await this.tryDisposeTunnel(remoteHost, remotePort, value); - } - } - - private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise) { - if (!this._tunnels.has(remoteHost)) { - this._tunnels.set(remoteHost, new Map()); - } - this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel }); - } - - private retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { + protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise | undefined { const portMap = this._tunnels.get(remoteHost); const existing = portMap ? portMap.get(remotePort) : undefined; if (existing) { @@ -229,7 +122,7 @@ export class TunnelService implements ITunnelService { this.addTunnelToMap(remoteHost, remotePort, tunnel); } return tunnel; - } else if (remoteHost === 'localhost') { + } else { const options: IConnectionOptions = { commit: product.commit, socketFactory: nodeSocketFactory, @@ -243,11 +136,10 @@ export class TunnelService implements ITunnelService { logService: this.logService }; - const tunnel = createRemoteTunnel(options, remotePort, localPort); + const tunnel = createRemoteTunnel(options, remoteHost, remotePort, localPort); this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; } - return undefined; } } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 983cf4c991c..93658cabb39 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -218,7 +218,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Handle untitled resources await Promise.all(untitledResources .filter(untitled => this.untitledTextEditorService.exists(untitled)) - .map(async untitled => (await this.untitledTextEditorService.loadOrCreate({ resource: untitled })).backup())); + .map(async untitled => (await this.untitledTextEditorService.createOrGet({ resource: untitled }).resolve()).backup())); } private async confirmBeforeShutdown(): Promise { @@ -710,7 +710,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (this.fileService.canHandleResource(resource)) { model = this._models.get(resource); } else if (resource.scheme === Schemas.untitled && this.untitledTextEditorService.exists(resource)) { - model = await this.untitledTextEditorService.loadOrCreate({ resource }); + model = await this.untitledTextEditorService.createOrGet({ resource }).resolve(); } // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 154daabf6bb..2b630820d7f 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -141,7 +141,7 @@ export class TextModelResolverService implements ITextModelService { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - const model = await this.untitledTextEditorService.loadOrCreate({ resource }); + const model = await this.untitledTextEditorService.createOrGet({ resource }).resolve(); return new ImmortalReference(model as IResolvedTextEditorModel); } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 7d8d31497ae..9131aa8def5 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -11,7 +11,6 @@ import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/file import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; -import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -19,7 +18,7 @@ import { basename } from 'vs/base/common/resources'; export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); -export interface IModelLoadOrCreateOptions { +export interface IUntitledCreationOptions { resource?: URI; mode?: string; initialValue?: string; @@ -88,16 +87,8 @@ export interface IUntitledTextEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput; - - /** - * Creates a new untitled model with the optional resource URI or returns an existing one - * if the provided resource exists already as untitled model. - * - * It is valid to pass in a file resource. In that case the path will be used as identifier. - * The use case is to be able to create a new file with a specific path with VSCode. - */ - loadOrCreate(options: IModelLoadOrCreateOptions): Promise; + createOrGet(options?: IUntitledCreationOptions): UntitledTextEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath?: boolean): UntitledTextEditorInput; /** * A check to find out if a untitled resource has a file path associated or not. @@ -201,11 +192,14 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe .map(i => i.getResource()); } - loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { - return this.createOrGet(options.resource, options.mode, options.initialValue, options.encoding, options.useResourcePath).resolve(); - } + createOrGet(options?: IUntitledCreationOptions): UntitledTextEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath?: boolean): UntitledTextEditorInput; + createOrGet(resourceOrOptions?: URI | IUntitledCreationOptions, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { + if (resourceOrOptions && !URI.isUri(resourceOrOptions)) { + return this.createOrGet(resourceOrOptions.resource, resourceOrOptions.mode, resourceOrOptions.initialValue, resourceOrOptions.encoding, resourceOrOptions.useResourcePath); + } - createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput { + let resource = resourceOrOptions; if (resource) { // Massage resource if it comes with known file based resource diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts new file mode 100644 index 00000000000..0a2adc53f3a --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SyncStatus, ISettingsSyncService, IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class SettingsSyncService extends Disposable implements ISettingsSyncService { + + _serviceBrand: undefined; + + private readonly channel: IChannel; + + private _status: SyncStatus = SyncStatus.Uninitialized; + get status(): SyncStatus { return this._status; } + private _onDidChangeStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + + private _conflicts: IConflictSetting[] = []; + get conflicts(): IConflictSetting[] { return this._conflicts; } + private _onDidChangeConflicts: Emitter = this._register(new Emitter()); + readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; + + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService + ) { + super(); + this.channel = sharedProcessService.getChannel('settingsSync'); + this.channel.call('_getInitialStatus').then(status => { + this.updateStatus(status); + this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + }); + this.channel.call('_getInitialConflicts').then(conflicts => { + if (conflicts.length) { + this.updateConflicts(conflicts); + } + this._register(this.channel.listen('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts))); + }); + } + + sync(_continue?: boolean): Promise { + return this.channel.call('sync', [_continue]); + } + + stop(): void { + this.channel.call('stop'); + } + + resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { + return this.channel.call('resolveConflicts', [conflicts]); + } + + private async updateStatus(status: SyncStatus): Promise { + this._status = status; + this._onDidChangeStatus.fire(status); + } + + private async updateConflicts(conflicts: IConflictSetting[]): Promise { + this._conflicts = conflicts; + this._onDidChangeConflicts.fire(conflicts); + } + +} + +registerSingleton(ISettingsSyncService, SettingsSyncService); diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 0303b39b6da..c9ca35dc144 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -148,10 +148,10 @@ suite('Workbench untitled text editors', () => { input.dispose(); }); - test('Untitled via loadOrCreate', async () => { + test('Untitled via createOrGet options', async () => { const service = accessor.untitledTextEditorService; - const model1 = await service.loadOrCreate(); + const model1 = await service.createOrGet().resolve(); model1.textEditorModel!.setValue('foo bar'); assert.ok(model1.isDirty()); @@ -159,17 +159,17 @@ suite('Workbench untitled text editors', () => { model1.textEditorModel!.setValue(''); assert.ok(!model1.isDirty()); - const model2 = await service.loadOrCreate({ initialValue: 'Hello World' }); + const model2 = await service.createOrGet({ initialValue: 'Hello World' }).resolve(); assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World'); const input = service.createOrGet(); - const model3 = await service.loadOrCreate({ resource: input.getResource() }); + const model3 = await service.createOrGet({ resource: input.getResource() }).resolve(); assert.equal(model3.resource.toString(), input.getResource().toString()); const file = URI.file(join('C:\\', '/foo/file44.txt')); - const model4 = await service.loadOrCreate({ resource: file }); + const model4 = await service.createOrGet({ resource: file }).resolve(); assert.ok(service.hasAssociatedFilePath(model4.resource)); assert.ok(model4.isDirty()); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index f611fe3763d..868e183d45c 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -95,8 +95,6 @@ import { find } from 'vs/base/common/arrays'; import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; -import { MockDebugService } from 'vs/workbench/contrib/debug/test/common/mockDebug'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -326,7 +324,6 @@ export function workbenchInstantiationService(): ITestInstantiationService { instantiationService.stub(ICodeEditorService, new TestCodeEditorService()); instantiationService.stub(IViewletService, new TestViewletService()); instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService()); - instantiationService.stub(IDebugService, new MockDebugService()); return instantiationService; } diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 7969bc16681..f303692242f 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -50,6 +50,7 @@ import 'vs/workbench/services/url/electron-browser/urlService'; import 'vs/workbench/services/workspaces/electron-browser/workspacesService'; import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; +import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService'; import 'vs/workbench/services/authToken/electron-browser/authTokenService'; import 'vs/workbench/services/host/electron-browser/desktopHostService'; import 'vs/workbench/services/request/electron-browser/requestService'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 5e2ea4fd604..d33ec02d21b 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -34,6 +34,26 @@ interface IExternalUriResolver { (uri: URI): Promise; } +interface TunnelOptions { + remoteAddress: { port: number, host: string }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; +} + +interface Tunnel { + remoteAddress: { port: number, host: string }; + //The complete local address(ex. localhost:1234) + localAddress: string; + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; + dispose(): void; +} + +interface ITunnelFactory { + (tunnelOptions: TunnelOptions): Thenable | undefined; +} + interface IWorkbenchConstructionOptions { /** @@ -104,6 +124,11 @@ interface IWorkbenchConstructionOptions { */ readonly resolveExternalUri?: IExternalUriResolver; + /** + * Support for creating tunnels. + */ + readonly tunnelFactory?: ITunnelFactory; + /** * Current logging level. Default is `LogLevel.Info`. */ diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index f424c87d927..ecd9ff04e17 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -60,25 +60,27 @@ import { BackupFileService } from 'vs/workbench/services/backup/common/backupFil import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; -import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; +import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(IContextMenuService, ContextMenuService); -registerSingleton(ITunnelService, NoOpTunnelService, true); +registerSingleton(ITunnelService, TunnelService, true); registerSingleton(ILoggerService, FileLoggerService); registerSingleton(IAuthTokenService, AuthTokenService); registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); +registerSingleton(ISettingsSyncService, SettingsSynchroniser); registerSingleton(IUserDataSyncService, UserDataSyncService); //#endregion diff --git a/yarn.lock b/yarn.lock index 6ef2a9f7c77..36fee127c84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -95,6 +95,33 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@electron/get@^1.0.1": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd" + integrity sha512-LSE4LZGMjGS9TloDx0yO44D2UTbaeKRk+QjlhWLiQlikV6J4spgDCjb6z4YIcqmPAwNzlNCnWF4dubytwI+ATA== + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^9.6.0" + sanitize-filename "^1.6.2" + sumchecker "^3.0.1" + optionalDependencies: + global-agent "^2.0.2" + global-tunnel-ng "^2.7.1" + +"@sindresorhus/is@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" + integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== + +"@szmarczak/http-timer@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" + integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== + dependencies: + defer-to-connect "^1.0.1" + "@types/applicationinsights@0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" @@ -172,10 +199,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^10.12.18": - version "10.17.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.9.tgz#4f251a1ed77ac7ef09d456247d67fc8173f6b9da" - integrity sha512-+6VygF9LbG7Gaqeog2G7u1+RUcmo0q1rI+2ZxdIg2fAUngk5Vz9fOCHXdloNUOHEPd1EuuOpL5O0CdgN9Fx5UQ== +"@types/node@^12.0.12": + version "12.12.24" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f" + integrity sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug== "@types/node@^12.11.7": version "12.12.14" @@ -825,11 +852,6 @@ array-each@^1.0.0, array-each@^1.0.1: resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -1200,6 +1222,11 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boolean@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.0.tgz#fab78d5907dbae6216ab46d32733bb7b76b99e76" + integrity sha512-OElxJ1lUSinuoUnkpOgLmxp0DC4ytEhODEL6QJU0NpxE/mI4rUSh8h1P1Wkvfi3xQEBcxXR2gBIPNYNuaFcAbQ== + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1444,24 +1471,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" + integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^3.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^1.0.2" + callsites@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -1720,6 +1747,13 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= +clone-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= + dependencies: + mimic-response "^1.0.0" + clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" @@ -1947,7 +1981,7 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.5.1" -config-chain@^1.1.12: +config-chain@^1.1.11, config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2051,6 +2085,11 @@ copy-webpack-plugin@^4.5.2: p-limit "^1.0.0" serialize-javascript "^1.4.0" +core-js@^3.4.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.1.tgz#39d5e2e346258cc01eb7d44345b1c3c014ca3f05" + integrity sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2235,13 +2274,6 @@ cuint@^0.2.1: resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -2278,7 +2310,7 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2292,7 +2324,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^3.0.0, debug@^3.1.0: +debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2363,7 +2395,12 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -define-properties@^1.1.2: +defer-to-connect@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f" + integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ== + +define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2463,6 +2500,11 @@ detect-libc@^1.0.2, detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +detect-node@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -2562,6 +2604,11 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2631,33 +2678,18 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -electron-download@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" - integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== - dependencies: - debug "^3.0.0" - env-paths "^1.0.0" - fs-extra "^4.0.1" - minimist "^1.2.0" - nugget "^2.0.1" - path-exists "^3.0.0" - rc "^1.2.1" - semver "^5.4.1" - sumchecker "^2.0.2" - electron-to-chromium@^1.2.7: version "1.3.27" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@6.1.6: - version "6.1.6" - resolved "https://registry.yarnpkg.com/electron/-/electron-6.1.6.tgz#d63ea9c89b85b981a29eb3088bf391bf52bd8d73" - integrity sha512-4c4GiFTbWY2Mgv20HB4Bfhf1hDKb0MWgC35wkwNepNom1ioWfumocPHZrSs1xNAEe+tOmezY6lq74n3LbwTnVQ== +electron@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/electron/-/electron-7.1.7.tgz#520e2bc422e3dfd4bae166dd3be62101f2cbdc52" + integrity sha512-aCLJ4BJwnvOckJgovNul22AYlMFDzm4S4KqKCG2iBlFJyMHBxXAKFKMsgYd40LBZWS3hcY6RHpaYjHSAPLS1pw== dependencies: - "@types/node" "^10.12.18" - electron-download "^4.1.0" + "@electron/get" "^1.0.1" + "@types/node" "^12.0.12" extract-zip "^1.0.3" elliptic@^6.0.0: @@ -2695,6 +2727,11 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +encodeurl@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" @@ -2721,10 +2758,10 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -env-paths@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" - integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= +env-paths@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" + integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -2748,6 +2785,11 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-iterator "~2.0.1" es6-symbol "~3.1.1" +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-iterator@^2.0.1, es6-iterator@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -2802,6 +2844,11 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + eslint-plugin-jsdoc@^19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014" @@ -3562,15 +3609,6 @@ fs-extra@0.26.7: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -3580,6 +3618,15 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -3687,18 +3734,20 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - -get-stream@^4.0.0: +get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" + integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3864,6 +3913,19 @@ glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +global-agent@^2.0.2: + version "2.1.7" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.7.tgz#12d7bc2b07cd862d0fa76b0f1b2c48cd5ffcf150" + integrity sha512-ooK7eqGYZku+LgnbfH/Iv0RJ74XfhrBZDlke1QSzcBt0bw1PmJcnRADPAQuFE+R45pKKDTynAr25SBasY2kvow== + dependencies: + boolean "^3.0.0" + core-js "^3.4.1" + es6-error "^4.1.1" + matcher "^2.0.0" + roarr "^2.14.5" + semver "^6.3.0" + serialize-error "^5.0.0" + global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -3900,6 +3962,16 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" +global-tunnel-ng@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" + integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== + dependencies: + encodeurl "^1.0.2" + lodash "^4.17.10" + npm-conf "^1.1.3" + tunnel "^0.0.6" + globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -3917,6 +3989,13 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" +globalthis@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" + integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== + dependencies: + define-properties "^1.1.3" + globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -3948,11 +4027,33 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" +got@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" + integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== + dependencies: + "@sindresorhus/is" "^0.14.0" + "@szmarczak/http-timer" "^1.1.2" + cacheable-request "^6.0.0" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^4.1.0" + lowercase-keys "^1.0.1" + mimic-response "^1.0.1" + p-cancelable "^1.0.0" + to-readable-stream "^1.0.0" + url-parse-lax "^3.0.0" + graceful-fs@4.1.11, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= +graceful-fs@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -4416,6 +4517,11 @@ html-comment-regex@^1.1.0: inherits "^2.0.1" readable-stream "^2.0.2" +http-cache-semantics@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" + integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== + http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -4549,13 +4655,6 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -4811,13 +4910,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -5214,6 +5306,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= + json-edm-parser@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" @@ -5253,7 +5350,7 @@ json-stable-stringify@^1.0.0: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -5322,6 +5419,13 @@ keytar@^4.11.0: nan "2.14.0" prebuild-install "5.3.0" +keyv@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" + integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== + dependencies: + json-buffer "3.0.0" + kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -5595,13 +5699,15 @@ long@^3.2.0: resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" +lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== lru-cache@2: version "2.7.3" @@ -5668,11 +5774,6 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - map-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" @@ -5711,6 +5812,13 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" +matcher@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.1.0.tgz#64e1041c15b993e23b786f93320a7474bf833c28" + integrity sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ== + dependencies: + escape-string-regexp "^2.0.0" + math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" @@ -5760,22 +5868,6 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -5895,6 +5987,11 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4= +mimic-response@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5925,7 +6022,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -6269,16 +6366,6 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-package-data@^2.3.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - normalize-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" @@ -6316,6 +6403,11 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +normalize-url@^4.1.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" @@ -6328,6 +6420,14 @@ npm-bundled@^1.0.1: resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" integrity sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow== +npm-conf@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + npm-packlist@^1.1.6: version "1.1.11" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" @@ -6360,19 +6460,6 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -nugget@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" - integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= - dependencies: - debug "^2.1.3" - minimist "^1.1.0" - pretty-bytes "^1.0.2" - progress-stream "^1.1.0" - request "^2.45.0" - single-line-log "^1.1.2" - throttleit "0.0.2" - num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -6640,6 +6727,11 @@ p-all@^1.0.0: dependencies: p-map "^1.0.0" +p-cancelable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" + integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -7263,19 +7355,16 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -pretty-bytes@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" - integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -7304,14 +7393,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress-stream@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" - integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= - dependencies: - speedometer "~0.1.2" - through2 "~0.2.3" - progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -7519,7 +7600,7 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" -rc@^1.2.1, rc@^1.2.7: +rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -7595,7 +7676,7 @@ read@^1.0.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^1.1.8, readable-stream@~1.1.9: +readable-stream@^1.1.8: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= @@ -7663,14 +7744,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" @@ -7750,13 +7823,6 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - replace-ext@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" @@ -7839,7 +7905,7 @@ request@2.79.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.45.0, request@^2.86.0, request@^2.88.0: +request@^2.86.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -7933,13 +7999,6 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" -resolve@^1.10.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16" - integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w== - dependencies: - path-parse "^1.0.6" - resolve@^1.4.0: version "1.10.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" @@ -7947,6 +8006,13 @@ resolve@^1.4.0: dependencies: path-parse "^1.0.6" +responselike@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= + dependencies: + lowercase-keys "^1.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8002,6 +8068,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +roarr@^2.14.5: + version "2.14.6" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.14.6.tgz#cebe8ad7ecbfd15bfa37b02dacf00809dd633912" + integrity sha512-qjbw0BEesKA+3XFBPt+KVe1PC/Z6ShfJ4wPlx2XifqH5h2Lj8/KQT5XJTsy3n1Es5kai+BwKALaECW3F70B1cg== + dependencies: + boolean "^3.0.0" + detect-node "^2.0.4" + globalthis "^1.0.0" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -8062,6 +8140,13 @@ samsam@~1.1: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= +sanitize-filename@^1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" + integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== + dependencies: + truncate-utf8-bytes "^1.0.0" + sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" @@ -8080,6 +8165,11 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= + semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -8141,6 +8231,13 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" +serialize-error@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" + integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== + dependencies: + type-fest "^0.8.0" + serialize-javascript@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" @@ -8250,13 +8347,6 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -single-line-log@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" - integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= - dependencies: - string-width "^1.0.1" - sinon@^1.17.2: version "1.17.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" @@ -8440,11 +8530,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== -speedometer@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" - integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= - split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -8466,6 +8551,11 @@ split@^1.0.1: dependencies: through "2" +sprintf-js@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" + integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -8711,13 +8801,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -8733,12 +8816,12 @@ sudo-prompt@9.1.1: resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== -sumchecker@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" - integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" + integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== dependencies: - debug "^2.2.0" + debug "^4.1.0" supports-color@1.2.0: version "1.2.0" @@ -8887,11 +8970,6 @@ textextensions@~1.0.0: resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-1.0.2.tgz#65486393ee1f2bb039a60cbba05b0b68bd9501d2" integrity sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI= -throttleit@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" - integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= - through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -8924,14 +9002,6 @@ through2@^3.0.0: readable-stream "2 || 3" xtend "~4.0.1" -through2@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" - integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= - dependencies: - readable-stream "~1.1.9" - xtend "~2.1.1" - through2@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" @@ -9020,6 +9090,11 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" +to-readable-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" + integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== + to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -9079,16 +9154,18 @@ tough-cookie@~2.4.3: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= - trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= + dependencies: + utf8-byte-length "^1.0.1" + ts-loader@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.4.2.tgz#778d4464b24436873c78f7f9e914d88194c2a248" @@ -9134,6 +9211,11 @@ tunnel@0.0.4: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9146,7 +9228,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-fest@^0.8.1: +type-fest@^0.8.0, type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -9185,10 +9267,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^3.8.0-dev.20200104: - version "3.8.0-dev.20200104" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649" - integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA== +typescript@^3.8.0-dev.20200108: + version "3.8.0-dev.20200108" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc" + integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w== uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" @@ -9349,6 +9431,13 @@ url-join@^1.1.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= + dependencies: + prepend-http "^2.0.0" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9362,6 +9451,11 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf8-byte-length@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" + integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= + util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"