Merge from vscode b12f623603e2fc1c5b3037115fa37c1a6acc4165 (#6760)

This commit is contained in:
Anthony Dresser
2019-08-15 02:19:31 -07:00
committed by GitHub
parent 4966ed8b42
commit 58bfba4b47
161 changed files with 2072 additions and 1317 deletions

View File

@@ -53,6 +53,7 @@ const buildfile = require('../src/buildfile');
const vscodeWebEntryPoints = [
buildfile.workbenchWeb,
buildfile.serviceWorker,
buildfile.workerExtensionHost,
buildfile.keyboardMaps,
buildfile.base
];
@@ -148,4 +149,4 @@ const dashed = (str) => (str ? `-${str}` : ``);
vscodeWebTaskCI
));
gulp.task(vscodeWebTask);
});
});

View File

@@ -20,8 +20,8 @@ Compression=lzma
SolidCompression=yes
AppMutex={code:GetAppMutex}
SetupMutex={#AppMutex}setup
WizardImageFile={#RepoDir}\resources\win32\inno-big.bmp
WizardSmallImageFile={#RepoDir}\resources\win32\inno-small.bmp
WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp"
WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp"
SetupIconFile={#RepoDir}\resources\win32\code.ico
UninstallDisplayIcon={app}\{#ExeBasename}.exe
ChangesEnvironment=true

View File

@@ -4,7 +4,7 @@
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/a595d8ba2ae9ce8864435d33db2afa0fe68b1487",
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/05ccfa3db6edbd357390431f9e316adb38ba41d8",
"name": "Markdown",
"scopeName": "text.html.markdown",
"patterns": [
@@ -716,7 +716,7 @@
{
"begin": "(^|\\G)(\\s*)(.*)",
"while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)",
"contentName": "meta.embedded.block.cpp source.cpp",
"contentName": "meta.embedded.block.cpp",
"patterns": [
{
"include": "source.cpp"
@@ -1987,8 +1987,29 @@
"name": "comment.block.html"
},
{
"begin": "(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?</(script|style|pre)>))",
"end": "(?=.*</(script|style|pre)>)",
"begin": "(?i)(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?</(script|style|pre)>))",
"end": "(?i)(.*)((</)(script|style|pre)(>))",
"endCaptures": {
"1": {
"patterns": [
{
"include": "text.html.basic"
}
]
},
"2": {
"name": "meta.tag.structure.$4.end.html"
},
"3": {
"name": "punctuation.definition.tag.begin.html"
},
"4": {
"name": "entity.name.tag.html"
},
"5": {
"name": "punctuation.definition.tag.end.html"
}
},
"patterns": [
{
"begin": "(\\s*|$)",
@@ -1997,12 +2018,12 @@
"include": "text.html.basic"
}
],
"while": "^(?!.*</(script|style|pre)>)"
"while": "(?i)^(?!.*</(script|style|pre)>)"
}
]
},
{
"begin": "(^|\\G)\\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\\s|$|/?>))",
"begin": "(?i)(^|\\G)\\s*(?=</?(address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(\\s|$|/?>))",
"patterns": [
{
"include": "text.html.basic"
@@ -2275,7 +2296,7 @@
},
"bracket": {
"comment": "Markdown will convert this for us. We match it so that the HTML grammar will not mark it up as invalid.",
"match": "<(?![a-z/?\\$!])",
"match": "<(?![a-zA-Z/?\\$!])",
"name": "meta.other.valid-bracket.markdown"
},
"escape": {
@@ -2566,4 +2587,4 @@
"name": "markup.inline.raw.string.markdown"
}
}
}
}

View File

@@ -111,7 +111,7 @@
},
{
"c": "</",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.inline.code.end.html punctuation.definition.tag.begin.html",
"t": "text.html.markdown meta.tag.inline.code.end.html punctuation.definition.tag.begin.html",
"r": {
"dark_plus": "punctuation.definition.tag: #808080",
"light_plus": "punctuation.definition.tag: #800000",
@@ -122,7 +122,7 @@
},
{
"c": "code",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.inline.code.end.html entity.name.tag.html",
"t": "text.html.markdown meta.tag.inline.code.end.html entity.name.tag.html",
"r": {
"dark_plus": "entity.name.tag: #569CD6",
"light_plus": "entity.name.tag: #800000",
@@ -133,7 +133,7 @@
},
{
"c": ">",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.inline.code.end.html punctuation.definition.tag.end.html",
"t": "text.html.markdown meta.tag.inline.code.end.html punctuation.definition.tag.end.html",
"r": {
"dark_plus": "punctuation.definition.tag: #808080",
"light_plus": "punctuation.definition.tag: #800000",
@@ -144,7 +144,7 @@
},
{
"c": "</",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.begin.html",
"t": "text.html.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.begin.html",
"r": {
"dark_plus": "punctuation.definition.tag: #808080",
"light_plus": "punctuation.definition.tag: #800000",
@@ -155,7 +155,7 @@
},
{
"c": "pre",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html entity.name.tag.html",
"t": "text.html.markdown meta.tag.structure.pre.end.html entity.name.tag.html",
"r": {
"dark_plus": "entity.name.tag: #569CD6",
"light_plus": "entity.name.tag: #800000",
@@ -166,7 +166,7 @@
},
{
"c": ">",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html",
"t": "text.html.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html",
"r": {
"dark_plus": "punctuation.definition.tag: #808080",
"light_plus": "punctuation.definition.tag: #800000",
@@ -254,7 +254,7 @@
},
{
"c": "a",
"t": "text.html.markdown meta.paragraph.markdown",
"t": "text.html.markdown",
"r": {
"dark_plus": "default: #D4D4D4",
"light_plus": "default: #000000",
@@ -265,7 +265,7 @@
},
{
"c": "</",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.begin.html",
"t": "text.html.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.begin.html",
"r": {
"dark_plus": "punctuation.definition.tag: #808080",
"light_plus": "punctuation.definition.tag: #800000",
@@ -276,7 +276,7 @@
},
{
"c": "pre",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html entity.name.tag.html",
"t": "text.html.markdown meta.tag.structure.pre.end.html entity.name.tag.html",
"r": {
"dark_plus": "entity.name.tag: #569CD6",
"light_plus": "entity.name.tag: #800000",
@@ -287,7 +287,7 @@
},
{
"c": ">",
"t": "text.html.markdown meta.paragraph.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html",
"t": "text.html.markdown meta.tag.structure.pre.end.html punctuation.definition.tag.end.html",
"r": {
"dark_plus": "punctuation.definition.tag: #808080",
"light_plus": "punctuation.definition.tag: #800000",

View File

@@ -131,7 +131,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",
@@ -168,7 +168,7 @@
"typemoq": "^0.3.2",
"typescript": "3.5.2",
"typescript-formatter": "7.1.0",
"uglify-es": "^3.0.18",
"uglify-es": "^3.3.9",
"vinyl": "^2.0.0",
"vinyl-fs": "^3.0.0",
"vsce": "1.48.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

BIN
resources/win32/inno-big-150.bmp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

BIN
resources/win32/inno-big-175.bmp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

BIN
resources/win32/inno-big-200.bmp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

BIN
resources/win32/inno-big-225.bmp Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. 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');

View File

@@ -10,8 +10,8 @@ import { IMessage, MessageType, defaultOpts } from 'vs/base/browser/ui/inputbox/
import * as dom from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer';
import { Emitter } from 'vs/base/common/event';
import { renderFormattedText, renderText, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
const $ = dom.$;
@@ -212,7 +212,7 @@ export class ListBox extends SelectBox {
div = dom.append(container, $('.monaco-inputbox-container'));
layout();
const renderOptions: RenderOptions = {
const renderOptions: FormattedTextRenderOptions = {
inline: true,
className: 'monaco-inputbox-message'
};

View File

@@ -9,10 +9,10 @@ import { SelectBox as vsSelectBox, ISelectBoxStyles as vsISelectBoxStyles, ISele
import { Color } from 'vs/base/common/color';
import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import * as dom from 'vs/base/browser/dom';
import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer';
import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import * as aria from 'vs/base/browser/ui/aria/aria';
import * as nls from 'vs/nls';
import { renderFormattedText, renderText, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
const $ = dom.$;
@@ -209,7 +209,7 @@ export class SelectBox extends vsSelectBox {
div = dom.append(container, $('.monaco-inputbox-container'));
layout();
const renderOptions: RenderOptions = {
const renderOptions: FormattedTextRenderOptions = {
inline: true,
className: 'monaco-inputbox-message'
};

View File

@@ -34,7 +34,7 @@ import { timeout } from 'vs/base/common/async';
import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { isString } from 'vs/base/common/types';
import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer';
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
@@ -934,7 +934,7 @@ class MarkdownRenderer {
) {
}
private getOptions(disposeables: DisposableStore): RenderOptions {
private getOptions(disposeables: DisposableStore): MarkdownRenderOptions {
return {
actionHandler: {
callback: (content) => {

View File

@@ -7,12 +7,12 @@ import * as fs from 'fs';
import { URI } from 'vs/base/common/uri';
import { RenderOptions } from 'vs/base/browser/htmlContentRenderer';
import { IMarkdownString, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
import * as marked from 'vs/base/common/marked/marked';
import { defaultGenerator } from 'vs/base/common/idGenerator';
import { revive } from 'vs/base/common/marshalling';
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
// Based off of HtmlContentRenderer
export class NotebookMarkdownRenderer {
@@ -30,7 +30,7 @@ export class NotebookMarkdownRenderer {
};
}
createElement(options: RenderOptions): HTMLElement {
createElement(options: MarkdownRenderOptions): HTMLElement {
const tagName = options.inline ? 'span' : 'div';
const element = document.createElement(tagName);
if (options.className) {
@@ -52,7 +52,7 @@ export class NotebookMarkdownRenderer {
* respects the trusted state of a notebook, and allows command links to
* be clickable.
*/
renderMarkdown(markdown: IMarkdownString, options: RenderOptions = {}): HTMLElement {
renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement {
const element = this.createElement(options);
// signal to code-block render that the element has been created

View File

@@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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;
}
}

View File

@@ -4,55 +4,25 @@
*--------------------------------------------------------------------------------------------*/
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 { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
import { onUnexpectedError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
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 IContentActionHandler {
callback: (content: string, event?: IMouseEvent) => void;
readonly disposeables: DisposableStore;
}
export interface RenderOptions {
className?: string;
inline?: boolean;
actionHandler?: IContentActionHandler;
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
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 {
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement {
const element = createElement(options);
const _uriMassage = function (part: string): string {
@@ -219,190 +189,3 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions
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;
}
}

View File

@@ -8,7 +8,8 @@ import 'vs/css!./inputBox';
import * as nls from 'vs/nls';
import * as Bal from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
import { RenderOptions, renderFormattedText, renderText } from 'vs/base/browser/htmlContentRenderer';
import { MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
import { renderFormattedText, renderText } from 'vs/base/browser/formattedTextRenderer';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { IAction } from 'vs/base/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
@@ -472,7 +473,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'
};

View File

@@ -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';
// {{SQL CARBON EDIT}} import color
import { Color } from 'vs/base/common/color';

View File

@@ -729,3 +729,18 @@ export function getNLines(str: string, n = 1): string {
str.substr(0, idx) :
str;
}
/**
* Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
*/
export function singleLetterHash(n: number): string {
const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
n = n % (2 * LETTERS_CNT);
if (n < LETTERS_CNT) {
return String.fromCharCode(CharCode.a + n);
}
return String.fromCharCode(CharCode.A + n - LETTERS_CNT);
}

View File

@@ -2,12 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown, renderText, renderFormattedText } from 'vs/base/browser/htmlContentRenderer';
import { renderText, renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { DisposableStore } from 'vs/base/common/lifecycle';
suite('HtmlContent', () => {
suite('FormattedTextRenderer', () => {
const store = new DisposableStore();
setup(() => {
@@ -101,36 +101,4 @@ suite('HtmlContent', () => {
assert.strictEqual(result.children.length, 0);
assert.strictEqual(result.innerHTML, '**bold**');
});
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as marked from 'vs/base/common/marked/marked';
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
suite('MarkdownRenderer', () => {
test('image rendering conforms to default', () => {
const markdown = { value: `![image](someimageurl 'caption')` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image rendering conforms to default without title', () => {
const markdown = { value: `![image](someimageurl)` };
const result: HTMLElement = renderMarkdown(markdown);
const renderer = new marked.Renderer();
const imageFromMarked = marked(markdown.value, {
sanitize: true,
renderer
}).trim();
assert.strictEqual(result.innerHTML, imageFromMarked);
});
test('image width from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
});
test('image height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
});
test('image width and height from title params', () => {
let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` });
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
});
});

View File

@@ -19,26 +19,31 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
// ESM-comment-begin
if (typeof require === 'function') {
// check if the JS lives on a different origin
const workerMain = require.toUrl('./' + workerId);
if (/^(http:)|(https:)|(file:)/.test(workerMain)) {
const currentUrl = String(window.location);
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
if (workerMain.substring(0, currentOrigin.length) !== currentOrigin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const workerBaseUrl = workerMain.substr(0, workerMain.length - 'vs/base/worker/workerMain.js'.length);
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${workerMain}');/*${label}*/`;
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
return new Worker(url);
}
}
return new Worker(workerMain + '#' + label);
const workerUrl = getWorkerBootstrapUrl(workerMain, label);
return new Worker(workerUrl, { name: label });
}
// ESM-comment-end
throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`);
}
export function getWorkerBootstrapUrl(scriptPath: string, label: string): string {
if (/^(http:)|(https:)|(file:)/.test(scriptPath)) {
const currentUrl = String(window.location);
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
if (scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
// this is the cross-origin case
// i.e. the webpage is running at a different origin than where the scripts are loaded from
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length);
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
return url;
}
}
return scriptPath + '#' + label;
}
function isPromiseLike<T>(obj: any): obj is PromiseLike<T> {
if (typeof obj.then === 'function') {
return true;

View File

@@ -7,7 +7,7 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, p
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { getShellEnvironment } from 'vs/code/node/shellEnv';

View File

@@ -49,7 +49,7 @@ export interface IPointerHandlerHelper {
*/
getLastViewCursorsRenderData(): IViewCursorRenderData[];
shouldSuppressMouseDownOnViewZone(viewZoneId: number): boolean;
shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean;
shouldSuppressMouseDownOnWidget(widgetId: string): boolean;
/**

View File

@@ -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;

View File

@@ -83,17 +83,17 @@ export interface IViewZoneChangeAccessor {
* @param zone Zone to create
* @return A unique identifier to the view zone.
*/
addZone(zone: IViewZone): number;
addZone(zone: IViewZone): string;
/**
* Remove a zone
* @param id A unique identifier to the view zone, as returned by the `addZone` call.
*/
removeZone(id: number): void;
removeZone(id: string): void;
/**
* Change a zone's position.
* The editor will rescan the `afterLineNumber` and `afterColumn` properties of a view zone.
*/
layoutZone(id: number): void;
layoutZone(id: string): void;
}
/**
@@ -399,7 +399,7 @@ export interface ICodeEditor extends editorCommon.IEditor {
*/
onWillType(listener: (text: string) => void): IDisposable;
/**
* An event emitted before interpreting typed characters (on the keyboard).
* An event emitted after interpreting typed characters (on the keyboard).
* @event
* @internal
*/

View File

@@ -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);
}
}

View File

@@ -248,7 +248,7 @@ export class View extends ViewEventHandler {
getLastViewCursorsRenderData: () => {
return this.viewCursors.getLastRenderData() || [];
},
shouldSuppressMouseDownOnViewZone: (viewZoneId: number) => {
shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => {
return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
},
shouldSuppressMouseDownOnWidget: (widgetId: string) => {
@@ -473,17 +473,17 @@ export class View extends ViewEventHandler {
this._renderOnce(() => {
const changeAccessor: editorBrowser.IViewZoneChangeAccessor = {
addZone: (zone: editorBrowser.IViewZone): number => {
addZone: (zone: editorBrowser.IViewZone): string => {
zonesHaveChanged = true;
return this.viewZones.addZone(zone);
},
removeZone: (id: number): void => {
removeZone: (id: string): void => {
if (!id) {
return;
}
zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged;
},
layoutZone: (id: number): void => {
layoutZone: (id: string): void => {
if (!id) {
return;
}

View File

@@ -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;
}

View File

@@ -72,7 +72,7 @@ interface IDiffEditorWidgetStyle {
}
class VisualEditorState {
private _zones: number[];
private _zones: string[];
private _zonesMap: { [zoneId: string]: boolean; };
private _decorations: string[];

View File

@@ -110,21 +110,6 @@ export function createTextBuffer(value: string | model.ITextBufferFactory, defau
let MODEL_ID = 0;
/**
* Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
*/
function singleLetter(result: number): string {
const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
result = result % (2 * LETTERS_CNT);
if (result < LETTERS_CNT) {
return String.fromCharCode(CharCode.a + result);
}
return String.fromCharCode(CharCode.A + result - LETTERS_CNT);
}
const LIMIT_FIND_COUNT = 999;
export const LONG_LINE_BOUNDARY = 10000;
@@ -343,7 +328,7 @@ export class TextModel extends Disposable implements model.ITextModel {
}
});
this._instanceId = singleLetter(MODEL_ID);
this._instanceId = strings.singleLetterHash(MODEL_ID);
this._lastDecorationId = 0;
this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees();

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -3,8 +3,10 @@
* Licensed under the Source EULA. 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];

View File

@@ -17,7 +17,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceCompute
import { ITheme } from 'vs/platform/theme/common/themeService';
export interface IViewWhitespaceViewportData {
readonly id: number;
readonly id: string;
readonly afterLineNumber: number;
readonly verticalOffset: number;
readonly height: number;
@@ -74,15 +74,15 @@ export interface IViewLayout {
* Reserve rendering space.
* @return an identifier that can be later used to remove or change the whitespace.
*/
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number;
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string;
/**
* Change the properties of a whitespace.
*/
changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean;
changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean;
/**
* Remove rendering space
*/
removeWhitespace(id: number): boolean;
removeWhitespace(id: string): boolean;
/**
* Get the layout information for whitespaces currently in the viewport
*/

View File

@@ -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[];

View File

@@ -116,7 +116,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private readonly _replaceFocusTracker: dom.IFocusTracker;
private readonly _replaceInputFocused: IContextKey<boolean>;
private _viewZone?: FindWidgetViewZone;
private _viewZoneId?: number;
private _viewZoneId?: string;
private _resizeSash!: Sash;
private _resized!: boolean;
@@ -224,15 +224,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
if (!this._isVisible) {
return;
}
if (this._viewZoneId === undefined) {
return;
}
this._codeEditor.changeViewZones((accessor) => {
if (this._viewZoneId) {
accessor.removeZone(this._viewZoneId);
}
this._viewZoneId = undefined;
});
this._viewZoneId = undefined;
}));

View File

@@ -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,

View File

@@ -27,6 +27,7 @@ export const KnownSnippetVariableNames: { [key: string]: true } = Object.freeze(
'CURRENT_DAY_NAME_SHORT': true,
'CURRENT_MONTH_NAME': true,
'CURRENT_MONTH_NAME_SHORT': true,
'CURRENT_SECONDS_UNIX': true,
'SELECTION': true,
'CLIPBOARD': true,
'TM_SELECTED_TEXT': true,
@@ -245,6 +246,8 @@ export class TimeBasedVariableResolver implements VariableResolver {
return TimeBasedVariableResolver.monthNames[new Date().getMonth()];
} else if (name === 'CURRENT_MONTH_NAME_SHORT') {
return TimeBasedVariableResolver.monthNamesShort[new Date().getMonth()];
} else if (name === 'CURRENT_SECONDS_UNIX') {
return String(Math.floor(Date.now() / 1000));
}
return undefined;

View File

@@ -281,6 +281,7 @@ suite('Snippet Variables Resolver', function () {
assertVariableResolve3(resolver, 'CURRENT_DAY_NAME_SHORT');
assertVariableResolve3(resolver, 'CURRENT_MONTH_NAME');
assertVariableResolve3(resolver, 'CURRENT_MONTH_NAME_SHORT');
assertVariableResolve3(resolver, 'CURRENT_SECONDS_UNIX');
});
test('creating snippet - format-condition doesn\'t work #53617', function () {

View File

@@ -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;

View File

@@ -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';

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

@@ -3609,17 +3609,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;
}
/**

View File

@@ -392,10 +392,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 {
@@ -440,7 +440,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;
}
}
@@ -788,7 +788,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 })));
@@ -874,7 +874,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);
}
}
@@ -906,7 +906,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));
}
@@ -986,4 +986,4 @@ export async function resolveMarketplaceHeaders(version: string, environmentServ
return headers;
}
}

View File

@@ -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

View File

@@ -9,7 +9,6 @@ import { IDisposable } from 'vs/base/common/lifecycle';
export const IOpenerService = createDecorator<IOpenerService>('openerService');
export interface IOpener {
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
}
@@ -18,6 +17,9 @@ export interface IOpenerService {
_serviceBrand: any;
/**
* Register a participant that can handle the open() call.
*/
registerOpener(opener: IOpener): IDisposable;
/**
@@ -27,10 +29,18 @@ export interface IOpenerService {
* @return A promise that resolves when the opening is done.
*/
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
/**
* Opens a URL externally.
*
* @param url A resource to open externally.
*/
openExternal(resource: URI): Promise<boolean>;
}
export const NullOpenerService: IOpenerService = Object.freeze({
_serviceBrand: undefined,
registerOpener() { return { dispose() { } }; },
open() { return Promise.resolve(false); }
open() { return Promise.resolve(false); },
openExternal() { return Promise.resolve(false); }
});

View File

@@ -10,44 +10,18 @@ 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 },
...{ vscodeVersion: '1.35.0' } // {{SQL CARBON EDIT}} add vscodeversion
};
}
get version(): string { return this.productConfiguration && this.productConfiguration.version ? this.productConfiguration.version : '1.38.0-unknown'; }
get vscodeVersion(): string { return '1.35.0'; } // {{SQL CARBON EDIT}} add vscodeversion
get recommendedExtensionsByScenario(): { [area: string]: Array<string> } { return this.productConfiguration ? this.productConfiguration.recommendedExtensionsByScenario : {}; }// {{SQL CARBON EDIT}} add getter
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; }
}

View File

@@ -11,40 +11,7 @@ export interface IProductService {
_serviceBrand: ServiceIdentifier<any>;
readonly version: string;
readonly vscodeVersion: string; // {{SQL CARBON EDIT}} add vscode version
readonly recommendedExtensionsByScenario: { [area: string]: Array<string> }; // {{SQL CARBON EDIT}} add getter
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 {
@@ -76,10 +43,12 @@ export interface IProductConfiguration {
readonly controlUrl: string;
readonly recommendationsUrl: string;
};
extensionTips: { [id: string]: string; };
recommendedExtensions: string[]; // {{SQL CARBON EDIT}}
recommendedExtensionsByScenario: { [area: string]: Array<string> }; // {{SQL CARBON EDIT}}
extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; };
readonly extensionTips: { [id: string]: string; };
readonly recommendedExtensions: string[]; // {{SQL CARBON EDIT}}
readonly recommendedExtensionsByScenario: { [area: string]: Array<string> }; // {{SQL CARBON EDIT}}
readonly vscodeVersion: string; // {{SQL CARBON EDIT}} add vscode version
readonly gettingStartedUrl: string; // {SQL CARBON EDIT}
readonly extensionImportantTips: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; };
readonly exeBasedExtensionTips: { [id: string]: IExeBasedExtensionTip; };
readonly extensionKeywords: { [extension: string]: readonly string[]; };
readonly extensionAllowedBadgeProviders: readonly string[];
@@ -100,8 +69,6 @@ export interface IProductConfiguration {
};
readonly documentationUrl: string;
readonly releaseNotesUrl: string;
readonly gettingStartedUrl: string; // {SQL CARBON EDIT}
readonly vscodeVersion: string; // {SQL CARBON EDIT}
readonly keyboardShortcutsUrlMac: string;
readonly keyboardShortcutsUrlLinux: string;
readonly keyboardShortcutsUrlWin: string;

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. 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,35 +12,12 @@ export class ProductService implements IProductService {
_serviceBrand!: ServiceIdentifier<IProductService>;
get version(): string { return pkg.version; }
readonly productConfiguration: IProductConfiguration;
get vscodeVersion(): string { return '1.35.0'; } // {{SQL CARBON EDIT}} add vscodeversion
constructor() {
this.productConfiguration = {
...product, ...{ version: pkg.version }, ...{ vscodeVersion: '1.35.0' } // {{SQL CARBON EDIT}} add vscodeversion}
};
}
get recommendedExtensionsByScenario(): { [area: string]: Array<string> } { return product.recommendedExtensionsByScenario; }// {{SQL CARBON EDIT}} add getter
get commit(): string | undefined { return product.commit; }
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; }
}

View File

@@ -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.
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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';

View File

@@ -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 {

View File

@@ -19,6 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files';
import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm';
import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote.contribution';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet';
@@ -79,6 +80,7 @@ interface IUserFriendlyViewDescriptor {
id: string;
name: string;
when?: string;
group?: string;
}
const viewDescriptor: IJSONSchema = {
@@ -99,6 +101,27 @@ const viewDescriptor: IJSONSchema = {
}
};
const nestableViewDescriptor: IJSONSchema = {
type: 'object',
properties: {
id: {
description: localize('vscode.extension.contributes.view.id', 'Identifier of the view. Use this to register a data provider through `vscode.window.registerTreeDataProviderForView` API. Also to trigger activating your extension by registering `onView:${id}` event to `activationEvents`.'),
type: 'string'
},
name: {
description: localize('vscode.extension.contributes.view.name', 'The human-readable name of the view. Will be shown'),
type: 'string'
},
when: {
description: localize('vscode.extension.contributes.view.when', 'Condition which must be true to show this view'),
type: 'string'
},
group: {
description: localize('vscode.extension.contributes.view.group', 'Nested group in the viewlet'),
type: 'string'
}
}
};
const viewsContribution: IJSONSchema = {
description: localize('vscode.extension.contributes.views', "Contributes views to the editor"),
type: 'object',
@@ -126,6 +149,12 @@ const viewsContribution: IJSONSchema = {
type: 'array',
items: viewDescriptor,
default: []
},
'remote': {
description: localize('views.remote', "Contributes views to Remote container in the Activity bar"),
type: 'array',
items: nestableViewDescriptor,
default: []
}
},
additionalProperties: {
@@ -376,6 +405,12 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
return null;
}
const order = ExtensionIdentifier.equals(extension.description.identifier, container.extensionId)
? index + 1
: container.orderDelegate
? container.orderDelegate.getOrder(item.group)
: undefined;
const viewDescriptor = <ICustomViewDescriptor>{
id: item.id,
name: item.name,
@@ -384,9 +419,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
canToggleVisibility: true,
collapsed: this.showCollapsed(container),
treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name, container),
order: ExtensionIdentifier.equals(extension.description.identifier, container.extensionId) ? index + 1 : undefined,
order: order,
extensionId: extension.description.identifier,
originalContainerId: entry.key
originalContainerId: entry.key,
group: item.group
};
viewIds.push(viewDescriptor.id);
@@ -440,6 +476,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
case 'explorer': return this.viewContainersRegistry.get(EXPLORER);
case 'debug': return this.viewContainersRegistry.get(DEBUG);
case 'scm': return this.viewContainersRegistry.get(SCM);
case 'remote': return this.viewContainersRegistry.get(REMOTE);
default: return this.viewContainersRegistry.get(`workbench.view.extension.${value}`);
}
}

View File

@@ -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;
}

View File

@@ -11,6 +11,9 @@ 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';
import { createAzdataApiFactory, createSqlopsApiFactory } from 'sql/workbench/api/common/sqlExtHost.api.impl'; // {{SQL CARBON EDIT}} use our extension initalizer
import { AzdataNodeModuleFactory, SqlopsNodeModuleFactory } from 'sql/workbench/api/node/extHostRequireInterceptor'; // {{SQL CARBON EDIT}} use our extension initalizer
@@ -61,12 +64,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 {

View File

@@ -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;
}

View File

@@ -1177,10 +1177,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
}
private createGridDescriptor(): ISerializedGrid {
const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, 600);
const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, 400);
const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, 300);
const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, 300);
const workbenchDimensions = getClientArea(this.parent);
const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width);
const height = this.storageService.getNumber(Storage.GRID_HEIGHT, StorageScope.GLOBAL, workbenchDimensions.height);
// At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys
const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))!);
const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3));
const titleBarHeight = this.titleBarPartView.minimumHeight;
const statusBarHeight = this.statusBarPartView.minimumHeight;

View File

@@ -74,7 +74,7 @@ export interface IEditorOpeningEvent extends IEditorIdentifier {
* Allows to prevent the opening of an editor by providing a callback
* that will be executed instead. By returning another editor promise
* it is possible to override the opening with another editor. It is ok
* to return a promise that resolves to NULL to prevent the opening
* to return a promise that resolves to `undefined` to prevent the opening
* alltogether.
*/
prevent(callback: () => undefined | Promise<IEditor | undefined>): void;

View File

@@ -382,7 +382,19 @@ export class ContributableViewsModel extends Disposable {
return 0;
}
return (this.getViewOrder(a) - this.getViewOrder(b)) || (a.id < b.id ? -1 : 1);
return (this.getViewOrder(a) - this.getViewOrder(b)) || this.getGroupOrderResult(a, b) || (a.id < b.id ? -1 : 1);
}
private getGroupOrderResult(a: IViewDescriptor, b: IViewDescriptor) {
if (!a.group || !b.group) {
return 0;
}
if (a.group === b.group) {
return 0;
}
return a.group < b.group ? -1 : 1;
}
private getViewOrder(viewDescriptor: IViewDescriptor): number {

View File

@@ -22,7 +22,7 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { ITunnelService } from 'vs/platform/remote/common/tunnel';
// tslint:disable-next-line: import-patterns
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { addDisposableListener, EventType, windowOpenNoOpener } from 'vs/base/browser/dom';
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
import { pathsToEditors } from 'vs/workbench/common/editor';
@@ -31,14 +31,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { ParsedArgs } from 'vs/platform/environment/common/environment';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage';
// tslint:disable-next-line: import-patterns
import { IExperimentService, IExperiment, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IProductService } from 'vs/platform/product/common/product';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
// tslint:disable-next-line: import-patterns
import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats';
//#region Extension Tips
@@ -751,13 +751,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);
@@ -855,33 +855,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

View File

@@ -51,7 +51,7 @@ export interface IViewContainersRegistry {
*
* @returns the registered ViewContainer.
*/
registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier): ViewContainer;
registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer;
/**
* Deregisters the given view container
@@ -67,8 +67,12 @@ export interface IViewContainersRegistry {
get(id: string): ViewContainer | undefined;
}
interface ViewOrderDelegate {
getOrder(group?: string): number | undefined;
}
export class ViewContainer {
protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier) { }
protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier, readonly orderDelegate?: ViewOrderDelegate) { }
}
class ViewContainersRegistryImpl extends Disposable implements IViewContainersRegistry {
@@ -85,7 +89,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe
return values(this.viewContainers);
}
registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier): ViewContainer {
registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier, viewOrderDelegate?: ViewOrderDelegate): ViewContainer {
const existing = this.viewContainers.get(id);
if (existing) {
return existing;
@@ -93,7 +97,7 @@ class ViewContainersRegistryImpl extends Disposable implements IViewContainersRe
const viewContainer = new class extends ViewContainer {
constructor() {
super(id, !!hideIfEmpty, extensionId);
super(id, !!hideIfEmpty, extensionId, viewOrderDelegate);
}
};
this.viewContainers.set(id, viewContainer);
@@ -126,6 +130,8 @@ export interface IViewDescriptor {
readonly when?: ContextKeyExpr;
readonly group?: string;
readonly order?: number;
readonly weight?: number;

View File

@@ -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';

View File

@@ -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';

View File

@@ -543,9 +543,9 @@ class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoi
}
}
export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise<IEditor | null> {
export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise<IEditor | undefined> {
if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) {
return Promise.resolve(null);
return Promise.resolve(undefined);
}
const selection = breakpoint.endLineNumber ? {

View File

@@ -35,6 +35,7 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c
import { onUnexpectedError } from 'vs/base/common/errors';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { CancellationToken } from 'vs/base/common/cancellation';
import { withUndefinedAsNull } from 'vs/base/common/types';
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
@@ -577,7 +578,7 @@ class Launch extends AbstractLaunch implements ILaunch {
pinned: created,
revealIfVisible: true
},
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created })));
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created })));
}, (error: Error) => {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error.message));
});
@@ -613,7 +614,7 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
return this.editorService.openEditor({
resource: this.contextService.getWorkspace().configuration!,
options: { preserveFocus }
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created: false }));
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor: withUndefinedAsNull(editor), created: false }));
}
}
@@ -647,6 +648,6 @@ class UserLaunch extends AbstractLaunch implements ILaunch {
}
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> {
return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor, created: false }));
return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor: withUndefinedAsNull(editor), created: false }));
}
}

View File

@@ -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,

View File

@@ -13,6 +13,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/
import { Schemas } from 'vs/base/common/network';
import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
import { ITextEditor } from 'vs/workbench/common/editor';
import { withUndefinedAsNull } from 'vs/base/common/types';
export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
@@ -104,7 +105,7 @@ export class Source {
revealInCenterIfOutsideViewport: true,
pinned: pinned || (!preserveFocus && !this.inMemory)
}
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(withUndefinedAsNull);
}
static getEncodedDebugData(modelUri: uri): { name: string, path: string, sessionId?: string, sourceReference?: number } {

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -1,407 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { 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;
}
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -79,10 +79,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(() => {

View File

@@ -435,7 +435,7 @@ export class ExtensionsListView extends ViewletPanel {
// {{SQL CARBON EDIT}}
if (this.productService) {
let promiseRecommendedExtensionsByScenario: Promise<IPagedModel<IExtension>> | undefined;
Object.keys(this.productService.recommendedExtensionsByScenario).forEach(scenarioType => {
Object.keys(this.productService.productConfiguration.recommendedExtensionsByScenario).forEach(scenarioType => {
let re = new RegExp('@' + scenarioType, 'i');
if (re.test(query.value)) {
promiseRecommendedExtensionsByScenario = this.getRecommendedExtensionsByScenario(token, scenarioType);
@@ -944,7 +944,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);
@@ -960,7 +960,7 @@ export class ServerExtensionsView extends ExtensionsListView {
}
getActions(): IAction[] {
if (this.extensionManagementServerService.localExtensionManagementServer === this.server) {
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];

View File

@@ -118,20 +118,19 @@ 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 && `${this.productService.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`; // {{SQL CARBON EDIT}} add check for itemurl
return `${this.productService.productConfiguration.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`;
}
// {{SQL CARBON EDIT}}
get downloadPage(): string {
if (!this.productService.extensionsGallery) {
if (!this.productService.productConfiguration.extensionsGallery) {
return null;
}
// {{SQL CARBON EDIT}}
return this.gallery && this.gallery.assets && this.gallery.assets.downloadPage && this.gallery.assets.downloadPage.uri;
}
@@ -632,7 +631,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
@@ -832,9 +831,9 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
// This is the execution path for install/update extension from marketplace.
// Check both the vscode version and azure data studio version
// The check is added here because we want to fail fast instead of downloading the VSIX and then fail.
if (gallery.properties.engine && (!isEngineValid(gallery.properties.engine, this.productService.vscodeVersion)
|| (gallery.properties.azDataEngine && !isEngineValid(gallery.properties.azDataEngine, this.productService.version)))) {
return Promise.reject(new Error(nls.localize('incompatible2', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", extension.gallery!.identifier.id, this.productService.version, gallery.version)));
if (gallery.properties.engine && (!isEngineValid(gallery.properties.engine, this.productService.productConfiguration.vscodeVersion)
|| (gallery.properties.azDataEngine && !isEngineValid(gallery.properties.azDataEngine, this.productService.productConfiguration.version)))) {
return Promise.reject(new Error(nls.localize('incompatible2', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", extension.gallery!.identifier.id, this.productService.productConfiguration.version, gallery.version)));
}
return this.installWithProgress(async () => {
@@ -1077,7 +1076,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;
}

View File

@@ -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';
// {{SQL CARBON EDIT}}
import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
@@ -28,13 +27,11 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, EXTENSIONS_CONFIG, ExtensionsPolicyKey, ExtensionsPolicy } 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';
@@ -47,11 +44,11 @@ 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 { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}}
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}}
import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats';
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again");
@@ -117,8 +114,9 @@ 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,
@IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService
@IAdsTelemetryService private readonly adsTelemetryService: IAdsTelemetryService, // {{SQL CARBON EDIT}}
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService,
@IProductService private readonly productService: IProductService
) {
super();
@@ -128,8 +126,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();
@@ -258,7 +256,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'] }));
}
@@ -615,10 +613,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;
}
}
@@ -629,13 +627,13 @@ 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;
// {{SQL CARBON EDIT}}
this._recommendations = product.recommendedExtensions;
this._recommendations = this.productService.productConfiguration.recommendedExtensions;
if (!extensionTips) {
return;
}
@@ -652,7 +650,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];
@@ -714,7 +712,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: [] };
@@ -768,7 +766,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;
}
@@ -994,14 +992,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 => {
@@ -1018,7 +1016,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;
}
@@ -1090,7 +1088,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;
@@ -1248,7 +1246,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return Promise.reject(new Error(localize('scenarioTypeUndefined', 'The scenario type for extension recommendations must be provided.')));
}
return Promise.resolve((product.recommendedExtensionsByScenario[scenarioType] || [])
return Promise.resolve((this.productService.productConfiguration.recommendedExtensionsByScenario[scenarioType] || [])
.filter(extensionId => this.isExtensionAllowedToBeRecommended(extensionId))
.map(extensionId => (<IExtensionRecommendation>{ extensionId, sources: ['application'] })));
}

View File

@@ -52,6 +52,7 @@ import { NullLogService } from 'vs/platform/log/common/log';
import { Schemas } from 'vs/base/common/network';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { IFileService } from 'vs/platform/files/common/files';
import { IProductService } from 'vs/platform/product/common/product';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {

View File

@@ -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';

View File

@@ -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 => {

View File

@@ -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());

View File

@@ -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");
}

View File

@@ -34,6 +34,7 @@ import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Registry } from 'vs/platform/registry/common/platform';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { withUndefinedAsNull } from 'vs/base/common/types';
export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution {
@@ -202,7 +203,7 @@ export class ExplorerViewlet extends ViewContainerViewlet {
openEditorsView.setStructuralRefreshDelay(delay);
}
let openedEditor: IEditor | null = null;
let openedEditor: IEditor | undefined;
try {
openedEditor = await this.editorService.openEditor(editor, options, group);
} catch (error) {
@@ -214,7 +215,7 @@ export class ExplorerViewlet extends ViewContainerViewlet {
}
}
return openedEditor;
return withUndefinedAsNull(openedEditor);
});
const explorerInstantiator = this.instantiationService.createChild(new ServiceCollection([IEditorService, delegatingEditorService]));

View File

@@ -503,7 +503,7 @@ export class GlobalCompareResourcesAction extends Action {
override: this.editorService.openEditor({
leftResource: activeResource,
rightResource: resource
}).then(() => null)
}).then(() => undefined)
};
}

View File

@@ -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 {

View File

@@ -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)) {

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