Merge remote-tracking branch 'origin/master' into alex/wrapping

This commit is contained in:
Alexandru Dima 2020-01-09 09:44:12 +01:00
commit 4f52d506ea
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
80 changed files with 1389 additions and 752 deletions

2
.github/copycat.yml vendored
View file

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

20
.vscode/launch.json vendored
View file

@ -19,10 +19,7 @@
"restart": true,
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"presentation": {
"hidden": true,
}
]
},
{
"type": "chrome",
@ -227,6 +224,21 @@
"order": 10
}
},
{
"type": "node",
"request": "launch",
"name": "HTML Unit Tests",
"program": "${workspaceFolder}/extensions/html-language-features/server/node_modules/mocha/bin/_mocha",
"stopOnEntry": false,
"cwd": "${workspaceFolder}/extensions/html-language-features/server",
"outFiles": [
"${workspaceFolder}/extensions/html-language-features/server/out/**/*.js"
],
"presentation": {
"group": "5_tests",
"order": 10
}
},
{
"type": "extensionHost",
"request": "launch",

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

@ -23,6 +23,7 @@ import { formatError, runSafe, runSafeAsync } from './utils/runner';
import { getFoldingRanges } from './modes/htmlFolding';
import { getDataProviders } from './customData';
import { getSelectionRanges } from './modes/selectionRanges';
import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanticTokens';
namespace TagCloseRequest {
export const type: RequestType<TextDocumentPositionParams, string | null, any, any> = new RequestType('html/tag');
@ -530,14 +531,19 @@ connection.onRequest(MatchingTagPositionRequest.type, (params, token) => {
}, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token);
});
let semanticTokensProvider: SemanticTokenProvider | undefined;
function getSemanticTokenProvider() {
if (!semanticTokensProvider) {
semanticTokensProvider = newSemanticTokenProvider(languageModes);
}
return semanticTokensProvider;
}
connection.onRequest(SemanticTokenRequest.type, (params, token) => {
return runSafe(() => {
const document = documents.get(params.textDocument.uri);
if (document) {
const jsMode = languageModes.getMode('javascript');
if (jsMode && jsMode.getSemanticTokens) {
return jsMode.getSemanticTokens(document, params.ranges);
}
return getSemanticTokenProvider().getSemanticTokens(document, params.ranges);
}
return null;
}, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token);
@ -545,11 +551,7 @@ connection.onRequest(SemanticTokenRequest.type, (params, token) => {
connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => {
return runSafe(() => {
const jsMode = languageModes.getMode('javascript');
if (jsMode && jsMode.getSemanticTokenLegend) {
return jsMode.getSemanticTokenLegend();
}
return null;
return getSemanticTokenProvider().legend;
}, null, `Error while computing semantic tokens legend`, token);
});

View file

@ -58,6 +58,8 @@ export function getDocumentRegions(languageService: LanguageService, document: T
} else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') {
if (/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(scanner.getTokenText())) {
languageIdFromType = 'javascript';
} else if (/["']text\/typescript["']/.test(scanner.getTokenText())) {
languageIdFromType = 'typescript';
} else {
languageIdFromType = undefined;
}

View file

@ -8,7 +8,7 @@ import {
SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation,
Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString,
DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange,
LanguageMode, Settings
LanguageMode, Settings, SemanticTokenData
} from './languageModes';
import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings';
import { HTMLDocumentRegions } from './embeddedSupport';
@ -17,8 +17,6 @@ import * as ts from 'typescript';
import { join } from 'path';
import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens';
const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents
const TS_FILE_NAME = 'vscode://javascript/2.ts';
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
let jquery_d_ts = join(__dirname, '../lib/jquery.d.ts'); // when packaged
@ -26,8 +24,10 @@ if (!ts.sys.fileExists(jquery_d_ts)) {
jquery_d_ts = join(__dirname, '../../lib/jquery.d.ts'); // from source
}
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript'));
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, languageId: 'javascript' | 'typescript'): LanguageMode {
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId));
const workingFile = languageId === 'javascript' ? 'vscode://javascript/1.js' : 'vscode://javascript/2.ts'; // the same 'file' is used for all contents
let compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic };
let currentTextDocument: TextDocument;
@ -40,10 +40,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
}
const host: ts.LanguageServiceHost = {
getCompilationSettings: () => compilerOptions,
getScriptFileNames: () => [FILE_NAME, TS_FILE_NAME, jquery_d_ts],
getScriptKind: (fileName) => fileName === TS_FILE_NAME ? ts.ScriptKind.TS : ts.ScriptKind.JS,
getScriptFileNames: () => [workingFile, jquery_d_ts],
getScriptKind: (fileName) => fileName.substr(fileName.length - 2) === 'ts' ? ts.ScriptKind.TS : ts.ScriptKind.JS,
getScriptVersion: (fileName: string) => {
if (fileName === FILE_NAME || fileName === TS_FILE_NAME) {
if (fileName === workingFile) {
return String(scriptFileVersion);
}
return '1'; // default lib an jquery.d.ts are static
@ -51,7 +51,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
getScriptSnapshot: (fileName: string) => {
let text = '';
if (startsWith(fileName, 'vscode:')) {
if (fileName === FILE_NAME || fileName === TS_FILE_NAME) {
if (fileName === workingFile) {
text = currentTextDocument.getText();
}
} else {
@ -72,17 +72,17 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
return {
getId() {
return 'javascript';
return languageId;
},
doValidation(document: TextDocument): Diagnostic[] {
updateCurrentTextDocument(document);
const syntaxDiagnostics: ts.Diagnostic[] = jsLanguageService.getSyntacticDiagnostics(FILE_NAME);
const semanticDiagnostics = jsLanguageService.getSemanticDiagnostics(FILE_NAME);
const syntaxDiagnostics: ts.Diagnostic[] = jsLanguageService.getSyntacticDiagnostics(workingFile);
const semanticDiagnostics = jsLanguageService.getSemanticDiagnostics(workingFile);
return syntaxDiagnostics.concat(semanticDiagnostics).map((diag: ts.Diagnostic): Diagnostic => {
return {
range: convertRange(currentTextDocument, diag),
severity: DiagnosticSeverity.Error,
source: 'js',
source: languageId,
message: ts.flattenDiagnosticMessageText(diag.messageText, '\n')
};
});
@ -90,7 +90,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
doComplete(document: TextDocument, position: Position): CompletionList {
updateCurrentTextDocument(document);
let offset = currentTextDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(FILE_NAME, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
let completions = jsLanguageService.getCompletionsAtPosition(workingFile, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
if (!completions) {
return { isIncomplete: false, items: [] };
}
@ -106,7 +106,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
kind: convertKind(entry.kind),
textEdit: TextEdit.replace(replaceRange, entry.name),
data: { // data used for resolving item details (see 'doResolve')
languageId: 'javascript',
languageId,
uri: document.uri,
offset: offset
}
@ -116,7 +116,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doResolve(document: TextDocument, item: CompletionItem): CompletionItem {
updateCurrentTextDocument(document);
let details = jsLanguageService.getCompletionEntryDetails(FILE_NAME, item.data.offset, item.label, undefined, undefined, undefined);
let details = jsLanguageService.getCompletionEntryDetails(workingFile, item.data.offset, item.label, undefined, undefined, undefined);
if (details) {
item.detail = ts.displayPartsToString(details.displayParts);
item.documentation = ts.displayPartsToString(details.documentation);
@ -126,7 +126,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doHover(document: TextDocument, position: Position): Hover | null {
updateCurrentTextDocument(document);
let info = jsLanguageService.getQuickInfoAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let info = jsLanguageService.getQuickInfoAtPosition(workingFile, currentTextDocument.offsetAt(position));
if (info) {
let contents = ts.displayPartsToString(info.displayParts);
return {
@ -138,7 +138,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
doSignatureHelp(document: TextDocument, position: Position): SignatureHelp | null {
updateCurrentTextDocument(document);
let signHelp = jsLanguageService.getSignatureHelpItems(FILE_NAME, currentTextDocument.offsetAt(position), undefined);
let signHelp = jsLanguageService.getSignatureHelpItems(workingFile, currentTextDocument.offsetAt(position), undefined);
if (signHelp) {
let ret: SignatureHelp = {
activeSignature: signHelp.selectedItemIndex,
@ -175,7 +175,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findDocumentHighlight(document: TextDocument, position: Position): DocumentHighlight[] {
updateCurrentTextDocument(document);
const highlights = jsLanguageService.getDocumentHighlights(FILE_NAME, currentTextDocument.offsetAt(position), [FILE_NAME]);
const highlights = jsLanguageService.getDocumentHighlights(workingFile, currentTextDocument.offsetAt(position), [workingFile]);
const out: DocumentHighlight[] = [];
for (const entry of highlights || []) {
for (const highlight of entry.highlightSpans) {
@ -189,7 +189,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findDocumentSymbols(document: TextDocument): SymbolInformation[] {
updateCurrentTextDocument(document);
let items = jsLanguageService.getNavigationBarItems(FILE_NAME);
let items = jsLanguageService.getNavigationBarItems(workingFile);
if (items) {
let result: SymbolInformation[] = [];
let existing = Object.create(null);
@ -225,9 +225,9 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findDefinition(document: TextDocument, position: Position): Definition | null {
updateCurrentTextDocument(document);
let definition = jsLanguageService.getDefinitionAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let definition = jsLanguageService.getDefinitionAtPosition(workingFile, currentTextDocument.offsetAt(position));
if (definition) {
return definition.filter(d => d.fileName === FILE_NAME).map(d => {
return definition.filter(d => d.fileName === workingFile).map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
@ -238,9 +238,9 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
findReferences(document: TextDocument, position: Position): Location[] {
updateCurrentTextDocument(document);
let references = jsLanguageService.getReferencesAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let references = jsLanguageService.getReferencesAtPosition(workingFile, currentTextDocument.offsetAt(position));
if (references) {
return references.filter(d => d.fileName === FILE_NAME).map(d => {
return references.filter(d => d.fileName === workingFile).map(d => {
return {
uri: document.uri,
range: convertRange(currentTextDocument, d.textSpan)
@ -255,7 +255,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
const parent = selectionRange.parent ? convertSelectionRange(selectionRange.parent) : undefined;
return SelectionRange.create(convertRange(currentTextDocument, selectionRange.textSpan), parent);
}
const range = jsLanguageService.getSmartSelectionRange(FILE_NAME, currentTextDocument.offsetAt(position));
const range = jsLanguageService.getSmartSelectionRange(workingFile, currentTextDocument.offsetAt(position));
return convertSelectionRange(range);
},
format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings): TextEdit[] {
@ -273,7 +273,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
end -= range.end.character;
lastLineRange = Range.create(Position.create(range.end.line, 0), range.end);
}
let edits = jsLanguageService.getFormattingEditsForRange(FILE_NAME, start, end, formatSettings);
let edits = jsLanguageService.getFormattingEditsForRange(workingFile, start, end, formatSettings);
if (edits) {
let result = [];
for (let edit of edits) {
@ -296,7 +296,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
},
getFoldingRanges(document: TextDocument): FoldingRange[] {
updateCurrentTextDocument(document);
let spans = jsLanguageService.getOutliningSpans(FILE_NAME);
let spans = jsLanguageService.getOutliningSpans(workingFile);
let ranges: FoldingRange[] = [];
for (let span of spans) {
let curr = convertRange(currentTextDocument, span.textSpan);
@ -316,12 +316,9 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
onDocumentRemoved(document: TextDocument) {
jsDocuments.onDocumentRemoved(document);
},
getSemanticTokens(document: TextDocument, ranges: Range[] | undefined): number[] {
getSemanticTokens(document: TextDocument): SemanticTokenData[] {
updateCurrentTextDocument(document);
if (!ranges) {
ranges = [Range.create(Position.create(0, 0), document.positionAt(document.getText().length))];
}
return getSemanticTokens(jsLanguageService, currentTextDocument, TS_FILE_NAME, ranges);
return getSemanticTokens(jsLanguageService, currentTextDocument, workingFile);
},
getSemanticTokenLegend(): { types: string[], modifiers: string[] } {
return getSemanticTokenLegend();

View file

@ -3,22 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, Range } from './languageModes';
import { TextDocument, SemanticTokenData } from './languageModes';
import * as ts from 'typescript';
type SemanticTokenData = { offset: number, length: number, typeIdx: number, modifierSet: number };
export function getSemanticTokens(jsLanguageService: ts.LanguageService, currentTextDocument: TextDocument, fileName: string, ranges: Range[]) {
export function getSemanticTokens(jsLanguageService: ts.LanguageService, currentTextDocument: TextDocument, fileName: string): SemanticTokenData[] {
//https://ts-ast-viewer.com/#code/AQ0g2CmAuwGbALzAJwG4BQZQGNwEMBnQ4AQQEYBmYAb2C22zgEtJwATJVTRxgcwD27AQAp8AGmAAjAJS0A9POB8+7NQ168oscAJz5wANXwAnLug2bsJmAFcTAO2XAA1MHyvgu-UdOeWbOw8ViAAvpagocBAA
let resultTokens: SemanticTokenData[] = [];
// const tokens = jsLanguageService.getSemanticClassifications(fileName, { start: 0, length: currentTextDocument.getText().length });
// for (let token of tokens) {
// const typeIdx = tokenFromClassificationMapping[token.classificationType];
// if (typeIdx !== undefined) {
// resultTokens.push({ offset: token.textSpan.start, length: token.textSpan.length, typeIdx, modifierSet: 0 });
// }
// }
const program = jsLanguageService.getProgram();
if (program) {
@ -46,7 +38,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current
modifierSet |= TokenModifier.async;
}
if (typeIdx !== undefined) {
resultTokens.push({ offset: node.getStart(), length: node.getWidth(), typeIdx, modifierSet });
resultTokens.push({ start: currentTextDocument.positionAt(node.getStart()), length: node.getWidth(), typeIdx, modifierSet });
}
}
}
@ -60,41 +52,7 @@ export function getSemanticTokens(jsLanguageService: ts.LanguageService, current
}
}
resultTokens = resultTokens.sort((d1, d2) => d1.offset - d2.offset);
const offsetRanges = ranges.map(r => ({ startOffset: currentTextDocument.offsetAt(r.start), endOffset: currentTextDocument.offsetAt(r.end) })).sort((d1, d2) => d1.startOffset - d2.startOffset);
let rangeIndex = 0;
let currRange = offsetRanges[rangeIndex++];
let prefLine = 0;
let prevChar = 0;
let encodedResult: number[] = [];
for (let k = 0; k < resultTokens.length && currRange; k++) {
const curr = resultTokens[k];
if (currRange.startOffset <= curr.offset && curr.offset + curr.length <= currRange.endOffset) {
// token inside a range
const startPos = currentTextDocument.positionAt(curr.offset);
if (prefLine !== startPos.line) {
prevChar = 0;
}
encodedResult.push(startPos.line - prefLine); // line delta
encodedResult.push(startPos.character - prevChar); // line delta
encodedResult.push(curr.length); // length
encodedResult.push(curr.typeIdx); // tokenType
encodedResult.push(curr.modifierSet); // tokenModifier
prefLine = startPos.line;
prevChar = startPos.character;
} else if (currRange.endOffset >= curr.offset) {
currRange = offsetRanges[rangeIndex++];
}
}
return encodedResult;
return resultTokens;
}
@ -103,15 +61,15 @@ export function getSemanticTokenLegend() {
}
const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'parameterType', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member'];
const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member'];
const tokenModifiers: string[] = ['declaration', 'static', 'async'];
enum TokenType {
const enum TokenType {
'class' = 0,
'enum' = 1,
'interface' = 2,
'namespace' = 3,
'parameterType' = 4,
'typeParameter' = 4,
'type' = 5,
'parameter' = 6,
'variable' = 7,
@ -121,22 +79,13 @@ enum TokenType {
'member' = 11
}
enum TokenModifier {
const enum TokenModifier {
'declaration' = 0x01,
'static' = 0x02,
'async' = 0x04,
}
// const tokenFromClassificationMapping: { [name: string]: TokenType } = {
// [ts.ClassificationTypeNames.className]: TokenType.class,
// [ts.ClassificationTypeNames.enumName]: TokenType.enum,
// [ts.ClassificationTypeNames.interfaceName]: TokenType.interface,
// [ts.ClassificationTypeNames.moduleName]: TokenType.namespace,
// [ts.ClassificationTypeNames.typeParameterName]: TokenType.parameterType,
// [ts.ClassificationTypeNames.typeAliasName]: TokenType.type,
// [ts.ClassificationTypeNames.parameterName]: TokenType.parameter
// };
const tokenFromDeclarationMapping: { [name: string]: TokenType } = {
[ts.SyntaxKind.VariableDeclaration]: TokenType.variable,
[ts.SyntaxKind.Parameter]: TokenType.parameter,
@ -150,4 +99,7 @@ const tokenFromDeclarationMapping: { [name: string]: TokenType } = {
[ts.SyntaxKind.MethodSignature]: TokenType.member,
[ts.SyntaxKind.GetAccessor]: TokenType.property,
[ts.SyntaxKind.PropertySignature]: TokenType.property,
[ts.SyntaxKind.InterfaceDeclaration]: TokenType.interface,
[ts.SyntaxKind.TypeAliasDeclaration]: TokenType.type,
[ts.SyntaxKind.TypeParameter]: TokenType.typeParameter
};

View file

@ -31,6 +31,13 @@ export interface Workspace {
readonly folders: WorkspaceFolder[];
}
export interface SemanticTokenData {
start: Position;
length: number;
typeIdx: number;
modifierSet: number;
}
export interface LanguageMode {
getId(): string;
getSelectionRange?: (document: TextDocument, position: Position) => SelectionRange;
@ -52,7 +59,7 @@ export interface LanguageMode {
findMatchingTagPosition?: (document: TextDocument, position: Position) => Position | null;
getFoldingRanges?: (document: TextDocument) => FoldingRange[];
onDocumentRemoved(document: TextDocument): void;
getSemanticTokens?(document: TextDocument, ranges: Range[] | undefined): number[];
getSemanticTokens?(document: TextDocument): SemanticTokenData[];
getSemanticTokenLegend?(): { types: string[], modifiers: string[] };
dispose(): void;
}
@ -87,7 +94,8 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo
modes['css'] = getCSSMode(cssLanguageService, documentRegions, workspace);
}
if (supportedLanguages['javascript']) {
modes['javascript'] = getJavaScriptMode(documentRegions);
modes['javascript'] = getJavaScriptMode(documentRegions, 'javascript');
modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript');
}
return {
getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined {

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { LanguageModes, TextDocument, Position, Range, SelectionRange } from './languageModes';
import { insideRangeButNotSame } from '../utils/positions';
export function getSelectionRanges(languageModes: LanguageModes, document: TextDocument, positions: Position[]) {
const htmlMode = languageModes.getMode('html');
@ -23,13 +24,3 @@ export function getSelectionRanges(languageModes: LanguageModes, document: TextD
});
}
function beforeOrSame(p1: Position, p2: Position) {
return p1.line < p2.line || p1.line === p2.line && p1.character <= p2.character;
}
function insideRangeButNotSame(r1: Range, r2: Range) {
return beforeOrSame(r1.start, r2.start) && beforeOrSame(r2.end, r1.end) && !equalRange(r1, r2);
}
function equalRange(r1: Range, r2: Range) {
return r1.start.line === r2.start.line && r1.start.character === r2.start.character && r1.end.line === r2.end.line && r1.end.character === r2.end.character;
}

View file

@ -0,0 +1,139 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SemanticTokenData, Range, TextDocument, LanguageModes, Position } from './languageModes';
import { beforeOrSame } from '../utils/positions';
interface LegendMapping {
types: number[] | undefined;
modifiers: number[] | undefined;
}
export interface SemanticTokenProvider {
readonly legend: { types: string[]; modifiers: string[] };
getSemanticTokens(document: TextDocument, ranges?: Range[]): number[];
}
export function newSemanticTokenProvider(languageModes: LanguageModes): SemanticTokenProvider {
// combined legend across modes
const legend = { types: [], modifiers: [] };
const legendMappings: { [modeId: string]: LegendMapping } = {};
for (let mode of languageModes.getAllModes()) {
if (mode.getSemanticTokenLegend && mode.getSemanticTokens) {
const modeLegend = mode.getSemanticTokenLegend();
legendMappings[mode.getId()] = { types: createMapping(modeLegend.types, legend.types), modifiers: createMapping(modeLegend.modifiers, legend.modifiers) };
}
}
return {
legend,
getSemanticTokens(document: TextDocument, ranges?: Range[]): number[] {
const allTokens: SemanticTokenData[] = [];
for (let mode of languageModes.getAllModesInDocument(document)) {
if (mode.getSemanticTokens) {
const mapping = legendMappings[mode.getId()];
const tokens = mode.getSemanticTokens(document);
applyTypesMapping(tokens, mapping.types);
applyModifiersMapping(tokens, mapping.modifiers);
for (let token of tokens) {
allTokens.push(token);
}
}
}
return encodeTokens(allTokens, ranges);
}
};
}
function createMapping(origLegend: string[], newLegend: string[]): number[] | undefined {
const mapping: number[] = [];
let needsMapping = false;
for (let origIndex = 0; origIndex < origLegend.length; origIndex++) {
const entry = origLegend[origIndex];
let newIndex = newLegend.indexOf(entry);
if (newIndex === -1) {
newIndex = newLegend.length;
newLegend.push(entry);
}
mapping.push(newIndex);
needsMapping = needsMapping || (newIndex !== origIndex);
}
return needsMapping ? mapping : undefined;
}
function applyTypesMapping(tokens: SemanticTokenData[], typesMapping: number[] | undefined): void {
if (typesMapping) {
for (let token of tokens) {
token.typeIdx = typesMapping[token.typeIdx];
}
}
}
function applyModifiersMapping(tokens: SemanticTokenData[], modifiersMapping: number[] | undefined): void {
if (modifiersMapping) {
for (let token of tokens) {
let modifierSet = token.modifierSet;
if (modifierSet) {
let index = 0;
let result = 0;
while (modifierSet > 0) {
if ((modifierSet & 1) !== 0) {
result = result + (1 << modifiersMapping[index]);
}
index++;
modifierSet = modifierSet >> 1;
}
token.modifierSet = result;
}
}
}
}
const fullRange = [Range.create(Position.create(0, 0), Position.create(Number.MAX_VALUE, 0))];
function encodeTokens(tokens: SemanticTokenData[], ranges?: Range[]): number[] {
const resultTokens = tokens.sort((d1, d2) => d1.start.line - d2.start.line || d1.start.character - d2.start.character);
if (ranges) {
ranges = ranges.sort((d1, d2) => d1.start.line - d2.start.line || d1.start.character - d2.start.character);
} else {
ranges = fullRange;
}
let rangeIndex = 0;
let currRange = ranges[rangeIndex++];
let prefLine = 0;
let prevChar = 0;
let encodedResult: number[] = [];
for (let k = 0; k < resultTokens.length && currRange; k++) {
const curr = resultTokens[k];
const start = curr.start;
while (currRange && beforeOrSame(currRange.end, start)) {
currRange = ranges[rangeIndex++];
}
if (currRange && beforeOrSame(currRange.start, start) && beforeOrSame({ line: start.line, character: start.character + curr.length }, currRange.end)) {
// token inside a range
if (prefLine !== start.line) {
prevChar = 0;
}
encodedResult.push(start.line - prefLine); // line delta
encodedResult.push(start.character - prevChar); // line delta
encodedResult.push(curr.length); // length
encodedResult.push(curr.typeIdx); // tokenType
encodedResult.push(curr.modifierSet); // tokenModifier
prefLine = start.line;
prevChar = start.character;
}
}
return encodedResult;
}

View file

@ -6,6 +6,7 @@
import 'mocha';
import * as assert from 'assert';
import { TextDocument, getLanguageModes, ClientCapabilities, Range, Position } from '../modes/languageModes';
import { newSemanticTokenProvider } from '../modes/semanticTokens';
interface ExpectedToken {
startLine: number;
@ -14,22 +15,17 @@ interface ExpectedToken {
tokenClassifiction: string;
}
function assertTokens(lines: string[], expected: ExpectedToken[], range?: Range, message?: string): void {
function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: Range[], message?: string): void {
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
const workspace = {
settings: {},
folders: [{ name: 'foo', uri: 'test://foo' }]
};
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST);
const semanticTokensProvider = newSemanticTokenProvider(languageModes);
if (!range) {
range = Range.create(Position.create(0, 0), document.positionAt(document.getText().length));
}
const jsMode = languageModes.getMode('javascript')!;
const legend = jsMode.getSemanticTokenLegend!();
const actual = jsMode.getSemanticTokens!(document, [range]);
const legend = semanticTokensProvider.legend;
const actual = semanticTokensProvider.getSemanticTokens(document, ranges);
let actualRanges = [];
let lastLine = 0;
@ -50,9 +46,9 @@ function t(startLine: number, character: number, length: number, tokenClassifict
return { startLine, character, length, tokenClassifiction };
}
suite('JavaScript Semantic Tokens', () => {
suite('HTML Semantic Tokens', () => {
test('variables', () => {
test('Variables', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -75,7 +71,7 @@ suite('JavaScript Semantic Tokens', () => {
]);
});
test('function', () => {
test('Functions', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -95,7 +91,7 @@ suite('JavaScript Semantic Tokens', () => {
]);
});
test('members', () => {
test('Members', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
@ -124,6 +120,87 @@ suite('JavaScript Semantic Tokens', () => {
]);
});
test('Interfaces', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script type="text/typescript">',
/*3*/' interface Position { x: number, y: number };',
/*4*/' const p = { x: 1, y: 2 } as Position;',
/*5*/' const foo = (o: Position) => o.x + o.y;',
/*6*/'</script>',
/*7*/'</head>',
/*8*/'</html>',
];
assertTokens(input, [
t(3, 12, 8, 'interface.declaration'), t(3, 23, 1, 'property.declaration'), t(3, 34, 1, 'property.declaration'),
t(4, 8, 1, 'variable.declaration'), t(4, 30, 8, 'interface'),
t(5, 8, 3, 'variable.declaration'), t(5, 15, 1, 'parameter.declaration'), t(5, 18, 8, 'interface'), t(5, 31, 1, 'parameter'), t(5, 33, 1, 'property'), t(5, 37, 1, 'parameter'), t(5, 39, 1, 'property')
]);
});
test('Type aliases and type parameters', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script type="text/typescript">',
/*3*/' type MyMap = Map<string, number>;',
/*4*/' function f<T extends MyMap>(t: T | number) : T { ',
/*5*/' return <T> <unknown> new Map<string, MyMap>();',
/*6*/' }',
/*7*/'</script>',
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
t(3, 7, 5, 'type.declaration'), t(3, 15, 3, 'variable') /* to investiagte */,
t(4, 11, 1, 'function.declaration'), t(4, 13, 1, 'typeParameter.declaration'), t(4, 23, 5, 'type'), t(4, 30, 1, 'parameter.declaration'), t(4, 33, 1, 'typeParameter'), t(4, 47, 1, 'typeParameter'),
t(5, 12, 1, 'typeParameter'), t(5, 29, 3, 'variable'), t(5, 41, 5, 'type'),
]);
});
test('TS and JS', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script type="text/typescript">',
/*3*/' function f<T>(p1: T): T[] { return [ p1 ]; }',
/*4*/'</script>',
/*5*/'<script>',
/*6*/' window.alert("Hello");',
/*7*/'</script>',
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
t(3, 11, 1, 'function.declaration'), t(3, 13, 1, 'typeParameter.declaration'), t(3, 16, 2, 'parameter.declaration'), t(3, 20, 1, 'typeParameter'), t(3, 24, 1, 'typeParameter'), t(3, 39, 2, 'parameter'),
t(6, 2, 6, 'variable'), t(6, 9, 5, 'member')
]);
});
test('Ranges', () => {
const input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script>',
/*3*/' window.alert("Hello");',
/*4*/'</script>',
/*5*/'<script>',
/*6*/' window.alert("World");',
/*7*/'</script>',
/*8*/'</head>',
/*9*/'</html>',
];
assertTokens(input, [
t(3, 2, 6, 'variable'), t(3, 9, 5, 'member')
], [Range.create(Position.create(2, 0), Position.create(4, 0))]);
assertTokens(input, [
t(6, 2, 6, 'variable'),
], [Range.create(Position.create(6, 2), Position.create(6, 8))]);
});
});

View file

@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Range } from '../modes/languageModes';
export function beforeOrSame(p1: Position, p2: Position) {
return p1.line < p2.line || p1.line === p2.line && p1.character <= p2.character;
}
export function insideRangeButNotSame(r1: Range, r2: Range) {
return beforeOrSame(r1.start, r2.start) && beforeOrSame(r2.end, r1.end) && !equalRange(r1, r2);
}
export function equalRange(r1: Range, r2: Range) {
return r1.start.line === r2.start.line && r1.start.character === r2.start.character && r1.end.line === r2.end.line && r1.end.character === r2.end.character;
}

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';
@ -78,6 +78,7 @@ export default class LanguageProvider extends Disposable {
import('./features/signatureHelp').then(provider => this._register(provider.register(selector, this.client))),
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/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

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.42.0",
"distro": "aafa05cffde354e7f58dc9fd2dcfc9aa3e81ea77",
"distro": "683b7a48a0cab9871ae34d129cc31e1036746b37",
"author": {
"name": "Microsoft Corporation"
},
@ -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

@ -329,24 +329,31 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
// commands that aren't needed anymore because there is now ContextKeyExpr.OR
CommandsRegistry.registerCommandAlias('goToNextReferenceFromEmbeddedEditor', 'goToNextReference');
CommandsRegistry.registerCommandAlias('goToPreviousReferenceFromEmbeddedEditor', 'goToPreviousReference');
// close
CommandsRegistry.registerCommandAlias('closeReferenceSearchEditor', 'closeReferenceSearch');
CommandsRegistry.registerCommand(
'closeReferenceSearch',
accessor => withController(accessor, controller => controller.closeWidget())
);
KeybindingsRegistry.registerKeybindingRule({
id: 'closeReferenceSearch',
weight: KeybindingWeight.EditorContrib - 101,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape],
when: ContextKeyExpr.or(
ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek')),
ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek'))
),
handler(accessor: ServicesAccessor) {
withController(accessor, controller => controller.closeWidget());
}
when: ContextKeyExpr.and(PeekContext.inPeekEditor, ContextKeyExpr.not('config.editor.stablePeek'))
});
KeybindingsRegistry.registerKeybindingRule({
id: 'closeReferenceSearch',
weight: KeybindingWeight.WorkbenchContrib + 50,
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape],
when: ContextKeyExpr.and(ctxReferenceSearchVisible, ContextKeyExpr.not('config.editor.stablePeek'))
});
// commands that aren't needed anymore because there is now ContextKeyExpr.OR
CommandsRegistry.registerCommandAlias('goToNextReferenceFromEmbeddedEditor', 'goToNextReference');
CommandsRegistry.registerCommandAlias('goToPreviousReferenceFromEmbeddedEditor', 'goToPreviousReference');
CommandsRegistry.registerCommandAlias('closeReferenceSearchEditor', 'closeReferenceSearch');
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'openReferenceToSide',

View file

@ -133,6 +133,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
const options = this.editor.getOptions();
const fontInfo = options.get(EditorOption.fontInfo);
const fontFamily = fontInfo.fontFamily;
const fontFeatureSettings = fontInfo.fontFeatureSettings;
const fontSize = options.get(EditorOption.suggestFontSize) || fontInfo.fontSize;
const lineHeight = options.get(EditorOption.suggestLineHeight) || fontInfo.lineHeight;
const fontWeight = fontInfo.fontWeight;
@ -142,6 +143,7 @@ class Renderer implements IListRenderer<CompletionItem, ISuggestionTemplateData>
data.root.style.fontSize = fontSizePx;
data.root.style.fontWeight = fontWeight;
main.style.fontFamily = fontFamily;
main.style.fontFeatureSettings = fontFeatureSettings;
main.style.lineHeight = lineHeightPx;
data.icon.style.height = lineHeightPx;
data.icon.style.width = lineHeightPx;

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

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

@ -378,7 +378,7 @@ function registerDefaultClassifications(): void {
registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type');
registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type');
registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type');
registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type');
registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), undefined, 'type');
registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]);
registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function'], ['support.function']]);
@ -395,7 +395,6 @@ function registerDefaultClassifications(): void {
tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined);
tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined);
//tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined);
tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined);
tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined);
tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined);

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';
@ -25,10 +25,12 @@ interface ISyncPreviewResult {
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';
@ -81,12 +83,44 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
return false;
}
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 getConflicts(): Promise<IConflictSetting[]> {
if (!this.syncPreviewResultPromise) {
return [];
}
if (this.status !== SyncStatus.HasConflicts) {
return [];
}
const preview = await this.syncPreviewResultPromise;
return preview.conflicts;
}
async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<void> {
if (this.status === SyncStatus.HasConflicts) {
this.stop();
await this.doSync(resolvedConflicts);
}
}
private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise<boolean> {
this.logService.trace('Settings: Started synchronizing settings...');
this.setStatus(SyncStatus.Syncing);
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 +144,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 +202,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,14 +217,14 @@ 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() : '{}';
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 };
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts };
}
if (!lastSyncData // First time sync
@ -209,12 +233,12 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
) {
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 +255,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(previewContent));
}
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, conflicts };
}
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;

View file

@ -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,41 @@ 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;
getConflicts(): Promise<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,30 @@ 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;
}
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 'stop': this.service.stop(); return Promise.resolve();
case 'getConflicts': return this.service.getConflicts();
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,

15
src/vs/vscode.d.ts vendored
View file

@ -9518,6 +9518,21 @@ declare module 'vscode' {
* @return The resolved debug configuration or undefined or null.
*/
resolveDebugConfiguration?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration>;
/**
* This hook is directly called after 'resolveDebugConfiguration' but with all variables substituted.
* It can be used to resolve or verify a [debug configuration](#DebugConfiguration) by filling in missing values or by adding/changing/removing attributes.
* If more than one debug configuration provider is registered for the same type, the 'resolveDebugConfigurationWithSubstitutedVariables' calls are chained
* in arbitrary order and the initial debug configuration is piped through the chain.
* Returning the value 'undefined' prevents the debug session from starting.
* Returning the value 'null' prevents the debug session from starting and opens the underlying debug configuration instead.
*
* @param folder The workspace folder from which the configuration originates from or `undefined` for a folderless setup.
* @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve.
* @param token A cancellation token.
* @return The resolved debug configuration or undefined or null.
*/
resolveDebugConfigurationWithSubstitutedVariables?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration>;
}
/**

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;
}
@ -792,24 +795,6 @@ declare module 'vscode' {
//#region Debug:
export interface DebugConfigurationProvider {
/**
* This hook is directly called after 'resolveDebugConfiguration' but with all variables substituted.
* It can be used to resolve or verify a [debug configuration](#DebugConfiguration) by filling in missing values or by adding/changing/removing attributes.
* If more than one debug configuration provider is registered for the same type, the 'resolveDebugConfigurationWithSubstitutedVariables' calls are chained
* in arbitrary order and the initial debug configuration is piped through the chain.
* Returning the value 'undefined' prevents the debug session from starting.
* Returning the value 'null' prevents the debug session from starting and opens the underlying debug configuration instead.
*
* @param folder The workspace folder from which the configuration originates from or `undefined` for a folderless setup.
* @param debugConfiguration The [debug configuration](#DebugConfiguration) to resolve.
* @param token A cancellation token.
* @return The resolved debug configuration or undefined or null.
*/
resolveDebugConfigurationWithSubstitutedVariables?(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration>;
}
// deprecated
export interface DebugConfigurationProvider {
@ -1320,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
@ -1446,6 +1431,8 @@ declare module 'vscode' {
workspaceLanguageValue?: T;
workspaceFolderLanguageValue?: T;
languages?: string[];
} | undefined;
/**

View file

@ -245,14 +245,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostTerminalService.getDefaultShell(false, configProvider);
},
openExternal(uri: URI) {
return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote });
return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.authority });
},
asExternalUri(uri: URI) {
if (uri.scheme === initData.environment.appUriScheme) {
return extHostUrls.createAppUri(uri);
}
return extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.isRemote });
return extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.authority });
},
get remoteName() {
return getRemoteName(initData.remote.authority);

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

@ -87,22 +87,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
}
const configProvider = await this._configurationService.getConfigProvider();
const terminalConfig = configProvider.getConfiguration('terminal');
let shell;
const automationShellConfig = terminalConfig.integrated.automationShell;
if (automationShellConfig) {
if (env.isWindows) {
shell = automationShellConfig.windows;
} else if (env.isLinux) {
shell = automationShellConfig.linux;
} else if (env.isMacintosh) {
shell = automationShellConfig.osx;
}
}
if (!shell) {
shell = this._terminalService.getDefaultShell(true, configProvider);
}
const shell = this._terminalService.getDefaultShell(true, configProvider);
if (needNewTerminal || !this._integratedTerminalInstance) {

View file

@ -59,7 +59,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
}
public getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string {
const fetchSetting = (key: string) => {
const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => {
const setting = configProvider
.getConfiguration(key.substr(0, key.lastIndexOf('.')))
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
@ -79,7 +79,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
}
private _getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string {
const fetchSetting = (key: string) => {
const fetchSetting = (key: string): { userValue: string | string[] | undefined, value: string | string[] | undefined, defaultValue: string | string[] | undefined } => {
const setting = configProvider
.getConfiguration(key.substr(0, key.lastIndexOf('.')))
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
@ -91,11 +91,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
private _apiInspectConfigToPlain<T>(
config: { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined
): { user: T | undefined, value: T | undefined, default: T | undefined } {
): { userValue: T | undefined, value: T | undefined, defaultValue: T | undefined } {
return {
user: config ? config.globalValue : undefined,
userValue: config ? config.globalValue : undefined,
value: config ? config.workspaceValue : undefined,
default: config ? config.defaultValue : undefined,
defaultValue: config ? config.defaultValue : undefined,
};
}

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

@ -32,6 +32,7 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
private registerListeners(): void {
this._register(this.hostService.onDidChangeFocus(focused => this.onWindowFocusChange(focused)));
this._register(this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange()));
this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(() => this.onAutoSaveConfigurationChange()));
}
private onWindowFocusChange(focused: boolean): void {
@ -83,4 +84,25 @@ export class EditorAutoSave extends Disposable implements IWorkbenchContribution
}
}
}
private onAutoSaveConfigurationChange(): void {
let reason: SaveReason | undefined = undefined;
switch (this.filesConfigurationService.getAutoSaveMode()) {
case AutoSaveMode.ON_FOCUS_CHANGE:
reason = SaveReason.FOCUS_CHANGE;
break;
case AutoSaveMode.ON_WINDOW_CHANGE:
reason = SaveReason.WINDOW_CHANGE;
break;
case AutoSaveMode.AFTER_SHORT_DELAY:
case AutoSaveMode.AFTER_LONG_DELAY:
reason = SaveReason.AUTO;
break;
}
// Trigger a save-all when auto save is enabled
if (reason) {
this.editorService.saveAll({ reason });
}
}
}

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

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

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

@ -59,8 +59,9 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
// Update editors from disk changes
this._register(this.fileService.onFileChanges(e => this.onFileChanges(e)));
// Open editors from dirty text file models
this._register(this.textFileService.models.onModelsDirty(e => this.onTextFilesDirty(e)));
// Ensure dirty text file 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)));
// Out of workspace file watchers
this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange()));
@ -263,29 +264,19 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut
//#endregion
//#region Text File Dirty: Ensure every dirty text file is opened in an editor
//#region Text File: Ensure every dirty text file is opened in an editor
private onTextFilesDirty(events: ReadonlyArray<TextFileModelChangeEvent>): void {
// If files become dirty but are not opened, we open it in the background unless there are pending to be saved
this.doOpenDirtyResourcesInBackground(distinct(events.filter(({ resource }) => {
// Only dirty models that are not PENDING_SAVE
private ensureDirtyTextFilesAreOpened(events: ReadonlyArray<TextFileModelChangeEvent>): void {
this.editorService.openEditors(distinct(events.filter(({ resource }) => {
const model = this.textFileService.models.get(resource);
const shouldOpen = model?.isDirty() && !model.hasState(ModelState.PENDING_SAVE);
// Only if not open already
return shouldOpen && !this.editorService.isOpen({ resource });
}).map(event => event.resource), resource => resource.toString()));
}
private doOpenDirtyResourcesInBackground(resources: URI[]): void {
this.editorService.openEditors(resources.map(resource => {
return {
resource,
options: { inactive: true, pinned: true, preserveFocus: true }
};
}));
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 => ({
resource,
options: { inactive: true, pinned: true, preserveFocus: true }
})));
}
//#endregion

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

@ -226,7 +226,8 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer<ITunnelGrou
}
private renderTunnel(node: ITunnelItem, templateData: ITunnelTemplateData) {
templateData.iconLabel.setLabel(node.label, node.description, { title: node.label + ' - ' + node.description, extraClasses: ['tunnel-view-label'] });
const label = node.label + (node.description ? (' - ' + node.description) : '');
templateData.iconLabel.setLabel(node.label, node.description, { title: label, extraClasses: ['tunnel-view-label'] });
templateData.actionBar.context = node;
const contextKeyService = this._register(this.contextKeyService.createScoped());
contextKeyService.createKey('view', this.viewId);
@ -355,10 +356,14 @@ class TunnelItem implements ITunnelItem {
get label(): string {
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);
} else if (this.localAddress) {
return nls.localize('remote.tunnelsView.forwardedPortLabel2', "{0} to {1}", this.remotePort, this.localAddress);
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} to {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 {
return nls.localize('remote.tunnelsView.forwardedPortLabel3', "{0} not forwarded", this.remotePort);
return nls.localize('remote.tunnelsView.forwardedPortLabel5', "{0} not forwarded", this.remotePort);
}
}

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

@ -41,7 +41,6 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi
import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { coalesce } from 'vs/base/common/arrays';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { isEqual } from 'vs/base/common/resources';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenubarControl } from '../browser/parts/titlebar/menubarControl';
@ -98,7 +97,6 @@ export class ElectronWindow extends Disposable {
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@ITextFileService private readonly textFileService: ITextFileService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IOpenerService private readonly openerService: IOpenerService,
@IElectronService private readonly electronService: IElectronService,
@ -626,7 +624,7 @@ export class ElectronWindow extends Disposable {
// to close the editor while the save still continues in the background. As such
// we have to also check if the files to wait for are dirty and if so wait
// for them to get saved before deleting the wait marker file.
const dirtyFilesToWait = this.textFileService.getDirty(resourcesToWaitFor);
const dirtyFilesToWait = resourcesToWaitFor.filter(resourceToWaitFor => this.workingCopyService.isDirty(resourceToWaitFor));
if (dirtyFilesToWait.length > 0) {
await Promise.all(dirtyFilesToWait.map(async dirtyFileToWait => await this.joinResourceSaved(dirtyFileToWait)));
}
@ -641,13 +639,13 @@ export class ElectronWindow extends Disposable {
private joinResourceSaved(resource: URI): Promise<void> {
return new Promise(resolve => {
if (!this.textFileService.isDirty(resource)) {
if (!this.workingCopyService.isDirty(resource)) {
return resolve(); // return early if resource is not dirty
}
// Otherwise resolve promise when resource is saved
const listener = this.textFileService.models.onModelSaved(e => {
if (isEqual(resource, e.resource)) {
const listener = this.workingCopyService.onDidChangeDirty(e => {
if (!e.isDirty() && isEqual(resource, e.resource)) {
listener.dispose();
resolve();

View file

@ -133,6 +133,9 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
@memoize
get settingsResource(): URI { return joinPath(this.userRoamingDataHome, 'settings.json'); }
@memoize
get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); }
@memoize
get settingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.settings.json'); }
@ -235,8 +238,6 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
nodeCachedDataDir?: string;
argvResource!: URI;
disableCrashReporter!: boolean;
driverHandle?: string;

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,

View file

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
export abstract class AbstractTunnelService implements ITunnelService {
_serviceBrand: undefined;
private _onTunnelOpened: Emitter<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;
protected readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>>();
protected _tunnelProvider: ITunnelProvider | undefined;
public constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@ILogService protected readonly logService: ILogService
) { }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
if (!provider) {
return {
dispose: () => { }
};
}
this._tunnelProvider = provider;
return {
dispose: () => {
this._tunnelProvider = undefined;
}
};
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
const promises: Promise<RemoteTunnel>[] = [];
Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value)));
return Promise.all(promises);
}
dispose(): void {
for (const portMap of this._tunnels.values()) {
for (const { value } of portMap.values()) {
value.then(tunnel => tunnel.dispose());
}
portMap.clear();
}
this._tunnels.clear();
}
openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
if (!remoteHost || (remoteHost === '127.0.0.1')) {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
return resolvedTunnel.then(tunnel => {
const newTunnel = this.makeTunnel(tunnel);
if (tunnel.tunnelRemoteHost !== remoteHost || tunnel.tunnelRemotePort !== remotePort) {
this.logService.warn('Created tunnel does not match requirements of requested tunnel. Host or port mismatch.');
}
this._onTunnelOpened.fire(newTunnel);
return newTunnel;
});
}
private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel {
return {
tunnelRemotePort: tunnel.tunnelRemotePort,
tunnelRemoteHost: tunnel.tunnelRemoteHost,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
dispose: () => {
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
const existing = existingHost.get(tunnel.tunnelRemotePort);
if (existing) {
existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing);
}
}
}
};
}
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
if (tunnel.refcount <= 0) {
const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
tunnel.dispose();
this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
});
if (this._tunnels.has(remoteHost)) {
this._tunnels.get(remoteHost)!.delete(remotePort);
}
return disposePromise;
}
}
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> {
const portMap = this._tunnels.get(remoteHost);
if (portMap && portMap.has(remotePort)) {
const value = portMap.get(remotePort)!;
value.refcount = 0;
await this.tryDisposeTunnel(remoteHost, remotePort, value);
}
}
protected addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise<RemoteTunnel>) {
if (!this._tunnels.has(remoteHost)) {
this._tunnels.set(remoteHost, new Map());
}
this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel });
}
protected abstract retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined;
}
export class TunnelService extends AbstractTunnelService {
protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number | undefined): Promise<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) {
++existing.refcount;
return existing.value;
}
if (this._tunnelProvider) {
const tunnel = this._tunnelProvider.forwardPort({ remoteAddress: { host: remoteHost, port: remotePort } });
if (tunnel) {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
}
return undefined;
}
}

View file

@ -5,22 +5,22 @@
import * as net from 'net';
import { Barrier } from 'vs/base/common/async';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import product from 'vs/platform/product/common/product';
import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ITunnelService, RemoteTunnel, ITunnelProvider } from 'vs/platform/remote/common/tunnel';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory';
import { ISignService } from 'vs/platform/sign/common/sign';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILogService } from 'vs/platform/log/common/log';
import { findFreePort } from 'vs/base/node/ports';
import { Event, Emitter } from 'vs/base/common/event';
import { AbstractTunnelService } from 'vs/workbench/services/remote/common/tunnelService';
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort, tunnelLocalPort);
export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort?: number): Promise<RemoteTunnel> {
const tunnel = new NodeRemoteTunnel(options, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort);
return tunnel.waitForReady();
}
@ -28,7 +28,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
public readonly tunnelRemotePort: number;
public tunnelLocalPort!: number;
public tunnelRemoteHost: string = 'localhost';
public tunnelRemoteHost: string;
public localAddress!: string;
private readonly _options: IConnectionOptions;
@ -38,7 +38,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
private readonly _listeningListener: () => void;
private readonly _connectionListener: (socket: net.Socket) => void;
constructor(options: IConnectionOptions, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
constructor(options: IConnectionOptions, tunnelRemoteHost: string, tunnelRemotePort: number, private readonly suggestedLocalPort?: number) {
super();
this._options = options;
this._server = net.createServer();
@ -51,7 +51,7 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
this._server.on('connection', this._connectionListener);
this.tunnelRemotePort = tunnelRemotePort;
this.tunnelRemoteHost = tunnelRemoteHost;
}
public dispose(): void {
@ -98,124 +98,17 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel {
}
}
export class TunnelService implements ITunnelService {
_serviceBrand: undefined;
private _onTunnelOpened: Emitter<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;
private readonly _tunnels = new Map</*host*/ string, Map</* port */ number, { refcount: number, readonly value: Promise<RemoteTunnel> }>>();
private _tunnelProvider: ITunnelProvider | undefined;
export class TunnelService extends AbstractTunnelService {
public constructor(
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@ILogService logService: ILogService,
@IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService,
@ISignService private readonly signService: ISignService,
@ILogService private readonly logService: ILogService,
) { }
setTunnelProvider(provider: ITunnelProvider | undefined): IDisposable {
if (!provider) {
return {
dispose: () => { }
};
}
this._tunnelProvider = provider;
return {
dispose: () => {
this._tunnelProvider = undefined;
}
};
) {
super(environmentService, logService);
}
public get tunnels(): Promise<readonly RemoteTunnel[]> {
const promises: Promise<RemoteTunnel>[] = [];
Array.from(this._tunnels.values()).forEach(portMap => Array.from(portMap.values()).forEach(x => promises.push(x.value)));
return Promise.all(promises);
}
dispose(): void {
for (const portMap of this._tunnels.values()) {
for (const { value } of portMap.values()) {
value.then(tunnel => tunnel.dispose());
}
portMap.clear();
}
this._tunnels.clear();
}
openTunnel(remoteHost: string | undefined, remotePort: number, localPort: number): Promise<RemoteTunnel> | undefined {
const remoteAuthority = this.environmentService.configuration.remoteAuthority;
if (!remoteAuthority) {
return undefined;
}
if (!remoteHost || (remoteHost === '127.0.0.1')) {
remoteHost = 'localhost';
}
const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remoteHost, remotePort, localPort);
if (!resolvedTunnel) {
return resolvedTunnel;
}
return resolvedTunnel.then(tunnel => {
const newTunnel = this.makeTunnel(tunnel);
this._onTunnelOpened.fire(newTunnel);
return newTunnel;
});
}
private makeTunnel(tunnel: RemoteTunnel): RemoteTunnel {
return {
tunnelRemotePort: tunnel.tunnelRemotePort,
tunnelRemoteHost: tunnel.tunnelRemoteHost,
tunnelLocalPort: tunnel.tunnelLocalPort,
localAddress: tunnel.localAddress,
dispose: () => {
const existingHost = this._tunnels.get(tunnel.tunnelRemoteHost);
if (existingHost) {
const existing = existingHost.get(tunnel.tunnelRemotePort);
if (existing) {
existing.refcount--;
this.tryDisposeTunnel(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort, existing);
}
}
}
};
}
private async tryDisposeTunnel(remoteHost: string, remotePort: number, tunnel: { refcount: number, readonly value: Promise<RemoteTunnel> }): Promise<void> {
if (tunnel.refcount <= 0) {
const disposePromise: Promise<void> = tunnel.value.then(tunnel => {
tunnel.dispose();
this._onTunnelClosed.fire({ host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort });
});
if (this._tunnels.has(remoteHost)) {
this._tunnels.get(remoteHost)!.delete(remotePort);
}
return disposePromise;
}
}
async closeTunnel(remoteHost: string, remotePort: number): Promise<void> {
const portMap = this._tunnels.get(remoteHost);
if (portMap && portMap.has(remotePort)) {
const value = portMap.get(remotePort)!;
value.refcount = 0;
await this.tryDisposeTunnel(remoteHost, remotePort, value);
}
}
private addTunnelToMap(remoteHost: string, remotePort: number, tunnel: Promise<RemoteTunnel>) {
if (!this._tunnels.has(remoteHost)) {
this._tunnels.set(remoteHost, new Map());
}
this._tunnels.get(remoteHost)!.set(remotePort, { refcount: 1, value: tunnel });
}
private retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
protected retainOrCreateTunnel(remoteAuthority: string, remoteHost: string, remotePort: number, localPort?: number): Promise<RemoteTunnel> | undefined {
const portMap = this._tunnels.get(remoteHost);
const existing = portMap ? portMap.get(remotePort) : undefined;
if (existing) {
@ -229,7 +122,7 @@ export class TunnelService implements ITunnelService {
this.addTunnelToMap(remoteHost, remotePort, tunnel);
}
return tunnel;
} else if (remoteHost === 'localhost') {
} else {
const options: IConnectionOptions = {
commit: product.commit,
socketFactory: nodeSocketFactory,
@ -243,11 +136,10 @@ export class TunnelService implements ITunnelService {
logService: this.logService
};
const tunnel = createRemoteTunnel(options, remotePort, localPort);
const tunnel = createRemoteTunnel(options, remoteHost, remotePort, localPort);
this.addTunnelToMap(remoteHost, remotePort, tunnel);
return tunnel;
}
return undefined;
}
}

View file

@ -92,17 +92,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// Lifecycle
this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason)));
this.lifecycleService.onShutdown(this.dispose, this);
// Auto save changes
this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(() => this.onAutoSaveConfigurationChange()));
}
private onAutoSaveConfigurationChange(): void {
// save all dirty when enabling auto save
if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) {
this.saveAll();
}
}
protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {

View file

@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, ISettingsSyncService, IConflictSetting } from 'vs/platform/userDataSync/common/userDataSync';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class SettingsSyncService extends Disposable implements ISettingsSyncService {
_serviceBrand: undefined;
private readonly channel: IChannel;
private _status: SyncStatus = SyncStatus.Uninitialized;
get status(): SyncStatus { return this._status; }
private _onDidChangeStatus: Emitter<SyncStatus> = this._register(new Emitter<SyncStatus>());
readonly onDidChangeStatus: Event<SyncStatus> = this._onDidChangeStatus.event;
get onDidChangeLocal(): Event<void> { return this.channel.listen('onDidChangeLocal'); }
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService
) {
super();
this.channel = sharedProcessService.getChannel('settingsSync');
this.channel.call<SyncStatus>('_getInitialStatus').then(status => {
this.updateStatus(status);
this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
});
}
sync(_continue?: boolean): Promise<boolean> {
return this.channel.call('sync', [_continue]);
}
stop(): void {
this.channel.call('stop');
}
getConflicts(): Promise<IConflictSetting[]> {
return this.channel.call<IConflictSetting[]>('getConflicts');
}
resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise<void> {
return this.channel.call('resolveConflicts', [conflicts]);
}
private async updateStatus(status: SyncStatus): Promise<void> {
this._status = status;
this._onDidChangeStatus.fire(status);
}
}
registerSingleton(ISettingsSyncService, SettingsSyncService);

View file

@ -95,8 +95,6 @@ import { find } from 'vs/base/common/arrays';
import { WorkingCopyService, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IFilesConfigurationService, FilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { IDebugService } from 'vs/workbench/contrib/debug/common/debug';
import { MockDebugService } from 'vs/workbench/contrib/debug/test/common/mockDebug';
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
@ -326,7 +324,6 @@ export function workbenchInstantiationService(): ITestInstantiationService {
instantiationService.stub(ICodeEditorService, new TestCodeEditorService());
instantiationService.stub(IViewletService, new TestViewletService());
instantiationService.stub(IWorkingCopyService, new TestWorkingCopyService());
instantiationService.stub(IDebugService, new MockDebugService());
return instantiationService;
}

View file

@ -50,6 +50,7 @@ import 'vs/workbench/services/url/electron-browser/urlService';
import 'vs/workbench/services/workspaces/electron-browser/workspacesService';
import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService';
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService';
import 'vs/workbench/services/userDataSync/electron-browser/settingsSyncService';
import 'vs/workbench/services/authToken/electron-browser/authTokenService';
import 'vs/workbench/services/host/electron-browser/desktopHostService';
import 'vs/workbench/services/request/electron-browser/requestService';

View file

@ -34,6 +34,26 @@ interface IExternalUriResolver {
(uri: URI): Promise<URI>;
}
interface TunnelOptions {
remoteAddress: { port: number, host: string };
// The desired local port. If this port can't be used, then another will be chosen.
localAddressPort?: number;
label?: string;
}
interface Tunnel {
remoteAddress: { port: number, host: string };
//The complete local address(ex. localhost:1234)
localAddress: string;
// Implementers of Tunnel should fire onDidDispose when dispose is called.
onDidDispose: Event<void>;
dispose(): void;
}
interface ITunnelFactory {
(tunnelOptions: TunnelOptions): Thenable<Tunnel> | undefined;
}
interface IWorkbenchConstructionOptions {
/**
@ -104,6 +124,11 @@ interface IWorkbenchConstructionOptions {
*/
readonly resolveExternalUri?: IExternalUriResolver;
/**
* Support for creating tunnels.
*/
readonly tunnelFactory?: ITunnelFactory;
/**
* Current logging level. Default is `LogLevel.Info`.
*/

View file

@ -60,25 +60,27 @@ import { BackupFileService } from 'vs/workbench/services/backup/common/backupFil
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService';
import { TunnelService } from 'vs/workbench/services/remote/common/tunnelService';
import { ILoggerService } from 'vs/platform/log/common/log';
import { FileLoggerService } from 'vs/platform/log/common/fileLogService';
import { IAuthTokenService } from 'vs/platform/auth/common/auth';
import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService';
import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService, ISettingsSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
registerSingleton(IExtensionManagementService, ExtensionManagementService);
registerSingleton(IBackupFileService, BackupFileService);
registerSingleton(IAccessibilityService, BrowserAccessibilityService, true);
registerSingleton(IContextMenuService, ContextMenuService);
registerSingleton(ITunnelService, NoOpTunnelService, true);
registerSingleton(ITunnelService, TunnelService, true);
registerSingleton(ILoggerService, FileLoggerService);
registerSingleton(IAuthTokenService, AuthTokenService);
registerSingleton(IUserDataSyncLogService, UserDataSyncLogService);
registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService);
registerSingleton(ISettingsSyncService, SettingsSynchroniser);
registerSingleton(IUserDataSyncService, UserDataSyncService);
//#endregion

View file

@ -9185,10 +9185,10 @@ typescript@^2.6.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=
typescript@^3.8.0-dev.20200104:
version "3.8.0-dev.20200104"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200104.tgz#521b2f0b5a288b6e3f8a095525f64712330cc649"
integrity sha512-Zdb8X1uzvUPrRvRBqega83NxqCuN/kyxuXG1u8BV10mGOqfwQb0SreSDoDDM1zUgrqFZ93neVh3DVyWTvx6XlA==
typescript@^3.8.0-dev.20200108:
version "3.8.0-dev.20200108"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.0-dev.20200108.tgz#ca3a4d950cd19112d80758be779fb07d577e49bc"
integrity sha512-SD3VEYUUrDGc0djorpi0zVdmVwmvuaSHta18WP3sS9X0HC7eA4izdjj07pVUc99IBpBw55ljUATm5vkNdvxX6w==
uc.micro@^1.0.1, uc.micro@^1.0.3:
version "1.0.3"