mirror of
https://github.com/coder/code-server.git
synced 2026-06-02 18:39:33 +00:00
Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { ViewsWelcomeContribution } from 'vs/workbench/contrib/welcome/common/viewsWelcomeContribution';
|
||||
import { ViewsWelcomeExtensionPoint, viewsWelcomeExtensionPointDescriptor } from 'vs/workbench/contrib/welcome/common/viewsWelcomeExtensionPoint';
|
||||
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
|
||||
const extensionPoint = ExtensionsRegistry.registerExtensionPoint<ViewsWelcomeExtensionPoint>(viewsWelcomeExtensionPointDescriptor);
|
||||
|
||||
class WorkbenchConfigurationContribution {
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
instantiationService.createInstance(ViewsWelcomeContribution, extensionPoint);
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(WorkbenchConfigurationContribution, LifecyclePhase.Eventually);
|
||||
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewsWelcomeExtensionPoint, ViewWelcome, ViewIdentifierMap } from './viewsWelcomeExtensionPoint';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as ViewContainerExtensions, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
|
||||
|
||||
export class ViewsWelcomeContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private viewWelcomeContents = new Map<ViewWelcome, IDisposable>();
|
||||
|
||||
constructor(extensionPoint: IExtensionPoint<ViewsWelcomeExtensionPoint>) {
|
||||
super();
|
||||
|
||||
extensionPoint.setHandler((_, { added, removed }) => {
|
||||
for (const contribution of removed) {
|
||||
for (const welcome of contribution.value) {
|
||||
const disposable = this.viewWelcomeContents.get(welcome);
|
||||
|
||||
if (disposable) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const contribution of added) {
|
||||
for (const welcome of contribution.value) {
|
||||
const id = ViewIdentifierMap[welcome.view] ?? welcome.view;
|
||||
const { group, order } = parseGroupAndOrder(welcome, contribution);
|
||||
const disposable = viewsRegistry.registerViewWelcomeContent(id, {
|
||||
content: welcome.contents,
|
||||
when: ContextKeyExpr.deserialize(welcome.when),
|
||||
group,
|
||||
order
|
||||
});
|
||||
|
||||
this.viewWelcomeContents.set(welcome, disposable);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function parseGroupAndOrder(welcome: ViewWelcome, contribution: IExtensionPointUser<ViewsWelcomeExtensionPoint>): { group: string | undefined, order: number | undefined } {
|
||||
|
||||
let group: string | undefined;
|
||||
let order: number | undefined;
|
||||
if (welcome.group) {
|
||||
if (!contribution.description.enableProposedApi) {
|
||||
contribution.collector.warn(nls.localize('ViewsWelcomeExtensionPoint.proposedAPI', "The viewsWelcome contribution in '{0}' requires 'enableProposedApi' to be enabled.", contribution.description.identifier.value));
|
||||
return { group, order };
|
||||
}
|
||||
|
||||
const idx = welcome.group.lastIndexOf('@');
|
||||
if (idx > 0) {
|
||||
group = welcome.group.substr(0, idx);
|
||||
order = Number(welcome.group.substr(idx + 1)) || undefined;
|
||||
} else {
|
||||
group = welcome.group;
|
||||
}
|
||||
}
|
||||
return { group, order };
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
|
||||
export enum ViewsWelcomeExtensionPointFields {
|
||||
view = 'view',
|
||||
contents = 'contents',
|
||||
when = 'when',
|
||||
group = 'group',
|
||||
}
|
||||
|
||||
export interface ViewWelcome {
|
||||
readonly [ViewsWelcomeExtensionPointFields.view]: string;
|
||||
readonly [ViewsWelcomeExtensionPointFields.contents]: string;
|
||||
readonly [ViewsWelcomeExtensionPointFields.when]: string;
|
||||
readonly [ViewsWelcomeExtensionPointFields.group]: string;
|
||||
}
|
||||
|
||||
export type ViewsWelcomeExtensionPoint = ViewWelcome[];
|
||||
|
||||
export const ViewIdentifierMap: { [key: string]: string } = {
|
||||
'explorer': 'workbench.explorer.emptyView',
|
||||
'debug': 'workbench.debug.welcome',
|
||||
'scm': 'workbench.scm',
|
||||
};
|
||||
|
||||
const viewsWelcomeExtensionPointSchema = Object.freeze<IConfigurationPropertySchema>({
|
||||
type: 'array',
|
||||
description: nls.localize('contributes.viewsWelcome', "Contributed views welcome content. Welcome content will be rendered in tree based views whenever they have no meaningful content to display, ie. the File Explorer when no folder is open. Such content is useful as in-product documentation to drive users to use certain features before they are available. A good example would be a `Clone Repository` button in the File Explorer welcome view."),
|
||||
items: {
|
||||
type: 'object',
|
||||
description: nls.localize('contributes.viewsWelcome.view', "Contributed welcome content for a specific view."),
|
||||
required: [
|
||||
ViewsWelcomeExtensionPointFields.view,
|
||||
ViewsWelcomeExtensionPointFields.contents
|
||||
],
|
||||
properties: {
|
||||
[ViewsWelcomeExtensionPointFields.view]: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.view', "Target view identifier for this welcome content. Only tree based views are supported.")
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.view', "Target view identifier for this welcome content. Only tree based views are supported."),
|
||||
enum: Object.keys(ViewIdentifierMap)
|
||||
}
|
||||
]
|
||||
},
|
||||
[ViewsWelcomeExtensionPointFields.contents]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.contents', "Welcome content to be displayed. The format of the contents is a subset of Markdown, with support for links only."),
|
||||
},
|
||||
[ViewsWelcomeExtensionPointFields.when]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.when', "Condition when the welcome content should be displayed."),
|
||||
},
|
||||
[ViewsWelcomeExtensionPointFields.group]: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.viewsWelcome.view.group', "Group to which this welcome content belongs."),
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const viewsWelcomeExtensionPointDescriptor = {
|
||||
extensionPoint: 'viewsWelcome',
|
||||
jsonSchema: viewsWelcomeExtensionPointSchema
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 40 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,172 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .welcomeOverlay {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
font-size: 10px;
|
||||
transition: font-size .25s;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#workbench\.parts\.editor {
|
||||
transition: filter .25s, opacity .2s;
|
||||
}
|
||||
|
||||
.monaco-workbench.blur-background #workbench\.parts\.panel,
|
||||
.monaco-workbench.blur-background #workbench\.parts\.sidebar,
|
||||
.monaco-workbench.blur-background #workbench\.parts\.editor {
|
||||
filter: blur(2px);
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
.monaco-workbench.hc-black.blur-background #workbench\.parts\.panel,
|
||||
.monaco-workbench.hc-black.blur-background #workbench\.parts\.sidebar,
|
||||
.monaco-workbench.hc-black.blur-background #workbench\.parts\.editor {
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key > .label {
|
||||
padding: 0 1ex;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key > .shortcut {
|
||||
letter-spacing: 0.15em;
|
||||
font-size: 0.8125em;
|
||||
font-family: "Lucida Grande", sans-serif;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.explorer {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.search {
|
||||
position: absolute;
|
||||
top: 63px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.git {
|
||||
position: absolute;
|
||||
top: 113px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.debug {
|
||||
position: absolute;
|
||||
top: 163px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.extensions {
|
||||
position: absolute;
|
||||
top: 213px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.watermark {
|
||||
position: absolute;
|
||||
top: calc(50% + 30px);
|
||||
left: calc(50% + 30px);
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.watermark > .label {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
@media (max-height: 500px) {
|
||||
.monaco-workbench > .welcomeOverlay > .key.watermark {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.problems {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
left: 45px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.terminal {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.notifications {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.problems > .label,
|
||||
.monaco-workbench > .welcomeOverlay > .key.problems > .shortcut,
|
||||
.monaco-workbench > .welcomeOverlay > .key.notifications > .label,
|
||||
.monaco-workbench > .welcomeOverlay > .key.notifications > .shortcut {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.openfile {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
right: 45px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.openfile > .label {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.commandPalette {
|
||||
position: absolute;
|
||||
top: 210px;
|
||||
left: calc(50% - 52px);
|
||||
transition: top .25s, left .25s;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.commandPalette > .label {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .key.commandPalette > .shortcut {
|
||||
padding-left: 0.5em;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
.monaco-workbench > .welcomeOverlay > .commandPalettePlaceholder {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: calc(50% - 193px);
|
||||
width: 386px;
|
||||
height: 197px;
|
||||
background-image: url('media/commandpalette.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench.vs-dark > .welcomeOverlay > .commandPalettePlaceholder,
|
||||
.monaco-workbench.hc-black > .welcomeOverlay > .commandPalettePlaceholder {
|
||||
background-image: url('media/commandpalette-dark.svg');
|
||||
}
|
||||
@media screen and (max-width: 880px) {
|
||||
.monaco-workbench > .welcomeOverlay {
|
||||
font-size: 8px;
|
||||
}
|
||||
.monaco-workbench > .welcomeOverlay > .commandPalettePlaceholder {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench > .welcomeOverlay > .key.commandPalette {
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./welcomeOverlay';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickaccess/browser/commandsQuickAccess';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { textPreformatForeground, foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
interface Key {
|
||||
id: string;
|
||||
arrow?: string;
|
||||
label: string;
|
||||
command?: string;
|
||||
arrowLast?: boolean;
|
||||
withEditor?: boolean;
|
||||
}
|
||||
|
||||
const keys: Key[] = [
|
||||
{
|
||||
id: 'explorer',
|
||||
arrow: '\u2190', // ←
|
||||
label: localize('welcomeOverlay.explorer', "File explorer"),
|
||||
command: 'workbench.view.explorer'
|
||||
},
|
||||
{
|
||||
id: 'search',
|
||||
arrow: '\u2190', // ←
|
||||
label: localize('welcomeOverlay.search', "Search across files"),
|
||||
command: 'workbench.view.search'
|
||||
},
|
||||
{
|
||||
id: 'git',
|
||||
arrow: '\u2190', // ←
|
||||
label: localize('welcomeOverlay.git', "Source code management"),
|
||||
command: 'workbench.view.scm'
|
||||
},
|
||||
{
|
||||
id: 'debug',
|
||||
arrow: '\u2190', // ←
|
||||
label: localize('welcomeOverlay.debug', "Launch and debug"),
|
||||
command: 'workbench.view.debug'
|
||||
},
|
||||
{
|
||||
id: 'extensions',
|
||||
arrow: '\u2190', // ←
|
||||
label: localize('welcomeOverlay.extensions', "Manage extensions"),
|
||||
command: 'workbench.view.extensions'
|
||||
},
|
||||
// {
|
||||
// id: 'watermark',
|
||||
// arrow: '⤹',
|
||||
// label: localize('welcomeOverlay.watermark', "Command Hints"),
|
||||
// withEditor: false
|
||||
// },
|
||||
{
|
||||
id: 'problems',
|
||||
arrow: '\u2939', // ⤹
|
||||
label: localize('welcomeOverlay.problems', "View errors and warnings"),
|
||||
command: 'workbench.actions.view.problems'
|
||||
},
|
||||
{
|
||||
id: 'terminal',
|
||||
label: localize('welcomeOverlay.terminal', "Toggle integrated terminal"),
|
||||
command: 'workbench.action.terminal.toggleTerminal'
|
||||
},
|
||||
// {
|
||||
// id: 'openfile',
|
||||
// arrow: '⤸',
|
||||
// label: localize('welcomeOverlay.openfile', "File Properties"),
|
||||
// arrowLast: true,
|
||||
// withEditor: true
|
||||
// },
|
||||
{
|
||||
id: 'commandPalette',
|
||||
arrow: '\u2196', // ↖
|
||||
label: localize('welcomeOverlay.commandPalette', "Find and run all commands"),
|
||||
command: ShowAllCommandsAction.ID
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
arrow: '\u2935', // ⤵
|
||||
arrowLast: true,
|
||||
label: localize('welcomeOverlay.notifications', "Show notifications"),
|
||||
command: 'notifications.showList'
|
||||
}
|
||||
];
|
||||
|
||||
const OVERLAY_VISIBLE = new RawContextKey<boolean>('interfaceOverviewVisible', false);
|
||||
|
||||
let welcomeOverlay: WelcomeOverlay;
|
||||
|
||||
export class WelcomeOverlayAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.showInterfaceOverview';
|
||||
public static readonly LABEL = localize('welcomeOverlay', "User Interface Overview");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<void> {
|
||||
if (!welcomeOverlay) {
|
||||
welcomeOverlay = this.instantiationService.createInstance(WelcomeOverlay);
|
||||
}
|
||||
welcomeOverlay.show();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class HideWelcomeOverlayAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.hideInterfaceOverview';
|
||||
public static readonly LABEL = localize('hideWelcomeOverlay', "Hide Interface Overview");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<void> {
|
||||
if (welcomeOverlay) {
|
||||
welcomeOverlay.hide();
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
class WelcomeOverlay extends Disposable {
|
||||
|
||||
private _overlayVisible: IContextKey<boolean>;
|
||||
private _overlay!: HTMLElement;
|
||||
|
||||
constructor(
|
||||
@ILayoutService private readonly layoutService: ILayoutService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||
) {
|
||||
super();
|
||||
this._overlayVisible = OVERLAY_VISIBLE.bindTo(this._contextKeyService);
|
||||
this.create();
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
const offset = this.layoutService.offset?.top ?? 0;
|
||||
this._overlay = dom.append(this.layoutService.container, $('.welcomeOverlay'));
|
||||
this._overlay.style.top = `${offset}px`;
|
||||
this._overlay.style.height = `calc(100% - ${offset}px)`;
|
||||
this._overlay.style.display = 'none';
|
||||
this._overlay.tabIndex = -1;
|
||||
|
||||
this._register(dom.addStandardDisposableListener(this._overlay, 'click', () => this.hide()));
|
||||
this.commandService.onWillExecuteCommand(() => this.hide());
|
||||
|
||||
dom.append(this._overlay, $('.commandPalettePlaceholder'));
|
||||
|
||||
const editorOpen = !!this.editorService.visibleEditors.length;
|
||||
keys.filter(key => !('withEditor' in key) || key.withEditor === editorOpen)
|
||||
.forEach(({ id, arrow, label, command, arrowLast }) => {
|
||||
const div = dom.append(this._overlay, $(`.key.${id}`));
|
||||
if (arrow && !arrowLast) {
|
||||
dom.append(div, $('span.arrow', undefined, arrow));
|
||||
}
|
||||
dom.append(div, $('span.label')).textContent = label;
|
||||
if (command) {
|
||||
const shortcut = this.keybindingService.lookupKeybinding(command);
|
||||
if (shortcut) {
|
||||
dom.append(div, $('span.shortcut')).textContent = shortcut.getLabel();
|
||||
}
|
||||
}
|
||||
if (arrow && arrowLast) {
|
||||
dom.append(div, $('span.arrow', undefined, arrow));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public show() {
|
||||
if (this._overlay.style.display !== 'block') {
|
||||
this._overlay.style.display = 'block';
|
||||
const workbench = document.querySelector('.monaco-workbench') as HTMLElement;
|
||||
workbench.classList.add('blur-background');
|
||||
this._overlayVisible.set(true);
|
||||
this.updateProblemsKey();
|
||||
this.updateActivityBarKeys();
|
||||
this._overlay.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private updateProblemsKey() {
|
||||
const problems = document.querySelector(`footer[id="workbench.parts.statusbar"] .statusbar-item.left ${Codicon.warning.cssSelector}`);
|
||||
const key = this._overlay.querySelector('.key.problems') as HTMLElement;
|
||||
if (problems instanceof HTMLElement) {
|
||||
const target = problems.getBoundingClientRect();
|
||||
const bounds = this._overlay.getBoundingClientRect();
|
||||
const bottom = bounds.bottom - target.top + 3;
|
||||
const left = (target.left + target.right) / 2 - bounds.left;
|
||||
key.style.bottom = bottom + 'px';
|
||||
key.style.left = left + 'px';
|
||||
} else {
|
||||
key.style.bottom = '';
|
||||
key.style.left = '';
|
||||
}
|
||||
}
|
||||
|
||||
private updateActivityBarKeys() {
|
||||
const ids = ['explorer', 'search', 'git', 'debug', 'extensions'];
|
||||
const activityBar = document.querySelector('.activitybar .composite-bar');
|
||||
if (activityBar instanceof HTMLElement) {
|
||||
const target = activityBar.getBoundingClientRect();
|
||||
const bounds = this._overlay.getBoundingClientRect();
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const key = this._overlay.querySelector(`.key.${ids[i]}`) as HTMLElement;
|
||||
const top = target.top - bounds.top + 50 * i + 13;
|
||||
key.style.top = top + 'px';
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const key = this._overlay.querySelector(`.key.${ids[i]}`) as HTMLElement;
|
||||
key.style.top = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public hide() {
|
||||
if (this._overlay.style.display !== 'none') {
|
||||
this._overlay.style.display = 'none';
|
||||
const workbench = document.querySelector('.monaco-workbench') as HTMLElement;
|
||||
workbench.classList.remove('blur-background');
|
||||
this._overlayVisible.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions)
|
||||
.registerWorkbenchAction(SyncActionDescriptor.from(WelcomeOverlayAction), 'Help: User Interface Overview', CATEGORIES.Help.value);
|
||||
|
||||
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions)
|
||||
.registerWorkbenchAction(SyncActionDescriptor.from(HideWelcomeOverlayAction, { primary: KeyCode.Escape }, OVERLAY_VISIBLE), 'Help: Hide Interface Overview', CATEGORIES.Help.value);
|
||||
|
||||
// theming
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const key = theme.getColor(foreground);
|
||||
if (key) {
|
||||
collector.addRule(`.monaco-workbench > .welcomeOverlay > .key { color: ${key}; }`);
|
||||
}
|
||||
const backgroundColor = Color.fromHex(theme.type === 'light' ? '#FFFFFF85' : '#00000085');
|
||||
if (backgroundColor) {
|
||||
collector.addRule(`.monaco-workbench > .welcomeOverlay { background: ${backgroundColor}; }`);
|
||||
}
|
||||
const shortcut = theme.getColor(textPreformatForeground);
|
||||
if (shortcut) {
|
||||
collector.addRule(`.monaco-workbench > .welcomeOverlay > .key > .shortcut { color: ${shortcut}; }`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export default () => `
|
||||
<div class="welcomePageContainer">
|
||||
<div class="welcomePage" role="document">
|
||||
<div class="title">
|
||||
<h1 class="caption">${escape(localize('welcomePage.vscode', "Visual Studio Code"))}</h1>
|
||||
<p class="subtitle detail">${escape(localize({ key: 'welcomePage.editingEvolved', comment: ['Shown as subtitle on the Welcome page.'] }, "Editing evolved"))}</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="splash">
|
||||
<div class="section start">
|
||||
<h2 class="caption">${escape(localize('welcomePage.start', "Start"))}</h2>
|
||||
<ul>
|
||||
<li><a href="command:workbench.action.files.newUntitledFile">${escape(localize('welcomePage.newFile', "New file"))}</a></li>
|
||||
<li class="mac-only"><a href="command:workbench.action.files.openFileFolder">${escape(localize('welcomePage.openFolder', "Open folder..."))}</a></li>
|
||||
<li class="windows-only linux-only"><a href="command:workbench.action.files.openFolder">${escape(localize('welcomePage.openFolder', "Open folder..."))}</a></li>
|
||||
<li><a href="command:workbench.action.addRootFolder">${escape(localize('welcomePage.addWorkspaceFolder', "Add workspace folder..."))}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section recent">
|
||||
<h2 class="caption">${escape(localize('welcomePage.recent', "Recent"))}</h2>
|
||||
<ul class="list">
|
||||
<!-- Filled programmatically -->
|
||||
<li class="moreRecent"><a href="command:workbench.action.openRecent">${escape(localize('welcomePage.moreRecent', "More..."))}</a><span class="path detail if_shortcut" data-command="workbench.action.openRecent">(<span class="shortcut" data-command="workbench.action.openRecent"></span>)</span></li>
|
||||
</ul>
|
||||
<p class="none detail">${escape(localize('welcomePage.noRecentFolders', "No recent folders"))}</p>
|
||||
</div>
|
||||
<div class="section help">
|
||||
<h2 class="caption">${escape(localize('welcomePage.help', "Help"))}</h2>
|
||||
<ul>
|
||||
<li class="keybindingsReferenceLink"><a href="command:workbench.action.keybindingsReference">${escape(localize('welcomePage.keybindingsCheatsheet', "Printable keyboard cheatsheet"))}</a></li>
|
||||
<li><a href="command:workbench.action.openIntroductoryVideosUrl">${escape(localize('welcomePage.introductoryVideos', "Introductory videos"))}</a></li>
|
||||
<li><a href="command:workbench.action.openTipsAndTricksUrl">${escape(localize('welcomePage.tipsAndTricks', "Tips and Tricks"))}</a></li>
|
||||
<li><a href="command:workbench.action.openDocumentationUrl">${escape(localize('welcomePage.productDocumentation', "Product documentation"))}</a></li>
|
||||
<li><a href="https://github.com/microsoft/vscode">${escape(localize('welcomePage.gitHubRepository', "GitHub repository"))}</a></li>
|
||||
<li><a href="https://stackoverflow.com/questions/tagged/vscode?sort=votes&pageSize=50">${escape(localize('welcomePage.stackOverflow', "Stack Overflow"))}</a></li>
|
||||
<li><a href="command:workbench.action.openNewsletterSignupUrl">${escape(localize('welcomePage.newsletterSignup', "Join our Newsletter"))}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="showOnStartup"><input type="checkbox" id="showOnStartup" class="checkbox"> <label class="caption" for="showOnStartup">${escape(localize('welcomePage.showOnStartup', "Show welcome page on startup"))}</label></p>
|
||||
</div>
|
||||
<div class="commands">
|
||||
<div class="section customize">
|
||||
<h2 class="caption">${escape(localize('welcomePage.customize', "Customize"))}</h2>
|
||||
<div class="list">
|
||||
<div class="item showLanguageExtensions"><button data-href="command:workbench.extensions.action.showLanguageExtensions"><h3 class="caption">${escape(localize('welcomePage.installExtensionPacks', "Tools and languages"))}</h3> <span class="detail">${escape(localize('welcomePage.installExtensionPacksDescription', "Install support for {0} and {1}"))
|
||||
.replace('{0}', `<span class="extensionPackList"></span>`)
|
||||
.replace('{1}', `<a href="command:workbench.extensions.action.showLanguageExtensions" title="${localize('welcomePage.showLanguageExtensions', "Show more language extensions")}">${escape(localize('welcomePage.moreExtensions', "more"))}</a>`)}
|
||||
</span></button></div>
|
||||
<div class="item showRecommendedKeymapExtensions"><button data-href="command:workbench.extensions.action.showRecommendedKeymapExtensions"><h3 class="caption">${escape(localize('welcomePage.installKeymapDescription', "Settings and keybindings"))}</h3> <span class="detail">${escape(localize('welcomePage.installKeymapExtension', "Install the settings and keyboard shortcuts of {0} and {1}"))
|
||||
.replace('{0}', `<span class="keymapList"></span>`)
|
||||
.replace('{1}', `<a href="command:workbench.extensions.action.showRecommendedKeymapExtensions" title="${localize('welcomePage.showKeymapExtensions', "Show other keymap extensions")}">${escape(localize('welcomePage.others', "others"))}</a>`)}
|
||||
</span></button></div>
|
||||
<div class="item selectTheme"><button data-href="command:workbench.action.selectTheme"><h3 class="caption">${escape(localize('welcomePage.colorTheme', "Color theme"))}</h3> <span class="detail">${escape(localize('welcomePage.colorThemeDescription', "Make the editor and your code look the way you love"))}</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section learn">
|
||||
<h2 class="caption">${escape(localize('welcomePage.learn', "Learn"))}</h2>
|
||||
<div class="list">
|
||||
<div class="item showCommands"><button data-href="command:workbench.action.showCommands"><h3 class="caption">${escape(localize('welcomePage.showCommands', "Find and run all commands"))}</h3> <span class="detail">${escape(localize('welcomePage.showCommandsDescription', "Rapidly access and search commands from the Command Palette ({0})")).replace('{0}', '<span class="shortcut" data-command="workbench.action.showCommands"></span>')}</span></button></div>
|
||||
<div class="item showInterfaceOverview"><button data-href="command:workbench.action.showInterfaceOverview"><h3 class="caption">${escape(localize('welcomePage.interfaceOverview', "Interface overview"))}</h3> <span class="detail">${escape(localize('welcomePage.interfaceOverviewDescription', "Get a visual overlay highlighting the major components of the UI"))}</span></button></div>
|
||||
<div class="item showInteractivePlayground"><button data-href="command:workbench.action.showInteractivePlayground"><h3 class="caption">${escape(localize('welcomePage.interactivePlayground', "Interactive playground"))}</h3> <span class="detail">${escape(localize('welcomePage.interactivePlaygroundDescription', "Try out essential editor features in a short walkthrough"))}</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { WelcomePageContribution, WelcomePageAction, WelcomeInputFactory } from 'vs/workbench/contrib/welcome/page/browser/welcomePage';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
|
||||
.registerConfiguration({
|
||||
...workbenchConfigurationNodeBase,
|
||||
'properties': {
|
||||
'workbench.startupEditor': {
|
||||
'scope': ConfigurationScope.APPLICATION, // Make sure repositories cannot trigger opening a README for tracking.
|
||||
'type': 'string',
|
||||
'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'],
|
||||
'enumDescriptions': [
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page (default)."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty workspace)."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),
|
||||
],
|
||||
'default': 'welcomePage',
|
||||
'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.")
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(WelcomePageContribution, LifecyclePhase.Restored);
|
||||
|
||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions)
|
||||
.registerWorkbenchAction(SyncActionDescriptor.from(WelcomePageAction), 'Help: Welcome', CATEGORIES.Help.value);
|
||||
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputFactory(WelcomeInputFactory.ID, WelcomeInputFactory);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '1_welcome',
|
||||
command: {
|
||||
id: 'workbench.action.showWelcomePage',
|
||||
title: localize({ key: 'miWelcome', comment: ['&& denotes a mnemonic'] }, "&&Welcome")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
@@ -0,0 +1,248 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePageContainer {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage {
|
||||
width: 90%;
|
||||
max-width: 1200px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .row {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .row .section {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .row .splash {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .row .commands {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .row .commands .list {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage p {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .keyboard {
|
||||
font-family: "Lucida Grande", sans-serif;/* Keyboard shortcuts */
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage a:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage h1 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
font-weight: normal;
|
||||
font-size: 3.6em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .title {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
flex: 1 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .subtitle {
|
||||
margin-top: .8em;
|
||||
font-size: 2.6em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.monaco-workbench.hc-black .part.editor > .content .welcomePage .subtitle {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash,
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage h2 {
|
||||
font-weight: 200;
|
||||
margin-top: 17px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 1.9em;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .section {
|
||||
margin-bottom: 5em;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash ul {
|
||||
margin: 0;
|
||||
font-size: 1.3em;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash li {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage.emptyRecent .splash .recent .list {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .recent .none {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench .part.editor > .content .welcomePage.emptyRecent .splash .recent .none {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .recent li.moreRecent {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .recent .path {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .title,
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .showOnStartup {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .splash .showOnStartup > .checkbox {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item {
|
||||
margin: 7px 0px;
|
||||
}
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button {
|
||||
margin: 1px;
|
||||
padding: 12px 10px;
|
||||
width: calc(100% - 2px);
|
||||
height: 5em;
|
||||
font-size: 1.3em;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button > span {
|
||||
display: inline-block;
|
||||
width:100%;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button h3 {
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
margin-bottom: .25em;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.monaco-workbench.hc-black .part.editor > .content .welcomePage .commands .item button > h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button:focus {
|
||||
outline-style: solid;
|
||||
outline-width: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench.hc-black .part.editor > .content .welcomePage .commands .item button {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.monaco-workbench.hc-black .part.editor > .content .welcomePage .commands .item button:hover {
|
||||
outline-width: 1px;
|
||||
outline-style: dashed;
|
||||
outline-offset: -5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button .enabledExtension {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button .installExtension.installed {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench .part.editor > .content .welcomePage .commands .item button .enabledExtension.installed {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePageContainer.max-height-685px .title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-icons-enabled .show-file-icons .vs_code_welcome_page-name-file-icon.file-icon::before {
|
||||
content: ' ';
|
||||
background-image: url('../../../../browser/media/code-icon.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .welcomePage .mac-only,
|
||||
.monaco-workbench .part.editor > .content .welcomePage .windows-only,
|
||||
.monaco-workbench .part.editor > .content .welcomePage .linux-only {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench.mac .part.editor > .content .welcomePage .mac-only {
|
||||
display: initial;
|
||||
}
|
||||
.monaco-workbench.windows .part.editor > .content .welcomePage .windows-only {
|
||||
display: initial;
|
||||
}
|
||||
.monaco-workbench.linux .part.editor > .content .welcomePage .linux-only {
|
||||
display: initial;
|
||||
}
|
||||
.monaco-workbench.mac .part.editor > .content .welcomePage li.mac-only {
|
||||
display: list-item;
|
||||
}
|
||||
.monaco-workbench.windows .part.editor > .content .welcomePage li.windows-only {
|
||||
display: list-item;
|
||||
}
|
||||
.monaco-workbench.linux .part.editor > .content .welcomePage li.linux-only {
|
||||
display: list-item;
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./welcomePage';
|
||||
import 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
|
||||
import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
|
||||
import { ILifecycleService, StartupKind } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { splitName } from 'vs/base/common/labels';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
|
||||
import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
|
||||
const configurationKey = 'workbench.startupEditor';
|
||||
const oldConfigurationKey = 'workbench.welcome.enabled';
|
||||
const telemetryFrom = 'welcomePage';
|
||||
|
||||
export class WelcomePageContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IBackupFileService backupFileService: IBackupFileService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
const enabled = isWelcomePageEnabled(configurationService, contextService);
|
||||
if (enabled && lifecycleService.startupKind !== StartupKind.ReloadedWindow) {
|
||||
backupFileService.hasBackups().then(hasBackups => {
|
||||
// Open the welcome even if we opened a set of default editors
|
||||
if ((!editorService.activeEditor || layoutService.openedDefaultEditors) && !hasBackups) {
|
||||
const openWithReadme = configurationService.getValue(configurationKey) === 'readme';
|
||||
if (openWithReadme) {
|
||||
return Promise.all(contextService.getWorkspace().folders.map(folder => {
|
||||
const folderUri = folder.uri;
|
||||
return fileService.resolve(folderUri)
|
||||
.then(folder => {
|
||||
const files = folder.children ? folder.children.map(child => child.name).sort() : [];
|
||||
|
||||
const file = files.find(file => file.toLowerCase() === 'readme.md') || files.find(file => file.toLowerCase().startsWith('readme'));
|
||||
|
||||
if (file) {
|
||||
return joinPath(folderUri, file);
|
||||
}
|
||||
return undefined;
|
||||
}, onUnexpectedError);
|
||||
})).then(arrays.coalesce)
|
||||
.then<any>(readmes => {
|
||||
if (!editorService.activeEditor) {
|
||||
if (readmes.length) {
|
||||
const isMarkDown = (readme: URI) => readme.path.toLowerCase().endsWith('.md');
|
||||
return Promise.all([
|
||||
this.commandService.executeCommand('markdown.showPreview', null, readmes.filter(isMarkDown), { locked: true }),
|
||||
editorService.openEditors(readmes.filter(readme => !isMarkDown(readme))
|
||||
.map(readme => ({ resource: readme }))),
|
||||
]);
|
||||
} else {
|
||||
return instantiationService.createInstance(WelcomePage).openEditor();
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
} else {
|
||||
let options: IEditorOptions;
|
||||
let editor = editorService.activeEditor;
|
||||
if (editor) {
|
||||
// Ensure that the welcome editor won't get opened more than once
|
||||
if (editor.getTypeId() === welcomeInputTypeId || editorService.editors.some(e => e.getTypeId() === welcomeInputTypeId)) {
|
||||
return undefined;
|
||||
}
|
||||
options = { pinned: false, index: 0 };
|
||||
} else {
|
||||
options = { pinned: false };
|
||||
}
|
||||
return instantiationService.createInstance(WelcomePage).openEditor(options);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}).then(undefined, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isWelcomePageEnabled(configurationService: IConfigurationService, contextService: IWorkspaceContextService) {
|
||||
const startupEditor = configurationService.inspect(configurationKey);
|
||||
if (!startupEditor.userValue && !startupEditor.workspaceValue) {
|
||||
const welcomeEnabled = configurationService.inspect(oldConfigurationKey);
|
||||
if (welcomeEnabled.value !== undefined && welcomeEnabled.value !== null) {
|
||||
return welcomeEnabled.value;
|
||||
}
|
||||
}
|
||||
return startupEditor.value === 'welcomePage' || startupEditor.value === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY;
|
||||
}
|
||||
|
||||
export class WelcomePageAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.showWelcomePage';
|
||||
public static readonly LABEL = localize('welcomePage', "Welcome");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<void> {
|
||||
return this.instantiationService.createInstance(WelcomePage)
|
||||
.openEditor()
|
||||
.then(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtensionSuggestion {
|
||||
name: string;
|
||||
title?: string;
|
||||
id: string;
|
||||
isKeymap?: boolean;
|
||||
isCommand?: boolean;
|
||||
}
|
||||
|
||||
const extensionPacks: ExtensionSuggestion[] = [
|
||||
{ name: localize('welcomePage.javaScript', "JavaScript"), id: 'dbaeumer.vscode-eslint' },
|
||||
{ name: localize('welcomePage.python', "Python"), id: 'ms-python.python' },
|
||||
{ name: localize('welcomePage.java', "Java"), id: 'vscjava.vscode-java-pack' },
|
||||
{ name: localize('welcomePage.php', "PHP"), id: 'felixfbecker.php-pack' },
|
||||
{ name: localize('welcomePage.azure', "Azure"), title: localize('welcomePage.showAzureExtensions', "Show Azure extensions"), id: 'workbench.extensions.action.showAzureExtensions', isCommand: true },
|
||||
{ name: localize('welcomePage.docker', "Docker"), id: 'ms-azuretools.vscode-docker' },
|
||||
];
|
||||
|
||||
const keymapExtensions: ExtensionSuggestion[] = [
|
||||
{ name: localize('welcomePage.vim', "Vim"), id: 'vscodevim.vim', isKeymap: true },
|
||||
{ name: localize('welcomePage.sublime', "Sublime"), id: 'ms-vscode.sublime-keybindings', isKeymap: true },
|
||||
{ name: localize('welcomePage.atom', "Atom"), id: 'ms-vscode.atom-keybindings', isKeymap: true },
|
||||
];
|
||||
|
||||
interface Strings {
|
||||
installEvent: string;
|
||||
installedEvent: string;
|
||||
detailsEvent: string;
|
||||
|
||||
alreadyInstalled: string;
|
||||
reloadAfterInstall: string;
|
||||
installing: string;
|
||||
extensionNotFound: string;
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"installExtension" : {
|
||||
"${include}": [
|
||||
"${WelcomePageInstall-1}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
/* __GDPR__
|
||||
"installedExtension" : {
|
||||
"${include}": [
|
||||
"${WelcomePageInstalled-1}",
|
||||
"${WelcomePageInstalled-2}",
|
||||
"${WelcomePageInstalled-3}",
|
||||
"${WelcomePageInstalled-4}",
|
||||
"${WelcomePageInstalled-6}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
/* __GDPR__
|
||||
"detailsExtension" : {
|
||||
"${include}": [
|
||||
"${WelcomePageDetails-1}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
const extensionPackStrings: Strings = {
|
||||
installEvent: 'installExtension',
|
||||
installedEvent: 'installedExtension',
|
||||
detailsEvent: 'detailsExtension',
|
||||
|
||||
alreadyInstalled: localize('welcomePage.extensionPackAlreadyInstalled', "Support for {0} is already installed."),
|
||||
reloadAfterInstall: localize('welcomePage.willReloadAfterInstallingExtensionPack', "The window will reload after installing additional support for {0}."),
|
||||
installing: localize('welcomePage.installingExtensionPack', "Installing additional support for {0}..."),
|
||||
extensionNotFound: localize('welcomePage.extensionPackNotFound', "Support for {0} with id {1} could not be found."),
|
||||
};
|
||||
|
||||
CommandsRegistry.registerCommand('workbench.extensions.action.showAzureExtensions', accessor => {
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
return viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@sort:installs azure ');
|
||||
viewlet.focus();
|
||||
});
|
||||
});
|
||||
|
||||
/* __GDPR__
|
||||
"installKeymap" : {
|
||||
"${include}": [
|
||||
"${WelcomePageInstall-1}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
/* __GDPR__
|
||||
"installedKeymap" : {
|
||||
"${include}": [
|
||||
"${WelcomePageInstalled-1}",
|
||||
"${WelcomePageInstalled-2}",
|
||||
"${WelcomePageInstalled-3}",
|
||||
"${WelcomePageInstalled-4}",
|
||||
"${WelcomePageInstalled-6}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
/* __GDPR__
|
||||
"detailsKeymap" : {
|
||||
"${include}": [
|
||||
"${WelcomePageDetails-1}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
const keymapStrings: Strings = {
|
||||
installEvent: 'installKeymap',
|
||||
installedEvent: 'installedKeymap',
|
||||
detailsEvent: 'detailsKeymap',
|
||||
|
||||
alreadyInstalled: localize('welcomePage.keymapAlreadyInstalled', "The {0} keyboard shortcuts are already installed."),
|
||||
reloadAfterInstall: localize('welcomePage.willReloadAfterInstallingKeymap', "The window will reload after installing the {0} keyboard shortcuts."),
|
||||
installing: localize('welcomePage.installingKeymap', "Installing the {0} keyboard shortcuts..."),
|
||||
extensionNotFound: localize('welcomePage.keymapNotFound', "The {0} keyboard shortcuts with id {1} could not be found."),
|
||||
};
|
||||
|
||||
const welcomeInputTypeId = 'workbench.editors.welcomePageInput';
|
||||
|
||||
class WelcomePage extends Disposable {
|
||||
|
||||
readonly editorInput: WalkThroughInput;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IWorkspacesService private readonly workspacesService: IWorkspacesService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionRecommendationsService private readonly tipsService: IExtensionRecommendationsService,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
|
||||
) {
|
||||
super();
|
||||
this._register(lifecycleService.onShutdown(() => this.dispose()));
|
||||
|
||||
const recentlyOpened = this.workspacesService.getRecentlyOpened();
|
||||
const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions);
|
||||
const resource = FileAccess.asBrowserUri('./vs_code_welcome_page', require)
|
||||
.with({
|
||||
scheme: Schemas.walkThrough,
|
||||
query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page' })
|
||||
});
|
||||
this.editorInput = this.instantiationService.createInstance(WalkThroughInput, {
|
||||
typeId: welcomeInputTypeId,
|
||||
name: localize('welcome.title', "Welcome"),
|
||||
resource,
|
||||
telemetryFrom,
|
||||
onReady: (container: HTMLElement) => this.onReady(container, recentlyOpened, installedExtensions)
|
||||
});
|
||||
}
|
||||
|
||||
public openEditor(options: IEditorOptions = { pinned: false }) {
|
||||
return this.editorService.openEditor(this.editorInput, options);
|
||||
}
|
||||
|
||||
private onReady(container: HTMLElement, recentlyOpened: Promise<IRecentlyOpened>, installedExtensions: Promise<IExtensionStatus[]>): void {
|
||||
const enabled = isWelcomePageEnabled(this.configurationService, this.contextService);
|
||||
const showOnStartup = <HTMLInputElement>container.querySelector('#showOnStartup');
|
||||
if (enabled) {
|
||||
showOnStartup.setAttribute('checked', 'checked');
|
||||
}
|
||||
showOnStartup.addEventListener('click', e => {
|
||||
this.configurationService.updateValue(configurationKey, showOnStartup.checked ? 'welcomePage' : 'newUntitledFile', ConfigurationTarget.USER);
|
||||
});
|
||||
|
||||
const prodName = container.querySelector('.welcomePage .title .caption') as HTMLElement;
|
||||
if (prodName) {
|
||||
prodName.textContent = this.productService.nameLong;
|
||||
}
|
||||
|
||||
recentlyOpened.then(({ workspaces }) => {
|
||||
// Filter out the current workspace
|
||||
workspaces = workspaces.filter(recent => !this.contextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri));
|
||||
if (!workspaces.length) {
|
||||
const recent = container.querySelector('.welcomePage') as HTMLElement;
|
||||
recent.classList.add('emptyRecent');
|
||||
return;
|
||||
}
|
||||
const ul = container.querySelector('.recent ul');
|
||||
if (!ul) {
|
||||
return;
|
||||
}
|
||||
const moreRecent = ul.querySelector('.moreRecent')!;
|
||||
const workspacesToShow = workspaces.slice(0, 5);
|
||||
const updateEntries = () => {
|
||||
const listEntries = this.createListEntries(workspacesToShow);
|
||||
while (ul.firstChild) {
|
||||
ul.removeChild(ul.firstChild);
|
||||
}
|
||||
ul.append(...listEntries, moreRecent);
|
||||
};
|
||||
updateEntries();
|
||||
this._register(this.labelService.onDidChangeFormatters(updateEntries));
|
||||
}).then(undefined, onUnexpectedError);
|
||||
|
||||
this.addExtensionList(container, '.extensionPackList', extensionPacks, extensionPackStrings);
|
||||
this.addExtensionList(container, '.keymapList', keymapExtensions, keymapStrings);
|
||||
|
||||
this.updateInstalledExtensions(container, installedExtensions);
|
||||
this._register(this.instantiationService.invokeFunction(onExtensionChanged)(ids => {
|
||||
for (const id of ids) {
|
||||
if (container.querySelector(`.installExtension[data-extension="${id.id}"], .enabledExtension[data-extension="${id.id}"]`)) {
|
||||
const installedExtensions = this.instantiationService.invokeFunction(getInstalledExtensions);
|
||||
this.updateInstalledExtensions(container, installedExtensions);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) {
|
||||
return recents.map(recent => {
|
||||
let fullPath: string;
|
||||
let windowOpenable: IWindowOpenable;
|
||||
if (isRecentFolder(recent)) {
|
||||
windowOpenable = { folderUri: recent.folderUri };
|
||||
fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true });
|
||||
} else {
|
||||
fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true });
|
||||
windowOpenable = { workspaceUri: recent.workspace.configPath };
|
||||
}
|
||||
|
||||
const { name, parentPath } = splitName(fullPath);
|
||||
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
|
||||
a.innerText = name;
|
||||
a.title = fullPath;
|
||||
a.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath));
|
||||
a.href = 'javascript:void(0)';
|
||||
a.addEventListener('click', e => {
|
||||
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', {
|
||||
id: 'openRecentFolder',
|
||||
from: telemetryFrom
|
||||
});
|
||||
this.hostService.openWindow([windowOpenable], { forceNewWindow: e.ctrlKey || e.metaKey });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
li.appendChild(a);
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.classList.add('path');
|
||||
span.classList.add('detail');
|
||||
span.innerText = parentPath;
|
||||
span.title = fullPath;
|
||||
li.appendChild(span);
|
||||
|
||||
return li;
|
||||
});
|
||||
}
|
||||
|
||||
private addExtensionList(container: HTMLElement, listSelector: string, suggestions: ExtensionSuggestion[], strings: Strings) {
|
||||
const list = container.querySelector(listSelector);
|
||||
if (list) {
|
||||
suggestions.forEach((extension, i) => {
|
||||
if (i) {
|
||||
list.appendChild(document.createTextNode(localize('welcomePage.extensionListSeparator', ", ")));
|
||||
}
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.innerText = extension.name;
|
||||
a.title = extension.title || (extension.isKeymap ? localize('welcomePage.installKeymap', "Install {0} keymap", extension.name) : localize('welcomePage.installExtensionPack', "Install additional support for {0}", extension.name));
|
||||
if (extension.isCommand) {
|
||||
a.href = `command:${extension.id}`;
|
||||
list.appendChild(a);
|
||||
} else {
|
||||
a.classList.add('installExtension');
|
||||
a.setAttribute('data-extension', extension.id);
|
||||
a.href = 'javascript:void(0)';
|
||||
a.addEventListener('click', e => {
|
||||
this.installExtension(extension, strings);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
list.appendChild(a);
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.innerText = extension.name;
|
||||
span.title = extension.isKeymap ? localize('welcomePage.installedKeymap', "{0} keymap is already installed", extension.name) : localize('welcomePage.installedExtensionPack', "{0} support is already installed", extension.name);
|
||||
span.classList.add('enabledExtension');
|
||||
span.setAttribute('data-extension', extension.id);
|
||||
list.appendChild(span);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private installExtension(extensionSuggestion: ExtensionSuggestion, strings: Strings): void {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageInstall-1" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.installEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
});
|
||||
this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
|
||||
const installedExtension = extensions.find(extension => areSameExtensions(extension.identifier, { id: extensionSuggestion.id }));
|
||||
if (installedExtension && installedExtension.globallyEnabled) {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageInstalled-1" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.installedEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
outcome: 'already_enabled',
|
||||
});
|
||||
this.notificationService.info(strings.alreadyInstalled.replace('{0}', extensionSuggestion.name));
|
||||
return;
|
||||
}
|
||||
const foundAndInstalled = installedExtension ? Promise.resolve(installedExtension.local) : this.extensionGalleryService.query({ names: [extensionSuggestion.id], source: telemetryFrom }, CancellationToken.None)
|
||||
.then((result): null | Promise<ILocalExtension | null> => {
|
||||
const [extension] = result.firstPage;
|
||||
if (!extension) {
|
||||
return null;
|
||||
}
|
||||
return this.extensionManagementService.installFromGallery(extension)
|
||||
.then(() => this.extensionManagementService.getInstalled())
|
||||
.then(installed => {
|
||||
const local = installed.filter(i => areSameExtensions(extension.identifier, i.identifier))[0];
|
||||
// TODO: Do this as part of the install to avoid multiple events.
|
||||
return this.extensionEnablementService.setEnablement([local], EnablementState.DisabledGlobally).then(() => local);
|
||||
});
|
||||
});
|
||||
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
strings.reloadAfterInstall.replace('{0}', extensionSuggestion.name),
|
||||
[{
|
||||
label: localize('ok', "OK"),
|
||||
run: () => {
|
||||
const messageDelay = new TimeoutTimer();
|
||||
messageDelay.cancelAndSet(() => {
|
||||
this.notificationService.info(strings.installing.replace('{0}', extensionSuggestion.name));
|
||||
}, 300);
|
||||
const extensionsToDisable = extensions.filter(extension => isKeymapExtension(this.tipsService, extension) && extension.globallyEnabled).map(extension => extension.local);
|
||||
extensionsToDisable.length ? this.extensionEnablementService.setEnablement(extensionsToDisable, EnablementState.DisabledGlobally) : Promise.resolve()
|
||||
.then(() => {
|
||||
return foundAndInstalled.then(foundExtension => {
|
||||
messageDelay.cancel();
|
||||
if (foundExtension) {
|
||||
return this.extensionEnablementService.setEnablement([foundExtension], EnablementState.EnabledGlobally)
|
||||
.then(() => {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageInstalled-2" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.installedEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
outcome: installedExtension ? 'enabled' : 'installed',
|
||||
});
|
||||
return this.hostService.reload();
|
||||
});
|
||||
} else {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageInstalled-3" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.installedEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
outcome: 'not_found',
|
||||
});
|
||||
this.notificationService.error(strings.extensionNotFound.replace('{0}', extensionSuggestion.name).replace('{1}', extensionSuggestion.id));
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}).then(undefined, err => {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageInstalled-4" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.installedEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
outcome: isPromiseCanceledError(err) ? 'canceled' : 'error',
|
||||
});
|
||||
this.notificationService.error(err);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: localize('details', "Details"),
|
||||
run: () => {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageDetails-1" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.detailsEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
});
|
||||
this.extensionsWorkbenchService.queryGallery({ names: [extensionSuggestion.id] }, CancellationToken.None)
|
||||
.then(result => this.extensionsWorkbenchService.open(result.firstPage[0]))
|
||||
.then(undefined, onUnexpectedError);
|
||||
}
|
||||
}]
|
||||
);
|
||||
}).then(undefined, err => {
|
||||
/* __GDPR__FRAGMENT__
|
||||
"WelcomePageInstalled-6" : {
|
||||
"from" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"extensionId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog(strings.installedEvent, {
|
||||
from: telemetryFrom,
|
||||
extensionId: extensionSuggestion.id,
|
||||
outcome: isPromiseCanceledError(err) ? 'canceled' : 'error',
|
||||
});
|
||||
this.notificationService.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
private updateInstalledExtensions(container: HTMLElement, installedExtensions: Promise<IExtensionStatus[]>) {
|
||||
installedExtensions.then(extensions => {
|
||||
const elements = container.querySelectorAll('.installExtension, .enabledExtension');
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].classList.remove('installed');
|
||||
}
|
||||
extensions.filter(ext => ext.globallyEnabled)
|
||||
.map(ext => ext.identifier.id)
|
||||
.forEach(id => {
|
||||
const install = container.querySelectorAll(`.installExtension[data-extension="${id}"]`);
|
||||
for (let i = 0; i < install.length; i++) {
|
||||
install[i].classList.add('installed');
|
||||
}
|
||||
const enabled = container.querySelectorAll(`.enabledExtension[data-extension="${id}"]`);
|
||||
for (let i = 0; i < enabled.length; i++) {
|
||||
enabled[i].classList.add('installed');
|
||||
}
|
||||
});
|
||||
}).then(undefined, onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
export class WelcomeInputFactory implements IEditorInputFactory {
|
||||
|
||||
static readonly ID = welcomeInputTypeId;
|
||||
|
||||
public canSerialize(editorInput: EditorInput): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): WalkThroughInput {
|
||||
return instantiationService.createInstance(WelcomePage)
|
||||
.editorInput;
|
||||
}
|
||||
}
|
||||
|
||||
// theming
|
||||
|
||||
export const buttonBackground = registerColor('welcomePage.buttonBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonBackground', 'Background color for the buttons on the Welcome page.'));
|
||||
export const buttonHoverBackground = registerColor('welcomePage.buttonHoverBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonHoverBackground', 'Hover background color for the buttons on the Welcome page.'));
|
||||
export const welcomePageBackground = registerColor('welcomePage.background', { light: null, dark: null, hc: null }, localize('welcomePage.background', 'Background color for the Welcome page.'));
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const backgroundColor = theme.getColor(welcomePageBackground);
|
||||
if (backgroundColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePageContainer { background-color: ${backgroundColor}; }`);
|
||||
}
|
||||
const foregroundColor = theme.getColor(foreground);
|
||||
if (foregroundColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .caption { color: ${foregroundColor}; }`);
|
||||
}
|
||||
const descriptionColor = theme.getColor(descriptionForeground);
|
||||
if (descriptionColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .detail { color: ${descriptionColor}; }`);
|
||||
}
|
||||
const buttonColor = getExtraColor(theme, buttonBackground, { dark: 'rgba(0, 0, 0, .2)', extra_dark: 'rgba(200, 235, 255, .042)', light: 'rgba(0,0,0,.04)', hc: 'black' });
|
||||
if (buttonColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button { background: ${buttonColor}; }`);
|
||||
}
|
||||
const buttonHoverColor = getExtraColor(theme, buttonHoverBackground, { dark: 'rgba(200, 235, 255, .072)', extra_dark: 'rgba(200, 235, 255, .072)', light: 'rgba(0,0,0,.10)', hc: null });
|
||||
if (buttonHoverColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button:hover { background: ${buttonHoverColor}; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage a { color: ${link}; }`);
|
||||
}
|
||||
const activeLink = theme.getColor(textLinkActiveForeground);
|
||||
if (activeLink) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage a:hover,
|
||||
.monaco-workbench .part.editor > .content .welcomePage a:active { color: ${activeLink}; }`);
|
||||
}
|
||||
const focusColor = theme.getColor(focusBorder);
|
||||
if (focusColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage a:focus { outline-color: ${focusColor}; }`);
|
||||
}
|
||||
const border = theme.getColor(contrastBorder);
|
||||
if (border) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button { border-color: ${border}; }`);
|
||||
}
|
||||
const activeBorder = theme.getColor(activeContrastBorder);
|
||||
if (activeBorder) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePage .commands .item button:hover { outline-color: ${activeBorder}; }`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { BrowserTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BrowserTelemetryOptOut, LifecyclePhase.Eventually);
|
||||
@@ -0,0 +1,188 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { language, locale } from 'vs/base/common/platform';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
export abstract class AbstractTelemetryOptOut implements IWorkbenchContribution {
|
||||
|
||||
private static readonly TELEMETRY_OPT_OUT_SHOWN = 'workbench.telemetryOptOutShown';
|
||||
private privacyUrl: string | undefined;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IHostService private readonly hostService: IHostService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IExperimentService private readonly experimentService: IExperimentService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService
|
||||
) {
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, version: 1 });
|
||||
}
|
||||
|
||||
protected async handleTelemetryOptOut(): Promise<void> {
|
||||
if (this.productService.telemetryOptOutUrl && !this.storageService.get(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, StorageScope.GLOBAL)) {
|
||||
const experimentId = 'telemetryOptOut';
|
||||
|
||||
const [count, experimentState] = await Promise.all([this.getWindowCount(), this.experimentService.getExperimentById(experimentId)]);
|
||||
|
||||
if (!this.hostService.hasFocus && count > 1) {
|
||||
return; // return early if meanwhile another window opened (we only show the opt-out once)
|
||||
}
|
||||
|
||||
this.storageService.store(AbstractTelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL);
|
||||
|
||||
this.privacyUrl = this.productService.privacyStatementUrl || this.productService.telemetryOptOutUrl;
|
||||
|
||||
if (experimentState && experimentState.state === ExperimentState.Run && this.telemetryService.isOptedIn) {
|
||||
this.runExperiment(experimentId);
|
||||
return;
|
||||
}
|
||||
|
||||
const telemetryOptOutUrl = this.productService.telemetryOptOutUrl;
|
||||
if (telemetryOptOutUrl) {
|
||||
this.showTelemetryOptOut(telemetryOptOutUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private showTelemetryOptOut(telemetryOptOutUrl: string): void {
|
||||
const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, this.productService.telemetryOptOutUrl);
|
||||
const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, this.productService.telemetryOptOutUrl);
|
||||
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
this.telemetryService.isOptedIn ? optOutNotice : optInNotice,
|
||||
[{
|
||||
label: localize('telemetryOptOut.readMore', "Read More"),
|
||||
run: () => this.openerService.open(URI.parse(telemetryOptOutUrl))
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
}
|
||||
|
||||
protected abstract getWindowCount(): Promise<number>;
|
||||
|
||||
private runExperiment(experimentId: string) {
|
||||
const promptMessageKey = 'telemetryOptOut.optOutOption';
|
||||
const yesLabelKey = 'telemetryOptOut.OptIn';
|
||||
const noLabelKey = 'telemetryOptOut.OptOut';
|
||||
|
||||
let promptMessage = localize('telemetryOptOut.optOutOption', "Please help Microsoft improve Visual Studio Code by allowing the collection of usage data. Read our [privacy statement]({0}) for more details.", this.privacyUrl);
|
||||
let yesLabel = localize('telemetryOptOut.OptIn', "Yes, glad to help");
|
||||
let noLabel = localize('telemetryOptOut.OptOut', "No, thanks");
|
||||
|
||||
let queryPromise = Promise.resolve(undefined);
|
||||
if (locale && locale !== language && locale !== 'en' && locale.indexOf('en-') === -1) {
|
||||
queryPromise = this.galleryService.query({ text: `tag:lp-${locale}` }, CancellationToken.None).then(tagResult => {
|
||||
if (!tagResult || !tagResult.total) {
|
||||
return undefined;
|
||||
}
|
||||
const extensionToFetchTranslationsFrom = tagResult.firstPage.filter(e => e.publisher === 'MS-CEINTL' && e.name.indexOf('vscode-language-pack') === 0)[0] || tagResult.firstPage[0];
|
||||
if (!extensionToFetchTranslationsFrom.assets || !extensionToFetchTranslationsFrom.assets.coreTranslations.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale!)
|
||||
.then(translation => {
|
||||
const translationsFromPack: any = translation && translation.contents ? translation.contents['vs/workbench/contrib/welcome/telemetryOptOut/electron-browser/telemetryOptOut'] : {};
|
||||
if (!!translationsFromPack[promptMessageKey] && !!translationsFromPack[yesLabelKey] && !!translationsFromPack[noLabelKey]) {
|
||||
promptMessage = translationsFromPack[promptMessageKey].replace('{0}', this.privacyUrl) + ' (Please help Microsoft improve Visual Studio Code by allowing the collection of usage data.)';
|
||||
yesLabel = translationsFromPack[yesLabelKey] + ' (Yes)';
|
||||
noLabel = translationsFromPack[noLabelKey] + ' (No)';
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const logTelemetry = (optout?: boolean) => {
|
||||
type ExperimentsOptOutClassification = {
|
||||
optout?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
type ExperimentsOptOutEvent = {
|
||||
optout?: boolean;
|
||||
};
|
||||
this.telemetryService.publicLog2<ExperimentsOptOutEvent, ExperimentsOptOutClassification>('experiments:optout', typeof optout === 'boolean' ? { optout } : {});
|
||||
};
|
||||
|
||||
queryPromise.then(() => {
|
||||
this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
promptMessage,
|
||||
[
|
||||
{
|
||||
label: yesLabel,
|
||||
run: () => {
|
||||
logTelemetry(false);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: noLabel,
|
||||
run: async () => {
|
||||
logTelemetry(true);
|
||||
this.configurationService.updateValue('telemetry.enableTelemetry', false);
|
||||
await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['enable-crash-reporter'], value: false }], true);
|
||||
}
|
||||
}
|
||||
],
|
||||
{
|
||||
sticky: true,
|
||||
onCancel: logTelemetry
|
||||
}
|
||||
);
|
||||
this.experimentService.markAsCompleted(experimentId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserTelemetryOptOut extends AbstractTelemetryOptOut {
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IHostService hostService: IHostService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExperimentService experimentService: IExperimentService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService,
|
||||
@IProductService productService: IProductService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IJSONEditingService jsonEditingService: IJSONEditingService
|
||||
) {
|
||||
super(storageService, storageKeysSyncRegistryService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService);
|
||||
|
||||
this.handleTelemetryOptOut();
|
||||
}
|
||||
|
||||
protected async getWindowCount(): Promise<number> {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { NativeTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/electron-sandbox/telemetryOptOut';
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeTelemetryOptOut, LifecyclePhase.Eventually);
|
||||
@@ -0,0 +1,46 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IExperimentService } from 'vs/workbench/contrib/experiments/common/experimentService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { AbstractTelemetryOptOut } from 'vs/workbench/contrib/welcome/telemetryOptOut/browser/telemetryOptOut';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
export class NativeTelemetryOptOut extends AbstractTelemetryOptOut {
|
||||
|
||||
constructor(
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IHostService hostService: IHostService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IExperimentService experimentService: IExperimentService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IExtensionGalleryService galleryService: IExtensionGalleryService,
|
||||
@IProductService productService: IProductService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IJSONEditingService jsonEditingService: IJSONEditingService,
|
||||
@INativeHostService private readonly nativeHostService: INativeHostService
|
||||
) {
|
||||
super(storageService, storageKeysSyncRegistryService, openerService, notificationService, hostService, telemetryService, experimentService, configurationService, galleryService, productService, environmentService, jsonEditingService);
|
||||
|
||||
this.handleTelemetryOptOut();
|
||||
}
|
||||
|
||||
protected getWindowCount(): Promise<number> {
|
||||
return this.nativeHostService.getWindowCount();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor';
|
||||
|
||||
const typeId = 'workbench.editors.walkThroughInput';
|
||||
const inputOptions: WalkThroughInputOptions = {
|
||||
typeId,
|
||||
name: localize('editorWalkThrough.title', "Interactive Playground"),
|
||||
resource: FileAccess.asBrowserUri('./vs_code_editor_walkthrough.md', require)
|
||||
.with({
|
||||
scheme: Schemas.walkThrough,
|
||||
query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough' })
|
||||
}),
|
||||
telemetryFrom: 'walkThrough'
|
||||
};
|
||||
|
||||
export class EditorWalkThroughAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.showInteractivePlayground';
|
||||
public static readonly LABEL = localize('editorWalkThrough', "Interactive Playground");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<void> {
|
||||
const input = this.instantiationService.createInstance(WalkThroughInput, inputOptions);
|
||||
return this.editorService.openEditor(input, { pinned: true })
|
||||
.then(() => void (0));
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorWalkThroughInputFactory implements IEditorInputFactory {
|
||||
|
||||
static readonly ID = typeId;
|
||||
|
||||
public canSerialize(editorInput: EditorInput): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): WalkThroughInput {
|
||||
return instantiationService.createInstance(WalkThroughInput, inputOptions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export default () => `
|
||||
## Interactive Editor Playground
|
||||
The core editor in VS Code is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for VS Code and more head over to our [documentation](command:workbench.action.openDocumentationUrl).
|
||||
|
||||
* [Multi-cursor Editing](#multi-cursor-editing) - block selection, select all occurrences, add additional cursors and more
|
||||
* [IntelliSense](#intellisense) - get code assistance and parameter suggestions for your code and external modules.
|
||||
* [Line Actions](#line-actions) - quickly move lines around to re-order your code.
|
||||
* [Rename Refactoring](#rename-refactoring) - quickly rename symbols across your code base.
|
||||
* [Formatting](#formatting) - keep your code looking great with inbuilt document & selection formatting.
|
||||
* [Code Folding](#code-folding) - focus on the most relevant parts of your code by folding other areas.
|
||||
* [Errors and Warnings](#errors-and-warnings) - see errors and warning as you type.
|
||||
* [Snippets](#snippets) - spend less time typing with snippets.
|
||||
* [Emmet](#emmet) - integrated Emmet support takes HTML and CSS editing to the next level.
|
||||
* [JavaScript Type Checking](#javascript-type-checking) - perform type checking on your JavaScript file using TypeScript with zero configuration.
|
||||
|
||||
|
||||
|
||||
### Multi-Cursor Editing
|
||||
Using multiple cursors allows you to edit multiple parts of the document at once, greatly improving your productivity. Try the following actions in the code block below:
|
||||
1. Box Selection - press <span class="mac-only windows-only">any combination of kb(cursorColumnSelectDown), kb(cursorColumnSelectRight), kb(cursorColumnSelectUp), kb(cursorColumnSelectLeft) to select a block of text. You can also press</span> <span class="shortcut mac-only">|⇧⌥|</span><span class="shortcut windows-only linux-only">|Shift+Alt|</span> while selecting text with the mouse or drag-select using the middle mouse button.
|
||||
2. Add a cursor - press kb(editor.action.insertCursorAbove) to add a new cursor above, or kb(editor.action.insertCursorBelow) to add a new cursor below. You can also use your mouse with <span class="shortcut"><span class="multi-cursor-modifier"></span>+Click</span> to add a cursor anywhere.
|
||||
3. Create cursors on all occurrences of a string - select one instance of a string e.g. |background-color| and press kb(editor.action.selectHighlights). Now you can replace all instances by simply typing.
|
||||
|
||||
That is the tip of the iceberg for multi-cursor editing. Have a look at the selection menu and our handy [keyboard reference guide](command:workbench.action.keybindingsReference) for additional actions.
|
||||
|
||||
|||css
|
||||
#p1 {background-color: #ff0000;} /* red in HEX format */
|
||||
#p2 {background-color: hsl(120, 100%, 50%);} /* green in HSL format */
|
||||
#p3 {background-color: rgba(0, 4, 255, 0.733);} /* blue with alpha channel in RGBA format */
|
||||
|||
|
||||
|
||||
> **CSS Tip:** you may have noticed in the example above we also provide color swatches inline for CSS, additionally if you hover over an element such as |#p1| we will show how this is represented in HTML. These swatches also act as color pickers that allow you to easily change a color value. A simple example of some language-specific editor features.
|
||||
|
||||
### IntelliSense
|
||||
|
||||
Visual Studio Code comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor in front of the error underline, right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestion comes from the Request API.
|
||||
|
||||
|||js
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send(|Hello \${req.}|);
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
|||
|
||||
|
||||
>**Tip:** while we ship JavaScript and TypeScript support out of the box other languages can be upgraded with better IntelliSense through one of the many [extensions](command:workbench.extensions.action.showPopularExtensions).
|
||||
|
||||
|
||||
### Line Actions
|
||||
Since it's very common to work with the entire text in a line we provide a set of useful shortcuts to help with this.
|
||||
1. <span class="mac-only windows-only">Copy a line and insert it above or below the current position with kb(editor.action.copyLinesDownAction) or kb(editor.action.copyLinesUpAction) respectively.</span><span class="linux-only">Copy the entire current line when no text is selected with kb(editor.action.clipboardCopyAction).</span>
|
||||
2. Move an entire line or selection of lines up or down with kb(editor.action.moveLinesUpAction) and kb(editor.action.moveLinesDownAction) respectively.
|
||||
3. Delete the entire line with kb(editor.action.deleteLines).
|
||||
|
||||
|||json
|
||||
{
|
||||
"name": "John",
|
||||
"age": 31,
|
||||
"city": "New York"
|
||||
}
|
||||
|||
|
||||
|
||||
>**Tip:** Another very common task is to comment out a block of code - you can toggle commenting by pressing kb(editor.action.commentLine).
|
||||
|
||||
|
||||
|
||||
### Rename Refactoring
|
||||
It's easy to rename a symbol such as a function name or variable name. Hit kb(editor.action.rename) while in the symbol |Book| to rename all instances - this will occur across all files in a project. You also have |Rename Symbol| in the right-click context menu.
|
||||
|
||||
|||js
|
||||
// Reference the function
|
||||
new Book("War of the Worlds", "H G Wells");
|
||||
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;
|
||||
this.author = author;
|
||||
}
|
||||
|||
|
||||
|
||||
> **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|.
|
||||
|
||||
|
||||
### Formatting
|
||||
Keeping your code looking great is hard without a good formatter. Luckily it's easy to format content, either for the entire document with kb(editor.action.formatDocument) or for the current selection with kb(editor.action.formatSelection). Both of these options are also available through the right-click context menu.
|
||||
|
||||
|||js
|
||||
const cars = ["🚗", "🚙", "🚕"];
|
||||
|
||||
for (const car of cars){
|
||||
// Drive the car
|
||||
console.log(|This is the car \${car}|);
|
||||
}
|
||||
|||
|
||||
|
||||
>**Tip:** Additional formatters are available in the [extension gallery](command:workbench.extensions.action.showPopularExtensions). Formatting support can also be configured via [settings](command:workbench.action.openGlobalSettings) e.g. enabling |editor.formatOnSave|.
|
||||
|
||||
|
||||
### Code Folding
|
||||
In a large file it can often be useful to collapse sections of code to increase readability. To do this, you can simply press kb(editor.fold) to fold or press kb(editor.unfold) to unfold the ranges at the current cursor position. Folding can also be done with the down and right angle bracket icons in the left gutter. To fold all sections use kb(editor.foldAll) or to unfold all use kb(editor.unfoldAll).
|
||||
|
||||
|||html
|
||||
<div>
|
||||
<header>
|
||||
<ul>
|
||||
<li><a href=""></a></li>
|
||||
<li><a href=""></a></li>
|
||||
</ul>
|
||||
</header>
|
||||
<footer>
|
||||
<p></p>
|
||||
</footer>
|
||||
</div>
|
||||
|||
|
||||
|
||||
>**Tip:** Folding is based on indentation and as a result can apply to all languages. Simply indent your code to create a foldable section you can fold a certain number of levels with shortcuts like kb(editor.foldLevel1) through to kb(editor.foldLevel5).
|
||||
|
||||
### Errors and Warnings
|
||||
Errors and warnings are highlighted as you edit your code with squiggles. In the sample below you can see a number of syntax errors. By pressing kb(editor.action.marker.nextInFiles) you can navigate across them in sequence and see the detailed error message. As you correct them the squiggles and scrollbar indicators will update.
|
||||
|
||||
|||js
|
||||
// This code has a few syntax errors
|
||||
Console.log(add(1, 1.5));
|
||||
|
||||
|
||||
function Add(a : Number, b : Number) : Int {
|
||||
return a + b;
|
||||
}
|
||||
|||
|
||||
|
||||
|
||||
### Snippets
|
||||
You can greatly accelerate your editing through the use of snippets. Simply start typing |try| and select |trycatch| from the suggestion list and press kb(insertSnippet) to create a |try|->|catch| block. Your cursor will be placed on the text |error| for easy editing. If more than one parameter exists then press kb(jumpToNextSnippetPlaceholder) to jump to it.
|
||||
|
||||
|||js
|
||||
|
||||
|||
|
||||
|
||||
>**Tip:** the [extension gallery](command:workbench.extensions.action.showPopularExtensions) includes snippets for almost every framework and language imaginable. You can also create your own [user-defined snippets](command:workbench.action.openSnippets).
|
||||
|
||||
|
||||
### Emmet
|
||||
Emmet takes the snippets idea to a whole new level: you can type CSS-like expressions that can be dynamically parsed, and produce output depending on what you type in the abbreviation. Try it by selecting |Emmet: Expand Abbreviation| from the |Edit| menu with the cursor at the end of a valid Emmet abbreviation or snippet and the expansion will occur.
|
||||
|
||||
|||html
|
||||
ul>li.item$*5
|
||||
|||
|
||||
|
||||
>**Tip:** The [Emmet cheat sheet](https://docs.emmet.io/cheat-sheet/) is a great source of Emmet syntax suggestions. To expand Emmet abbreviations and snippets using the |tab| key use the |emmet.triggerExpansionOnTab| [setting](command:workbench.action.openGlobalSettings). Check out the docs on [Emmet in VS Code](https://code.visualstudio.com/docs/editor/emmet) to learn more.
|
||||
|
||||
|
||||
|
||||
### JavaScript Type Checking
|
||||
Sometimes type checking your JavaScript code can help you spot mistakes you might have not caught otherwise. You can run the TypeScript type checker against your existing JavaScript code by simply adding a |// @ts-check| comment to the top of your file.
|
||||
|
||||
|||js
|
||||
// @ts-nocheck
|
||||
|
||||
let easy = true;
|
||||
easy = 42;
|
||||
|||
|
||||
|
||||
>**Tip:** You can also enable the checks workspace or application wide by adding |"javascript.implicitProjectConfig.checkJs": true| to your workspace or user settings and explicitly ignoring files or lines using |// @ts-nocheck| and |// @ts-expect-error|. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more.
|
||||
|
||||
|
||||
## Thanks!
|
||||
Well if you have got this far then you will have touched on some of the editing features in Visual Studio Code. But don't stop now :) We have lots of additional [documentation](https://code.visualstudio.com/docs), [introductory videos](https://code.visualstudio.com/docs/getstarted/introvideos) and [tips and tricks](https://go.microsoft.com/fwlink/?linkid=852118) for the product that will help you learn how to use it. And while you are here, here are a few additional things you can try:
|
||||
- Open the Integrated Terminal by pressing kb(workbench.action.terminal.toggleTerminal), then see what's possible by [reviewing the terminal documentation](https://code.visualstudio.com/docs/editor/integrated-terminal)
|
||||
- Work with version control by pressing kb(workbench.view.scm). Understand how to stage, commit, change branches, and view diffs and more by reviewing the [version control documentation](https://code.visualstudio.com/docs/editor/versioncontrol)
|
||||
- Browse thousands of extensions in our integrated gallery by pressing kb(workbench.view.extensions). The [documentation](https://code.visualstudio.com/docs/editor/extension-gallery) will show you how to see the most popular extensions, disable installed ones and more.
|
||||
|
||||
That's all for now,
|
||||
|
||||
Happy Coding! 🎉
|
||||
|
||||
`.replace(/\|/g, '`');
|
||||
@@ -0,0 +1,55 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
|
||||
import { WalkThroughPart } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart';
|
||||
import { WalkThroughArrowUp, WalkThroughArrowDown, WalkThroughPageUp, WalkThroughPageDown } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions';
|
||||
import { WalkThroughSnippetContentProvider } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider';
|
||||
import { EditorWalkThroughAction, EditorWalkThroughInputFactory } from 'vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IWorkbenchActionRegistry, Extensions, CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(EditorDescriptor.create(
|
||||
WalkThroughPart,
|
||||
WalkThroughPart.ID,
|
||||
localize('walkThrough.editor.label', "Interactive Playground"),
|
||||
),
|
||||
[new SyncDescriptor(WalkThroughInput)]);
|
||||
|
||||
Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions)
|
||||
.registerWorkbenchAction(
|
||||
SyncActionDescriptor.from(EditorWalkThroughAction),
|
||||
'Help: Interactive Playground', CATEGORIES.Help.value);
|
||||
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(EditorWalkThroughInputFactory.ID, EditorWalkThroughInputFactory);
|
||||
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(WalkThroughSnippetContentProvider, LifecyclePhase.Starting);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowUp);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowDown);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughPageUp);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughPageDown);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '1_welcome',
|
||||
command: {
|
||||
id: 'workbench.action.showInteractivePlayground',
|
||||
title: localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "I&&nteractive Playground")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { WalkThroughPart, WALK_THROUGH_FOCUS } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart';
|
||||
import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
|
||||
export const WalkThroughArrowUp: ICommandAndKeybindingRule = {
|
||||
id: 'workbench.action.interactivePlayground.arrowUp',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()),
|
||||
primary: KeyCode.UpArrow,
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const activeEditorPane = editorService.activeEditorPane;
|
||||
if (activeEditorPane instanceof WalkThroughPart) {
|
||||
activeEditorPane.arrowUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const WalkThroughArrowDown: ICommandAndKeybindingRule = {
|
||||
id: 'workbench.action.interactivePlayground.arrowDown',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()),
|
||||
primary: KeyCode.DownArrow,
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const activeEditorPane = editorService.activeEditorPane;
|
||||
if (activeEditorPane instanceof WalkThroughPart) {
|
||||
activeEditorPane.arrowDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const WalkThroughPageUp: ICommandAndKeybindingRule = {
|
||||
id: 'workbench.action.interactivePlayground.pageUp',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()),
|
||||
primary: KeyCode.PageUp,
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const activeEditorPane = editorService.activeEditorPane;
|
||||
if (activeEditorPane instanceof WalkThroughPart) {
|
||||
activeEditorPane.pageUp();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const WalkThroughPageDown: ICommandAndKeybindingRule = {
|
||||
id: 'workbench.action.interactivePlayground.pageDown',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(WALK_THROUGH_FOCUS, EditorContextKeys.editorTextFocus.toNegated()),
|
||||
primary: KeyCode.PageDown,
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const activeEditorPane = editorService.activeEditorPane;
|
||||
if (activeEditorPane instanceof WalkThroughPart) {
|
||||
activeEditorPane.pageDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, EditorModel, ITextEditorModel } from 'vs/workbench/common/editor';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IReference } from 'vs/base/common/lifecycle';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { requireToContent } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider';
|
||||
|
||||
export class WalkThroughModel extends EditorModel {
|
||||
|
||||
constructor(
|
||||
private mainRef: string,
|
||||
private snippetRefs: IReference<ITextEditorModel>[]
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get main() {
|
||||
return this.mainRef;
|
||||
}
|
||||
|
||||
get snippets() {
|
||||
return this.snippetRefs.map(snippet => snippet.object);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.snippetRefs.forEach(ref => ref.dispose());
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface WalkThroughInputOptions {
|
||||
readonly typeId: string;
|
||||
readonly name: string;
|
||||
readonly description?: string;
|
||||
readonly resource: URI;
|
||||
readonly telemetryFrom: string;
|
||||
readonly onReady?: (container: HTMLElement) => void;
|
||||
}
|
||||
|
||||
export class WalkThroughInput extends EditorInput {
|
||||
|
||||
private promise: Promise<WalkThroughModel> | null = null;
|
||||
|
||||
private maxTopScroll = 0;
|
||||
private maxBottomScroll = 0;
|
||||
|
||||
get resource() { return this.options.resource; }
|
||||
|
||||
constructor(
|
||||
private readonly options: WalkThroughInputOptions,
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return this.options.typeId;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this.options.name;
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return this.options.description || '';
|
||||
}
|
||||
|
||||
getTelemetryFrom(): string {
|
||||
return this.options.telemetryFrom;
|
||||
}
|
||||
|
||||
getTelemetryDescriptor(): { [key: string]: unknown; } {
|
||||
const descriptor = super.getTelemetryDescriptor();
|
||||
descriptor['target'] = this.getTelemetryFrom();
|
||||
/* __GDPR__FRAGMENT__
|
||||
"EditorTelemetryDescriptor" : {
|
||||
"target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
get onReady() {
|
||||
return this.options.onReady;
|
||||
}
|
||||
|
||||
resolve(): Promise<WalkThroughModel> {
|
||||
if (!this.promise) {
|
||||
this.promise = requireToContent(this.options.resource)
|
||||
.then(content => {
|
||||
if (this.resource.path.endsWith('.html')) {
|
||||
return new WalkThroughModel(content, []);
|
||||
}
|
||||
|
||||
const snippets: Promise<IReference<ITextEditorModel>>[] = [];
|
||||
let i = 0;
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.code = (code, lang) => {
|
||||
i++;
|
||||
const resource = this.options.resource.with({ scheme: Schemas.walkThroughSnippet, fragment: `${i}.${lang}` });
|
||||
snippets.push(this.textModelResolverService.createModelReference(resource));
|
||||
return `<div id="snippet-${resource.fragment}" class="walkThroughEditorContainer" ></div>`;
|
||||
};
|
||||
content = marked(content, { renderer });
|
||||
|
||||
return Promise.all(snippets)
|
||||
.then(refs => new WalkThroughModel(content, refs));
|
||||
});
|
||||
}
|
||||
|
||||
return this.promise;
|
||||
}
|
||||
|
||||
matches(otherInput: unknown): boolean {
|
||||
if (super.matches(otherInput) === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (otherInput instanceof WalkThroughInput) {
|
||||
let otherResourceEditorInput = <WalkThroughInput>otherInput;
|
||||
|
||||
// Compare by properties
|
||||
return isEqual(otherResourceEditorInput.options.resource, this.options.resource);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.promise) {
|
||||
this.promise.then(model => model.dispose());
|
||||
this.promise = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public relativeScrollPosition(topScroll: number, bottomScroll: number) {
|
||||
this.maxTopScroll = Math.max(this.maxTopScroll, topScroll);
|
||||
this.maxBottomScroll = Math.max(this.maxBottomScroll, bottomScroll);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent {
|
||||
box-sizing: border-box;
|
||||
padding: 10px 20px;
|
||||
line-height: 22px;
|
||||
user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent a:focus,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent input:focus,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent select:focus,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent textarea:focus {
|
||||
outline: 1px solid -webkit-focus-ring-color;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent hr {
|
||||
border: 0;
|
||||
height: 2px;
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h1,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h2,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h3 {
|
||||
font-weight: lighter;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h1 {
|
||||
padding-bottom: 0.3em;
|
||||
line-height: 1.2;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
font-size: 40px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h2 {
|
||||
font-size: 30px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h3 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent h4 {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table > thead > tr > th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table > thead > tr > th,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table > thead > tr > td,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table > tbody > tr > th,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table > tbody > tr > td {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent table > tbody > tr + tr > td {
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent blockquote {
|
||||
margin: 0 7px 0 5px;
|
||||
padding: 0 16px 0 10px;
|
||||
border-left: 5px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent code,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .shortcut {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent blockquote {
|
||||
margin: 0 7px 0 5px;
|
||||
padding: 0 16px 0 10px;
|
||||
border-left: 5px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .monaco-tokenized-source {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.file-icons-enabled .show-file-icons .vs_code_editor_walkthrough\.md-name-file-icon.md-ext-file-icon.ext-file-icon.markdown-lang-file-icon.file-icon::before {
|
||||
content: ' ';
|
||||
background-image: url('../../../../browser/media/code-icon.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .mac-only,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .windows-only,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .linux-only {
|
||||
display: none;
|
||||
}
|
||||
.monaco-workbench.mac .part.editor > .content .walkThroughContent .mac-only {
|
||||
display: initial;
|
||||
}
|
||||
.monaco-workbench.windows .part.editor > .content .walkThroughContent .windows-only {
|
||||
display: initial;
|
||||
}
|
||||
.monaco-workbench.linux .part.editor > .content .walkThroughContent .linux-only {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench.hc-black .part.editor > .content .walkThroughContent .monaco-editor {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./walkThroughPart';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { EditorOptions, IEditorMemento, IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, textPreformatForeground, contrastBorder, textBlockQuoteBackground, textBlockQuoteBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
|
||||
import { UILabelProvider } from 'vs/base/common/keybindingLabels';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Dimension, safeInnerHtml, size } from 'vs/base/browser/dom';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
|
||||
export const WALK_THROUGH_FOCUS = new RawContextKey<boolean>('interactivePlaygroundFocus', false);
|
||||
|
||||
const UNBOUND_COMMAND = localize('walkThrough.unboundCommand', "unbound");
|
||||
const WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'walkThroughEditorViewState';
|
||||
|
||||
interface IViewState {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}
|
||||
|
||||
interface IWalkThroughEditorViewState {
|
||||
viewState: IViewState;
|
||||
}
|
||||
|
||||
export class WalkThroughPart extends EditorPane {
|
||||
|
||||
static readonly ID: string = 'workbench.editor.walkThroughPart';
|
||||
|
||||
private readonly disposables = new DisposableStore();
|
||||
private contentDisposables: IDisposable[] = [];
|
||||
private content!: HTMLDivElement;
|
||||
private scrollbar!: DomScrollableElement;
|
||||
private editorFocus: IContextKey<boolean>;
|
||||
private lastFocus: HTMLElement | undefined;
|
||||
private size: Dimension | undefined;
|
||||
private editorMemento: IEditorMemento<IWalkThroughEditorViewState>;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(WalkThroughPart.ID, telemetryService, themeService, storageService);
|
||||
this.editorFocus = WALK_THROUGH_FOCUS.bindTo(this.contextKeyService);
|
||||
this.editorMemento = this.getEditorMemento<IWalkThroughEditorViewState>(editorGroupService, WALK_THROUGH_EDITOR_VIEW_STATE_PREFERENCE_KEY);
|
||||
}
|
||||
|
||||
createEditor(container: HTMLElement): void {
|
||||
this.content = document.createElement('div');
|
||||
this.content.tabIndex = 0;
|
||||
this.content.style.outlineStyle = 'none';
|
||||
|
||||
this.scrollbar = new DomScrollableElement(this.content, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Auto
|
||||
});
|
||||
this.disposables.add(this.scrollbar);
|
||||
container.appendChild(this.scrollbar.getDomNode());
|
||||
|
||||
this.registerFocusHandlers();
|
||||
this.registerClickHandler();
|
||||
|
||||
this.disposables.add(this.scrollbar.onScroll(e => this.updatedScrollPosition()));
|
||||
}
|
||||
|
||||
private updatedScrollPosition() {
|
||||
const scrollDimensions = this.scrollbar.getScrollDimensions();
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
const scrollHeight = scrollDimensions.scrollHeight;
|
||||
if (scrollHeight && this.input instanceof WalkThroughInput) {
|
||||
const scrollTop = scrollPosition.scrollTop;
|
||||
const height = scrollDimensions.height;
|
||||
this.input.relativeScrollPosition(scrollTop / scrollHeight, (scrollTop + height) / scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private onTouchChange(event: GestureEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - event.translationY });
|
||||
}
|
||||
|
||||
private addEventListener<K extends keyof HTMLElementEventMap, E extends HTMLElement>(element: E, type: K, listener: (this: E, ev: HTMLElementEventMap[K]) => any, useCapture?: boolean): IDisposable;
|
||||
private addEventListener<E extends HTMLElement>(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable;
|
||||
private addEventListener<E extends HTMLElement>(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable {
|
||||
element.addEventListener(type, listener, useCapture);
|
||||
return toDisposable(() => { element.removeEventListener(type, listener, useCapture); });
|
||||
}
|
||||
|
||||
private registerFocusHandlers() {
|
||||
this.disposables.add(this.addEventListener(this.content, 'mousedown', e => {
|
||||
this.focus();
|
||||
}));
|
||||
this.disposables.add(this.addEventListener(this.content, 'focus', e => {
|
||||
this.editorFocus.set(true);
|
||||
}));
|
||||
this.disposables.add(this.addEventListener(this.content, 'blur', e => {
|
||||
this.editorFocus.reset();
|
||||
}));
|
||||
this.disposables.add(this.addEventListener(this.content, 'focusin', (e: FocusEvent) => {
|
||||
// Work around scrolling as side-effect of setting focus on the offscreen zone widget (#18929)
|
||||
if (e.target instanceof HTMLElement && e.target.classList.contains('zone-widget-container')) {
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
this.content.scrollTop = scrollPosition.scrollTop;
|
||||
this.content.scrollLeft = scrollPosition.scrollLeft;
|
||||
}
|
||||
if (e.target instanceof HTMLElement) {
|
||||
this.lastFocus = e.target;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private registerClickHandler() {
|
||||
this.content.addEventListener('click', event => {
|
||||
for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) {
|
||||
if (node instanceof HTMLAnchorElement && node.href) {
|
||||
let baseElement = window.document.getElementsByTagName('base')[0] || window.location;
|
||||
if (baseElement && node.href.indexOf(baseElement.href) >= 0 && node.hash) {
|
||||
const scrollTarget = this.content.querySelector(node.hash);
|
||||
const innerContent = this.content.firstElementChild;
|
||||
if (scrollTarget && innerContent) {
|
||||
const targetTop = scrollTarget.getBoundingClientRect().top - 20;
|
||||
const containerTop = innerContent.getBoundingClientRect().top;
|
||||
this.scrollbar.setScrollPosition({ scrollTop: targetTop - containerTop });
|
||||
}
|
||||
} else {
|
||||
this.open(URI.parse(node.href));
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
} else if (node instanceof HTMLButtonElement) {
|
||||
const href = node.getAttribute('data-href');
|
||||
if (href) {
|
||||
this.open(URI.parse(href));
|
||||
}
|
||||
break;
|
||||
} else if (node === event.currentTarget) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private open(uri: URI) {
|
||||
if (uri.scheme === 'command' && uri.path === 'git.clone' && !CommandsRegistry.getCommand('git.clone')) {
|
||||
this.notificationService.info(localize('walkThrough.gitNotFound', "It looks like Git is not installed on your system."));
|
||||
return;
|
||||
}
|
||||
this.openerService.open(this.addFrom(uri));
|
||||
}
|
||||
|
||||
private addFrom(uri: URI) {
|
||||
if (uri.scheme !== 'command' || !(this.input instanceof WalkThroughInput)) {
|
||||
return uri;
|
||||
}
|
||||
const query = uri.query ? JSON.parse(uri.query) : {};
|
||||
query.from = this.input.getTelemetryFrom();
|
||||
return uri.with({ query: JSON.stringify(query) });
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
this.size = dimension;
|
||||
size(this.content, dimension.width, dimension.height);
|
||||
this.updateSizeClasses();
|
||||
this.contentDisposables.forEach(disposable => {
|
||||
if (disposable instanceof CodeEditorWidget) {
|
||||
disposable.layout();
|
||||
}
|
||||
});
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
private updateSizeClasses() {
|
||||
const innerContent = this.content.firstElementChild;
|
||||
if (this.size && innerContent) {
|
||||
const classList = innerContent.classList;
|
||||
classList[this.size.height <= 685 ? 'add' : 'remove']('max-height-685px');
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
let active = document.activeElement;
|
||||
while (active && active !== this.content) {
|
||||
active = active.parentElement;
|
||||
}
|
||||
if (!active) {
|
||||
(this.lastFocus || this.content).focus();
|
||||
}
|
||||
this.editorFocus.set(true);
|
||||
}
|
||||
|
||||
arrowUp() {
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - this.getArrowScrollHeight() });
|
||||
}
|
||||
|
||||
arrowDown() {
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop + this.getArrowScrollHeight() });
|
||||
}
|
||||
|
||||
private getArrowScrollHeight() {
|
||||
let fontSize = this.configurationService.getValue<number>('editor.fontSize');
|
||||
if (typeof fontSize !== 'number' || fontSize < 1) {
|
||||
fontSize = 12;
|
||||
}
|
||||
return 3 * fontSize;
|
||||
}
|
||||
|
||||
pageUp() {
|
||||
const scrollDimensions = this.scrollbar.getScrollDimensions();
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - scrollDimensions.height });
|
||||
}
|
||||
|
||||
pageDown() {
|
||||
const scrollDimensions = this.scrollbar.getScrollDimensions();
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop + scrollDimensions.height });
|
||||
}
|
||||
|
||||
setInput(input: WalkThroughInput, options: EditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||
if (this.input instanceof WalkThroughInput) {
|
||||
this.saveTextEditorViewState(this.input);
|
||||
}
|
||||
|
||||
this.contentDisposables = dispose(this.contentDisposables);
|
||||
this.content.innerText = '';
|
||||
|
||||
return super.setInput(input, options, context, token)
|
||||
.then(async () => {
|
||||
if (input.resource.path.endsWith('.md')) {
|
||||
await this.extensionService.whenInstalledExtensionsRegistered();
|
||||
}
|
||||
return input.resolve();
|
||||
})
|
||||
.then(model => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = model.main;
|
||||
if (!input.resource.path.endsWith('.md')) {
|
||||
safeInnerHtml(this.content, content);
|
||||
|
||||
this.updateSizeClasses();
|
||||
this.decorateContent();
|
||||
this.contentDisposables.push(this.keybindingService.onDidUpdateKeybindings(() => this.decorateContent()));
|
||||
if (input.onReady) {
|
||||
input.onReady(this.content.firstElementChild as HTMLElement);
|
||||
}
|
||||
this.scrollbar.scanDomNode();
|
||||
this.loadTextEditorViewState(input);
|
||||
this.updatedScrollPosition();
|
||||
return;
|
||||
}
|
||||
|
||||
const innerContent = document.createElement('div');
|
||||
innerContent.classList.add('walkThroughContent'); // only for markdown files
|
||||
const markdown = this.expandMacros(content);
|
||||
safeInnerHtml(innerContent, markdown);
|
||||
this.content.appendChild(innerContent);
|
||||
|
||||
model.snippets.forEach((snippet, i) => {
|
||||
const model = snippet.textEditorModel;
|
||||
const id = `snippet-${model.uri.fragment}`;
|
||||
const div = innerContent.querySelector(`#${id.replace(/[\\.]/g, '\\$&')}`) as HTMLElement;
|
||||
|
||||
const options = this.getEditorOptions(snippet.textEditorModel.getModeId());
|
||||
const telemetryData = {
|
||||
target: this.input instanceof WalkThroughInput ? this.input.getTelemetryFrom() : undefined,
|
||||
snippet: i
|
||||
};
|
||||
const editor = this.instantiationService.createInstance(CodeEditorWidget, div, options, {
|
||||
telemetryData: telemetryData
|
||||
});
|
||||
editor.setModel(model);
|
||||
this.contentDisposables.push(editor);
|
||||
|
||||
const updateHeight = (initial: boolean) => {
|
||||
const lineHeight = editor.getOption(EditorOption.lineHeight);
|
||||
const height = `${Math.max(model.getLineCount() + 1, 4) * lineHeight}px`;
|
||||
if (div.style.height !== height) {
|
||||
div.style.height = height;
|
||||
editor.layout();
|
||||
if (!initial) {
|
||||
this.scrollbar.scanDomNode();
|
||||
}
|
||||
}
|
||||
};
|
||||
updateHeight(true);
|
||||
this.contentDisposables.push(editor.onDidChangeModelContent(() => updateHeight(false)));
|
||||
this.contentDisposables.push(editor.onDidChangeCursorPosition(e => {
|
||||
const innerContent = this.content.firstElementChild;
|
||||
if (innerContent) {
|
||||
const targetTop = div.getBoundingClientRect().top;
|
||||
const containerTop = innerContent.getBoundingClientRect().top;
|
||||
const lineHeight = editor.getOption(EditorOption.lineHeight);
|
||||
const lineTop = (targetTop + (e.position.lineNumber - 1) * lineHeight) - containerTop;
|
||||
const lineBottom = lineTop + lineHeight;
|
||||
const scrollDimensions = this.scrollbar.getScrollDimensions();
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
const scrollTop = scrollPosition.scrollTop;
|
||||
const height = scrollDimensions.height;
|
||||
if (scrollTop > lineTop) {
|
||||
this.scrollbar.setScrollPosition({ scrollTop: lineTop });
|
||||
} else if (scrollTop < lineBottom - height) {
|
||||
this.scrollbar.setScrollPosition({ scrollTop: lineBottom - height });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.contentDisposables.push(this.configurationService.onDidChangeConfiguration(() => {
|
||||
if (snippet.textEditorModel) {
|
||||
editor.updateOptions(this.getEditorOptions(snippet.textEditorModel.getModeId()));
|
||||
}
|
||||
}));
|
||||
|
||||
type WalkThroughSnippetInteractionClassification = {
|
||||
from?: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
type: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
|
||||
snippet: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
type WalkThroughSnippetInteractionEvent = {
|
||||
from?: string,
|
||||
type: string,
|
||||
snippet: number
|
||||
};
|
||||
|
||||
this.contentDisposables.push(Event.once(editor.onMouseDown)(() => {
|
||||
this.telemetryService.publicLog2<WalkThroughSnippetInteractionEvent, WalkThroughSnippetInteractionClassification>('walkThroughSnippetInteraction', {
|
||||
from: this.input instanceof WalkThroughInput ? this.input.getTelemetryFrom() : undefined,
|
||||
type: 'mouseDown',
|
||||
snippet: i
|
||||
});
|
||||
}));
|
||||
this.contentDisposables.push(Event.once(editor.onKeyDown)(() => {
|
||||
this.telemetryService.publicLog2<WalkThroughSnippetInteractionEvent, WalkThroughSnippetInteractionClassification>('walkThroughSnippetInteraction', {
|
||||
from: this.input instanceof WalkThroughInput ? this.input.getTelemetryFrom() : undefined,
|
||||
type: 'keyDown',
|
||||
snippet: i
|
||||
});
|
||||
}));
|
||||
this.contentDisposables.push(Event.once(editor.onDidChangeModelContent)(() => {
|
||||
this.telemetryService.publicLog2<WalkThroughSnippetInteractionEvent, WalkThroughSnippetInteractionClassification>('walkThroughSnippetInteraction', {
|
||||
from: this.input instanceof WalkThroughInput ? this.input.getTelemetryFrom() : undefined,
|
||||
type: 'changeModelContent',
|
||||
snippet: i
|
||||
});
|
||||
}));
|
||||
});
|
||||
this.updateSizeClasses();
|
||||
this.multiCursorModifier();
|
||||
this.contentDisposables.push(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('editor.multiCursorModifier')) {
|
||||
this.multiCursorModifier();
|
||||
}
|
||||
}));
|
||||
if (input.onReady) {
|
||||
input.onReady(innerContent);
|
||||
}
|
||||
this.scrollbar.scanDomNode();
|
||||
this.loadTextEditorViewState(input);
|
||||
this.updatedScrollPosition();
|
||||
this.contentDisposables.push(Gesture.addTarget(innerContent));
|
||||
this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(this.onTouchChange, this, this.disposables));
|
||||
});
|
||||
}
|
||||
|
||||
private getEditorOptions(language: string): IEditorOptions {
|
||||
const config = deepClone(this.configurationService.getValue<IEditorOptions>('editor', { overrideIdentifier: language }));
|
||||
return {
|
||||
...isObject(config) ? config : Object.create(null),
|
||||
scrollBeyondLastLine: false,
|
||||
scrollbar: {
|
||||
verticalScrollbarSize: 14,
|
||||
horizontal: 'auto',
|
||||
useShadows: true,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false,
|
||||
alwaysConsumeMouseWheel: false
|
||||
},
|
||||
overviewRulerLanes: 3,
|
||||
fixedOverflowWidgets: false,
|
||||
lineNumbersMinChars: 1,
|
||||
minimap: { enabled: false },
|
||||
};
|
||||
}
|
||||
|
||||
private expandMacros(input: string) {
|
||||
return input.replace(/kb\(([a-z.\d\-]+)\)/gi, (match: string, kb: string) => {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(kb);
|
||||
const shortcut = keybinding ? keybinding.getLabel() || '' : UNBOUND_COMMAND;
|
||||
return `<span class="shortcut">${strings.escape(shortcut)}</span>`;
|
||||
});
|
||||
}
|
||||
|
||||
private decorateContent() {
|
||||
const keys = this.content.querySelectorAll('.shortcut[data-command]');
|
||||
Array.prototype.forEach.call(keys, (key: Element) => {
|
||||
const command = key.getAttribute('data-command');
|
||||
const keybinding = command && this.keybindingService.lookupKeybinding(command);
|
||||
const label = keybinding ? keybinding.getLabel() || '' : UNBOUND_COMMAND;
|
||||
while (key.firstChild) {
|
||||
key.removeChild(key.firstChild);
|
||||
}
|
||||
key.appendChild(document.createTextNode(label));
|
||||
});
|
||||
const ifkeys = this.content.querySelectorAll('.if_shortcut[data-command]');
|
||||
Array.prototype.forEach.call(ifkeys, (key: HTMLElement) => {
|
||||
const command = key.getAttribute('data-command');
|
||||
const keybinding = command && this.keybindingService.lookupKeybinding(command);
|
||||
key.style.display = !keybinding ? 'none' : '';
|
||||
});
|
||||
}
|
||||
|
||||
private multiCursorModifier() {
|
||||
const labels = UILabelProvider.modifierLabels[OS];
|
||||
const value = this.configurationService.getValue<string>('editor.multiCursorModifier');
|
||||
const modifier = labels[value === 'ctrlCmd' ? (OS === OperatingSystem.Macintosh ? 'metaKey' : 'ctrlKey') : 'altKey'];
|
||||
const keys = this.content.querySelectorAll('.multi-cursor-modifier');
|
||||
Array.prototype.forEach.call(keys, (key: Element) => {
|
||||
while (key.firstChild) {
|
||||
key.removeChild(key.firstChild);
|
||||
}
|
||||
key.appendChild(document.createTextNode(modifier));
|
||||
});
|
||||
}
|
||||
|
||||
private saveTextEditorViewState(input: WalkThroughInput): void {
|
||||
const scrollPosition = this.scrollbar.getScrollPosition();
|
||||
|
||||
if (this.group) {
|
||||
this.editorMemento.saveEditorState(this.group, input, {
|
||||
viewState: {
|
||||
scrollTop: scrollPosition.scrollTop,
|
||||
scrollLeft: scrollPosition.scrollLeft
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private loadTextEditorViewState(input: WalkThroughInput) {
|
||||
if (this.group) {
|
||||
const state = this.editorMemento.loadEditorState(this.group, input);
|
||||
if (state) {
|
||||
this.scrollbar.setScrollPosition(state.viewState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public clearInput(): void {
|
||||
if (this.input instanceof WalkThroughInput) {
|
||||
this.saveTextEditorViewState(this.input);
|
||||
}
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
protected saveState(): void {
|
||||
if (this.input instanceof WalkThroughInput) {
|
||||
this.saveTextEditorViewState(this.input);
|
||||
}
|
||||
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.editorFocus.reset();
|
||||
this.contentDisposables = dispose(this.contentDisposables);
|
||||
this.disposables.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// theming
|
||||
|
||||
export const embeddedEditorBackground = registerColor('walkThrough.embeddedEditorBackground', { dark: null, light: null, hc: null }, localize('walkThrough.embeddedEditorBackground', 'Background color for the embedded editors on the Interactive Playground.'));
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null });
|
||||
if (color) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .monaco-editor-background,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .margin-view-overlays { background: ${color}; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent a { color: ${link}; }`);
|
||||
}
|
||||
const activeLink = theme.getColor(textLinkActiveForeground);
|
||||
if (activeLink) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent a:hover,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent a:active { color: ${activeLink}; }`);
|
||||
}
|
||||
const focusColor = theme.getColor(focusBorder);
|
||||
if (focusColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent a:focus { outline-color: ${focusColor}; }`);
|
||||
}
|
||||
const shortcut = theme.getColor(textPreformatForeground);
|
||||
if (shortcut) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent code,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .shortcut { color: ${shortcut}; }`);
|
||||
}
|
||||
const border = theme.getColor(contrastBorder);
|
||||
if (border) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .monaco-editor { border-color: ${border}; }`);
|
||||
}
|
||||
const quoteBackground = theme.getColor(textBlockQuoteBackground);
|
||||
if (quoteBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent blockquote { background: ${quoteBackground}; }`);
|
||||
}
|
||||
const quoteBorder = theme.getColor(textBlockQuoteBorder);
|
||||
if (quoteBorder) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent blockquote { border-color: ${quoteBorder}; }`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ITextModel, DefaultEndOfLine, EndOfLinePreference, ITextBufferFactory } from 'vs/editor/common/model';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
|
||||
export function requireToContent(resource: URI): Promise<string> {
|
||||
if (!resource.query) {
|
||||
throw new Error('Welcome: invalid resource');
|
||||
}
|
||||
|
||||
const query = JSON.parse(resource.query);
|
||||
if (!query.moduleId) {
|
||||
throw new Error('Welcome: invalid resource');
|
||||
}
|
||||
|
||||
const content: Promise<string> = new Promise<string>((resolve, reject) => {
|
||||
require([query.moduleId], content => {
|
||||
try {
|
||||
resolve(content.default());
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
export class WalkThroughSnippetContentProvider implements ITextModelContentProvider, IWorkbenchContribution {
|
||||
private loads = new Map<string, Promise<ITextBufferFactory>>();
|
||||
|
||||
constructor(
|
||||
@ITextModelService private readonly textModelResolverService: ITextModelService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
) {
|
||||
this.textModelResolverService.registerTextModelContentProvider(Schemas.walkThroughSnippet, this);
|
||||
}
|
||||
|
||||
private async textBufferFactoryFromResource(resource: URI): Promise<ITextBufferFactory> {
|
||||
let ongoing = this.loads.get(resource.toString());
|
||||
if (!ongoing) {
|
||||
ongoing = new Promise(async c => {
|
||||
c(createTextBufferFactory(await requireToContent(resource)));
|
||||
this.loads.delete(resource.toString());
|
||||
});
|
||||
this.loads.set(resource.toString(), ongoing);
|
||||
}
|
||||
return ongoing;
|
||||
}
|
||||
|
||||
public async provideTextContent(resource: URI): Promise<ITextModel> {
|
||||
const factory = await this.textBufferFactoryFromResource(resource.with({ fragment: '' }));
|
||||
let codeEditorModel = this.modelService.getModel(resource);
|
||||
if (!codeEditorModel) {
|
||||
const j = parseInt(resource.fragment);
|
||||
let i = 0;
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.code = (code, lang) => {
|
||||
i++;
|
||||
const languageId = this.modeService.getModeIdForLanguageName(lang) || '';
|
||||
const languageSelection = this.modeService.create(languageId);
|
||||
// Create all models for this resource in one go... we'll need them all and we don't want to re-parse markdown each time
|
||||
const model = this.modelService.createModel(code, languageSelection, resource.with({ fragment: `${i}.${lang}` }));
|
||||
if (i === j) { codeEditorModel = model; }
|
||||
return '';
|
||||
};
|
||||
const textBuffer = factory.create(DefaultEndOfLine.LF);
|
||||
const lineCount = textBuffer.getLineCount();
|
||||
const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1);
|
||||
const markdown = textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined);
|
||||
marked(markdown, { renderer });
|
||||
}
|
||||
return assertIsDefined(codeEditorModel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IColorTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBackground, ColorDefaults, ColorValue } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export function getExtraColor(theme: IColorTheme, colorId: string, defaults: ColorDefaults & { extra_dark: string }): ColorValue | null {
|
||||
const color = theme.getColor(colorId);
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
|
||||
if (theme.type === 'dark') {
|
||||
const background = theme.getColor(editorBackground);
|
||||
if (background && background.getRelativeLuminance() < 0.004) {
|
||||
return defaults.extra_dark;
|
||||
}
|
||||
}
|
||||
|
||||
return defaults[theme.type];
|
||||
}
|
||||
Reference in New Issue
Block a user