diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index cddd86631d..a40c461aa9 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -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); -}); \ No newline at end of file +}); diff --git a/build/win32/code.iss b/build/win32/code.iss index d13e4014e5..66b2053053 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -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 diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index f63c46a878..6fb25ae6ce 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -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|$|>)(?!.*?))", - "end": "(?=.*)", + "begin": "(?i)(^|\\G)\\s*(?=<(script|style|pre)(\\s|$|>)(?!.*?))", + "end": "(?i)(.*)(())", + "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": "^(?!.*)" + "while": "(?i)^(?!.*)" } ] }, { - "begin": "(^|\\G)\\s*(?=))", + "begin": "(?i)(^|\\G)\\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" } } -} +} \ No newline at end of file diff --git a/extensions/markdown-basics/test/colorize-results/test-33886_md.json b/extensions/markdown-basics/test/colorize-results/test-33886_md.json index 179172a573..25799e89a3 100644 --- a/extensions/markdown-basics/test/colorize-results/test-33886_md.json +++ b/extensions/markdown-basics/test/colorize-results/test-33886_md.json @@ -111,7 +111,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.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.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", diff --git a/package.json b/package.json index 94213a79a4..168a40f189 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/resources/win32/inno-big-100.bmp b/resources/win32/inno-big-100.bmp new file mode 100644 index 0000000000..99cf4ba666 Binary files /dev/null and b/resources/win32/inno-big-100.bmp differ diff --git a/resources/win32/inno-big.bmp b/resources/win32/inno-big-125.bmp old mode 100644 new mode 100755 similarity index 100% rename from resources/win32/inno-big.bmp rename to resources/win32/inno-big-125.bmp diff --git a/resources/win32/inno-big-150.bmp b/resources/win32/inno-big-150.bmp new file mode 100755 index 0000000000..554461982f Binary files /dev/null and b/resources/win32/inno-big-150.bmp differ diff --git a/resources/win32/inno-big-175.bmp b/resources/win32/inno-big-175.bmp new file mode 100755 index 0000000000..be0e7df91c Binary files /dev/null and b/resources/win32/inno-big-175.bmp differ diff --git a/resources/win32/inno-big-200.bmp b/resources/win32/inno-big-200.bmp new file mode 100755 index 0000000000..25ecb88e83 Binary files /dev/null and b/resources/win32/inno-big-200.bmp differ diff --git a/resources/win32/inno-big-225.bmp b/resources/win32/inno-big-225.bmp new file mode 100755 index 0000000000..89cf4efb1e Binary files /dev/null and b/resources/win32/inno-big-225.bmp differ diff --git a/resources/win32/inno-big-250.bmp b/resources/win32/inno-big-250.bmp new file mode 100644 index 0000000000..a791fb63ec Binary files /dev/null and b/resources/win32/inno-big-250.bmp differ diff --git a/resources/win32/inno-small-100.bmp b/resources/win32/inno-small-100.bmp new file mode 100755 index 0000000000..59f09fd813 Binary files /dev/null and b/resources/win32/inno-small-100.bmp differ diff --git a/resources/win32/inno-small-125.bmp b/resources/win32/inno-small-125.bmp new file mode 100755 index 0000000000..69b3b1fa2e Binary files /dev/null and b/resources/win32/inno-small-125.bmp differ diff --git a/resources/win32/inno-small-150.bmp b/resources/win32/inno-small-150.bmp new file mode 100755 index 0000000000..3f65efc5f4 Binary files /dev/null and b/resources/win32/inno-small-150.bmp differ diff --git a/resources/win32/inno-small-175.bmp b/resources/win32/inno-small-175.bmp new file mode 100755 index 0000000000..a11b53bcfe Binary files /dev/null and b/resources/win32/inno-small-175.bmp differ diff --git a/resources/win32/inno-small-200.bmp b/resources/win32/inno-small-200.bmp new file mode 100755 index 0000000000..c0ad5436b6 Binary files /dev/null and b/resources/win32/inno-small-200.bmp differ diff --git a/resources/win32/inno-small-225.bmp b/resources/win32/inno-small-225.bmp new file mode 100755 index 0000000000..9a974c4d9c Binary files /dev/null and b/resources/win32/inno-small-225.bmp differ diff --git a/resources/win32/inno-small-250.bmp b/resources/win32/inno-small-250.bmp new file mode 100755 index 0000000000..f32a791ea8 Binary files /dev/null and b/resources/win32/inno-small-250.bmp differ diff --git a/resources/win32/inno-small.bmp b/resources/win32/inno-small.bmp deleted file mode 100644 index 789fff066e..0000000000 Binary files a/resources/win32/inno-small.bmp and /dev/null differ diff --git a/src/buildfile.js b/src/buildfile.js index da10314e0a..fc52d8acb5 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -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'); diff --git a/src/sql/base/browser/ui/listBox/listBox.ts b/src/sql/base/browser/ui/listBox/listBox.ts index 244003bf60..7e22fa50c7 100644 --- a/src/sql/base/browser/ui/listBox/listBox.ts +++ b/src/sql/base/browser/ui/listBox/listBox.ts @@ -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' }; diff --git a/src/sql/base/browser/ui/selectBox/selectBox.ts b/src/sql/base/browser/ui/selectBox/selectBox.ts index 9a637af19a..2fb8f58cac 100644 --- a/src/sql/base/browser/ui/selectBox/selectBox.ts +++ b/src/sql/base/browser/ui/selectBox/selectBox.ts @@ -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' }; diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index da2ff41961..99be7ab34c 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -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) => { diff --git a/src/sql/workbench/parts/notebook/electron-browser/outputs/notebookMarkdown.ts b/src/sql/workbench/parts/notebook/electron-browser/outputs/notebookMarkdown.ts index d2de8f9d4c..5358c099a7 100644 --- a/src/sql/workbench/parts/notebook/electron-browser/outputs/notebookMarkdown.ts +++ b/src/sql/workbench/parts/notebook/electron-browser/outputs/notebookMarkdown.ts @@ -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 diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts new file mode 100644 index 0000000000..4d46725f78 --- /dev/null +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -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; + } +} diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/markdownRenderer.ts similarity index 50% rename from src/vs/base/browser/htmlContentRenderer.ts rename to src/vs/base/browser/markdownRenderer.ts index 631038c5b5..2878142f0b 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -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; 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; - } -} diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index e3d30c3ad5..b78389a77b 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -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' }; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index ae7f5aa370..936d83f9df 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -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'; diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 058a2f59ee..9305b25cae 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -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); +} diff --git a/src/vs/base/test/browser/htmlContent.test.ts b/src/vs/base/test/browser/formattedTextRenderer.test.ts similarity index 65% rename from src/vs/base/test/browser/htmlContent.test.ts rename to src/vs/base/test/browser/formattedTextRenderer.test.ts index 707e1bd5e2..baededd138 100644 --- a/src/vs/base/test/browser/htmlContent.test.ts +++ b/src/vs/base/test/browser/formattedTextRenderer.test.ts @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; -import * as marked from 'vs/base/common/marked/marked'; -import { renderMarkdown, renderText, renderFormattedText } from 'vs/base/browser/htmlContentRenderer'; +import { renderText, renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; import { DisposableStore } from 'vs/base/common/lifecycle'; -suite('HtmlContent', () => { +suite('FormattedTextRenderer', () => { const store = new DisposableStore(); setup(() => { @@ -101,36 +101,4 @@ suite('HtmlContent', () => { assert.strictEqual(result.children.length, 0); assert.strictEqual(result.innerHTML, '**bold**'); }); - test('image rendering conforms to default', () => { - const markdown = { value: `![image](someimageurl 'caption')` }; - const result: HTMLElement = renderMarkdown(markdown); - const renderer = new marked.Renderer(); - const imageFromMarked = marked(markdown.value, { - sanitize: true, - renderer - }).trim(); - assert.strictEqual(result.innerHTML, imageFromMarked); - }); - test('image rendering conforms to default without title', () => { - const markdown = { value: `![image](someimageurl)` }; - const result: HTMLElement = renderMarkdown(markdown); - const renderer = new marked.Renderer(); - const imageFromMarked = marked(markdown.value, { - sanitize: true, - renderer - }).trim(); - assert.strictEqual(result.innerHTML, imageFromMarked); - }); - test('image width from title params', () => { - let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` }); - assert.strictEqual(result.innerHTML, `

image

`); - }); - test('image height from title params', () => { - let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` }); - assert.strictEqual(result.innerHTML, `

image

`); - }); - test('image width and height from title params', () => { - let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` }); - assert.strictEqual(result.innerHTML, `

image

`); - }); }); diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts new file mode 100644 index 0000000000..8f48b0d690 --- /dev/null +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as marked from 'vs/base/common/marked/marked'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; + +suite('MarkdownRenderer', () => { + test('image rendering conforms to default', () => { + const markdown = { value: `![image](someimageurl 'caption')` }; + const result: HTMLElement = renderMarkdown(markdown); + const renderer = new marked.Renderer(); + const imageFromMarked = marked(markdown.value, { + sanitize: true, + renderer + }).trim(); + assert.strictEqual(result.innerHTML, imageFromMarked); + }); + + test('image rendering conforms to default without title', () => { + const markdown = { value: `![image](someimageurl)` }; + const result: HTMLElement = renderMarkdown(markdown); + const renderer = new marked.Renderer(); + const imageFromMarked = marked(markdown.value, { + sanitize: true, + renderer + }).trim(); + assert.strictEqual(result.innerHTML, imageFromMarked); + }); + + test('image width from title params', () => { + let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|width=100 'caption')` }); + assert.strictEqual(result.innerHTML, `

image

`); + }); + + test('image height from title params', () => { + let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=100 'caption')` }); + assert.strictEqual(result.innerHTML, `

image

`); + }); + + test('image width and height from title params', () => { + let result: HTMLElement = renderMarkdown({ value: `![image](someimageurl|height=200,width=100 'caption')` }); + assert.strictEqual(result.innerHTML, `

image

`); + }); +}); diff --git a/src/vs/base/worker/defaultWorkerFactory.ts b/src/vs/base/worker/defaultWorkerFactory.ts index 290b7a29f5..98f42104ce 100644 --- a/src/vs/base/worker/defaultWorkerFactory.ts +++ b/src/vs/base/worker/defaultWorkerFactory.ts @@ -19,26 +19,31 @@ function getWorker(workerId: string, label: string): Worker | Promise { // 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(obj: any): obj is PromiseLike { if (typeof obj.then === 'function') { return true; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index a77e6a61eb..b9cac66a00 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -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'; diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 7871f66421..e9696bbe29 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -49,7 +49,7 @@ export interface IPointerHandlerHelper { */ getLastViewCursorsRenderData(): IViewCursorRenderData[]; - shouldSuppressMouseDownOnViewZone(viewZoneId: number): boolean; + shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean; shouldSuppressMouseDownOnWidget(widgetId: string): boolean; /** diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 06776f19f0..c681bf6732 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -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; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index cba6e57afd..7439ec0622 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -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 */ diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index b69abee0ba..d537b9ea9e 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -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 { + dom.windowOpenNoOpener(encodeURI(resource.toString(true))); + + return Promise.resolve(true); + } } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 1f5f2286b9..2837bfe182 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -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; } diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index b86eabea57..143a5a291c 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -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; @@ -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; } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 2255370655..131acb5bc1 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -72,7 +72,7 @@ interface IDiffEditorWidgetStyle { } class VisualEditorState { - private _zones: number[]; + private _zones: string[]; private _zonesMap: { [zoneId: string]: boolean; }; private _decorations: string[]; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index c070748b1e..a918fd0591 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -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(); diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 822be751ea..673c178331 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -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); } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index b9e2f8995b..22635b7f4f 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -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 { diff --git a/src/vs/editor/common/viewLayout/whitespaceComputer.ts b/src/vs/editor/common/viewLayout/whitespaceComputer.ts index c920ea5a7e..914aaecd5e 100644 --- a/src/vs/editor/common/viewLayout/whitespaceComputer.ts +++ b/src/vs/editor/common/viewLayout/whitespaceComputer.ts @@ -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]; diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 3b15be4d78..18be24c1eb 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -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 */ diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index d12738caf7..6a95fba8fb 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -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[]; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 553f43a9de..24bc0f88ea 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -116,7 +116,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private readonly _replaceFocusTracker: dom.IFocusTracker; private readonly _replaceInputFocused: IContextKey; 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; })); diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index a2d4690521..5a94e63fb8 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -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, diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index 7ef44d80ad..91b7d162ec 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -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; diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index c67a35d98a..ff3455173e 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -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 () { diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 91e54ed2de..c63c66611d 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -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; diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index ef85a863ac..a118257414 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -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'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 43d11160fb..f32c6bc9d8 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -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; } /** diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 571f0c6513..c5d4278be3 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -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; -} \ No newline at end of file +} diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index a1fc24c8fe..d89410fb46 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -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 diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 1bc294ec2a..e5e67e3543 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -9,7 +9,6 @@ import { IDisposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); - export interface IOpener { open(resource: URI, options?: { openToSide?: boolean }): Promise; } @@ -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; + + /** + * Opens a URL externally. + * + * @param url A resource to open externally. + */ + openExternal(resource: URI): Promise; } 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); } }); diff --git a/src/vs/platform/product/browser/productService.ts b/src/vs/platform/product/browser/productService.ts index 1a46cd6412..fd2cf7d966 100644 --- a/src/vs/platform/product/browser/productService.ts +++ b/src/vs/platform/product/browser/productService.ts @@ -10,44 +10,18 @@ export class ProductService implements IProductService { _serviceBrand!: ServiceIdentifier; - 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 } { 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; } } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index b2ead4e836..d8eaf3de15 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -11,40 +11,7 @@ export interface IProductService { _serviceBrand: ServiceIdentifier; - readonly version: string; - readonly vscodeVersion: string; // {{SQL CARBON EDIT}} add vscode version - readonly recommendedExtensionsByScenario: { [area: string]: Array }; // {{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 }; // {{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 }; // {{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; diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts index 6e701ae2dd..9721daf86c 100644 --- a/src/vs/platform/product/node/productService.ts +++ b/src/vs/platform/product/node/productService.ts @@ -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; - 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 } { 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; } } diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index 20801da3e5..560e93dca5 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -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. } } diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts similarity index 100% rename from src/vs/platform/windows/node/windowsIpc.ts rename to src/vs/platform/windows/common/windowsIpc.ts diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index c53d69a0ae..1a910cd8c9 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -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; private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('windows'); - } - get onWindowOpen(): Event { return this.channel.listen('onWindowOpen'); } get onWindowFocus(): Event { return this.channel.listen('onWindowFocus'); } get onWindowBlur(): Event { return this.channel.listen('onWindowBlur'); } @@ -31,6 +28,10 @@ export class WindowsService implements IWindowsService { get onWindowUnmaximize(): Event { return this.channel.listen('onWindowUnmaximize'); } get onRecentlyOpenedChange(): Event { return this.channel.listen('onRecentlyOpenedChange'); } + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('windows'); + } + pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { return this.channel.call('pickFileFolderAndOpen', options); } diff --git a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts index b8e38f8f8a..ac02f14f9a 100644 --- a/src/vs/workbench/api/browser/mainThreadCodeInsets.ts +++ b/src/vs/workbench/api/browser/mainThreadCodeInsets.ts @@ -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; diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index d181d029dd..c718af569e 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -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'; diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 9dd58173fc..e473d27452 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -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 | undefined { diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 965359c212..077a47ea29 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -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 = { 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}`); } } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index 3ffbd8b2f6..8836b32bee 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -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([ - 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, values[0], values[1], activationTimesBuilder); }); } - protected abstract _loadCommonJSModule(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; + protected abstract _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise; private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { @@ -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; } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index edaa74be2f..3c3d0f010f 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -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(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + 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(modulePath); + r = require.__$__nodeRequire(module.fsPath); } catch (e) { return Promise.reject(e); } finally { diff --git a/src/vs/workbench/api/worker/extHostExtensionService.ts b/src/vs/workbench/api/worker/extHostExtensionService.ts index f2667aeea4..0ad4982620 100644 --- a/src/vs/workbench/api/worker/extHostExtensionService.ts +++ b/src/vs/workbench/api/worker/extHostExtensionService.ts @@ -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(modulePath: string, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { + protected _loadCommonJSModule(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise { - // 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 = 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(exports); } async $setRemoteEnvironment(env: { [key: string]: string | null }): Promise { 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; +} diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 2a00a80c24..32b29633a6 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -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; diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 21b70abd80..9babf58813 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -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): void; diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 1788c6e4b2..edffcc7172 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -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 { diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index af235b633b..d6132b940f 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -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 { 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 { - return { - enabled: false, - id: '', - state: ExperimentState.NoRun - }; + getTags(): Promise { + return Promise.resolve({}); } - async getExperimentsByType(type: ExperimentActionType): Promise { - return []; + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined { + return undefined; } - async getCuratedExtensionsList(curatedExtensionsKey: string): Promise { - return []; + getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise { + return Promise.resolve([]); } - markAsCompleted(experimentId: string): void { } - - onExperimentEnabled: Event = Event.None; - } -registerSingleton(IExperimentService, ExperimentService); +registerSingleton(IWorkspaceStatsService, WorkspaceStatsService); //#endregion diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 4716abf4b4..78308d5ba6 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -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; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index cc54ccbac8..90b06f46d5 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -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'; diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 99c38a87c6..b3851bcb9a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -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'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index f6ec5df703..cea2d6ba2c 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -543,9 +543,9 @@ class FunctionBreakpointInputRenderer implements IListRenderer { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { - return Promise.resolve(null); + return Promise.resolve(undefined); } const selection = breakpoint.endLineNumber ? { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index cf0997c6e6..66004f31e2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -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(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 })); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index b0572dd077..75a80f0ee5 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -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, diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index fe4fd729d4..66588efa42 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -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 } { diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts similarity index 100% rename from src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts rename to src/vs/workbench/contrib/experiments/browser/experimentalPrompt.ts diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts similarity index 79% rename from src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts rename to src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts index f7e0730926..fd13afb805 100644 --- a/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/browser/experiments.contribution.ts @@ -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); diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 449c4bc4d8..d88fffd288 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -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; } -export const IExperimentService = createDecorator('experimentService'); \ No newline at end of file +export const IExperimentService = createDecorator('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; + private _curatedMapping = Object.create(null); + + private readonly _onExperimentEnabled = this._register(new Emitter()); + onExperimentEnabled: Event = 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 { + return this._loadExperimentsPromise.then(() => { + return this._experiments.filter(x => x.id === id)[0]; + }); + } + + public getExperimentsByType(type: ExperimentActionType): Promise { + 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 { + 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 { + 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 { + 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) { + ((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 { + 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; + } +} diff --git a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts deleted file mode 100644 index a24a9f35fd..0000000000 --- a/src/vs/workbench/contrib/experiments/electron-browser/experimentService.ts +++ /dev/null @@ -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; - private _curatedMapping = Object.create(null); - - private readonly _onExperimentEnabled = this._register(new Emitter()); - onExperimentEnabled: Event = 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 { - return this._loadExperimentsPromise.then(() => { - return this._experiments.filter(x => x.id === id)[0]; - }); - } - - public getExperimentsByType(type: ExperimentActionType): Promise { - 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 { - 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 { - 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 { - 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) { - ((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 { - 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; - } -} diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 0c91a2337f..23207c76dd 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -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'; diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index f1f89fce44..8817cecef2 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -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'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index c03daece76..a836fcc061 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -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(() => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 79cc19ddc6..4e411ce202 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -435,7 +435,7 @@ export class ExtensionsListView extends ViewletPanel { // {{SQL CARBON EDIT}} if (this.productService) { let promiseRecommendedExtensionsByScenario: Promise> | 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]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index c72d62938f..f178284e92 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -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; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index 05922a0800..ddc6e307d7 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -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 => ({ 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 { const homeDir = os.homedir(); let foundExecutables: Set = new Set(); 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[] = []; // 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 => ({ extensionId, sources: ['application'] }))); } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 88308a656c..1f48f715f4 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -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', { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index f389a5ea57..c63e9f4a05 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -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'; diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index 76ad9b4801..aa9d9e015b 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -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 => { diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index e725cab005..5b10eb3202 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -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()); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 22107f7dfe..10a258a03a 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -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 { - 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"); } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index b883d0c4d0..4df07f409b 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -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])); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 157395c93e..2056a95568 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -503,7 +503,7 @@ export class GlobalCompareResourcesAction extends Action { override: this.editorService.openEditor({ leftResource: activeResource, rightResource: resource - }).then(() => null) + }).then(() => undefined) }; } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 40741dded7..38874db536 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -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 { diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 3702b34c62..89719cb974 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -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)) { diff --git a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts index f450babf49..79b1948318 100644 --- a/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts +++ b/src/vs/workbench/contrib/preferences/browser/keyboardLayoutPicker.ts @@ -158,9 +158,9 @@ export class KeyboardLayoutPickerAction extends Action { await this.fileService.resolve(file).then(undefined, (error) => { return this.fileService.createFile(file, VSBuffer.fromString(KeyboardLayoutPickerAction.DEFAULT_CONTENT)); - }).then((stat): Promise | null => { + }).then((stat): Promise | undefined => { if (!stat) { - return null; + return undefined; } return this.editorService.openEditor({ resource: stat.resource, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index e428e6df32..278357d9f7 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -225,7 +225,7 @@ export class OpenFolderSettingsAction extends Action { return this.preferencesService.openFolderSettings(workspaceFolder.uri); } - return null; + return undefined; }); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index f1eab6c64d..31d3292bf7 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -73,7 +73,7 @@ export class PreferencesSearchService extends Disposable implements IPreferences }; } else { return { - urlBase: this.productService.settingsSearchUrl + urlBase: this.productService.productConfiguration.settingsSearchUrl }; } } @@ -364,7 +364,7 @@ class RemoteSearchProvider implements ISearchProvider { const extensions = await this.installedExtensions; const filters = this.options.newExtensionsOnly ? [`diminish eq 'latest'`] : - this.getVersionFilters(extensions, this.productService.settingsSearchBuildId); + this.getVersionFilters(extensions, this.productService.productConfiguration.settingsSearchBuildId); const filterStr = filters .slice(filterPage * RemoteSearchProvider.MAX_REQUEST_FILTERS, (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS) @@ -563,4 +563,4 @@ export class SettingMatches { endColumn: setting.valueRange.startColumn + match.end + 1 }; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 0f68a89da9..ca50155931 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -36,7 +36,7 @@ import { ISettingsGroup } from 'vs/workbench/services/preferences/common/prefere export class SettingsHeaderWidget extends Widget implements IViewZone { - private id: number; + private id: string; private _domNode: HTMLElement; protected titleContainer: HTMLElement; @@ -121,7 +121,7 @@ export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget { export class SettingsGroupTitleWidget extends Widget implements IViewZone { - private id: number; + private id: string; private _afterLineNumber: number; private _domNode: HTMLElement; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 4eead4fe9a..b01c9291cc 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -466,12 +466,12 @@ export class SettingsEditor2 extends BaseEditor { } } - switchToSettingsFile(): Promise { + switchToSettingsFile(): Promise { const query = parseQuery(this.searchWidget.getValue()); return this.openSettingsFile(query.query); } - private openSettingsFile(query?: string): Promise { + private openSettingsFile(query?: string): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; const options: ISettingsEditorOptions = { query }; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 0e612186e0..71c234b6ce 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg new file mode 100644 index 0000000000..2673902c68 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-documentation-dark.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg new file mode 100644 index 0000000000..e8dc8205ba --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-documentation-hc.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg b/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg new file mode 100644 index 0000000000..4a3009baee --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-documentation-light.svg @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg new file mode 100644 index 0000000000..5d99408934 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-feedback-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg new file mode 100644 index 0000000000..941430e9dd --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-feedback-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg b/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg new file mode 100644 index 0000000000..72437202b7 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-feedback-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg new file mode 100644 index 0000000000..0ea65d8319 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-getting-started-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg new file mode 100644 index 0000000000..5bb05d3d8c --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-getting-started-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg b/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg new file mode 100644 index 0000000000..46cde7f7cc --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-getting-started-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg new file mode 100644 index 0000000000..0117ceb7de --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-report-issue-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg new file mode 100644 index 0000000000..b0c521b7dc --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-report-issue-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg b/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg new file mode 100644 index 0000000000..5da9322b6a --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-report-issue-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg new file mode 100644 index 0000000000..21eec9cbcb --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-review-issues-dark.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg new file mode 100644 index 0000000000..94013ea52a --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-review-issues-hc.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg b/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg new file mode 100644 index 0000000000..826d0eefbf --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/help-review-issues-light.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg b/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg new file mode 100644 index 0000000000..029e6b051c --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remote-activity-bar.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts new file mode 100644 index 0000000000..c367144aa5 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -0,0 +1,419 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./remoteViewlet'; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { URI } from 'vs/base/common/uri'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; +import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { Event } from 'vs/base/common/event'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; + +interface HelpInformation { + extensionDescription: IExtensionDescription; + getStarted?: string; + documentation?: string; + feedback?: string; + issues?: string; +} + +const remoteHelpExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'remoteHelp', + jsonSchema: { + description: nls.localize('RemoteHelpInformationExtPoint', 'Contributes help information for Remote'), + type: 'object', + properties: { + 'getStarted': { + description: nls.localize('RemoteHelpInformationExtPoint.getStarted', "The url to your project's Getting Started page"), + type: 'string' + }, + 'documentation': { + description: nls.localize('RemoteHelpInformationExtPoint.documentation', "The url to your project's documentation page"), + type: 'string' + }, + 'feedback': { + description: nls.localize('RemoteHelpInformationExtPoint.feedback', "The url to your project's feedback reporter"), + type: 'string' + }, + 'issues': { + description: nls.localize('RemoteHelpInformationExtPoint.issues', "The url to your project's issues list"), + type: 'string' + } + } + } +}); + +interface IViewModel { + helpInformations: HelpInformation[]; +} + +class HelpTreeVirtualDelegate implements IListVirtualDelegate { + getHeight(element: IHelpItem): number { + return 22; + } + + getTemplateId(element: IHelpItem): string { + return 'HelpItemTemplate'; + } +} + +interface IHelpItemTemplateData { + parent: HTMLElement; + icon: HTMLElement; +} + +class HelpTreeRenderer implements ITreeRenderer { + templateId: string = 'HelpItemTemplate'; + + renderTemplate(container: HTMLElement): IHelpItemTemplateData { + dom.addClass(container, 'remote-help-tree-node-item'); + + const icon = dom.append(container, dom.$('.remote-help-tree-node-item-icon')); + + const data = Object.create(null); + data.parent = container; + data.icon = icon; + + return data; + } + + renderElement(element: ITreeNode, index: number, templateData: IHelpItemTemplateData, height: number | undefined): void { + const container = templateData.parent; + dom.append(container, templateData.icon); + dom.addClass(templateData.icon, element.element.key); + const labelContainer = dom.append(container, dom.$('.help-item-label')); + labelContainer.innerText = element.element.label; + } + + disposeTemplate(templateData: IHelpItemTemplateData): void { + + } +} + +class HelpDataSource implements IAsyncDataSource { + hasChildren(element: any) { + return element instanceof HelpModel; + } + + getChildren(element: any) { + if (element instanceof HelpModel) { + return element.items; + } + + return []; + } +} + +interface IHelpItem { + key: string; + label: string; + handleClick(): Promise; +} + +class HelpItem implements IHelpItem { + constructor( + public key: string, + public label: string, + public values: { extensionDescription: IExtensionDescription; url: string }[], + private openerService: IOpenerService, + private quickInputService: IQuickInputService + ) { + } + + async handleClick() { + if (this.values.length > 1) { + let actions = this.values.map(value => { + return { + label: value.extensionDescription.displayName || value.extensionDescription.identifier.value, + description: value.url + }; + }); + + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtension', "Select url to open") }); + + if (action) { + await this.openerService.open(URI.parse(action.label)); + } + } else { + await this.openerService.open(URI.parse(this.values[0].url)); + } + } +} + +class IssueReporterItem implements IHelpItem { + constructor( + public key: string, + public label: string, + public extensionDescriptions: IExtensionDescription[], + private quickInputService: IQuickInputService, + private commandService: ICommandService + ) { + } + + async handleClick() { + if (this.extensionDescriptions.length > 1) { + let actions = this.extensionDescriptions.map(extension => { + return { + label: extension.displayName || extension.identifier.value, + identifier: extension.identifier + }; + }); + + const action = await this.quickInputService.pick(actions, { placeHolder: nls.localize('pickRemoteExtensionToReportIssue', "Select an extension to report issue") }); + + if (action) { + await this.commandService.executeCommand('workbench.action.openIssueReporter', [action.identifier.value]); + } + } else { + await this.commandService.executeCommand('workbench.action.openIssueReporter', [this.extensionDescriptions[0].identifier.value]); + } + } +} + +class HelpModel { + items: IHelpItem[]; + + constructor( + viewModel: IViewModel, + openerService: IOpenerService, + quickInputService: IQuickInputService, + commandService: ICommandService + ) { + let helpItems: IHelpItem[] = []; + const getStarted = viewModel.helpInformations.filter(info => info.getStarted); + + if (getStarted.length) { + helpItems.push(new HelpItem( + 'getStarted', + nls.localize('remote.help.getStarted', "Get Started"), + getStarted.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.getStarted! + })), + openerService, + quickInputService + )); + } + + const documentation = viewModel.helpInformations.filter(info => info.documentation); + + if (documentation.length) { + helpItems.push(new HelpItem( + 'documentation', + nls.localize('remote.help.documentation', "Read Documentation"), + documentation.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.documentation! + })), + openerService, + quickInputService + )); + } + + const feedback = viewModel.helpInformations.filter(info => info.feedback); + + if (feedback.length) { + helpItems.push(new HelpItem( + 'feedback', + nls.localize('remote.help.feedback', "Provide Feedback"), + feedback.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.feedback! + })), + openerService, + quickInputService + )); + } + + const issues = viewModel.helpInformations.filter(info => info.issues); + + if (issues.length) { + helpItems.push(new HelpItem( + 'issues', + nls.localize('remote.help.issues', "Review Issues"), + issues.map((info: HelpInformation) => ({ + extensionDescription: info.extensionDescription, + url: info.issues! + })), + openerService, + quickInputService + )); + } + + if (helpItems.length) { + helpItems.push(new IssueReporterItem( + 'issueReporter', + nls.localize('remote.help.report', "Report Issue"), + viewModel.helpInformations.map(info => info.extensionDescription), + quickInputService, + commandService + )); + } + + if (helpItems.length) { + this.items = helpItems; + } + } +} + +class HelpPanel extends ViewletPanel { + static readonly ID = '~remote.helpPanel'; + static readonly TITLE = nls.localize('remote.help', "Help and feedback"); + private tree!: WorkbenchAsyncDataTree; + + constructor( + protected viewModel: IViewModel, + options: IViewletPanelOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextKeyService protected contextKeyService: IContextKeyService, + @IConfigurationService protected configurationService: IConfigurationService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IOpenerService protected openerService: IOpenerService, + @IQuickInputService protected quickInputService: IQuickInputService, + @ICommandService protected commandService: ICommandService + + + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + } + + protected renderBody(container: HTMLElement): void { + dom.addClass(container, 'remote-help'); + const treeContainer = document.createElement('div'); + dom.addClass(treeContainer, 'remote-help-content'); + container.appendChild(treeContainer); + + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, + treeContainer, + new HelpTreeVirtualDelegate(), + [new HelpTreeRenderer()], + new HelpDataSource(), + { + keyboardSupport: true, + } + ); + + const model = new HelpModel(this.viewModel, this.openerService, this.quickInputService, this.commandService); + + this.tree.setInput(model); + + const helpItemNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: false, openOnSelection: false })); + + this._register(Event.debounce(helpItemNavigator.onDidOpenResource, (last, event) => event, 75, true)(e => { + e.element.handleClick(); + })); + } + + protected layoutBody(height: number, width: number): void { + this.tree.layout(height, width); + } +} + +class HelpPanelDescriptor implements IViewDescriptor { + readonly id = HelpPanel.ID; + readonly name = HelpPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly workspace = true; + + constructor(viewModel: IViewModel) { + this.ctorDescriptor = { ctor: HelpPanel, arguments: [viewModel] }; + } +} + + +export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { + private helpPanelDescriptor = new HelpPanelDescriptor(this); + + helpInformations: HelpInformation[] = []; + + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService + ) { + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + + remoteHelpExtPoint.setHandler((extensions) => { + let helpInformation: HelpInformation[] = []; + for (let extension of extensions) { + this._handleRemoteInfoExtensionPoint(extension, helpInformation); + } + + this.helpInformations = helpInformation; + + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + if (this.helpInformations.length) { + viewsRegistry.registerViews([this.helpPanelDescriptor], VIEW_CONTAINER); + } else { + viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); + } + }); + } + + private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { + if (!extension.value.documentation && !extension.value.feedback && !extension.value.getStarted && !extension.value.issues) { + return; + } + + helpInformation.push({ + extensionDescription: extension.description, + getStarted: extension.value.getStarted, + documentation: extension.value.documentation, + feedback: extension.value.feedback, + issues: extension.value.issues + }); + } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { + // too late, already added to the view model + return super.onDidAddViews(added); + } + + getTitle(): string { + const title = nls.localize('remote.explorer', "Remote Explorer"); + return title; + } +} + +Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( + RemoteViewlet, + VIEWLET_ID, + nls.localize('remote.explorer', "Remote Explorer"), + 'remote', + 4 +)); diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css new file mode 100644 index 0000000000..a278b060ec --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .activitybar>.content .monaco-action-bar .action-label.remote { + -webkit-mask: url('remote-activity-bar.svg') no-repeat 50% 50%; +} + +.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item { + display: flex; + height: 22px; + line-height: 22px; + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + flex-wrap: nowrap; +} + +.remote-help-content .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon { + background-size: 16px; + background-position: left center; + background-repeat: no-repeat; + padding-right: 6px; + width: 16px; + height: 22px; + -webkit-font-smoothing: antialiased; +} + +.remote-help-content .monaco-list .monaco-list-row .monaco-tl-twistie { + width: 0px !important; +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { + background-image: url('help-getting-started-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { + background-image: url('help-documentation-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { + background-image: url('help-feedback-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { + background-image: url('help-review-issues-light.svg') +} + +.vs .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { + background-image: url('help-report-issue-light.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { + background-image: url('help-getting-started-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { + background-image: url('help-documentation-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { + background-image: url('help-feedback-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { + background-image: url('help-review-issues-dark.svg') +} + +.vs-dark .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { + background-image: url('help-report-issue-dark.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.getStarted { + background-image: url('help-getting-started-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.documentation { + background-image: url('help-documentation-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.feedback { + background-image: url('help-feedback-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issues { + background-image: url('help-review-issues-hc.svg') +} + +.hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { + background-image: url('help-report-issue-hc.svg') +} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 7be158d995..e20289161a 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -17,6 +17,34 @@ import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/c import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; + +export const VIEWLET_ID = 'workbench.view.remote'; +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( + VIEWLET_ID, + true, + undefined, + { + getOrder: (group?: string) => { + if (!group) { + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + + let matches = /^targets@(\d+)$/.exec(group); + if (matches) { + return -1000; + } + + matches = /^details@(\d+)$/.exec(group); + + if (matches) { + return -500; + } + + return undefined; // {{SQL CARBON EDIT}} strict-null-checks + } + } +); export class LabelContribution implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index acb42d434d..38c5d1e192 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -1455,7 +1455,7 @@ export class SearchView extends ViewletPanel { this.openSettings('.exclude'); } - private openSettings(query: string): Promise { + private openSettings(query: string): Promise { const options: ISettingsEditorOptions = { query }; return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.preferencesService.openWorkspaceSettings(undefined, options) : diff --git a/src/vs/workbench/contrib/stats/common/workspaceStats.ts b/src/vs/workbench/contrib/stats/common/workspaceStats.ts new file mode 100644 index 0000000000..6b7016863e --- /dev/null +++ b/src/vs/workbench/contrib/stats/common/workspaceStats.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; + +export type Tags = { [index: string]: boolean | number | string | undefined }; + +export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); + +export interface IWorkspaceStatsService { + _serviceBrand: any; + + getTags(): Promise; + + /** + * Returns an id for the workspace, different from the id returned by the context service. A hash based + * on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces. + */ + getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined; + + getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit?: boolean): Promise; +} diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index 256ff6fb24..ced540f7ee 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -14,7 +14,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; +import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnosticsService'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; @@ -136,20 +136,6 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool }); } -export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, textFileService: ITextFileService, stripEndingDotGit: boolean = false): Promise { - const path = workspaceUri.path; - const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` }); - return fileService.exists(uri).then(exists => { - if (!exists) { - return []; - } - return textFileService.read(uri, { acceptTextOnly: true }).then( - content => getHashedRemotesFromConfig(content.value, stripEndingDotGit), - err => [] // ignore missing or binary file - ); - }); -} - export class WorkspaceStats implements IWorkbenchContribution { constructor( @@ -230,7 +216,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private reportRemotes(workspaceUris: URI[]): void { Promise.all(workspaceUris.map(workspaceUri => { - return getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true); + return this.workspaceStatsService.getHashedRemotesFromUri(workspaceUri, true); })).then(hashedRemotes => { /* __GDPR__ "workspace.hashedRemotes" : { diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts index a646548255..249fb6d95d 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStatsService.ts @@ -18,10 +18,9 @@ import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspa import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { joinPath } from 'vs/base/common/resources'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export type Tags = { [index: string]: boolean | number | string | undefined }; +import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; +import { getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/electron-browser/workspaceStats'; const DISABLE_WORKSPACE_PROMPT_KEY = 'workspaces.dontPromptToOpen'; @@ -93,20 +92,6 @@ const PyModulesToLookFor = [ 'botframework-connector' ]; -export const IWorkspaceStatsService = createDecorator('workspaceStatsService'); - -export interface IWorkspaceStatsService { - _serviceBrand: any; - getTags(): Promise; - - /** - * Returns an id for the workspace, different from the id returned by the context service. A hash based - * on the folder uri or workspace configuration, not time-based, and undefined for empty workspaces. - */ - getTelemetryWorkspaceId(workspace: IWorkspace, state: WorkbenchState): string | undefined; -} - - export class WorkspaceStatsService implements IWorkspaceStatsService { _serviceBrand: any; private _tags: Tags; @@ -152,6 +137,20 @@ export class WorkspaceStatsService implements IWorkspaceStatsService { return workspaceId; } + getHashedRemotesFromUri(workspaceUri: URI, stripEndingDotGit: boolean = false): Promise { + const path = workspaceUri.path; + const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` }); + return this.fileService.exists(uri).then(exists => { + if (!exists) { + return []; + } + return this.textFileService.read(uri, { acceptTextOnly: true }).then( + content => getHashedRemotesFromConfig(content.value, stripEndingDotGit), + err => [] // ignore missing or binary file + ); + }); + } + /* __GDPR__FRAGMENT__ "WorkspaceTags" : { "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 9ced7b2156..d14a2dd864 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -20,7 +20,7 @@ import * as panel from 'vs/workbench/browser/panel'; import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { AllowWorkspaceShellTerminalCommand, ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, DisallowWorkspaceShellTerminalCommand, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, ITerminalService, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -387,8 +387,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearTerminalAct mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_K } }, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KeybindingWeight.WorkbenchContrib + 1), 'Terminal: Clear', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(SelectDefaultShellWindowsTerminalAction, SelectDefaultShellWindowsTerminalAction.ID, SelectDefaultShellWindowsTerminalAction.LABEL), 'Terminal: Select Default Shell', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(AllowWorkspaceShellTerminalCommand, AllowWorkspaceShellTerminalCommand.ID, AllowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Allow Workspace Shell Configuration', category); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisallowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand.ID, DisallowWorkspaceShellTerminalCommand.LABEL), 'Terminal: Disallow Workspace Shell Configuration', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ManageWorkspaceShellPermissionsTerminalCommand, ManageWorkspaceShellPermissionsTerminalCommand.ID, ManageWorkspaceShellPermissionsTerminalCommand.LABEL), 'Terminal: Manage Workspace Shell Permissions', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RenameTerminalAction, RenameTerminalAction.ID, RenameTerminalAction.LABEL), 'Terminal: Rename', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalFindWidgetAction, FocusTerminalFindWidgetAction.ID, FocusTerminalFindWidgetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_F diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index ea096daffc..6f2c3348ac 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -993,10 +993,10 @@ export class ClearSelectionTerminalAction extends Action { } } -export class AllowWorkspaceShellTerminalCommand extends Action { +export class ManageWorkspaceShellPermissionsTerminalCommand extends Action { - public static readonly ID = TERMINAL_COMMAND_ID.WORKSPACE_SHELL_ALLOW; - public static readonly LABEL = nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration"); + public static readonly ID = TERMINAL_COMMAND_ID.MANAGE_WORKSPACE_SHELL_PERMISSIONS; + public static readonly LABEL = nls.localize('workbench.action.terminal.manageWorkspaceShellPermissions', "Manage Workspace Shell Permissions"); constructor( id: string, label: string, @@ -1005,27 +1005,8 @@ export class AllowWorkspaceShellTerminalCommand extends Action { super(id, label); } - public run(event?: any): Promise { - this.terminalService.setWorkspaceShellAllowed(true); - return Promise.resolve(undefined); - } -} - -export class DisallowWorkspaceShellTerminalCommand extends Action { - - public static readonly ID = TERMINAL_COMMAND_ID.WORKSPACE_SHELL_DISALLOW; - public static readonly LABEL = nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration"); - - constructor( - id: string, label: string, - @ITerminalService private readonly terminalService: ITerminalService - ) { - super(id, label); - } - - public run(event?: any): Promise { - this.terminalService.setWorkspaceShellAllowed(false); - return Promise.resolve(undefined); + public async run(event?: any): Promise { + await this.terminalService.manageWorkspaceShellPermissions(); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 3ae3694719..76327565de 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -226,7 +226,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(); this._configHelper.showRecommendations(shellLaunchConfig); const baseEnv = this._configHelper.config.inheritEnv ? process.env as platform.IProcessEnvironment : await this._terminalInstanceService.getMainProcessParentEnv(); - const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables, baseEnv); + const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.productConfiguration.version, this._configHelper.config.setLocaleVariables, baseEnv); const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled; return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c1518c06e2..6418393afd 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -279,7 +279,7 @@ export interface ITerminalService { selectDefaultWindowsShell(): Promise; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; - setWorkspaceShellAllowed(isAllowed: boolean): void; + manageWorkspaceShellPermissions(): void; /** * Takes a path and returns the properly escaped path to send to the terminal. diff --git a/src/vs/workbench/contrib/terminal/common/terminalCommands.ts b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts index 347d0b3072..6cbf276e25 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalCommands.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts @@ -48,8 +48,7 @@ export const enum TERMINAL_COMMAND_ID { SCROLL_TO_TOP = 'workbench.action.terminal.scrollToTop', CLEAR = 'workbench.action.terminal.clear', CLEAR_SELECTION = 'workbench.action.terminal.clearSelection', - WORKSPACE_SHELL_ALLOW = 'workbench.action.terminal.allowWorkspaceShell', - WORKSPACE_SHELL_DISALLOW = 'workbench.action.terminal.disallowWorkspaceShell', + MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions', RENAME = 'workbench.action.terminal.rename', FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget', FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget', diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index e343191a67..2ffbf54bab 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -479,22 +479,28 @@ export abstract class TerminalService implements ITerminalService { return terminalIndex; } - public setWorkspaceShellAllowed(isAllowed: boolean): void { - this.configHelper.setWorkspaceShellAllowed(isAllowed); + public async manageWorkspaceShellPermissions(): Promise { + const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration") }; + const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration") }; + const value = await this._quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); + if (!value) { + return; + } + this.configHelper.setWorkspaceShellAllowed(value === allowItem); } - protected _showTerminalCloseConfirmation(): Promise { - let message; + protected async _showTerminalCloseConfirmation(): Promise { + let message: string; if (this.terminalInstances.length === 1) { message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?"); } else { message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length); } - - return this._dialogService.confirm({ + const res = await this._dialogService.confirm({ message, type: 'warning', - }).then(res => !res.confirmed); + }); + return !res.confirmed; } protected _showNotEnoughSpaceToast(): void { diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index bd52911061..e8ba6f6e22 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -188,6 +188,11 @@ return; } + // Prevent middle clicks opening a broken link in the browser + if (event.button == 1) { + event.preventDefault(); + } + let baseElement = event.view.document.getElementsByTagName('base')[0]; /** @type {any} */ let node = event.target; @@ -443,7 +448,7 @@ }); // Bubble out link clicks - newFrame.contentWindow.addEventListener('click', handleInnerClick); + newFrame.contentWindow.addEventListener('mousedown', handleInnerClick); if (host.onIframeLoaded) { host.onIframeLoaded(newFrame); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md index 43e8bc83a8..ffd95c8971 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md @@ -77,6 +77,9 @@ new Book("The Martian", "Andy Weir"); /** * Represents a book. + * + * @param {string} title Title of the book + * @param {string} author Who wrote the book */ function Book(title, author) { this.title = title; @@ -84,7 +87,7 @@ function Book(title, author) { } ``` -> **JSDoc Tip:** The example above also showcased another way to get IntelliSense hints by using `JSDoc` comments. You can try this out by invoking the `Book` function and seeing the enhanced context in the IntelliSense menu for the function as well as parameters. +> **JSDoc Tip:** VS Code's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to `Book` or in IntelliSense when you create a new instance of `Book`. ### Refactoring via Extraction diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index b148745ef7..0cd0d5e7f7 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -220,11 +220,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise { // Typed Editor Support if (editor instanceof EditorInput) { @@ -243,11 +243,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return this.doOpenEditor(targetGroup, typedInput, editorOptions); } - return Promise.resolve(null); + return Promise.resolve(undefined); } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { - return group.openEditor(editor, options); + protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + return group.openEditor(editor, options).then(withNullAsUndefined); } private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): IEditorGroup { @@ -659,7 +659,7 @@ export class DelegatingEditorService extends EditorService { this.editorOpenHandler = handler; } - protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected async doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { if (!this.editorOpenHandler) { return super.doOpenEditor(group, editor, options); } diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 0824b4ed29..0a2c4d6af6 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -36,7 +36,7 @@ export interface IOpenEditorOverride { * If defined, will prevent the opening of an editor and replace the resulting * promise with the provided promise for the openEditor() call. */ - override?: Promise; + override?: Promise; } export interface IVisibleEditor extends IEditor { @@ -117,13 +117,13 @@ export interface IEditorService { * active group. Use `SIDE_GROUP_TYPE` to open the editor in a new editor group to the side * of the currently active group. * - * @returns the editor that opened or NULL if the operation failed or the editor was not + * @returns the editor that opened or `undefined` if the operation failed or the editor was not * opened to be active. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; /** * Open editors in an editor group. diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index 9e8e09f10b..06b6fa0c9c 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -20,6 +20,8 @@ import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEn import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { WebWorkerExtensionHostStarter } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter'; import { URI } from 'vs/base/common/uri'; +import { isWebExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -34,6 +36,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @IFileService fileService: IFileService, @IProductService productService: IProductService, @IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService, + @IConfigurationService private readonly _configService: IConfigurationService, ) { super( instantiationService, @@ -65,11 +68,14 @@ export class ExtensionService extends AbstractExtensionService implements IExten const remoteAgentConnection = this._remoteAgentService.getConnection()!; - const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, Promise.resolve([]), URI.parse('empty:value')); //todo@joh + const webExtensions = this.getExtensions().then(extensions => extensions.filter(ext => isWebExtension(ext, this._configService))); + const remoteExtensions = this.getExtensions().then(extensions => extensions.filter(ext => !isWebExtension(ext, this._configService))); + + const webHostProcessWorker = this._instantiationService.createInstance(WebWorkerExtensionHostStarter, true, webExtensions, URI.parse('empty:value')); //todo@joh const webHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, webHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); result.push(webHostProcessManager); - const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, this.getExtensions(), this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); + const remoteExtHostProcessWorker = this._instantiationService.createInstance(RemoteExtensionHostClient, remoteExtensions, this._createProvider(remoteAgentConnection.remoteAuthority), this._remoteAgentService.socketFactory); const remoteExtHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, false, remoteExtHostProcessWorker, remoteAgentConnection.remoteAuthority, initialActivationEvents); result.push(remoteExtHostProcessManager); diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts index d3e2491589..2ae647ca37 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory'; +import { getWorkerBootstrapUrl } from 'vs/base/worker/defaultWorkerFactory'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { VSBuffer } from 'vs/base/common/buffer'; import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -49,23 +49,29 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { if (!this._protocol) { const emitter = new Emitter(); - const worker = new DefaultWorkerFactory('WorkerExtensionHost').create( - 'vs/workbench/services/extensions/worker/extensionHostWorker', data => { - if (data instanceof ArrayBuffer) { - emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength))); - } else { - console.warn('UNKNOWN data received', data); - this._onDidExit.fire([77, 'UNKNOWN data received']); - } - }, err => { - this._onDidExit.fire([81, err]); - console.error(err); + + const url = getWorkerBootstrapUrl(require.toUrl('../worker/extensionHostWorkerMain.js'), 'WorkerExtensionHost'); + const worker = new Worker(url); + + worker.onmessage = (event) => { + const { data } = event; + if (!(data instanceof ArrayBuffer)) { + console.warn('UNKNOWN data received', data); + this._onDidExit.fire([77, 'UNKNOWN data received']); + return; } - ); + + emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength))); + }; + + worker.onerror = (event) => { + console.error(event.error); + this._onDidExit.fire([81, event.error]); + }; // keep for cleanup this._toDispose.add(emitter); - this._toDispose.add(worker); + this._toDispose.add(toDisposable(() => worker.terminate())); const protocol: IMessagePassingProtocol = { onMessage: emitter.event, @@ -111,16 +117,16 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { const [telemetryInfo, extensionDescriptions] = await Promise.all([this._telemetryService.getTelemetryInfo(), this._extensions]); const workspace = this._contextService.getWorkspace(); return { - commit: this._productService.commit, - version: this._productService.version, - vscodeVersion: this._productService.vscodeVersion, + commit: this._productService.productConfiguration.commit, + version: this._productService.productConfiguration.version, + vscodeVersion: this._productService.productConfiguration.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: -1, environment: { isExtensionDevelopmentDebug: false, appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined, - appName: this._productService.nameLong, - appUriScheme: this._productService.urlProtocol, + appName: this._productService.productConfiguration.nameLong, + appUriScheme: this._productService.productConfiguration.urlProtocol, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 9ffd299f00..bb16bfcd8a 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -462,12 +462,12 @@ class ProposedApiController { } this.enableProposedApiForAll = !environmentService.isBuilt || - (!!environmentService.extensionDevelopmentLocationURI && productService.nameLong !== 'Visual Studio Code') || + (!!environmentService.extensionDevelopmentLocationURI && productService.productConfiguration.nameLong !== 'Visual Studio Code') || (this.enableProposedApiFor.length === 0 && 'enable-proposed-api' in environmentService.args); this.productAllowProposedApi = new Set(); - if (isNonEmptyArray(productService.extensionAllowedProposedApi)) { - productService.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); + if (isNonEmptyArray(productService.productConfiguration.extensionAllowedProposedApi)) { + productService.productConfiguration.extensionAllowedProposedApi.forEach((id) => this.productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); } } diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index 9b4699fc75..9b902d4246 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -10,6 +10,11 @@ import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionM import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IProductService } from 'vs/platform/product/common/product'; +export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { + const extensionKind = getExtensionKind(manifest, configurationService); + return extensionKind === 'web'; +} + export function isUIExtension(manifest: IExtensionManifest, productService: IProductService, configurationService: IConfigurationService): boolean { const uiContributions = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); @@ -19,7 +24,7 @@ export function isUIExtension(manifest: IExtensionManifest, productService: IPro case 'workspace': return false; default: { // Tagged as UI extension in product - if (isNonEmptyArray(productService.uiExtensions) && productService.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { + if (isNonEmptyArray(productService.productConfiguration.uiExtensions) && productService.productConfiguration.uiExtensions.some(id => areSameExtensions({ id }, { id: extensionId }))) { return true; } // Not an UI extension if it has main diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 80a0df64f3..b159eb5299 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -71,7 +71,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH public start(): Promise { const options: IConnectionOptions = { - commit: this._productService.commit, + commit: this._productService.productConfiguration.commit, socketFactory: this._socketFactory, addressProvider: { getAddress: async () => { @@ -181,16 +181,16 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH const hostExtensions = allExtensions.filter(extension => extension.main && extension.api === 'none').map(extension => extension.identifier); const workspace = this._contextService.getWorkspace(); const r: IInitData = { - commit: this._productService.commit, - version: this._productService.version, - vscodeVersion: this._productService.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version + commit: this._productService.productConfiguration.commit, + version: this._productService.productConfiguration.version, + vscodeVersion: this._productService.productConfiguration.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: remoteExtensionHostData.pid, environment: { isExtensionDevelopmentDebug, appRoot: remoteExtensionHostData.appRoot, appSettingsHome: remoteExtensionHostData.appSettingsHome, - appName: this._productService.nameLong, - appUriScheme: this._productService.urlProtocol, + appName: this._productService.productConfiguration.nameLong, + appUriScheme: this._productService.productConfiguration.urlProtocol, appLanguage: platform.language, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index 8366a02073..c767f79187 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -69,7 +69,7 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC const installed = await this.getInstalled(ExtensionType.User); const compatible = await this.galleryService.getCompatibleExtension(extension); if (!compatible) { - return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.version))); + return Promise.reject(new Error(localize('incompatible', "Unable to install extension '{0}' as it is not compatible with VS Code '{1}'.", extension.identifier.id, this.productService.productConfiguration.version))); } const manifest = await this.galleryService.getManifest(compatible, CancellationToken.None); if (manifest) { @@ -140,4 +140,4 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC } return this.getDependenciesAndPackedExtensionsRecursively(toGet, result, uiExtension, token); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index e56f942196..84254e6761 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -313,11 +313,6 @@ class ExtensionManifestValidator extends ExtensionManifestHandler { extensionDescription.id = `${extensionDescription.publisher}.${extensionDescription.name}`; extensionDescription.identifier = new ExtensionIdentifier(extensionDescription.id); - // main := absolutePath(`main`) - if (extensionDescription.main) { - extensionDescription.main = path.join(this._absoluteFolderPath, extensionDescription.main); - } - extensionDescription.extensionLocation = URI.file(this._absoluteFolderPath); return extensionDescription; diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 1fa3ce99ce..81812dfc29 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -469,26 +469,24 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - // Not using await to work around minifier bug (https://github.com/microsoft/vscode/issues/79044). - return import('vscode-windows-ca-certs') - .then(winCA => { - let ders: any[] = []; - const store = winCA(); - try { - let der: any; - while (der = store.next()) { - ders.push(der); - } - } finally { - store.done(); - } + const winCA = await import('vscode-windows-ca-certs'); - const certs = new Set(ders.map(derToPem)); - return { - certs: Array.from(certs), - append: true - }; - }); + let ders: any[] = []; + const store = winCA(); + try { + let der: any; + while (der = store.next()) { + ders.push(der); + } + } finally { + store.done(); + } + + const certs = new Set(ders.map(derToPem)); + return { + certs: Array.from(certs), + append: true + }; } async function readMacCaCertificates() { diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts index 6906d62a21..d767e85362 100644 --- a/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorker.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; @@ -38,21 +37,18 @@ const hostUtil = new class implements IHostUtils { //todo@joh do not allow extensions to call postMessage and other globals... -class ExtensionWorker implements IRequestHandler { - - // worker-contract - readonly _requestHandlerBrand: any; - readonly onmessage: (data: any) => any; +class ExtensionWorker { // protocol readonly protocol: IMessagePassingProtocol; - constructor(postMessage: (message: any, transfer?: Transferable[]) => any) { + constructor() { let emitter = new Emitter(); let terminating = false; - this.onmessage = data => { + onmessage = event => { + const { data } = event; if (!(data instanceof ArrayBuffer)) { console.warn('UNKNOWN data received', data); return; @@ -98,8 +94,8 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise any): IRequestHandler { - const res = new ExtensionWorker(postMessage); +(function create(): void { + const res = new ExtensionWorker(); connectToRenderer(res.protocol).then(data => { @@ -112,6 +108,4 @@ export function create(postMessage: (message: any, transfer?: Transferable[]) => onTerminate = () => extHostMain.terminate(); }); - - return res; -} +})(); diff --git a/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts new file mode 100644 index 0000000000..66cc5c3f1a --- /dev/null +++ b/src/vs/workbench/services/extensions/worker/extensionHostWorkerMain.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +(function () { + + let MonacoEnvironment = (self).MonacoEnvironment; + let monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../../../'; + + if (typeof (self).define !== 'function' || !(self).define.amd) { + importScripts(monacoBaseUrl + 'vs/loader.js'); + } + + require.config({ + baseUrl: monacoBaseUrl, + catchError: true + }); + + require(['vs/workbench/services/extensions/worker/extensionHostWorker'], () => { }, err => console.error(err)); +})(); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index ac4ee5e312..b298364c60 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -453,7 +453,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.doNavigate(this.stack[this.index], !acrossEditors).finally(() => this.navigatingInStack = false); } - private doNavigate(location: IStackEntry, withSelection: boolean): Promise { + private doNavigate(location: IStackEntry, withSelection: boolean): Promise { const options: ITextEditorOptions = { revealIfOpened: true // support to navigate across editor groups }; diff --git a/src/vs/workbench/services/opener/electron-browser/openerService.ts b/src/vs/workbench/services/opener/electron-browser/openerService.ts new file mode 100644 index 0000000000..efba9695d2 --- /dev/null +++ b/src/vs/workbench/services/opener/electron-browser/openerService.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { OpenerService as BaseOpenerService } from 'vs/editor/browser/services/openerService'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + +export class OpenerService extends BaseOpenerService { + + _serviceBrand!: ServiceIdentifier; + + constructor( + @ICodeEditorService codeEditorService: ICodeEditorService, + @ICommandService commandService: ICommandService, + @IWindowsService private readonly windowsService: IWindowsService + ) { + super(codeEditorService, commandService); + } + + async openExternal(resource: URI): Promise { + const success = this.windowsService.openExternal(encodeURI(resource.toString(true))); + if (!success && resource.scheme === Schemas.file) { + await this.windowsService.showItemInFolder(resource); + + return true; + } + + return success; + } +} + +registerSingleton(IOpenerService, OpenerService, true); diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index c9d92d39dc..42bdc80cd8 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -38,6 +38,7 @@ import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSetti import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { withNullAsUndefined } from 'vs/base/common/types'; const emptyEditableSettingsContent = '{\n}'; @@ -188,15 +189,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic return null; } - openRawDefaultSettings(): Promise { + openRawDefaultSettings(): Promise { return this.editorService.openEditor({ resource: this.defaultSettingsRawResource }); } - openRawUserSettings(): Promise { + openRawUserSettings(): Promise { return this.editorService.openEditor({ resource: this.userSettingsResource }); } - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -217,7 +218,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic .then(() => this.editorGroupService.activeGroup.activeControl!); } - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -227,16 +228,16 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.USER_LOCAL, undefined, options, group); } - async openRemoteSettings(): Promise { + async openRemoteSettings(): Promise { const environment = await this.remoteAgentService.getEnvironment(); if (environment) { await this.createIfNotExists(environment.settingsPath, emptyEditableSettingsContent); return this.editorService.openEditor({ resource: environment.settingsPath, options: { pinned: true, revealIfOpened: true } }); } - return null; + return undefined; } - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -251,7 +252,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group); } - async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + async openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -306,7 +307,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => undefined); } - openDefaultKeybindingsFile(): Promise { + openDefaultKeybindingsFile(): Promise { return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } @@ -328,7 +329,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic })); } - private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { const editorInput = this.getActiveSettingsEditorInput(group); if (editorInput) { const editorInputResource = editorInput.master.getResource(); @@ -339,11 +340,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.doOpenSettings(configurationTarget, resource, options, group); } - private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { return this.doOpenSettings2(configurationTarget, folderUri, options, group); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); if (openSplitJSON) { return this.doOpenSplitJSON(configurationTarget, resource, options, group); @@ -365,14 +366,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic return Promise.all([ this.editorService.openEditor({ resource: this.defaultSettingsRawResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true }, label: nls.localize('defaultSettings', "Default Settings"), description: '' }), this.editorService.openEditor(editableSettingsEditorInput, { pinned: true, revealIfOpened: true }, sideEditorGroup.id) - ]).then(([defaultEditor, editor]) => editor); + ]).then(([defaultEditor, editor]) => withNullAsUndefined(editor)); } else { return this.editorService.openEditor(editableSettingsEditorInput, SettingsEditorOptions.create(options), group); } }); } - private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (!options) { @@ -392,7 +393,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); } - private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { const input = this.settingsEditor2Input; const settingsOptions: ISettingsEditorOptions = { ...options, @@ -633,4 +634,4 @@ export class PreferencesService extends Disposable implements IPreferencesServic } } -registerSingleton(IPreferencesService, PreferencesService); \ No newline at end of file +registerSingleton(IPreferencesService, PreferencesService); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 854188deb9..d536a475e9 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -201,15 +201,15 @@ export interface IPreferencesService { createPreferencesEditorModel(uri: URI): Promise | null>; createSettings2EditorModel(): Settings2EditorModel; // TODO - openRawDefaultSettings(): Promise; - openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openRemoteSettings(): Promise; - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRawDefaultSettings(): Promise; + openSettings(jsonEditor: boolean | undefined, query: string | undefined): Promise; + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRemoteSettings(): Promise; + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise; openGlobalKeybindingSettings(textual: boolean): Promise; - openDefaultKeybindingsFile(): Promise; + openDefaultKeybindingsFile(): Promise; configureSettingsForLanguage(language: string | null): void; } diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index b5345d054f..58f7d8e255 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -28,7 +28,7 @@ export class RemoteAgentService extends AbstractRemoteAgentService implements IR super(environmentService); this.socketFactory = new BrowserSocketFactory(webSocketFactory); - this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.commit, this.socketFactory, remoteAuthorityResolverService, signService)); + this._connection = this._register(new RemoteAgentConnection(environmentService.configuration.remoteAuthority!, productService.productConfiguration.commit, this.socketFactory, remoteAuthorityResolverService, signService)); } getConnection(): IRemoteAgentConnection | null { diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index fb15d82c28..bc7f61b01b 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -79,11 +79,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - const aiKey = productService.aiConfig && productService.aiConfig.asimovKey; - if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry && !!aiKey) { + const aiKey = productService.productConfiguration.aiConfig && productService.productConfiguration.aiConfig.asimovKey; + if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.productConfiguration.enableTelemetry && !!aiKey) { const config: ITelemetryServiceConfig = { appender: combinedAppender(new WebTelemetryAppender(aiKey, logService), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.productConfiguration.commit, productService.productConfiguration.version, environmentService.configuration.machineId, environmentService.configuration.remoteAuthority), piiPaths: [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 91e12fbf2f..eef5d71bab 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -34,11 +34,11 @@ export class TelemetryService extends Disposable implements ITelemetryService { ) { super(); - if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { + if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.productConfiguration.enableTelemetry) { const channel = sharedProcessService.getChannel('telemetryAppender'); const config: ITelemetryServiceConfig = { appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), - commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, environmentService.configuration.machineId, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.productConfiguration.commit, productService.productConfiguration.version, environmentService.configuration.machineId, environmentService.installSourcePath, environmentService.configuration.remoteAuthority), piiPaths: environmentService.extensionsPath ? [environmentService.appRoot, environmentService.extensionsPath] : [environmentService.appRoot] }; diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index d597e4538c..63bcab0664 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -4,16 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { rimraf, RimRafMode, copy, readFile, exists } from 'vs/base/node/pfs'; @@ -36,13 +31,8 @@ import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; class ServiceAccessor { constructor( - @ILifecycleService public lifecycleService: TestLifecycleService, @ITextFileService public textFileService: TestTextFileService, - @IUntitledEditorService public untitledEditorService: IUntitledEditorService, - @IWindowsService public windowsService: TestWindowsService, - @IWorkspaceContextService public contextService: TestContextService, - @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService + @IUntitledEditorService public untitledEditorService: IUntitledEditorService ) { } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 5edebd3b79..ed9319b161 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -81,8 +81,6 @@ import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; @@ -103,7 +101,6 @@ import { DownloadService } from 'vs/platform/download/common/downloadService'; registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); registerSingleton(IContextViewService, ContextViewService, true); registerSingleton(IListService, ListService, true); -registerSingleton(IOpenerService, OpenerService, true); registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); registerSingleton(IMarkerService, MarkerService, true); @@ -196,6 +193,7 @@ import 'vs/workbench/contrib/relauncher/common/relauncher.contribution'; // Remote import 'vs/workbench/contrib/remote/common/remote.contribution'; +import 'vs/workbench/contrib/remote/browser/remote'; // Emmet // import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; {{SQL CARBON EDIT}} @@ -232,4 +230,7 @@ import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; // Outline import 'vs/workbench/contrib/outline/browser/outline.contribution'; +// Experiments +import 'vs/workbench/contrib/experiments/browser/experiments.contribution'; + //#endregion diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index f26571a796..20823844d4 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -49,6 +49,7 @@ import 'vs/workbench/services/extensionManagement/node/extensionManagementServic import 'vs/workbench/services/accessibility/node/accessibilityService'; import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; +import 'vs/workbench/services/opener/electron-browser/openerService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -263,9 +264,6 @@ import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contributi import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; -// Experiments -import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; - // Issues import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index b1d44d3599..812a61c2ce 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -54,6 +54,8 @@ import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuS import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { BackupFileService } from 'vs/workbench/services/backup/common/backupFileService'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; registerSingleton(IRequestService, RequestService, true); registerSingleton(IExtensionManagementService, ExtensionManagementService); @@ -63,6 +65,7 @@ registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(ILifecycleService, BrowserLifecycleService); registerSingleton(IContextMenuService, ContextMenuService); +registerSingleton(IOpenerService, OpenerService, true); //#endregion diff --git a/yarn.lock b/yarn.lock index 38deacd755..6016c1acfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4154,15 +4154,18 @@ gulp-tslint@^8.1.3: plugin-error "1.0.1" through "~2.3.8" -gulp-uglify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.0.tgz#0df0331d72a0d302e3e37e109485dddf33c6d1ca" - integrity sha1-DfAzHXKg0wLj434QlIXd3zPG0co= +gulp-uglify@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gulp-uglify/-/gulp-uglify-3.0.2.tgz#5f5b2e8337f879ca9dec971feb1b82a5a87850b0" + integrity sha512-gk1dhB74AkV2kzqPMQBLA3jPoIAPd/nlNzP2XMDSG8XZrqnlCiDGAqC+rZOumzFvB5zOphlFh6yr3lgcAb/OOg== dependencies: + array-each "^1.0.1" + extend-shallow "^3.0.2" gulplog "^1.0.0" has-gulplog "^0.1.0" - lodash "^4.13.1" + isobject "^3.0.1" make-error-cause "^1.1.1" + safe-buffer "^5.1.2" through2 "^2.0.0" uglify-js "^3.0.5" vinyl-sourcemaps-apply "^0.2.0" @@ -5622,7 +5625,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.13.1, lodash@^4.15.0, lodash@^4.3.0: +lodash@^4.15.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= @@ -9469,15 +9472,7 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" integrity sha1-ftUNXg+an7ClczeSWfKndFjVAZI= -uglify-es@^3.0.18: - version "3.1.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.1.9.tgz#6c82df628ac9eb7af9c61fd70c744a084abe6161" - integrity sha512-wVSiJKHDgDDFmxTVVvnbAH6IpamAFHYDI+5JvwPdaqIMnk8kRTX2JKwq1Fx7gb2+Jj5Dus8kzvIpKkWOMNU51w== - dependencies: - commander "~2.11.0" - source-map "~0.6.1" - -uglify-es@^3.3.4: +uglify-es@^3.3.4, uglify-es@^3.3.9: version "3.3.9" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" integrity sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==