Merge remote-tracking branch 'origin/master' into joao/scm-menu-actions

This commit is contained in:
João Moreno 2021-02-04 10:04:47 +01:00
commit a0d9df38ad
No known key found for this signature in database
GPG key ID: 896B853774D1A575
53 changed files with 751 additions and 504 deletions

View file

@ -1,3 +1,3 @@
disturl "https://electronjs.org/headers"
target "11.2.1"
target "11.2.2"
runtime "electron"

View file

@ -60,12 +60,12 @@
"git": {
"name": "electron",
"repositoryUrl": "https://github.com/electron/electron",
"commitHash": "8805b996e0d8cfb6e3921f9b586366bafb125b59"
"commitHash": "805e442ff873e10735a1ea18021f491597afa885"
}
},
"isOnlyProductionDependency": true,
"license": "MIT",
"version": "11.2.1"
"version": "11.2.2"
},
{
"component": {

View file

@ -439,7 +439,7 @@
"@emmetio/html-matcher": "^0.3.3",
"@emmetio/math-expression": "^1.0.4",
"image-size": "^0.5.2",
"vscode-emmet-helper": "2.2.4-2",
"vscode-emmet-helper": "2.2.4-3",
"vscode-languageserver-textdocument": "^1.0.1"
}
}

View file

@ -54,9 +54,9 @@
integrity sha1-Rs/+oRmgoAMxKiHC2bVijLX81EI=
"@types/node@^12.19.9":
version "12.19.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.12.tgz#04793c2afa4ce833a9972e4c476432e30f9df47b"
integrity sha512-UwfL2uIU9arX/+/PRcIkT08/iBadGN2z6ExOROA2Dh5mAuWTBj6iJbQX4nekiV5H8cTrEG569LeX+HRco9Cbxw==
version "12.19.15"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.15.tgz#0de7e978fb43db62da369db18ea088a63673c182"
integrity sha512-lowukE3GUI+VSYSu6VcBXl14d61Rp5hA1D+61r16qnwC0lYNSqdxcvRh0pswejorHfS+HgwBasM8jLXz0/aOsw==
"emmet@https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a":
version "2.3.0"
@ -75,10 +75,10 @@ jsonc-parser@^2.3.0:
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==
vscode-emmet-helper@2.2.4-2:
version "2.2.4-2"
resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.2.4-2.tgz#8019188077a91dbe9a8d8c10c0b79369bb5c24d6"
integrity sha512-7UTZXwt9M1xwaV72o2YgSBVoghtDtscTgYTOl1kiPkXN9OKiM4N52hcHFA1LlRtdTvIQd4PEkgaz57F9ZT/4kg==
vscode-emmet-helper@2.2.4-3:
version "2.2.4-3"
resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.2.4-3.tgz#b1aed7eaaf0ecc2480c65c819f6e9d42d2d3cf5a"
integrity sha512-M+pAiAro8nRiHb0/9lRoRzBod6DPlSwhOX26ZfM8n9a7/zr9ZJojxohDyGmnsApU5xVPhcrsNVI/fEagSeeO0Q==
dependencies:
emmet "https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a"
jsonc-parser "^2.3.0"

View file

@ -50,7 +50,6 @@ suite('vscode', function () {
});
test('no rpc, createStatusBarItem(...)', function () {
this.skip();
const item = vscode.window.createStatusBarItem();
dispo.push(item);
assertNoRpcFromEntry([item, 'StatusBarItem']);

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.54.0",
"distro": "ef4e25f8dccec71b96ee8c6d0a770f1e53bcf84c",
"distro": "3b4f6074b6501e7e9f09a53dcaffce592d264b77",
"author": {
"name": "Microsoft Corporation"
},
@ -46,7 +46,7 @@
"compile-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-web",
"watch-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-web",
"eslint": "node build/eslint",
"electron-rebuild": "electron-rebuild --arch=arm64 --force --version=11.2.1",
"electron-rebuild": "electron-rebuild --arch=arm64 --force --version=11.2.2",
"playwright-install": "node build/azure-pipelines/common/installPlaywright.js",
"compile-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-build",
"compile-extensions-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-extensions-build",
@ -82,10 +82,10 @@
"vscode-ripgrep": "^1.11.1",
"vscode-sqlite3": "4.0.10",
"vscode-textmate": "5.2.0",
"xterm": "4.10.0-beta.39",
"xterm-addon-search": "0.8.0-beta.3",
"xterm": "4.11.0-beta.2",
"xterm-addon-search": "0.8.0",
"xterm-addon-unicode11": "0.3.0-beta.3",
"xterm-addon-webgl": "0.10.0-beta.1",
"xterm-addon-webgl": "0.10.0-beta.2",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
@ -125,7 +125,7 @@
"cssnano": "^4.1.10",
"debounce": "^1.0.0",
"deemon": "^1.4.0",
"electron": "11.2.1",
"electron": "11.2.2",
"electron-rebuild": "2.0.3",
"eslint": "6.8.0",
"eslint-plugin-jsdoc": "^19.1.0",

View file

@ -22,10 +22,10 @@
"vscode-regexpp": "^3.1.0",
"vscode-ripgrep": "^1.11.1",
"vscode-textmate": "5.2.0",
"xterm": "4.10.0-beta.39",
"xterm-addon-search": "0.8.0-beta.3",
"xterm": "4.11.0-beta.2",
"xterm-addon-search": "0.8.0",
"xterm-addon-unicode11": "0.3.0-beta.3",
"xterm-addon-webgl": "0.10.0-beta.1",
"xterm-addon-webgl": "0.10.0-beta.2",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},

View file

@ -8,9 +8,9 @@
"tas-client-umd": "0.1.2",
"vscode-oniguruma": "1.3.1",
"vscode-textmate": "5.2.0",
"xterm": "4.10.0-beta.39",
"xterm-addon-search": "0.8.0-beta.3",
"xterm": "4.11.0-beta.2",
"xterm-addon-search": "0.8.0",
"xterm-addon-unicode11": "0.3.0-beta.3",
"xterm-addon-webgl": "0.10.0-beta.1"
"xterm-addon-webgl": "0.10.0-beta.2"
}
}

View file

@ -27,22 +27,22 @@ vscode-textmate@5.2.0:
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e"
integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==
xterm-addon-search@0.8.0-beta.3:
version "0.8.0-beta.3"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0-beta.3.tgz#c6c7e36a03706bd43d8bba383511acf9e435aed0"
integrity sha512-EZP97KJIJ4KGQaOPYiiOaRRJst6LOgeEFoQL46WcBl5EWH9pH8qfrv0BHAJ8+6nBV2B9u5M6rzxO1GvLLec19w==
xterm-addon-search@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0.tgz#e33eab918df7eac7e7baf95dd2b3d14133754881"
integrity sha512-MPJGPVPpHRUw9cLIuqQbrVepmENMOybVUSxIALz5h1ryyQBrVqVujq2hL5aroX5/dZJoHx9lGHQTVLQ07SKgKA==
xterm-addon-unicode11@0.3.0-beta.3:
version "0.3.0-beta.3"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.3.tgz#70af2dfb67809258edb62c19e2861f7ce5ccf5cd"
integrity sha512-vaYopnOjn19wCLDCyIWPWLwKR7CvLPxB5YZ3CAxt9qL05o3symxIJJJC0DuCa4GaGKVjNc7EmjRCs5bsJ2O1tw==
xterm-addon-webgl@0.10.0-beta.1:
version "0.10.0-beta.1"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0-beta.1.tgz#e0bf964945a9aa8fc18318ddbd32e56ec99c219e"
integrity sha512-XNZMrmiyFaz3XiPq+LqF0qn2QHpUEwuk+cG53JwpJHnWo3dd2jxoIgHFQUcrnvHIVPZMbTKySIwLCCC9uQVl7Q==
xterm-addon-webgl@0.10.0-beta.2:
version "0.10.0-beta.2"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0-beta.2.tgz#520547b2f845b2f9265f1817140b0f4e114c4a55"
integrity sha512-DLfmF5+H1M/0BABEaqJlLUasKck7TjyRWVlzGflFTWVCr7/Kqaf0al4KMlw3yWX76A1ITGXrWj0qbDn2Sixl2Q==
xterm@4.10.0-beta.39:
version "4.10.0-beta.39"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.10.0-beta.39.tgz#29c5ce3ed814d04e0bf2781cf9bd09e354b6e422"
integrity sha512-0qtknUlct48WyVj8tnoVvwn4Bz2+lmVBLIGKjifTvawRccw2QVXjTa5hUgNIbecgZv8PWZhseBREtccRbbqbuA==
xterm@4.11.0-beta.2:
version "4.11.0-beta.2"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.11.0-beta.2.tgz#a95560b61c771f54a336c2eb10e1472e556d9f4b"
integrity sha512-0BUaAfuclnowirdOuB13OGgq6OUGg/8etnRVT6apgnOrLGOLRCE1NiL3KhxotleAf4gVP0m3iCxsIr3csDY40g==

View file

@ -450,25 +450,25 @@ vscode-windows-registry@1.0.2:
resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.2.tgz#b863e704a6a69c50b3098a55fbddbe595b0c124a"
integrity sha512-/CLLvuOSM2Vme2z6aNyB+4Omd7hDxpf4Thrt8ImxnXeQtxzel2bClJpFQvQqK/s4oaXlkBKS7LqVLeZM+uSVIA==
xterm-addon-search@0.8.0-beta.3:
version "0.8.0-beta.3"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0-beta.3.tgz#c6c7e36a03706bd43d8bba383511acf9e435aed0"
integrity sha512-EZP97KJIJ4KGQaOPYiiOaRRJst6LOgeEFoQL46WcBl5EWH9pH8qfrv0BHAJ8+6nBV2B9u5M6rzxO1GvLLec19w==
xterm-addon-search@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0.tgz#e33eab918df7eac7e7baf95dd2b3d14133754881"
integrity sha512-MPJGPVPpHRUw9cLIuqQbrVepmENMOybVUSxIALz5h1ryyQBrVqVujq2hL5aroX5/dZJoHx9lGHQTVLQ07SKgKA==
xterm-addon-unicode11@0.3.0-beta.3:
version "0.3.0-beta.3"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.3.tgz#70af2dfb67809258edb62c19e2861f7ce5ccf5cd"
integrity sha512-vaYopnOjn19wCLDCyIWPWLwKR7CvLPxB5YZ3CAxt9qL05o3symxIJJJC0DuCa4GaGKVjNc7EmjRCs5bsJ2O1tw==
xterm-addon-webgl@0.10.0-beta.1:
version "0.10.0-beta.1"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0-beta.1.tgz#e0bf964945a9aa8fc18318ddbd32e56ec99c219e"
integrity sha512-XNZMrmiyFaz3XiPq+LqF0qn2QHpUEwuk+cG53JwpJHnWo3dd2jxoIgHFQUcrnvHIVPZMbTKySIwLCCC9uQVl7Q==
xterm-addon-webgl@0.10.0-beta.2:
version "0.10.0-beta.2"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0-beta.2.tgz#520547b2f845b2f9265f1817140b0f4e114c4a55"
integrity sha512-DLfmF5+H1M/0BABEaqJlLUasKck7TjyRWVlzGflFTWVCr7/Kqaf0al4KMlw3yWX76A1ITGXrWj0qbDn2Sixl2Q==
xterm@4.10.0-beta.39:
version "4.10.0-beta.39"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.10.0-beta.39.tgz#29c5ce3ed814d04e0bf2781cf9bd09e354b6e422"
integrity sha512-0qtknUlct48WyVj8tnoVvwn4Bz2+lmVBLIGKjifTvawRccw2QVXjTa5hUgNIbecgZv8PWZhseBREtccRbbqbuA==
xterm@4.11.0-beta.2:
version "4.11.0-beta.2"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.11.0-beta.2.tgz#a95560b61c771f54a336c2eb10e1472e556d9f4b"
integrity sha512-0BUaAfuclnowirdOuB13OGgq6OUGg/8etnRVT6apgnOrLGOLRCE1NiL3KhxotleAf4gVP0m3iCxsIr3csDY40g==
yauzl@^2.9.2:
version "2.10.0"

View file

@ -89,7 +89,6 @@
padding: 0.4em;
font-size: 12px;
line-height: 17px;
min-height: 34px;
margin-top: -1px;
word-wrap: break-word;
}

View file

@ -292,6 +292,9 @@ export class InputBox extends Widget {
if (range) {
this.input.setSelectionRange(range.start, range.end);
if (range.end === this.input.value.length) {
this.input.scrollLeft = this.input.scrollWidth;
}
}
}

View file

@ -64,6 +64,7 @@ export interface IListViewOptions<T> extends IListViewOptionsUpdate {
readonly mouseSupport?: boolean;
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
readonly transformOptimization?: boolean;
readonly alwaysConsumeMouseWheel?: boolean;
}
const DefaultOptions = {
@ -80,7 +81,8 @@ const DefaultOptions = {
drop() { }
},
horizontalScrolling: false,
transformOptimization: true
transformOptimization: true,
alwaysConsumeMouseWheel: true,
};
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
@ -327,6 +329,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb));
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: getOrDefault(options, o => o.alwaysConsumeMouseWheel, DefaultOptions.alwaysConsumeMouseWheel),
horizontal: ScrollbarVisibility.Auto,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),

View file

@ -854,6 +854,7 @@ export interface IListOptions<T> {
readonly additionalScrollHeight?: number;
readonly transformOptimization?: boolean;
readonly smoothScrolling?: boolean;
readonly alwaysConsumeMouseWheel?: boolean;
}
export interface IListStyles {

View file

@ -103,7 +103,7 @@ export class SuggestWidget implements IDisposable {
private _focusedItem?: CompletionItem;
private _ignoreFocusEvents: boolean = false;
private _completionModel?: CompletionModel;
private _cappedHeight?: { wanted: number, capped: number };
private _cappedHeight?: { wanted: number; capped: number; };
private _explainMode: boolean = false;
readonly element: ResizableHTMLElement;
@ -217,6 +217,7 @@ export class SuggestWidget implements IDisposable {
getHeight: (_element: CompletionItem): number => this.getLayoutInfo().itemHeight,
getTemplateId: (_element: CompletionItem): string => 'suggestion'
}, [renderer], {
alwaysConsumeMouseWheel: true,
useShadows: false,
mouseSupport: false,
accessibilityProvider: {

View file

@ -81,12 +81,21 @@ function createKeywordMatcher(arr: string[], caseInsensitive: boolean = false):
/**
* Compiles a regular expression string, adding the 'i' flag if 'ignoreCase' is set, and the 'u' flag if 'unicode' is set.
* Also replaces @\w+ or sequences with the content of the specified attribute
* @\w+ replacement can be avoided by escaping `@` signs with another `@` sign.
* @example /@attr/ will be replaced with the value of lexer[attr]
* @example /@@text/ will not be replaced and will become /@text/.
*/
function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp {
let n = 0;
while (str.indexOf('@') >= 0 && n < 5) { // at most 5 expansions
n++;
str = str.replace(/@(\w+)/g, function (s, attr?) {
let hadExpansion: boolean;
do {
hadExpansion = false;
str = str.replace(/(.|^)@(\w+)/g, function (s, charBeforeAtSign, attr?) {
if (charBeforeAtSign === '@') {
// do not expand @@
return s;
}
hadExpansion = true;
let sub = '';
if (typeof (lexer[attr]) === 'string') {
sub = lexer[attr];
@ -99,9 +108,13 @@ function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp {
throw monarchCommon.createError(lexer, 'attribute reference \'' + attr + '\' must be a string, used at: ' + str);
}
}
return (monarchCommon.empty(sub) ? '' : '(?:' + sub + ')');
return charBeforeAtSign + (monarchCommon.empty(sub) ? '' : '(?:' + sub + ')');
});
}
n++;
} while (hadExpansion && n < 5);
// handle escaped @@
str = str.replace(/@@/g, '@');
let flags = (lexer.ignoreCase ? 'i' : '') + (lexer.unicode ? 'u' : '');
return new RegExp(str, flags);

View file

@ -46,6 +46,10 @@ export interface IMonarchLanguage {
* Defaults to false
*/
includeLF?: boolean;
/**
* Other keys that can be referred to by the tokenizer.
*/
[key: string]: any;
}
/**

View file

@ -19,6 +19,17 @@ suite('Monarch', () => {
return new MonarchTokenizer(modeService, null!, languageId, compile(languageId, language));
}
function getTokens(tokenizer: MonarchTokenizer, lines: string[]): Token[][] {
const actualTokens: Token[][] = [];
let state = tokenizer.getInitialState();
for (const line of lines) {
const result = tokenizer.tokenize(line, true, state, 0);
actualTokens.push(result.tokens);
state = result.endState;
}
return actualTokens;
}
test('Ensure @rematch and nextEmbedded can be used together in Monarch grammar', () => {
const modeService = new ModeServiceImpl();
const innerModeRegistration = ModesRegistry.registerLanguage({
@ -65,13 +76,7 @@ suite('Monarch', () => {
`""")`,
];
const actualTokens: Token[][] = [];
let state = tokenizer.getInitialState();
for (const line of lines) {
const result = tokenizer.tokenize(line, true, state, 0);
actualTokens.push(result.tokens);
state = result.endState;
}
const actualTokens = getTokens(tokenizer, lines);
assert.deepStrictEqual(actualTokens, [
[
@ -140,13 +145,7 @@ suite('Monarch', () => {
`But the line was empty. This line should not be commented.`,
];
const actualTokens: Token[][] = [];
let state = tokenizer.getInitialState();
for (const line of lines) {
const result = tokenizer.tokenize(line, true, state, 0);
actualTokens.push(result.tokens);
state = result.endState;
}
const actualTokens = getTokens(tokenizer, lines);
assert.deepStrictEqual(actualTokens, [
[new Token(0, 'comment.test', 'test')],
@ -190,13 +189,7 @@ suite('Monarch', () => {
`PRINT 2*3:*FX200, 3`
];
const actualTokens: Token[][] = [];
let state = tokenizer.getInitialState();
for (const line of lines) {
const result = tokenizer.tokenize(line, true, state, 0);
actualTokens.push(result.tokens);
state = result.endState;
}
const actualTokens = getTokens(tokenizer, lines);
assert.deepStrictEqual(actualTokens, [
[
@ -218,4 +211,57 @@ suite('Monarch', () => {
]);
});
test('issue #115662: monarchCompile function need an extra option which can control replacement', () => {
const modeService = new ModeServiceImpl();
const tokenizer1 = createMonarchTokenizer(modeService, 'test', {
ignoreCase: false,
uselessReplaceKey1: '@uselessReplaceKey2',
uselessReplaceKey2: '@uselessReplaceKey3',
uselessReplaceKey3: '@uselessReplaceKey4',
uselessReplaceKey4: '@uselessReplaceKey5',
uselessReplaceKey5: '@ham' || '',
tokenizer: {
root: [
{
regex: /@\w+/.test('@ham')
? new RegExp(`^${'@uselessReplaceKey1'}$`)
: new RegExp(`^${'@ham'}$`),
action: { token: 'ham' }
},
],
},
});
const tokenizer2 = createMonarchTokenizer(modeService, 'test', {
ignoreCase: false,
tokenizer: {
root: [
{
regex: /@@ham/,
action: { token: 'ham' }
},
],
},
});
const lines = [
`@ham`
];
const actualTokens1 = getTokens(tokenizer1, lines);
assert.deepStrictEqual(actualTokens1, [
[
new Token(0, 'ham.test', 'test'),
]
]);
const actualTokens2 = getTokens(tokenizer2, lines);
assert.deepStrictEqual(actualTokens2, [
[
new Token(0, 'ham.test', 'test'),
]
]);
});
});

4
src/vs/monaco.d.ts vendored
View file

@ -6505,6 +6505,10 @@ declare namespace monaco.languages {
* Defaults to false
*/
includeLF?: boolean;
/**
* Other keys that can be referred to by the tokenizer.
*/
[key: string]: any;
}
/**

View file

@ -27,10 +27,10 @@ export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuAct
return asDisposable(groups);
}
export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean): IDisposable {
export function createAndFillInActionBarActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, isPrimaryGroup?: (group: string) => boolean, primaryMaxCount?: number): IDisposable {
const groups = menu.getActions(options);
// Action bars handle alternative actions on their own so the alternative actions should be ignored
fillInActions(groups, target, false, isPrimaryGroup);
fillInActions(groups, target, false, isPrimaryGroup, primaryMaxCount);
return asDisposable(groups);
}
@ -44,26 +44,38 @@ function asDisposable(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemActio
return disposables;
}
function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation'): void {
function fillInActions(groups: ReadonlyArray<[string, ReadonlyArray<MenuItemAction | SubmenuItemAction>]>, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, useAlternativeActions: boolean, isPrimaryGroup: (group: string) => boolean = group => group === 'navigation', primaryMaxCount: number = Number.MAX_SAFE_INTEGER): void {
let primaryBucket: IAction[];
let secondaryBucket: IAction[];
if (Array.isArray(target)) {
primaryBucket = target;
secondaryBucket = target;
} else {
primaryBucket = target.primary;
secondaryBucket = target.secondary;
}
for (let [group, actions] of groups) {
if (useAlternativeActions) {
actions = actions.map(a => (a instanceof MenuItemAction) && !!a.alt ? a.alt : a);
}
if (isPrimaryGroup(group)) {
const to = Array.isArray(target) ? target : target.primary;
to.unshift(...actions);
primaryBucket.unshift(...actions);
} else {
const to = Array.isArray(target) ? target : target.secondary;
if (to.length > 0) {
to.push(new Separator());
if (secondaryBucket.length > 0) {
secondaryBucket.push(new Separator());
}
to.push(...actions);
secondaryBucket.push(...actions);
}
}
// overflow items from the primary group into the secondary bucket
if (primaryBucket !== secondaryBucket && primaryBucket.length > primaryMaxCount) {
const overflow = primaryBucket.splice(primaryMaxCount, primaryBucket.length - primaryMaxCount);
secondaryBucket.unshift(...overflow, new Separator());
}
}
export class MenuEntryActionViewItem extends ActionViewItem {

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

@ -12287,6 +12287,9 @@ declare module 'vscode' {
* on the accounts activity bar icon. An entry for the extension will be added under the menu to sign in. This
* allows quietly prompting the user to sign in.
*
* If there is a matching session but the extension has not been granted access to it, setting this to true
* will also result in an immediate modal dialog, and false will add a numbered badge to the accounts icon.
*
* Defaults to false.
*/
createIfNone?: boolean;

View file

@ -7,67 +7,15 @@ import { Disposable } from 'vs/base/common/lifecycle';
import * as modes from 'vs/editor/common/modes';
import * as nls from 'vs/nls';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent } from 'vs/workbench/services/authentication/browser/authenticationService';
import { IAuthenticationService, AllowedExtension, readAllowedExtensions, getAuthenticationProviderActivationEvent, addAccountUsage, readAccountUsages, removeAccountUsage } from 'vs/workbench/services/authentication/browser/authenticationService';
import { ExtHostAuthenticationShape, ExtHostContext, IExtHostContext, MainContext, MainThreadAuthenticationShape } from '../common/extHost.protocol';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import Severity from 'vs/base/common/severity';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { fromNow } from 'vs/base/common/date';
import { ActivationKind, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { isWeb } from 'vs/base/common/platform';
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces'];
interface IAccountUsage {
extensionId: string;
extensionName: string;
lastUsed: number;
}
function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] {
const accountKey = `${providerId}-${accountName}-usages`;
const storedUsages = storageService.get(accountKey, StorageScope.GLOBAL);
let usages: IAccountUsage[] = [];
if (storedUsages) {
try {
usages = JSON.parse(storedUsages);
} catch (e) {
// ignore
}
}
return usages;
}
function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
const accountKey = `${providerId}-${accountName}-usages`;
storageService.remove(accountKey, StorageScope.GLOBAL);
}
function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) {
const accountKey = `${providerId}-${accountName}-usages`;
const usages = readAccountUsages(storageService, providerId, accountName);
const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
if (existingUsageIndex > -1) {
usages.splice(existingUsageIndex, 1, {
extensionId,
extensionName,
lastUsed: Date.now()
});
} else {
usages.push({
extensionId,
extensionName,
lastUsed: Date.now()
});
}
storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
export class MainThreadAuthenticationProvider extends Disposable {
private _accounts = new Map<string, string[]>(); // Map account name to session ids
@ -220,7 +168,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
@IDialogService private readonly dialogService: IDialogService,
@IStorageService private readonly storageService: IStorageService,
@INotificationService private readonly notificationService: INotificationService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IExtensionService private readonly extensionService: IExtensionService
) {
@ -246,10 +193,6 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}));
}
$getProviderIds(): Promise<string[]> {
return Promise.resolve(this.authenticationService.getProviderIds());
}
async $registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): Promise<void> {
const provider = new MainThreadAuthenticationProvider(this._proxy, id, label, supportsMultipleAccounts, this.notificationService, this.storageService, this.quickInputService, this.dialogService);
await provider.initialize();
@ -268,172 +211,17 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
this.authenticationService.sessionsUpdate(id, event);
}
$getSessions(id: string): Promise<ReadonlyArray<modes.AuthenticationSession>> {
return this.authenticationService.getSessions(id);
}
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession> {
return this.authenticationService.login(providerId, scopes);
}
$logout(providerId: string, sessionId: string): Promise<void> {
return this.authenticationService.logout(providerId, sessionId);
}
async $requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {
return this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
}
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.$getSessions(providerId)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const label = this.authenticationService.getLabel(providerId);
if (sessions.length) {
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
const session = sessions[0];
const allowed = await this.$getSessionsPrompt(providerId, session.account.label, label, extensionId, extensionName);
if (allowed) {
return session;
} else {
throw new Error('User did not consent to login.');
}
}
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
const selected = await this.$selectSession(providerId, label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
return sessions.find(session => session.id === selected.id);
} else {
if (options.createIfNone) {
const isAllowed = await this.$loginPrompt(label, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
const session = await this.authenticationService.login(providerId, scopes);
await this.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
return session;
} else {
await this.$requestNewSession(providerId, scopes, extensionId, extensionName);
return undefined;
}
}
}
async $selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
if (!potentialSessions.length) {
throw new Error('No potential sessions found');
}
if (clearSessionPreference) {
this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
} else {
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
const allowed = await this.$getSessionsPrompt(providerId, matchingSession.account.label, providerName, extensionId, extensionName);
if (allowed) {
return matchingSession;
}
}
}
}
return new Promise((resolve, reject) => {
const quickPick = this.quickInputService.createQuickPick<{ label: string, session?: modes.AuthenticationSession }>();
quickPick.ignoreFocusOut = true;
const items: { label: string, session?: modes.AuthenticationSession }[] = potentialSessions.map(session => {
return {
label: session.account.label,
session
};
});
items.push({
label: nls.localize('useOtherAccount', "Sign in to another account")
});
quickPick.items = items;
quickPick.title = nls.localize(
{
key: 'selectAccount',
comment: ['The placeholder {0} is the name of an extension. {1} is the name of the type of account, such as Microsoft or GitHub.']
},
"The extension '{0}' wants to access a {1} account",
extensionName,
providerName);
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName);
quickPick.onDidAccept(async _ => {
const selected = quickPick.selectedItems[0];
const session = selected.session ?? await this.authenticationService.login(providerId, scopes);
const accountName = session.account.label;
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
}
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
quickPick.dispose();
resolve(session);
});
quickPick.onDidHide(_ => {
if (!quickPick.selectedItems[0]) {
reject('User did not consent to account access');
}
quickPick.dispose();
});
quickPick.show();
});
}
async $getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean> {
private isAccessAllowed(providerId: string, accountName: string, extensionId: string): boolean {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
if (extensionData) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
return true;
}
const remoteConnection = this.remoteAgentService.getConnection();
const isVSO = remoteConnection !== null
? remoteConnection.remoteAuthority.startsWith('vsonline') || remoteConnection.remoteAuthority.startsWith('codespaces')
: isWeb;
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
return true;
}
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName),
[nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")],
{
cancelId: 1
}
);
const allow = choice === 0;
if (allow) {
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
}
return allow;
return !!extensionData;
}
async $loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
private async loginPrompt(providerName: string, extensionName: string): Promise<boolean> {
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmLogin', "The extension '{0}' wants to sign in using {1}.", extensionName, providerName),
@ -446,7 +234,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
return choice === 0;
}
async $setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
private async setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void> {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
@ -454,6 +242,77 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu
}
this.storageService.store(`${extensionName}-${providerId}`, sessionId, StorageScope.GLOBAL, StorageTarget.MACHINE);
addAccountUsage(this.storageService, providerId, accountName, extensionId, extensionName);
}
private async selectSession(providerId: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession> {
if (!potentialSessions.length) {
throw new Error('No potential sessions found');
}
if (clearSessionPreference) {
this.storageService.remove(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
} else {
const existingSessionPreference = this.storageService.get(`${extensionName}-${providerId}`, StorageScope.GLOBAL);
if (existingSessionPreference) {
const matchingSession = potentialSessions.find(session => session.id === existingSessionPreference);
if (matchingSession) {
const allowed = await this.authenticationService.showGetSessionPrompt(providerId, matchingSession.account.label, extensionId, extensionName);
if (allowed) {
return matchingSession;
}
}
}
}
return this.authenticationService.selectSession(providerId, extensionId, extensionName, potentialSessions);
}
async $getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone: boolean, clearSessionPreference: boolean }): Promise<modes.AuthenticationSession | undefined> {
const orderedScopes = scopes.sort().join(' ');
const sessions = (await this.authenticationService.getSessions(providerId)).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
const silent = !options.createIfNone;
let session: modes.AuthenticationSession | undefined;
if (sessions.length) {
if (!this.authenticationService.supportsMultipleAccounts(providerId)) {
session = sessions[0];
const allowed = this.isAccessAllowed(providerId, session.account.label, extensionId);
if (!allowed) {
if (!silent) {
const didAcceptPrompt = await this.authenticationService.showGetSessionPrompt(providerId, session.account.label, extensionId, extensionName);
if (!didAcceptPrompt) {
throw new Error('User did not consent to login.');
}
} else {
this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, [session]);
}
}
} else {
if (!silent) {
session = await this.selectSession(providerId, extensionId, extensionName, sessions, !!options.clearSessionPreference);
} else {
this.authenticationService.requestSessionAccess(providerId, extensionId, extensionName, sessions);
}
}
} else {
if (!silent) {
const isAllowed = await this.loginPrompt(providerId, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
session = await this.authenticationService.login(providerId, scopes);
await this.setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
} else {
await this.authenticationService.requestNewSession(providerId, scopes, extensionId, extensionName);
}
}
if (session) {
addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName);
}
return session;
}
}

View file

@ -165,17 +165,8 @@ export interface MainThreadAuthenticationShape extends IDisposable {
$registerAuthenticationProvider(id: string, label: string, supportsMultipleAccounts: boolean): void;
$unregisterAuthenticationProvider(id: string): void;
$ensureProvider(id: string): Promise<void>;
$getProviderIds(): Promise<string[]>;
$sendDidChangeSessions(providerId: string, event: modes.AuthenticationSessionsChangeEvent): void;
$getSession(providerId: string, scopes: string[], extensionId: string, extensionName: string, options: { createIfNone?: boolean, clearSessionPreference?: boolean }): Promise<modes.AuthenticationSession | undefined>;
$selectSession(providerId: string, providerName: string, extensionId: string, extensionName: string, potentialSessions: modes.AuthenticationSession[], scopes: string[], clearSessionPreference: boolean): Promise<modes.AuthenticationSession>;
$getSessionsPrompt(providerId: string, accountName: string, providerName: string, extensionId: string, extensionName: string): Promise<boolean>;
$loginPrompt(providerName: string, extensionName: string): Promise<boolean>;
$setTrustedExtensionAndAccountPreference(providerId: string, accountName: string, extensionId: string, extensionName: string, sessionId: string): Promise<void>;
$requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;
$getSessions(providerId: string): Promise<ReadonlyArray<modes.AuthenticationSession>>;
$login(providerId: string, scopes: string[]): Promise<modes.AuthenticationSession>;
$logout(providerId: string, sessionId: string): Promise<void>;
}

View file

@ -83,45 +83,8 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape {
private async _getSession(requestingExtension: IExtensionDescription, extensionId: string, providerId: string, scopes: string[], options: vscode.AuthenticationGetSessionOptions = {}): Promise<vscode.AuthenticationSession | undefined> {
await this._proxy.$ensureProvider(providerId);
const providerData = this._authenticationProviders.get(providerId);
const extensionName = requestingExtension.displayName || requestingExtension.name;
if (!providerData) {
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
}
const orderedScopes = scopes.sort().join(' ');
const sessions = (await providerData.provider.getSessions()).filter(session => session.scopes.slice().sort().join(' ') === orderedScopes);
let session: vscode.AuthenticationSession | undefined = undefined;
if (sessions.length) {
if (!providerData.options.supportsMultipleAccounts) {
session = sessions[0];
const allowed = await this._proxy.$getSessionsPrompt(providerId, session.account.label, providerData.label, extensionId, extensionName);
if (!allowed) {
throw new Error('User did not consent to login.');
}
} else {
// On renderer side, confirm consent, ask user to choose between accounts if multiple sessions are valid
const selected = await this._proxy.$selectSession(providerId, providerData.label, extensionId, extensionName, sessions, scopes, !!options.clearSessionPreference);
session = sessions.find(session => session.id === selected.id);
}
} else {
if (options.createIfNone) {
const isAllowed = await this._proxy.$loginPrompt(providerData.label, extensionName);
if (!isAllowed) {
throw new Error('User did not consent to login.');
}
session = await providerData.provider.login(scopes);
await this._proxy.$setTrustedExtensionAndAccountPreference(providerId, session.account.label, extensionId, extensionName, session.id);
} else {
await this._proxy.$requestNewSession(providerId, scopes, extensionId, extensionName);
}
}
return session;
return this._proxy.$getSession(providerId, scopes, extensionId, extensionName, options);
}
async logout(providerId: string, sessionId: string): Promise<void> {

View file

@ -18,6 +18,9 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
[['statusBarItem.errorBackground', new ThemeColor('statusBarItem.errorForeground')]]
);
#proxy: MainThreadStatusBarShape;
#commands: CommandsConverter;
private _id: number;
private _alignment: number;
private _priority?: number;
@ -38,14 +41,13 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
};
private _timeoutHandle: any;
private _proxy: MainThreadStatusBarShape;
private _commands: CommandsConverter;
private _accessibilityInformation?: vscode.AccessibilityInformation;
constructor(proxy: MainThreadStatusBarShape, commands: CommandsConverter, id: string, name: string, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number, accessibilityInformation?: vscode.AccessibilityInformation) {
this.#proxy = proxy;
this.#commands = commands;
this._id = ExtHostStatusBarEntry.ID_GEN++;
this._proxy = proxy;
this._commands = commands;
this._statusId = id;
this._statusName = name;
this._alignment = alignment;
@ -122,12 +124,12 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
if (typeof command === 'string') {
this._command = {
fromApi: command,
internal: this._commands.toInternal({ title: '', command }, this._internalCommandRegistration),
internal: this.#commands.toInternal({ title: '', command }, this._internalCommandRegistration),
};
} else if (command) {
this._command = {
fromApi: command,
internal: this._commands.toInternal(command, this._internalCommandRegistration),
internal: this.#commands.toInternal(command, this._internalCommandRegistration),
};
} else {
this._command = undefined;
@ -148,7 +150,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
public hide(): void {
clearTimeout(this._timeoutHandle);
this._visible = false;
this._proxy.$dispose(this.id);
this.#proxy.$dispose(this.id);
}
private update(): void {
@ -169,7 +171,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
}
// Set to status bar
this._proxy.$setEntry(this.id, this._statusId, this._statusName, this._text, this._tooltip, this._command?.internal, color,
this.#proxy.$setEntry(this.id, this._statusId, this._statusName, this._text, this._tooltip, this._command?.internal, color,
this._backgroundColor, this._alignment === ExtHostStatusBarAlignment.Left ? MainThreadStatusBarAlignment.LEFT : MainThreadStatusBarAlignment.RIGHT,
this._priority, this._accessibilityInformation);
}, 0);

View file

@ -224,7 +224,7 @@ export abstract class TitleControl extends Themable {
this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change
}));
this.editorToolBarMenuDisposables.add(createAndFillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, (group: string) => group === 'navigation' || group === '1_run'));
this.editorToolBarMenuDisposables.add(createAndFillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, (group: string) => group === 'navigation' || group === '1_run', 7));
}
return { primary, secondary };

View file

@ -167,6 +167,7 @@
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon {
line-height: 22px;
height: 22px;
}
.customview-tree .monaco-list .custom-view-tree-node-item .actions .action-label.codicon::before {

View file

@ -456,6 +456,11 @@ function registerConfiguration(): void {
description: nls.localize('debug.console.historySuggestions', "Controls if the debug console should suggest previously typed input."),
default: true
},
'debug.console.collapseIdenticalLines': {
type: 'boolean',
description: nls.localize('debug.console.collapseIdenticalLines', "Controls if the debug console should collapse identical lines and show a number of occurrences with a badge."),
default: true
},
'launch': {
type: 'object',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."),

View file

@ -88,7 +88,7 @@ export class DebugSession implements IDebugSession {
) {
this._options = options || {};
if (this.hasSeparateRepl()) {
this.repl = new ReplModel();
this.repl = new ReplModel(this.configurationService);
} else {
this.repl = (this.parentSession as DebugSession).repl;
}

View file

@ -65,6 +65,9 @@
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
/*Use direction so the source shows elipses on the left*/
direction: rtl;
max-width: 400px;
}
.monaco-workbench .repl .repl-tree .output.expression > .value,

View file

@ -578,7 +578,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
accessibilityProvider: new ReplAccessibilityProvider(),
identityProvider,
mouseSupport: false,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e },
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IReplElement) => e.toString(true) },
horizontalScrolling: !wordWrap,
setRowLineHeight: false,
supportDynamicHeights: wordWrap,

View file

@ -61,7 +61,7 @@ export class ReplFilter implements ITreeFilter<IReplElement> {
let includeQueryPresent = false;
let includeQueryMatched = false;
const text = element.toString();
const text = element.toString(true);
for (let { type, query } of this._parsedQueries) {
if (type === 'exclude' && ReplFilter.matchQuery(query, text)) {

View file

@ -107,7 +107,7 @@ export interface ITreeElement {
}
export interface IReplElement extends ITreeElement {
toString(): string;
toString(includeSource?: boolean): string;
readonly sourceData?: IReplElementSource;
}
@ -504,6 +504,7 @@ export interface IDebugConfiguration {
lineHeight: number;
wordWrap: boolean;
closeOnEnd: boolean;
collapseIdenticalLines: boolean;
historySuggestions: boolean;
};
focusWindowOnBreak: boolean;

View file

@ -5,13 +5,14 @@
import * as nls from 'vs/nls';
import severity from 'vs/base/common/severity';
import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
import { ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel';
import { isString, isUndefinedOrNull, isObject } from 'vs/base/common/types';
import { basenameOrAuthority } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { Emitter, Event } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const MAX_REPL_LENGTH = 10000;
let topReplElementCounter = 0;
@ -29,12 +30,12 @@ export class SimpleReplElement implements IReplElement {
public sourceData?: IReplElementSource,
) { }
toString(): string {
toString(includeSource = false): string {
let valueRespectCount = this.value;
for (let i = 1; i < this.count; i++) {
valueRespectCount += (valueRespectCount.endsWith('\n') ? '' : '\n') + this.value;
}
const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : '';
const sourceStr = (this.sourceData && includeSource) ? ` ${this.sourceData.source.name}` : '';
return valueRespectCount + sourceStr;
}
@ -164,8 +165,8 @@ export class ReplGroup implements IReplElement {
return this.id;
}
toString(): string {
const sourceStr = this.sourceData ? ` ${this.sourceData.source.name}` : '';
toString(includeSource = false): string {
const sourceStr = (includeSource && this.sourceData) ? ` ${this.sourceData.source.name}` : '';
return this.name + sourceStr;
}
@ -201,6 +202,8 @@ export class ReplModel {
private readonly _onDidChangeElements = new Emitter<void>();
readonly onDidChangeElements = this._onDidChangeElements.event;
constructor(private readonly configurationService: IConfigurationService) { }
getReplElements(): IReplElement[] {
return this.replElements;
}
@ -224,7 +227,8 @@ export class ReplModel {
if (typeof data === 'string') {
const previousElement = this.replElements.length ? this.replElements[this.replElements.length - 1] : undefined;
if (previousElement instanceof SimpleReplElement && previousElement.severity === sev) {
if (previousElement.value === data) {
const config = this.configurationService.getValue<IDebugConfiguration>('debug');
if (previousElement.value === data && config.console.collapseIdenticalLines) {
previousElement.count++;
// No need to fire an event, just the count updates and badge will adjust automatically
return;

View file

@ -19,6 +19,7 @@ import { getStackFrameThreadAndSessionToFocus } from 'vs/workbench/contrib/debug
import { generateUuid } from 'vs/base/common/uuid';
import { debugStackframe, debugStackframeFocused } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
const mockWorkspaceContextService = {
getWorkspace: () => {
@ -37,7 +38,7 @@ export function createMockSession(model: DebugModel, name = 'mockSession', optio
}
};
}
} as IDebugService, undefined!, undefined!, undefined!, undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService);
} as IDebugService, undefined!, undefined!, new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } }), undefined!, mockWorkspaceContextService, undefined!, undefined!, NullOpenerService, undefined!, undefined!, mockUriIdentityService);
}
function createTwoStackFrames(session: DebugSession): { firstStackFrame: StackFrame, secondStackFrame: StackFrame } {

View file

@ -14,10 +14,12 @@ import { timeout } from 'vs/base/common/async';
import { createMockSession } from 'vs/workbench/contrib/debug/test/browser/callStack.test';
import { ReplFilter } from 'vs/workbench/contrib/debug/browser/replFilter';
import { TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
suite('Debug - REPL', () => {
let model: DebugModel;
let rawSession: MockRawSession;
const configurationService = new TestConfigurationService({ debug: { console: { collapseIdenticalLines: true } } });
setup(() => {
model = createMockDebugModel();
@ -26,7 +28,7 @@ suite('Debug - REPL', () => {
test('repl output', () => {
const session = createMockSession(model);
const repl = new ReplModel();
const repl = new ReplModel(configurationService);
repl.appendToRepl(session, 'first line\n', severity.Error);
repl.appendToRepl(session, 'second line ', severity.Error);
repl.appendToRepl(session, 'third line ', severity.Error);
@ -84,7 +86,7 @@ suite('Debug - REPL', () => {
test('repl output count', () => {
const session = createMockSession(model);
const repl = new ReplModel();
const repl = new ReplModel(configurationService);
repl.appendToRepl(session, 'first line\n', severity.Info);
repl.appendToRepl(session, 'first line\n', severity.Info);
repl.appendToRepl(session, 'first line\n', severity.Info);
@ -155,7 +157,7 @@ suite('Debug - REPL', () => {
session['raw'] = <any>rawSession;
const thread = new Thread(session, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, <any>undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1);
const replModel = new ReplModel();
const replModel = new ReplModel(configurationService);
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
replModel.addReplExpression(session, stackFrame, 'myVariable').then();
@ -193,7 +195,7 @@ suite('Debug - REPL', () => {
test('repl groups', async () => {
const session = createMockSession(model);
const repl = new ReplModel();
const repl = new ReplModel(configurationService);
repl.appendToRepl(session, 'first global line', severity.Info);
repl.startGroup('group_1', true);
@ -231,7 +233,7 @@ suite('Debug - REPL', () => {
test('repl filter', async () => {
const session = createMockSession(model);
const repl = new ReplModel();
const repl = new ReplModel(configurationService);
const replFilter = new ReplFilter();
const getFilteredElements = () => {

View file

@ -211,7 +211,7 @@ class RichRenderer implements IOutputTransformContribution {
renderPlainText(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput {
const data = output.model.data['text/plain'];
const contentNode = DOM.$('.output-plaintext');
truncatedArrayOfString(contentNode, isArray(data) ? data : [data], this.openerService, this.textFileService, this.themeService, true);
truncatedArrayOfString(contentNode, isArray(data) ? data : [data], this.openerService, this.textFileService, this.themeService);
container.appendChild(contentNode);
return { type: RenderOutputType.None, hasDynamicHeight: false };

View file

@ -23,8 +23,8 @@ class StreamRenderer implements IOutputTransformContribution {
render(viewModel: IStreamOutputViewModel, container: HTMLElement): IRenderOutput {
const output = viewModel.model;
const contentNode = DOM.$('.output-stream');
truncatedArrayOfString(contentNode, [output.text], this.openerService, this.textFileService, this.themeService, false);
const contentNode = DOM.$('span.output-stream');
truncatedArrayOfString(contentNode, [output.text], this.openerService, this.textFileService, this.themeService);
container.appendChild(contentNode);
return { type: RenderOutputType.None, hasDynamicHeight: false };
}

View file

@ -49,7 +49,7 @@ function generateViewMoreElement(outputs: string[], openerService: IOpenerServic
return element;
}
export function truncatedArrayOfString(container: HTMLElement, outputs: string[], openerService: IOpenerService, textFileService: ITextFileService, themeService: IThemeService, renderANSI: boolean) {
export function truncatedArrayOfString(container: HTMLElement, outputs: string[], openerService: IOpenerService, textFileService: ITextFileService, themeService: IThemeService) {
const fullLen = outputs.reduce((p, c) => {
return p + c.length;
}, 0);
@ -65,14 +65,7 @@ export function truncatedArrayOfString(container: HTMLElement, outputs: string[]
const sizeBufferLimitPosition = buffer.getPositionAt(SIZE_LIMIT);
if (sizeBufferLimitPosition.lineNumber < LINES_LIMIT) {
const truncatedText = buffer.getValueInRange(new Range(1, 1, sizeBufferLimitPosition.lineNumber, sizeBufferLimitPosition.column), EndOfLinePreference.TextDefined);
if (renderANSI) {
container.appendChild(handleANSIOutput(truncatedText, themeService));
} else {
const pre = DOM.$('pre');
pre.innerText = truncatedText;
container.appendChild(pre);
}
container.appendChild(handleANSIOutput(truncatedText, themeService));
// view more ...
container.appendChild(generateViewMoreElement(outputs, openerService, textFileService));
return;
@ -89,42 +82,19 @@ export function truncatedArrayOfString(container: HTMLElement, outputs: string[]
if (buffer.getLineCount() < LINES_LIMIT) {
const lineCount = buffer.getLineCount();
const fullRange = new Range(1, 1, lineCount, Math.max(1, buffer.getLineLastNonWhitespaceColumn(lineCount)));
if (renderANSI) {
const pre = DOM.$('pre');
container.appendChild(pre);
pre.appendChild(handleANSIOutput(buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined), themeService));
} else {
const pre = DOM.$('pre');
container.appendChild(pre);
pre.innerText = buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined);
}
container.appendChild(handleANSIOutput(buffer.getValueInRange(fullRange, EndOfLinePreference.TextDefined), themeService));
return;
}
if (renderANSI) {
const pre = DOM.$('pre');
container.appendChild(pre);
pre.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined), themeService));
} else {
const pre = DOM.$('pre');
pre.innerText = buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined);
container.appendChild(pre);
}
const pre = DOM.$('pre');
container.appendChild(pre);
pre.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(1, 1, LINES_LIMIT - 5, buffer.getLineLastNonWhitespaceColumn(LINES_LIMIT - 5)), EndOfLinePreference.TextDefined), themeService));
// view more ...
container.appendChild(generateViewMoreElement(outputs, openerService, textFileService));
const lineCount = buffer.getLineCount();
if (renderANSI) {
const pre = DOM.$('div');
container.appendChild(pre);
pre.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined), themeService));
} else {
const post = DOM.$('div');
post.innerText = buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined);
container.appendChild(post);
}
const pre2 = DOM.$('div');
container.appendChild(pre2);
pre2.appendChild(handleANSIOutput(buffer.getValueInRange(new Range(lineCount - 5, 1, lineCount, buffer.getLineLastNonWhitespaceColumn(lineCount)), EndOfLinePreference.TextDefined), themeService));
}

View file

@ -31,11 +31,21 @@ interface IMimeTypeRenderer extends IQuickPickItem {
index: number;
}
export class OutputElement extends Disposable {
export class CellOutputElement extends Disposable {
readonly resizeListener = new DisposableStore();
domNode!: HTMLElement;
renderResult?: IRenderOutput;
public useDedicatedDOM: boolean = true;
get domClientHeight() {
if (this.useDedicatedDOM) {
return this.domNode.clientHeight;
} else {
return 0;
}
}
constructor(
private notebookEditor: INotebookEditor,
private notebookService: INotebookService,
@ -47,6 +57,17 @@ export class OutputElement extends Disposable {
super();
}
detach() {
this.domNode.parentElement?.removeChild(this.domNode);
}
updateDOMTop(top: number) {
if (this.useDedicatedDOM) {
this.domNode.style.top = `${top}px`;
}
}
render(index: number, beforeElement?: HTMLElement) {
if (this.viewCell.metadata.outputCollapsed) {
return;
@ -58,10 +79,11 @@ export class OutputElement extends Disposable {
const notebookTextModel = this.notebookEditor.viewModel.notebookDocument;
const outputItemDiv = document.createElement('div');
let outputItemDiv;
let result: IRenderOutput | undefined = undefined;
if (this.output.isDisplayOutput()) {
outputItemDiv = document.createElement('div');
const [mimeTypes, pick] = this.output.resolveMimeTypes(notebookTextModel);
if (mimeTypes.length > 1) {
outputItemDiv.style.position = 'relative';
@ -106,8 +128,27 @@ export class OutputElement extends Disposable {
this.output.pickedMimeType = pick;
}
} else if (this.output.isStreamOutput()) {
if (!beforeElement && this.outputContainer.lastChild && (<HTMLElement>this.outputContainer.lastChild).classList.contains('stream-output') && this.output.isStreamOutput()) {
this.useDedicatedDOM = false;
// the previous output and this one are both stream output
outputItemDiv = this.outputContainer.lastChild as HTMLElement;
const innerContainer = outputItemDiv.lastChild && (<HTMLElement>outputItemDiv.lastChild).classList.contains('output-inner-container') ? outputItemDiv.lastChild as HTMLElement : document.createElement('div');
outputItemDiv.classList.add('stream-output');
DOM.append(outputItemDiv, innerContainer);
result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this.getNotebookUri(),);
} else {
outputItemDiv = document.createElement('div');
const innerContainer = DOM.$('.output-inner-container');
outputItemDiv.classList.add('stream-output');
DOM.append(outputItemDiv, innerContainer);
result = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this.getNotebookUri(),);
}
} else {
// for text and error, there is no mimetype
outputItemDiv = document.createElement('div');
const innerContainer = DOM.$('.output-inner-container');
DOM.append(outputItemDiv, innerContainer);
@ -124,7 +165,7 @@ export class OutputElement extends Disposable {
if (beforeElement) {
this.outputContainer.insertBefore(outputItemDiv, beforeElement);
} else {
} else if (this.useDedicatedDOM) {
this.outputContainer.appendChild(outputItemDiv);
}
@ -165,11 +206,13 @@ export class OutputElement extends Disposable {
this.resizeListener.add(elementSizeObserver);
this.viewCell.updateOutputHeight(index, clientHeight);
} else if (result.type === RenderOutputType.None) { // no-op if it's a webview
const clientHeight = Math.ceil(outputItemDiv.clientHeight);
this.viewCell.updateOutputHeight(index, clientHeight);
if (this.useDedicatedDOM) {
const clientHeight = Math.ceil(outputItemDiv.clientHeight);
this.viewCell.updateOutputHeight(index, clientHeight);
const top = this.viewCell.getOutputOffsetInContainer(index);
outputItemDiv.style.top = `${top}px`;
const top = this.viewCell.getOutputOffsetInContainer(index);
outputItemDiv.style.top = `${top}px`;
}
}
}
@ -245,8 +288,8 @@ export class OutputElement extends Disposable {
}
}
export class OutputContainer extends Disposable {
private outputEntries = new Map<ICellOutputViewModel, OutputElement>();
export class CellOutputContainer extends Disposable {
private outputEntries = new Map<ICellOutputViewModel, CellOutputElement>();
constructor(
private notebookEditor: INotebookEditor,
@ -268,7 +311,7 @@ export class OutputContainer extends Disposable {
const index = viewCell.outputsViewModels.indexOf(key);
if (index >= 0) {
const top = this.viewCell.getOutputOffsetInContainer(index);
value.domNode.style.top = `${top}px`;
value.updateDOMTop(top);
}
});
}));
@ -330,8 +373,7 @@ export class OutputContainer extends Disposable {
if (renderedOutput.renderResult.type !== RenderOutputType.None) {
this.notebookEditor.createInset(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index));
} else {
// Anything else, just update the height
this.viewCell.updateOutputHeight(index, renderedOutput.domNode.clientHeight);
this.viewCell.updateOutputHeight(index, renderedOutput.domClientHeight);
}
} else {
// Wasn't previously rendered, render it now
@ -352,7 +394,7 @@ export class OutputContainer extends Disposable {
this.viewCell.outputsViewModels.forEach((o, i) => {
const renderedOutput = this.outputEntries.get(o);
if (renderedOutput && renderedOutput.renderResult && renderedOutput.renderResult.type === RenderOutputType.None && !renderedOutput.renderResult.hasDynamicHeight) {
this.viewCell.updateOutputHeight(i, renderedOutput.domNode.clientHeight);
this.viewCell.updateOutputHeight(i, renderedOutput.domClientHeight);
}
});
}
@ -420,7 +462,7 @@ export class OutputContainer extends Disposable {
// already removed
removedKeys.push(key);
// remove element from DOM
this.templateData?.outputContainer?.removeChild(value.domNode);
value.detach();
if (key.isDisplayOutput()) {
this.notebookEditor.removeInset(key);
}
@ -468,7 +510,7 @@ export class OutputContainer extends Disposable {
private _renderOutput(currOutput: ICellOutputViewModel, index: number, beforeElement?: HTMLElement) {
if (!this.outputEntries.has(currOutput)) {
this.outputEntries.set(currOutput, new OutputElement(this.notebookEditor, this.notebookService, this.quickInputService, this.viewCell, this.templateData.outputContainer, currOutput));
this.outputEntries.set(currOutput, new CellOutputElement(this.notebookEditor, this.notebookService, this.quickInputService, this.viewCell, this.templateData.outputContainer, currOutput));
}
this.outputEntries.get(currOutput)!.render(index, beforeElement);

View file

@ -17,13 +17,13 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ClickTargetType, getExecuteCellPlaceholder } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets';
import { OutputContainer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellOutput';
import { CellOutputContainer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellOutput';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
export class CodeCell extends Disposable {
private _outputContainerRenderer: OutputContainer;
private _outputContainerRenderer: CellOutputContainer;
private _activeCellRunPlaceholder: IDisposable | null = null;
private _untrustedStatusItem: IDisposable | null = null;
@ -122,6 +122,12 @@ export class CodeCell extends Disposable {
}
}));
this._register(viewCell.onDidChangeLayout((e) => {
if (e.totalHeight) {
this.relayoutCell();
}
}));
this._register(templateData.editor.onDidContentSizeChange((e) => {
if (e.contentHeightChanged) {
if (this.viewCell.layoutInfo.editorHeight !== e.contentHeight) {
@ -215,7 +221,7 @@ export class CodeCell extends Disposable {
updateFocusMode();
// Render Outputs
this._outputContainerRenderer = new OutputContainer(notebookEditor, viewCell, templateData, notebookService, quickInputService, openerService, textFileService);
this._outputContainerRenderer = new CellOutputContainer(notebookEditor, viewCell, templateData, notebookService, quickInputService, openerService, textFileService);
this._outputContainerRenderer.render(editorHeight);
// Need to do this after the intial renderOutput
updateForCollapseState();

View file

@ -171,7 +171,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
}
},
markdownDescription: localize('remote.portsAttributes', "Allows setting of default properties that are set when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```"),
markdownDescription: localize('remote.portsAttributes', "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Labeled Port\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n}\n```"),
defaultSnippets: [{ body: { '${1:3000}': { label: '${2:My Port}', onAutoForward: 'notify' } } }]
}
}

View file

@ -1593,7 +1593,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Recreate the process if the terminal has not yet been interacted with and it's not a
// special terminal (eg. task, extension terminal)
if (info.requiresAction && !this._processManager.hasWrittenData && !this._shellLaunchConfig.isFeatureTerminal && !this._shellLaunchConfig.isExtensionTerminal) {
this.reuseTerminal(this._shellLaunchConfig, true);
this.relaunch();
return;
}

View file

@ -238,10 +238,11 @@
outline-style: solid;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button {
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button.button-link {
position: absolute;
left: -2px;
top: -10px;
left: 40px;
top: 5px;
padding: 0 2px 2px;
margin: 10px;
z-index: 1;
}
@ -252,7 +253,8 @@
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button .codicon {
position: relative;
top: 2px;
top: 3px;
left: -4px;
}
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .skip {

View file

@ -294,8 +294,11 @@ export class GettingStartedPage extends Disposable {
}
this.buildCategorySlide(container, this.editorInput.selectedCategory, this.editorInput.selectedTask);
categoriesSlide.classList.add('prev');
this.setButtonEnablement(container, 'details');
} else {
tasksSlide.classList.add('next');
this.focusFirstUncompletedCategory(container);
this.setButtonEnablement(container, 'categories');
}
setTimeout(() => assertIsDefined(container.querySelector('.gettingStartedContainer')).classList.add('animationReady'), 0);
}
@ -339,6 +342,7 @@ export class GettingStartedPage extends Disposable {
this.buildCategorySlide(container, categoryID);
slides[currentSlide].classList.add('prev');
slides[currentSlide + 1].classList.remove('next');
this.setButtonEnablement(container, 'details');
}
});
}
@ -418,9 +422,30 @@ export class GettingStartedPage extends Disposable {
if (currentSlide > 0) {
slides[currentSlide].classList.add('next');
assertIsDefined(slides[currentSlide - 1]).classList.remove('prev');
this.setButtonEnablement(container, 'categories');
}
this.focusFirstUncompletedCategory(container);
});
}
private focusFirstUncompletedCategory(container: HTMLElement) {
let toFocus!: HTMLElement;
container.querySelectorAll('.category-progress').forEach(progress => {
const progressAmount = assertIsDefined(progress.querySelector('.progress-bar-inner') as HTMLDivElement).style.width;
if (!toFocus && progressAmount !== '100%') { toFocus = assertIsDefined(progress.parentElement?.parentElement); }
});
(toFocus ?? assertIsDefined(container.querySelector('button.skip')) as HTMLButtonElement).focus();
}
private setButtonEnablement(container: HTMLElement, toEnable: 'details' | 'categories') {
if (toEnable === 'categories') {
container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = true);
container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = false);
} else {
container.querySelector('.gettingStartedSlideDetails')!.querySelectorAll('button').forEach(button => button.disabled = false);
container.querySelector('.gettingStartedSlideCategory')!.querySelectorAll('button').forEach(button => button.disabled = true);
}
}
}
export class GettingStartedInputFactory implements IEditorInputFactory {
@ -503,6 +528,7 @@ registerThemingParticipant((theme, collector) => {
if (link) {
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .button-link { color: ${link}; }`);
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .button-link * { color: ${link}; }`);
}
const activeLink = theme.getColor(textLinkActiveForeground);
if (activeLink) {

View file

@ -17,7 +17,7 @@ export default () => `
</div>
</div>
<div class="gettingStartedSlideDetails gettingStartedSlide detail">
<a class="prev-button" x-dispatch="scrollPrev"><span class="scroll-button codicon codicon-chevron-left"></span>Back</a>
<button class="prev-button button-link" x-dispatch="scrollPrev"><span class="scroll-button codicon codicon-chevron-left"></span>Back</a>
<div class="gettingStartedDetailsContent">
<div class="gap"></div>
<div class="getting-started-detail-columns">

View file

@ -72,7 +72,12 @@ export class WelcomePageContribution implements IWorkbenchContribution {
// Open the welcome even if we opened a set of default editors
if ((!editorService.activeEditor || layoutService.openedDefaultEditors) && !hasBackups) {
const startupEditorSetting = configurationService.inspect<string>(configurationKey);
const openWithReadme = startupEditorSetting.userValue === 'readme';
// 'readme' should not be set in workspace settings to prevent tracking,
// but it can be set as a default (as in codespaces) or a user setting
const openWithReadme = startupEditorSetting.value === 'readme' &&
(startupEditorSetting.userValue === 'readme' || startupEditorSetting.defaultValue === 'readme');
if (openWithReadme) {
return Promise.all(contextService.getWorkspace().folders.map(folder => {
const folderUri = folder.uri;

View file

@ -23,9 +23,64 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { flatten } from 'vs/base/common/arrays';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { Severity } from 'vs/platform/notification/common/notification';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { isWeb } from 'vs/base/common/platform';
export function getAuthenticationProviderActivationEvent(id: string): string { return `onAuthenticationRequest:${id}`; }
export interface IAccountUsage {
extensionId: string;
extensionName: string;
lastUsed: number;
}
const VSO_ALLOWED_EXTENSIONS = ['github.vscode-pull-request-github', 'github.vscode-pull-request-github-insiders', 'vscode.git', 'ms-vsonline.vsonline', 'vscode.github-browser', 'ms-vscode.github-browser', 'github.codespaces'];
export function readAccountUsages(storageService: IStorageService, providerId: string, accountName: string,): IAccountUsage[] {
const accountKey = `${providerId}-${accountName}-usages`;
const storedUsages = storageService.get(accountKey, StorageScope.GLOBAL);
let usages: IAccountUsage[] = [];
if (storedUsages) {
try {
usages = JSON.parse(storedUsages);
} catch (e) {
// ignore
}
}
return usages;
}
export function removeAccountUsage(storageService: IStorageService, providerId: string, accountName: string): void {
const accountKey = `${providerId}-${accountName}-usages`;
storageService.remove(accountKey, StorageScope.GLOBAL);
}
export function addAccountUsage(storageService: IStorageService, providerId: string, accountName: string, extensionId: string, extensionName: string) {
const accountKey = `${providerId}-${accountName}-usages`;
const usages = readAccountUsages(storageService, providerId, accountName);
const existingUsageIndex = usages.findIndex(usage => usage.extensionId === extensionId);
if (existingUsageIndex > -1) {
usages.splice(existingUsageIndex, 1, {
extensionId,
extensionName,
lastUsed: Date.now()
});
} else {
usages.push({
extensionId,
extensionName,
lastUsed: Date.now()
});
}
storageService.store(accountKey, JSON.stringify(usages), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
export type AuthenticationSessionInfo = { readonly id: string, readonly accessToken: string, readonly providerId: string, readonly canSignOut?: boolean };
export async function getCurrentAuthenticationSessionInfo(environmentService: IWorkbenchEnvironmentService, productService: IProductService): Promise<AuthenticationSessionInfo | undefined> {
if (environmentService.options?.credentialsProvider) {
@ -53,7 +108,11 @@ export interface IAuthenticationService {
getProviderIds(): string[];
registerAuthenticationProvider(id: string, provider: MainThreadAuthenticationProvider): void;
unregisterAuthenticationProvider(id: string): void;
requestNewSession(id: string, scopes: string[], extensionId: string, extensionName: string): void;
showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<boolean>;
selectSession(providerId: string, extensionId: string, extensionName: string, possibleSessions: AuthenticationSession[]): Promise<AuthenticationSession>;
requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: AuthenticationSession[]): void;
completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string): Promise<void>
requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void>;
sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void;
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
@ -134,6 +193,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
private _placeholderMenuItem: IDisposable | undefined;
private _noAccountsMenuItem: IDisposable | undefined;
private _signInRequestItems = new Map<string, SessionRequestInfo>();
private _sessionAccessRequestItems = new Map<string, { [extensionId: string]: { disposables: IDisposable[], possibleSessions: AuthenticationSession[] } }>();
private _accountBadgeDisposable = this._register(new MutableDisposable());
private _authenticationProviders: Map<string, MainThreadAuthenticationProvider> = new Map<string, MainThreadAuthenticationProvider>();
@ -157,7 +217,11 @@ export class AuthenticationService extends Disposable implements IAuthentication
constructor(
@IActivityService private readonly activityService: IActivityService,
@IExtensionService private readonly extensionService: IExtensionService
@IExtensionService private readonly extensionService: IExtensionService,
@IStorageService private readonly storageService: IStorageService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IDialogService private readonly dialogService: IDialogService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super();
this._placeholderMenuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
@ -255,6 +319,13 @@ export class AuthenticationService extends Disposable implements IAuthentication
this._authenticationProviders.delete(id);
this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label });
this.updateAccountsMenuItem();
const accessRequests = this._sessionAccessRequestItems.get(id) || {};
Object.keys(accessRequests).forEach(extensionId => {
this.removeAccessRequest(id, extensionId);
});
this.updateBadgeCount();
}
if (!this._authenticationProviders.size) {
@ -278,6 +349,12 @@ export class AuthenticationService extends Disposable implements IAuthentication
if (event.added) {
await this.updateNewSessionRequests(provider);
}
if (event.removed) {
await this.updateAccessRequests(id, event.removed);
}
this.updateBadgeCount();
}
}
@ -288,12 +365,9 @@ export class AuthenticationService extends Disposable implements IAuthentication
}
const sessions = await provider.getSessions();
let changed = false;
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
if (sessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
// Request has been completed
changed = true;
const sessionRequest = existingRequestsForProvider[requestedScopes];
sessionRequest?.disposables.forEach(item => item.dispose());
@ -305,22 +379,214 @@ export class AuthenticationService extends Disposable implements IAuthentication
}
}
});
}
if (changed) {
this._accountBadgeDisposable.clear();
if (this._signInRequestItems.size > 0) {
let numberOfRequests = 0;
this._signInRequestItems.forEach(providerRequests => {
Object.keys(providerRequests).forEach(request => {
numberOfRequests += providerRequests[request].requestingExtensionIds.length;
});
private async updateAccessRequests(providerId: string, removedSessionIds: readonly string[]) {
const providerRequests = this._sessionAccessRequestItems.get(providerId);
if (providerRequests) {
Object.keys(providerRequests).forEach(extensionId => {
removedSessionIds.forEach(removedId => {
const indexOfSession = providerRequests[extensionId].possibleSessions.findIndex(session => session.id === removedId);
if (indexOfSession) {
providerRequests[extensionId].possibleSessions.splice(indexOfSession, 1);
}
});
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
if (!providerRequests[extensionId].possibleSessions.length) {
this.removeAccessRequest(providerId, extensionId);
}
});
}
}
private updateBadgeCount(): void {
this._accountBadgeDisposable.clear();
let numberOfRequests = 0;
this._signInRequestItems.forEach(providerRequests => {
Object.keys(providerRequests).forEach(request => {
numberOfRequests += providerRequests[request].requestingExtensionIds.length;
});
});
this._sessionAccessRequestItems.forEach(accessRequest => {
numberOfRequests += Object.keys(accessRequest).length;
});
if (numberOfRequests > 0) {
const badge = new NumberBadge(numberOfRequests, () => nls.localize('sign in', "Sign in requested"));
this._accountBadgeDisposable.value = this.activityService.showAccountsActivity({ badge });
}
}
private removeAccessRequest(providerId: string, extensionId: string): void {
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
if (providerRequests[extensionId]) {
providerRequests[extensionId].disposables.forEach(d => d.dispose());
delete providerRequests[extensionId];
}
}
async showGetSessionPrompt(providerId: string, accountName: string, extensionId: string, extensionName: string): Promise<boolean> {
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
const extensionData = allowList.find(extension => extension.id === extensionId);
if (extensionData) {
return true;
}
const remoteConnection = this.remoteAgentService.getConnection();
const isVSO = remoteConnection !== null
? remoteConnection.remoteAuthority.startsWith('vsonline') || remoteConnection.remoteAuthority.startsWith('codespaces')
: isWeb;
if (isVSO && VSO_ALLOWED_EXTENSIONS.includes(extensionId)) {
return true;
}
const providerName = this.getLabel(providerId);
const { choice } = await this.dialogService.show(
Severity.Info,
nls.localize('confirmAuthenticationAccess', "The extension '{0}' wants to access the {1} account '{2}'.", extensionName, providerName, accountName),
[nls.localize('allow', "Allow"), nls.localize('cancel', "Cancel")],
{
cancelId: 1
}
);
const allow = choice === 0;
if (allow) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
this.removeAccessRequest(providerId, extensionId);
}
return allow;
}
async selectSession(providerId: string, extensionId: string, extensionName: string, availableSessions: AuthenticationSession[]): Promise<AuthenticationSession> {
return new Promise((resolve, reject) => {
// This function should be used only when there are sessions to disambiguate.
if (!availableSessions.length) {
reject('No available sessions');
}
const quickPick = this.quickInputService.createQuickPick<{ label: string, session?: AuthenticationSession }>();
quickPick.ignoreFocusOut = true;
const items: { label: string, session?: AuthenticationSession }[] = availableSessions.map(session => {
return {
label: session.account.label,
session: session
};
});
items.push({
label: nls.localize('useOtherAccount', "Sign in to another account")
});
const providerName = this.getLabel(providerId);
quickPick.items = items;
quickPick.title = nls.localize(
{
key: 'selectAccount',
comment: ['The placeholder {0} is the name of an extension. {1} is the name of the type of account, such as Microsoft or GitHub.']
},
"The extension '{0}' wants to access a {1} account",
extensionName,
providerName);
quickPick.placeholder = nls.localize('getSessionPlateholder', "Select an account for '{0}' to use or Esc to cancel", extensionName);
quickPick.onDidAccept(async _ => {
const session = quickPick.selectedItems[0].session ?? await this.login(providerId, availableSessions[0].scopes as string[]);
const accountName = session.account.label;
const allowList = readAllowedExtensions(this.storageService, providerId, accountName);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
this.storageService.store(`${providerId}-${accountName}`, JSON.stringify(allowList), StorageScope.GLOBAL, StorageTarget.USER);
this.removeAccessRequest(providerId, extensionId);
}
this.storageService.store(`${extensionName}-${providerId}`, session.id, StorageScope.GLOBAL, StorageTarget.MACHINE);
quickPick.dispose();
resolve(session);
});
quickPick.onDidHide(_ => {
if (!quickPick.selectedItems[0]) {
reject('User did not consent to account access');
}
quickPick.dispose();
});
quickPick.show();
});
}
async completeSessionAccessRequest(providerId: string, extensionId: string, extensionName: string): Promise<void> {
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
const existingRequest = providerRequests[extensionId];
if (!existingRequest) {
return;
}
const possibleSessions = existingRequest.possibleSessions;
const supportsMultipleAccounts = this.supportsMultipleAccounts(providerId);
let session: AuthenticationSession | undefined;
if (supportsMultipleAccounts) {
try {
session = await this.selectSession(providerId, extensionId, extensionName, possibleSessions);
} catch (_) {
// ignore cancel
}
} else {
const approved = await this.showGetSessionPrompt(providerId, possibleSessions[0].account.label, extensionId, extensionName);
if (approved) {
session = possibleSessions[0];
}
}
if (session) {
addAccountUsage(this.storageService, providerId, session.account.label, extensionId, extensionName);
const providerName = this.getLabel(providerId);
this._onDidChangeSessions.fire({ providerId, label: providerName, event: { added: [], removed: [], changed: [session.id] } });
}
}
requestSessionAccess(providerId: string, extensionId: string, extensionName: string, possibleSessions: AuthenticationSession[]): void {
const providerRequests = this._sessionAccessRequestItems.get(providerId) || {};
const hasExistingRequest = providerRequests[extensionId];
if (hasExistingRequest) {
return;
}
const menuItem = MenuRegistry.appendMenuItem(MenuId.AccountsContext, {
group: '3_accessRequests',
command: {
id: `${providerId}${extensionId}Access`,
title: nls.localize({
key: 'accessRequest',
comment: ['The placeholder {0} will be replaced with an extension name. (1) is to indicate that this menu item contributes to a badge count']
},
"Grant access to {0}... (1)", extensionName)
}
});
const accessCommand = CommandsRegistry.registerCommand({
id: `${providerId}${extensionId}Access`,
handler: async (accessor) => {
const authenticationService = accessor.get(IAuthenticationService);
authenticationService.completeSessionAccessRequest(providerId, extensionId, extensionName);
}
});
providerRequests[extensionId] = { possibleSessions, disposables: [menuItem, accessCommand] };
this._sessionAccessRequestItems.set(providerId, providerRequests);
this.updateBadgeCount();
}
async requestNewSession(providerId: string, scopes: string[], extensionId: string, extensionName: string): Promise<void> {

View file

@ -264,7 +264,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
const isMachineScoped = await this.hasToFlagExtensionsMachineScoped([gallery]);
installOptions = { isMachineScoped, isBuiltin: false };
}
if (!installOptions.isMachineScoped) {
if (!installOptions.isMachineScoped && this.isExtensionsSyncEnabled()) {
if (this.extensionManagementServerService.localExtensionManagementServer && !servers.includes(this.extensionManagementServerService.localExtensionManagementServer)) {
servers.push(this.extensionManagementServerService.localExtensionManagementServer);
}
@ -301,32 +301,36 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return this.extensionManagementServerService.localExtensionManagementServer;
}
private isExtensionsSyncEnabled(): boolean {
return this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions);
}
private async hasToFlagExtensionsMachineScoped(extensions: IGalleryExtension[]): Promise<boolean> {
if (!this.userDataAutoSyncEnablementService.isEnabled() || !this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions)) {
return false;
}
const result = await this.dialogService.show(
Severity.Info,
extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"),
[
localize('install', "Install"),
localize('install and do no sync', "Install (Do not sync)"),
localize('cancel', "Cancel"),
],
{
cancelId: 2,
detail: extensions.length === 1
? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName)
: localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?")
if (this.isExtensionsSyncEnabled()) {
const result = await this.dialogService.show(
Severity.Info,
extensions.length === 1 ? localize('install extension', "Install Extension") : localize('install extensions', "Install Extensions"),
[
localize('install', "Install"),
localize('install and do no sync', "Install (Do not sync)"),
localize('cancel', "Cancel"),
],
{
cancelId: 2,
detail: extensions.length === 1
? localize('install single extension', "Would you like to install and synchronize '{0}' extension across your devices?", extensions[0].displayName)
: localize('install multiple extensions', "Would you like to install and synchronize extensions across your devices?")
}
);
switch (result.choice) {
case 0:
return false;
case 1:
return true;
}
);
switch (result.choice) {
case 0:
return false;
case 1:
return true;
throw canceled();
}
throw canceled();
return false;
}
getExtensionsReport(): Promise<IReportedExtension[]> {

View file

@ -210,6 +210,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
public _onExtensionHostExit(code: number): void {
// Dispose everything associated with the extension host
this._stopExtensionHosts();
// We log the exit code to the console. Do NOT remove this
// code as the automated integration tests in browser rely
// on this message to exit properly.

View file

@ -420,7 +420,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
this._onDidChangeExtensionsStatus.fire(this._registry.getAllExtensionDescriptions().map(e => e.identifier));
}
private _stopExtensionHosts(): void {
protected _stopExtensionHosts(): void {
let previouslyActivatedExtensionIds: ExtensionIdentifier[] = [];
this._extensionHostActiveExtensions.forEach((value) => {
previouslyActivatedExtensionIds.push(value);

View file

@ -408,6 +408,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
public _onExtensionHostExit(code: number): void {
// Dispose everything associated with the extension host
this._stopExtensionHosts();
if (this._isExtensionDevTestFromCli) {
// When CLI testing make sure to exit with proper exit code
this._nativeHostService.exit(code);

View file

@ -2914,10 +2914,10 @@ electron-to-chromium@^1.3.634:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz#8b884f50296c2ae2a9997f024d0e3e57facc2b94"
integrity sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ==
electron@11.2.1:
version "11.2.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-11.2.1.tgz#8641dd1a62911a1144e0c73c34fd9f37ccc65c2b"
integrity sha512-Im1y29Bnil+Nzs+FCTq01J1OtLbs+2ZGLLllaqX/9n5GgpdtDmZhS/++JHBsYZ+4+0n7asO+JKQgJD+CqPClzg==
electron@11.2.2:
version "11.2.2"
resolved "https://registry.yarnpkg.com/electron/-/electron-11.2.2.tgz#c2e53eb56bd21ae1dc01bc781f2f6bcbe0c18033"
integrity sha512-+OitkBrnCFwOF5LXAeNnfIJDKhdBm77jboc16WCIpDsCyT+JpGsKK4y6o30nRZq3zC+wZggUm5w6Ujw5n76CGg==
dependencies:
"@electron/get" "^1.0.1"
"@types/node" "^12.0.12"
@ -10358,25 +10358,25 @@ xtend@~2.1.1:
dependencies:
object-keys "~0.4.0"
xterm-addon-search@0.8.0-beta.3:
version "0.8.0-beta.3"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0-beta.3.tgz#c6c7e36a03706bd43d8bba383511acf9e435aed0"
integrity sha512-EZP97KJIJ4KGQaOPYiiOaRRJst6LOgeEFoQL46WcBl5EWH9pH8qfrv0BHAJ8+6nBV2B9u5M6rzxO1GvLLec19w==
xterm-addon-search@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.8.0.tgz#e33eab918df7eac7e7baf95dd2b3d14133754881"
integrity sha512-MPJGPVPpHRUw9cLIuqQbrVepmENMOybVUSxIALz5h1ryyQBrVqVujq2hL5aroX5/dZJoHx9lGHQTVLQ07SKgKA==
xterm-addon-unicode11@0.3.0-beta.3:
version "0.3.0-beta.3"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.3.0-beta.3.tgz#70af2dfb67809258edb62c19e2861f7ce5ccf5cd"
integrity sha512-vaYopnOjn19wCLDCyIWPWLwKR7CvLPxB5YZ3CAxt9qL05o3symxIJJJC0DuCa4GaGKVjNc7EmjRCs5bsJ2O1tw==
xterm-addon-webgl@0.10.0-beta.1:
version "0.10.0-beta.1"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0-beta.1.tgz#e0bf964945a9aa8fc18318ddbd32e56ec99c219e"
integrity sha512-XNZMrmiyFaz3XiPq+LqF0qn2QHpUEwuk+cG53JwpJHnWo3dd2jxoIgHFQUcrnvHIVPZMbTKySIwLCCC9uQVl7Q==
xterm-addon-webgl@0.10.0-beta.2:
version "0.10.0-beta.2"
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.10.0-beta.2.tgz#520547b2f845b2f9265f1817140b0f4e114c4a55"
integrity sha512-DLfmF5+H1M/0BABEaqJlLUasKck7TjyRWVlzGflFTWVCr7/Kqaf0al4KMlw3yWX76A1ITGXrWj0qbDn2Sixl2Q==
xterm@4.10.0-beta.39:
version "4.10.0-beta.39"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.10.0-beta.39.tgz#29c5ce3ed814d04e0bf2781cf9bd09e354b6e422"
integrity sha512-0qtknUlct48WyVj8tnoVvwn4Bz2+lmVBLIGKjifTvawRccw2QVXjTa5hUgNIbecgZv8PWZhseBREtccRbbqbuA==
xterm@4.11.0-beta.2:
version "4.11.0-beta.2"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.11.0-beta.2.tgz#a95560b61c771f54a336c2eb10e1472e556d9f4b"
integrity sha512-0BUaAfuclnowirdOuB13OGgq6OUGg/8etnRVT6apgnOrLGOLRCE1NiL3KhxotleAf4gVP0m3iCxsIr3csDY40g==
y18n@^3.2.1:
version "3.2.2"