Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781 (#8649)

* Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781

* distro

* fix tests
This commit is contained in:
Anthony Dresser
2019-12-11 22:42:23 -08:00
committed by GitHub
parent 82974a2135
commit 4ba6a979ba
280 changed files with 10898 additions and 14231 deletions

View File

@@ -47,7 +47,7 @@
"service-downloader": "github:anthonydresser/service-downloader#0.1.7", "service-downloader": "github:anthonydresser/service-downloader#0.1.7",
"terser": "4.3.8", "terser": "4.3.8",
"tslint": "^5.9.1", "tslint": "^5.9.1",
"typescript": "3.7.2", "typescript": "3.7.3",
"vsce": "1.48.0", "vsce": "1.48.0",
"vscode-telemetry-extractor": "^1.5.4", "vscode-telemetry-extractor": "^1.5.4",
"xml2js": "^0.4.17" "xml2js": "^0.4.17"

View File

@@ -3806,10 +3806,10 @@ typed-rest-client@^0.9.0:
tunnel "0.0.4" tunnel "0.0.4"
underscore "1.8.3" underscore "1.8.3"
typescript@3.7.2: typescript@3.7.3:
version "3.7.2" version "3.7.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69"
integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==
typescript@^3.0.1: typescript@^3.0.1:
version "3.5.3" version "3.5.3"

View File

@@ -392,7 +392,7 @@ export class Model {
if (hint instanceof Uri) { if (hint instanceof Uri) {
let resourcePath: string; let resourcePath: string;
if (hint.scheme === 'git') { if (hint.scheme === 'git' || hint.scheme === 'gitfs') {
resourcePath = fromGitUri(hint).path; resourcePath = fromGitUri(hint).path;
} else { } else {
resourcePath = hint.fsPath; resourcePath = hint.fsPath;

View File

@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.", "If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request." "Once accepted there, we are happy to receive an update request."
], ],
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/e3091a421bdcad527018c897652ded47585cbd12", "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/8fbbc11a6bb917f287bbe21d0573454020599547",
"name": "Markdown", "name": "Markdown",
"scopeName": "text.html.markdown", "scopeName": "text.html.markdown",
"patterns": [ "patterns": [
@@ -2623,4 +2623,4 @@
"name": "markup.inline.raw.string.markdown" "name": "markup.inline.raw.string.markdown"
} }
} }
} }

View File

@@ -346,7 +346,7 @@
"mocha-junit-reporter": "^1.17.0", "mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7", "mocha-multi-reporters": "^1.1.7",
"ts-loader": "^6.2.1", "ts-loader": "^6.2.1",
"typescript": "^3.7.2", "typescript": "^3.7.3",
"vscode": "^1.1.10", "vscode": "^1.1.10",
"webpack": "^4.41.2", "webpack": "^4.41.2",
"webpack-cli": "^3.3.0" "webpack-cli": "^3.3.0"

View File

@@ -4528,10 +4528,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript@^3.7.2: typescript@^3.7.3:
version "3.7.2" version "3.7.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69"
integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==
uc.micro@^1.0.1: uc.micro@^1.0.1:
version "1.0.3" version "1.0.3"

View File

@@ -16,6 +16,7 @@
"*" "*"
], ],
"scripts": { "scripts": {
"generate-grammar": "node ./syntaxes/generateTMLanguage.js",
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json" "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json"
}, },
"contributes": { "contributes": {

View File

@@ -12,9 +12,27 @@ const SEARCH_RESULT_SELECTOR = { language: 'search-result' };
const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:']; const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:'];
const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']; const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'];
let cachedLastParse: { version: number, parse: ParsedSearchResults } | undefined; let cachedLastParse: { version: number, parse: ParsedSearchResults, uri: vscode.Uri } | undefined;
let documentChangeListener: vscode.Disposable | undefined;
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
const contextLineDecorations = vscode.window.createTextEditorDecorationType({ opacity: '0.7' });
const matchLineDecorations = vscode.window.createTextEditorDecorationType({ fontWeight: 'bold' });
const decorate = (editor: vscode.TextEditor) => {
const parsed = parseSearchResults(editor.document).filter(isResultLine);
const contextRanges = parsed.filter(line => line.isContext).map(line => line.prefixRange);
const matchRanges = parsed.filter(line => !line.isContext).map(line => line.prefixRange);
editor.setDecorations(contextLineDecorations, contextRanges);
editor.setDecorations(matchLineDecorations, matchRanges);
};
if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.languageId === 'search-result') {
decorate(vscode.window.activeTextEditor);
}
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')), vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')),
vscode.commands.registerCommand('searchResult.rerunSearchWithContext', () => vscode.commands.executeCommand('search.action.rerunEditorSearchWithContext')), vscode.commands.registerCommand('searchResult.rerunSearchWithContext', () => vscode.commands.executeCommand('search.action.rerunEditorSearchWithContext')),
@@ -84,15 +102,24 @@ export function activate(context: vscode.ExtensionContext) {
} }
}), }),
vscode.window.onDidChangeActiveTextEditor(e => { vscode.window.onDidChangeActiveTextEditor(editor => {
if (e?.document.languageId === 'search-result') { if (editor?.document.languageId === 'search-result') {
// Clear the parse whenever we open a new editor. // Clear the parse whenever we open a new editor.
// Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast. // Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast.
cachedLastParse = undefined; cachedLastParse = undefined;
documentChangeListener?.dispose();
documentChangeListener = vscode.workspace.onDidChangeTextDocument(doc => {
if (doc.document.uri === editor.document.uri) {
decorate(editor);
}
});
decorate(editor);
} }
}), }),
{ dispose() { cachedLastParse = undefined; } } { dispose() { cachedLastParse = undefined; documentChangeListener?.dispose(); } }
); );
} }
@@ -129,14 +156,15 @@ function relativePathToUri(path: string, resultsUri: vscode.Uri): vscode.Uri | u
} }
type ParsedSearchFileLine = { type: 'file', location: vscode.LocationLink, allLocations: vscode.LocationLink[], path: string }; type ParsedSearchFileLine = { type: 'file', location: vscode.LocationLink, allLocations: vscode.LocationLink[], path: string };
type ParsedSearchResultLine = { type: 'result', location: vscode.LocationLink }; type ParsedSearchResultLine = { type: 'result', location: vscode.LocationLink, isContext: boolean, prefixRange: vscode.Range };
type ParsedSearchResults = Array<ParsedSearchFileLine | ParsedSearchResultLine>; type ParsedSearchResults = Array<ParsedSearchFileLine | ParsedSearchResultLine>;
const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file'; const isFileLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchFileLine => line.type === 'file';
const isResultLine = (line: ParsedSearchResultLine | ParsedSearchFileLine): line is ParsedSearchResultLine => line.type === 'result';
function parseSearchResults(document: vscode.TextDocument, token: vscode.CancellationToken): ParsedSearchResults { function parseSearchResults(document: vscode.TextDocument, token?: vscode.CancellationToken): ParsedSearchResults {
if (cachedLastParse && cachedLastParse.version === document.version) { if (cachedLastParse && cachedLastParse.uri === document.uri && cachedLastParse.version === document.version) {
return cachedLastParse.parse; return cachedLastParse.parse;
} }
@@ -147,7 +175,8 @@ function parseSearchResults(document: vscode.TextDocument, token: vscode.Cancell
let currentTargetLocations: vscode.LocationLink[] | undefined = undefined; let currentTargetLocations: vscode.LocationLink[] | undefined = undefined;
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
if (token.isCancellationRequested) { return []; } // TODO: This is probably always false, given we're pegging the thread...
if (token?.isCancellationRequested) { return []; }
const line = lines[i]; const line = lines[i];
const fileLine = FILE_LINE_REGEX.exec(line); const fileLine = FILE_LINE_REGEX.exec(line);
@@ -186,13 +215,14 @@ function parseSearchResults(document: vscode.TextDocument, token: vscode.Cancell
currentTargetLocations?.push(location); currentTargetLocations?.push(location);
links[i] = { type: 'result', location }; links[i] = { type: 'result', location, isContext: seperator === ' ', prefixRange: new vscode.Range(i, 0, i, metadataOffset) };
} }
} }
cachedLastParse = { cachedLastParse = {
version: document.version, version: document.version,
parse: links parse: links,
uri: document.uri
}; };
return links; return links;

View File

@@ -0,0 +1,243 @@
// @ts-check
const mappings = [
['bat', 'source.batchfile'],
['c', 'source.c'],
['cc', 'source.cpp'],
['clj', 'source.clojure'],
['coffee', 'source.coffee'],
['cpp', 'source.cpp'],
['cs', 'source.cs'],
['cshtml', 'text.html.cshtml'],
['css', 'source.css'],
['dart', 'source.dart'],
['diff', 'source.diff'],
['dockerfile', 'source.dockerfile', '(?:dockerfile|Dockerfile)'],
['fs', 'source.fsharp'],
['go', 'source.go'],
['groovy', 'source.groovy'],
['h', 'source.objc'],
['handlebars', 'text.html.handlebars'],
['hbs', 'text.html.handlebars'],
['hlsl', 'source.hlsl'],
['hpp', 'source.objcpp'],
['html', 'text.html.basic'],
['ini', 'source.ini'],
['java', 'source.java'],
['js', 'source.js'],
['json', 'source.json.comments'],
['jsx', 'source.js.jsx'],
['less', 'source.css.less'],
['log', 'text.log'],
['lua', 'source.lua'],
['m', 'source.objc'],
['makefile', 'source.makefile', '(?:makefile|Makefile)(?:\\..*)?'],
['md', 'text.html.markdown'],
['mm', 'source.objcpp'],
['p6', 'source.perl.6'],
['perl', 'source.perl'],
['php', 'source.php'],
['pl', 'source.perl'],
['ps1', 'source.powershell'],
['pug', 'text.pug'],
['py', 'source.python'],
['r', 'source.r'],
['rb', 'source.ruby'],
['rs', 'source.rust'],
['scala', 'source.scala'],
['scss', 'source.css.scss'],
['sh', 'source.shell'],
['sql', 'source.sql'],
['swift', 'source.swift'],
['ts', 'source.ts'],
['tsx', 'source.tsx'],
['vb', 'source.asp.vb.net'],
['xml', 'text.xml'],
['yaml', 'source.yaml'],
];
const scopes = {
root: 'text.searchResult',
header: {
meta: 'meta.header.search keyword.operator.word.search',
key: 'entity.other.attribute-name',
value: 'entity.other.attribute-value string.unquoted',
flags: {
keyword: 'keyword.other',
},
contextLines: {
number: 'constant.numeric.integer',
invalid: 'invalid.illegal',
},
query: {
escape: 'constant.character.escape',
invalid: 'invalid.illegal',
}
},
resultBlock: {
meta: 'meta.resultBlock.search',
path: {
meta: 'string meta.path.search',
dirname: 'meta.path.dirname.search',
basename: 'meta.path.basename.search',
colon: 'punctuation.separator',
},
result: {
meta: 'meta.resultLine.search',
metaSingleLine: 'meta.resultLine.singleLine.search',
metaMultiLine: 'meta.resultLine.multiLine.search',
prefix: {
meta: 'constant.numeric.integer meta.resultLinePrefix.search',
metaContext: 'meta.resultLinePrefix.contextLinePrefix.search',
metaMatch: 'meta.resultLinePrefix.matchLinePrefix.search',
lineNumber: 'meta.resultLinePrefix.lineNumber.search',
colon: 'punctuation.separator',
}
}
}
};
const repository = {};
mappings.forEach(([ext, scope, regexp]) =>
repository[ext] = {
name: scopes.resultBlock.meta,
begin: `^(?!\\s)(.*?)([^\\\\\\/\\n]*${regexp || `\\.${ext}`})(:)$`,
end: "^(?!\\s)",
beginCaptures: {
"0": { name: scopes.resultBlock.path.meta },
"1": { name: scopes.resultBlock.path.dirname },
"2": { name: scopes.resultBlock.path.basename },
"3": { name: scopes.resultBlock.path.colon },
},
patterns: [
{
name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaMultiLine].join(' '),
begin: "^ ((\\d+) )",
while: "^ ((\\d+)(:))|((\\d+) )",
beginCaptures: {
"0": { name: scopes.resultBlock.result.prefix.meta },
"1": { name: scopes.resultBlock.result.prefix.metaContext },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
},
whileCaptures: {
"0": { name: scopes.resultBlock.result.prefix.meta },
"1": { name: scopes.resultBlock.result.prefix.metaMatch },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
"3": { name: scopes.resultBlock.result.prefix.colon },
"4": { name: scopes.resultBlock.result.prefix.metaContext },
"5": { name: scopes.resultBlock.result.prefix.lineNumber },
},
patterns: [{ include: scope }]
},
{
begin: "^ ((\\d+)(:))",
while: "(?=not)possible",
name: [scopes.resultBlock.result.meta, scopes.resultBlock.result.metaSingleLine].join(' '),
beginCaptures: {
"0": { name: scopes.resultBlock.result.prefix.meta },
"1": { name: scopes.resultBlock.result.prefix.metaMatch },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
"3": { name: scopes.resultBlock.result.prefix.colon },
},
patterns: [{ include: scope }]
}
]
});
const header = [
{
begin: "^(# Query): ",
end: "\n",
name: scopes.header.meta,
beginCaptures: { "1": { name: scopes.header.key }, },
patterns: [
{
match: '(\\\\n)|(\\\\\\\\)',
name: [scopes.header.value, scopes.header.query.escape].join(' ')
},
{
match: '\\\\.|\\\\$',
name: [scopes.header.value, scopes.header.query.invalid].join(' ')
},
{
match: '[^\\\\\\\n]+',
name: [scopes.header.value].join(' ')
},
]
},
{
begin: "^(# Flags): ",
end: "\n",
name: scopes.header.meta,
beginCaptures: { "1": { name: scopes.header.key }, },
patterns: [
{
match: '(RegExp|CaseSensitive|IgnoreExcludeSettings|WordMatch)',
name: [scopes.header.value, 'keyword.other'].join(' ')
},
{ match: '.' },
]
},
{
begin: "^(# ContextLines): ",
end: "\n",
name: scopes.header.meta,
beginCaptures: { "1": { name: scopes.header.key }, },
patterns: [
{
match: '\\d',
name: [scopes.header.value, scopes.header.contextLines.number].join(' ')
},
{ match: '.', name: scopes.header.contextLines.invalid },
]
},
{
match: "^(# (?:Including|Excluding)): (.*)$",
name: scopes.header.meta,
captures: {
"1": { name: scopes.header.key },
"2": { name: scopes.header.value }
}
},
];
const plainText = [
{
match: "^(?!\\s)(.*?)([^\\\\\\/\\n]*)(:)$",
name: [scopes.resultBlock.meta, scopes.resultBlock.path.meta].join(' '),
captures: {
"1": { name: scopes.resultBlock.path.dirname },
"2": { name: scopes.resultBlock.path.basename },
"3": { name: scopes.resultBlock.path.colon }
}
},
{
match: "^ ((\\d+)(:))|((\\d+)( ))(.*)",
name: [scopes.resultBlock.meta, scopes.resultBlock.result.meta].join(' '),
captures: {
"1": { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaMatch].join(' ') },
"2": { name: scopes.resultBlock.result.prefix.lineNumber },
"3": { name: scopes.resultBlock.result.prefix.colon },
"4": { name: [scopes.resultBlock.result.prefix.meta, scopes.resultBlock.result.prefix.metaContext].join(' ') },
"5": { name: scopes.resultBlock.result.prefix.lineNumber },
}
}
];
const tmLanguage = {
"information_for_contributors": "This file is generated from ./generateTMLanguage.js.",
name: "Search Results",
scopeName: scopes.root,
patterns: [
...header,
...mappings.map(([ext]) => ({ include: `#${ext}` })),
...plainText
],
repository
};
require('fs').writeFileSync(
require('path').join(__dirname, './searchResult.tmLanguage.json'),
JSON.stringify(tmLanguage, null, 2));

File diff suppressed because it is too large Load Diff

View File

@@ -114,6 +114,13 @@
"settings": { "settings": {
"foreground": "#CE9178" "foreground": "#CE9178"
} }
},
{
"name": "HC Search Editor context line override",
"scope": "meta.resultLinePrefix.contextLinePrefix.search",
"settings": {
"foreground": "#CBEDCB",
}
} }
] ]
} }

View File

@@ -1,7 +1,7 @@
{ {
"name": "azuredatastudio", "name": "azuredatastudio",
"version": "1.14.0", "version": "1.14.0",
"distro": "0fd359ecaf4b9ee6948b976a69a36a2179deaefd", "distro": "70b195f4ddcfa2e6540928240456ed949ffb6ae0",
"author": { "author": {
"name": "Microsoft Corporation" "name": "Microsoft Corporation"
}, },
@@ -65,7 +65,7 @@
"reflect-metadata": "^0.1.8", "reflect-metadata": "^0.1.8",
"rxjs": "5.4.0", "rxjs": "5.4.0",
"sanitize-html": "^1.19.1", "sanitize-html": "^1.19.1",
"semver-umd": "^5.5.3", "semver-umd": "^5.5.5",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.32", "slickgrid": "github:anthonydresser/SlickGrid#2.3.32",
"spdlog": "^0.11.1", "spdlog": "^0.11.1",
"sudo-prompt": "9.1.1", "sudo-prompt": "9.1.1",
@@ -99,7 +99,6 @@
"@types/node": "^10.12.12", "@types/node": "^10.12.12",
"@types/plotly.js": "^1.44.9", "@types/plotly.js": "^1.44.9",
"@types/sanitize-html": "^1.18.2", "@types/sanitize-html": "^1.18.2",
"@types/semver": "^5.5.0",
"@types/sinon": "^1.16.36", "@types/sinon": "^1.16.36",
"@types/webpack": "^4.4.10", "@types/webpack": "^4.4.10",
"@types/windows-foreground-love": "^0.3.0", "@types/windows-foreground-love": "^0.3.0",
@@ -114,6 +113,7 @@
"coveralls": "^2.11.11", "coveralls": "^2.11.11",
"cson-parser": "^1.3.3", "cson-parser": "^1.3.3",
"debounce": "^1.0.0", "debounce": "^1.0.0",
"electron": "6.1.5",
"event-stream": "3.3.4", "event-stream": "3.3.4",
"express": "^4.13.1", "express": "^4.13.1",
"fancy-log": "^1.3.3", "fancy-log": "^1.3.3",
@@ -169,7 +169,7 @@
"tslint": "^5.16.0", "tslint": "^5.16.0",
"tslint-microsoft-contrib": "^6.0.0", "tslint-microsoft-contrib": "^6.0.0",
"typemoq": "^0.3.2", "typemoq": "^0.3.2",
"typescript": "3.7.2", "typescript": "3.7.3",
"typescript-formatter": "7.1.0", "typescript-formatter": "7.1.0",
"vinyl": "^2.0.0", "vinyl": "^2.0.0",
"vinyl-fs": "^3.0.0", "vinyl-fs": "^3.0.0",

View File

@@ -13,7 +13,7 @@
"native-watchdog": "1.3.0", "native-watchdog": "1.3.0",
"node-pty": "^0.10.0-beta2", "node-pty": "^0.10.0-beta2",
"onigasm-umd": "2.2.5", "onigasm-umd": "2.2.5",
"semver-umd": "^5.5.3", "semver-umd": "^5.5.5",
"spdlog": "^0.11.1", "spdlog": "^0.11.1",
"vscode-minimist": "^1.2.2", "vscode-minimist": "^1.2.2",
"vscode-nsfw": "1.2.8", "vscode-nsfw": "1.2.8",

View File

@@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"onigasm-umd": "2.2.5", "onigasm-umd": "2.2.5",
"semver-umd": "^5.5.3", "semver-umd": "^5.5.5",
"vscode-textmate": "4.4.0", "vscode-textmate": "4.4.0",
"xterm": "4.3.0-beta.28.vscode.1", "xterm": "4.3.0-beta.28.vscode.1",
"xterm-addon-search": "0.4.0-beta4", "xterm-addon-search": "0.4.0-beta4",

View File

@@ -19,10 +19,10 @@ oniguruma@^7.2.0:
dependencies: dependencies:
nan "^2.14.0" nan "^2.14.0"
semver-umd@^5.5.3: semver-umd@^5.5.5:
version "5.5.3" version "5.5.5"
resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e" resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.5.tgz#a2e4280d0e92a2b27695c18811f0e939e144d86f"
integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw== integrity sha512-8rUq0nnTzlexpAdYmm8UDYsLkBn0MnBkfrGWPmyDBDDzv71dPOH07szOOaLj/5hO3BYmumYwS+wp3C60zLzh5g==
vscode-textmate@4.4.0: vscode-textmate@4.4.0:
version "4.4.0" version "4.4.0"

View File

@@ -317,10 +317,10 @@ readdirp@~3.2.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver-umd@^5.5.3: semver-umd@^5.5.5:
version "5.5.3" version "5.5.5"
resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e" resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.5.tgz#a2e4280d0e92a2b27695c18811f0e939e144d86f"
integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw== integrity sha512-8rUq0nnTzlexpAdYmm8UDYsLkBn0MnBkfrGWPmyDBDDzv71dPOH07szOOaLj/5hO3BYmumYwS+wp3C60zLzh5g==
semver@^5.3.0: semver@^5.3.0:
version "5.6.0" version "5.6.0"

View File

@@ -62,6 +62,17 @@ const server = http.createServer((req, res) => {
// favicon // favicon
return serveFile(req, res, path.join(APP_ROOT, 'resources', 'win32', 'code.ico')); return serveFile(req, res, path.join(APP_ROOT, 'resources', 'win32', 'code.ico'));
} }
if (pathname === '/manifest.json') {
// manifest
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({
"name": "Code Web - OSS",
"short_name": "Code Web - OSS",
"start_url": "/",
"lang": "en-US",
"display": "standalone"
}));
}
if (/^\/static\//.test(pathname)) { if (/^\/static\//.test(pathname)) {
// static requests // static requests
return handleStatic(req, res, parsedUrl); return handleStatic(req, res, parsedUrl);

View File

@@ -33,14 +33,14 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
export class CategoryView extends ViewletPane { export class CategoryView extends ViewPane {
constructor( constructor(
private contentElement: HTMLElement, private contentElement: HTMLElement,
private size: number, private size: number,
options: IViewletPaneOptions, options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService, @IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,

View File

@@ -49,9 +49,9 @@ import { IOEShimService } from 'sql/workbench/contrib/objectExplorer/browser/obj
import { NodeContextKey } from 'sql/workbench/contrib/dataExplorer/browser/nodeContext'; import { NodeContextKey } from 'sql/workbench/contrib/dataExplorer/browser/nodeContext';
import { UserCancelledConnectionError } from 'sql/base/common/errors'; import { UserCancelledConnectionError } from 'sql/base/common/errors';
import { firstIndex } from 'vs/base/common/arrays'; import { firstIndex } from 'vs/base/common/arrays';
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
export class CustomTreeViewPanel extends ViewletPane { export class CustomTreeViewPanel extends ViewPane {
private treeView: ITreeView; private treeView: ITreeView;
@@ -63,7 +63,7 @@ export class CustomTreeViewPanel extends ViewletPane {
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
) { ) {
super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id)); const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
this.treeView = treeView as ITreeView; this.treeView = treeView as ITreeView;
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));

View File

@@ -35,14 +35,14 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
class AccountPanel extends ViewletPane { class AccountPanel extends ViewPane {
public index: number; public index: number;
private accountList: List<azdata.Account>; private accountList: List<azdata.Account>;
constructor( constructor(
private options: IViewletPaneOptions, private options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService, @IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,

View File

@@ -19,9 +19,9 @@ import {
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
export class ConnectionViewletPanel extends ViewletPane { export class ConnectionViewletPanel extends ViewPane {
public static readonly ID = 'dataExplorer.servers'; public static readonly ID = 'dataExplorer.servers';
@@ -40,7 +40,7 @@ export class ConnectionViewletPanel extends ViewletPane {
@IObjectExplorerService private readonly objectExplorerService: IObjectExplorerService, @IObjectExplorerService private readonly objectExplorerService: IObjectExplorerService,
@IContextKeyService contextKeyService: IContextKeyService @IContextKeyService contextKeyService: IContextKeyService
) { ) {
super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService); super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
this._addServerAction = this.instantiationService.createInstance(AddServerAction, this._addServerAction = this.instantiationService.createInstance(AddServerAction,
AddServerAction.ID, AddServerAction.ID,
AddServerAction.LABEL); AddServerAction.LABEL);

View File

@@ -11,7 +11,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -23,10 +23,10 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet'; import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
export const VIEWLET_ID = 'workbench.view.connections'; export const VIEWLET_ID = 'workbench.view.connections';
@@ -72,7 +72,23 @@ export class DataExplorerViewletViewsContribution implements IWorkbenchContribut
} }
} }
export class DataExplorerViewlet extends ViewContainerViewlet { export class DataExplorerViewlet extends Viewlet {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IStorageService protected storageService: IStorageService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService,
@IContextMenuService protected contextMenuService: IContextMenuService,
@IExtensionService protected extensionService: IExtensionService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
@IConfigurationService protected configurationService: IConfigurationService
) {
super(VIEWLET_ID, instantiationService.createInstance(DataExplorerViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService);
}
}
export class DataExplorerViewPaneContainer extends ViewPaneContainer {
private root: HTMLElement; private root: HTMLElement;
private dataSourcesBox: HTMLElement; private dataSourcesBox: HTMLElement;
@@ -90,7 +106,7 @@ export class DataExplorerViewlet extends ViewContainerViewlet {
@IMenuService private menuService: IMenuService, @IMenuService private menuService: IMenuService,
@IContextKeyService private contextKeyService: IContextKeyService @IContextKeyService private contextKeyService: IContextKeyService
) { ) {
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
} }
create(parent: HTMLElement): void { create(parent: HTMLElement): void {
@@ -134,13 +150,13 @@ export class DataExplorerViewlet extends ViewContainerViewlet {
return actions; return actions;
} }
protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] { protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewPane[] {
const addedViews = super.onDidAddViews(added); const addedViews = super.onDidAddViews(added);
return addedViews; return addedViews;
} }
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane { protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane {
let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPane; let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewPane;
this._register(viewletPanel); this._register(viewletPanel);
return viewletPanel; return viewletPanel;
} }

View File

@@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as Platform from 'vs/platform/registry/common/platform';
import { ViewletDescriptor, Extensions, Viewlet, ViewletRegistry } from 'vs/workbench/browser/viewlet';
import * as Types from 'vs/base/common/types';
suite('Data Explorer Viewlet', () => {
class DataExplorerTestViewlet extends Viewlet {
constructor() {
super('dataExplorer', null, null, null, null, null);
}
public layout(dimension: any): void {
throw new Error('Method not implemented.');
}
}
test('ViewletDescriptor API', function () {
let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 1);
assert.strictEqual(d.id, 'id');
assert.strictEqual(d.name, 'name');
assert.strictEqual(d.cssClass, 'class');
assert.strictEqual(d.order, 1);
});
test('Editor Aware ViewletDescriptor API', function () {
let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
assert.strictEqual(d.id, 'id');
assert.strictEqual(d.name, 'name');
d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
assert.strictEqual(d.id, 'id');
assert.strictEqual(d.name, 'name');
});
test('Data Explorer Viewlet extension point and registration', function () {
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).registerViewlet));
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet));
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets));
let oldCount = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets().length;
let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name');
Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).registerViewlet(d);
let retrieved = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet('dataExplorer-test-id');
assert(d === retrieved);
let newCount = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets().length;
assert.equal(oldCount + 1, newCount);
});
});

View File

@@ -6,10 +6,11 @@
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { PagedModel } from 'vs/base/common/paging'; import { PagedModel } from 'vs/base/common/paging';
import { ExtensionsViewlet, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
function getScenarioID(scenarioType: string) { function getScenarioID(scenarioType: string) {
return 'workbench.extensions.action.show' + scenarioType; return 'workbench.extensions.action.show' + scenarioType;
@@ -25,9 +26,8 @@ export class ShowRecommendedExtensionsByScenarioAction extends Action {
run(): Promise<void> { run(): Promise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true) return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet) .then((viewlet: ExtensionsViewlet) => {
.then(viewlet => { (viewlet.getViewPaneContainer() as ExtensionsViewPaneContainer).search('@' + this.scenarioType);
viewlet.search('@' + this.scenarioType);
viewlet.focus(); viewlet.focus();
}); });
} }
@@ -51,9 +51,8 @@ export class InstallRecommendedExtensionsByScenarioAction extends Action {
run(): Promise<any> { run(): Promise<any> {
if (!this.recommendations.length) { return Promise.resolve(); } if (!this.recommendations.length) { return Promise.resolve(); }
return this.viewletService.openViewlet(VIEWLET_ID, true) return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet) .then((viewlet: ExtensionsViewlet) => {
.then(viewlet => { (viewlet.getViewPaneContainer() as ExtensionsViewPaneContainer).search('@' + this.scenarioType);
viewlet.search('@' + this.scenarioType);
viewlet.focus(); viewlet.focus();
const names = this.recommendations.map(({ extensionId }) => extensionId); const names = this.recommendations.map(({ extensionId }) => extensionId);
return this.extensionWorkbenchService.queryGallery({ names, source: 'install-' + this.scenarioType }, CancellationToken.None).then(pager => { return this.extensionWorkbenchService.queryGallery({ names, source: 'install-' + this.scenarioType }, CancellationToken.None).then(pager => {

View File

@@ -39,7 +39,7 @@ import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHos
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { CellMagicMapper } from 'sql/workbench/contrib/notebook/browser/models/cellMagicMapper'; import { CellMagicMapper } from 'sql/workbench/contrib/notebook/browser/models/cellMagicMapper';
import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { CellModel } from 'sql/workbench/contrib/notebook/browser/models/cell'; import { CellModel } from 'sql/workbench/contrib/notebook/browser/models/cell';
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
import { isValidBasename } from 'vs/base/common/extpath'; import { isValidBasename } from 'vs/base/common/extpath';
@@ -55,6 +55,7 @@ import { isUndefinedOrNull } from 'vs/base/common/types';
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams'; import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
import { getErrorMessage } from 'vs/base/common/errors'; import { getErrorMessage } from 'vs/base/common/errors';
import { find, firstIndex } from 'vs/base/common/arrays'; import { find, firstIndex } from 'vs/base/common/arrays';
import { ExtensionsViewlet, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
export const NOTEBOOK_SELECTOR: string = 'notebook-component'; export const NOTEBOOK_SELECTOR: string = 'notebook-component';
@@ -343,8 +344,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
private async openExtensionGallery(): Promise<void> { private async openExtensionGallery(): Promise<void> {
try { try {
let viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true) as IExtensionsViewlet; let viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true) as ExtensionsViewlet;
viewlet.search('sql-vnext'); (viewlet.getViewPaneContainer() as ExtensionsViewPaneContainer).search('sql-vnext');
viewlet.focus(); viewlet.focus();
} catch (error) { } catch (error) {
this.notificationService.error(error.message); this.notificationService.error(error.message);

View File

@@ -21,10 +21,11 @@ import { getBaseLabel } from 'vs/base/common/labels';
import { ShowFileInFolderAction, OpenFileInFolderAction } from 'sql/workbench/common/workspaceActions'; import { ShowFileInFolderAction, OpenFileInFolderAction } from 'sql/workbench/common/workspaceActions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/common/pathUtilities'; import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/common/pathUtilities';
import { IOutputService, IOutputChannelRegistry, IOutputChannel, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs'; import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
let prevSavePath: string; let prevSavePath: string;

View File

@@ -41,14 +41,14 @@ import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRe
import { TaskRegistry } from 'sql/platform/tasks/browser/tasksRegistry'; import { TaskRegistry } from 'sql/platform/tasks/browser/tasksRegistry';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
const labelDisplay = nls.localize("insights.item", "Item"); const labelDisplay = nls.localize("insights.item", "Item");
const valueDisplay = nls.localize("insights.value", "Value"); const valueDisplay = nls.localize("insights.value", "Value");
const iconClass = 'codicon'; const iconClass = 'codicon';
class InsightTableView<T> extends ViewletPane { class InsightTableView<T> extends ViewPane {
private _table: Table<T>; private _table: Table<T>;
public get table(): Table<T> { public get table(): Table<T> {
return this._table; return this._table;
@@ -58,7 +58,7 @@ class InsightTableView<T> extends ViewletPane {
private columns: Slick.Column<T>[], private columns: Slick.Column<T>[],
private data: IDisposableDataProvider<T> | Array<T>, private data: IDisposableDataProvider<T> | Array<T>,
private tableOptions: Slick.GridOptions<T>, private tableOptions: Slick.GridOptions<T>,
options: IViewletPaneOptions, options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService, @IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService, @IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,

10730
src/typings/electron.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
import { escape } from 'vs/base/common/strings'; import { escape } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
export interface MarkdownRenderOptions extends FormattedTextRenderOptions { export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>; codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
@@ -118,7 +119,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
} }
}; };
renderer.paragraph = (text): string => { renderer.paragraph = (text): string => {
return `<p>${text}</p>`; return `<p>${markdown.supportThemeIcons ? renderCodicons(text) : text}</p>`;
}; };
if (options.codeBlockRenderer) { if (options.codeBlockRenderer) {
@@ -192,7 +193,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
allowedSchemes.push(Schemas.command); allowedSchemes.push(Schemas.command);
} }
const renderedMarkdown = marked.parse(markdown.value, markedOptions); const renderedMarkdown = marked.parse(
markdown.supportThemeIcons
? markdownEscapeEscapedCodicons(markdown.value)
: markdown.value,
markedOptions
);
element.innerHTML = insane(renderedMarkdown, { element.innerHTML = insane(renderedMarkdown, {
allowedSchemes, allowedSchemes,
allowedAttributes: { allowedAttributes: {

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3.76345L5.80687 11.9351L5.08584 11.8927L1 7.29614L1.76345 6.61752L5.50997 10.8324L14.3214 3L15 3.76345Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 278 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3.76345L5.80687 11.9351L5.08584 11.8927L1 7.29614L1.76345 6.61752L5.50997 10.8324L14.3214 3L15 3.76345Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 278 B

View File

@@ -44,10 +44,7 @@
background-size: 16px !important; background-size: 16px !important;
} }
.monaco-custom-checkbox.monaco-simple-checkbox.checked { /* hide check when unchecked */
background: url('check-light.svg') center center no-repeat; .monaco-custom-checkbox.monaco-simple-checkbox.unchecked:not(.checked)::before {
} visibility: hidden;;
.monaco-custom-checkbox.monaco-simple-checkbox.checked {
background: url('check-dark.svg') center center no-repeat;
} }

View File

@@ -192,7 +192,7 @@ export class SimpleCheckbox extends Widget {
constructor(private title: string, private isChecked: boolean) { constructor(private title: string, private isChecked: boolean) {
super(); super();
this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox' }); this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox codicon-check' });
this.domNode = this.checkbox.domNode; this.domNode = this.checkbox.domNode;

View File

@@ -395,9 +395,9 @@
.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" } .codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
.codicon-debug-breakpoint-function:before { content: "\eb88" } .codicon-debug-breakpoint-function:before { content: "\eb88" }
.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" } .codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" } .codicon-debug-stackframe-active:before { content: "\eb89" }
.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" } .codicon-debug-stackframe:before { content: "\eb8b" }
.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" } .codicon-debug-stackframe-focused:before { content: "\eb8b" }
.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } .codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
.codicon-symbol-string:before { content: "\eb8d" } .codicon-symbol-string:before { content: "\eb8d" }
.codicon-debug-reverse-continue:before { content: "\eb8e" } .codicon-debug-reverse-continue:before { content: "\eb8e" }

View File

@@ -6,16 +6,7 @@
import 'vs/css!./codicon/codicon'; import 'vs/css!./codicon/codicon';
import 'vs/css!./codicon/codicon-animations'; import 'vs/css!./codicon/codicon-animations';
import { escape } from 'vs/base/common/strings'; import { escape } from 'vs/base/common/strings';
import { renderCodicons } from 'vs/base/common/codicons';
function expand(text: string): string {
return text.replace(/\$\((([a-z0-9\-]+?)(~([a-z0-9\-]*?))?)\)/gi, (_match, _g1, name, _g3, animation) => {
return `<span class="codicon codicon-${name} ${animation ? `codicon-animation-${animation}` : ''}"></span>`;
});
}
export function renderCodicons(label: string): string {
return expand(escape(label));
}
export class CodiconLabel { export class CodiconLabel {
@@ -24,7 +15,7 @@ export class CodiconLabel {
) { } ) { }
set text(text: string) { set text(text: string) {
this._container.innerHTML = renderCodicons(text || ''); this._container.innerHTML = renderCodicons(escape(text ?? ''));
} }
set title(title: string) { set title(title: string) {

View File

@@ -5,5 +5,5 @@
.context-view { .context-view {
position: absolute; position: absolute;
z-index: 2000; z-index: 2500;
} }

View File

@@ -28,6 +28,7 @@
max-width: 90%; max-width: 90%;
min-height: 75px; min-height: 75px;
padding: 10px; padding: 10px;
transform: translate3d(0px, 0px, 0px);
} }
/** Dialog: Title Actions Row */ /** Dialog: Title Actions Row */

View File

@@ -46,6 +46,7 @@ interface ButtonMapEntry {
export class Dialog extends Disposable { export class Dialog extends Disposable {
private element: HTMLElement | undefined; private element: HTMLElement | undefined;
private shadowElement: HTMLElement | undefined;
private modal: HTMLElement | undefined; private modal: HTMLElement | undefined;
private buttonsContainer: HTMLElement | undefined; private buttonsContainer: HTMLElement | undefined;
private messageDetailElement: HTMLElement | undefined; private messageDetailElement: HTMLElement | undefined;
@@ -61,7 +62,8 @@ export class Dialog extends Disposable {
constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) { constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) {
super(); super();
this.modal = this.container.appendChild($(`.dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`)); this.modal = this.container.appendChild($(`.dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
this.element = this.modal.appendChild($('.dialog-box')); this.shadowElement = this.modal.appendChild($('.dialog-shadow'));
this.element = this.shadowElement.appendChild($('.dialog-box'));
hide(this.element); hide(this.element);
// If no button is provided, default to OK // If no button is provided, default to OK
@@ -249,10 +251,13 @@ export class Dialog extends Disposable {
const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : '';
const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : '';
if (this.shadowElement) {
this.shadowElement.style.boxShadow = shadowColor;
}
if (this.element) { if (this.element) {
this.element.style.color = fgColor; this.element.style.color = fgColor;
this.element.style.backgroundColor = bgColor; this.element.style.backgroundColor = bgColor;
this.element.style.boxShadow = shadowColor;
this.element.style.border = border; this.element.style.border = border;
if (this.buttonGroup) { if (this.buttonGroup) {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as objects from 'vs/base/common/objects'; import * as objects from 'vs/base/common/objects';
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { renderCodicons } from 'vs/base/common/codicons';
import { escape } from 'vs/base/common/strings'; import { escape } from 'vs/base/common/strings';
export interface IHighlight { export interface IHighlight {
@@ -65,13 +65,13 @@ export class HighlightedLabel {
if (pos < highlight.start) { if (pos < highlight.start) {
htmlContent += '<span>'; htmlContent += '<span>';
const substring = this.text.substring(pos, highlight.start); const substring = this.text.substring(pos, highlight.start);
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring); htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
htmlContent += '</span>'; htmlContent += '</span>';
pos = highlight.end; pos = highlight.end;
} }
htmlContent += '<span class="highlight">'; htmlContent += '<span class="highlight">';
const substring = this.text.substring(highlight.start, highlight.end); const substring = this.text.substring(highlight.start, highlight.end);
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring); htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
htmlContent += '</span>'; htmlContent += '</span>';
pos = highlight.end; pos = highlight.end;
} }
@@ -79,7 +79,7 @@ export class HighlightedLabel {
if (pos < this.text.length) { if (pos < this.text.length) {
htmlContent += '<span>'; htmlContent += '<span>';
const substring = this.text.substring(pos); const substring = this.text.substring(pos);
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring); htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
htmlContent += '</span>'; htmlContent += '</span>';
} }

View File

@@ -593,7 +593,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
const isSelected = this.element && hasClass(this.element, 'focused'); const isSelected = this.element && hasClass(this.element, 'focused');
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor; const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined;
const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : ''; const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : '';
if (this.item) { if (this.item) {

View File

@@ -1298,7 +1298,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
onDidModelSplice(() => null, null, this.disposables); onDidModelSplice(() => null, null, this.disposables);
// Active nodes can change when the model changes or when focus or selection change. // Active nodes can change when the model changes or when focus or selection change.
// We debouce it with 0 delay since these events may fire in the same stack and we only // We debounce it with 0 delay since these events may fire in the same stack and we only
// want to run this once. It also doesn't matter if it runs on the next tick since it's only // want to run this once. It also doesn't matter if it runs on the next tick since it's only
// a nice to have UI feature. // a nice to have UI feature.
onDidChangeActiveNodes.input = Event.chain(Event.any<any>(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange)) onDidChangeActiveNodes.input = Event.chain(Event.any<any>(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange))

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const escapeCodiconsRegex = /(?<!\\)\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
export function escapeCodicons(text: string): string {
return text.replace(escapeCodiconsRegex, match => `\\${match}`);
}
const markdownEscapedCodiconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
export function markdownEscapeEscapedCodicons(text: string): string {
// Need to add an extra \ for escaping in markdown
return text.replace(markdownEscapedCodiconsRegex, match => `\\${match}`);
}
const markdownUnescapeCodiconsRegex = /(?<!\\)\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
export function markdownUnescapeCodicons(text: string): string {
return text.replace(markdownUnescapeCodiconsRegex, (_, codicon) => `$(${codicon})`);
}
const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi;
export function renderCodicons(text: string): string {
return text.replace(renderCodiconsRegex, (_, escape, codicon, name, animation) => {
return escape
? `$(${codicon})`
: `<span class="codicon codicon-${name}${animation ? ` codicon-animation-${animation}` : ''}"></span>`;
});
}

View File

@@ -84,11 +84,11 @@ export function memoize(target: any, key: string, descriptor: any) {
return createMemoizer()(target, key, descriptor); return createMemoizer()(target, key, descriptor);
} }
export interface IDebouceReducer<T> { export interface IDebounceReducer<T> {
(previousValue: T, ...args: any[]): T; (previousValue: T, ...args: any[]): T;
} }
export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initialValueProvider?: () => T): Function { export function debounce<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
return createDecorator((fn, key) => { return createDecorator((fn, key) => {
const timerKey = `$debounce$${key}`; const timerKey = `$debounce$${key}`;
const resultKey = `$debounce$result$${key}`; const resultKey = `$debounce$result$${key}`;
@@ -112,3 +112,44 @@ export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initial
}; };
}); });
} }
export function throttle<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
return createDecorator((fn, key) => {
const timerKey = `$throttle$timer$${key}`;
const resultKey = `$throttle$result$${key}`;
const lastRunKey = `$throttle$lastRun$${key}`;
const pendingKey = `$throttle$pending$${key}`;
return function (this: any, ...args: any[]) {
if (!this[resultKey]) {
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}
if (this[lastRunKey] === null || this[lastRunKey] === undefined) {
this[lastRunKey] = -Number.MAX_VALUE;
}
if (reducer) {
this[resultKey] = reducer(this[resultKey], ...args);
}
if (this[pendingKey]) {
return;
}
const nextTime = this[lastRunKey] + delay;
if (nextTime <= Date.now()) {
this[lastRunKey] = Date.now();
fn.apply(this, [this[resultKey]]);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
} else {
this[pendingKey] = true;
this[timerKey] = setTimeout(() => {
this[pendingKey] = false;
this[lastRunKey] = Date.now();
fn.apply(this, [this[resultKey]]);
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
}, nextTime - Date.now());
}
};
});
}

View File

@@ -8,15 +8,11 @@ import * as types from 'vs/base/common/types';
import * as arrays from 'vs/base/common/arrays'; import * as arrays from 'vs/base/common/arrays';
function exceptionToErrorMessage(exception: any, verbose: boolean): string { function exceptionToErrorMessage(exception: any, verbose: boolean): string {
if (exception.message) { if (verbose && (exception.stack || exception.stacktrace)) {
if (verbose && (exception.stack || exception.stacktrace)) { return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
}
return detectSystemErrorMessage(exception);
} }
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); return detectSystemErrorMessage(exception);
} }
function stackToString(stack: string[] | string | undefined): string | undefined { function stackToString(stack: string[] | string | undefined): string | undefined {
@@ -34,7 +30,7 @@ function detectSystemErrorMessage(exception: any): string {
return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message); return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message);
} }
return exception.message; return exception.message || nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
} }
/** /**

View File

@@ -31,7 +31,7 @@ export class ErrorHandler {
}; };
} }
public addListener(listener: ErrorListenerCallback): ErrorListenerUnbind { addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
this.listeners.push(listener); this.listeners.push(listener);
return () => { return () => {
@@ -49,21 +49,21 @@ export class ErrorHandler {
this.listeners.splice(this.listeners.indexOf(listener), 1); this.listeners.splice(this.listeners.indexOf(listener), 1);
} }
public setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void { setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
this.unexpectedErrorHandler = newUnexpectedErrorHandler; this.unexpectedErrorHandler = newUnexpectedErrorHandler;
} }
public getUnexpectedErrorHandler(): (e: any) => void { getUnexpectedErrorHandler(): (e: any) => void {
return this.unexpectedErrorHandler; return this.unexpectedErrorHandler;
} }
public onUnexpectedError(e: any): void { onUnexpectedError(e: any): void {
this.unexpectedErrorHandler(e); this.unexpectedErrorHandler(e);
this.emit(e); this.emit(e);
} }
// For external errors, we don't want the listeners to be called // For external errors, we don't want the listeners to be called
public onUnexpectedExternalError(e: any): void { onUnexpectedExternalError(e: any): void {
this.unexpectedErrorHandler(e); this.unexpectedErrorHandler(e);
} }
} }

View File

@@ -5,37 +5,51 @@
import { equals } from 'vs/base/common/arrays'; import { equals } from 'vs/base/common/arrays';
import { UriComponents } from 'vs/base/common/uri'; import { UriComponents } from 'vs/base/common/uri';
import { escapeCodicons, markdownUnescapeCodicons } from 'vs/base/common/codicons';
export interface IMarkdownString { export interface IMarkdownString {
readonly value: string; readonly value: string;
readonly isTrusted?: boolean; readonly isTrusted?: boolean;
readonly supportThemeIcons?: boolean;
uris?: { [href: string]: UriComponents }; uris?: { [href: string]: UriComponents };
} }
export class MarkdownString implements IMarkdownString { export class MarkdownString implements IMarkdownString {
private readonly _isTrusted: boolean;
private readonly _supportThemeIcons: boolean;
private _value: string; constructor(
private _isTrusted: boolean; private _value: string = '',
isTrustedOrOptions: boolean | { isTrusted?: boolean, supportThemeIcons?: boolean } = false,
) {
if (typeof isTrustedOrOptions === 'boolean') {
this._isTrusted = isTrustedOrOptions;
this._supportThemeIcons = false;
}
else {
this._isTrusted = isTrustedOrOptions.isTrusted ?? false;
this._supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
}
constructor(value: string = '', isTrusted = false) {
this._value = value;
this._isTrusted = isTrusted;
} }
get value() { return this._value; } get value() { return this._value; }
get isTrusted() { return this._isTrusted; } get isTrusted() { return this._isTrusted; }
get supportThemeIcons() { return this._supportThemeIcons; }
appendText(value: string): MarkdownString { appendText(value: string): MarkdownString {
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
this._value += value value = value
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
.replace('\n', '\n\n'); .replace('\n', '\n\n');
this._value += this.supportThemeIcons ? markdownUnescapeCodicons(value) : value;
return this; return this;
} }
appendMarkdown(value: string): MarkdownString { appendMarkdown(value: string): MarkdownString {
this._value += value; this._value += value;
return this; return this;
} }
@@ -47,6 +61,10 @@ export class MarkdownString implements IMarkdownString {
this._value += '\n```\n'; this._value += '\n```\n';
return this; return this;
} }
static escapeThemeIcons(value: string): string {
return escapeCodicons(value);
}
} }
export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[] | null | undefined): boolean { export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[] | null | undefined): boolean {
@@ -64,7 +82,8 @@ export function isMarkdownString(thing: any): thing is IMarkdownString {
return true; return true;
} else if (thing && typeof thing === 'object') { } else if (thing && typeof thing === 'object') {
return typeof (<IMarkdownString>thing).value === 'string' return typeof (<IMarkdownString>thing).value === 'string'
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined); && (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined)
&& (typeof (<IMarkdownString>thing).supportThemeIcons === 'boolean' || (<IMarkdownString>thing).supportThemeIcons === undefined);
} }
return false; return false;
} }
@@ -89,7 +108,7 @@ function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean {
} else if (!a || !b) { } else if (!a || !b) {
return false; return false;
} else { } else {
return a.value === b.value && a.isTrusted === b.isTrusted; return a.value === b.value && a.isTrusted === b.isTrusted && a.supportThemeIcons === b.supportThemeIcons;
} }
} }

View File

@@ -227,7 +227,7 @@ export class Client implements IChannelClient, IDisposable {
this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err)); this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err));
this.child.on('exit', (code: any, signal: any) => { this.child.on('exit', (code: any, signal: any) => {
process.removeListener('exit', onExit); process.removeListener('exit' as 'loaded', onExit); // https://github.com/electron/electron/issues/21475
this.activeRequests.forEach(r => dispose(r)); this.activeRequests.forEach(r => dispose(r));
this.activeRequests.clear(); this.activeRequests.clear();

View File

@@ -6,42 +6,104 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as marked from 'vs/base/common/marked/marked'; import * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { MarkdownString } from 'vs/base/common/htmlContent';
suite('MarkdownRenderer', () => { suite('MarkdownRenderer', () => {
test('image rendering conforms to default', () => { suite('Images', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown); test('image rendering conforms to default', () => {
const renderer = new marked.Renderer(); const markdown = { value: `![image](someimageurl 'caption')` };
const imageFromMarked = marked(markdown.value, { const result: HTMLElement = renderMarkdown(markdown);
sanitize: true, const renderer = new marked.Renderer();
renderer const imageFromMarked = marked(markdown.value, {
}).trim(); sanitize: true,
assert.strictEqual(result.innerHTML, imageFromMarked); renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
}); });
test('image rendering conforms to default without title', () => { suite('ThemeIcons Support On', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown); test('render appendText', () => {
const renderer = new marked.Renderer(); const mds = new MarkdownString(undefined, { supportThemeIcons: true });
const imageFromMarked = marked(markdown.value, { mds.appendText('$(zap) $(dont match me)');
sanitize: true,
renderer let result: HTMLElement = renderMarkdown(mds);
}).trim(); assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(dont match me)</p>`);
assert.strictEqual(result.innerHTML, imageFromMarked); });
test('render appendText escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)'));
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
test('render appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(dont match me)</p>`);
});
test('render appendMarkdown escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)'));
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
}); });
test('image width from title params', () => { suite('ThemeIcons Support Off', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`); test('render appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendText('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
test('render appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendMarkdown('$(zap) $(dont match me)');
let result: HTMLElement = renderMarkdown(mds);
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
});
}); });
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
}); });

View File

@@ -3,8 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as sinon from 'sinon';
import * as assert from 'assert'; import * as assert from 'assert';
import { memoize, createMemoizer } from 'vs/base/common/decorators'; import { memoize, createMemoizer, throttle } from 'vs/base/common/decorators';
suite('Decorators', () => { suite('Decorators', () => {
test('memoize should memoize methods', () => { test('memoize should memoize methods', () => {
@@ -100,7 +101,9 @@ suite('Decorators', () => {
test('memoized property should not be enumerable', () => { test('memoized property should not be enumerable', () => {
class Foo { class Foo {
@memoize @memoize
get answer() { return 42; } get answer() {
return 42;
}
} }
const foo = new Foo(); const foo = new Foo();
@@ -112,7 +115,9 @@ suite('Decorators', () => {
test('memoized property should not be writable', () => { test('memoized property should not be writable', () => {
class Foo { class Foo {
@memoize @memoize
get answer() { return 42; } get answer() {
return 42;
}
} }
const foo = new Foo(); const foo = new Foo();
@@ -131,7 +136,9 @@ suite('Decorators', () => {
let counter = 0; let counter = 0;
class Foo { class Foo {
@memoizer @memoizer
get answer() { return ++counter; } get answer() {
return ++counter;
}
} }
const foo = new Foo(); const foo = new Foo();
@@ -145,4 +152,49 @@ suite('Decorators', () => {
assert.equal(foo.answer, 3); assert.equal(foo.answer, 3);
assert.equal(foo.answer, 3); assert.equal(foo.answer, 3);
}); });
test('throttle', () => {
const spy = sinon.spy();
const clock = sinon.useFakeTimers();
try {
class ThrottleTest {
private _handle: Function;
constructor(fn: Function) {
this._handle = fn;
}
@throttle(
100,
(a: number, b: number) => a + b,
() => 0
)
report(p: number): void {
this._handle(p);
}
}
const t = new ThrottleTest(spy);
t.report(1);
t.report(2);
t.report(3);
assert.deepEqual(spy.args, [[1]]);
clock.tick(200);
assert.deepEqual(spy.args, [[1], [5]]);
spy.reset();
t.report(4);
t.report(5);
clock.tick(50);
t.report(6);
assert.deepEqual(spy.args, [[4]]);
clock.tick(60);
assert.deepEqual(spy.args, [[4], [11]]);
} finally {
clock.restore();
}
});
}); });

View File

@@ -2,6 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as assert from 'assert'; import * as assert from 'assert';
import { toErrorMessage } from 'vs/base/common/errorMessage'; import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -16,9 +17,17 @@ suite('Errors', () => {
error.detail.exception = {}; error.detail.exception = {};
error.detail.exception.message = 'Foo Bar'; error.detail.exception.message = 'Foo Bar';
assert.strictEqual(toErrorMessage(error), 'Foo Bar'); assert.strictEqual(toErrorMessage(error), 'Foo Bar');
assert.strictEqual(toErrorMessage(error, true), 'Foo Bar');
assert(toErrorMessage()); assert(toErrorMessage());
assert(toErrorMessage(null)); assert(toErrorMessage(null));
assert(toErrorMessage({})); assert(toErrorMessage({}));
try {
throw new Error();
} catch (error) {
assert.strictEqual(toErrorMessage(error), 'An unknown error occurred. Please consult the log for more details.');
assert.ok(toErrorMessage(error, true).length > 'An unknown error occurred. Please consult the log for more details.'.length);
}
}); });
}); });

View File

@@ -43,4 +43,14 @@ suite('Hash', () => {
assert.notEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' })); assert.notEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' }));
assert.notEqual(hash({}), hash([])); assert.notEqual(hash({}), hash([]));
}); });
});
test('array - unexpected collision', function () {
this.skip();
const a = hash([undefined, undefined, undefined, undefined, undefined]);
const b = hash([undefined, undefined, 'HHHHHH', [{ line: 0, character: 0 }, { line: 0, character: 0 }], undefined]);
// console.log(a);
// console.log(b);
assert.notEqual(a, b);
});
});

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { MarkdownString } from 'vs/base/common/htmlContent'; import { MarkdownString } from 'vs/base/common/htmlContent';
suite('markdownString', () => { suite('MarkdownString', () => {
test('escape', () => { test('escape', () => {
@@ -16,4 +16,63 @@ suite('markdownString', () => {
assert.equal(mds.value, '\\# foo\n\n\\*bar\\*'); assert.equal(mds.value, '\\# foo\n\n\\*bar\\*');
}); });
suite('ThemeIcons', () => {
test('escapeThemeIcons', () => {
assert.equal(
MarkdownString.escapeThemeIcons('$(zap) $(not an icon) foo$(bar)'),
'\\$(zap) $(not an icon) foo\\$(bar)'
);
});
suite('Support On', () => {
test('appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText('$(zap)');
assert.equal(mds.value, '$(zap)');
});
test('appendText escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendText(MarkdownString.escapeThemeIcons('$(zap)'));
assert.equal(mds.value, '\\\\$\\(zap\\)');
});
test('appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown('$(zap)');
assert.equal(mds.value, '$(zap)');
});
test('appendMarkdown escaped', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap)'));
assert.equal(mds.value, '\\$(zap)');
});
});
suite('Support Off', () => {
test('appendText', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendText('$(zap)');
assert.equal(mds.value, '$\\(zap\\)');
});
test('appendMarkdown', () => {
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
mds.appendMarkdown('$(zap)');
assert.equal(mds.value, '$(zap)');
});
});
});
}); });

View File

@@ -3,43 +3,43 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/issueReporter'; import { clipboard, ipcRenderer, shell, webFrame } from 'electron';
import { shell, ipcRenderer, webFrame, clipboard } from 'electron';
import { localize } from 'vs/nls';
import { $ } from 'vs/base/browser/dom';
import * as collections from 'vs/base/common/collections';
import * as browser from 'vs/base/browser/browser';
import { escape } from 'vs/base/common/strings';
import product from 'vs/platform/product/common/product';
import * as os from 'os'; import * as os from 'os';
import * as browser from 'vs/base/browser/browser';
import { $ } from 'vs/base/browser/dom';
import { Button } from 'vs/base/browser/ui/button/button';
import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
import * as collections from 'vs/base/common/collections';
import { debounce } from 'vs/base/common/decorators'; import { debounce } from 'vs/base/common/decorators';
import * as platform from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import { escape } from 'vs/base/common/strings';
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc'; import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel';
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue';
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil'; import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil';
import { Button } from 'vs/base/browser/ui/button/button'; import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import 'vs/css!./media/issueReporter';
import { localize } from 'vs/nls';
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/node/issue';
import { getLogLevel, ILogService } from 'vs/platform/log/common/log';
import { FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc';
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import product from 'vs/platform/product/common/product';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
const MAX_URL_LENGTH = 2045; const MAX_URL_LENGTH = 2045;
@@ -226,7 +226,7 @@ export class IssueReporter extends Disposable {
} }
if (styles.buttonHoverBackground) { if (styles.buttonHoverBackground) {
content.push(`.monaco-text-button:hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`); content.push(`.monaco-text-button:not(.disabled):hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`);
} }
styleTag.innerHTML = content.join('\n'); styleTag.innerHTML = content.join('\n');
@@ -432,6 +432,11 @@ export class IssueReporter extends Disposable {
sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled'); sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled');
}); });
this.addEventListener('extensionBugsLink', 'click', (e: MouseEvent) => {
const url = (<HTMLElement>e.target).innerText;
shell.openExternal(url);
});
this.addEventListener('disableExtensions', 'keydown', (e: Event) => { this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
e.stopPropagation(); e.stopPropagation();
if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) { if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) {
@@ -1029,15 +1034,63 @@ export class IssueReporter extends Disposable {
const matches = extensions.filter(extension => extension.id === selectedExtensionId); const matches = extensions.filter(extension => extension.id === selectedExtensionId);
if (matches.length) { if (matches.length) {
this.issueReporterModel.update({ selectedExtension: matches[0] }); this.issueReporterModel.update({ selectedExtension: matches[0] });
this.validateSelectedExtension();
const title = (<HTMLInputElement>this.getElementById('issue-title')).value; const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
this.searchExtensionIssues(title); this.searchExtensionIssues(title);
} else { } else {
this.issueReporterModel.update({ selectedExtension: undefined }); this.issueReporterModel.update({ selectedExtension: undefined });
this.clearSearchResults(); this.clearSearchResults();
this.validateSelectedExtension();
} }
}); });
} }
this.addEventListener('problem-source', 'change', (_) => {
this.validateSelectedExtension();
});
}
private validateSelectedExtension(): void {
const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!;
const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!;
hide(extensionValidationMessage);
hide(extensionValidationNoUrlsMessage);
if (!this.issueReporterModel.getData().selectedExtension) {
this.previewButton.enabled = true;
return;
}
const hasValidGitHubUrl = this.getExtensionGitHubUrl();
if (hasValidGitHubUrl) {
this.previewButton.enabled = true;
} else {
this.setExtensionValidationMessage();
this.previewButton.enabled = false;
}
}
private setExtensionValidationMessage(): void {
const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!;
const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!;
const bugsUrl = this.getExtensionBugsUrl();
if (bugsUrl) {
show(extensionValidationMessage);
const link = this.getElementById('extensionBugsLink')!;
link.textContent = bugsUrl;
return;
}
const extensionUrl = this.getExtensionRepositoryUrl();
if (extensionUrl) {
show(extensionValidationMessage);
const link = this.getElementById('extensionBugsLink');
link!.textContent = extensionUrl;
return;
}
show(extensionValidationNoUrlsMessage);
} }
private updateProcessInfo(state: IssueReporterModelData) { private updateProcessInfo(state: IssueReporterModelData) {

View File

@@ -32,6 +32,11 @@ export default (): string => `
<select id="extension-selector" class="inline-form-control"> <select id="extension-selector" class="inline-form-control">
<!-- To be dynamically filled --> <!-- To be dynamically filled -->
</select> </select>
<div id="extension-selection-validation-error" class="validation-error hidden" role="alert">${escape(localize('extensionWithNonstandardBugsUrl', "The issue reporter is unable to create issues for this extension. Please visit {0} to report an issue."))
.replace('{0}', `<span tabIndex=0 role="button" id="extensionBugsLink" class="workbenchCommand"><!-- To be dynamically filled --></span>`)}</div>
<div id="extension-selection-validation-error-no-url" class="validation-error hidden" role="alert">
${escape(localize('extensionWithNoBugsUrl', "The issue reporter is unable to create issues for this extension, as it does not specify a URL for reporting issues. Please check the marketplace page of this extension so see if other instructions are available."))}
</div>
</div> </div>
</div> </div>

View File

@@ -50,11 +50,10 @@ import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService'; import { IProductService } from 'vs/platform/product/common/productService';
import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { UserDataSyncChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc';
import { IElectronService } from 'vs/platform/electron/node/electron'; import { IElectronService } from 'vs/platform/electron/node/electron';
import { LoggerService } from 'vs/platform/log/node/loggerService'; import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
@@ -63,7 +62,6 @@ import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenSer
import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc';
import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync'; import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync';
export interface ISharedProcessConfiguration { export interface ISharedProcessConfiguration {
@@ -186,8 +184,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService)); services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
services.set(IAuthTokenService, new SyncDescriptor(AuthTokenService)); services.set(IAuthTokenService, new SyncDescriptor(AuthTokenService));
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter);
services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel));
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter))); services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter)));
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));

View File

@@ -140,8 +140,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
} }
}; };
// Apply icon to window
// Linux: always
// Windows: only when running out of sources, otherwise an icon is set by us on the executable
if (isLinux) { if (isLinux) {
options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s) options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png');
} else if (isWindows && !this.environmentService.isBuilt) {
options.icon = path.join(this.environmentService.appRoot, 'resources/win32/code_150x150.png');
} }
const windowConfig = this.configurationService.getValue<IWindowSettings>('window'); const windowConfig = this.configurationService.getValue<IWindowSettings>('window');

View File

@@ -20,7 +20,7 @@ import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccess
import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { withNullAsUndefined } from 'vs/base/common/types'; import { withNullAsUndefined, assertType } from 'vs/base/common/types';
export type ServicesAccessor = InstantiationServicesAccessor; export type ServicesAccessor = InstantiationServicesAccessor;
export type IEditorContributionCtor = IConstructorSignature1<ICodeEditor, IEditorContribution>; export type IEditorContributionCtor = IConstructorSignature1<ICodeEditor, IEditorContribution>;
@@ -311,6 +311,60 @@ export function registerDefaultLanguageCommand(id: string, handler: (model: ITex
}); });
} }
export function registerModelAndPositionCommand(id: string, handler: (model: ITextModel, position: Position, ...args: any[]) => any) {
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
const [resource, position] = args;
assertType(URI.isUri(resource));
assertType(Position.isIPosition(position));
const model = accessor.get(IModelService).getModel(resource);
if (model) {
const editorPosition = Position.lift(position);
return handler(model, editorPosition, args.slice(2));
}
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
return new Promise((resolve, reject) => {
try {
const result = handler(reference.object.textEditorModel, Position.lift(position), args.slice(2));
resolve(result);
} catch (err) {
reject(err);
}
}).finally(() => {
reference.dispose();
});
});
});
}
export function registerModelCommand(id: string, handler: (model: ITextModel, ...args: any[]) => any) {
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
const [resource] = args;
assertType(URI.isUri(resource));
const model = accessor.get(IModelService).getModel(resource);
if (model) {
return handler(model, args.slice(1));
}
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
return new Promise((resolve, reject) => {
try {
const result = handler(reference.object.textEditorModel, args.slice(1));
resolve(result);
} catch (err) {
reject(err);
}
}).finally(() => {
reference.dispose();
});
});
});
}
export function registerEditorCommand<T extends EditorCommand>(editorCommand: T): T { export function registerEditorCommand<T extends EditorCommand>(editorCommand: T): T {
EditorContributionRegistry.INSTANCE.registerEditorCommand(editorCommand); EditorContributionRegistry.INSTANCE.registerEditorCommand(editorCommand);
return editorCommand; return editorCommand;

View File

@@ -50,6 +50,7 @@ export class EditorScrollbar extends ViewPart {
horizontalScrollbarSize: scrollbar.horizontalScrollbarSize, horizontalScrollbarSize: scrollbar.horizontalScrollbarSize,
horizontalSliderSize: scrollbar.horizontalSliderSize, horizontalSliderSize: scrollbar.horizontalSliderSize,
handleMouseWheel: scrollbar.handleMouseWheel, handleMouseWheel: scrollbar.handleMouseWheel,
alwaysConsumeMouseWheel: scrollbar.alwaysConsumeMouseWheel,
arrowSize: scrollbar.arrowSize, arrowSize: scrollbar.arrowSize,
mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, mouseWheelScrollSensitivity: mouseWheelScrollSensitivity,
fastScrollSensitivity: fastScrollSensitivity, fastScrollSensitivity: fastScrollSensitivity,

View File

@@ -417,9 +417,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
// Current model is the new model // Current model is the new model
return; return;
} }
const hasTextFocus = this.hasTextFocus();
const detachedModel = this._detachModel(); const detachedModel = this._detachModel();
this._attachModel(model); this._attachModel(model);
if (hasTextFocus && this.hasModel()) {
this.focus();
}
const e: editorCommon.IModelChangedEvent = { const e: editorCommon.IModelChangedEvent = {
oldModelUrl: detachedModel ? detachedModel.uri : null, oldModelUrl: detachedModel ? detachedModel.uri : null,

View File

@@ -2327,6 +2327,11 @@ export interface IEditorScrollbarOptions {
* Defaults to true. * Defaults to true.
*/ */
handleMouseWheel?: boolean; handleMouseWheel?: boolean;
/**
* Always consume mouse wheel events (always call preventDefault() and stopPropagation() on the browser events).
* Defaults to true.
*/
alwaysConsumeMouseWheel?: boolean;
/** /**
* Height in pixels for the horizontal scrollbar. * Height in pixels for the horizontal scrollbar.
* Defaults to 10 (px). * Defaults to 10 (px).
@@ -2357,6 +2362,7 @@ export interface InternalEditorScrollbarOptions {
readonly verticalHasArrows: boolean; readonly verticalHasArrows: boolean;
readonly horizontalHasArrows: boolean; readonly horizontalHasArrows: boolean;
readonly handleMouseWheel: boolean; readonly handleMouseWheel: boolean;
readonly alwaysConsumeMouseWheel: boolean;
readonly horizontalScrollbarSize: number; readonly horizontalScrollbarSize: number;
readonly horizontalSliderSize: number; readonly horizontalSliderSize: number;
readonly verticalScrollbarSize: number; readonly verticalScrollbarSize: number;
@@ -2391,6 +2397,7 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
verticalScrollbarSize: 14, verticalScrollbarSize: 14,
verticalSliderSize: 14, verticalSliderSize: 14,
handleMouseWheel: true, handleMouseWheel: true,
alwaysConsumeMouseWheel: true
} }
); );
} }
@@ -2410,6 +2417,7 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
verticalHasArrows: EditorBooleanOption.boolean(input.verticalHasArrows, this.defaultValue.verticalHasArrows), verticalHasArrows: EditorBooleanOption.boolean(input.verticalHasArrows, this.defaultValue.verticalHasArrows),
horizontalHasArrows: EditorBooleanOption.boolean(input.horizontalHasArrows, this.defaultValue.horizontalHasArrows), horizontalHasArrows: EditorBooleanOption.boolean(input.horizontalHasArrows, this.defaultValue.horizontalHasArrows),
handleMouseWheel: EditorBooleanOption.boolean(input.handleMouseWheel, this.defaultValue.handleMouseWheel), handleMouseWheel: EditorBooleanOption.boolean(input.handleMouseWheel, this.defaultValue.handleMouseWheel),
alwaysConsumeMouseWheel: EditorBooleanOption.boolean(input.alwaysConsumeMouseWheel, this.defaultValue.alwaysConsumeMouseWheel),
horizontalScrollbarSize: horizontalScrollbarSize, horizontalScrollbarSize: horizontalScrollbarSize,
horizontalSliderSize: EditorIntOption.clampedInt(input.horizontalSliderSize, horizontalScrollbarSize, 0, 1000), horizontalSliderSize: EditorIntOption.clampedInt(input.horizontalSliderSize, horizontalScrollbarSize, 0, 1000),
verticalScrollbarSize: verticalScrollbarSize, verticalScrollbarSize: verticalScrollbarSize,
@@ -3358,7 +3366,7 @@ export const EditorOptions = {
lineNumbers: register(new EditorRenderLineNumbersOption()), lineNumbers: register(new EditorRenderLineNumbersOption()),
lineNumbersMinChars: register(new EditorIntOption( lineNumbersMinChars: register(new EditorIntOption(
EditorOption.lineNumbersMinChars, 'lineNumbersMinChars', EditorOption.lineNumbersMinChars, 'lineNumbersMinChars',
5, 1, 10 5, 1, 300
)), )),
links: register(new EditorBooleanOption( links: register(new EditorBooleanOption(
EditorOption.links, 'links', true, EditorOption.links, 'links', true,

View File

@@ -386,7 +386,7 @@ export class TextModelResolvedOptions {
defaultEOL: DefaultEndOfLine; defaultEOL: DefaultEndOfLine;
trimAutoWhitespace: boolean; trimAutoWhitespace: boolean;
}) { }) {
this.tabSize = src.tabSize | 0; this.tabSize = Math.max(1, src.tabSize | 0);
this.indentSize = src.tabSize | 0; this.indentSize = src.tabSize | 0;
this.insertSpaces = Boolean(src.insertSpaces); this.insertSpaces = Boolean(src.insertSpaces);
this.defaultEOL = src.defaultEOL | 0; this.defaultEOL = src.defaultEOL | 0;

View File

@@ -1246,9 +1246,9 @@ export function isResourceTextEdit(thing: any): thing is ResourceTextEdit {
} }
export interface ResourceFileEdit { export interface ResourceFileEdit {
oldUri: URI; oldUri?: URI;
newUri: URI; newUri?: URI;
options: { overwrite?: boolean, ignoreIfNotExists?: boolean, ignoreIfExists?: boolean, recursive?: boolean }; options?: { overwrite?: boolean, ignoreIfNotExists?: boolean, ignoreIfExists?: boolean, recursive?: boolean };
} }
export interface ResourceTextEdit { export interface ResourceTextEdit {

View File

@@ -5,7 +5,8 @@
import 'vs/css!./codelensWidget'; import 'vs/css!./codelensWidget';
import * as dom from 'vs/base/browser/dom'; import * as dom from 'vs/base/browser/dom';
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { renderCodicons } from 'vs/base/common/codicons';
import { escape } from 'vs/base/common/strings';
import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
@@ -88,7 +89,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
} }
hasSymbol = true; hasSymbol = true;
if (lens.command) { if (lens.command) {
const title = renderCodicons(lens.command.title); const title = renderCodicons(escape(lens.command.title));
if (lens.command.id) { if (lens.command.id) {
innerHtml += `<a id=${i}>${title}</a>`; innerHtml += `<a id=${i}>${title}</a>`;
this._commands.set(String(i), lens.command); this._commands.set(String(i), lens.command);

View File

@@ -10,7 +10,7 @@ import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/error
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { CodeEditorStateFlag, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { CodeEditorStateFlag, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerLanguageCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
@@ -25,6 +25,8 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList'; import { LinkedList } from 'vs/base/common/linkedList';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { assertType } from 'vs/base/common/types';
export function alertFormattingEdits(edits: ISingleEditOperation[]): void { export function alertFormattingEdits(edits: ISingleEditOperation[]): void {
@@ -354,11 +356,11 @@ export function getOnTypeFormattingEdits(
}); });
} }
registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args) { CommandsRegistry.registerCommand('_executeFormatRangeProvider', function (accessor, ...args) {
const { resource, range, options } = args; const [resource, range, options] = args;
if (!(resource instanceof URI) || !Range.isIRange(range)) { assertType(URI.isUri(resource));
throw illegalArgument(); assertType(Range.isIRange(range));
}
const model = accessor.get(IModelService).getModel(resource); const model = accessor.get(IModelService).getModel(resource);
if (!model) { if (!model) {
throw illegalArgument('resource'); throw illegalArgument('resource');
@@ -366,11 +368,10 @@ registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args)
return getDocumentRangeFormattingEditsUntilResult(accessor.get(IEditorWorkerService), model, Range.lift(range), options, CancellationToken.None); return getDocumentRangeFormattingEditsUntilResult(accessor.get(IEditorWorkerService), model, Range.lift(range), options, CancellationToken.None);
}); });
registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, args) { CommandsRegistry.registerCommand('_executeFormatDocumentProvider', function (accessor, ...args) {
const { resource, options } = args; const [resource, options] = args;
if (!(resource instanceof URI)) { assertType(URI.isUri(resource));
throw illegalArgument('resource');
}
const model = accessor.get(IModelService).getModel(resource); const model = accessor.get(IModelService).getModel(resource);
if (!model) { if (!model) {
throw illegalArgument('resource'); throw illegalArgument('resource');
@@ -379,11 +380,12 @@ registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, ar
return getDocumentFormattingEditsUntilResult(accessor.get(IEditorWorkerService), model, options, CancellationToken.None); return getDocumentFormattingEditsUntilResult(accessor.get(IEditorWorkerService), model, options, CancellationToken.None);
}); });
registerLanguageCommand('_executeFormatOnTypeProvider', function (accessor, args) { CommandsRegistry.registerCommand('_executeFormatOnTypeProvider', function (accessor, ...args) {
const { resource, position, ch, options } = args; const [resource, position, ch, options] = args;
if (!(resource instanceof URI) || !Position.isIPosition(position) || typeof ch !== 'string') { assertType(URI.isUri(resource));
throw illegalArgument(); assertType(Position.isIPosition(position));
} assertType(typeof ch === 'string');
const model = accessor.get(IModelService).getModel(resource); const model = accessor.get(IModelService).getModel(resource);
if (!model) { if (!model) {
throw illegalArgument('resource'); throw illegalArgument('resource');

View File

@@ -6,7 +6,7 @@
import { flatten, coalesce } from 'vs/base/common/arrays'; import { flatten, coalesce } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes'; import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes';
@@ -72,8 +72,8 @@ export function getReferencesAtPosition(model: ITextModel, position: Position, c
}); });
} }
registerDefaultLanguageCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None)); registerModelAndPositionCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None));
registerDefaultLanguageCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None)); registerModelAndPositionCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None));
registerDefaultLanguageCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None)); registerModelAndPositionCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None));
registerDefaultLanguageCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None)); registerModelAndPositionCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None));
registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None)); registerModelAndPositionCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None));

View File

@@ -23,7 +23,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async
import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView'; import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView';
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
export const ctxReferenceSearchVisible = new RawContextKey<boolean>('referenceSearchVisible', false); export const ctxReferenceSearchVisible = new RawContextKey<boolean>('referenceSearchVisible', false);
@@ -173,6 +173,18 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
}); });
} }
changeFocusBetweenPreviewAndReferences() {
if (!this._widget) {
// can be called while still resolving...
return;
}
if (this._widget.isPreviewEditorFocused()) {
this._widget.focusOnReferenceTree();
} else {
this._widget.focusOnPreviewEditor();
}
}
async goToNextOrPreviousReference(fwd: boolean) { async goToNextOrPreviousReference(fwd: boolean) {
if (!this._editor.hasModel() || !this._model || !this._widget) { if (!this._editor.hasModel() || !this._model || !this._widget) {
// can be called while still resolving... // can be called while still resolving...
@@ -229,7 +241,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
if (this._editor === openedEditor) { if (this._editor === openedEditor) {
// //
this._widget.show(range); this._widget.show(range);
this._widget.focus(); this._widget.focusOnReferenceTree();
} else { } else {
// we opened a different editor instance which means a different controller instance. // we opened a different editor instance which means a different controller instance.
@@ -278,6 +290,18 @@ function withController(accessor: ServicesAccessor, fn: (controller: ReferencesC
} }
} }
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'changePeekFocus',
weight: KeybindingWeight.WorkbenchContrib + 50,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.F2),
when: ContextKeyExpr.or(ctxReferenceSearchVisible, PeekContext.inPeekEditor),
handler(accessor) {
withController(accessor, controller => {
controller.changeFocusBetweenPreviewAndReferences();
});
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'goToNextReference', id: 'goToNextReference',
weight: KeybindingWeight.WorkbenchContrib + 50, weight: KeybindingWeight.WorkbenchContrib + 50,

View File

@@ -251,10 +251,18 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
super.show(where, this.layoutData.heightInLines || 18); super.show(where, this.layoutData.heightInLines || 18);
} }
focus(): void { focusOnReferenceTree(): void {
this._tree.domFocus(); this._tree.domFocus();
} }
focusOnPreviewEditor(): void {
this._preview.focus();
}
isPreviewEditorFocused(): boolean {
return this._preview.hasTextFocus();
}
protected _onTitleClick(e: IMouseEvent): void { protected _onTitleClick(e: IMouseEvent): void {
if (this._preview && this._preview.getModel()) { if (this._preview && this._preview.getModel()) {
this._onDidSelectReference.fire({ this._onDidSelectReference.fire({
@@ -283,7 +291,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
horizontal: 'auto', horizontal: 'auto',
useShadows: true, useShadows: true,
verticalHasArrows: false, verticalHasArrows: false,
horizontalHasArrows: false horizontalHasArrows: false,
alwaysConsumeMouseWheel: false
}, },
overviewRulerLanes: 2, overviewRulerLanes: 2,
fixedOverflowWidgets: true, fixedOverflowWidgets: true,
@@ -457,7 +466,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
dom.show(this._treeContainer); dom.show(this._treeContainer);
dom.show(this._previewContainer); dom.show(this._previewContainer);
this._splitView.layout(this._dim.width); this._splitView.layout(this._dim.width);
this.focus(); this.focusOnReferenceTree();
// pick input and a reference to begin with // pick input and a reference to begin with
return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model); return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model);

View File

@@ -6,7 +6,7 @@
import { coalesce } from 'vs/base/common/arrays'; import { coalesce } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { onUnexpectedExternalError } from 'vs/base/common/errors'; import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { Hover, HoverProviderRegistry } from 'vs/editor/common/modes'; import { Hover, HoverProviderRegistry } from 'vs/editor/common/modes';
@@ -27,7 +27,7 @@ export function getHover(model: ITextModel, position: Position, token: Cancellat
return Promise.all(promises).then(coalesce); return Promise.all(promises).then(coalesce);
} }
registerDefaultLanguageCommand('_executeHoverProvider', (model, position) => getHover(model, position, CancellationToken.None)); registerModelAndPositionCommand('_executeHoverProvider', (model, position) => getHover(model, position, CancellationToken.None));
function isValid(result: Hover) { function isValid(result: Hover) {
const hasRange = (typeof result.range !== 'undefined'); const hasRange = (typeof result.range !== 'undefined');

View File

@@ -19,7 +19,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget {
protected _editor: ICodeEditor; protected _editor: ICodeEditor;
private _isVisible: boolean; private _isVisible: boolean;
private readonly _containerDomNode: HTMLElement; private readonly _containerDomNode: HTMLElement;
private readonly _domNode: HTMLElement; protected readonly _domNode: HTMLElement;
protected _showAtPosition: Position | null; protected _showAtPosition: Position | null;
protected _showAtRange: Range | null; protected _showAtRange: Range | null;
private _stoleFocus: boolean; private _stoleFocus: boolean;

View File

@@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range'; import { IRange, Range } from 'vs/editor/common/core/range';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes'; import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor, TokenizationRegistry } from 'vs/editor/common/modes';
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color'; import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
@@ -238,6 +238,12 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
this._register(editor.onDidChangeConfiguration((e) => { this._register(editor.onDidChangeConfiguration((e) => {
this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay); this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay);
})); }));
this._register(TokenizationRegistry.onDidChange((e) => {
if (this.isVisible && this._lastRange && this._messages.length > 0) {
this._domNode.textContent = '';
this._renderMessages(this._lastRange, this._messages);
}
}));
} }
dispose(): void { dispose(): void {

View File

@@ -176,7 +176,7 @@ class MessageWidget implements IContentWidget {
} }
getPosition(): IContentWidgetPosition { getPosition(): IContentWidgetPosition {
return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE] }; return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW] };
} }
} }

View File

@@ -54,6 +54,10 @@
white-space: initial; white-space: initial;
} }
.monaco-editor .parameter-hints-widget .docs .markdown-docs p code {
font-family: var(--monaco-monospace-font);
}
.monaco-editor .parameter-hints-widget .docs .code { .monaco-editor .parameter-hints-widget .docs .code {
white-space: pre-wrap; white-space: pre-wrap;
} }

View File

@@ -3,17 +3,17 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { illegalArgument } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
import { DocumentSymbol } from 'vs/editor/common/modes'; import { DocumentSymbol } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService'; import { IModelService } from 'vs/editor/common/services/modelService';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
import { values } from 'vs/base/common/collections'; import { values } from 'vs/base/common/collections';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { assertType } from 'vs/base/common/types';
export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> { export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
@@ -63,26 +63,19 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo
} }
registerLanguageCommand('_executeDocumentSymbolProvider', function (accessor, args) { CommandsRegistry.registerCommand('_executeDocumentSymbolProvider', async function (accessor, ...args) {
const { resource } = args; const [resource] = args;
if (!(resource instanceof URI)) { assertType(URI.isUri(resource));
throw illegalArgument('resource');
}
const model = accessor.get(IModelService).getModel(resource); const model = accessor.get(IModelService).getModel(resource);
if (model) { if (model) {
return getDocumentSymbols(model, false, CancellationToken.None); return getDocumentSymbols(model, false, CancellationToken.None);
} }
return accessor.get(ITextModelService).createModelReference(resource).then(reference => { const reference = await accessor.get(ITextModelService).createModelReference(resource);
return new Promise((resolve, reject) => { try {
try { return await getDocumentSymbols(reference.object.textEditorModel, false, CancellationToken.None);
const result = getDocumentSymbols(reference.object.textEditorModel, false, CancellationToken.None); } finally {
resolve(result); reference.dispose();
} catch (err) { }
reject(err);
}
}).finally(() => {
reference.dispose();
});
});
}); });

View File

@@ -7,7 +7,7 @@ import * as arrays from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor, registerModelCommand } from 'vs/editor/browser/editorExtensions';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
@@ -302,6 +302,7 @@ export function provideSelectionRanges(model: ITextModel, positions: Position[],
}); });
} }
registerDefaultLanguageCommand('_executeSelectionRangeProvider', function (model, _position, args) { registerModelCommand('_executeSelectionRangeProvider', function (model, ...args) {
return provideSelectionRanges(model, args.positions, CancellationToken.None); const [positions] = args;
return provideSelectionRanges(model, positions, CancellationToken.None);
}); });

View File

@@ -21,6 +21,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance'; import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { isLowSurrogate, isHighSurrogate } from 'vs/base/common/strings';
export interface ICancelEvent { export interface ICancelEvent {
readonly retrigger: boolean; readonly retrigger: boolean;
@@ -95,7 +96,7 @@ export class SuggestModel implements IDisposable {
private readonly _toDispose = new DisposableStore(); private readonly _toDispose = new DisposableStore();
private _quickSuggestDelay: number = 10; private _quickSuggestDelay: number = 10;
private _triggerCharacterListener?: IDisposable; private readonly _triggerCharacterListener = new DisposableStore();
private readonly _triggerQuickSuggest = new TimeoutTimer(); private readonly _triggerQuickSuggest = new TimeoutTimer();
private _state: State = State.Idle; private _state: State = State.Idle;
@@ -181,8 +182,7 @@ export class SuggestModel implements IDisposable {
} }
private _updateTriggerCharacters(): void { private _updateTriggerCharacters(): void {
this._triggerCharacterListener.clear();
dispose(this._triggerCharacterListener);
if (this._editor.getOption(EditorOption.readOnly) if (this._editor.getOption(EditorOption.readOnly)
|| !this._editor.hasModel() || !this._editor.hasModel()
@@ -191,29 +191,49 @@ export class SuggestModel implements IDisposable {
return; return;
} }
const supportsByTriggerCharacter: { [ch: string]: Set<CompletionItemProvider> } = Object.create(null); const supportsByTriggerCharacter = new Map<string, Set<CompletionItemProvider>>();
for (const support of CompletionProviderRegistry.all(this._editor.getModel())) { for (const support of CompletionProviderRegistry.all(this._editor.getModel())) {
for (const ch of support.triggerCharacters || []) { for (const ch of support.triggerCharacters || []) {
let set = supportsByTriggerCharacter[ch]; let set = supportsByTriggerCharacter.get(ch);
if (!set) { if (!set) {
set = supportsByTriggerCharacter[ch] = new Set(); set = new Set();
set.add(getSnippetSuggestSupport()); set.add(getSnippetSuggestSupport());
supportsByTriggerCharacter.set(ch, set);
} }
set.add(support); set.add(support);
} }
} }
this._triggerCharacterListener = this._editor.onDidType(text => {
const lastChar = text.charAt(text.length - 1);
const supports = supportsByTriggerCharacter[lastChar];
const checkTriggerCharacter = (text?: string) => {
if (!text) {
// came here from the compositionEnd-event
const position = this._editor.getPosition()!;
const model = this._editor.getModel()!;
text = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
}
let lastChar = '';
if (isLowSurrogate(text.charCodeAt(text.length - 1))) {
if (isHighSurrogate(text.charCodeAt(text.length - 2))) {
lastChar = text.substr(text.length - 2);
}
} else {
lastChar = text.charAt(text.length - 1);
}
const supports = supportsByTriggerCharacter.get(lastChar);
if (supports) { if (supports) {
// keep existing items that where not computed by the // keep existing items that where not computed by the
// supports/providers that want to trigger now // supports/providers that want to trigger now
const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined; const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined;
this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items); this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items);
} }
}); };
this._triggerCharacterListener.add(this._editor.onDidType(checkTriggerCharacter));
this._triggerCharacterListener.add(this._editor.onCompositionEnd(checkTriggerCharacter));
} }
// --- trigger/retrigger/cancel suggest // --- trigger/retrigger/cancel suggest

View File

@@ -11,7 +11,7 @@ import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/err
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, IActionOptions, registerDefaultLanguageCommand, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { Position } from 'vs/editor/common/core/position'; import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
@@ -155,7 +155,7 @@ function computeOccurencesAtPosition(model: ITextModel, selection: Selection, wo
return new TextualOccurenceAtPositionRequest(model, selection, wordSeparators); return new TextualOccurenceAtPositionRequest(model, selection, wordSeparators);
} }
registerDefaultLanguageCommand('_executeDocumentHighlights', (model, position) => getOccurrencesAtPosition(model, position, CancellationToken.None)); registerModelAndPositionCommand('_executeDocumentHighlights', (model, position) => getOccurrencesAtPosition(model, position, CancellationToken.None));
class WordHighlighter { class WordHighlighter {

View File

@@ -305,6 +305,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target); let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
if (shouldPreventDefault) { if (shouldPreventDefault) {
keyEvent.preventDefault(); keyEvent.preventDefault();
keyEvent.stopPropagation();
} }
})); }));
} }

View File

@@ -424,7 +424,7 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod
*/ */
export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable { export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable {
return modes.CodeActionProviderRegistry.register(languageId, { return modes.CodeActionProviderRegistry.register(languageId, {
provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise<modes.CodeActionList> => { provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): modes.ProviderResult<modes.CodeActionList> => {
let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => { let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => {
return Range.areIntersectingOrTouching(m, range); return Range.areIntersectingOrTouching(m, range);
}); });
@@ -521,7 +521,7 @@ export interface CodeActionProvider {
/** /**
* Provide commands for the given document and range. * Provide commands for the given document and range.
*/ */
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise<modes.CodeActionList>; provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): modes.ProviderResult<modes.CodeActionList>;
} }
/** /**

View File

@@ -60,6 +60,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
verticalHasArrows: input.verticalScrollbarHasArrows, verticalHasArrows: input.verticalScrollbarHasArrows,
horizontalHasArrows: false, horizontalHasArrows: false,
handleMouseWheel: EditorOptions.scrollbar.defaultValue.handleMouseWheel, handleMouseWheel: EditorOptions.scrollbar.defaultValue.handleMouseWheel,
alwaysConsumeMouseWheel: true,
horizontalScrollbarSize: input.horizontalScrollbarHeight, horizontalScrollbarSize: input.horizontalScrollbarHeight,
horizontalSliderSize: EditorOptions.scrollbar.defaultValue.horizontalSliderSize, horizontalSliderSize: EditorOptions.scrollbar.defaultValue.horizontalSliderSize,
verticalScrollbarSize: input.verticalScrollbarWidth, verticalScrollbarSize: input.verticalScrollbarWidth,

View File

@@ -728,7 +728,7 @@ var AMDLoader;
var result = compileWrapper.apply(this.exports, args); var result = compileWrapper.apply(this.exports, args);
// cached data aftermath // cached data aftermath
that._handleCachedData(script, scriptSource, cachedDataPath, !options.cachedData, moduleManager); that._handleCachedData(script, scriptSource, cachedDataPath, !options.cachedData, moduleManager);
that._verifyCachedData(script, scriptSource, cachedDataPath, hashData); that._verifyCachedData(script, scriptSource, cachedDataPath, hashData, moduleManager);
return result; return result;
}; };
}; };
@@ -775,7 +775,7 @@ var AMDLoader;
var scriptOpts = { filename: vmScriptPathOrUri_1, cachedData: cachedData }; var scriptOpts = { filename: vmScriptPathOrUri_1, cachedData: cachedData };
var script = _this._createAndEvalScript(moduleManager, scriptSource, scriptOpts, callback, errorback); var script = _this._createAndEvalScript(moduleManager, scriptSource, scriptOpts, callback, errorback);
_this._handleCachedData(script, scriptSource, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager); _this._handleCachedData(script, scriptSource, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager);
_this._verifyCachedData(script, scriptSource, cachedDataPath_1, hashData); _this._verifyCachedData(script, scriptSource, cachedDataPath_1, hashData, moduleManager);
}); });
} }
}; };
@@ -906,7 +906,7 @@ var AMDLoader;
}); });
} }
}; };
NodeScriptLoader.prototype._verifyCachedData = function (script, scriptSource, cachedDataPath, hashData) { NodeScriptLoader.prototype._verifyCachedData = function (script, scriptSource, cachedDataPath, hashData, moduleManager) {
var _this = this; var _this = this;
if (!hashData) { if (!hashData) {
// nothing to do // nothing to do
@@ -922,8 +922,8 @@ var AMDLoader;
// for violations of this contract. // for violations of this contract.
var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest();
if (!hashData.equals(hashDataNow)) { if (!hashData.equals(hashDataNow)) {
console.warn("FAILED TO VERIFY CACHED DATA. Deleting '" + cachedDataPath + "' now, but a RESTART IS REQUIRED"); moduleManager.getConfig().onError(new Error("FAILED TO VERIFY CACHED DATA, deleting stale '" + cachedDataPath + "' now, but a RESTART IS REQUIRED"));
_this._fs.unlink(cachedDataPath, function (err) { return console.error("FAILED to unlink: '" + cachedDataPath + "'", err); }); _this._fs.unlink(cachedDataPath, function (err) { return moduleManager.getConfig().onError(err); });
} }
}, Math.ceil(5000 * (1 + Math.random()))); }, Math.ceil(5000 * (1 + Math.random())));
}; };

15
src/vs/monaco.d.ts vendored
View File

@@ -380,6 +380,7 @@ declare namespace monaco {
export interface IMarkdownString { export interface IMarkdownString {
readonly value: string; readonly value: string;
readonly isTrusted?: boolean; readonly isTrusted?: boolean;
readonly supportThemeIcons?: boolean;
uris?: { uris?: {
[href: string]: UriComponents; [href: string]: UriComponents;
}; };
@@ -3363,6 +3364,11 @@ declare namespace monaco.editor {
* Defaults to true. * Defaults to true.
*/ */
handleMouseWheel?: boolean; handleMouseWheel?: boolean;
/**
* Always consume mouse wheel events (always call preventDefault() and stopPropagation() on the browser events).
* Defaults to true.
*/
alwaysConsumeMouseWheel?: boolean;
/** /**
* Height in pixels for the horizontal scrollbar. * Height in pixels for the horizontal scrollbar.
* Defaults to 10 (px). * Defaults to 10 (px).
@@ -3393,6 +3399,7 @@ declare namespace monaco.editor {
readonly verticalHasArrows: boolean; readonly verticalHasArrows: boolean;
readonly horizontalHasArrows: boolean; readonly horizontalHasArrows: boolean;
readonly handleMouseWheel: boolean; readonly handleMouseWheel: boolean;
readonly alwaysConsumeMouseWheel: boolean;
readonly horizontalScrollbarSize: number; readonly horizontalScrollbarSize: number;
readonly horizontalSliderSize: number; readonly horizontalSliderSize: number;
readonly verticalScrollbarSize: number; readonly verticalScrollbarSize: number;
@@ -4522,7 +4529,7 @@ declare namespace monaco.languages {
/** /**
* Provide commands for the given document and range. * Provide commands for the given document and range.
*/ */
provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeActionList | Promise<CodeActionList>; provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<CodeActionList>;
} }
/** /**
@@ -5521,9 +5528,9 @@ declare namespace monaco.languages {
} }
export interface ResourceFileEdit { export interface ResourceFileEdit {
oldUri: Uri; oldUri?: Uri;
newUri: Uri; newUri?: Uri;
options: { options?: {
overwrite?: boolean; overwrite?: boolean;
ignoreIfNotExists?: boolean; ignoreIfNotExists?: boolean;
ignoreIfExists?: boolean; ignoreIfExists?: boolean;

View File

@@ -796,6 +796,7 @@ export interface IFilesConfiguration {
eol: string; eol: string;
enableTrash: boolean; enableTrash: boolean;
hotExit: string; hotExit: string;
preventSaveConflicts: boolean;
}; };
} }

View File

@@ -484,15 +484,15 @@ export class DiskFileSystemProvider extends Disposable implements
//#region File Watching //#region File Watching
private _onDidWatchErrorOccur: Emitter<string> = this._register(new Emitter<string>()); private _onDidWatchErrorOccur = this._register(new Emitter<string>());
readonly onDidErrorOccur: Event<string> = this._onDidWatchErrorOccur.event; readonly onDidErrorOccur = this._onDidWatchErrorOccur.event;
private _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>()); private _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
get onDidChangeFile(): Event<readonly IFileChange[]> { return this._onDidChangeFile.event; } readonly onDidChangeFile = this._onDidChangeFile.event;
private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined; private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined;
private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = []; private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = [];
private recursiveWatchRequestDelayer: ThrottledDelayer<void> = this._register(new ThrottledDelayer<void>(0)); private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(0));
private recursiveWatcherLogLevelListener: IDisposable | undefined; private recursiveWatcherLogLevelListener: IDisposable | undefined;

View File

@@ -42,7 +42,7 @@ suite('Files', () => {
assert.strictEqual(true, r1.gotDeleted()); assert.strictEqual(true, r1.gotDeleted());
}); });
function testIsEqual(testMethod: (pA: string | undefined, pB: string, ignoreCase: boolean) => boolean): void { function testIsEqual(testMethod: (pA: string, pB: string, ignoreCase: boolean) => boolean): void {
// corner cases // corner cases
assert(testMethod('', '', true)); assert(testMethod('', '', true));
@@ -136,7 +136,7 @@ suite('Files', () => {
test('isEqualOrParent (ignorecase)', function () { test('isEqualOrParent (ignorecase)', function () {
// same assertions apply as with isEqual() // same assertions apply as with isEqual()
testIsEqual(isEqualOrParent); testIsEqual(isEqualOrParent); //
if (isWindows) { if (isWindows) {
assert(isEqualOrParent('c:\\some\\path', 'c:\\', true)); assert(isEqualOrParent('c:\\some\\path', 'c:\\', true));
@@ -182,4 +182,4 @@ suite('Files', () => {
assert(!isEqualOrParent('foo/bar/test.ts', 'foo/BAR/test.', true)); assert(!isEqualOrParent('foo/bar/test.ts', 'foo/BAR/test.', true));
} }
}); });
}); });

View File

@@ -22,7 +22,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { attachListStyler, computeStyles, defaultListStyles, IColorMapping, attachStyler } from 'vs/platform/theme/common/styler'; import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
@@ -287,7 +287,7 @@ export class WorkbenchList<T> extends List<T> {
this.disposables.add((listService as ListService).register(this)); this.disposables.add((listService as ListService).register(this));
if (options.overrideStyles) { if (options.overrideStyles) {
this.disposables.add(attachStyler(themeService, options.overrideStyles, this)); this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
} }
this.disposables.add(this.onSelectionChange(() => { this.disposables.add(this.onSelectionChange(() => {
@@ -368,7 +368,7 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
this.disposables.add((listService as ListService).register(this)); this.disposables.add((listService as ListService).register(this));
if (options.overrideStyles) { if (options.overrideStyles) {
this.disposables.add(attachStyler(themeService, options.overrideStyles, this)); this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
} }
this.registerListeners(); this.registerListeners();
@@ -1044,7 +1044,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
this.disposables.push( this.disposables.push(
this.contextKeyService, this.contextKeyService,
(listService as ListService).register(tree), (listService as ListService).register(tree),
overrideStyles ? attachStyler(themeService, overrideStyles, tree) : Disposable.None, overrideStyles ? attachListStyler(tree, themeService, overrideStyles) : Disposable.None,
tree.onDidChangeSelection(() => { tree.onDidChangeSelection(() => {
const selection = tree.getSelection(); const selection = tree.getSelection();
const focus = tree.getFocus(); const focus = tree.getFocus();

View File

@@ -102,7 +102,7 @@ export interface IProductConfiguration {
readonly portable?: string; readonly portable?: string;
readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind | ExtensionKind[]; }; readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind[]; };
readonly extensionAllowedProposedApi?: readonly string[]; readonly extensionAllowedProposedApi?: readonly string[];
readonly msftInternalDomains?: string[]; readonly msftInternalDomains?: string[];

View File

@@ -11,8 +11,9 @@ export const ITunnelService = createDecorator<ITunnelService>('tunnelService');
export interface RemoteTunnel { export interface RemoteTunnel {
readonly tunnelRemotePort: number; readonly tunnelRemotePort: number;
readonly tunnelRemoteHost: string;
readonly tunnelLocalPort: number; readonly tunnelLocalPort: number;
readonly localAddress?: string; readonly localAddress: string;
dispose(): void; dispose(): void;
} }

View File

@@ -5,11 +5,19 @@
import { ISignService } from 'vs/platform/sign/common/sign'; import { ISignService } from 'vs/platform/sign/common/sign';
/* tslint:disable */
declare module vsda {
export class signer {
sign(arg: any): any;
}
}
export class SignService implements ISignService { export class SignService implements ISignService {
_serviceBrand: undefined; _serviceBrand: undefined;
private vsda(): Promise<typeof import('vsda')> { private vsda(): Promise<typeof vsda> {
return import('vsda'); return new Promise((resolve, reject) => require(['vsda'], resolve, reject));
} }
async sign(value: string): Promise<string> { async sign(value: string): Promise<string> {
@@ -20,9 +28,8 @@ export class SignService implements ISignService {
return signer.sign(value); return signer.sign(value);
} }
} catch (e) { } catch (e) {
console.error('signer.sign: ' + e); // ignore errors silently
} }
return value; return value;
} }
} }

View File

@@ -11,16 +11,16 @@ import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
import { IStateService } from 'vs/platform/state/node/state'; import { IStateService } from 'vs/platform/state/node/state';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
type StorageDatebase = { [key: string]: any; }; type StorageDatabase = { [key: string]: any; };
export class FileStorage { export class FileStorage {
private _database: StorageDatebase | null = null; private _database: StorageDatabase | null = null;
private lastFlushedSerializedDatabase: string | null = null; private lastFlushedSerializedDatabase: string | null = null;
constructor(private dbPath: string, private onError: (error: Error) => void) { } constructor(private dbPath: string, private onError: (error: Error) => void) { }
private get database(): StorageDatebase { private get database(): StorageDatabase {
if (!this._database) { if (!this._database) {
this._database = this.loadSync(); this._database = this.loadSync();
} }
@@ -42,7 +42,7 @@ export class FileStorage {
this._database = database; this._database = database;
} }
private loadSync(): StorageDatebase { private loadSync(): StorageDatabase {
try { try {
this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString(); this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString();
@@ -56,7 +56,7 @@ export class FileStorage {
} }
} }
private async loadAsync(): Promise<StorageDatebase> { private async loadAsync(): Promise<StorageDatabase> {
try { try {
this.lastFlushedSerializedDatabase = (await readFile(this.dbPath)).toString(); this.lastFlushedSerializedDatabase = (await readFile(this.dbPath)).toString();

View File

@@ -0,0 +1,169 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { values, keys } from 'vs/base/common/map';
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { startsWith } from 'vs/base/common/strings';
export interface IMergeResult {
added: ISyncExtension[];
removed: IExtensionIdentifier[];
updated: ISyncExtension[];
remote: ISyncExtension[] | null;
}
export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[], ignoredExtensions: string[]): IMergeResult {
const added: ISyncExtension[] = [];
const removed: IExtensionIdentifier[] = [];
const updated: ISyncExtension[] = [];
if (!remoteExtensions) {
return {
added,
removed,
updated,
remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase()))
};
}
const uuids: Map<string, string> = new Map<string, string>();
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
localExtensions.forEach(({ identifier }) => addUUID(identifier));
remoteExtensions.forEach(({ identifier }) => addUUID(identifier));
if (lastSyncExtensions) {
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
}
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
map.set(key, extension);
return map;
};
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
const uuid = uuids.get(id.toLowerCase());
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
}, new Set<string>());
const localToRemote = compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return { added: [], removed: [], updated: [], remote: null };
}
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
const massagedExtension: ISyncExtension = {
identifier: {
id: extension.identifier.id,
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
},
enabled: extension.enabled,
};
if (extension.version) {
massagedExtension.version = extension.version;
}
return massagedExtension;
};
// Remotely removed extension.
for (const key of values(baseToRemote.removed)) {
const e = localExtensionsMap.get(key);
if (e) {
removed.push(e.identifier);
}
}
// Remotely added extension
for (const key of values(baseToRemote.added)) {
// Got added in local
if (baseToLocal.added.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
}
} else {
// Add to local
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
}
}
// Remotely updated extensions
for (const key of values(baseToRemote.updated)) {
// If updated in local
if (baseToLocal.updated.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
// update it in local
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
}
}
}
// Locally added extensions
for (const key of values(baseToLocal.added)) {
// Not there in remote
if (!baseToRemote.added.has(key)) {
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
}
}
// Locally updated extensions
for (const key of values(baseToLocal.updated)) {
// If removed in remote
if (baseToRemote.removed.has(key)) {
continue;
}
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
}
}
// Locally removed extensions
for (const key of values(baseToLocal.removed)) {
// If not skipped and not updated in remote
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.delete(key);
}
}
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
return { added, removed, updated, remote };
}
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = from ? keys(from).filter(key => !ignoredExtensions.has(key)) : [];
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const updated: Set<string> = new Set<string>();
for (const key of fromKeys) {
if (removed.has(key)) {
continue;
}
const fromExtension = from!.get(key)!;
const toExtension = to.get(key);
if (!toExtension
|| fromExtension.enabled !== toExtension.enabled
|| fromExtension.version !== toExtension.version
) {
updated.add(key);
}
}
return { added, removed, updated };
}

View File

@@ -13,12 +13,11 @@ import { joinPath } from 'vs/base/common/resources';
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { keys, values } from 'vs/base/common/map';
import { startsWith } from 'vs/base/common/strings';
import { IFileService } from 'vs/platform/files/common/files'; import { IFileService } from 'vs/platform/files/common/files';
import { Queue } from 'vs/base/common/async'; import { Queue } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
export interface ISyncPreviewResult { export interface ISyncPreviewResult {
readonly added: ISyncExtension[]; readonly added: ISyncExtension[];
@@ -135,8 +134,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
const localExtensions = await this.getLocalExtensions(); const localExtensions = await this.getLocalExtensions();
this.logService.trace('Extensions: Merging remote extensions with local extensions...'); if (remoteExtensions) {
const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions); this.logService.trace('Extensions: Merging remote extensions with local extensions...');
} else {
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
}
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions);
if (!added.length && !removed.length && !updated.length && !remote) { if (!added.length && !removed.length && !updated.length && !remote) {
this.logService.trace('Extensions: No changes found during synchronizing extensions.'); this.logService.trace('Extensions: No changes found during synchronizing extensions.');
@@ -162,160 +167,6 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
} }
} }
/**
* Merge Strategy:
* - If remote does not exist, merge with local (First time sync)
* - Overwrite local with remote changes. Removed, Added, Updated.
* - Update remote with those local extension which are newly added or updated or removed and untouched in remote.
*/
private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[]): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } {
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
// First time sync
if (!remoteExtensions) {
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase())) };
}
const uuids: Map<string, string> = new Map<string, string>();
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
localExtensions.forEach(({ identifier }) => addUUID(identifier));
remoteExtensions.forEach(({ identifier }) => addUUID(identifier));
if (lastSyncExtensions) {
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
}
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
map.set(key, extension);
return map;
};
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
const uuid = uuids.get(id.toLowerCase());
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
}, new Set<string>());
const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
// No changes found between local and remote.
return { added: [], removed: [], updated: [], remote: null };
}
const added: ISyncExtension[] = [];
const removed: IExtensionIdentifier[] = [];
const updated: ISyncExtension[] = [];
const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
return {
identifier: {
id: extension.identifier.id,
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
},
enabled: extension.enabled,
version: extension.version
};
};
// Remotely removed extension.
for (const key of values(baseToRemote.removed)) {
const e = localExtensionsMap.get(key);
if (e) {
removed.push(e.identifier);
}
}
// Remotely added extension
for (const key of values(baseToRemote.added)) {
// Got added in local
if (baseToLocal.added.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
}
} else {
// Add to local
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
}
}
// Remotely updated extensions
for (const key of values(baseToRemote.updated)) {
// If updated in local
if (baseToLocal.updated.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
// update it in local
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
}
}
}
// Locally added extensions
for (const key of values(baseToLocal.added)) {
// Not there in remote
if (!baseToRemote.added.has(key)) {
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
}
}
// Locally updated extensions
for (const key of values(baseToLocal.updated)) {
// If removed in remote
if (baseToRemote.removed.has(key)) {
continue;
}
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
}
}
// Locally removed extensions
for (const key of values(baseToLocal.removed)) {
// If not skipped and not updated in remote
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.delete(key);
}
}
const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
return { added, removed, updated, remote };
}
private compare(from: Map<string, ISyncExtension>, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = keys(from).filter(key => !ignoredExtensions.has(key));
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const updated: Set<string> = new Set<string>();
for (const key of fromKeys) {
if (removed.has(key)) {
continue;
}
const fromExtension = from.get(key)!;
const toExtension = to.get(key);
if (!toExtension
|| fromExtension.enabled !== toExtension.enabled
|| fromExtension.version !== toExtension.version
) {
updated.add(key);
}
}
return { added, removed, updated };
}
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> { private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> {
const removeFromSkipped: IExtensionIdentifier[] = []; const removeFromSkipped: IExtensionIdentifier[] = [];
const addToSkipped: ISyncExtension[] = []; const addToSkipped: ISyncExtension[] = [];

View File

@@ -19,6 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { OS, OperatingSystem } from 'vs/base/common/platform'; import { OS, OperatingSystem } from 'vs/base/common/platform';
import { isUndefined } from 'vs/base/common/types'; import { isUndefined } from 'vs/base/common/types';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
interface ISyncContent { interface ISyncContent {
mac?: string; mac?: string;
@@ -217,7 +218,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|| lastSyncContent !== remoteContent // Remote has forwarded || lastSyncContent !== remoteContent // Remote has forwarded
) { ) {
this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...'); this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...');
const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource); const formattingOptions = await this.getFormattingOptions();
const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService); const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService);
// Sync only if there are changes // Sync only if there are changes
if (result.hasChanges) { if (result.hasChanges) {
@@ -243,6 +244,14 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
} }
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
private getFormattingOptions(): Promise<FormattingOptions> {
if (!this._formattingOptions) {
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource);
}
return this._formattingOptions;
}
private async getLocalContent(): Promise<IFileContent | null> { private async getLocalContent(): Promise<IFileContent | null> {
try { try {
return await this.fileService.readFile(this.environmentService.keybindingsResource); return await this.fileService.readFile(this.environmentService.keybindingsResource);

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { IStringDictionary } from 'vs/base/common/collections';
import { URI } from 'vs/base/common/uri';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
export class UserDataSycnUtilServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncUtilService) { }
listen(_: unknown, event: string): Event<any> {
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
}
throw new Error('Invalid call');
}
}
export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
_serviceBrand: undefined;
constructor(private readonly channel: IChannel) {
}
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
return this.channel.call('resolveUserKeybindings', [userbindings]);
}
async resolveFormattingOptions(file: URI): Promise<FormattingOptions> {
return this.channel.call('resolveFormattingOptions', [file]);
}
}

View File

@@ -0,0 +1,191 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as objects from 'vs/base/common/objects';
import { parse, findNodeAtLocation, parseTree, Node } from 'vs/base/common/json';
import { setProperty } from 'vs/base/common/jsonEdit';
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';
export function computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string {
if (ignoredSettings.length) {
const remote = parse(remoteContent);
const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set<string>());
for (const key of ignoredSettings) {
if (ignored.has(key)) {
localContent = contentUtil.edit(localContent, [key], remote[key], formattingOptions);
}
}
}
return localContent;
}
export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } {
const local = parse(localContent);
const remote = parse(remoteContent);
const base = baseContent ? parse(baseContent) : null;
const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set<string>());
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 };
}
const conflicts: 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;
// Removed settings in Local
for (const key of values(baseToLocal.removed)) {
// Got updated in remote
if (baseToRemote.updated.has(key)) {
conflicts.add(key);
}
}
// Removed settings in Remote
for (const key of values(baseToRemote.removed)) {
if (conflicts.has(key)) {
continue;
}
// Got updated in local
if (baseToLocal.updated.has(key)) {
conflicts.add(key);
} else {
mergeContent = contentUtil.edit(mergeContent, [key], undefined, formattingOptions);
}
}
// Added settings in Local
for (const key of values(baseToLocal.added)) {
if (conflicts.has(key)) {
continue;
}
// Got added in remote
if (baseToRemote.added.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
}
}
}
// Added settings in remote
for (const key of values(baseToRemote.added)) {
if (conflicts.has(key)) {
continue;
}
// Got added in local
if (baseToLocal.added.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
}
} else {
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
}
}
// Updated settings in Local
for (const key of values(baseToLocal.updated)) {
if (conflicts.has(key)) {
continue;
}
// Got updated in remote
if (baseToRemote.updated.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
}
}
}
// Updated settings in Remote
for (const key of values(baseToRemote.updated)) {
if (conflicts.has(key)) {
continue;
}
// Got updated in local
if (baseToLocal.updated.has(key)) {
// Has different value
if (localToRemote.updated.has(key)) {
conflicts.add(key);
}
} else {
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
}
}
if (conflicts.size > 0) {
const conflictNodes: { key: string, node: Node | undefined }[] = [];
const tree = parseTree(mergeContent);
const eol = formattingOptions.eol!;
for (const key of values(conflicts)) {
const node = findNodeAtLocation(tree, [key]);
conflictNodes.push({ key, node });
}
conflictNodes.sort((a, b) => {
if (a.node && b.node) {
return b.node.offset - a.node.offset;
}
return a.node ? 1 : -1;
});
const lastNode = tree.children ? tree.children[tree.children.length - 1] : undefined;
for (const { key, node } of conflictNodes) {
const remoteEdit = setProperty(`{${eol}\t${eol}}`, [key], remote[key], { tabSize: 4, insertSpaces: false, eol: eol })[0];
const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : '';
if (node) {
// Updated in Local and Remote with different value
const localStartOffset = contentUtil.getLineStartOffset(mergeContent, eol, node.parent!.offset);
const localEndOffset = contentUtil.getLineEndOffset(mergeContent, eol, node.offset + node.length);
mergeContent = mergeContent.substring(0, localStartOffset)
+ `<<<<<<< local${eol}`
+ mergeContent.substring(localStartOffset, localEndOffset)
+ `${eol}=======${eol}${remoteContent}>>>>>>> remote`
+ mergeContent.substring(localEndOffset);
} else {
// Removed in Local, but updated in Remote
if (lastNode) {
const localStartOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastNode.offset + lastNode.length);
mergeContent = mergeContent.substring(0, localStartOffset)
+ `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`
+ mergeContent.substring(localStartOffset);
} else {
const localStartOffset = tree.offset + 1;
mergeContent = mergeContent.substring(0, localStartOffset)
+ `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote${eol}`
+ mergeContent.substring(localStartOffset);
}
}
}
}
return { mergeContent, hasChanges: true, hasConflicts: conflicts.size > 0 };
}
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
const fromKeys = Object.keys(from).filter(key => !ignored.has(key));
const toKeys = Object.keys(to).filter(key => !ignored.has(key));
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
const updated: Set<string> = new Set<string>();
for (const key of fromKeys) {
if (removed.has(key)) {
continue;
}
const value1 = from[key];
const value2 = to[key];
if (!objects.equals(value1, value2)) {
updated.add(key);
}
}
return { added, removed, updated };
}

View File

@@ -5,7 +5,7 @@
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { parse, ParseError } from 'vs/base/common/json'; import { parse, ParseError } from 'vs/base/common/json';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
@@ -17,6 +17,8 @@ import { joinPath, dirname } from 'vs/base/common/resources';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { startsWith } from 'vs/base/common/strings'; import { startsWith } from 'vs/base/common/strings';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
interface ISyncPreviewResult { interface ISyncPreviewResult {
readonly fileContent: IFileContent | null; readonly fileContent: IFileContent | null;
@@ -46,8 +48,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
@IFileService private readonly fileService: IFileService, @IFileService private readonly fileService: IFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService, @IEnvironmentService private readonly environmentService: IEnvironmentService,
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
@ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService,
@IConfigurationService private readonly configurationService: IConfigurationService, @IConfigurationService private readonly configurationService: IConfigurationService,
) { ) {
super(); super();
@@ -148,7 +150,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
await this.writeToLocal(content, fileContent); await this.writeToLocal(content, fileContent);
} }
if (hasRemoteChanged) { if (hasRemoteChanged) {
const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content)) : content; const formatUtils = await this.getFormattingOptions();
const remoteContent = remoteUserData.content ? computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content), formatUtils) : content;
this.logService.info('Settings: Updating remote settings'); this.logService.info('Settings: Updating remote settings');
const ref = await this.writeToRemote(remoteContent, remoteUserData.ref); const ref = await this.writeToRemote(remoteContent, remoteUserData.ref);
remoteUserData = { ref, content }; remoteUserData = { ref, content };
@@ -205,7 +208,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|| lastSyncData.content !== remoteContent // Remote has forwarded || lastSyncData.content !== remoteContent // Remote has forwarded
) { ) {
this.logService.trace('Settings: Merging remote settings with local settings...'); this.logService.trace('Settings: Merging remote settings with local settings...');
const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings()); const formatUtils = await this.getFormattingOptions();
const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), formatUtils);
// Sync only if there are changes // Sync only if there are changes
if (result.hasChanges) { if (result.hasChanges) {
hasLocalChanged = result.mergeContent !== localContent; hasLocalChanged = result.mergeContent !== localContent;
@@ -230,6 +234,14 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
} }
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
private getFormattingOptions(): Promise<FormattingOptions> {
if (!this._formattingOptions) {
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource);
}
return this._formattingOptions;
}
private getIgnoredSettings(settingsContent?: string): string[] { private getIgnoredSettings(settingsContent?: string): string[] {
let value: string[] = []; let value: string[] = [];
if (settingsContent) { if (settingsContent) {

View File

@@ -1,42 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
export class SettingsMergeChannel implements IServerChannel {
constructor(private readonly service: ISettingsMergeService) { }
listen(_: unknown, event: string): Event<any> {
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'merge': return this.service.merge(args[0], args[1], args[2], args[3]);
case 'computeRemoteContent': return this.service.computeRemoteContent(args[0], args[1], args[2]);
}
throw new Error('Invalid call');
}
}
export class SettingsMergeChannelClient implements ISettingsMergeService {
_serviceBrand: undefined;
constructor(private readonly channel: IChannel) {
}
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
return this.channel.call('merge', [localContent, remoteContent, baseContent, ignoredSettings]);
}
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string> {
return this.channel.call('computeRemoteContent', [localContent, remoteContent, ignoredSettings]);
}
}

View File

@@ -177,18 +177,6 @@ export interface IUserDataSyncService extends ISynchroniser {
removeExtension(identifier: IExtensionIdentifier): Promise<void>; removeExtension(identifier: IExtensionIdentifier): Promise<void>;
} }
export const ISettingsMergeService = createDecorator<ISettingsMergeService>('ISettingsMergeService');
export interface ISettingsMergeService {
_serviceBrand: undefined;
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>;
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string>;
}
export const IUserDataSyncUtilService = createDecorator<IUserDataSyncUtilService>('IUserDataSyncUtilService'); export const IUserDataSyncUtilService = createDecorator<IUserDataSyncUtilService>('IUserDataSyncUtilService');
export interface IUserDataSyncUtilService { export interface IUserDataSyncUtilService {

View File

@@ -3,9 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncService, IUserDataSyncUtilService } 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';
export class UserDataSyncChannel implements IServerChannel { export class UserDataSyncChannel implements IServerChannel {
@@ -30,3 +33,38 @@ export class UserDataSyncChannel implements IServerChannel {
throw new Error('Invalid call'); throw new Error('Invalid call');
} }
} }
export class UserDataSycnUtilServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncUtilService) { }
listen(_: unknown, event: string): Event<any> {
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
}
throw new Error('Invalid call');
}
}
export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
_serviceBrand: undefined;
constructor(private readonly channel: IChannel) {
}
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
return this.channel.call('resolveUserKeybindings', [userbindings]);
}
async resolveFormattingOptions(file: URI): Promise<FormattingOptions> {
return this.channel.call('resolveFormattingOptions', [file]);
}
}

View File

@@ -0,0 +1,500 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
suite('ExtensionsMerge - No Conflicts', () => {
test('merge returns local extension if remote does not exist', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, null, null, [], []);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, localExtensions);
});
test('merge returns local extension if remote does not exist with ignored extensions', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, null, null, [], ['a']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, null, null, [], ['A']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge returns local extension if remote does not exist with skipped extensions', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, null, null, skippedExtension, []);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge returns local extension if remote does not exist with skipped and ignored extensions', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, null, null, skippedExtension, ['a']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when there is no base', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when there is no base and with ignored extensions', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, null, [], ['a']);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when remote is moved forwarded', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
});
test('merge local and remote extensions when remote moved forwarded with ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
});
test('merge local and remote extensions when remote is moved forwarded with skipped extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
});
test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']);
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
});
test('merge local and remote extensions when local is moved forwarded', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, localExtensions);
});
test('merge local and remote extensions when local is moved forwarded with ignored settings', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, [
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
]);
});
test('merge local and remote extensions when local is moved forwarded with skipped extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when both moved forwarded', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when both moved forwarded with ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when both moved forwarded with skipped extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']);
assert.deepEqual(actual.added, []);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
test('merge when remote extension has no uuid and different extension id case', async () => {
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'A' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'A' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, enabled: true }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
});
});

View File

@@ -6,7 +6,6 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
import { IStringDictionary } from 'vs/base/common/collections'; import { IStringDictionary } from 'vs/base/common/collections';
import { OperatingSystem, OS } from 'vs/base/common/platform';
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
@@ -441,7 +440,7 @@ suite('KeybindingsMerge - No Conflicts', () => {
}); });
suite.skip('KeybindingsMerge - Conflicts', () => { suite('KeybindingsMerge - Conflicts', () => {
test('merge when local and remote with one entry but different value', async () => { test('merge when local and remote with one entry but different value', async () => {
const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]); const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
@@ -730,6 +729,6 @@ class MockUserDataSyncUtilService implements IUserDataSyncUtilService {
} }
async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> { async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> {
return { eol: OS === OperatingSystem.Windows ? '\r\n' : '\n', insertSpaces: false, tabSize: 4 }; return { eol: '\n', insertSpaces: false, tabSize: 4 };
} }
} }

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