Merge branch 'master' into aeschli/ts-sem

This commit is contained in:
Martin Aeschlimann 2020-01-09 15:41:56 +01:00
commit 741a8b2b2f
112 changed files with 1720 additions and 1020 deletions

2
.github/copycat.yml vendored
View file

@ -1,5 +1,5 @@
{
perform: false,
perform: true,
target_owner: 'chrmarti',
target_repo: 'testissues'
}

View file

@ -1,3 +1,3 @@
disturl "https://atom.io/download/electron"
target "6.1.6"
target "7.1.7"
runtime "electron"

View file

@ -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"

View file

@ -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
OpenWithCodeContextMenu=Open w&ith %1

View file

@ -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"

View file

@ -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": {

View file

@ -11,7 +11,7 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],

View file

@ -11,7 +11,7 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -22,4 +22,4 @@
"folding": {
"offSide": true
}
}
}

View file

@ -12,8 +12,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],

View file

@ -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"
}
}

View file

@ -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"

View file

@ -12,8 +12,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -22,4 +22,4 @@
["\"", "\""],
["'", "'"]
]
}
}

View file

@ -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;

View file

@ -12,8 +12,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -22,4 +22,4 @@
["\"", "\""],
["'", "'"]
]
}
}

View file

@ -12,7 +12,7 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -21,4 +21,4 @@
["\"", "\""],
["'", "'"]
]
}
}

View file

@ -12,8 +12,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "/**", "close": " */", "notIn": ["string"] }
],
"surroundingPairs": [

View file

@ -12,8 +12,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],

View file

@ -144,7 +144,7 @@ async function detectNpmScripts(): Promise<Task[]> {
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);

View file

@ -12,8 +12,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -22,4 +22,4 @@
["\"", "\""],
["'", "'"]
]
}
}

View file

@ -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))"
}
}
}
}

View file

@ -11,9 +11,9 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
["`", "`"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "`", "close": "`", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -23,4 +23,4 @@
["'", "'"],
["`", "`"]
]
}
}

View file

@ -11,8 +11,8 @@
["{", "}"],
["[", "]"],
["(", ")"],
["'", "'"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -24,4 +24,4 @@
"folding": {
"offSide": true
}
}
}

View file

@ -11,12 +11,14 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
["\"", "\""],
["'", "'"]
]
}
}

View file

@ -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)*(#.*)?$",

View file

@ -12,14 +12,13 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
["\"", "\""]
],
"indentationRules": {
"increaseIndentPattern": "^.*\\{[^}\"']*$|^.*\\([^\\)\"']*$",

View file

@ -12,7 +12,7 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -20,4 +20,4 @@
["(", ")"],
["\"", "\""]
]
}
}

View file

@ -11,9 +11,9 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
["`", "`"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "`", "close": "`", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],

View file

@ -12,9 +12,9 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"],
["`", "`"]
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "`", "close": "`", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -24,4 +24,4 @@
["'", "'"],
["`", "`"]
]
}
}

View file

@ -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<vscode.CallHierarchyItem | vscode.CallHierarchyItem[] | undefined> {
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<vscode.CallHierarchyIncomingCall[] | undefined> {
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<vscode.CallHierarchyOutgoingCall[] | undefined> {
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)));
}

View file

@ -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,
});
}

View file

@ -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();

View file

@ -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';

View file

@ -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';

View file

@ -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))),
]);
}

View file

@ -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';
}

View file

@ -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[];
}
}

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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 {

View file

@ -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 : {}) });

View file

@ -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);

View file

@ -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();

View file

@ -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;
}

View file

@ -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;
}
}
}

View file

@ -12,7 +12,7 @@
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""]
{ "open": "\"", "close": "\"", "notIn": ["string"] }
],
"surroundingPairs": [
["{", "}"],
@ -27,4 +27,4 @@
"end": "^\\s*#End Region\\b"
}
}
}
}

View file

@ -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<T extends vscode.Disposable>(disposable: T) {

View file

@ -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);
}
});

View file

@ -12,8 +12,8 @@
{ "open": "{", "close": "}"},
{ "open": "[", "close": "]"},
{ "open": "(", "close": ")" },
{ "open": "'", "close": "'" },
{ "open": "\"", "close": "\"" },
{ "open": "\"", "close": "\"", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string"] },
{ "open": "<!--", "close": "-->", "notIn": [ "comment", "string" ]},
{ "open": "<![CDATA[", "close": "]]>", "notIn": [ "comment", "string" ]}
],

View file

@ -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",

View file

@ -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);

View file

@ -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

View file

@ -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<string, (string) | (string[])>;
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<string, string> })));
}
private onWindowError(error: WindowError): void {

View file

@ -83,6 +83,8 @@ export interface IConfigurationValue<T> {
readonly workspace?: { value?: T, override?: T };
readonly workspaceFolder?: { value?: T, override?: T };
readonly memory?: { value?: T, override?: T };
readonly overrideIdentifiers?: string[];
}
export interface IConfigurationService {

View file

@ -391,6 +391,7 @@ export class Configuration {
const workspaceFolderValue = folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue<C>(key) : folderConfigurationModel.freeze().getValue<C>(key) : undefined;
const memoryValue = overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue<C>(key) : memoryConfigurationModel.getValue<C>(key);
const value = consolidateConfigurationModel.getValue<C>(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
};
}

View file

@ -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', () => {

View file

@ -173,7 +173,7 @@ export class DialogMainService implements IDialogMainService {
showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise<OpenDialogReturnValue> {
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
}

View file

@ -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<NativeImage>(c => webContents.capturePage(c));
const image = await webContents.capturePage();
return image.toPNG().toString('base64');
}

View file

@ -367,15 +367,13 @@ export class ElectronMainService implements IElectronMainService {
//#region Connectivity
async resolveProxy(windowId: number | undefined, url: string): Promise<string | undefined> {
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

View file

@ -57,6 +57,7 @@ export interface IProgressNotificationOptions extends IProgressOptions {
readonly location: ProgressLocation.Notification;
readonly primaryActions?: ReadonlyArray<IAction>;
readonly secondaryActions?: ReadonlyArray<IAction>;
delay?: number;
}
export interface IProgressWindowOptions extends IProgressOptions {

View file

@ -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;
}

View file

@ -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<readonly RemoteTunnel[]> = Promise.resolve([]);
private _onTunnelOpened: Emitter<RemoteTunnel> = new Emitter();
public onTunnelOpened: Event<RemoteTunnel> = 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<RemoteTunnel> | undefined {
return undefined;
}
async closeTunnel(_remoteHost: string, _remotePort: number): Promise<void> {
}
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
throw new Error('Method not implemented.');
}
}

View file

@ -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<string> = new Set<string>();
const conflicts: Map<string, IConflictSetting> = new Map<string, IConflictSetting>();
const handledConflicts: Set<string> = new Set<string>();
const baseToLocal = base ? compare(base, local, ignored) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = base ? compare(base, remote, ignored) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
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<any>, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {

View file

@ -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<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangStatus.event;
private _conflicts: IConflictSetting[] = [];
get conflicts(): IConflictSetting[] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<IConflictSetting[]> = this._register(new Emitter<IConflictSetting[]>());
readonly onDidChangeConflicts: Event<IConflictSetting[]> = this._onDidChangeConflicts.event;
private _onDidChangeLocal: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeLocal: Event<void> = 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<boolean> {
@ -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<void> {
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<boolean> {
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<boolean> {
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<ISyncPreviewResult> {
private getPreview(resolvedConflicts: { key: string, value: any }[]): Promise<ISyncPreviewResult> {
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<ISyncPreviewResult> {
private async generatePreview(resolvedConflicts: { key: string, value: any }[], token: CancellationToken): Promise<ISyncPreviewResult> {
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<FormattingOptions> | undefined = undefined;

View file

@ -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>('IUserDataSyncStoreService');
export interface IUserDataSyncStoreService {
_serviceBrand: undefined;
readonly userDataSyncStore: IUserDataSyncStore | undefined;
read(key: string, oldValue: IUserData | null): Promise<IUserData>;
write(key: string, content: string, ref: string | null): Promise<string>;
}
@ -170,40 +167,42 @@ export const enum SyncStatus {
}
export interface ISynchroniser {
readonly status: SyncStatus;
readonly onDidChangeStatus: Event<SyncStatus>;
readonly onDidChangeLocal: Event<void>;
sync(_continue?: boolean): Promise<boolean>;
stop(): void;
}
export const IUserDataSyncService = createDecorator<IUserDataSyncService>('IUserDataSyncService');
export interface IUserDataSyncService extends ISynchroniser {
_serviceBrand: any;
readonly conflictsSource: SyncSource | null;
removeExtension(identifier: IExtensionIdentifier): Promise<void>;
}
export const IUserDataSyncUtilService = createDecorator<IUserDataSyncUtilService>('IUserDataSyncUtilService');
export interface IUserDataSyncUtilService {
_serviceBrand: undefined;
resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>>;
resolveFormattingOptions(resource: URI): Promise<FormattingOptions>;
}
export const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('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>('ISettingsSyncService');
export interface ISettingsSyncService extends ISynchroniser {
_serviceBrand: any;
readonly onDidChangeConflicts: Event<IConflictSetting[]>;
readonly conflicts: IConflictSetting[];
resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<void>;
}
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);

View file

@ -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<any> {
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<any> {
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) { }

View file

@ -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));

View file

@ -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,

View file

@ -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<void>;
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;
/**

View file

@ -227,12 +227,12 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
}
private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise<URI> {
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())) {

View file

@ -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<IProgressStep>, token: CancellationToken): Promise<void>;
}
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<void> {
async participate(model: IResolvedTextFileEditorModel, _env: { reason: SaveReason; }): Promise<void> {
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<void> {
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
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<any>((resolve, reject) => {
const source = new CancellationTokenSource();
const editorOrModel = findEditor(model, this._codeEditorService) || model;
const timeout = this._configurationService.getValue<number>('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<void> {
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
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<number>('editor.codeActionsOnSaveTimeout', settingsOverrides);
return Promise.race([
new Promise<void>((_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<void> {
@ -354,7 +328,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant);
}
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise<void> {
async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason; }, _progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void> {
if (!shouldSynchronizeModel(editorModel.textEditorModel)) {
// the model never made it to the extension
@ -363,6 +337,9 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant {
}
return new Promise<any>((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<void> {
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);
});
}

View file

@ -45,14 +45,9 @@ type ConfigurationInspect<T> = {
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;

View file

@ -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' && (<DebugProtocol.Request>message).command === 'handshake') {
if (message.type === 'request' && (<DebugProtocol.Request>message).command === 'handshake') {
const request = <DebugProtocol.Request>message;
const request = <DebugProtocol.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<IAdapterDescriptor> {
const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle);
if (!adapterProvider) {
return Promise.reject(new Error('no handler found'));
public async $provideDebugAdapter(adapterFactoryHandle: number, sessionDto: IDebugSessionDto): Promise<IAdapterDescriptor> {
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<void> {
@ -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 <IDebugAdapterExecutable>{
@ -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<vscode.DebugAdapterDescriptor | undefined> {
private async getAdapterDescriptor(adapterDescriptorFactory: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise<vscode.DebugAdapterDescriptor | undefined> {
// 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;
}

View file

@ -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()
];
}

View file

@ -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();
}

View file

@ -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))));

View file

@ -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);
}

View file

@ -40,13 +40,7 @@ const codeActionsOnSaveSchema: IConfigurationPropertySchema = {
export const editorConfiguration = Object.freeze<IConfigurationNode>({
...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
}
});

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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, {

View file

@ -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; } });

View file

@ -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

View file

@ -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<IDebugService, IExp
const debugService = element as IDebugService;
const watchExpressions = debugService.getModel().getWatchExpressions();
const viewModel = debugService.getViewModel();
return Promise.all(watchExpressions.map(we => !!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());
}

View file

@ -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<any>().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<State> = noopEvent;
public get onDidChangeState(): Event<State> {
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<IBreakpointsChangeEvent | undefined> = noopEvent;
get onDidChangeCallStack(): Event<void> {
throw new Error('not implemented');
}
get onDidChangeWatchExpressions(): Event<IExpression | undefined> {
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<IDebugSession | undefined> {
throw new Error('not implemented');
}
onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined; explicit: boolean; }> = noopEvent;
get onDidSelectExpression(): Event<IExpression | undefined> {
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.');
}
}

View file

@ -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;
}

View file

@ -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<IDisposable>();
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<URI>(units => this.ensureDirtyUntitledTextFilesAreOpened(units), 250));
private ensureDirtyTextFilesAreOpened(events: ReadonlyArray<TextFileModelChangeEvent>): 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();

View file

@ -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];

View file

@ -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,
}
}
});

View file

@ -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<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
EditorDescriptor.create(
TextFileEditor,
TextFileEditor.ID,
'Text File Editor'
),
[new SyncDescriptor<EditorInput>(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();
(<TextFileEditorModelManager>accessor.textFileService.models).clear();
(<TextFileEditorModelManager>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();
(<TextFileEditorModelManager>accessor.textFileService.models).clear();
(<TextFileEditorModelManager>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<void> {
return new Promise(c => {
Event.once(editorService.onDidActiveEditorChange)(c);
});
}
});

View file

@ -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<TunnelItem[]>;
readonly input: ITunnelItem | ITunnelGroup | undefined;
readonly input: TunnelItem;
groups(): Promise<ITunnelGroup[]>;
}
@ -64,16 +64,23 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel {
private _onForwardedPortsChanged: Emitter<void> = new Emitter();
public onForwardedPortsChanged: Event<void> = 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<ITunnelGroup[]> {
@ -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<ITunnelItem | ITunnelGroup>, 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<ITunnelGroup | ITunnelItem | null>): 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',

View file

@ -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<IWorkbenchContributionsRegist
workbenchContributionsRegistry.registerWorkbenchContribution(LabelContribution, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting);
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChannels, LifecyclePhase.Restored);
workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready);

View file

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, TunnelOptions, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
export class TunnelFactoryContribution extends Disposable implements IWorkbenchContribution {
constructor(
@ITunnelService tunnelService: ITunnelService,
@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService,
) {
super();
if (workbenchEnvironmentService.options && workbenchEnvironmentService.options.tunnelFactory) {
this._register(tunnelService.setTunnelProvider({
forwardPort: (tunnelOptions: TunnelOptions): Promise<RemoteTunnel> | 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);
});
});
}
}));
}
}
}

View file

@ -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")

View file

@ -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<Response | undefined>;
type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean; } | undefined;
type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise<Response | undefined>;
type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined;
class WebviewSession extends Disposable {

View file

@ -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 || ''
}
};

View file

@ -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);
});

View file

@ -146,10 +146,7 @@ export class ProgressService extends Disposable implements IProgressService {
private withNotificationProgress<P extends Promise<R>, 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();
}

View file

@ -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<IRemoteExplorerService>('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,

Some files were not shown because too many files have changed in this diff Show more