Merge from vscode b12f623603e2fc1c5b3037115fa37c1a6acc4165 (#6760)
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
BIN
resources/win32/inno-big-100.bmp
Normal file
|
After Width: | Height: | Size: 151 KiB |
0
resources/win32/inno-big.bmp → resources/win32/inno-big-125.bmp
Normal file → Executable file
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 201 KiB |
BIN
resources/win32/inno-big-150.bmp
Executable file
|
After Width: | Height: | Size: 332 KiB |
BIN
resources/win32/inno-big-175.bmp
Executable file
|
After Width: | Height: | Size: 445 KiB |
BIN
resources/win32/inno-big-200.bmp
Executable file
|
After Width: | Height: | Size: 580 KiB |
BIN
resources/win32/inno-big-225.bmp
Executable file
|
After Width: | Height: | Size: 730 KiB |
BIN
resources/win32/inno-big-250.bmp
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
resources/win32/inno-small-100.bmp
Executable file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
resources/win32/inno-small-125.bmp
Executable file
|
After Width: | Height: | Size: 13 KiB |
BIN
resources/win32/inno-small-150.bmp
Executable file
|
After Width: | Height: | Size: 20 KiB |
BIN
resources/win32/inno-small-175.bmp
Executable file
|
After Width: | Height: | Size: 26 KiB |
BIN
resources/win32/inno-small-200.bmp
Executable file
|
After Width: | Height: | Size: 34 KiB |
BIN
resources/win32/inno-small-225.bmp
Executable file
|
After Width: | Height: | Size: 43 KiB |
BIN
resources/win32/inno-small-250.bmp
Executable file
|
After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@@ -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');
|
||||
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
220
src/vs/base/browser/formattedTextRenderer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -729,3 +729,18 @@ export function getNLines(str: string, n = 1): string {
|
||||
str.substr(0, idx) :
|
||||
str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
|
||||
*/
|
||||
export function singleLetterHash(n: number): string {
|
||||
const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
|
||||
|
||||
n = n % (2 * LETTERS_CNT);
|
||||
|
||||
if (n < LETTERS_CNT) {
|
||||
return String.fromCharCode(CharCode.a + n);
|
||||
}
|
||||
|
||||
return String.fromCharCode(CharCode.A + n - LETTERS_CNT);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the 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: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
});
|
||||
test('image rendering conforms to default without title', () => {
|
||||
const markdown = { value: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
});
|
||||
test('image width from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
|
||||
});
|
||||
test('image height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
|
||||
});
|
||||
test('image width and height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
|
||||
});
|
||||
});
|
||||
47
src/vs/base/test/browser/markdownRenderer.test.ts
Normal 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: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
});
|
||||
|
||||
test('image rendering conforms to default without title', () => {
|
||||
const markdown = { value: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
});
|
||||
|
||||
test('image width from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
|
||||
});
|
||||
|
||||
test('image height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
|
||||
});
|
||||
|
||||
test('image width and height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
|
||||
});
|
||||
});
|
||||
@@ -19,26 +19,31 @@ function getWorker(workerId: string, label: string): Worker | Promise<Worker> {
|
||||
// ESM-comment-begin
|
||||
if (typeof require === 'function') {
|
||||
// check if the JS lives on a different origin
|
||||
|
||||
const workerMain = require.toUrl('./' + workerId);
|
||||
if (/^(http:)|(https:)|(file:)/.test(workerMain)) {
|
||||
const currentUrl = String(window.location);
|
||||
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
|
||||
if (workerMain.substring(0, currentOrigin.length) !== currentOrigin) {
|
||||
// this is the cross-origin case
|
||||
// i.e. the webpage is running at a different origin than where the scripts are loaded from
|
||||
const workerBaseUrl = workerMain.substr(0, workerMain.length - 'vs/base/worker/workerMain.js'.length);
|
||||
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${workerMain}');/*${label}*/`;
|
||||
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
|
||||
return new Worker(url);
|
||||
}
|
||||
}
|
||||
return new Worker(workerMain + '#' + label);
|
||||
const workerUrl = getWorkerBootstrapUrl(workerMain, label);
|
||||
return new Worker(workerUrl, { name: label });
|
||||
}
|
||||
// ESM-comment-end
|
||||
throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`);
|
||||
}
|
||||
|
||||
export function getWorkerBootstrapUrl(scriptPath: string, label: string): string {
|
||||
if (/^(http:)|(https:)|(file:)/.test(scriptPath)) {
|
||||
const currentUrl = String(window.location);
|
||||
const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length);
|
||||
if (scriptPath.substring(0, currentOrigin.length) !== currentOrigin) {
|
||||
// this is the cross-origin case
|
||||
// i.e. the webpage is running at a different origin than where the scripts are loaded from
|
||||
const myPath = 'vs/base/worker/defaultWorkerFactory.js';
|
||||
const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length);
|
||||
const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${scriptPath}');/*${label}*/`;
|
||||
const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return scriptPath + '#' + label;
|
||||
}
|
||||
|
||||
function isPromiseLike<T>(obj: any): obj is PromiseLike<T> {
|
||||
if (typeof obj.then === 'function') {
|
||||
return true;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, p
|
||||
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { WindowsManager } from 'vs/code/electron-main/windows';
|
||||
import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows';
|
||||
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
|
||||
import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc';
|
||||
import { WindowsService } from 'vs/platform/windows/electron-main/windowsService';
|
||||
import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface IPointerHandlerHelper {
|
||||
*/
|
||||
getLastViewCursorsRenderData(): IViewCursorRenderData[];
|
||||
|
||||
shouldSuppressMouseDownOnViewZone(viewZoneId: number): boolean;
|
||||
shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean;
|
||||
shouldSuppressMouseDownOnWidget(widgetId: string): boolean;
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { CursorColumns } from 'vs/editor/common/controller/cursorCommon';
|
||||
|
||||
export interface IViewZoneData {
|
||||
viewZoneId: number;
|
||||
viewZoneId: string;
|
||||
positionBefore: Position | null;
|
||||
positionAfter: Position | null;
|
||||
position: Position;
|
||||
|
||||
@@ -83,17 +83,17 @@ export interface IViewZoneChangeAccessor {
|
||||
* @param zone Zone to create
|
||||
* @return A unique identifier to the view zone.
|
||||
*/
|
||||
addZone(zone: IViewZone): number;
|
||||
addZone(zone: IViewZone): string;
|
||||
/**
|
||||
* Remove a zone
|
||||
* @param id A unique identifier to the view zone, as returned by the `addZone` call.
|
||||
*/
|
||||
removeZone(id: number): void;
|
||||
removeZone(id: string): void;
|
||||
/**
|
||||
* Change a zone's position.
|
||||
* The editor will rescan the `afterLineNumber` and `afterColumn` properties of a view zone.
|
||||
*/
|
||||
layoutZone(id: number): void;
|
||||
layoutZone(id: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,7 +399,7 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
*/
|
||||
onWillType(listener: (text: string) => void): IDisposable;
|
||||
/**
|
||||
* An event emitted before interpreting typed characters (on the keyboard).
|
||||
* An event emitted after interpreting typed characters (on the keyboard).
|
||||
* @event
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -55,8 +55,7 @@ export class OpenerService implements IOpenerService {
|
||||
|
||||
if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) {
|
||||
// open http or default mail application
|
||||
dom.windowOpenNoOpener(encodeURI(resource.toString(true)));
|
||||
return Promise.resolve(true);
|
||||
return this.openExternal(resource);
|
||||
|
||||
} else if (equalsIgnoreCase(scheme, Schemas.command)) {
|
||||
// run command or bail out if command isn't known
|
||||
@@ -100,4 +99,10 @@ export class OpenerService implements IOpenerService {
|
||||
).then(() => true);
|
||||
}
|
||||
}
|
||||
|
||||
openExternal(resource: URI): Promise<boolean> {
|
||||
dom.windowOpenNoOpener(encodeURI(resource.toString(true)));
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ export class View extends ViewEventHandler {
|
||||
getLastViewCursorsRenderData: () => {
|
||||
return this.viewCursors.getLastRenderData() || [];
|
||||
},
|
||||
shouldSuppressMouseDownOnViewZone: (viewZoneId: number) => {
|
||||
shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => {
|
||||
return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
|
||||
},
|
||||
shouldSuppressMouseDownOnWidget: (widgetId: string) => {
|
||||
@@ -473,17 +473,17 @@ export class View extends ViewEventHandler {
|
||||
|
||||
this._renderOnce(() => {
|
||||
const changeAccessor: editorBrowser.IViewZoneChangeAccessor = {
|
||||
addZone: (zone: editorBrowser.IViewZone): number => {
|
||||
addZone: (zone: editorBrowser.IViewZone): string => {
|
||||
zonesHaveChanged = true;
|
||||
return this.viewZones.addZone(zone);
|
||||
},
|
||||
removeZone: (id: number): void => {
|
||||
removeZone: (id: string): void => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
zonesHaveChanged = this.viewZones.removeZone(id) || zonesHaveChanged;
|
||||
},
|
||||
layoutZone: (id: number): void => {
|
||||
layoutZone: (id: string): void => {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
|
||||
|
||||
export interface IMyViewZone {
|
||||
whitespaceId: number;
|
||||
whitespaceId: string;
|
||||
delegate: IViewZone;
|
||||
isVisible: boolean;
|
||||
domNode: FastDomNode<HTMLElement>;
|
||||
@@ -74,7 +74,7 @@ export class ViewZones extends ViewPart {
|
||||
const id = keys[i];
|
||||
const zone = this._zones[id];
|
||||
const props = this._computeWhitespaceProps(zone.delegate);
|
||||
if (this._context.viewLayout.changeWhitespace(parseInt(id, 10), props.afterViewLineNumber, props.heightInPx)) {
|
||||
if (this._context.viewLayout.changeWhitespace(id, props.afterViewLineNumber, props.heightInPx)) {
|
||||
this._safeCallOnComputedHeight(zone.delegate, props.heightInPx);
|
||||
hadAChange = true;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ export class ViewZones extends ViewPart {
|
||||
};
|
||||
}
|
||||
|
||||
public addZone(zone: IViewZone): number {
|
||||
public addZone(zone: IViewZone): string {
|
||||
const props = this._computeWhitespaceProps(zone);
|
||||
const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx);
|
||||
|
||||
@@ -200,18 +200,18 @@ export class ViewZones extends ViewPart {
|
||||
myZone.domNode.setPosition('absolute');
|
||||
myZone.domNode.domNode.style.width = '100%';
|
||||
myZone.domNode.setDisplay('none');
|
||||
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString());
|
||||
myZone.domNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
|
||||
this.domNode.appendChild(myZone.domNode);
|
||||
|
||||
if (myZone.marginDomNode) {
|
||||
myZone.marginDomNode.setPosition('absolute');
|
||||
myZone.marginDomNode.domNode.style.width = '100%';
|
||||
myZone.marginDomNode.setDisplay('none');
|
||||
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId.toString());
|
||||
myZone.marginDomNode.setAttribute('monaco-view-zone', myZone.whitespaceId);
|
||||
this.marginDomNode.appendChild(myZone.marginDomNode);
|
||||
}
|
||||
|
||||
this._zones[myZone.whitespaceId.toString()] = myZone;
|
||||
this._zones[myZone.whitespaceId] = myZone;
|
||||
|
||||
|
||||
this.setShouldRender();
|
||||
@@ -219,10 +219,10 @@ export class ViewZones extends ViewPart {
|
||||
return myZone.whitespaceId;
|
||||
}
|
||||
|
||||
public removeZone(id: number): boolean {
|
||||
if (this._zones.hasOwnProperty(id.toString())) {
|
||||
const zone = this._zones[id.toString()];
|
||||
delete this._zones[id.toString()];
|
||||
public removeZone(id: string): boolean {
|
||||
if (this._zones.hasOwnProperty(id)) {
|
||||
const zone = this._zones[id];
|
||||
delete this._zones[id];
|
||||
this._context.viewLayout.removeWhitespace(zone.whitespaceId);
|
||||
|
||||
zone.domNode.removeAttribute('monaco-visible-view-zone');
|
||||
@@ -242,10 +242,10 @@ export class ViewZones extends ViewPart {
|
||||
return false;
|
||||
}
|
||||
|
||||
public layoutZone(id: number): boolean {
|
||||
public layoutZone(id: string): boolean {
|
||||
let changed = false;
|
||||
if (this._zones.hasOwnProperty(id.toString())) {
|
||||
const zone = this._zones[id.toString()];
|
||||
if (this._zones.hasOwnProperty(id)) {
|
||||
const zone = this._zones[id];
|
||||
const props = this._computeWhitespaceProps(zone.delegate);
|
||||
// const newOrdinal = this._getZoneOrdinal(zone.delegate);
|
||||
changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed;
|
||||
@@ -259,9 +259,9 @@ export class ViewZones extends ViewPart {
|
||||
return changed;
|
||||
}
|
||||
|
||||
public shouldSuppressMouseDownOnViewZone(id: number): boolean {
|
||||
if (this._zones.hasOwnProperty(id.toString())) {
|
||||
const zone = this._zones[id.toString()];
|
||||
public shouldSuppressMouseDownOnViewZone(id: string): boolean {
|
||||
if (this._zones.hasOwnProperty(id)) {
|
||||
const zone = this._zones[id];
|
||||
return Boolean(zone.delegate.suppressMouseDown);
|
||||
}
|
||||
return false;
|
||||
@@ -314,7 +314,7 @@ export class ViewZones extends ViewPart {
|
||||
|
||||
let hasVisibleZone = false;
|
||||
for (let i = 0, len = visibleWhitespaces.length; i < len; i++) {
|
||||
visibleZones[visibleWhitespaces[i].id.toString()] = visibleWhitespaces[i];
|
||||
visibleZones[visibleWhitespaces[i].id] = visibleWhitespaces[i];
|
||||
hasVisibleZone = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ interface IDiffEditorWidgetStyle {
|
||||
}
|
||||
|
||||
class VisualEditorState {
|
||||
private _zones: number[];
|
||||
private _zones: string[];
|
||||
private _zonesMap: { [zoneId: string]: boolean; };
|
||||
private _decorations: string[];
|
||||
|
||||
|
||||
@@ -110,21 +110,6 @@ export function createTextBuffer(value: string | model.ITextBufferFactory, defau
|
||||
|
||||
let MODEL_ID = 0;
|
||||
|
||||
/**
|
||||
* Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc.
|
||||
*/
|
||||
function singleLetter(result: number): string {
|
||||
const LETTERS_CNT = (CharCode.Z - CharCode.A + 1);
|
||||
|
||||
result = result % (2 * LETTERS_CNT);
|
||||
|
||||
if (result < LETTERS_CNT) {
|
||||
return String.fromCharCode(CharCode.a + result);
|
||||
}
|
||||
|
||||
return String.fromCharCode(CharCode.A + result - LETTERS_CNT);
|
||||
}
|
||||
|
||||
const LIMIT_FIND_COUNT = 999;
|
||||
export const LONG_LINE_BOUNDARY = 10000;
|
||||
|
||||
@@ -343,7 +328,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
});
|
||||
|
||||
this._instanceId = singleLetter(MODEL_ID);
|
||||
this._instanceId = strings.singleLetterHash(MODEL_ID);
|
||||
this._lastDecorationId = 0;
|
||||
this._decorations = Object.create(null);
|
||||
this._decorationsTree = new DecorationsTrees();
|
||||
|
||||
@@ -63,14 +63,14 @@ export class LinesLayout {
|
||||
* @param heightInPx The height of the whitespace, in pixels.
|
||||
* @return An id that can be used later to mutate or delete the whitespace
|
||||
*/
|
||||
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): number {
|
||||
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
|
||||
return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change properties associated with a certain whitespace.
|
||||
*/
|
||||
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export class LinesLayout {
|
||||
* @param id The whitespace to remove
|
||||
* @return Returns true if the whitespace is found and it is removed.
|
||||
*/
|
||||
public removeWhitespace(id: number): boolean {
|
||||
public removeWhitespace(id: string): boolean {
|
||||
return this._whitespaces.removeWhitespace(id);
|
||||
}
|
||||
|
||||
|
||||
@@ -173,13 +173,13 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
|
||||
// ---- IVerticalLayoutProvider
|
||||
|
||||
public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number {
|
||||
public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string {
|
||||
return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth);
|
||||
}
|
||||
public changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight);
|
||||
}
|
||||
public removeWhitespace(id: number): boolean {
|
||||
public removeWhitespace(id: string): boolean {
|
||||
return this._linesLayout.removeWhitespace(id);
|
||||
}
|
||||
public getVerticalOffsetForLineNumber(lineNumber: number): number {
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
* Licensed under the 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];
|
||||
|
||||
@@ -17,7 +17,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceCompute
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface IViewWhitespaceViewportData {
|
||||
readonly id: number;
|
||||
readonly id: string;
|
||||
readonly afterLineNumber: number;
|
||||
readonly verticalOffset: number;
|
||||
readonly height: number;
|
||||
@@ -74,15 +74,15 @@ export interface IViewLayout {
|
||||
* Reserve rendering space.
|
||||
* @return an identifier that can be later used to remove or change the whitespace.
|
||||
*/
|
||||
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): number;
|
||||
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string;
|
||||
/**
|
||||
* Change the properties of a whitespace.
|
||||
*/
|
||||
changeWhitespace(id: number, newAfterLineNumber: number, newHeight: number): boolean;
|
||||
changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean;
|
||||
/**
|
||||
* Remove rendering space
|
||||
*/
|
||||
removeWhitespace(id: number): boolean;
|
||||
removeWhitespace(id: string): boolean;
|
||||
/**
|
||||
* Get the layout information for whitespaces currently in the viewport
|
||||
*/
|
||||
|
||||
@@ -193,7 +193,7 @@ export class CodeLensWidget {
|
||||
|
||||
private readonly _editor: editorBrowser.ICodeEditor;
|
||||
private readonly _viewZone!: CodeLensViewZone;
|
||||
private readonly _viewZoneId!: number;
|
||||
private readonly _viewZoneId!: string;
|
||||
private readonly _contentWidget!: CodeLensContentWidget;
|
||||
private _decorationIds: string[];
|
||||
private _data: CodeLensItem[];
|
||||
|
||||
@@ -116,7 +116,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
private readonly _replaceFocusTracker: dom.IFocusTracker;
|
||||
private readonly _replaceInputFocused: IContextKey<boolean>;
|
||||
private _viewZone?: FindWidgetViewZone;
|
||||
private _viewZoneId?: number;
|
||||
private _viewZoneId?: string;
|
||||
|
||||
private _resizeSash!: Sash;
|
||||
private _resized!: boolean;
|
||||
@@ -224,15 +224,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
||||
if (!this._isVisible) {
|
||||
return;
|
||||
}
|
||||
if (this._viewZoneId === undefined) {
|
||||
return;
|
||||
}
|
||||
this._codeEditor.changeViewZones((accessor) => {
|
||||
if (this._viewZoneId) {
|
||||
accessor.removeZone(this._viewZoneId);
|
||||
}
|
||||
this._viewZoneId = undefined;
|
||||
});
|
||||
this._viewZoneId = undefined;
|
||||
}));
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer';
|
||||
import { renderMarkdown, MarkdownRenderOptions } from 'vs/base/browser/markdownRenderer';
|
||||
import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -33,7 +33,7 @@ export class MarkdownRenderer extends Disposable {
|
||||
super();
|
||||
}
|
||||
|
||||
private getOptions(disposeables: DisposableStore): RenderOptions {
|
||||
private getOptions(disposeables: DisposableStore): MarkdownRenderOptions {
|
||||
return {
|
||||
codeBlockRenderer: (languageAlias, value) => {
|
||||
// In markdown,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -51,7 +51,7 @@ const WIDGET_ID = 'vs.editor.contrib.zoneWidget';
|
||||
export class ViewZoneDelegate implements IViewZone {
|
||||
|
||||
public domNode: HTMLElement;
|
||||
public id: number = 0; // A valid zone id should be greater than 0
|
||||
public id: string = ''; // A valid zone id should be greater than 0
|
||||
public afterLineNumber: number;
|
||||
public afterColumn: number;
|
||||
public heightInLines: number;
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./accessibilityHelp';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { renderFormattedText } from 'vs/base/browser/htmlContentRenderer';
|
||||
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
6
src/vs/monaco.d.ts
vendored
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,6 @@ import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const IOpenerService = createDecorator<IOpenerService>('openerService');
|
||||
|
||||
|
||||
export interface IOpener {
|
||||
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
|
||||
}
|
||||
@@ -18,6 +17,9 @@ export interface IOpenerService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Register a participant that can handle the open() call.
|
||||
*/
|
||||
registerOpener(opener: IOpener): IDisposable;
|
||||
|
||||
/**
|
||||
@@ -27,10 +29,18 @@ export interface IOpenerService {
|
||||
* @return A promise that resolves when the opening is done.
|
||||
*/
|
||||
open(resource: URI, options?: { openToSide?: boolean }): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Opens a URL externally.
|
||||
*
|
||||
* @param url A resource to open externally.
|
||||
*/
|
||||
openExternal(resource: URI): Promise<boolean>;
|
||||
}
|
||||
|
||||
export const NullOpenerService: IOpenerService = Object.freeze({
|
||||
_serviceBrand: undefined,
|
||||
registerOpener() { return { dispose() { } }; },
|
||||
open() { return Promise.resolve(false); }
|
||||
open() { return Promise.resolve(false); },
|
||||
openExternal() { return Promise.resolve(false); }
|
||||
});
|
||||
|
||||
@@ -10,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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,17 +13,14 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export class WindowsService implements IWindowsService {
|
||||
|
||||
_serviceBrand: any;
|
||||
_serviceBrand!: ServiceIdentifier<any>;
|
||||
|
||||
private channel: IChannel;
|
||||
|
||||
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
|
||||
this.channel = mainProcessService.getChannel('windows');
|
||||
}
|
||||
|
||||
get onWindowOpen(): Event<number> { return this.channel.listen('onWindowOpen'); }
|
||||
get onWindowFocus(): Event<number> { return this.channel.listen('onWindowFocus'); }
|
||||
get onWindowBlur(): Event<number> { return this.channel.listen('onWindowBlur'); }
|
||||
@@ -31,6 +28,10 @@ export class WindowsService implements IWindowsService {
|
||||
get onWindowUnmaximize(): Event<number> { return this.channel.listen('onWindowUnmaximize'); }
|
||||
get onRecentlyOpenedChange(): Event<void> { return this.channel.listen('onRecentlyOpenedChange'); }
|
||||
|
||||
constructor(@IMainProcessService mainProcessService: IMainProcessService) {
|
||||
this.channel = mainProcessService.getChannel('windows');
|
||||
}
|
||||
|
||||
pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise<void> {
|
||||
return this.channel.call('pickFileFolderAndOpen', options);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class EditorWebviewZone implements IViewZone {
|
||||
readonly afterColumn: number;
|
||||
readonly heightInLines: number;
|
||||
|
||||
private _id?: number;
|
||||
private _id?: string;
|
||||
// suppressMouseDown?: boolean | undefined;
|
||||
// heightInPx?: number | undefined;
|
||||
// minWidthInPx?: number | undefined;
|
||||
|
||||
@@ -327,7 +327,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews
|
||||
if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) {
|
||||
return true;
|
||||
}
|
||||
if (this._productService.urlProtocol === link.scheme) {
|
||||
if (this._productService.productConfiguration.urlProtocol === link.scheme) {
|
||||
return true;
|
||||
}
|
||||
return !!webview.webview.contentOptions.enableCommandUris && link.scheme === 'command';
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol';
|
||||
import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { extractLocalHostUriMetaDataForPortMapping } from 'vs/workbench/contrib/webview/common/portMapping';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWindow)
|
||||
export class MainThreadWindow implements MainThreadWindowShape {
|
||||
@@ -23,7 +24,7 @@ export class MainThreadWindow implements MainThreadWindowShape {
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IWindowsService private readonly windowsService: IWindowsService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@ITunnelService private readonly tunnelService: ITunnelService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
|
||||
) {
|
||||
@@ -58,7 +59,7 @@ export class MainThreadWindow implements MainThreadWindowShape {
|
||||
}
|
||||
}
|
||||
|
||||
return this.windowsService.openExternal(encodeURI(uri.toString(true)));
|
||||
return this.openerService.openExternal(uri);
|
||||
}
|
||||
|
||||
private getOrCreateTunnel(remotePort: number): Promise<RemoteTunnel> | undefined {
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { originalFSPath } from 'vs/base/common/resources';
|
||||
import { originalFSPath, joinPath } from 'vs/base/common/resources';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
@@ -332,14 +332,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
|
||||
const activationTimesBuilder = new ExtensionActivationTimesBuilder(reason.startup);
|
||||
return Promise.all<any>([
|
||||
this._loadCommonJSModule(extensionDescription.main, activationTimesBuilder),
|
||||
this._loadCommonJSModule(joinPath(extensionDescription.extensionLocation, extensionDescription.main), activationTimesBuilder),
|
||||
this._loadExtensionContext(extensionDescription)
|
||||
]).then(values => {
|
||||
return AbstractExtHostExtensionService._callActivate(this._logService, extensionDescription.identifier, <IExtensionModule>values[0], <IExtensionContext>values[1], activationTimesBuilder);
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract _loadCommonJSModule<T>(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
protected abstract _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T>;
|
||||
|
||||
private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise<vscode.ExtensionContext> {
|
||||
|
||||
@@ -536,7 +536,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
let testRunner: ITestRunner | INewTestRunner | undefined;
|
||||
let requireError: Error | undefined;
|
||||
try {
|
||||
testRunner = await this._loadCommonJSModule(extensionTestsPath, new ExtensionActivationTimesBuilder(false));
|
||||
testRunner = await this._loadCommonJSModule(URI.file(extensionTestsPath), new ExtensionActivationTimesBuilder(false));
|
||||
} catch (error) {
|
||||
requireError = error;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'vs/css!./accessibility';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { renderFormattedText } from 'vs/base/browser/htmlContentRenderer';
|
||||
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as nls from 'vs/nls';
|
||||
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
|
||||
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
@@ -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 ? {
|
||||
|
||||
@@ -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 }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 } {
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService';
|
||||
import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt';
|
||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
|
||||
|
||||
registerSingleton(IExperimentService, ExperimentService, true);
|
||||
|
||||
@@ -4,7 +4,23 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { language } from 'vs/base/common/platform';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { match } from 'vs/base/common/glob';
|
||||
import { IRequestService, asJson } from 'vs/platform/request/common/request';
|
||||
import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { IProductService } from 'vs/platform/product/common/product';
|
||||
import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats';
|
||||
|
||||
export const enum ExperimentState {
|
||||
Evaluating,
|
||||
@@ -56,4 +72,388 @@ export interface IExperimentService {
|
||||
onExperimentEnabled: Event<IExperiment>;
|
||||
}
|
||||
|
||||
export const IExperimentService = createDecorator<IExperimentService>('experimentService');
|
||||
export const IExperimentService = createDecorator<IExperimentService>('experimentService');
|
||||
|
||||
interface IExperimentStorageState {
|
||||
enabled: boolean;
|
||||
state: ExperimentState;
|
||||
editCount?: number;
|
||||
lastEditedDate?: string;
|
||||
}
|
||||
|
||||
interface IRawExperiment {
|
||||
id: string;
|
||||
enabled?: boolean;
|
||||
condition?: {
|
||||
insidersOnly?: boolean;
|
||||
newUser?: boolean;
|
||||
displayLanguage?: string;
|
||||
installedExtensions?: {
|
||||
excludes?: string[];
|
||||
includes?: string[];
|
||||
},
|
||||
fileEdits?: {
|
||||
filePathPattern?: string;
|
||||
workspaceIncludes?: string[];
|
||||
workspaceExcludes?: string[];
|
||||
minEditCount: number;
|
||||
},
|
||||
experimentsPreviouslyRun?: {
|
||||
excludes?: string[];
|
||||
includes?: string[];
|
||||
}
|
||||
userProbability?: number;
|
||||
};
|
||||
action?: IExperimentAction;
|
||||
}
|
||||
|
||||
export class ExperimentService extends Disposable implements IExperimentService {
|
||||
_serviceBrand: any;
|
||||
private _experiments: IExperiment[] = [];
|
||||
private _loadExperimentsPromise: Promise<void>;
|
||||
private _curatedMapping = Object.create(null);
|
||||
|
||||
private readonly _onExperimentEnabled = this._register(new Emitter<IExperiment>());
|
||||
onExperimentEnabled: Event<IExperiment> = this._onExperimentEnabled.event;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@ITextFileService private readonly textFileService: ITextFileService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IRequestService private readonly requestService: IRequestService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._loadExperimentsPromise = Promise.resolve(this.lifecycleService.when(LifecyclePhase.Eventually)).then(() => this.loadExperiments());
|
||||
}
|
||||
|
||||
public getExperimentById(id: string): Promise<IExperiment> {
|
||||
return this._loadExperimentsPromise.then(() => {
|
||||
return this._experiments.filter(x => x.id === id)[0];
|
||||
});
|
||||
}
|
||||
|
||||
public getExperimentsByType(type: ExperimentActionType): Promise<IExperiment[]> {
|
||||
return this._loadExperimentsPromise.then(() => {
|
||||
if (type === ExperimentActionType.Custom) {
|
||||
return this._experiments.filter(x => x.enabled && (!x.action || x.action.type === type));
|
||||
}
|
||||
return this._experiments.filter(x => x.enabled && x.action && x.action.type === type);
|
||||
});
|
||||
}
|
||||
|
||||
public getCuratedExtensionsList(curatedExtensionsKey: string): Promise<string[]> {
|
||||
return this._loadExperimentsPromise.then(() => {
|
||||
for (const experiment of this._experiments) {
|
||||
if (experiment.enabled
|
||||
&& experiment.state === ExperimentState.Run
|
||||
&& this._curatedMapping[experiment.id]
|
||||
&& this._curatedMapping[experiment.id].curatedExtensionsKey === curatedExtensionsKey) {
|
||||
return this._curatedMapping[experiment.id].curatedExtensionsList;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
public markAsCompleted(experimentId: string): void {
|
||||
const storageKey = 'experiments.' + experimentId;
|
||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
|
||||
experimentState.state = ExperimentState.Complete;
|
||||
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
protected getExperiments(): Promise<IRawExperiment[]> {
|
||||
if (!this.productService.productConfiguration.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return this.requestService.request({ type: 'GET', url: this.productService.productConfiguration.experimentsUrl }, CancellationToken.None).then(context => {
|
||||
if (context.res.statusCode !== 200) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
return asJson(context).then((result: any) => {
|
||||
return result && Array.isArray(result['experiments']) ? result['experiments'] : [];
|
||||
});
|
||||
}, () => Promise.resolve(null));
|
||||
}
|
||||
|
||||
private loadExperiments(): Promise<any> {
|
||||
return this.getExperiments().then(rawExperiments => {
|
||||
// Offline mode
|
||||
if (!rawExperiments) {
|
||||
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []);
|
||||
if (Array.isArray(allExperimentIdsFromStorage)) {
|
||||
allExperimentIdsFromStorage.forEach(experimentId => {
|
||||
const storageKey = 'experiments.' + experimentId;
|
||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), null);
|
||||
if (experimentState) {
|
||||
this._experiments.push({
|
||||
id: experimentId,
|
||||
enabled: experimentState.enabled,
|
||||
state: experimentState.state
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Clear disbaled/deleted experiments from storage
|
||||
const allExperimentIdsFromStorage = safeParse(this.storageService.get('allExperiments', StorageScope.GLOBAL), []);
|
||||
const enabledExperiments = rawExperiments.filter(experiment => !!experiment.enabled).map(experiment => experiment.id.toLowerCase());
|
||||
if (Array.isArray(allExperimentIdsFromStorage)) {
|
||||
allExperimentIdsFromStorage.forEach(experiment => {
|
||||
if (enabledExperiments.indexOf(experiment) === -1) {
|
||||
this.storageService.remove(`experiments.${experiment}`, StorageScope.GLOBAL);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (enabledExperiments.length) {
|
||||
this.storageService.store('allExperiments', JSON.stringify(enabledExperiments), StorageScope.GLOBAL);
|
||||
} else {
|
||||
this.storageService.remove('allExperiments', StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
const promises = rawExperiments.map(experiment => {
|
||||
const processedExperiment: IExperiment = {
|
||||
id: experiment.id,
|
||||
enabled: !!experiment.enabled,
|
||||
state: !!experiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun
|
||||
};
|
||||
|
||||
if (experiment.action) {
|
||||
processedExperiment.action = {
|
||||
type: ExperimentActionType[experiment.action.type] || ExperimentActionType.Custom,
|
||||
properties: experiment.action.properties
|
||||
};
|
||||
if (processedExperiment.action.type === ExperimentActionType.Prompt) {
|
||||
((<IExperimentActionPromptProperties>processedExperiment.action.properties).commands || []).forEach(x => {
|
||||
if (x.curatedExtensionsKey && Array.isArray(x.curatedExtensionsList)) {
|
||||
this._curatedMapping[experiment.id] = x;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!processedExperiment.action.properties) {
|
||||
processedExperiment.action.properties = {};
|
||||
}
|
||||
}
|
||||
this._experiments.push(processedExperiment);
|
||||
|
||||
if (!processedExperiment.enabled) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const storageKey = 'experiments.' + experiment.id;
|
||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
|
||||
if (!experimentState.hasOwnProperty('enabled')) {
|
||||
experimentState.enabled = processedExperiment.enabled;
|
||||
}
|
||||
if (!experimentState.hasOwnProperty('state')) {
|
||||
experimentState.state = processedExperiment.enabled ? ExperimentState.Evaluating : ExperimentState.NoRun;
|
||||
} else {
|
||||
processedExperiment.state = experimentState.state;
|
||||
}
|
||||
|
||||
return this.shouldRunExperiment(experiment, processedExperiment).then((state: ExperimentState) => {
|
||||
experimentState.state = processedExperiment.state = state;
|
||||
this.storageService.store(storageKey, JSON.stringify(experimentState), StorageScope.GLOBAL);
|
||||
|
||||
if (state === ExperimentState.Run) {
|
||||
this.fireRunExperiment(processedExperiment);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
});
|
||||
return Promise.all(promises).then(() => {
|
||||
type ExperimentsClassification = {
|
||||
experiments: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
this.telemetryService.publicLog2<{ experiments: IExperiment[] }, ExperimentsClassification>('experiments', { experiments: this._experiments });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private fireRunExperiment(experiment: IExperiment) {
|
||||
this._onExperimentEnabled.fire(experiment);
|
||||
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
|
||||
if (runExperimentIdsFromStorage.indexOf(experiment.id) === -1) {
|
||||
runExperimentIdsFromStorage.push(experiment.id);
|
||||
}
|
||||
|
||||
// Ensure we dont store duplicates
|
||||
const distinctExperiments = distinct(runExperimentIdsFromStorage);
|
||||
if (runExperimentIdsFromStorage.length !== distinctExperiments.length) {
|
||||
this.storageService.store('currentOrPreviouslyRunExperiments', JSON.stringify(distinctExperiments), StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
private checkExperimentDependencies(experiment: IRawExperiment): boolean {
|
||||
const experimentsPreviouslyRun = experiment.condition ? experiment.condition.experimentsPreviouslyRun : undefined;
|
||||
if (experimentsPreviouslyRun) {
|
||||
const runExperimentIdsFromStorage: string[] = safeParse(this.storageService.get('currentOrPreviouslyRunExperiments', StorageScope.GLOBAL), []);
|
||||
let includeCheck = true;
|
||||
let excludeCheck = true;
|
||||
const includes = experimentsPreviouslyRun.includes;
|
||||
if (Array.isArray(includes)) {
|
||||
includeCheck = runExperimentIdsFromStorage.some(x => includes.indexOf(x) > -1);
|
||||
}
|
||||
const excludes = experimentsPreviouslyRun.excludes;
|
||||
if (includeCheck && Array.isArray(excludes)) {
|
||||
excludeCheck = !runExperimentIdsFromStorage.some(x => excludes.indexOf(x) > -1);
|
||||
}
|
||||
if (!includeCheck || !excludeCheck) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private shouldRunExperiment(experiment: IRawExperiment, processedExperiment: IExperiment): Promise<ExperimentState> {
|
||||
if (processedExperiment.state !== ExperimentState.Evaluating) {
|
||||
return Promise.resolve(processedExperiment.state);
|
||||
}
|
||||
|
||||
if (!experiment.enabled) {
|
||||
return Promise.resolve(ExperimentState.NoRun);
|
||||
}
|
||||
|
||||
const condition = experiment.condition;
|
||||
if (!condition) {
|
||||
return Promise.resolve(ExperimentState.Run);
|
||||
}
|
||||
|
||||
if (!this.checkExperimentDependencies(experiment)) {
|
||||
return Promise.resolve(ExperimentState.NoRun);
|
||||
}
|
||||
|
||||
if (this.environmentService.appQuality === 'stable' && condition.insidersOnly === true) {
|
||||
return Promise.resolve(ExperimentState.NoRun);
|
||||
}
|
||||
|
||||
const isNewUser = !this.storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL);
|
||||
if ((condition.newUser === true && !isNewUser)
|
||||
|| (condition.newUser === false && isNewUser)) {
|
||||
return Promise.resolve(ExperimentState.NoRun);
|
||||
}
|
||||
|
||||
if (typeof condition.displayLanguage === 'string') {
|
||||
let localeToCheck = condition.displayLanguage.toLowerCase();
|
||||
let displayLanguage = language!.toLowerCase();
|
||||
|
||||
if (localeToCheck !== displayLanguage) {
|
||||
const a = displayLanguage.indexOf('-');
|
||||
const b = localeToCheck.indexOf('-');
|
||||
if (a > -1) {
|
||||
displayLanguage = displayLanguage.substr(0, a);
|
||||
}
|
||||
if (b > -1) {
|
||||
localeToCheck = localeToCheck.substr(0, b);
|
||||
}
|
||||
if (displayLanguage !== localeToCheck) {
|
||||
return Promise.resolve(ExperimentState.NoRun);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!condition.userProbability) {
|
||||
condition.userProbability = 1;
|
||||
}
|
||||
|
||||
let extensionsCheckPromise = Promise.resolve(true);
|
||||
const installedExtensions = condition.installedExtensions;
|
||||
if (installedExtensions) {
|
||||
extensionsCheckPromise = this.extensionManagementService.getInstalled(ExtensionType.User).then(locals => {
|
||||
let includesCheck = true;
|
||||
let excludesCheck = true;
|
||||
const localExtensions = locals.map(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}`);
|
||||
if (Array.isArray(installedExtensions.includes) && installedExtensions.includes.length) {
|
||||
const extensionIncludes = installedExtensions.includes.map(e => e.toLowerCase());
|
||||
includesCheck = localExtensions.some(e => extensionIncludes.indexOf(e) > -1);
|
||||
}
|
||||
if (Array.isArray(installedExtensions.excludes) && installedExtensions.excludes.length) {
|
||||
const extensionExcludes = installedExtensions.excludes.map(e => e.toLowerCase());
|
||||
excludesCheck = !localExtensions.some(e => extensionExcludes.indexOf(e) > -1);
|
||||
}
|
||||
return includesCheck && excludesCheck;
|
||||
});
|
||||
}
|
||||
|
||||
const storageKey = 'experiments.' + experiment.id;
|
||||
const experimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
|
||||
|
||||
return extensionsCheckPromise.then(success => {
|
||||
const fileEdits = condition.fileEdits;
|
||||
if (!success || !fileEdits || typeof fileEdits.minEditCount !== 'number') {
|
||||
const runExperiment = success && typeof condition.userProbability === 'number' && Math.random() < condition.userProbability;
|
||||
return runExperiment ? ExperimentState.Run : ExperimentState.NoRun;
|
||||
}
|
||||
|
||||
experimentState.editCount = experimentState.editCount || 0;
|
||||
if (experimentState.editCount >= fileEdits.minEditCount) {
|
||||
return ExperimentState.Run;
|
||||
}
|
||||
|
||||
const onSaveHandler = this.textFileService.models.onModelsSaved(e => {
|
||||
const date = new Date().toDateString();
|
||||
const latestExperimentState: IExperimentStorageState = safeParse(this.storageService.get(storageKey, StorageScope.GLOBAL), {});
|
||||
if (latestExperimentState.state !== ExperimentState.Evaluating) {
|
||||
onSaveHandler.dispose();
|
||||
return;
|
||||
}
|
||||
e.forEach(async event => {
|
||||
if (event.kind !== StateChange.SAVED
|
||||
|| latestExperimentState.state !== ExperimentState.Evaluating
|
||||
|| date === latestExperimentState.lastEditedDate
|
||||
|| (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let filePathCheck = true;
|
||||
let workspaceCheck = true;
|
||||
|
||||
if (typeof fileEdits.filePathPattern === 'string') {
|
||||
filePathCheck = match(fileEdits.filePathPattern, event.resource.fsPath);
|
||||
}
|
||||
if (Array.isArray(fileEdits.workspaceIncludes) && fileEdits.workspaceIncludes.length) {
|
||||
const tags = await this.workspaceStatsService.getTags();
|
||||
workspaceCheck = !!tags && fileEdits.workspaceIncludes.some(x => !!tags[x]);
|
||||
}
|
||||
if (workspaceCheck && Array.isArray(fileEdits.workspaceExcludes) && fileEdits.workspaceExcludes.length) {
|
||||
const tags = await this.workspaceStatsService.getTags();
|
||||
workspaceCheck = !!tags && !fileEdits.workspaceExcludes.some(x => !!tags[x]);
|
||||
}
|
||||
if (filePathCheck && workspaceCheck) {
|
||||
latestExperimentState.editCount = (latestExperimentState.editCount || 0) + 1;
|
||||
latestExperimentState.lastEditedDate = date;
|
||||
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL);
|
||||
}
|
||||
});
|
||||
if (typeof latestExperimentState.editCount === 'number' && latestExperimentState.editCount >= fileEdits.minEditCount) {
|
||||
processedExperiment.state = latestExperimentState.state = (typeof condition.userProbability === 'number' && Math.random() < condition.userProbability && this.checkExperimentDependencies(experiment)) ? ExperimentState.Run : ExperimentState.NoRun;
|
||||
this.storageService.store(storageKey, JSON.stringify(latestExperimentState), StorageScope.GLOBAL);
|
||||
if (latestExperimentState.state === ExperimentState.Run && experiment.action && ExperimentActionType[experiment.action.type] === ExperimentActionType.Prompt) {
|
||||
this.fireRunExperiment(processedExperiment);
|
||||
}
|
||||
}
|
||||
});
|
||||
this._register(onSaveHandler);
|
||||
return ExperimentState.Evaluating;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function safeParse(text: string | undefined, defaultObject: any) {
|
||||
try {
|
||||
return text ? JSON.parse(text) || defaultObject : defaultObject;
|
||||
} catch (e) {
|
||||
return defaultObject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the 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;
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService';
|
||||
import { ExperimentActionType, ExperimentState, IExperiment, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt';
|
||||
import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/browser/experimentalPrompt';
|
||||
import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService, LocalizedPromptText } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test';
|
||||
import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices';
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'] })));
|
||||
}
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -35,8 +35,7 @@ import { URLService } from 'vs/platform/url/common/urlService';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { SinonStub } from 'sinon';
|
||||
import { IExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { ExperimentService } from 'vs/workbench/contrib/experiments/electron-browser/experimentService';
|
||||
import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
|
||||
import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
@@ -73,8 +73,8 @@ export class FeedbackDropdown extends Dropdown {
|
||||
this.feedbackDelegate = options.feedbackService;
|
||||
this.maxFeedbackCharacters = this.feedbackDelegate.getCharacterLimit(this.sentiment);
|
||||
|
||||
if (productService.sendASmile) {
|
||||
this.requestFeatureLink = productService.sendASmile.requestFeatureUrl;
|
||||
if (productService.productConfiguration.sendASmile) {
|
||||
this.requestFeatureLink = productService.productConfiguration.sendASmile.requestFeatureUrl;
|
||||
}
|
||||
|
||||
this.integrityService.isPure().then(result => {
|
||||
|
||||
@@ -58,7 +58,7 @@ export class FeedbackStatusbarConribution extends Disposable implements IWorkben
|
||||
) {
|
||||
super();
|
||||
|
||||
if (productService.sendASmile) {
|
||||
if (productService.productConfiguration.sendASmile) {
|
||||
this.entry = this._register(statusbarService.addEntry(this.getStatusEntry(), 'status.feedback', localize('status.feedback', "Tweet Feedback"), StatusbarAlignment.RIGHT, -100 /* towards the end of the right hand side */));
|
||||
|
||||
CommandsRegistry.registerCommand('_feedback.open', () => this.toggleFeedback());
|
||||
|
||||
@@ -7,16 +7,15 @@ import * as nls from 'vs/nls';
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
/**
|
||||
* An implementation of editor for binary files like images.
|
||||
@@ -28,7 +27,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWindowsService private readonly windowsService: IWindowsService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IFileService fileService: IFileService,
|
||||
@@ -39,7 +38,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
|
||||
BinaryFileEditor.ID,
|
||||
{
|
||||
openInternal: (input, options) => this.openInternal(input, options),
|
||||
openExternal: resource => this.openExternal(resource)
|
||||
openExternal: resource => this.openerService.openExternal(resource)
|
||||
},
|
||||
telemetryService,
|
||||
themeService,
|
||||
@@ -58,13 +57,6 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor {
|
||||
}
|
||||
}
|
||||
|
||||
private async openExternal(resource: URI): Promise<void> {
|
||||
const didOpen = await this.windowsService.openExternal(resource.toString());
|
||||
if (!didOpen) {
|
||||
return this.windowsService.showItemInFolder(resource);
|
||||
}
|
||||
}
|
||||
|
||||
getTitle(): string | null {
|
||||
return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer");
|
||||
}
|
||||
|
||||
@@ -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]));
|
||||
|
||||
@@ -503,7 +503,7 @@ export class GlobalCompareResourcesAction extends Action {
|
||||
override: this.editorService.openEditor({
|
||||
leftResource: activeResource,
|
||||
rightResource: resource
|
||||
}).then(() => null)
|
||||
}).then(() => undefined)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,11 @@ export class ExplorerService implements IExplorerService {
|
||||
// Stat needs to be resolved first and then revealed
|
||||
const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' };
|
||||
const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
|
||||
const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource;
|
||||
if (workspaceFolder === null) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
const rootUri = workspaceFolder.uri;
|
||||
|
||||
const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!;
|
||||
|
||||
try {
|
||||
|
||||
@@ -534,8 +534,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
|
||||
const span1 = dom.append(container, dom.$('span'));
|
||||
span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS;
|
||||
const link = dom.append(container, dom.$('a.messageAction'));
|
||||
link.textContent = localize('clearFilter', "Clear Filter.");
|
||||
link.textContent = localize('clearFilter', "Clear Filter");
|
||||
link.setAttribute('tabIndex', '0');
|
||||
const span2 = dom.append(container, dom.$('span'));
|
||||
span2.textContent = '.';
|
||||
dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.filterAction.filterText = '');
|
||||
dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => {
|
||||
if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) {
|
||||
|
||||