Merge remote-tracking branch 'origin/master' into tyriar/puppeteer

This commit is contained in:
Daniel Imms
2019-08-14 10:21:09 -07:00
113 changed files with 1779 additions and 1677 deletions
+2 -1
View File
@@ -53,6 +53,7 @@ const buildfile = require('../src/buildfile');
const vscodeWebEntryPoints = [
buildfile.workbenchWeb,
buildfile.serviceWorker,
buildfile.workerExtensionHost,
buildfile.keyboardMaps,
buildfile.base
];
@@ -148,4 +149,4 @@ const dashed = (str) => (str ? `-${str}` : ``);
vscodeWebTaskCI
));
gulp.task(vscodeWebTask);
});
});
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.38.0",
"distro": "6ce1c040c0d555b998dba62dd333f437a7b2d44f",
"distro": "afe23e028d98eeca8e26c62804b876ad9cce27b9",
"author": {
"name": "Microsoft Corporation"
},
@@ -101,7 +101,7 @@
"gulp-shell": "^0.6.5",
"gulp-tsb": "2.0.7",
"gulp-tslint": "^8.1.3",
"gulp-uglify": "^3.0.0",
"gulp-uglify": "^3.0.2",
"gulp-untar": "^0.0.7",
"gulp-vinyl-zip": "^2.1.2",
"http-server": "^0.11.1",
@@ -134,7 +134,7 @@
"tslint": "^5.16.0",
"typescript": "3.5.2",
"typescript-formatter": "7.1.0",
"uglify-es": "^3.0.18",
"uglify-es": "^3.3.9",
"underscore": "^1.8.2",
"vinyl": "^2.0.0",
"vinyl-fs": "^3.0.0",
+9 -1
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
function entrypoint (name) {
function entrypoint(name) {
return [{ name: name, include: [], exclude: ['vs/css', 'vs/nls'] }];
}
@@ -23,6 +23,14 @@ exports.serviceWorker = [{
dest: 'vs/workbench/contrib/resources/browser/resourceServiceWorkerMain.js'
}];
exports.workerExtensionHost = [{
name: 'vs/workbench/services/extensions/worker/extensionHostWorker',
// include: [],
prepend: ['vs/loader.js'],
append: ['vs/workbench/services/extensions/worker/extensionHostWorkerMain'],
dest: 'vs/workbench/services/extensions/worker/extensionHostWorkerMain.js'
}];
exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.desktop.main']);
exports.workbenchWeb = entrypoint('vs/workbench/workbench.web.api');
@@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { DisposableStore } from 'vs/base/common/lifecycle';
export interface IContentActionHandler {
callback: (content: string, event?: IMouseEvent) => void;
readonly disposeables: DisposableStore;
}
export interface FormattedTextRenderOptions {
readonly className?: string;
readonly inline?: boolean;
readonly actionHandler?: IContentActionHandler;
}
export function renderText(text: string, options: FormattedTextRenderOptions = {}): HTMLElement {
const element = createElement(options);
element.textContent = text;
return element;
}
export function renderFormattedText(formattedText: string, options: FormattedTextRenderOptions = {}): HTMLElement {
const element = createElement(options);
_renderFormattedText(element, parseFormattedText(formattedText), options.actionHandler);
return element;
}
export function createElement(options: FormattedTextRenderOptions): HTMLElement {
const tagName = options.inline ? 'span' : 'div';
const element = document.createElement(tagName);
if (options.className) {
element.className = options.className;
}
return element;
}
class StringStream {
private source: string;
private index: number;
constructor(source: string) {
this.source = source;
this.index = 0;
}
public eos(): boolean {
return this.index >= this.source.length;
}
public next(): string {
const next = this.peek();
this.advance();
return next;
}
public peek(): string {
return this.source[this.index];
}
public advance(): void {
this.index++;
}
}
const enum FormatType {
Invalid,
Root,
Text,
Bold,
Italics,
Action,
ActionClose,
NewLine
}
interface IFormatParseTree {
type: FormatType;
content?: string;
index?: number;
children?: IFormatParseTree[];
}
function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler) {
let child: Node | undefined;
if (treeNode.type === FormatType.Text) {
child = document.createTextNode(treeNode.content || '');
} else if (treeNode.type === FormatType.Bold) {
child = document.createElement('b');
} else if (treeNode.type === FormatType.Italics) {
child = document.createElement('i');
} else if (treeNode.type === FormatType.Action && actionHandler) {
const a = document.createElement('a');
a.href = '#';
actionHandler.disposeables.add(DOM.addStandardDisposableListener(a, 'click', (event) => {
actionHandler.callback(String(treeNode.index), event);
}));
child = a;
} else if (treeNode.type === FormatType.NewLine) {
child = document.createElement('br');
} else if (treeNode.type === FormatType.Root) {
child = element;
}
if (child && element !== child) {
element.appendChild(child);
}
if (child && Array.isArray(treeNode.children)) {
treeNode.children.forEach((nodeChild) => {
_renderFormattedText(child!, nodeChild, actionHandler);
});
}
}
function parseFormattedText(content: string): IFormatParseTree {
const root: IFormatParseTree = {
type: FormatType.Root,
children: []
};
let actionViewItemIndex = 0;
let current = root;
const stack: IFormatParseTree[] = [];
const stream = new StringStream(content);
while (!stream.eos()) {
let next = stream.next();
const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek()) !== FormatType.Invalid);
if (isEscapedFormatType) {
next = stream.next(); // unread the backslash if it escapes a format tag type
}
if (!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) {
stream.advance();
if (current.type === FormatType.Text) {
current = stack.pop()!;
}
const type = formatTagType(next);
if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) {
current = stack.pop()!;
} else {
const newCurrent: IFormatParseTree = {
type: type,
children: []
};
if (type === FormatType.Action) {
newCurrent.index = actionViewItemIndex;
actionViewItemIndex++;
}
current.children!.push(newCurrent);
stack.push(current);
current = newCurrent;
}
} else if (next === '\n') {
if (current.type === FormatType.Text) {
current = stack.pop()!;
}
current.children!.push({
type: FormatType.NewLine
});
} else {
if (current.type !== FormatType.Text) {
const textCurrent: IFormatParseTree = {
type: FormatType.Text,
content: next
};
current.children!.push(textCurrent);
stack.push(current);
current = textCurrent;
} else {
current.content += next;
}
}
}
if (current.type === FormatType.Text) {
current = stack.pop()!;
}
if (stack.length) {
// incorrectly formatted string literal
}
return root;
}
function isFormatTag(char: string): boolean {
return formatTagType(char) !== FormatType.Invalid;
}
function formatTagType(char: string): FormatType {
switch (char) {
case '*':
return FormatType.Bold;
case '_':
return FormatType.Italics;
case '[':
return FormatType.Action;
case ']':
return FormatType.ActionClose;
default:
return FormatType.Invalid;
}
}
-394
View File
@@ -1,394 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { escape } from 'vs/base/common/strings';
import { removeMarkdownEscapes, IMarkdownString, parseHrefAndDimensions } from 'vs/base/common/htmlContent';
import * as marked from 'vs/base/common/marked/marked';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { parse } from 'vs/base/common/marshalling';
import { cloneAndChange } from 'vs/base/common/objects';
export interface IContentActionHandler {
callback: (content: string, event?: IMouseEvent) => void;
readonly disposeables: DisposableStore;
}
export interface RenderOptions {
className?: string;
inline?: boolean;
actionHandler?: IContentActionHandler;
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
codeBlockRenderCallback?: () => void;
}
function createElement(options: RenderOptions): HTMLElement {
const tagName = options.inline ? 'span' : 'div';
const element = document.createElement(tagName);
if (options.className) {
element.className = options.className;
}
return element;
}
export function renderText(text: string, options: RenderOptions = {}): HTMLElement {
const element = createElement(options);
element.textContent = text;
return element;
}
export function renderFormattedText(formattedText: string, options: RenderOptions = {}): HTMLElement {
const element = createElement(options);
_renderFormattedText(element, parseFormattedText(formattedText), options.actionHandler);
return element;
}
/**
* Create html nodes for the given content element.
*/
export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): HTMLElement {
const element = createElement(options);
const _uriMassage = function (part: string): string {
let data: any;
try {
data = parse(decodeURIComponent(part));
} catch (e) {
// ignore
}
if (!data) {
return part;
}
data = cloneAndChange(data, value => {
if (markdown.uris && markdown.uris[value]) {
return URI.revive(markdown.uris[value]);
} else {
return undefined;
}
});
return encodeURIComponent(JSON.stringify(data));
};
const _href = function (href: string, isDomUri: boolean): string {
const data = markdown.uris && markdown.uris[href];
if (!data) {
return href;
}
let uri = URI.revive(data);
if (isDomUri) {
uri = DOM.asDomUri(uri);
}
if (uri.query) {
uri = uri.with({ query: _uriMassage(uri.query) });
}
if (data) {
href = uri.toString(true);
}
return href;
};
// signal to code-block render that the
// element has been created
let signalInnerHTML: () => void;
const withInnerHTML = new Promise(c => signalInnerHTML = c);
const renderer = new marked.Renderer();
renderer.image = (href: string, title: string, text: string) => {
let dimensions: string[] = [];
let attributes: string[] = [];
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
href = _href(href, true);
attributes.push(`src="${href}"`);
}
if (text) {
attributes.push(`alt="${text}"`);
}
if (title) {
attributes.push(`title="${title}"`);
}
if (dimensions.length) {
attributes = attributes.concat(dimensions);
}
return '<img ' + attributes.join(' ') + '>';
};
renderer.link = (href, title, text): string => {
// Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829
if (href === text) { // raw link case
text = removeMarkdownEscapes(text);
}
href = _href(href, false);
title = removeMarkdownEscapes(title);
href = removeMarkdownEscapes(href);
if (
!href
|| href.match(/^data:|javascript:/i)
|| (href.match(/^command:/i) && !markdown.isTrusted)
|| href.match(/^command:(\/\/\/)?_workbench\.downloadResource/i)
) {
// drop the link
return text;
} else {
// HTML Encode href
href = href.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return `<a href="#" data-href="${href}" title="${title || href}">${text}</a>`;
}
};
renderer.paragraph = (text): string => {
return `<p>${text}</p>`;
};
if (options.codeBlockRenderer) {
renderer.code = (code, lang) => {
const value = options.codeBlockRenderer!(lang, code);
// when code-block rendering is async we return sync
// but update the node with the real result later.
const id = defaultGenerator.nextId();
const promise = Promise.all([value, withInnerHTML]).then(values => {
const strValue = values[0];
const span = element.querySelector(`div[data-code="${id}"]`);
if (span) {
span.innerHTML = strValue;
}
}).catch(err => {
// ignore
});
if (options.codeBlockRenderCallback) {
promise.then(options.codeBlockRenderCallback);
}
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
};
}
if (options.actionHandler) {
options.actionHandler.disposeables.add(DOM.addStandardDisposableListener(element, 'click', event => {
let target: HTMLElement | null = event.target;
if (target.tagName !== 'A') {
target = target.parentElement;
if (!target || target.tagName !== 'A') {
return;
}
}
try {
const href = target.dataset['href'];
if (href) {
options.actionHandler!.callback(href, event);
}
} catch (err) {
onUnexpectedError(err);
} finally {
event.preventDefault();
}
}));
}
const markedOptions: marked.MarkedOptions = {
sanitize: true,
renderer
};
element.innerHTML = marked.parse(markdown.value, markedOptions);
signalInnerHTML!();
return element;
}
// --- formatted string parsing
class StringStream {
private source: string;
private index: number;
constructor(source: string) {
this.source = source;
this.index = 0;
}
public eos(): boolean {
return this.index >= this.source.length;
}
public next(): string {
const next = this.peek();
this.advance();
return next;
}
public peek(): string {
return this.source[this.index];
}
public advance(): void {
this.index++;
}
}
const enum FormatType {
Invalid,
Root,
Text,
Bold,
Italics,
Action,
ActionClose,
NewLine
}
interface IFormatParseTree {
type: FormatType;
content?: string;
index?: number;
children?: IFormatParseTree[];
}
function _renderFormattedText(element: Node, treeNode: IFormatParseTree, actionHandler?: IContentActionHandler) {
let child: Node | undefined;
if (treeNode.type === FormatType.Text) {
child = document.createTextNode(treeNode.content || '');
}
else if (treeNode.type === FormatType.Bold) {
child = document.createElement('b');
}
else if (treeNode.type === FormatType.Italics) {
child = document.createElement('i');
}
else if (treeNode.type === FormatType.Action && actionHandler) {
const a = document.createElement('a');
a.href = '#';
actionHandler.disposeables.add(DOM.addStandardDisposableListener(a, 'click', (event) => {
actionHandler.callback(String(treeNode.index), event);
}));
child = a;
}
else if (treeNode.type === FormatType.NewLine) {
child = document.createElement('br');
}
else if (treeNode.type === FormatType.Root) {
child = element;
}
if (child && element !== child) {
element.appendChild(child);
}
if (child && Array.isArray(treeNode.children)) {
treeNode.children.forEach((nodeChild) => {
_renderFormattedText(child!, nodeChild, actionHandler);
});
}
}
function parseFormattedText(content: string): IFormatParseTree {
const root: IFormatParseTree = {
type: FormatType.Root,
children: []
};
let actionViewItemIndex = 0;
let current = root;
const stack: IFormatParseTree[] = [];
const stream = new StringStream(content);
while (!stream.eos()) {
let next = stream.next();
const isEscapedFormatType = (next === '\\' && formatTagType(stream.peek()) !== FormatType.Invalid);
if (isEscapedFormatType) {
next = stream.next(); // unread the backslash if it escapes a format tag type
}
if (!isEscapedFormatType && isFormatTag(next) && next === stream.peek()) {
stream.advance();
if (current.type === FormatType.Text) {
current = stack.pop()!;
}
const type = formatTagType(next);
if (current.type === type || (current.type === FormatType.Action && type === FormatType.ActionClose)) {
current = stack.pop()!;
} else {
const newCurrent: IFormatParseTree = {
type: type,
children: []
};
if (type === FormatType.Action) {
newCurrent.index = actionViewItemIndex;
actionViewItemIndex++;
}
current.children!.push(newCurrent);
stack.push(current);
current = newCurrent;
}
} else if (next === '\n') {
if (current.type === FormatType.Text) {
current = stack.pop()!;
}
current.children!.push({
type: FormatType.NewLine
});
} else {
if (current.type !== FormatType.Text) {
const textCurrent: IFormatParseTree = {
type: FormatType.Text,
content: next
};
current.children!.push(textCurrent);
stack.push(current);
current = textCurrent;
} else {
current.content += next;
}
}
}
if (current.type === FormatType.Text) {
current = stack.pop()!;
}
if (stack.length) {
// incorrectly formatted string literal
}
return root;
}
function isFormatTag(char: string): boolean {
return formatTagType(char) !== FormatType.Invalid;
}
function formatTagType(char: string): FormatType {
switch (char) {
case '*':
return FormatType.Bold;
case '_':
return FormatType.Italics;
case '[':
return FormatType.Action;
case ']':
return FormatType.ActionClose;
default:
return FormatType.Invalid;
}
}
+177
View File
@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import * as marked from 'vs/base/common/marked/marked';
import { parse } from 'vs/base/common/marshalling';
import { cloneAndChange } from 'vs/base/common/objects';
import { escape } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
codeBlockRenderCallback?: () => void;
}
/**
* Create html nodes for the given content element.
*/
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement {
const element = createElement(options);
const _uriMassage = function (part: string): string {
let data: any;
try {
data = parse(decodeURIComponent(part));
} catch (e) {
// ignore
}
if (!data) {
return part;
}
data = cloneAndChange(data, value => {
if (markdown.uris && markdown.uris[value]) {
return URI.revive(markdown.uris[value]);
} else {
return undefined;
}
});
return encodeURIComponent(JSON.stringify(data));
};
const _href = function (href: string, isDomUri: boolean): string {
const data = markdown.uris && markdown.uris[href];
if (!data) {
return href;
}
let uri = URI.revive(data);
if (isDomUri) {
uri = DOM.asDomUri(uri);
}
if (uri.query) {
uri = uri.with({ query: _uriMassage(uri.query) });
}
if (data) {
href = uri.toString(true);
}
return href;
};
// signal to code-block render that the
// element has been created
let signalInnerHTML: () => void;
const withInnerHTML = new Promise(c => signalInnerHTML = c);
const renderer = new marked.Renderer();
renderer.image = (href: string, title: string, text: string) => {
let dimensions: string[] = [];
let attributes: string[] = [];
if (href) {
({ href, dimensions } = parseHrefAndDimensions(href));
href = _href(href, true);
attributes.push(`src="${href}"`);
}
if (text) {
attributes.push(`alt="${text}"`);
}
if (title) {
attributes.push(`title="${title}"`);
}
if (dimensions.length) {
attributes = attributes.concat(dimensions);
}
return '<img ' + attributes.join(' ') + '>';
};
renderer.link = (href, title, text): string => {
// Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829
if (href === text) { // raw link case
text = removeMarkdownEscapes(text);
}
href = _href(href, false);
title = removeMarkdownEscapes(title);
href = removeMarkdownEscapes(href);
if (
!href
|| href.match(/^data:|javascript:/i)
|| (href.match(/^command:/i) && !markdown.isTrusted)
|| href.match(/^command:(\/\/\/)?_workbench\.downloadResource/i)
) {
// drop the link
return text;
} else {
// HTML Encode href
href = href.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
return `<a href="#" data-href="${href}" title="${title || href}">${text}</a>`;
}
};
renderer.paragraph = (text): string => {
return `<p>${text}</p>`;
};
if (options.codeBlockRenderer) {
renderer.code = (code, lang) => {
const value = options.codeBlockRenderer!(lang, code);
// when code-block rendering is async we return sync
// but update the node with the real result later.
const id = defaultGenerator.nextId();
const promise = Promise.all([value, withInnerHTML]).then(values => {
const strValue = values[0];
const span = element.querySelector(`div[data-code="${id}"]`);
if (span) {
span.innerHTML = strValue;
}
}).catch(err => {
// ignore
});
if (options.codeBlockRenderCallback) {
promise.then(options.codeBlockRenderCallback);
}
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
};
}
if (options.actionHandler) {
options.actionHandler.disposeables.add(DOM.addStandardDisposableListener(element, 'click', event => {
let target: HTMLElement | null = event.target;
if (target.tagName !== 'A') {
target = target.parentElement;
if (!target || target.tagName !== 'A') {
return;
}
}
try {
const href = target.dataset['href'];
if (href) {
options.actionHandler!.callback(href, event);
}
} catch (err) {
onUnexpectedError(err);
} finally {
event.preventDefault();
}
}));
}
const markedOptions: marked.MarkedOptions = {
sanitize: true,
renderer
};
element.innerHTML = marked.parse(markdown.value, markedOptions);
signalInnerHTML!();
return element;
}
+3 -2
View File
@@ -8,7 +8,8 @@ import 'vs/css!./inputBox';
import * as nls from 'vs/nls';
import * as Bal from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer';
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IAction } from 'vs/base/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -438,7 +439,7 @@ export class InputBox extends Widget {
div = dom.append(container, $('.monaco-inputbox-container'));
layout();
const renderOptions: RenderOptions = {
const renderOptions: MarkdownRenderOptions = {
inline: true,
className: 'monaco-inputbox-message'
};
@@ -18,7 +18,7 @@ import { domEvent } from 'vs/base/browser/event';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ISelectBoxDelegate, ISelectOptionItem, ISelectBoxOptions, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
import { isMacintosh } from 'vs/base/common/platform';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
const $ = dom.$;
+15
View File
@@ -729,3 +729,18 @@ export function getNLines(str: string, n = 1): string {
str.substr(0, idx) :
str;
}
/**
* Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
*/
export function singleLetterHash(n: number): string {
const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
n = n % (2 * LETTERS_CNT);
if (n < LETTERS_CNT) {
return String.fromCharCode(CharCode.a + n);
}
return String.fromCharCode(CharCode.A + n - LETTERS_CNT);
}
@@ -2,12 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown, renderText, renderFormattedText } from 'vs/base/browser/htmlContentRenderer';
import { renderText, renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { DisposableStore } from 'vs/base/common/lifecycle';
suite('HtmlContent', () => {
suite('FormattedTextRenderer', () => {
const store = new DisposableStore();
setup(() => {
@@ -101,36 +101,4 @@ suite('HtmlContent', () => {
assert.strictEqual(result.children.length, 0);
assert.strictEqual(result.innerHTML, '**bold**');
});
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});
@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
suite('MarkdownRenderer', () => {
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});
@@ -9,6 +9,7 @@ import { TestView, nodesToArrays } from './util';
import { deepClone } from 'vs/base/common/objects';
// Simple example:
//
// +-----+---------------+
// | 4 | 2 |
// +-----+---------+-----+
@@ -16,6 +17,16 @@ import { deepClone } from 'vs/base/common/objects';
// +---------------+ 3 |
// | 5 | |
// +---------------+-----+
//
// V
// +-H
// | +-4
// | +-2
// +-H
// | +-V
// | +-1
// | +-5
// +-3
suite('Grid', function () {
let container: HTMLElement;
+19 -14
View File
@@ -19,26 +19,31 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
// ESM-comment-begin
if (typeof require === 'function') {
// check if the JS lives on a different origin
const workerMain = require.toUrl('./' + workerId);
if (/^(http:)|(https:)|(file:)/.test(workerMain)) {
const currentUrl = String(window.location);
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
if (workerMain.substring(0, currentOrigin.length) !== currentOrigin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const workerBaseUrl = workerMain.substr(0, workerMain.length - 'vs/base/worker/workerMain.js'.length);
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${workerMain}');/*${label}*/`;
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
return new Worker(url);
}
}
return new Worker(workerMain + '#' + label);
const workerUrl = getWorkerBootstrapUrl(workerMain, label);
return new Worker(workerUrl, { name: label });
}
// ESM-comment-end
throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`);
}
export function getWorkerBootstrapUrl(scriptPath: string, label: string): string {
if (/^(http:)|(https:)|(file:)/.test(scriptPath)) {
const currentUrl = String(window.location);
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
if (scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length);
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
return url;
}
}
return scriptPath + '#' + label;
}
function isPromiseLike<T>(obj: any): obj is PromiseLike<T> {
if (typeof obj.then === 'function') {
return true;
+1 -1
View File
@@ -7,7 +7,7 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, p
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { getShellEnvironment } from 'vs/code/node/shellEnv';
@@ -49,7 +49,7 @@ export interface IPointerHandlerHelper {
*/
getLastViewCursorsRenderData(): IViewCursorRenderData[];
shouldSuppressMouseDownOnViewZone(viewZoneId: number): boolean;
shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean;
shouldSuppressMouseDownOnWidget(widgetId: string): boolean;
/**
@@ -19,7 +19,7 @@ import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
export interface IViewZoneData {
viewZoneId: number;
viewZoneId: string;
positionBefore: Position | null;
positionAfter: Position | null;
position: Position;
+4 -4
View File
@@ -83,17 +83,17 @@ export interface IViewZoneChangeAccessor {
* @param zone Zone to create
* @return A unique identifier to the view zone.
*/
addZone(zone: IViewZone): number;
addZone(zone: IViewZone): string;
/**
* Remove a zone
* @param id A unique identifier to the view zone, as returned by the `addZone` call.
*/
removeZone(id: number): void;
removeZone(id: string): void;
/**
* Change a zone's position.
* The editor will rescan the `afterLineNumber` and `afterColumn` properties of a view zone.
*/
layoutZone(id: number): void;
layoutZone(id: string): void;
}
/**
@@ -399,7 +399,7 @@ export interface ICodeEditor extends editorCommon.IEditor {
*/
onWillType(listener: (text: string) => void): IDisposable;
/**
* An event emitted before interpreting typed characters (on the keyboard).
* An event emitted after interpreting typed characters (on the keyboard).
* @event
* @internal
*/
@@ -55,8 +55,7 @@ export class OpenerService implements IOpenerService {
if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) {
// open http or default mail application
dom.windowOpenNoOpener(encodeURI(resource.toString(true)));
return Promise.resolve(true);
return this.openExternal(resource);
} else if (equalsIgnoreCase(scheme, Schemas.command)) {
// run command or bail out if command isn't known
@@ -100,4 +99,10 @@ export class OpenerService implements IOpenerService {
).then(() => true);
}
}
openExternal(resource: URI): Promise<boolean> {
dom.windowOpenNoOpener(encodeURI(resource.toString(true)));
return Promise.resolve(true);
}
}
+4 -4
View File
@@ -248,7 +248,7 @@ export class View extends ViewEventHandler {
getLastViewCursorsRenderData: () => {
return this.viewCursors.getLastRenderData() || [];
},
shouldSuppressMouseDownOnViewZone: (viewZoneId: number) => {
shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => {
return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
},
shouldSuppressMouseDownOnWidget: (widgetId: string) => {
@@ -473,17 +473,17 @@ export class View extends ViewEventHandler {
this._renderOnce(() => {
const changeAccessor: editorBrowser.IViewZoneChangeAccessor = {
addZone: (zone: editorBrowser.IViewZone): number => {
addZone: (zone: editorBrowser.IViewZone): string => {
zonesHaveChanged = true;
return this.viewZones.addZone(zone);
},
removeZone: (id: number): void => {
removeZone: (id: string): void => {
if (!id) {
return;
}
zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged;
},
layoutZone: (id: number): void => {
layoutZone: (id: string): void => {
if (!id) {
return;
}
@@ -14,7 +14,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
export interface IMyViewZone {
whitespaceId: number;
whitespaceId: string;
delegate: IViewZone;
isVisible: boolean;
domNode: FastDomNode<HTMLElement>;
@@ -74,7 +74,7 @@ export class ViewZones extends ViewPart {
const id = keys[i];
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
if (this._context.viewLayout.changeWhitespace(parseInt(id, 10), props.afterViewLineNumber, props.heightInPx)) {
if (this._context.viewLayout.changeWhitespace(id, props.afterViewLineNumber, props.heightInPx)) {
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
hadAChange = true;
}
@@ -183,7 +183,7 @@ export class ViewZones extends ViewPart {
};
}
public addZone(zone: IViewZone): number {
public addZone(zone: IViewZone): string {
const props = this._computeWhitespaceProps(zone);
const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
@@ -200,18 +200,18 @@ export class ViewZones extends ViewPart {
myZone.domNode.setPosition('absolute');
myZone.domNode.domNode.style.width = '100%';
myZone.domNode.setDisplay('none');
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString());
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
this.domNode.appendChild(myZone.domNode);
if (myZone.marginDomNode) {
myZone.marginDomNode.setPosition('absolute');
myZone.marginDomNode.domNode.style.width = '100%';
myZone.marginDomNode.setDisplay('none');
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString());
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
this.marginDomNode.appendChild(myZone.marginDomNode);
}
this._zones[myZone.whitespaceId.toString()] = myZone;
this._zones[myZone.whitespaceId] = myZone;
this.setShouldRender();
@@ -219,10 +219,10 @@ export class ViewZones extends ViewPart {
return myZone.whitespaceId;
}
public removeZone(id: number): boolean {
if (this._zones.hasOwnProperty(id.toString())) {
const zone = this._zones[id.toString()];
delete this._zones[id.toString()];
public removeZone(id: string): boolean {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
delete this._zones[id];
this._context.viewLayout.removeWhitespace(zone.whitespaceId);
zone.domNode.removeAttribute('monaco-visible-view-zone');
@@ -242,10 +242,10 @@ export class ViewZones extends ViewPart {
return false;
}
public layoutZone(id: number): boolean {
public layoutZone(id: string): boolean {
let changed = false;
if (this._zones.hasOwnProperty(id.toString())) {
const zone = this._zones[id.toString()];
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
const props = this._computeWhitespaceProps(zone.delegate);
// const newOrdinal = this._getZoneOrdinal(zone.delegate);
changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed;
@@ -259,9 +259,9 @@ export class ViewZones extends ViewPart {
return changed;
}
public shouldSuppressMouseDownOnViewZone(id: number): boolean {
if (this._zones.hasOwnProperty(id.toString())) {
const zone = this._zones[id.toString()];
public shouldSuppressMouseDownOnViewZone(id: string): boolean {
if (this._zones.hasOwnProperty(id)) {
const zone = this._zones[id];
return Boolean(zone.delegate.suppressMouseDown);
}
return false;
@@ -314,7 +314,7 @@ export class ViewZones extends ViewPart {
let hasVisibleZone = false;
for (let i = 0, len = visibleWhitespaces.length; i < len; i++) {
visibleZones[visibleWhitespaces[i].id.toString()] = visibleWhitespaces[i];
visibleZones[visibleWhitespaces[i].id] = visibleWhitespaces[i];
hasVisibleZone = true;
}
@@ -69,7 +69,7 @@ interface IDiffEditorWidgetStyle {
}
class VisualEditorState {
private _zones: number[];
private _zones: string[];
private _zonesMap: { [zoneId: string]: boolean; };
private _decorations: string[];
+1 -16
View File
@@ -110,21 +110,6 @@ export function createTextBuffer(value: string | model.ITextBufferFactory, defau
let MODEL_ID = 0;
/**
* Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
*/
function singleLetter(result: number): string {
const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
result = result % (2 * LETTERS_CNT);
if (result < LETTERS_CNT) {
return String.fromCharCode(CharCode.a + result);
}
return String.fromCharCode(CharCode.A + result - LETTERS_CNT);
}
const LIMIT_FIND_COUNT = 999;
export const LONG_LINE_BOUNDARY = 10000;
@@ -343,7 +328,7 @@ export class TextModel extends Disposable implements model.ITextModel {
}
});
this._instanceId = singleLetter(MODEL_ID);
this._instanceId = strings.singleLetterHash(MODEL_ID);
this._lastDecorationId = 0;
this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees();
@@ -63,14 +63,14 @@ export class LinesLayout {
* @param heightInPx The height of the whitespace, in pixels.
* @return An id that can be used later to mutate or delete the whitespace
*/
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): number {
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth);
}
/**
* Change properties associated with a certain whitespace.
*/
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight);
}
@@ -80,7 +80,7 @@ export class LinesLayout {
* @param id The whitespace to remove
* @return Returns true if the whitespace is found and it is removed.
*/
public removeWhitespace(id: number): boolean {
public removeWhitespace(id: string): boolean {
return this._whitespaces.removeWhitespace(id);
}
@@ -173,13 +173,13 @@ export class ViewLayout extends Disposable implements IViewLayout {
// ---- IVerticalLayoutProvider
public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number {
public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string {
return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth);
}
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight);
}
public removeWhitespace(id: number): boolean {
public removeWhitespace(id: string): boolean {
return this._linesLayout.removeWhitespace(id);
}
public getVerticalOffsetForLineNumber(lineNumber: number): number {
@@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
export interface IEditorWhitespace {
readonly id: number;
readonly id: string;
readonly afterLineNumber: number;
readonly heightInLines: number;
}
@@ -15,6 +17,10 @@ export interface IEditorWhitespace {
*/
export class WhitespaceComputer {
private static INSTANCE_COUNT = 0;
private readonly _instanceId: string;
/**
* heights[i] is the height in pixels for whitespace at index i
*/
@@ -48,7 +54,7 @@ export class WhitespaceComputer {
/**
* ids[i] is the whitespace id of whitespace at index i
*/
private readonly _ids: number[];
private readonly _ids: string[];
/**
* index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members)
@@ -65,6 +71,7 @@ export class WhitespaceComputer {
private _minWidth: number;
constructor() {
this._instanceId = strings.singleLetterHash(++WhitespaceComputer.INSTANCE_COUNT);
this._heights = [];
this._minWidths = [];
this._ids = [];
@@ -113,21 +120,20 @@ export class WhitespaceComputer {
* @param heightInPx The height of the whitespace, in pixels.
* @return An id that can be used later to mutate or delete the whitespace
*/
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): number {
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
heightInPx = heightInPx | 0;
minWidth = minWidth | 0;
let id = (++this._lastWhitespaceId);
let id = this._instanceId + (++this._lastWhitespaceId);
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal);
this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx, minWidth);
this._minWidth = -1; /* marker for not being computed */
return id;
}
private _insertWhitespaceAtIndex(id: number, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void {
id = id | 0;
private _insertWhitespaceAtIndex(id: string, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void {
insertIndex = insertIndex | 0;
afterLineNumber = afterLineNumber | 0;
ordinal = ordinal | 0;
@@ -150,15 +156,14 @@ export class WhitespaceComputer {
}
}
this._whitespaceId2Index[id.toString()] = insertIndex;
this._whitespaceId2Index[id] = insertIndex;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
}
/**
* Change properties associated with a certain whitespace.
*/
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
id = id | 0;
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
newAfterLineNumber = newAfterLineNumber | 0;
newHeight = newHeight | 0;
@@ -175,13 +180,11 @@ export class WhitespaceComputer {
* @param newHeightInPx The new height of the whitespace, in pixels
* @return Returns true if the whitespace is found and if the new height is different than the old height
*/
public changeWhitespaceHeight(id: number, newHeightInPx: number): boolean {
id = id | 0;
public changeWhitespaceHeight(id: string, newHeightInPx: number): boolean {
newHeightInPx = newHeightInPx | 0;
let sid = id.toString();
if (this._whitespaceId2Index.hasOwnProperty(sid)) {
let index = this._whitespaceId2Index[sid];
if (this._whitespaceId2Index.hasOwnProperty(id)) {
let index = this._whitespaceId2Index[id];
if (this._heights[index] !== newHeightInPx) {
this._heights[index] = newHeightInPx;
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
@@ -198,13 +201,11 @@ export class WhitespaceComputer {
* @param newAfterLineNumber The new line number the whitespace will follow
* @return Returns true if the whitespace is found and if the new line number is different than the old line number
*/
public changeWhitespaceAfterLineNumber(id: number, newAfterLineNumber: number): boolean {
id = id | 0;
public changeWhitespaceAfterLineNumber(id: string, newAfterLineNumber: number): boolean {
newAfterLineNumber = newAfterLineNumber | 0;
let sid = id.toString();
if (this._whitespaceId2Index.hasOwnProperty(sid)) {
let index = this._whitespaceId2Index[sid];
if (this._whitespaceId2Index.hasOwnProperty(id)) {
let index = this._whitespaceId2Index[id];
if (this._afterLineNumbers[index] !== newAfterLineNumber) {
// `afterLineNumber` changed for this whitespace
@@ -236,14 +237,10 @@ export class WhitespaceComputer {
* @param id The whitespace to remove
* @return Returns true if the whitespace is found and it is removed.
*/
public removeWhitespace(id: number): boolean {
id = id | 0;
let sid = id.toString();
if (this._whitespaceId2Index.hasOwnProperty(sid)) {
let index = this._whitespaceId2Index[sid];
delete this._whitespaceId2Index[sid];
public removeWhitespace(id: string): boolean {
if (this._whitespaceId2Index.hasOwnProperty(id)) {
let index = this._whitespaceId2Index[id];
delete this._whitespaceId2Index[id];
this._removeWhitespaceAtIndex(index);
this._minWidth = -1; /* marker for not being computed */
return true;
@@ -459,7 +456,7 @@ export class WhitespaceComputer {
* @param index The index of the whitespace.
* @return `id` of whitespace at `index`.
*/
public getIdForWhitespaceIndex(index: number): number {
public getIdForWhitespaceIndex(index: number): string {
index = index | 0;
return this._ids[index];
+4 -4
View File
@@ -17,7 +17,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceCompute
import { ITheme } from 'vs/platform/theme/common/themeService';
export interface IViewWhitespaceViewportData {
readonly id: number;
readonly id: string;
readonly afterLineNumber: number;
readonly verticalOffset: number;
readonly height: number;
@@ -74,15 +74,15 @@ export interface IViewLayout {
* Reserve rendering space.
* @return an identifier that can be later used to remove or change the whitespace.
*/
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number;
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string;
/**
* Change the properties of a whitespace.
*/
changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean;
changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean;
/**
* Remove rendering space
*/
removeWhitespace(id: number): boolean;
removeWhitespace(id: string): boolean;
/**
* Get the layout information for whitespaces currently in the viewport
*/
@@ -193,7 +193,7 @@ export class CodeLensWidget {
private readonly _editor: editorBrowser.ICodeEditor;
private readonly _viewZone!: CodeLensViewZone;
private readonly _viewZoneId!: number;
private readonly _viewZoneId!: string;
private readonly _contentWidget!: CodeLensContentWidget;
private _decorationIds: string[];
private _data: CodeLensItem[];
+2 -10
View File
@@ -116,7 +116,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private readonly _replaceFocusTracker: dom.IFocusTracker;
private readonly _replaceInputFocused: IContextKey<boolean>;
private _viewZone?: FindWidgetViewZone;
private _viewZoneId?: number;
private _viewZoneId?: string;
private _resizeSash!: Sash;
private _resized!: boolean;
@@ -224,15 +224,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (!this._isVisible) {
return;
}
if (this._viewZoneId === undefined) {
return;
}
this._codeEditor.changeViewZones((accessor) => {
if (this._viewZoneId) {
accessor.removeZone(this._viewZoneId);
}
this._viewZoneId = undefined;
});
this._viewZoneId = undefined;
}));
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer';
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
import { IModeService } from 'vs/editor/common/services/modeService';
import { URI } from 'vs/base/common/uri';
@@ -33,7 +33,7 @@ export class MarkdownRenderer extends Disposable {
super();
}
private getOptions(disposeables: DisposableStore): RenderOptions {
private getOptions(disposeables: DisposableStore): MarkdownRenderOptions {
return {
codeBlockRenderer: (languageAlias, value) => {
// In markdown,
@@ -51,7 +51,7 @@ const WIDGET_ID = 'vs.editor.contrib.zoneWidget';
export class ViewZoneDelegate implements IViewZone {
public domNode: HTMLElement;
public id: number = 0; // A valid zone id should be greater than 0
public id: string = ''; // A valid zone id should be greater than 0
public afterLineNumber: number;
public afterColumn: number;
public heightInLines: number;
@@ -7,7 +7,7 @@ import 'vs/css!./accessibilityHelp';
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { renderFormattedText } from 'vs/base/browser/htmlContentRenderer';
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { Widget } from 'vs/base/browser/ui/widget';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
+3 -3
View File
@@ -3605,17 +3605,17 @@ declare namespace monaco.editor {
* @param zone Zone to create
* @return A unique identifier to the view zone.
*/
addZone(zone: IViewZone): number;
addZone(zone: IViewZone): string;
/**
* Remove a zone
* @param id A unique identifier to the view zone, as returned by the `addZone` call.
*/
removeZone(id: number): void;
removeZone(id: string): void;
/**
* Change a zone's position.
* The editor will rescan the `afterLineNumber` and `afterColumn` properties of a view zone.
*/
layoutZone(id: number): void;
layoutZone(id: string): void;
}
/**
@@ -342,10 +342,10 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
@IProductService private readonly productService: IProductService,
@optional(IStorageService) private readonly storageService: IStorageService,
) {
const config = productService.extensionsGallery;
const config = productService.productConfiguration.extensionsGallery;
this.extensionsGalleryUrl = config && config.serviceUrl;
this.extensionsControlUrl = config && config.controlUrl;
this.commonHeadersPromise = resolveMarketplaceHeaders(productService.version, this.environmentService, this.fileService, this.storageService);
this.commonHeadersPromise = resolveMarketplaceHeaders(productService.productConfiguration.version, this.environmentService, this.fileService, this.storageService);
}
private api(path = ''): string {
@@ -358,7 +358,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, version?: string): Promise<IGalleryExtension | null> {
const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1;
if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) {
if (extension && extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.productConfiguration.version)) {
return Promise.resolve(extension);
}
const { id, uuid } = extension ? extension.identifier : <IExtensionIdentifier>arg1;
@@ -384,7 +384,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const versionAsset = rawExtension.versions.filter(v => v.version === version)[0];
if (versionAsset) {
const extension = toExtension(rawExtension, versionAsset, 0, query);
if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.version)) {
if (extension.properties.engine && isEngineValid(extension.properties.engine, this.productService.productConfiguration.version)) {
return extension;
}
}
@@ -619,7 +619,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => {
if (galleryExtensions.length) {
if (compatible) {
return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine, this.productService.version) ? v : null)))
return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine, this.productService.productConfiguration.version) ? v : null)))
.then(versions => versions
.filter(v => !!v)
.map(v => ({ version: v!.version, date: v!.lastUpdated })));
@@ -705,7 +705,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
if (!engine) {
return null;
}
if (isEngineValid(engine, this.productService.version)) {
if (isEngineValid(engine, this.productService.productConfiguration.version)) {
return Promise.resolve(version);
}
}
@@ -737,7 +737,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const version = versions[0];
return this.getEngine(version)
.then(engine => {
if (!isEngineValid(engine, this.productService.version)) {
if (!isEngineValid(engine, this.productService.productConfiguration.version)) {
return this.getLastValidExtensionVersionRecursively(extension, versions.slice(1));
}
@@ -817,4 +817,4 @@ export async function resolveMarketplaceHeaders(version: string, environmentServ
return headers;
}
}
@@ -106,7 +106,7 @@ export interface IExtensionContributions {
localizations?: ILocalization[];
}
export type ExtensionKind = 'ui' | 'workspace';
export type ExtensionKind = 'ui' | 'workspace' | 'web';
export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifier {
return thing
@@ -221,4 +221,4 @@ export interface IExtensionDescription extends IExtensionManifest {
export function isLanguagePackExtension(manifest: IExtensionManifest): boolean {
return manifest.contributes && manifest.contributes.localizations ? manifest.contributes.localizations.length > 0 : false;
}
}
+12 -2
View File
@@ -9,7 +9,6 @@ import { IDisposable } from 'vs/base/common/lifecycle';
export const IOpenerService = createDecorator<IOpenerService>('openerService');
export interface IOpener {
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
}
@@ -18,6 +17,9 @@ export interface IOpenerService {
_serviceBrand: any;
/**
* Register a participant that can handle the open() call.
*/
registerOpener(opener: IOpener): IDisposable;
/**
@@ -27,10 +29,18 @@ export interface IOpenerService {
* @return A promise that resolves when the opening is done.
*/
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
/**
* Opens a URL externally.
*
* @param url A resource to open externally.
*/
openExternal(resource: URI): Promise<boolean>;
}
export const NullOpenerService: IOpenerService = Object.freeze({
_serviceBrand: undefined,
registerOpener() { return { dispose() { } }; },
open() { return Promise.resolve(false); }
open() { return Promise.resolve(false); },
openExternal() { return Promise.resolve(false); }
});
@@ -10,40 +10,17 @@ export class ProductService implements IProductService {
_serviceBrand!: ServiceIdentifier<IProductService>;
private readonly productConfiguration: IProductConfiguration | null;
readonly productConfiguration: IProductConfiguration;
constructor() {
const element = document.getElementById('vscode-remote-product-configuration');
this.productConfiguration = element ? JSON.parse(element.getAttribute('data-settings')!) : null;
this.productConfiguration = {
...element ? JSON.parse(element.getAttribute('data-settings')!) : {
version: '1.38.0-unknown',
nameLong: 'Unknown',
extensionAllowedProposedApi: [],
}, ...{ urlProtocol: '', enableTelemetry: false }
};
}
get version(): string { return this.productConfiguration && this.productConfiguration.version ? this.productConfiguration.version : '1.38.0-unknown'; }
get commit(): string | undefined { return this.productConfiguration ? this.productConfiguration.commit : undefined; }
get nameLong(): string { return this.productConfiguration ? this.productConfiguration.nameLong : 'Unknown'; }
get urlProtocol(): string { return ''; }
get extensionAllowedProposedApi(): readonly string[] { return this.productConfiguration ? this.productConfiguration.extensionAllowedProposedApi : []; }
get uiExtensions(): readonly string[] | undefined { return this.productConfiguration ? this.productConfiguration.uiExtensions : undefined; }
get enableTelemetry(): boolean { return false; }
get sendASmile(): { reportIssueUrl: string, requestFeatureUrl: string } | undefined { return this.productConfiguration ? this.productConfiguration.sendASmile : undefined; }
get extensionsGallery() { return this.productConfiguration ? this.productConfiguration.extensionsGallery : undefined; }
get settingsSearchBuildId(): number | undefined { return this.productConfiguration ? this.productConfiguration.settingsSearchBuildId : undefined; }
get settingsSearchUrl(): string | undefined { return this.productConfiguration ? this.productConfiguration.settingsSearchUrl : undefined; }
get experimentsUrl(): string | undefined { return this.productConfiguration ? this.productConfiguration.experimentsUrl : undefined; }
get extensionKeywords(): { [extension: string]: readonly string[]; } | undefined { return this.productConfiguration ? this.productConfiguration.extensionKeywords : undefined; }
get extensionAllowedBadgeProviders(): readonly string[] | undefined { return this.productConfiguration ? this.productConfiguration.extensionAllowedBadgeProviders : undefined; }
get aiConfig() { return this.productConfiguration ? this.productConfiguration.aiConfig : undefined; }
}
+1 -32
View File
@@ -11,38 +11,7 @@ export interface IProductService {
_serviceBrand: ServiceIdentifier<any>;
readonly version: string;
readonly commit?: string;
readonly date?: string;
readonly nameLong: string;
readonly urlProtocol: string;
readonly extensionAllowedProposedApi: readonly string[];
readonly uiExtensions?: readonly string[];
readonly enableTelemetry: boolean;
readonly extensionsGallery?: {
readonly serviceUrl: string;
readonly itemUrl: string;
readonly controlUrl: string;
readonly recommendationsUrl: string;
};
readonly sendASmile?: {
readonly reportIssueUrl: string;
readonly requestFeatureUrl: string;
};
readonly settingsSearchBuildId?: number;
readonly settingsSearchUrl?: string;
readonly experimentsUrl?: string;
readonly extensionKeywords?: { [extension: string]: readonly string[]; };
readonly extensionAllowedBadgeProviders?: readonly string[];
readonly aiConfig?: {
readonly asimovKey: string;
};
readonly productConfiguration: IProductConfiguration;
}
export interface IProductConfiguration {
+7 -26
View File
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IProductService } from 'vs/platform/product/common/product';
import { IProductService, IProductConfiguration } from 'vs/platform/product/common/product';
import product from 'vs/platform/product/node/product';
import pkg from 'vs/platform/product/node/package';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
@@ -12,31 +12,12 @@ export class ProductService implements IProductService {
_serviceBrand!: ServiceIdentifier<IProductService>;
get version(): string { return pkg.version; }
readonly productConfiguration: IProductConfiguration;
get commit(): string | undefined { return product.commit; }
constructor() {
this.productConfiguration = {
...product, ...{ version: pkg.version }
};
}
get nameLong(): string { return product.nameLong; }
get urlProtocol(): string { return product.urlProtocol; }
get extensionAllowedProposedApi(): readonly string[] { return product.extensionAllowedProposedApi; }
get uiExtensions(): readonly string[] | undefined { return product.uiExtensions; }
get enableTelemetry(): boolean { return product.enableTelemetry; }
get sendASmile(): { reportIssueUrl: string, requestFeatureUrl: string } { return product.sendASmile; }
get extensionsGallery() { return product.extensionsGallery; }
get settingsSearchBuildId(): number | undefined { return product.settingsSearchBuildId; }
get settingsSearchUrl(): string | undefined { return product.settingsSearchUrl; }
get experimentsUrl(): string | undefined { return product.experimentsUrl; }
get extensionKeywords(): { [extension: string]: readonly string[]; } | undefined { return product.extensionKeywords; }
get extensionAllowedBadgeProviders(): readonly string[] | undefined { return product.extensionAllowedBadgeProviders; }
}
@@ -144,8 +144,10 @@ export class BrowserStorageService extends Disposable implements IStorageService
// Signal as event so that clients can still store data
this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN });
// Close DBs
this.globalStorage.close();
this.workspaceStorage.close();
// We explicitly do not close our DBs because writing data onBeforeUnload()
// can result in unexpected results. Namely, it seems that - even though this
// operation is async - sometimes it is being triggered on unload and
// succeeds. Often though, the DBs turn out to be empty because the write
// never had a chance to complete.
}
}
@@ -12,6 +12,7 @@ export const lastSessionDateStorageKey = 'telemetry.lastSessionDate';
import * as Platform from 'vs/base/common/platform';
import * as uuid from 'vs/base/common/uuid';
import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils';
export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> {
const result: { [name: string]: string | undefined; } = Object.create(null);
@@ -69,18 +70,3 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS
return result;
}
function cleanRemoteAuthority(remoteAuthority?: string): string {
if (!remoteAuthority) {
return 'none';
}
let ret = 'other';
// Whitelisted remote authorities
['ssh-remote', 'dev-container', 'attached-container', 'wsl'].forEach((res: string) => {
if (remoteAuthority!.indexOf(`${res}+`) === 0) {
ret = res;
}
});
return ret;
}
@@ -288,6 +288,22 @@ export function validateTelemetryData(data?: any): { properties: Properties, mea
};
}
export function cleanRemoteAuthority(remoteAuthority?: string): string {
if (!remoteAuthority) {
return 'none';
}
let ret = 'other';
// Whitelisted remote authorities
['ssh-remote', 'dev-container', 'attached-container', 'wsl'].forEach((res: string) => {
if (remoteAuthority!.indexOf(`${res}+`) === 0) {
ret = res;
}
});
return ret;
}
function flatten(obj: any, result: { [key: string]: any }, order: number = 0, prefix?: string): void {
if (!obj) {
return;
@@ -6,6 +6,7 @@
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils';
export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, installSourcePath: string, remoteAuthority?: string): Promise<{ [name: string]: string | undefined }> {
const result = await resolveCommonProperties(commit, version, machineId, installSourcePath);
@@ -30,19 +31,3 @@ export async function resolveWorkbenchCommonProperties(storageService: IStorageS
return result;
}
function cleanRemoteAuthority(remoteAuthority?: string): string {
if (!remoteAuthority) {
return 'none';
}
let ret = 'other';
// Whitelisted remote authorities
['ssh-remote', 'dev-container', 'wsl'].forEach((res: string) => {
if (remoteAuthority!.indexOf(`${res}+`) === 0) {
ret = res;
}
});
return ret;
}
@@ -13,17 +13,14 @@ import { URI } from 'vs/base/common/uri';
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
export class WindowsService implements IWindowsService {
_serviceBrand: any;
_serviceBrand!: ServiceIdentifier<any>;
private channel: IChannel;
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
this.channel = mainProcessService.getChannel('windows');
}
get onWindowOpen(): Event<number> { return this.channel.listen('onWindowOpen'); }
get onWindowFocus(): Event<number> { return this.channel.listen('onWindowFocus'); }
get onWindowBlur(): Event<number> { return this.channel.listen('onWindowBlur'); }
@@ -31,6 +28,10 @@ export class WindowsService implements IWindowsService {
get onWindowUnmaximize(): Event<number> { return this.channel.listen('onWindowUnmaximize'); }
get onRecentlyOpenedChange(): Event<void> { return this.channel.listen('onRecentlyOpenedChange'); }
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
this.channel = mainProcessService.getChannel('windows');
}
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
return this.channel.call('pickFileFolderAndOpen', options);
}
@@ -21,7 +21,7 @@ class EditorWebviewZone implements IViewZone {
readonly afterColumn: number;
readonly heightInLines: number;
private _id?: number;
private _id?: string;
// suppressMouseDown?: boolean | undefined;
// heightInPx?: number | undefined;
// minWidthInPx?: number | undefined;
@@ -327,7 +327,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) {
return true;
}
if (this._productService.urlProtocol === link.scheme) {
if (this._productService.productConfiguration.urlProtocol === link.scheme) {
return true;
}
return !!webview.webview.contentOptions.enableCommandUris && link.scheme === 'command';
@@ -6,12 +6,13 @@
import { Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol';
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { extractLocalHostUriMetaDataForPortMapping } from 'vs/workbench/contrib/webview/common/portMapping';
import { IOpenerService } from 'vs/platform/opener/common/opener';
@extHostNamedCustomer(MainContext.MainThreadWindow)
export class MainThreadWindow implements MainThreadWindowShape {
@@ -23,7 +24,7 @@ export class MainThreadWindow implements MainThreadWindowShape {
constructor(
extHostContext: IExtHostContext,
@IWindowService private readonly windowService: IWindowService,
@IWindowsService private readonly windowsService: IWindowsService,
@IOpenerService private readonly openerService: IOpenerService,
@ITunnelService private readonly tunnelService: ITunnelService,
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
@@ -58,7 +59,7 @@ export class MainThreadWindow implements MainThreadWindowShape {
}
}
return this.windowsService.openExternal(encodeURI(uri.toString(true)));
return this.openerService.openExternal(uri);
}
private getOrCreateTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import * as path from 'vs/base/common/path';
import { originalFSPath } from 'vs/base/common/resources';
import { originalFSPath, joinPath } from 'vs/base/common/resources';
import { Barrier } from 'vs/base/common/async';
import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { TernarySearchTree } from 'vs/base/common/map';
@@ -332,14 +332,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
return Promise.all<any>([
this._loadCommonJSModule(extensionDescription.main, activationTimesBuilder),
this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder),
this._loadExtensionContext(extensionDescription)
]).then(values => {
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
});
}
protected abstract _loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
@@ -536,7 +536,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
let testRunner: ITestRunner | INewTestRunner | undefined;
let requireError: Error | undefined;
try {
testRunner = await this._loadCommonJSModule(extensionTestsPath, new ExtensionActivationTimesBuilder(false));
testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
} catch (error) {
requireError = error;
}
@@ -11,6 +11,8 @@ import { connectProxyResolver } from 'vs/workbench/services/extensions/node/prox
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadService';
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
export class ExtHostExtensionService extends AbstractExtHostExtensionService {
@@ -55,12 +57,15 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
};
}
protected _loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
if (module.scheme !== Schemas.file) {
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
}
let r: T | null = null;
activationTimesBuilder.codeLoadingStart();
this._logService.info(`ExtensionService#loadCommonJSModule ${modulePath}`);
this._logService.info(`ExtensionService#loadCommonJSModule ${module.toString(true)}`);
try {
r = require.__$__nodeRequire<T>(modulePath);
r = require.__$__nodeRequire<T>(module.fsPath);
} catch (e) {
return Promise.reject(e);
} finally {
@@ -6,13 +6,16 @@
import { createApiFactoryAndRegisterActors, IExtensionApiFactory } from 'vs/workbench/api/common/extHost.api.impl';
import { ExtensionActivationTimesBuilder } from 'vs/workbench/api/common/extHostExtensionActivator';
import { AbstractExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { endsWith } from 'vs/base/common/strings';
import { endsWith, startsWith } from 'vs/base/common/strings';
import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import * as vscode from 'vscode';
import { TernarySearchTree } from 'vs/base/common/map';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { joinPath } from 'vs/base/common/resources';
class ApiInstances {
@@ -52,11 +55,8 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
this._apiInstances = new ApiInstances(apiFactory, extensionPath, this._registry, configProvider);
}
protected _loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
// make sure modulePath ends with `.js`
const suffix = '.js';
modulePath = endsWith(modulePath, suffix) ? modulePath : modulePath + suffix;
interface FakeCommonJSSelf {
module?: object;
@@ -69,30 +69,66 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
// FAKE commonjs world that only collects exports
const patchSelf: FakeCommonJSSelf = <any>self;
const module = { exports: {} };
patchSelf.module = module;
patchSelf.exports = module.exports;
patchSelf.window = self; // <- that's improper but might help extensions that aren't authored correctly
// FAKE require function that only works for the vscode-module
patchSelf.require = (module: string) => {
if (module !== 'vscode') {
throw new Error(`Cannot load module '${module}'`);
const moduleStack: URI[] = [];
patchSelf.require = (mod: string) => {
const parent = moduleStack[moduleStack.length - 1];
if (mod === 'vscode') {
return this._apiInstances!.get(parent.fsPath);
}
return this._apiInstances!.get(modulePath);
if (!startsWith(mod, '.')) {
throw new Error(`Cannot load module '${mod}'`);
}
const exports = Object.create(null);
patchSelf.module = { exports };
patchSelf.exports = exports;
const next = joinPath(parent, '..', ensureSuffix(mod, '.js'));
moduleStack.push(next);
importScripts(asDomUri(next).toString(true));
moduleStack.pop();
return exports;
};
try {
activationTimesBuilder.codeLoadingStart();
importScripts(modulePath);
const exports = Object.create(null);
patchSelf.module = { exports };
patchSelf.exports = exports;
module = module.with({ path: ensureSuffix(module.path, '.js') });
moduleStack.push(module);
importScripts(asDomUri(module).toString(true));
moduleStack.pop();
} finally {
activationTimesBuilder.codeLoadingStop();
}
return Promise.resolve(module.exports as T);
return Promise.resolve<T>(exports);
}
async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise<void> {
throw new Error('Not supported');
}
}
// todo@joh this is a copy of `dom.ts#asDomUri`
function asDomUri(uri: URI): URI {
if (Schemas.vscodeRemote === uri.scheme) {
// rewrite vscode-remote-uris to uris of the window location
// so that they can be intercepted by the service worker
return URI.parse(window.location.href).with({ path: '/vscode-remote', query: JSON.stringify(uri) });
}
return uri;
}
function ensureSuffix(path: string, suffix: string): string {
return endsWith(path, suffix) ? path : path + suffix;
}
@@ -63,7 +63,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
return;
}
return width;
return Math.max(width, 300);
}
//#endregion
@@ -7,6 +7,10 @@
border-top: none !important; /* less clutter: do not show any border for first views in a panel */
}
.monaco-panel-view .panel > .panel-header > .actions.show {
display: initial;
}
.monaco-panel-view .panel > .panel-header h3.title {
white-space: nowrap;
text-overflow: ellipsis;
@@ -41,6 +41,7 @@ export interface IViewletPanelOptions extends IPanelOptions {
actionRunner?: IActionRunner;
id: string;
title: string;
showActionsAlways?: boolean;
}
export abstract class ViewletPanel extends Panel implements IView {
@@ -67,6 +68,7 @@ export abstract class ViewletPanel extends Panel implements IView {
protected actionRunner?: IActionRunner;
protected toolbar: ToolBar;
private readonly showActionsAlways: boolean = false;
private headerContainer: HTMLElement;
private titleContainer: HTMLElement;
@@ -82,6 +84,7 @@ export abstract class ViewletPanel extends Panel implements IView {
this.id = options.id;
this.title = options.title;
this.actionRunner = options.actionRunner;
this.showActionsAlways = !!options.showActionsAlways;
this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService);
}
@@ -133,6 +136,7 @@ export abstract class ViewletPanel extends Panel implements IView {
this.renderHeaderTitle(container, this.title);
const actions = append(container, $('.actions'));
toggleClass(actions, 'show', this.showActionsAlways);
this.toolbar = new ToolBar(actions, this.contextMenuService, {
orientation: ActionsOrientation.HORIZONTAL,
actionViewItemProvider: action => this.getActionViewItem(action),
-5
View File
@@ -84,11 +84,6 @@ class CodeRendererMain extends Disposable {
}));
this._register(workbench.onShutdown(() => this.dispose()));
// Driver
if (this.configuration.driver) {
registerWindowDriver();
}
// Startup
workbench.startup();
}
+17 -24
View File
@@ -22,7 +22,7 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
// tslint:disable-next-line: import-patterns
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { addDisposableListener, EventType, windowOpenNoOpener } from 'vs/base/browser/dom';
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
import { pathsToEditors } from 'vs/workbench/common/editor';
@@ -31,14 +31,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage';
// tslint:disable-next-line: import-patterns
import { IExperimentService, IExperiment, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProductService } from 'vs/platform/product/common/product';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
// tslint:disable-next-line: import-patterns
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats';
//#region Extension Tips
@@ -741,13 +741,13 @@ export class SimpleWindowsService implements IWindowsService {
async openAboutDialog(): Promise<void> {
const detail = localize('aboutDetail',
"Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
this.productService.version || 'Unknown',
this.productService.commit || 'Unknown',
this.productService.date || 'Unknown',
this.productService.productConfiguration.version || 'Unknown',
this.productService.productConfiguration.commit || 'Unknown',
this.productService.productConfiguration.date || 'Unknown',
navigator.userAgent
);
const result = await this.dialogService.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail });
const result = await this.dialogService.show(Severity.Info, this.productService.productConfiguration.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail });
if (result === 0) {
this.clipboardService.writeText(detail);
@@ -845,33 +845,26 @@ registerSingleton(ITunnelService, SimpleTunnelService);
//#endregion
//#region experiments
//#region workspace stats
class WorkspaceStatsService implements IWorkspaceStatsService {
class ExperimentService implements IExperimentService {
_serviceBrand: any;
async getExperimentById(id: string): Promise<IExperiment> {
return {
enabled: false,
id: '',
state: ExperimentState.NoRun
};
getTags(): Promise<Tags> {
return Promise.resolve({});
}
async getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]> {
return [];
getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined {
return undefined;
}
async getCuratedExtensionsList(curatedExtensionsKey: string): Promise<string[]> {
return [];
getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise<string[]> {
return Promise.resolve([]);
}
markAsCompleted(experimentId: string): void { }
onExperimentEnabled: Event<IExperiment> = Event.None;
}
registerSingleton(IExperimentService, ExperimentService);
registerSingleton(IWorkspaceStatsService, WorkspaceStatsService);
//#endregion
@@ -7,7 +7,7 @@ import 'vs/css!./accessibility';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { renderFormattedText } from 'vs/base/browser/htmlContentRenderer';
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { Widget } from 'vs/base/browser/ui/widget';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -5,7 +5,7 @@
import * as dom from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
@@ -175,7 +175,7 @@ export class DebugSession implements IDebugSession {
return this.raw!.initialize({
clientID: 'vscode',
clientName: this.productService.nameLong,
clientName: this.productService.productConfiguration.nameLong,
adapterID: this.configuration.type,
pathFormat: 'path',
linesStartAt1: true,
@@ -4,12 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService';
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt';
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
registerSingleton(IExperimentService, ExperimentService, true);
@@ -4,7 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { language } from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { match } from 'vs/base/common/glob';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { CancellationToken } from 'vs/base/common/cancellation';
import { distinct } from 'vs/base/common/arrays';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { IProductService } from 'vs/platform/product/common/product';
import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats';
export const enum ExperimentState {
Evaluating,
@@ -56,4 +72,388 @@ export interface IExperimentService {
onExperimentEnabled: Event<IExperiment>;
}
export const IExperimentService = createDecorator<IExperimentService>('experimentService');
export const IExperimentService = createDecorator<IExperimentService>('experimentService');
interface IExperimentStorageState {
enabled: boolean;
state: ExperimentState;
editCount?: number;
lastEditedDate?: string;
}
interface IRawExperiment {
id: string;
enabled?: boolean;
condition?: {
insidersOnly?: boolean;
newUser?: boolean;
displayLanguage?: string;
installedExtensions?: {
excludes?: string[];
includes?: string[];
},
fileEdits?: {
filePathPattern?: string;
workspaceIncludes?: string[];
workspaceExcludes?: string[];
minEditCount: number;
},
experimentsPreviouslyRun?: {
excludes?: string[];
includes?: string[];
}
userProbability?: number;
};
action?: IExperimentAction;
}
export class ExperimentService extends Disposable implements IExperimentService {
_serviceBrand: any;
private _experiments: IExperiment[] = [];
private _loadExperimentsPromise: Promise<void>;
private _curatedMapping = Object.create(null);
private readonly _onExperimentEnabled = this._register(new Emitter<IExperiment>());
onExperimentEnabled: Event<IExperiment> = this._onExperimentEnabled.event;
constructor(
@IStorageService private readonly storageService: IStorageService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@ITextFileService private readonly textFileService: ITextFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IRequestService private readonly requestService: IRequestService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService,
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService
) {
super();
this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments());
}
public getExperimentById(id: string): Promise<IExperiment> {
return this._loadExperimentsPromise.then(() => {
return this._experiments.filter(x => x.id === id)[0];
});
}
public getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]> {
return this._loadExperimentsPromise.then(() => {
if (type === ExperimentActionType.Custom) {
return this._experiments.filter(x => x.enabled && (!x.action || x.action.type === type));
}
return this._experiments.filter(x => x.enabled && x.action && x.action.type === type);
});
}
public getCuratedExtensionsList(curatedExtensionsKey: string): Promise<string[]> {
return this._loadExperimentsPromise.then(() => {
for (const experiment of this._experiments) {
if (experiment.enabled
&& experiment.state === ExperimentState.Run
&& this._curatedMapping[experiment.id]
&& this._curatedMapping[experiment.id].curatedExtensionsKey === curatedExtensionsKey) {
return this._curatedMapping[experiment.id].curatedExtensionsList;
}
}
return [];
});
}
public markAsCompleted(experimentId: string): void {
const storageKey = 'experiments.' + experimentId;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
experimentState.state = ExperimentState.Complete;
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
}
protected getExperiments(): Promise<IRawExperiment[]> {
if (!this.productService.productConfiguration.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
return Promise.resolve([]);
}
return this.requestService.request({ type: 'GET', url: this.productService.productConfiguration.experimentsUrl }, CancellationToken.None).then(context => {
if (context.res.statusCode !== 200) {
return Promise.resolve(null);
}
return asJson(context).then((result: any) => {
return result && Array.isArray(result['experiments']) ? result['experiments'] : [];
});
}, () => Promise.resolve(null));
}
private loadExperiments(): Promise<any> {
return this.getExperiments().then(rawExperiments => {
// Offline mode
if (!rawExperiments) {
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []);
if (Array.isArray(allExperimentIdsFromStorage)) {
allExperimentIdsFromStorage.forEach(experimentId => {
const storageKey = 'experiments.' + experimentId;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), null);
if (experimentState) {
this._experiments.push({
id: experimentId,
enabled: experimentState.enabled,
state: experimentState.state
});
}
});
}
return Promise.resolve(null);
}
// Clear disbaled/deleted experiments from storage
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []);
const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase());
if (Array.isArray(allExperimentIdsFromStorage)) {
allExperimentIdsFromStorage.forEach(experiment => {
if (enabledExperiments.indexOf(experiment) === -1) {
this.storageService.remove(`experiments.${experiment}`, StorageScope.GLOBAL);
}
});
}
if (enabledExperiments.length) {
this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL);
} else {
this.storageService.remove('allExperiments', StorageScope.GLOBAL);
}
const promises = rawExperiments.map(experiment => {
const processedExperiment: IExperiment = {
id: experiment.id,
enabled: !!experiment.enabled,
state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun
};
if (experiment.action) {
processedExperiment.action = {
type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom,
properties: experiment.action.properties
};
if (processedExperiment.action.type === ExperimentActionType.Prompt) {
((<IExperimentActionPromptProperties>processedExperiment.action.properties).commands || []).forEach(x => {
if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) {
this._curatedMapping[experiment.id] = x;
}
});
}
if (!processedExperiment.action.properties) {
processedExperiment.action.properties = {};
}
}
this._experiments.push(processedExperiment);
if (!processedExperiment.enabled) {
return Promise.resolve(null);
}
const storageKey = 'experiments.' + experiment.id;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
if (!experimentState.hasOwnProperty('enabled')) {
experimentState.enabled = processedExperiment.enabled;
}
if (!experimentState.hasOwnProperty('state')) {
experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun;
} else {
processedExperiment.state = experimentState.state;
}
return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => {
experimentState.state = processedExperiment.state = state;
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
if (state === ExperimentState.Run) {
this.fireRunExperiment(processedExperiment);
}
return Promise.resolve(null);
});
});
return Promise.all(promises).then(() => {
type ExperimentsClassification = {
experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
this.telemetryService.publicLog2<{ experiments: IExperiment[] }, ExperimentsClassification>('experiments', { experiments: this._experiments });
});
});
}
private fireRunExperiment(experiment: IExperiment) {
this._onExperimentEnabled.fire(experiment);
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
if (runExperimentIdsFromStorage.indexOf(experiment.id) === -1) {
runExperimentIdsFromStorage.push(experiment.id);
}
// Ensure we dont store duplicates
const distinctExperiments = distinct(runExperimentIdsFromStorage);
if (runExperimentIdsFromStorage.length !== distinctExperiments.length) {
this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL);
}
}
private checkExperimentDependencies(experiment: IRawExperiment): boolean {
const experimentsPreviouslyRun = experiment.condition ? experiment.condition.experimentsPreviouslyRun : undefined;
if (experimentsPreviouslyRun) {
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
let includeCheck = true;
let excludeCheck = true;
const includes = experimentsPreviouslyRun.includes;
if (Array.isArray(includes)) {
includeCheck = runExperimentIdsFromStorage.some(x => includes.indexOf(x) > -1);
}
const excludes = experimentsPreviouslyRun.excludes;
if (includeCheck && Array.isArray(excludes)) {
excludeCheck = !runExperimentIdsFromStorage.some(x => excludes.indexOf(x) > -1);
}
if (!includeCheck || !excludeCheck) {
return false;
}
}
return true;
}
private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise<ExperimentState> {
if (processedExperiment.state !== ExperimentState.Evaluating) {
return Promise.resolve(processedExperiment.state);
}
if (!experiment.enabled) {
return Promise.resolve(ExperimentState.NoRun);
}
const condition = experiment.condition;
if (!condition) {
return Promise.resolve(ExperimentState.Run);
}
if (!this.checkExperimentDependencies(experiment)) {
return Promise.resolve(ExperimentState.NoRun);
}
if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) {
return Promise.resolve(ExperimentState.NoRun);
}
const isNewUser = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL);
if ((condition.newUser === true && !isNewUser)
|| (condition.newUser === false && isNewUser)) {
return Promise.resolve(ExperimentState.NoRun);
}
if (typeof condition.displayLanguage === 'string') {
let localeToCheck = condition.displayLanguage.toLowerCase();
let displayLanguage = language!.toLowerCase();
if (localeToCheck !== displayLanguage) {
const a = displayLanguage.indexOf('-');
const b = localeToCheck.indexOf('-');
if (a > -1) {
displayLanguage = displayLanguage.substr(0, a);
}
if (b > -1) {
localeToCheck = localeToCheck.substr(0, b);
}
if (displayLanguage !== localeToCheck) {
return Promise.resolve(ExperimentState.NoRun);
}
}
}
if (!condition.userProbability) {
condition.userProbability = 1;
}
let extensionsCheckPromise = Promise.resolve(true);
const installedExtensions = condition.installedExtensions;
if (installedExtensions) {
extensionsCheckPromise = this.extensionManagementService.getInstalled(ExtensionType.User).then(locals => {
let includesCheck = true;
let excludesCheck = true;
const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`);
if (Array.isArray(installedExtensions.includes) && installedExtensions.includes.length) {
const extensionIncludes = installedExtensions.includes.map(e => e.toLowerCase());
includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1);
}
if (Array.isArray(installedExtensions.excludes) && installedExtensions.excludes.length) {
const extensionExcludes = installedExtensions.excludes.map(e => e.toLowerCase());
excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1);
}
return includesCheck && excludesCheck;
});
}
const storageKey = 'experiments.' + experiment.id;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
return extensionsCheckPromise.then(success => {
const fileEdits = condition.fileEdits;
if (!success || !fileEdits || typeof fileEdits.minEditCount !== 'number') {
const runExperiment = success && typeof condition.userProbability === 'number' && Math.random() < condition.userProbability;
return runExperiment ? ExperimentState.Run : ExperimentState.NoRun;
}
experimentState.editCount = experimentState.editCount || 0;
if (experimentState.editCount >= fileEdits.minEditCount) {
return ExperimentState.Run;
}
const onSaveHandler = this.textFileService.models.onModelsSaved(e => {
const date = new Date().toDateString();
const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
if (latestExperimentState.state !== ExperimentState.Evaluating) {
onSaveHandler.dispose();
return;
}
e.forEach(async event => {
if (event.kind !== StateChange.SAVED
|| latestExperimentState.state !== ExperimentState.Evaluating
|| date === latestExperimentState.lastEditedDate
|| (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount)
) {
return;
}
let filePathCheck = true;
let workspaceCheck = true;
if (typeof fileEdits.filePathPattern === 'string') {
filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath);
}
if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) {
const tags = await this.workspaceStatsService.getTags();
workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]);
}
if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) {
const tags = await this.workspaceStatsService.getTags();
workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]);
}
if (filePathCheck && workspaceCheck) {
latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1;
latestExperimentState.lastEditedDate = date;
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL);
}
});
if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) {
processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun;
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL);
if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) {
this.fireRunExperiment(processedExperiment);
}
}
});
this._register(onSaveHandler);
return ExperimentState.Evaluating;
});
}
}
function safeParse(text: string | undefined, defaultObject: any) {
try {
return text ? JSON.parse(text) || defaultObject : defaultObject;
} catch (e) {
return defaultObject;
}
}
@@ -1,407 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { language } from 'vs/base/common/platform';
import { Disposable } from 'vs/base/common/lifecycle';
import { match } from 'vs/base/common/glob';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { Emitter, Event } from 'vs/base/common/event';
import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { CancellationToken } from 'vs/base/common/cancellation';
import { distinct } from 'vs/base/common/arrays';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { ExperimentState, IExperimentAction, IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IProductService } from 'vs/platform/product/common/product';
import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService';
interface IExperimentStorageState {
enabled: boolean;
state: ExperimentState;
editCount?: number;
lastEditedDate?: string;
}
interface IRawExperiment {
id: string;
enabled?: boolean;
condition?: {
insidersOnly?: boolean;
newUser?: boolean;
displayLanguage?: string;
installedExtensions?: {
excludes?: string[];
includes?: string[];
},
fileEdits?: {
filePathPattern?: string;
workspaceIncludes?: string[];
workspaceExcludes?: string[];
minEditCount: number;
},
experimentsPreviouslyRun?: {
excludes?: string[];
includes?: string[];
}
userProbability?: number;
};
action?: IExperimentAction;
}
export class ExperimentService extends Disposable implements IExperimentService {
_serviceBrand: any;
private _experiments: IExperiment[] = [];
private _loadExperimentsPromise: Promise<void>;
private _curatedMapping = Object.create(null);
private readonly _onExperimentEnabled = this._register(new Emitter<IExperiment>());
onExperimentEnabled: Event<IExperiment> = this._onExperimentEnabled.event;
constructor(
@IStorageService private readonly storageService: IStorageService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@ITextFileService private readonly textFileService: ITextFileService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IRequestService private readonly requestService: IRequestService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService,
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService
) {
super();
this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments());
}
public getExperimentById(id: string): Promise<IExperiment> {
return this._loadExperimentsPromise.then(() => {
return this._experiments.filter(x => x.id === id)[0];
});
}
public getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]> {
return this._loadExperimentsPromise.then(() => {
if (type === ExperimentActionType.Custom) {
return this._experiments.filter(x => x.enabled && (!x.action || x.action.type === type));
}
return this._experiments.filter(x => x.enabled && x.action && x.action.type === type);
});
}
public getCuratedExtensionsList(curatedExtensionsKey: string): Promise<string[]> {
return this._loadExperimentsPromise.then(() => {
for (const experiment of this._experiments) {
if (experiment.enabled
&& experiment.state === ExperimentState.Run
&& this._curatedMapping[experiment.id]
&& this._curatedMapping[experiment.id].curatedExtensionsKey === curatedExtensionsKey) {
return this._curatedMapping[experiment.id].curatedExtensionsList;
}
}
return [];
});
}
public markAsCompleted(experimentId: string): void {
const storageKey = 'experiments.' + experimentId;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
experimentState.state = ExperimentState.Complete;
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
}
protected getExperiments(): Promise<IRawExperiment[]> {
if (!this.productService.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
return Promise.resolve([]);
}
return this.requestService.request({ type: 'GET', url: this.productService.experimentsUrl }, CancellationToken.None).then(context => {
if (context.res.statusCode !== 200) {
return Promise.resolve(null);
}
return asJson(context).then((result: any) => {
return result && Array.isArray(result['experiments']) ? result['experiments'] : [];
});
}, () => Promise.resolve(null));
}
private loadExperiments(): Promise<any> {
return this.getExperiments().then(rawExperiments => {
// Offline mode
if (!rawExperiments) {
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []);
if (Array.isArray(allExperimentIdsFromStorage)) {
allExperimentIdsFromStorage.forEach(experimentId => {
const storageKey = 'experiments.' + experimentId;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), null);
if (experimentState) {
this._experiments.push({
id: experimentId,
enabled: experimentState.enabled,
state: experimentState.state
});
}
});
}
return Promise.resolve(null);
}
// Clear disbaled/deleted experiments from storage
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []);
const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase());
if (Array.isArray(allExperimentIdsFromStorage)) {
allExperimentIdsFromStorage.forEach(experiment => {
if (enabledExperiments.indexOf(experiment) === -1) {
this.storageService.remove(`experiments.${experiment}`, StorageScope.GLOBAL);
}
});
}
if (enabledExperiments.length) {
this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL);
} else {
this.storageService.remove('allExperiments', StorageScope.GLOBAL);
}
const promises = rawExperiments.map(experiment => {
const processedExperiment: IExperiment = {
id: experiment.id,
enabled: !!experiment.enabled,
state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun
};
if (experiment.action) {
processedExperiment.action = {
type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom,
properties: experiment.action.properties
};
if (processedExperiment.action.type === ExperimentActionType.Prompt) {
((<IExperimentActionPromptProperties>processedExperiment.action.properties).commands || []).forEach(x => {
if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) {
this._curatedMapping[experiment.id] = x;
}
});
}
if (!processedExperiment.action.properties) {
processedExperiment.action.properties = {};
}
}
this._experiments.push(processedExperiment);
if (!processedExperiment.enabled) {
return Promise.resolve(null);
}
const storageKey = 'experiments.' + experiment.id;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
if (!experimentState.hasOwnProperty('enabled')) {
experimentState.enabled = processedExperiment.enabled;
}
if (!experimentState.hasOwnProperty('state')) {
experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun;
} else {
processedExperiment.state = experimentState.state;
}
return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => {
experimentState.state = processedExperiment.state = state;
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
if (state === ExperimentState.Run) {
this.fireRunExperiment(processedExperiment);
}
return Promise.resolve(null);
});
});
return Promise.all(promises).then(() => {
type ExperimentsClassification = {
experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
};
this.telemetryService.publicLog2<{ experiments: IExperiment[] }, ExperimentsClassification>('experiments', { experiments: this._experiments });
});
});
}
private fireRunExperiment(experiment: IExperiment) {
this._onExperimentEnabled.fire(experiment);
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
if (runExperimentIdsFromStorage.indexOf(experiment.id) === -1) {
runExperimentIdsFromStorage.push(experiment.id);
}
// Ensure we dont store duplicates
const distinctExperiments = distinct(runExperimentIdsFromStorage);
if (runExperimentIdsFromStorage.length !== distinctExperiments.length) {
this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL);
}
}
private checkExperimentDependencies(experiment: IRawExperiment): boolean {
const experimentsPreviouslyRun = experiment.condition ? experiment.condition.experimentsPreviouslyRun : undefined;
if (experimentsPreviouslyRun) {
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
let includeCheck = true;
let excludeCheck = true;
const includes = experimentsPreviouslyRun.includes;
if (Array.isArray(includes)) {
includeCheck = runExperimentIdsFromStorage.some(x => includes.indexOf(x) > -1);
}
const excludes = experimentsPreviouslyRun.excludes;
if (includeCheck && Array.isArray(excludes)) {
excludeCheck = !runExperimentIdsFromStorage.some(x => excludes.indexOf(x) > -1);
}
if (!includeCheck || !excludeCheck) {
return false;
}
}
return true;
}
private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise<ExperimentState> {
if (processedExperiment.state !== ExperimentState.Evaluating) {
return Promise.resolve(processedExperiment.state);
}
if (!experiment.enabled) {
return Promise.resolve(ExperimentState.NoRun);
}
const condition = experiment.condition;
if (!condition) {
return Promise.resolve(ExperimentState.Run);
}
if (!this.checkExperimentDependencies(experiment)) {
return Promise.resolve(ExperimentState.NoRun);
}
if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) {
return Promise.resolve(ExperimentState.NoRun);
}
const isNewUser = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL);
if ((condition.newUser === true && !isNewUser)
|| (condition.newUser === false && isNewUser)) {
return Promise.resolve(ExperimentState.NoRun);
}
if (typeof condition.displayLanguage === 'string') {
let localeToCheck = condition.displayLanguage.toLowerCase();
let displayLanguage = language!.toLowerCase();
if (localeToCheck !== displayLanguage) {
const a = displayLanguage.indexOf('-');
const b = localeToCheck.indexOf('-');
if (a > -1) {
displayLanguage = displayLanguage.substr(0, a);
}
if (b > -1) {
localeToCheck = localeToCheck.substr(0, b);
}
if (displayLanguage !== localeToCheck) {
return Promise.resolve(ExperimentState.NoRun);
}
}
}
if (!condition.userProbability) {
condition.userProbability = 1;
}
let extensionsCheckPromise = Promise.resolve(true);
const installedExtensions = condition.installedExtensions;
if (installedExtensions) {
extensionsCheckPromise = this.extensionManagementService.getInstalled(ExtensionType.User).then(locals => {
let includesCheck = true;
let excludesCheck = true;
const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`);
if (Array.isArray(installedExtensions.includes) && installedExtensions.includes.length) {
const extensionIncludes = installedExtensions.includes.map(e => e.toLowerCase());
includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1);
}
if (Array.isArray(installedExtensions.excludes) && installedExtensions.excludes.length) {
const extensionExcludes = installedExtensions.excludes.map(e => e.toLowerCase());
excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1);
}
return includesCheck && excludesCheck;
});
}
const storageKey = 'experiments.' + experiment.id;
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
return extensionsCheckPromise.then(success => {
const fileEdits = condition.fileEdits;
if (!success || !fileEdits || typeof fileEdits.minEditCount !== 'number') {
const runExperiment = success && typeof condition.userProbability === 'number' && Math.random() < condition.userProbability;
return runExperiment ? ExperimentState.Run : ExperimentState.NoRun;
}
experimentState.editCount = experimentState.editCount || 0;
if (experimentState.editCount >= fileEdits.minEditCount) {
return ExperimentState.Run;
}
const onSaveHandler = this.textFileService.models.onModelsSaved(e => {
const date = new Date().toDateString();
const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
if (latestExperimentState.state !== ExperimentState.Evaluating) {
onSaveHandler.dispose();
return;
}
e.forEach(async event => {
if (event.kind !== StateChange.SAVED
|| latestExperimentState.state !== ExperimentState.Evaluating
|| date === latestExperimentState.lastEditedDate
|| (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount)
) {
return;
}
let filePathCheck = true;
let workspaceCheck = true;
if (typeof fileEdits.filePathPattern === 'string') {
filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath);
}
if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) {
const tags = await this.workspaceStatsService.getTags();
workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]);
}
if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) {
const tags = await this.workspaceStatsService.getTags();
workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]);
}
if (filePathCheck && workspaceCheck) {
latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1;
latestExperimentState.lastEditedDate = date;
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL);
}
});
if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) {
processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun;
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL);
if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) {
this.fireRunExperiment(processedExperiment);
}
}
});
this._register(onSaveHandler);
return ExperimentState.Evaluating;
});
}
}
function safeParse(text: string | undefined, defaultObject: any) {
try {
return text ? JSON.parse(text) || defaultObject : defaultObject;
} catch (e) {
return defaultObject;
}
}
@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/contrib/experiments/common/experimentService';
import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService';
import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
@@ -12,7 +12,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt';
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService, LocalizedPromptText } from 'vs/workbench/contrib/experiments/common/experimentService';
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
@@ -133,29 +133,33 @@ interface IActiveElement {
focus(): void;
}
interface IExtensionEditorTemplate {
iconContainer: HTMLElement;
icon: HTMLImageElement;
name: HTMLElement;
identifier: HTMLElement;
preview: HTMLElement;
builtin: HTMLElement;
license: HTMLElement;
publisher: HTMLElement;
installCount: HTMLElement;
rating: HTMLElement;
repository: HTMLElement;
description: HTMLElement;
extensionActionBar: ActionBar;
navbar: NavBar;
content: HTMLElement;
subtextContainer: HTMLElement;
subtext: HTMLElement;
ignoreActionbar: ActionBar;
header: HTMLElement;
}
export class ExtensionEditor extends BaseEditor {
static readonly ID: string = 'workbench.editor.extension';
private iconContainer: HTMLElement;
private icon: HTMLImageElement;
private name: HTMLElement;
private identifier: HTMLElement;
private preview: HTMLElement;
private builtin: HTMLElement;
private license: HTMLElement;
private publisher: HTMLElement;
private installCount: HTMLElement;
private rating: HTMLElement;
private repository: HTMLElement;
private description: HTMLElement;
private extensionActionBar: ActionBar;
private navbar: NavBar;
private content: HTMLElement;
private subtextContainer: HTMLElement;
private subtext: HTMLElement;
private ignoreActionbar: ActionBar;
private header: HTMLElement;
private template: IExtensionEditorTemplate | undefined;
private extensionReadme: Cache<string> | null;
private extensionChangelog: Cache<string> | null;
@@ -164,7 +168,7 @@ export class ExtensionEditor extends BaseEditor {
private layoutParticipants: ILayoutParticipant[] = [];
private readonly contentDisposables = this._register(new DisposableStore());
private readonly transientDisposables = this._register(new DisposableStore());
private activeElement: IActiveElement | null;
private activeElement: IActiveElement | null = null;
private editorLoadComplete: boolean = false;
constructor(
@@ -192,43 +196,43 @@ export class ExtensionEditor extends BaseEditor {
const root = append(parent, $('.extension-editor'));
root.tabIndex = 0; // this is required for the focus tracker on the editor
root.style.outline = 'none';
this.header = append(root, $('.header'));
const header = append(root, $('.header'));
this.iconContainer = append(this.header, $('.icon-container'));
this.icon = append(this.iconContainer, $<HTMLImageElement>('img.icon', { draggable: false }));
const iconContainer = append(header, $('.icon-container'));
const icon = append(iconContainer, $<HTMLImageElement>('img.icon', { draggable: false }));
const details = append(this.header, $('.details'));
const details = append(header, $('.details'));
const title = append(details, $('.title'));
this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
const name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
const identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") }));
this.preview.textContent = localize('preview', "Preview");
const preview = append(title, $('span.preview', { title: localize('preview', "Preview") }));
preview.textContent = localize('preview', "Preview");
this.builtin = append(title, $('span.builtin'));
this.builtin.textContent = localize('builtin', "Built-in");
const builtin = append(title, $('span.builtin'));
builtin.textContent = localize('builtin', "Built-in");
const subtitle = append(details, $('.subtitle'));
this.publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 }));
const publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name"), tabIndex: 0 }));
this.installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 }));
const installCount = append(subtitle, $('span.install', { title: localize('install count', "Install count"), tabIndex: 0 }));
this.rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 }));
const rating = append(subtitle, $('span.rating.clickable', { title: localize('rating', "Rating"), tabIndex: 0 }));
this.repository = append(subtitle, $('span.repository.clickable'));
this.repository.textContent = localize('repository', 'Repository');
this.repository.style.display = 'none';
this.repository.tabIndex = 0;
const repository = append(subtitle, $('span.repository.clickable'));
repository.textContent = localize('repository', 'Repository');
repository.style.display = 'none';
repository.tabIndex = 0;
this.license = append(subtitle, $('span.license.clickable'));
this.license.textContent = localize('license', 'License');
this.license.style.display = 'none';
this.license.tabIndex = 0;
const license = append(subtitle, $('span.license.clickable'));
license.textContent = localize('license', 'License');
license.style.display = 'none';
license.tabIndex = 0;
this.description = append(details, $('.description'));
const description = append(details, $('.description'));
const extensionActions = append(details, $('.actions'));
this.extensionActionBar = new ActionBar(extensionActions, {
const extensionActionBar = this._register(new ActionBar(extensionActions, {
animated: false,
actionViewItemProvider: (action: Action) => {
if (action instanceof ExtensionEditorDropDownAction) {
@@ -236,29 +240,48 @@ export class ExtensionEditor extends BaseEditor {
}
return undefined;
}
});
}));
this.subtextContainer = append(details, $('.subtext-container'));
this.subtext = append(this.subtextContainer, $('.subtext'));
this.ignoreActionbar = new ActionBar(this.subtextContainer, { animated: false });
const subtextContainer = append(details, $('.subtext-container'));
const subtext = append(subtextContainer, $('.subtext'));
const ignoreActionbar = this._register(new ActionBar(subtextContainer, { animated: false }));
this._register(this.extensionActionBar);
this._register(this.ignoreActionbar);
this._register(Event.chain(this.extensionActionBar.onDidRun)
this._register(Event.chain(extensionActionBar.onDidRun)
.map(({ error }) => error)
.filter(error => !!error)
.on(this.onError, this));
this._register(Event.chain(this.ignoreActionbar.onDidRun)
this._register(Event.chain(ignoreActionbar.onDidRun)
.map(({ error }) => error)
.filter(error => !!error)
.on(this.onError, this));
const body = append(root, $('.body'));
this.navbar = new NavBar(body);
const navbar = new NavBar(body);
this.content = append(body, $('.content'));
const content = append(body, $('.content'));
this.template = {
builtin,
content,
description,
extensionActionBar,
header,
icon,
iconContainer,
identifier,
ignoreActionbar,
installCount,
license,
name,
navbar,
preview,
publisher,
rating,
repository,
subtext,
subtextContainer
};
}
private onClick(element: HTMLElement, callback: () => void): IDisposable {
@@ -276,6 +299,13 @@ export class ExtensionEditor extends BaseEditor {
}
async setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise<void> {
if (this.template) {
await this.updateTemplate(input, this.template);
}
return super.setInput(input, options, token);
}
private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate): Promise<void> {
const runningExtensions = await this.extensionService.getExtensions();
const colorThemes = await this.workbenchThemeService.getColorThemes();
const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
@@ -290,18 +320,18 @@ export class ExtensionEditor extends BaseEditor {
this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token)));
this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token)));
const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true);
const onError = Event.once(domEvent(this.icon, 'error'));
onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables);
this.icon.src = extension.iconUrl;
const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, template.iconContainer, true);
const onError = Event.once(domEvent(template.icon, 'error'));
onError(() => template.icon.src = extension.iconUrlFallback, null, this.transientDisposables);
template.icon.src = extension.iconUrl;
this.name.textContent = extension.displayName;
this.identifier.textContent = extension.identifier.id;
this.preview.style.display = extension.preview ? 'inherit' : 'none';
this.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none';
template.name.textContent = extension.displayName;
template.identifier.textContent = extension.identifier.id;
template.preview.style.display = extension.preview ? 'inherit' : 'none';
template.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none';
this.publisher.textContent = extension.publisherDisplayName;
this.description.textContent = extension.description;
template.publisher.textContent = extension.publisherDisplayName;
template.description.textContent = extension.description;
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
let recommendationsData = {};
@@ -319,40 +349,40 @@ export class ExtensionEditor extends BaseEditor {
*/
this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));
toggleClass(this.name, 'clickable', !!extension.url);
toggleClass(this.publisher, 'clickable', !!extension.url);
toggleClass(this.rating, 'clickable', !!extension.url);
toggleClass(template.name, 'clickable', !!extension.url);
toggleClass(template.publisher, 'clickable', !!extension.url);
toggleClass(template.rating, 'clickable', !!extension.url);
if (extension.url) {
this.transientDisposables.add(this.onClick(this.name, () => window.open(extension.url)));
this.transientDisposables.add(this.onClick(this.rating, () => window.open(`${extension.url}#review-details`)));
this.transientDisposables.add(this.onClick(this.publisher, () => {
this.transientDisposables.add(this.onClick(template.name, () => window.open(extension.url)));
this.transientDisposables.add(this.onClick(template.rating, () => window.open(`${extension.url}#review-details`)));
this.transientDisposables.add(this.onClick(template.publisher, () => {
this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`));
}));
if (extension.licenseUrl) {
this.transientDisposables.add(this.onClick(this.license, () => window.open(extension.licenseUrl)));
this.license.style.display = 'initial';
this.transientDisposables.add(this.onClick(template.license, () => window.open(extension.licenseUrl)));
template.license.style.display = 'initial';
} else {
this.license.style.display = 'none';
template.license.style.display = 'none';
}
} else {
this.license.style.display = 'none';
template.license.style.display = 'none';
}
if (extension.repository) {
this.transientDisposables.add(this.onClick(this.repository, () => window.open(extension.repository)));
this.repository.style.display = 'initial';
this.transientDisposables.add(this.onClick(template.repository, () => window.open(extension.repository)));
template.repository.style.display = 'initial';
}
else {
this.repository.style.display = 'none';
template.repository.style.display = 'none';
}
const widgets = [
remoteBadge,
this.instantiationService.createInstance(InstallCountWidget, this.installCount, false),
this.instantiationService.createInstance(RatingsWidget, this.rating, false)
this.instantiationService.createInstance(InstallCountWidget, template.installCount, false),
this.instantiationService.createInstance(RatingsWidget, template.rating, false)
];
const reloadAction = this.instantiationService.createInstance(ReloadAction);
const combinedInstallAction = this.instantiationService.createInstance(CombinedInstallAction);
@@ -375,20 +405,20 @@ export class ExtensionEditor extends BaseEditor {
const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]);
extensionContainers.extension = extension;
this.extensionActionBar.clear();
this.extensionActionBar.push(actions, { icon: true, label: true });
template.extensionActionBar.clear();
template.extensionActionBar.push(actions, { icon: true, label: true });
for (const disposable of [...actions, ...widgets, extensionContainers]) {
this.transientDisposables.add(disposable);
}
this.setSubText(extension, reloadAction);
this.content.innerHTML = ''; // Clear content before setting navbar actions.
this.setSubText(extension, reloadAction, template);
template.content.innerHTML = ''; // Clear content before setting navbar actions.
this.navbar.clear();
this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
template.navbar.clear();
template.navbar.onChange(e => this.onNavbarChange(extension, e, template), this, this.transientDisposables);
if (extension.hasReadme()) {
this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
template.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file"));
}
this.extensionManifest.get()
.promise
@@ -397,25 +427,23 @@ export class ExtensionEditor extends BaseEditor {
combinedInstallAction.manifest = manifest;
}
if (extension.extensionPack.length) {
this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together"));
template.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together"));
}
if (manifest && manifest.contributes) {
this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension"));
template.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension"));
}
if (extension.hasChangelog()) {
this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file"));
template.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file"));
}
if (extension.dependencies.length) {
this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on"));
template.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on"));
}
this.editorLoadComplete = true;
});
return super.setInput(input, options, token);
}
private setSubText(extension: IExtension, reloadAction: ReloadAction): void {
hide(this.subtextContainer);
private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void {
hide(template.subtextContainer);
const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction);
const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction);
@@ -424,23 +452,23 @@ export class ExtensionEditor extends BaseEditor {
ignoreAction.enabled = false;
undoIgnoreAction.enabled = false;
this.ignoreActionbar.clear();
this.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true });
template.ignoreActionbar.clear();
template.ignoreActionbar.push([ignoreAction, undoIgnoreAction], { icon: true, label: true });
this.transientDisposables.add(ignoreAction);
this.transientDisposables.add(undoIgnoreAction);
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
ignoreAction.enabled = true;
this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
show(this.subtextContainer);
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
show(template.subtextContainer);
} else if (this.extensionTipsService.getAllIgnoredRecommendations().global.indexOf(extension.identifier.id.toLowerCase()) !== -1) {
undoIgnoreAction.enabled = true;
this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
show(this.subtextContainer);
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
show(template.subtextContainer);
}
else {
this.subtext.textContent = '';
template.subtext.textContent = '';
}
this.extensionTipsService.onRecommendationChange(change => {
@@ -450,28 +478,28 @@ export class ExtensionEditor extends BaseEditor {
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
if (extRecommendations[extension.identifier.id.toLowerCase()]) {
ignoreAction.enabled = true;
this.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
template.subtext.textContent = extRecommendations[extension.identifier.id.toLowerCase()].reasonText;
}
} else {
undoIgnoreAction.enabled = true;
ignoreAction.enabled = false;
this.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
template.subtext.textContent = localize('recommendationHasBeenIgnored', "You have chosen not to receive recommendations for this extension.");
}
}
});
this.transientDisposables.add(reloadAction.onDidChange(e => {
if (e.tooltip) {
this.subtext.textContent = reloadAction.tooltip;
show(this.subtextContainer);
template.subtext.textContent = reloadAction.tooltip;
show(template.subtextContainer);
ignoreAction.enabled = false;
undoIgnoreAction.enabled = false;
}
if (e.enabled === true) {
show(this.subtextContainer);
show(template.subtextContainer);
}
if (e.enabled === false) {
hide(this.subtextContainer);
hide(template.subtextContainer);
}
}));
}
@@ -495,7 +523,7 @@ export class ExtensionEditor extends BaseEditor {
}
}
private onNavbarChange(extension: IExtension, { id, focus }: { id: string, focus: boolean }): void {
private onNavbarChange(extension: IExtension, { id, focus }: { id: string | null, focus: boolean }, template: IExtensionEditorTemplate): void {
if (this.editorLoadComplete) {
/* __GDPR__
"extensionEditor:navbarChange" : {
@@ -509,30 +537,32 @@ export class ExtensionEditor extends BaseEditor {
}
this.contentDisposables.clear();
this.content.innerHTML = '';
template.content.innerHTML = '';
this.activeElement = null;
this.open(id, extension)
.then(activeElement => {
this.activeElement = activeElement;
if (focus) {
this.focus();
}
});
if (id) {
this.open(id, extension, template)
.then(activeElement => {
this.activeElement = activeElement;
if (focus) {
this.focus();
}
});
}
}
private open(id: string, extension: IExtension): Promise<IActiveElement | null> {
private open(id: string, extension: IExtension, template: IExtensionEditorTemplate): Promise<IActiveElement | null> {
switch (id) {
case NavbarSection.Readme: return this.openReadme();
case NavbarSection.Contributions: return this.openContributions();
case NavbarSection.Changelog: return this.openChangelog();
case NavbarSection.Dependencies: return this.openDependencies(extension);
case NavbarSection.ExtensionPack: return this.openExtensionPack(extension);
case NavbarSection.Readme: return this.openReadme(template);
case NavbarSection.Contributions: return this.openContributions(template);
case NavbarSection.Changelog: return this.openChangelog(template);
case NavbarSection.Dependencies: return this.openDependencies(extension, template);
case NavbarSection.ExtensionPack: return this.openExtensionPack(extension, template);
}
return Promise.resolve(null);
}
private openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string): Promise<IActiveElement> {
return this.loadContents(() => cacheResult)
private openMarkdown(cacheResult: CacheResult<string>, noContentCopy: string, template: IExtensionEditorTemplate): Promise<IActiveElement> {
return this.loadContents(() => cacheResult, template)
.then(marked.parse)
.then(content => this.renderBody(content))
.then(removeEmbeddedSVGs)
@@ -544,7 +574,7 @@ export class ExtensionEditor extends BaseEditor {
{
svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders,
});
webviewElement.mountTo(this.content);
webviewElement.mountTo(template.content);
this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus()));
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, webviewElement);
this.contentDisposables.add(toDisposable(removeLayoutParticipant));
@@ -563,7 +593,7 @@ export class ExtensionEditor extends BaseEditor {
return webviewElement;
})
.then(undefined, () => {
const p = append(this.content, $('p.nocontent'));
const p = append(template.content, $('p.nocontent'));
p.textContent = noContentCopy;
return p;
});
@@ -757,17 +787,17 @@ export class ExtensionEditor extends BaseEditor {
</html>`;
}
private openReadme(): Promise<IActiveElement> {
return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."));
private openReadme(template: IExtensionEditorTemplate): Promise<IActiveElement> {
return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available."), template);
}
private openChangelog(): Promise<IActiveElement> {
return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."));
private openChangelog(template: IExtensionEditorTemplate): Promise<IActiveElement> {
return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available."), template);
}
private openContributions(): Promise<IActiveElement> {
private openContributions(template: IExtensionEditorTemplate): Promise<IActiveElement> {
const content = $('div', { class: 'subcontent', tabindex: '0' });
return this.loadContents(() => this.extensionManifest!.get())
return this.loadContents(() => this.extensionManifest!.get(), template)
.then(manifest => {
if (!manifest) {
return content;
@@ -798,28 +828,28 @@ export class ExtensionEditor extends BaseEditor {
const isEmpty = !renders.some(x => x);
if (isEmpty) {
append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
append(this.content, content);
append(template.content, content);
} else {
append(this.content, scrollableContent.getDomNode());
append(template.content, scrollableContent.getDomNode());
this.contentDisposables.add(scrollableContent);
}
return content;
}, () => {
append(content, $('p.nocontent')).textContent = localize('noContributions', "No Contributions");
append(this.content, content);
append(template.content, content);
return content;
});
}
private openDependencies(extension: IExtension): Promise<IActiveElement> {
private openDependencies(extension: IExtension, template: IExtensionEditorTemplate): Promise<IActiveElement> {
if (arrays.isFalsyOrEmpty(extension.dependencies)) {
append(this.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
return Promise.resolve(this.content);
append(template.content, $('p.nocontent')).textContent = localize('noDependencies', "No Dependencies");
return Promise.resolve(template.content);
}
const content = $('div', { class: 'subcontent' });
const scrollableContent = new DomScrollableElement(content, {});
append(this.content, scrollableContent.getDomNode());
append(template.content, scrollableContent.getDomNode());
this.contentDisposables.add(scrollableContent);
const dependenciesTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.dependencies || [], this.extensionsWorkbenchService), content);
@@ -836,10 +866,10 @@ export class ExtensionEditor extends BaseEditor {
return Promise.resolve({ focus() { dependenciesTree.domFocus(); } });
}
private openExtensionPack(extension: IExtension): Promise<IActiveElement> {
private openExtensionPack(extension: IExtension, template: IExtensionEditorTemplate): Promise<IActiveElement> {
const content = $('div', { class: 'subcontent' });
const scrollableContent = new DomScrollableElement(content, {});
append(this.content, scrollableContent.getDomNode());
append(template.content, scrollableContent.getDomNode());
this.contentDisposables.add(scrollableContent);
const extensionsPackTree = this.instantiationService.createInstance(ExtensionsTree, new ExtensionData(extension, null, extension => extension.extensionPack || [], this.extensionsWorkbenchService), content);
@@ -1259,11 +1289,11 @@ export class ExtensionEditor extends BaseEditor {
return null;
}
private loadContents<T>(loadingTask: () => CacheResult<T>): Promise<T> {
addClass(this.content, 'loading');
private loadContents<T>(loadingTask: () => CacheResult<T>, template: IExtensionEditorTemplate): Promise<T> {
addClass(template.content, 'loading');
const result = loadingTask();
const onDone = () => removeClass(this.content, 'loading');
const onDone = () => removeClass(template.content, 'loading');
result.promise.then(onDone, onDone);
this.contentDisposables.add(toDisposable(() => result.dispose()));
@@ -76,10 +76,10 @@ export function toExtensionDescription(local: ILocalExtension): IExtensionDescri
const promptDownloadManually = (extension: IGalleryExtension | undefined, message: string, error: Error,
instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService, productService: IProductService) => {
if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.extensionsGallery) {
if (!extension || error.name === INSTALL_ERROR_INCOMPATIBLE || error.name === INSTALL_ERROR_MALICIOUS || !productService.productConfiguration.extensionsGallery) {
return Promise.reject(error);
} else {
const downloadUrl = `${productService.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`;
const downloadUrl = `${productService.productConfiguration.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`;
notificationService.prompt(Severity.Error, message, [{
label: localize('download', "Download Manually"),
run: () => openerService.open(URI.parse(downloadUrl)).then(() => {
@@ -3022,7 +3022,10 @@ interface IExtensionPickItem extends IQuickPickItem {
export class InstallLocalExtensionsInRemoteAction extends Action {
private extensions: IExtension[] | undefined = undefined;
constructor(
private readonly selectAndInstall: boolean,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@@ -3034,32 +3037,56 @@ export class InstallLocalExtensionsInRemoteAction extends Action {
) {
super('workbench.extensions.actions.installLocalExtensionsInRemote');
this.update();
this._register(this.extensionsWorkbenchService.onChange(() => this.update()));
this.extensionsWorkbenchService.queryLocal().then(() => this.updateExtensions());
this._register(this.extensionsWorkbenchService.onChange(() => {
if (this.extensions) {
this.updateExtensions();
}
}));
}
get label(): string {
return this.extensionManagementServerService.remoteExtensionManagementServer ?
localize('install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label) : '';
if (this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.selectAndInstall ?
localize('select and install local extensions', "Install Local Extensions in {0}...", this.extensionManagementServerService.remoteExtensionManagementServer.label)
: localize('install local extensions', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label);
}
return '';
}
private updateExtensions(): void {
this.extensions = this.extensionsWorkbenchService.local;
this.update();
}
private update(): void {
this.enabled = this.getLocalExtensionsToInstall().length > 0;
this.enabled = !!this.extensions && this.getExtensionsToInstall(this.extensions).length > 0;
this.tooltip = this.label;
}
private getLocalExtensionsToInstall(): IExtension[] {
return this.extensionsWorkbenchService.local.filter(extension => {
async run(): Promise<void> {
if (this.selectAndInstall) {
return this.selectAndInstallLocalExtensions();
} else {
const extensionsToInstall = await this.queryExtensionsToInstall();
return this.installLocalExtensions(extensionsToInstall);
}
}
private async queryExtensionsToInstall(): Promise<IExtension[]> {
const local = await this.extensionsWorkbenchService.queryLocal();
return this.getExtensionsToInstall(local);
}
private getExtensionsToInstall(local: IExtension[]): IExtension[] {
return local.filter(extension => {
const action = this.instantiationService.createInstance(RemoteInstallAction);
action.extension = extension;
return action.enabled;
});
}
async run(): Promise<void> {
this.selectAndInstallLocalExtensions();
return Promise.resolve();
}
private selectAndInstallLocalExtensions(): void {
private async selectAndInstallLocalExtensions(): Promise<void> {
const quickPick = this.quickInputService.createQuickPick<IExtensionPickItem>();
quickPick.busy = true;
const disposable = quickPick.onDidAccept(() => {
@@ -3069,7 +3096,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action {
this.onDidAccept(quickPick.selectedItems);
});
quickPick.show();
const localExtensionsToInstall = this.getLocalExtensionsToInstall();
const localExtensionsToInstall = await this.queryExtensionsToInstall();
quickPick.busy = false;
if (localExtensionsToInstall.length) {
quickPick.title = localize('install local extensions title', "Install Local Extensions in {0}", this.extensionManagementServerService.remoteExtensionManagementServer!.label);
@@ -156,7 +156,7 @@ export class UnknownExtensionRenderer implements IListRenderer<ITreeNode<IExtens
class OpenExtensionAction extends Action {
private _extensionData: IExtensionData;
private _extensionData: IExtensionData | undefined;
constructor(@IExtensionsWorkbenchService private readonly extensionsWorkdbenchService: IExtensionsWorkbenchService) {
super('extensions.action.openExtension', '');
@@ -166,12 +166,11 @@ class OpenExtensionAction extends Action {
this._extensionData = extension;
}
public get extensionData(): IExtensionData {
return this._extensionData;
}
run(sideByside: boolean): Promise<any> {
return this.extensionsWorkdbenchService.open(this.extensionData.extension, sideByside);
if (this._extensionData) {
return this.extensionsWorkdbenchService.open(this._extensionData.extension, sideByside);
}
return Promise.resolve();
}
}
@@ -263,4 +262,4 @@ export class ExtensionData implements IExtensionData {
}
return null;
}
}
}
@@ -22,7 +22,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, AutoUpdate
import {
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, InstallLocalExtensionsInRemoteAction
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -319,7 +319,8 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet {
private onSearchChange: EventOf<string>;
private readonly _onSearchChange: Emitter<string> = this._register(new Emitter<string>());
private readonly onSearchChange: EventOf<string> = this._onSearchChange.event;
private nonEmptyWorkspaceContextKey: IContextKey<boolean>;
private defaultViewsContextKey: IContextKey<boolean>;
private searchMarketplaceExtensionsContextKey: IContextKey<boolean>;
@@ -333,12 +334,10 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
private defaultRecommendedExtensionsContextKey: IContextKey<boolean>;
private searchDelayer: Delayer<void>;
private root: HTMLElement;
private searchBox: SuggestEnabledInput;
private extensionsBox: HTMLElement;
private primaryActions: IAction[];
private secondaryActions: IAction[] | null;
private root: HTMLElement | undefined;
private searchBox: SuggestEnabledInput | undefined;
private primaryActions: IAction[] | undefined;
private secondaryActions: IAction[] | null = null;
private readonly searchViewletState: MementoObject;
constructor(
@@ -348,7 +347,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
@IInstantiationService instantiationService: IInstantiationService,
@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService,
@INotificationService private readonly notificationService: INotificationService,
@IViewletService private readonly viewletService: IViewletService,
@IThemeService themeService: IThemeService,
@@ -418,32 +416,35 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this._register(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService));
const _searchChange = new Emitter<string>();
this.onSearchChange = _searchChange.event;
this._register(this.searchBox.onInputDidChange(() => {
this.triggerSearch();
_searchChange.fire(this.searchBox.getValue());
this._onSearchChange.fire(this.searchBox!.getValue());
}, this));
this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this));
this._register(this.onDidChangeVisibility(visible => {
if (visible) {
this.searchBox.focus();
this.searchBox!.focus();
}
}));
this.extensionsBox = append(this.root, $('.extensions'));
super.create(this.extensionsBox);
super.create(append(this.root, $('.extensions')));
}
focus(): void {
this.searchBox.focus();
if (this.searchBox) {
this.searchBox.focus();
}
}
layout(dimension: Dimension): void {
toggleClass(this.root, 'narrow', dimension.width <= 300);
this.searchBox.layout({ height: 20, width: dimension.width - 34 });
if (this.root) {
toggleClass(this.root, 'narrow', dimension.width <= 300);
}
if (this.searchBox) {
this.searchBox.layout({ height: 20, width: dimension.width - 34 });
}
super.layout(new Dimension(dimension.width, dimension.height - 38));
}
@@ -454,7 +455,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
getActions(): IAction[] {
if (!this.primaryActions) {
this.primaryActions = [
this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox.getValue())
this.instantiationService.createInstance(ClearExtensionsInputAction, ClearExtensionsInputAction.ID, ClearExtensionsInputAction.LABEL, this.onSearchChange, this.searchBox ? this.searchBox.getValue() : '')
];
}
return this.primaryActions;
@@ -478,7 +479,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
this.instantiationService.createInstance(CheckForUpdatesAction, CheckForUpdatesAction.ID, CheckForUpdatesAction.LABEL),
...(this.configurationService.getValue(AutoUpdateConfigurationKey) ? [this.instantiationService.createInstance(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL)] : [this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), this.instantiationService.createInstance(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL)]),
this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL),
...(this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer ? [this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction)] : []),
new Separator(),
this.instantiationService.createInstance(DisableAllAction, DisableAllAction.ID, DisableAllAction.LABEL),
this.instantiationService.createInstance(EnableAllAction, EnableAllAction.ID, EnableAllAction.LABEL)
@@ -489,22 +489,24 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio
}
search(value: string): void {
const event = new Event('input', { bubbles: true }) as SearchInputEvent;
event.immediate = true;
if (this.searchBox) {
const event = new Event('input', { bubbles: true }) as SearchInputEvent;
event.immediate = true;
this.searchBox.setValue(value);
this.searchBox.setValue(value);
}
}
private triggerSearch(immediate = false): void {
this.searchDelayer.trigger(() => this.doSearch(), immediate || !this.searchBox.getValue() ? 0 : 500).then(undefined, err => this.onError(err));
private triggerSearch(): void {
this.searchDelayer.trigger(() => this.doSearch(), this.searchBox && this.searchBox.getValue() ? 500 : 0).then(undefined, err => this.onError(err));
}
private normalizedQuery(): string {
return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:');
return this.searchBox ? this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:') : '';
}
protected saveState(): void {
const value = this.searchBox.getValue();
const value = this.searchBox ? this.searchBox.getValue() : '';
if (ExtensionsListView.isLocalExtensionsQuery(value)) {
this.searchViewletState['query.value'] = value;
} else {
@@ -14,7 +14,7 @@ import { IExtensionManagementServer, IExtensionManagementServerService, IExtensi
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { append, $, toggleClass } from 'vs/base/browser/dom';
import { append, $, toggleClass, addClass } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/browser/extensionsList';
import { IExtension, IExtensionsWorkbenchService, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions';
@@ -27,8 +27,8 @@ import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/brows
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction, InstallLocalExtensionsInRemoteAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { WorkbenchPagedList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -73,15 +73,16 @@ class ExtensionListViewWarning extends Error { }
export class ExtensionsListView extends ViewletPanel {
private readonly server: IExtensionManagementServer | undefined;
private messageContainer: HTMLElement;
private messageSeverityIcon: HTMLElement;
private messageBox: HTMLElement;
private extensionsList: HTMLElement;
private badge: CountBadge;
protected badgeContainer: HTMLElement;
private list: WorkbenchPagedList<IExtension> | null;
private queryRequest: { query: string, request: CancelablePromise<IPagedModel<IExtension>> } | null;
protected readonly server: IExtensionManagementServer | undefined;
private bodyTemplate: {
messageContainer: HTMLElement;
messageSeverityIcon: HTMLElement;
messageBox: HTMLElement;
extensionsList: HTMLElement;
} | undefined;
private badge: CountBadge | undefined;
private list: WorkbenchPagedList<IExtension> | null = null;
private queryRequest: { query: string, request: CancelablePromise<IPagedModel<IExtension>> } | null = null;
constructor(
options: ExtensionsListViewOptions,
@@ -103,31 +104,27 @@ export class ExtensionsListView extends ViewletPanel {
@IProductService protected readonly productService: IProductService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService);
this.server = options.server;
}
protected renderHeader(container: HTMLElement): void {
this.renderHeaderTitle(container);
}
addClass(container, 'extension-view-header');
super.renderHeader(container);
renderHeaderTitle(container: HTMLElement): void {
super.renderHeaderTitle(container, this.title);
this.badgeContainer = append(container, $('.count-badge-wrapper'));
this.badge = new CountBadge(this.badgeContainer);
this.badge = new CountBadge(append(container, $('.count-badge-wrapper')));
this._register(attachBadgeStyler(this.badge, this.themeService));
}
renderBody(container: HTMLElement): void {
this.extensionsList = append(container, $('.extensions-list'));
this.messageContainer = append(container, $('.message-container'));
this.messageSeverityIcon = append(this.messageContainer, $(''));
this.messageBox = append(this.messageContainer, $('.message'));
const extensionsList = append(container, $('.extensions-list'));
const messageContainer = append(container, $('.message-container'));
const messageSeverityIcon = append(messageContainer, $(''));
const messageBox = append(messageContainer, $('.message'));
const delegate = new Delegate();
const extensionsViewState = new ExtensionsViewState();
const renderer = this.instantiationService.createInstance(Renderer, extensionsViewState);
this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], {
this.list = this.instantiationService.createInstance(WorkbenchPagedList, extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions"),
multipleSelectionSupport: false,
setRowLineHeight: false,
@@ -147,10 +144,19 @@ export class ExtensionsListView extends ViewletPanel {
.map(e => e.elements[0])
.filter(e => !!e)
.on(this.pin, this));
this.bodyTemplate = {
extensionsList,
messageBox,
messageContainer,
messageSeverityIcon
};
}
protected layoutBody(height: number, width: number): void {
this.extensionsList.style.height = height + 'px';
if (this.bodyTemplate) {
this.bodyTemplate.extensionsList.style.height = height + 'px';
}
if (this.list) {
this.list.layout(height, width);
}
@@ -482,7 +488,7 @@ export class ExtensionsListView extends ViewletPanel {
}
private _searchExperiments: Promise<IExperiment[]>;
private _searchExperiments: Promise<IExperiment[]> | undefined;
private getSearchExperiments(): Promise<IExperiment[]> {
if (!this._searchExperiments) {
this._searchExperiments = this.experimentService.getExperimentsByType(ExperimentActionType.ExtensionSearchResults);
@@ -693,24 +699,27 @@ export class ExtensionsListView extends ViewletPanel {
this.list.scrollTop = 0;
const count = this.count();
toggleClass(this.extensionsList, 'hidden', count === 0);
toggleClass(this.messageContainer, 'hidden', count > 0);
this.badge.setCount(count);
if (this.bodyTemplate && this.badge) {
if (count === 0 && this.isBodyVisible()) {
if (error) {
if (error instanceof ExtensionListViewWarning) {
this.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning);
this.messageBox.textContent = getErrorMessage(error);
toggleClass(this.bodyTemplate.extensionsList, 'hidden', count === 0);
toggleClass(this.bodyTemplate.messageContainer, 'hidden', count > 0);
this.badge.setCount(count);
if (count === 0 && this.isBodyVisible()) {
if (error) {
if (error instanceof ExtensionListViewWarning) {
this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Warning);
this.bodyTemplate.messageBox.textContent = getErrorMessage(error);
} else {
this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(Severity.Error);
this.bodyTemplate.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error));
}
} else {
this.messageSeverityIcon.className = SeverityIcon.className(Severity.Error);
this.messageBox.textContent = localize('error', "Error while loading extensions. {0}", getErrorMessage(error));
this.bodyTemplate.messageSeverityIcon.className = '';
this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No extensions found.");
}
} else {
this.messageSeverityIcon.className = '';
this.messageBox.textContent = localize('no extensions found', "No extensions found.");
alert(this.bodyTemplate.messageBox.textContent);
}
alert(this.messageBox.textContent);
}
}
}
@@ -850,7 +859,7 @@ export class ServerExtensionsView extends ExtensionsListView {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IProductService productService: IProductService,
@IContextKeyService contextKeyService: IContextKeyService,
@IContextKeyService contextKeyService: IContextKeyService
) {
options.server = server;
super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService);
@@ -864,6 +873,15 @@ export class ServerExtensionsView extends ExtensionsListView {
}
return super.show(query.trim());
}
getActions(): IAction[] {
if (this.extensionManagementServerService.remoteExtensionManagementServer && this.extensionManagementServerService.localExtensionManagementServer === this.server) {
const installLocalExtensionsInRemoteAction = this._register(this.instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, false));
installLocalExtensionsInRemoteAction.class = 'octicon octicon-cloud-download';
return [installLocalExtensionsInRemoteAction];
}
return [];
}
}
export class EnabledExtensionsView extends ExtensionsListView {
@@ -943,7 +961,7 @@ export class RecommendedExtensionsView extends ExtensionsListView {
export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
private readonly recommendedExtensionsQuery = '@recommended:workspace';
private installAllAction: InstallWorkspaceRecommendedExtensionsAction;
private installAllAction: InstallWorkspaceRecommendedExtensionsAction | undefined;
renderBody(container: HTMLElement): void {
super.renderBody(container);
@@ -953,25 +971,15 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
this._register(this.contextService.onDidChangeWorkbenchState(() => this.update()));
}
renderHeader(container: HTMLElement): void {
super.renderHeader(container);
getActions(): IAction[] {
if (!this.installAllAction) {
this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []));
this.installAllAction.class = 'octicon octicon-cloud-download';
}
const listActionBar = $('.list-actionbar-container');
container.insertBefore(listActionBar, this.badgeContainer);
const actionbar = this._register(new ActionBar(listActionBar, {
animated: false
}));
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
this.installAllAction = this._register(this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL, []));
const configureWorkspaceFolderAction = this._register(this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL));
this.installAllAction.class = 'octicon octicon-cloud-download';
configureWorkspaceFolderAction.class = 'octicon octicon-pencil';
actionbar.push([this.installAllAction], { icon: true, label: false });
actionbar.push([configureWorkspaceFolderAction], { icon: true, label: false });
return [this.installAllAction, configureWorkspaceFolderAction];
}
async show(query: string): Promise<IPagedModel<IExtension>> {
@@ -986,9 +994,11 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
this.setRecommendationsToInstall();
}
private setRecommendationsToInstall(): Promise<void> {
return this.getRecommendationsToInstall()
.then(recommendations => { this.installAllAction.recommendations = recommendations; });
private async setRecommendationsToInstall(): Promise<void> {
const recommendations = await this.getRecommendationsToInstall();
if (this.installAllAction) {
this.installAllAction.recommendations = recommendations;
}
}
private getRecommendationsToInstall(): Promise<IExtensionRecommendation[]> {
@@ -18,9 +18,9 @@ import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export abstract class ExtensionWidget extends Disposable implements IExtensionContainer {
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
private _extension: IExtension | null = null;
get extension(): IExtension | null { return this._extension; }
set extension(extension: IExtension | null) { this._extension = extension; this.update(); }
update(): void { this.render(); }
abstract render(): void;
}
@@ -183,7 +183,7 @@ export class RecommendationWidget extends ExtensionWidget {
private element?: HTMLElement;
private readonly disposables = this._register(new DisposableStore());
private _tooltip: string;
private _tooltip: string = '';
get tooltip(): string { return this._tooltip; }
set tooltip(tooltip: string) {
if (this._tooltip !== tooltip) {
@@ -314,4 +314,4 @@ class RemoteBadge extends Disposable {
updateTitle();
}
}
}
}
@@ -114,11 +114,11 @@ class Extension implements IExtension {
}
get url(): string | undefined {
if (!this.productService.extensionsGallery || !this.gallery) {
if (!this.productService.productConfiguration.extensionsGallery || !this.gallery) {
return undefined;
}
return `${this.productService.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`;
return `${this.productService.productConfiguration.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`;
}
get iconUrl(): string {
@@ -484,7 +484,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
private readonly _onChange: Emitter<IExtension | undefined> = new Emitter<IExtension | undefined>();
get onChange(): Event<IExtension | undefined> { return this._onChange.event; }
private _extensionAllowedBadgeProviders: string[];
private _extensionAllowedBadgeProviders: string[] | undefined;
private installing: IExtension[] = [];
constructor(
@@ -615,7 +615,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
text = text.replace(extensionRegex, (m, ext) => {
// Get curated keywords
const lookup = this.productService.extensionKeywords || {};
const lookup = this.productService.productConfiguration.extensionKeywords || {};
const keywords = lookup[ext] || [];
// Get mode name
@@ -1022,7 +1022,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
get allowedBadgeProviders(): string[] {
if (!this._extensionAllowedBadgeProviders) {
this._extensionAllowedBadgeProviders = (this.productService.extensionAllowedBadgeProviders || []).map(s => s.toLowerCase());
this._extensionAllowedBadgeProviders = (this.productService.productConfiguration.extensionAllowedBadgeProviders || []).map(s => s.toLowerCase());
}
return this._extensionAllowedBadgeProviders;
}
@@ -1096,12 +1096,12 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
private _ignoredAutoUpdateExtensions: string[];
private _ignoredAutoUpdateExtensions: string[] | undefined;
private get ignoredAutoUpdateExtensions(): string[] {
if (!this._ignoredAutoUpdateExtensions) {
this._ignoredAutoUpdateExtensions = JSON.parse(this.storageService.get('extensions.ignoredAutoUpdateExtension', StorageScope.GLOBAL, '[]') || '[]');
}
return this._ignoredAutoUpdateExtensions;
return this._ignoredAutoUpdateExtensions!;
}
private set ignoredAutoUpdateExtensions(extensionIds: string[]) {
@@ -6,8 +6,3 @@
.monaco-workbench .activitybar > .content .monaco-action-bar .action-label.extensions {
-webkit-mask: url('extensions-activity-bar.svg') no-repeat 50% 50%;
}
.extensions .split-view-view .panel-header .count-badge-wrapper {
position: absolute;
right: 12px;
}
@@ -27,13 +27,16 @@
height: calc(100% - 38px);
}
.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item > .octicon {
font-size: 12px;
line-height: 1;
margin-right: 10px;
.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar {
margin-right: 4px;
}
.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item.disabled {
.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item > .action-label.icon.octicon {
vertical-align: middle;
line-height: 22px;
}
.extensions-viewlet > .extensions .extension-view-header .monaco-action-bar .action-item.disabled {
display: none;
}
@@ -44,7 +47,7 @@
}
.extensions-viewlet > .extensions .panel-header {
padding-right: 28px;
padding-right: 6px;
}
.extensions-viewlet > .extensions .panel-header > .title {
@@ -22,7 +22,7 @@ export class RemoteExtensionsInstaller extends Disposable implements IWorkbenchC
) {
super();
if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) {
const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction);
const installLocalExtensionsInRemoteAction = instantiationService.createInstance(InstallLocalExtensionsInRemoteAction, true);
CommandsRegistry.registerCommand('workbench.extensions.installLocalExtensions', () => installLocalExtensionsInRemoteAction.run());
let disposable = Disposable.None;
const appendMenuItem = () => {
@@ -14,7 +14,6 @@ import { IExtensionTipsService, ExtensionRecommendationReason, IExtensionsConfig
import { IModelService } from 'vs/editor/common/services/modelService';
import { ITextModel } from 'vs/editor/common/model';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import product from 'vs/platform/product/node/product';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import Severity from 'vs/base/common/severity';
@@ -23,13 +22,11 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as pfs from 'vs/base/node/pfs';
import * as os from 'os';
import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { getHashedRemotesFromUri } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats';
import { IRequestService, asJson } from 'vs/platform/request/common/request';
import { isNumber } from 'vs/base/common/types';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -42,9 +39,9 @@ import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/wo
import { CancellationToken } from 'vs/base/common/cancellation';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { extname } from 'vs/base/common/resources';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IExeBasedExtensionTip } from 'vs/platform/product/common/product';
import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/product';
import { timeout } from 'vs/base/common/async';
import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats';
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again");
@@ -109,7 +106,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService,
@IExperimentService private readonly experimentService: IExperimentService,
@ITextFileService private readonly textFileService: ITextFileService
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService,
@IProductService private readonly productService: IProductService
) {
super();
@@ -117,8 +115,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return;
}
if (product.extensionsGallery && product.extensionsGallery.recommendationsUrl) {
this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl;
if (this.productService.productConfiguration.extensionsGallery && this.productService.productConfiguration.extensionsGallery.recommendationsUrl) {
this._extensionsRecommendationsUrl = this.productService.productConfiguration.extensionsGallery.recommendationsUrl;
}
this.sessionSeed = +new Date();
@@ -244,7 +242,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
getKeymapRecommendations(): IExtensionRecommendation[] {
return (product.keymapExtensionTips || [])
return (this.productService.productConfiguration.keymapExtensionTips || [])
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId))
.map(extensionId => (<IExtensionRecommendation>{ extensionId, sources: ['application'] }));
}
@@ -601,10 +599,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return Object.keys(this._fileBasedRecommendations)
.sort((a, b) => {
if (this._fileBasedRecommendations[a].recommendedTime === this._fileBasedRecommendations[b].recommendedTime) {
if (!product.extensionImportantTips || caseInsensitiveGet(product.extensionImportantTips, a)) {
if (!this.productService.productConfiguration.extensionImportantTips || caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, a)) {
return -1;
}
if (caseInsensitiveGet(product.extensionImportantTips, b)) {
if (caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, b)) {
return 1;
}
}
@@ -615,11 +613,11 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
/**
* Parse all file based recommendations from product.extensionTips
* Retire existing recommendations if they are older than a week or are not part of product.extensionTips anymore
* Parse all file based recommendations from this.productService.productConfiguration.extensionTips
* Retire existing recommendations if they are older than a week or are not part of this.productService.productConfiguration.extensionTips anymore
*/
private fetchFileBasedRecommendations() {
const extensionTips = product.extensionTips;
const extensionTips = this.productService.productConfiguration.extensionTips;
if (!extensionTips) {
return;
}
@@ -636,7 +634,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
});
forEach(product.extensionImportantTips, entry => {
forEach(this.productService.productConfiguration.extensionImportantTips, entry => {
let { key: id, value } = entry;
const { pattern } = value;
let ids = this._availableRecommendations[pattern];
@@ -698,7 +696,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
let { key: pattern, value: ids } = entry;
if (match(pattern, model.uri.toString())) {
for (let id of ids) {
if (caseInsensitiveGet(product.extensionImportantTips, id)) {
if (caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, id)) {
recommendationsToSuggest.push(id);
}
const filedBasedRecommendation = this._fileBasedRecommendations[id.toLowerCase()] || { recommendedTime: now, sources: [] };
@@ -752,7 +750,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
const id = recommendationsToSuggest[0];
const entry = caseInsensitiveGet(product.extensionImportantTips, id);
const entry = caseInsensitiveGet(this.productService.productConfiguration.extensionImportantTips, id);
if (!entry) {
return false;
}
@@ -982,14 +980,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
/**
* If user has any of the tools listed in product.exeBasedExtensionTips, fetch corresponding recommendations
* If user has any of the tools listed in this.productService.productConfiguration.exeBasedExtensionTips, fetch corresponding recommendations
*/
private fetchExecutableRecommendations(important: boolean): Promise<void> {
const homeDir = os.homedir();
let foundExecutables: Set<string> = new Set<string>();
let findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => {
return pfs.fileExists(path).then(exists => {
return this.fileService.exists(URI.file(path)).then(exists => {
if (exists && !foundExecutables.has(exeName)) {
foundExecutables.add(exeName);
(tip['recommendations'] || []).forEach(extensionId => {
@@ -1006,7 +1004,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
let promises: Promise<void>[] = [];
// Loop through recommended extensions
forEach(product.exeBasedExtensionTips, entry => {
forEach(this.productService.productConfiguration.exeBasedExtensionTips, entry => {
if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) {
return;
}
@@ -1078,7 +1076,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations';
const workspaceUri = this.contextService.getWorkspace().folders[0].uri;
return Promise.all([getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => {
return Promise.all([this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, false), this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true)]).then(([hashedRemotes1, hashedRemotes2]) => {
const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []);
if (!hashedRemotes.length) {
return undefined;
@@ -35,8 +35,7 @@ import { URLService } from 'vs/platform/url/common/urlService';
import { URI } from 'vs/base/common/uri';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { SinonStub } from 'sinon';
import { IExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService';
import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService';
import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -73,8 +73,8 @@ export class FeedbackDropdown extends Dropdown {
this.feedbackDelegate = options.feedbackService;
this.maxFeedbackCharacters = this.feedbackDelegate.getCharacterLimit(this.sentiment);
if (productService.sendASmile) {
this.requestFeatureLink = productService.sendASmile.requestFeatureUrl;
if (productService.productConfiguration.sendASmile) {
this.requestFeatureLink = productService.productConfiguration.sendASmile.requestFeatureUrl;
}
this.integrityService.isPure().then(result => {
@@ -58,7 +58,7 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben
) {
super();
if (productService.sendASmile) {
if (productService.productConfiguration.sendASmile) {
this.entry = this._register(statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */));
CommandsRegistry.registerCommand('_feedback.open', () => this.toggleFeedback());
@@ -7,16 +7,15 @@ import * as nls from 'vs/nls';
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { URI } from 'vs/base/common/uri';
import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
/**
* An implementation of editor for binary files like images.
@@ -28,7 +27,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IWindowsService private readonly windowsService: IWindowsService,
@IOpenerService private readonly openerService: IOpenerService,
@IEditorService private readonly editorService: IEditorService,
@IStorageService storageService: IStorageService,
@IFileService fileService: IFileService,
@@ -39,7 +38,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
BinaryFileEditor.ID,
{
openInternal: (input, options) => this.openInternal(input, options),
openExternal: resource => this.openExternal(resource)
openExternal: resource => this.openerService.openExternal(resource)
},
telemetryService,
themeService,
@@ -58,13 +57,6 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
}
}
private async openExternal(resource: URI): Promise<void> {
const didOpen = await this.windowsService.openExternal(resource.toString());
if (!didOpen) {
return this.windowsService.showItemInFolder(resource);
}
}
getTitle(): string | null {
return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer");
}
@@ -158,7 +158,11 @@ export class ExplorerService implements IExplorerService {
// Stat needs to be resolved first and then revealed
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' };
const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource;
if (workspaceFolder === null) {
return Promise.resolve(undefined);
}
const rootUri = workspaceFolder.uri;
const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!;
try {
@@ -534,8 +534,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
const span1 = dom.append(container, dom.$('span'));
span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS;
const link = dom.append(container, dom.$('a.messageAction'));
link.textContent = localize('clearFilter', "Clear Filter.");
link.textContent = localize('clearFilter', "Clear Filter");
link.setAttribute('tabIndex', '0');
const span2 = dom.append(container, dom.$('span'));
span2.textContent = '.';
dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = '');
dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => {
if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) {
@@ -73,7 +73,7 @@ export class PreferencesSearchService extends Disposable implements IPreferences
};
} else {
return {
urlBase: this.productService.settingsSearchUrl
urlBase: this.productService.productConfiguration.settingsSearchUrl
};
}
}
@@ -364,7 +364,7 @@ class RemoteSearchProvider implements ISearchProvider {
const extensions = await this.installedExtensions;
const filters = this.options.newExtensionsOnly ?
[`diminish eq 'latest'`] :
this.getVersionFilters(extensions, this.productService.settingsSearchBuildId);
this.getVersionFilters(extensions, this.productService.productConfiguration.settingsSearchBuildId);
const filterStr = filters
.slice(filterPage * RemoteSearchProvider.MAX_REQUEST_FILTERS, (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS)
@@ -563,4 +563,4 @@ export class SettingMatches {
endColumn: setting.valueRange.startColumn + match.end + 1
};
}
}
}
@@ -36,7 +36,7 @@ import { ISettingsGroup } from 'vs/workbench/services/preferences/common/prefere
export class SettingsHeaderWidget extends Widget implements IViewZone {
private id: number;
private id: string;
private _domNode: HTMLElement;
protected titleContainer: HTMLElement;
@@ -121,7 +121,7 @@ export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget {
export class SettingsGroupTitleWidget extends Widget implements IViewZone {
private id: number;
private id: string;
private _afterLineNumber: number;
private _domNode: HTMLElement;
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
export type Tags = { [index: string]: boolean | number | string | undefined };
export const IWorkspaceStatsService = createDecorator<IWorkspaceStatsService>('workspaceStatsService');
export interface IWorkspaceStatsService {
_serviceBrand: any;
getTags(): Promise<Tags>;
/**
* Returns an id for the workspace, different from the id returned by the context service. A hash based
* on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces.
*/
getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined;
getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise<string[]>;
}
@@ -14,7 +14,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { endsWith } from 'vs/base/common/strings';
import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService';
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats';
import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnosticsService';
const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/;
@@ -136,20 +136,6 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool
});
}
export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, textFileService: ITextFileService, stripEndingDotGit: boolean = false): Promise<string[]> {
const path = workspaceUri.path;
const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` });
return fileService.exists(uri).then(exists => {
if (!exists) {
return [];
}
return textFileService.read(uri, { acceptTextOnly: true }).then(
content => getHashedRemotesFromConfig(content.value, stripEndingDotGit),
err => [] // ignore missing or binary file
);
});
}
export class WorkspaceStats implements IWorkbenchContribution {
constructor(
@@ -230,7 +216,7 @@ export class WorkspaceStats implements IWorkbenchContribution {
private reportRemotes(workspaceUris: URI[]): void {
Promise.all<string[]>(workspaceUris.map(workspaceUri => {
return getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true);
return this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true);
})).then(hashedRemotes => {
/* __GDPR__
"workspace.hashedRemotes" : {
@@ -18,10 +18,9 @@ import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspa
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { joinPath } from 'vs/base/common/resources';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export type Tags = { [index: string]: boolean | number | string | undefined };
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats';
import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats';
const DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen';
@@ -93,20 +92,6 @@ const PyModulesToLookFor = [
'botframework-connector'
];
export const IWorkspaceStatsService = createDecorator<IWorkspaceStatsService>('workspaceStatsService');
export interface IWorkspaceStatsService {
_serviceBrand: any;
getTags(): Promise<Tags>;
/**
* Returns an id for the workspace, different from the id returned by the context service. A hash based
* on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces.
*/
getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined;
}
export class WorkspaceStatsService implements IWorkspaceStatsService {
_serviceBrand: any;
private _tags: Tags;
@@ -152,6 +137,20 @@ export class WorkspaceStatsService implements IWorkspaceStatsService {
return workspaceId;
}
getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit: boolean = false): Promise<string[]> {
const path = workspaceUri.path;
const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` });
return this.fileService.exists(uri).then(exists => {
if (!exists) {
return [];
}
return this.textFileService.read(uri, { acceptTextOnly: true }).then(
content => getHashedRemotesFromConfig(content.value, stripEndingDotGit),
err => [] // ignore missing or binary file
);
});
}
/* __GDPR__FRAGMENT__
"WorkspaceTags" : {
"workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
@@ -20,7 +20,7 @@ import * as panel from 'vs/workbench/browser/panel';
import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { AllowWorkspaceShellTerminalCommand, ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, DisallowWorkspaceShellTerminalCommand, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen';
import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, ITerminalService, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -387,8 +387,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct
mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K }
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowWorkspaceShellTerminalCommand, AllowWorkspaceShellTerminalCommand.ID, AllowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Allow Workspace Shell Configuration', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand.ID, DisallowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Disallow Workspace Shell Configuration', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_F
@@ -993,10 +993,10 @@ export class ClearSelectionTerminalAction extends Action {
}
}
export class AllowWorkspaceShellTerminalCommand extends Action {
export class ManageWorkspaceShellPermissionsTerminalCommand extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.WORKSPACE_SHELL_ALLOW;
public static readonly LABEL = nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration");
public static readonly ID = TERMINAL_COMMAND_ID.MANAGE_WORKSPACE_SHELL_PERMISSIONS;
public static readonly LABEL = nls.localize('workbench.action.terminal.manageWorkspaceShellPermissions', "Manage Workspace Shell Permissions");
constructor(
id: string, label: string,
@@ -1005,27 +1005,8 @@ export class AllowWorkspaceShellTerminalCommand extends Action {
super(id, label);
}
public run(event?: any): Promise<any> {
this.terminalService.setWorkspaceShellAllowed(true);
return Promise.resolve(undefined);
}
}
export class DisallowWorkspaceShellTerminalCommand extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.WORKSPACE_SHELL_DISALLOW;
public static readonly LABEL = nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration");
constructor(
id: string, label: string,
@ITerminalService private readonly terminalService: ITerminalService
) {
super(id, label);
}
public run(event?: any): Promise<any> {
this.terminalService.setWorkspaceShellAllowed(false);
return Promise.resolve(undefined);
public async run(event?: any): Promise<any> {
await this.terminalService.manageWorkspaceShellPermissions();
}
}
@@ -226,7 +226,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
this._configHelper.showRecommendations(shellLaunchConfig);
const baseEnv = this._configHelper.config.inheritEnv ? process.env as platform.IProcessEnvironment : await this._terminalInstanceService.getMainProcessParentEnv();
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables, baseEnv);
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.productConfiguration.version, this._configHelper.config.setLocaleVariables, baseEnv);
const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled;
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty);
@@ -279,7 +279,7 @@ export interface ITerminalService {
selectDefaultWindowsShell(): Promise<void>;
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
setWorkspaceShellAllowed(isAllowed: boolean): void;
manageWorkspaceShellPermissions(): void;
/**
* Takes a path and returns the properly escaped path to send to the terminal.
@@ -48,8 +48,7 @@ export const enum TERMINAL_COMMAND_ID {
SCROLL_TO_TOP = 'workbench.action.terminal.scrollToTop',
CLEAR = 'workbench.action.terminal.clear',
CLEAR_SELECTION = 'workbench.action.terminal.clearSelection',
WORKSPACE_SHELL_ALLOW = 'workbench.action.terminal.allowWorkspaceShell',
WORKSPACE_SHELL_DISALLOW = 'workbench.action.terminal.disallowWorkspaceShell',
MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions',
RENAME = 'workbench.action.terminal.rename',
FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget',
FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget',
@@ -479,22 +479,28 @@ export abstract class TerminalService implements ITerminalService {
return terminalIndex;
}
public setWorkspaceShellAllowed(isAllowed: boolean): void {
this.configHelper.setWorkspaceShellAllowed(isAllowed);
public async manageWorkspaceShellPermissions(): Promise<void> {
const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration") };
const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration") };
const value = await this._quickInputService.pick([allowItem, disallowItem], { canPickMany: false });
if (!value) {
return;
}
this.configHelper.setWorkspaceShellAllowed(value === allowItem);
}
protected _showTerminalCloseConfirmation(): Promise<boolean> {
let message;
protected async _showTerminalCloseConfirmation(): Promise<boolean> {
let message: string;
if (this.terminalInstances.length === 1) {
message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?");
} else {
message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length);
}
return this._dialogService.confirm({
const res = await this._dialogService.confirm({
message,
type: 'warning',
}).then(res => !res.confirmed);
});
return !res.confirmed;
}
protected _showNotEnoughSpaceToast(): void {
@@ -47,16 +47,16 @@ export class WebviewPortMappingManager extends Disposable {
if (this.extensionLocation && this.extensionLocation.scheme === REMOTE_HOST_SCHEME) {
const tunnel = await this.getOrCreateTunnel(mapping.extensionHostPort);
if (tunnel) {
return uri.with({
return encodeURI(uri.with({
authority: `127.0.0.1:${tunnel.tunnelLocalPort}`,
}).toString();
}).toString(true));
}
}
if (mapping.webviewPort !== mapping.extensionHostPort) {
return uri.with({
return encodeURI(uri.with({
authority: `${requestLocalHostInfo.address}:${mapping.extensionHostPort}`
}).toString();
}).toString(true));
}
}
}
@@ -84,4 +84,4 @@ export class WebviewPortMappingManager extends Disposable {
}
return tunnel;
}
}
}
@@ -20,6 +20,8 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { WebWorkerExtensionHostStarter } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter';
import { URI } from 'vs/base/common/uri';
import { isWebExtension } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class ExtensionService extends AbstractExtensionService implements IExtensionService {
@@ -34,6 +36,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IFileService fileService: IFileService,
@IProductService productService: IProductService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IConfigurationService private readonly _configService: IConfigurationService,
) {
super(
instantiationService,
@@ -65,11 +68,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten
const remoteAgentConnection = this._remoteAgentService.getConnection()!;
const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, Promise.resolve([]), URI.parse('empty:value')); //todo@joh
const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => isWebExtension(ext, this._configService)));
const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !isWebExtension(ext, this._configService)));
const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.parse('empty:value')); //todo@joh
const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
result.push(webHostProcessManager);
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, remoteExtensions, this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory);
const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents);
result.push(remoteExtHostProcessManager);
@@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory';
import { getWorkerBootstrapUrl } from 'vs/base/worker/defaultWorkerFactory';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import { VSBuffer } from 'vs/base/common/buffer';
import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol';
@@ -49,23 +49,29 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter {
if (!this._protocol) {
const emitter = new Emitter<VSBuffer>();
const worker = new DefaultWorkerFactory('WorkerExtensionHost').create(
'vs/workbench/services/extensions/worker/extensionHostWorker', data => {
if (data instanceof ArrayBuffer) {
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
} else {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
}
}, err => {
this._onDidExit.fire([81, err]);
console.error(err);
const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost');
const worker = new Worker(url);
worker.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
return;
}
);
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
};
worker.onerror = (event) => {
console.error(event.error);
this._onDidExit.fire([81, event.error]);
};
// keep for cleanup
this._toDispose.add(emitter);
this._toDispose.add(worker);
this._toDispose.add(toDisposable(() => worker.terminate()));
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
@@ -111,15 +117,15 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter {
const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]);
const workspace = this._contextService.getWorkspace();
return {
commit: this._productService.commit,
version: this._productService.version,
commit: this._productService.productConfiguration.commit,
version: this._productService.productConfiguration.version,
parentPid: -1,
environment: {
isExtensionDevelopmentDebug: false,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined,
appName: this._productService.nameLong,
appUriScheme: this._productService.urlProtocol,
appName: this._productService.productConfiguration.nameLong,
appUriScheme: this._productService.productConfiguration.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
@@ -462,12 +462,12 @@ class ProposedApiController {
}
this.enableProposedApiForAll = !environmentService.isBuilt ||
(!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') ||
(!!environmentService.extensionDevelopmentLocationURI && productService.productConfiguration.nameLong !== 'Visual Studio Code') ||
(this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args);
this.productAllowProposedApi = new Set<string>();
if (isNonEmptyArray(productService.extensionAllowedProposedApi)) {
productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id)));
if (isNonEmptyArray(productService.productConfiguration.extensionAllowedProposedApi)) {
productService.productConfiguration.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id)));
}
}
@@ -10,6 +10,11 @@ import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionM
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IProductService } from 'vs/platform/product/common/product';
export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
const extensionKind = getExtensionKind(manifest, configurationService);
return extensionKind === 'web';
}
export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean {
const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name);
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
@@ -19,7 +24,7 @@ export function isUIExtension(manifest: IExtensionManifest, productService: IPro
case 'workspace': return false;
default: {
// Tagged as UI extension in product
if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
if (isNonEmptyArray(productService.productConfiguration.uiExtensions) && productService.productConfiguration.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) {
return true;
}
// Not an UI extension if it has main
@@ -71,7 +71,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
public start(): Promise<IMessagePassingProtocol> {
const options: IConnectionOptions = {
commit: this._productService.commit,
commit: this._productService.productConfiguration.commit,
socketFactory: this._socketFactory,
addressProvider: {
getAddress: async () => {
@@ -181,15 +181,15 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH
const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier);
const workspace = this._contextService.getWorkspace();
const r: IInitData = {
commit: this._productService.commit,
version: this._productService.version,
commit: this._productService.productConfiguration.commit,
version: this._productService.productConfiguration.version,
parentPid: remoteExtensionHostData.pid,
environment: {
isExtensionDevelopmentDebug,
appRoot: remoteExtensionHostData.appRoot,
appSettingsHome: remoteExtensionHostData.appSettingsHome,
appName: this._productService.nameLong,
appUriScheme: this._productService.urlProtocol,
appName: this._productService.productConfiguration.nameLong,
appUriScheme: this._productService.productConfiguration.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
@@ -69,7 +69,7 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC
const installed = await this.getInstalled(ExtensionType.User);
const compatible = await this.galleryService.getCompatibleExtension(extension);
if (!compatible) {
return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version)));
return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.productConfiguration.version)));
}
const manifest = await this.galleryService.getManifest(compatible, CancellationToken.None);
if (manifest) {
@@ -140,4 +140,4 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC
}
return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token);
}
}
}

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