Merge remote-tracking branch 'origin/master' into joao/scm-menu-actions
This commit is contained in:
commit
a0d9df38ad
53 changed files with 751 additions and 504 deletions
2
.yarnrc
2
.yarnrc
|
@ -1,3 +1,3 @@
|
|||
disturl "https://electronjs.org/headers"
|
||||
target "11.2.1"
|
||||
target "11.2.2"
|
||||
runtime "electron"
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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']);
|
||||
|
|
12
package.json
12
package.json
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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==
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -89,7 +89,6 @@
|
|||
padding: 0.4em;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
min-height: 34px;
|
||||
margin-top: -1px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -854,6 +854,7 @@ export interface IListOptions<T> {
|
|||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly alwaysConsumeMouseWheel?: boolean;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
4
src/vs/monaco.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
3
src/vs/vscode.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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."),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 } {
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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' } } }]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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[]> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
32
yarn.lock
32
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue