mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-10 08:45:56 +01:00
Merge remote-tracking branch 'origin/master' into tyriar/puppeteer
This commit is contained in:
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
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;
|
||||
}
|
||||
@@ -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.$;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+3
-35
@@ -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: `` };
|
||||
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: `` };
|
||||
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: `` });
|
||||
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: `` });
|
||||
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: `` });
|
||||
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: `` };
|
||||
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: `` };
|
||||
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: `` });
|
||||
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: `` });
|
||||
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: `` });
|
||||
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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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';
|
||||
|
||||
Vendored
+3
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -84,11 +84,6 @@ class CodeRendererMain extends Disposable {
|
||||
}));
|
||||
this._register(workbench.onShutdown(() => this.dispose()));
|
||||
|
||||
// Driver
|
||||
if (this.configuration.driver) {
|
||||
registerWindowDriver();
|
||||
}
|
||||
|
||||
// Startup
|
||||
workbench.startup();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -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';
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+2
-2
@@ -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
Reference in New Issue
Block a user