Archived
Merge from vscode 79a1f5a5ca0c6c53db617aa1fa5a2396d2caebe2
This commit is contained in:
@@ -34,4 +34,5 @@ export interface IContextMenuDelegate {
|
||||
actionRunner?: IActionRunner;
|
||||
autoSelectFirstItem?: boolean;
|
||||
anchorAlignment?: AnchorAlignment;
|
||||
anchorAsContainer?: boolean;
|
||||
}
|
||||
|
||||
@@ -16,16 +16,22 @@ import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
|
||||
import { resolvePath } from 'vs/base/common/resources';
|
||||
|
||||
export interface MarkedOptions extends marked.MarkedOptions {
|
||||
baseUrl?: never;
|
||||
}
|
||||
|
||||
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
|
||||
codeBlockRenderCallback?: () => void;
|
||||
baseUrl?: URI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create html nodes for the given content element.
|
||||
*/
|
||||
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: marked.MarkedOptions = {}): HTMLElement {
|
||||
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: MarkedOptions = {}): HTMLElement {
|
||||
const element = createElement(options);
|
||||
|
||||
const _uriMassage = function (part: string): string {
|
||||
@@ -82,6 +88,9 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
if (href) {
|
||||
({ href, dimensions } = parseHrefAndDimensions(href));
|
||||
href = _href(href, true);
|
||||
if (options.baseUrl) {
|
||||
href = resolvePath(options.baseUrl, href).toString();
|
||||
}
|
||||
attributes.push(`src="${href}"`);
|
||||
}
|
||||
if (text) {
|
||||
@@ -101,6 +110,12 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
text = removeMarkdownEscapes(text);
|
||||
}
|
||||
href = _href(href, false);
|
||||
if (options.baseUrl) {
|
||||
const hasScheme = /^\w[\w\d+.-]*:/.test(href);
|
||||
if (!hasScheme) {
|
||||
href = resolvePath(options.baseUrl, href).toString();
|
||||
}
|
||||
}
|
||||
title = removeMarkdownEscapes(title);
|
||||
href = removeMarkdownEscapes(href);
|
||||
if (
|
||||
@@ -187,6 +202,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
}));
|
||||
}
|
||||
|
||||
// Use our own sanitizer so that we can let through only spans.
|
||||
// Otherwise, we'd be letting all html be rendered.
|
||||
// If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize.
|
||||
markedOptions.sanitizer = (html: string): string => {
|
||||
const match = markdown.isTrusted ? html.match(/^(<span[^<]+>)|(<\/\s*span>)$/) : undefined;
|
||||
return match ? html : '';
|
||||
};
|
||||
markedOptions.sanitize = true;
|
||||
markedOptions.renderer = renderer;
|
||||
|
||||
@@ -202,18 +224,32 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
markedOptions
|
||||
);
|
||||
|
||||
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
|
||||
if (token.tag === 'span' && markdown.isTrusted) {
|
||||
if (token.attrs['style'] && Object.keys(token.attrs).length === 1) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
element.innerHTML = insane(renderedMarkdown, {
|
||||
allowedSchemes,
|
||||
// allowedTags should included everything that markdown renders to.
|
||||
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
|
||||
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
|
||||
allowedTags: ['ul', 'li', 'p', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'tr', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'],
|
||||
allowedAttributes: {
|
||||
'a': ['href', 'name', 'target', 'data-href'],
|
||||
'iframe': ['allowfullscreen', 'frameborder', 'src'],
|
||||
'img': ['src', 'title', 'alt', 'width', 'height'],
|
||||
'div': ['class', 'data-code'],
|
||||
'span': ['class'],
|
||||
'span': ['class', 'style'],
|
||||
// https://github.com/microsoft/vscode/issues/95937
|
||||
'th': ['align'],
|
||||
'td': ['align']
|
||||
}
|
||||
},
|
||||
filter
|
||||
});
|
||||
|
||||
signalInnerHTML!();
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface IActionViewItem extends IDisposable {
|
||||
export interface IBaseActionViewItemOptions {
|
||||
draggable?: boolean;
|
||||
isMenu?: boolean;
|
||||
useEventAsContext?: boolean;
|
||||
}
|
||||
|
||||
export class BaseActionViewItem extends Disposable implements IActionViewItem {
|
||||
@@ -178,7 +179,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem {
|
||||
onClick(event: DOM.EventLike): void {
|
||||
DOM.EventHelper.stop(event, true);
|
||||
|
||||
const context = types.isUndefinedOrNull(this._context) ? undefined : this._context;
|
||||
const context = types.isUndefinedOrNull(this._context) ? this.options?.useEventAsContext ? event : undefined : this._context;
|
||||
this.actionRunner.run(this._action, context);
|
||||
}
|
||||
|
||||
@@ -404,6 +405,7 @@ export interface IActionBarOptions {
|
||||
ariaLabel?: string;
|
||||
animated?: boolean;
|
||||
triggerKeys?: ActionTrigger;
|
||||
allowContextMenu?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: IActionBarOptions = {
|
||||
@@ -633,9 +635,11 @@ export class ActionBar extends Disposable implements IActionRunner {
|
||||
actionViewItemElement.setAttribute('role', 'presentation');
|
||||
|
||||
// Prevent native context menu on actions
|
||||
this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}));
|
||||
if (!this.options.allowContextMenu) {
|
||||
this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
}));
|
||||
}
|
||||
|
||||
let item: IActionViewItem | undefined;
|
||||
|
||||
|
||||
Binary file not shown.
@@ -7,3 +7,12 @@
|
||||
position: absolute;
|
||||
z-index: 2500;
|
||||
}
|
||||
|
||||
.context-view.fixed {
|
||||
all: initial;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
position: fixed;
|
||||
z-index: 2500;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface IDelegate {
|
||||
}
|
||||
|
||||
export interface IContextViewProvider {
|
||||
showContextView(delegate: IDelegate): void;
|
||||
showContextView(delegate: IDelegate, container?: HTMLElement): void;
|
||||
hideContextView(): void;
|
||||
layout(): void;
|
||||
}
|
||||
@@ -104,23 +104,25 @@ export class ContextView extends Disposable {
|
||||
|
||||
private container: HTMLElement | null = null;
|
||||
private view: HTMLElement;
|
||||
private useFixedPosition: boolean;
|
||||
private delegate: IDelegate | null = null;
|
||||
private toDisposeOnClean: IDisposable = Disposable.None;
|
||||
private toDisposeOnSetContainer: IDisposable = Disposable.None;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
constructor(container: HTMLElement, useFixedPosition: boolean) {
|
||||
super();
|
||||
|
||||
this.view = DOM.$('.context-view');
|
||||
this.useFixedPosition = false;
|
||||
|
||||
DOM.hide(this.view);
|
||||
|
||||
this.setContainer(container);
|
||||
this.setContainer(container, useFixedPosition);
|
||||
|
||||
this._register(toDisposable(() => this.setContainer(null)));
|
||||
this._register(toDisposable(() => this.setContainer(null, false)));
|
||||
}
|
||||
|
||||
setContainer(container: HTMLElement | null): void {
|
||||
setContainer(container: HTMLElement | null, useFixedPosition: boolean): void {
|
||||
if (this.container) {
|
||||
this.toDisposeOnSetContainer.dispose();
|
||||
this.container.removeChild(this.view);
|
||||
@@ -146,6 +148,8 @@ export class ContextView extends Disposable {
|
||||
|
||||
this.toDisposeOnSetContainer = toDisposeOnSetContainer;
|
||||
}
|
||||
|
||||
this.useFixedPosition = useFixedPosition;
|
||||
}
|
||||
|
||||
show(delegate: IDelegate): void {
|
||||
@@ -254,10 +258,11 @@ export class ContextView extends Disposable {
|
||||
DOM.removeClasses(this.view, 'top', 'bottom', 'left', 'right');
|
||||
DOM.addClass(this.view, anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');
|
||||
DOM.addClass(this.view, anchorAlignment === AnchorAlignment.LEFT ? 'left' : 'right');
|
||||
DOM.toggleClass(this.view, 'fixed', this.useFixedPosition);
|
||||
|
||||
const containerPosition = DOM.getDomNodePagePosition(this.container!);
|
||||
this.view.style.top = `${top - containerPosition.top}px`;
|
||||
this.view.style.left = `${left - containerPosition.left}px`;
|
||||
this.view.style.top = `${top - (this.useFixedPosition ? DOM.getDomNodePagePosition(this.view).top : containerPosition.top)}px`;
|
||||
this.view.style.left = `${left - (this.useFixedPosition ? DOM.getDomNodePagePosition(this.view).left : containerPosition.left)}px`;
|
||||
this.view.style.width = 'initial';
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EventHelper, EventType, removeClass, addClass, append, $, addDisposableListener, addClasses } from 'vs/base/browser/dom';
|
||||
import { IContextMenuDelegate } from 'vs/base/browser/contextmenu';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface ILabelRenderer {
|
||||
(container: HTMLElement): IDisposable | null;
|
||||
@@ -29,7 +30,10 @@ export class BaseDropdown extends ActionRunner {
|
||||
private boxContainer?: HTMLElement;
|
||||
private _label?: HTMLElement;
|
||||
private contents?: HTMLElement;
|
||||
|
||||
private visible: boolean | undefined;
|
||||
private _onDidChangeVisibility = new Emitter<boolean>();
|
||||
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
|
||||
|
||||
constructor(container: HTMLElement, options: IBaseDropdownOptions) {
|
||||
super();
|
||||
@@ -48,7 +52,7 @@ export class BaseDropdown extends ActionRunner {
|
||||
}
|
||||
|
||||
for (const event of [EventType.CLICK, EventType.MOUSE_DOWN, GestureEventType.Tap]) {
|
||||
this._register(addDisposableListener(this._label, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
|
||||
this._register(addDisposableListener(this.element, event, e => EventHelper.stop(e, true))); // prevent default click behaviour to trigger
|
||||
}
|
||||
|
||||
for (const event of [EventType.MOUSE_DOWN, GestureEventType.Tap]) {
|
||||
@@ -101,11 +105,17 @@ export class BaseDropdown extends ActionRunner {
|
||||
}
|
||||
|
||||
show(): void {
|
||||
this.visible = true;
|
||||
if (!this.visible) {
|
||||
this.visible = true;
|
||||
this._onDidChangeVisibility.fire(true);
|
||||
}
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.visible = false;
|
||||
if (this.visible) {
|
||||
this.visible = false;
|
||||
this._onDidChangeVisibility.fire(false);
|
||||
}
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
@@ -256,7 +266,8 @@ export class DropdownMenu extends BaseDropdown {
|
||||
getMenuClassName: () => this.menuClassName,
|
||||
onHide: () => this.onHide(),
|
||||
actionRunner: this.menuOptions ? this.menuOptions.actionRunner : undefined,
|
||||
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT
|
||||
anchorAlignment: this.menuOptions ? this.menuOptions.anchorAlignment : AnchorAlignment.LEFT,
|
||||
anchorAsContainer: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,6 +314,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
this.element.tabIndex = 0;
|
||||
this.element.setAttribute('role', 'button');
|
||||
this.element.setAttribute('aria-haspopup', 'true');
|
||||
this.element.setAttribute('aria-expanded', 'false');
|
||||
this.element.title = this._action.label || '';
|
||||
|
||||
return null;
|
||||
@@ -321,6 +333,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
|
||||
}
|
||||
|
||||
this.dropdownMenu = this._register(new DropdownMenu(container, options));
|
||||
this._register(this.dropdownMenu.onDidChangeVisibility(visible => this.element?.setAttribute('aria-expanded', `${visible}`)));
|
||||
|
||||
this.dropdownMenu.menuOptions = {
|
||||
actionViewItemProvider: this.actionViewItemProvider,
|
||||
|
||||
@@ -380,7 +380,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
|
||||
this.splitview.addView(node, size, index);
|
||||
this.splitview.addView(node, size, index, skipLayout);
|
||||
this._addChild(node, index);
|
||||
this.onDidChildrenChange();
|
||||
}
|
||||
@@ -791,7 +791,7 @@ function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number)
|
||||
newSize += size - totalSize;
|
||||
}
|
||||
|
||||
result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0);
|
||||
result.addChild(flipNode(child, orthogonalSize, newSize), newSize, 0, true);
|
||||
}
|
||||
|
||||
return result as T;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor-hover {
|
||||
.monaco-hover {
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
@@ -16,34 +16,34 @@
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.monaco-editor-hover.hidden {
|
||||
.monaco-hover.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-contents {
|
||||
.monaco-hover .hover-contents {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) {
|
||||
.monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) {
|
||||
max-width: 500px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr {
|
||||
.monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) hr {
|
||||
/* This is a strange rule but it avoids https://github.com/microsoft/vscode/issues/96795, just 100vw on its own caused the actual hover width to increase */
|
||||
min-width: calc(100% + 100vw);
|
||||
}
|
||||
|
||||
.monaco-editor-hover p,
|
||||
.monaco-editor-hover ul {
|
||||
.monaco-hover p,
|
||||
.monaco-hover ul {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.monaco-editor-hover code {
|
||||
.monaco-hover code {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.monaco-editor-hover hr {
|
||||
.monaco-hover hr {
|
||||
margin-top: 4px;
|
||||
margin-bottom: -6px;
|
||||
margin-left: -10px;
|
||||
@@ -51,78 +51,78 @@
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover p:first-child,
|
||||
.monaco-editor-hover ul:first-child {
|
||||
.monaco-hover p:first-child,
|
||||
.monaco-hover ul:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.monaco-editor-hover p:last-child,
|
||||
.monaco-editor-hover ul:last-child {
|
||||
.monaco-hover p:last-child,
|
||||
.monaco-hover ul:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* MarkupContent Layout */
|
||||
.monaco-editor-hover ul {
|
||||
.monaco-hover ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.monaco-editor-hover ol {
|
||||
.monaco-hover ol {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover li > p {
|
||||
.monaco-hover li > p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-editor-hover li > ul {
|
||||
.monaco-hover li > ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.monaco-editor-hover code {
|
||||
.monaco-hover code {
|
||||
border-radius: 3px;
|
||||
padding: 0 0.4em;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .monaco-tokenized-source {
|
||||
.monaco-hover .monaco-tokenized-source {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-row.status-bar {
|
||||
.monaco-hover .hover-row.status-bar {
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-row.status-bar .actions {
|
||||
.monaco-hover .hover-row.status-bar .actions {
|
||||
display: flex;
|
||||
padding: 0px 8px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-row.status-bar .actions .action-container {
|
||||
.monaco-hover .hover-row.status-bar .actions .action-container {
|
||||
margin-right: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon {
|
||||
.monaco-hover .hover-row.status-bar .actions .action-container .action .icon {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .markdown-hover .hover-contents .codicon {
|
||||
.monaco-hover .markdown-hover .hover-contents .codicon {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-contents a.code-link:before {
|
||||
.monaco-hover .hover-contents a.code-link:before {
|
||||
content: '(';
|
||||
}
|
||||
.monaco-editor-hover .hover-contents a.code-link:after {
|
||||
.monaco-hover .hover-contents a.code-link:after {
|
||||
content: ')';
|
||||
}
|
||||
|
||||
.monaco-editor-hover .hover-contents a.code-link {
|
||||
.monaco-hover .hover-contents a.code-link {
|
||||
color: inherit;
|
||||
}
|
||||
.monaco-editor-hover .hover-contents a.code-link > span {
|
||||
.monaco-hover .hover-contents a.code-link > span {
|
||||
text-decoration: underline;
|
||||
/** Hack to force underline to show **/
|
||||
border-bottom: 1px solid transparent;
|
||||
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./hover';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class HoverWidget extends Disposable {
|
||||
|
||||
public readonly containerDomNode: HTMLElement;
|
||||
public readonly contentsDomNode: HTMLElement;
|
||||
private readonly _scrollbar: DomScrollableElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.containerDomNode = document.createElement('div');
|
||||
this.containerDomNode.className = 'monaco-hover';
|
||||
this.containerDomNode.tabIndex = 0;
|
||||
this.containerDomNode.setAttribute('role', 'tooltip');
|
||||
|
||||
this.contentsDomNode = document.createElement('div');
|
||||
this.contentsDomNode.className = 'monaco-hover-content';
|
||||
|
||||
this._scrollbar = this._register(new DomScrollableElement(this.contentsDomNode, {}));
|
||||
this.containerDomNode.appendChild(this._scrollbar.getDomNode());
|
||||
}
|
||||
|
||||
public onContentsChanged(): void {
|
||||
this._scrollbar.scanDomNode();
|
||||
}
|
||||
}
|
||||
|
||||
export function renderHoverAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }, keybindingLabel: string | null): IDisposable {
|
||||
const actionContainer = dom.append(parent, $('div.action-container'));
|
||||
const action = dom.append(actionContainer, $('a.action'));
|
||||
action.setAttribute('href', '#');
|
||||
action.setAttribute('role', 'button');
|
||||
if (actionOptions.iconClass) {
|
||||
dom.append(action, $(`span.icon.${actionOptions.iconClass}`));
|
||||
}
|
||||
const label = dom.append(action, $('span'));
|
||||
label.textContent = keybindingLabel ? `${actionOptions.label} (${keybindingLabel})` : actionOptions.label;
|
||||
return dom.addDisposableListener(actionContainer, dom.EventType.CLICK, e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
actionOptions.run(actionContainer);
|
||||
});
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export class PagedList<T> implements IDisposable {
|
||||
}
|
||||
|
||||
get onDidChangeFocus(): Event<IListEvent<T>> {
|
||||
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
return Event.map(this.list.onDidChangeFocus, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
}
|
||||
|
||||
get onDidOpen(): Event<IListEvent<T>> {
|
||||
@@ -173,11 +173,11 @@ export class PagedList<T> implements IDisposable {
|
||||
}
|
||||
|
||||
get onDidChangeSelection(): Event<IListEvent<T>> {
|
||||
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
return Event.map(this.list.onDidChangeSelection, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
}
|
||||
|
||||
get onPin(): Event<IListEvent<T>> {
|
||||
return Event.map(this.list.onDidPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
|
||||
return Event.map(this.list.onDidPin, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
|
||||
}
|
||||
|
||||
get onContextMenu(): Event<IListContextMenuEvent<T>> {
|
||||
|
||||
@@ -9,8 +9,8 @@ import { Gesture, EventType as TouchEventType, GestureEvent } from 'vs/base/brow
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions } from 'vs/base/common/scrollable';
|
||||
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollEvent, ScrollbarVisibility, INewScrollDimensions, Scrollable } from 'vs/base/common/scrollable';
|
||||
import { RangeMap, shift } from './rangeMap';
|
||||
import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListDragEvent, IListDragAndDrop, ListDragOverEffect } from './list';
|
||||
import { RowCache, IRow } from './rowCache';
|
||||
@@ -44,11 +44,16 @@ export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> {
|
||||
export interface IListViewAccessibilityProvider<T> {
|
||||
getSetSize?(element: T, index: number, listLength: number): number;
|
||||
getPosInSet?(element: T, index: number): number;
|
||||
getRole?(element: T): string;
|
||||
getRole?(element: T): string | undefined;
|
||||
isChecked?(element: T): boolean | undefined;
|
||||
}
|
||||
|
||||
export interface IListViewOptions<T> {
|
||||
export interface IListViewOptionsUpdate {
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly smoothScrolling?: boolean;
|
||||
}
|
||||
|
||||
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
|
||||
readonly dnd?: IListViewDragAndDrop<T>;
|
||||
readonly useShadows?: boolean;
|
||||
readonly verticalScrollMode?: ScrollbarVisibility;
|
||||
@@ -58,7 +63,6 @@ export interface IListViewOptions<T> {
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly accessibilityProvider?: IListViewAccessibilityProvider<T>;
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
}
|
||||
|
||||
@@ -158,7 +162,7 @@ class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilit
|
||||
|
||||
readonly getSetSize: (element: any, index: number, listLength: number) => number;
|
||||
readonly getPosInSet: (element: any, index: number) => number;
|
||||
readonly getRole: (element: T) => string;
|
||||
readonly getRole: (element: T) => string | undefined;
|
||||
readonly isChecked: (element: T) => boolean | undefined;
|
||||
|
||||
constructor(accessibilityProvider?: IListViewAccessibilityProvider<T>) {
|
||||
@@ -204,7 +208,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
private lastRenderHeight: number;
|
||||
private renderWidth = 0;
|
||||
private rowsContainer: HTMLElement;
|
||||
private scrollableElement: ScrollableElement;
|
||||
private scrollable: Scrollable;
|
||||
private scrollableElement: SmoothScrollableElement;
|
||||
private _scrollHeight: number = 0;
|
||||
private scrollableElementUpdateDisposable: IDisposable | null = null;
|
||||
private scrollableElementWidthDelayer = new Delayer<void>(50);
|
||||
@@ -278,18 +283,20 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.rowsContainer = document.createElement('div');
|
||||
this.rowsContainer.className = 'monaco-list-rows';
|
||||
|
||||
if (options.transformOptimization) {
|
||||
const transformOptimization = getOrDefault(options, o => o.transformOptimization, DefaultOptions.transformOptimization);
|
||||
if (transformOptimization) {
|
||||
this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)';
|
||||
}
|
||||
|
||||
this.disposables.add(Gesture.addTarget(this.rowsContainer));
|
||||
|
||||
this.scrollableElement = this.disposables.add(new ScrollableElement(this.rowsContainer, {
|
||||
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => DOM.scheduleAtNextAnimationFrame(cb));
|
||||
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontal: this.horizontalScrolling ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
|
||||
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows)
|
||||
}));
|
||||
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
|
||||
}, this.scrollable));
|
||||
|
||||
this.domNode.appendChild(this.scrollableElement.getDomNode());
|
||||
container.appendChild(this.domNode);
|
||||
@@ -319,6 +326,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
if (options.additionalScrollHeight !== undefined) {
|
||||
this.additionalScrollHeight = options.additionalScrollHeight;
|
||||
}
|
||||
|
||||
if (options.smoothScrolling !== undefined) {
|
||||
this.scrollable.setSmoothScrollDuration(options.smoothScrolling ? 125 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
@@ -651,7 +662,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
if (!item.row) {
|
||||
item.row = this.cache.alloc(item.templateId);
|
||||
const role = this.accessibilityProvider.getRole(item.element);
|
||||
const role = this.accessibilityProvider.getRole(item.element) || 'listitem';
|
||||
item.row!.domNode!.setAttribute('role', role);
|
||||
const checked = this.accessibilityProvider.isChecked(item.element);
|
||||
if (typeof checked !== 'undefined') {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./list';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
import { range, firstIndex, binarySearch } from 'vs/base/common/arrays';
|
||||
@@ -17,7 +16,7 @@ import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardE
|
||||
import { Event, Emitter, EventBufferer } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListError, IKeyboardNavigationDelegate } from './list';
|
||||
import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider } from './listView';
|
||||
import { ListView, IListViewOptions, IListViewDragAndDrop, IListViewAccessibilityProvider, IListViewOptionsUpdate } from './listView';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
|
||||
@@ -26,6 +25,7 @@ import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { matchesPrefix } from 'vs/base/common/filters';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
|
||||
interface ITraitChangeEvent {
|
||||
indexes: number[];
|
||||
@@ -344,6 +344,7 @@ class TypeLabelController<T> implements IDisposable {
|
||||
|
||||
private automaticKeyboardNavigation = true;
|
||||
private triggered = false;
|
||||
private previouslyFocused = -1;
|
||||
|
||||
private readonly enabledDisposables = new DisposableStore();
|
||||
private readonly disposables = new DisposableStore();
|
||||
@@ -393,6 +394,7 @@ class TypeLabelController<T> implements IDisposable {
|
||||
const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i));
|
||||
|
||||
onInput(this.onInput, this, this.enabledDisposables);
|
||||
onClear(this.onClear, this, this.enabledDisposables);
|
||||
|
||||
this.enabled = true;
|
||||
this.triggered = false;
|
||||
@@ -408,6 +410,19 @@ class TypeLabelController<T> implements IDisposable {
|
||||
this.triggered = false;
|
||||
}
|
||||
|
||||
private onClear(): void {
|
||||
const focus = this.list.getFocus();
|
||||
if (focus.length > 0 && focus[0] === this.previouslyFocused) {
|
||||
// List: re-anounce element on typing end since typed keys will interupt aria label of focused element
|
||||
// Do not announce if there was a focus change at the end to prevent duplication https://github.com/microsoft/vscode/issues/95961
|
||||
const ariaLabel = this.list.options.accessibilityProvider?.getAriaLabel(this.list.element(focus[0]));
|
||||
if (ariaLabel) {
|
||||
alert(ariaLabel);
|
||||
}
|
||||
}
|
||||
this.previouslyFocused = -1;
|
||||
}
|
||||
|
||||
private onInput(word: string | null): void {
|
||||
if (!word) {
|
||||
this.state = TypeLabelControllerState.Idle;
|
||||
@@ -426,6 +441,7 @@ class TypeLabelController<T> implements IDisposable {
|
||||
const labelStr = label && label.toString();
|
||||
|
||||
if (typeof labelStr === 'undefined' || matchesPrefix(word, labelStr)) {
|
||||
this.previouslyFocused = start;
|
||||
this.list.setFocus([index]);
|
||||
this.list.reveal(index);
|
||||
return;
|
||||
@@ -1091,10 +1107,9 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListOptionsUpdate {
|
||||
export interface IListOptionsUpdate extends IListViewOptionsUpdate {
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
readonly automaticKeyboardNavigation?: boolean;
|
||||
readonly additionalScrollHeight?: number;
|
||||
}
|
||||
|
||||
export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
@@ -1272,9 +1287,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
this.typeLabelController.updateOptions(this._options);
|
||||
}
|
||||
|
||||
if (optionsUpdate.additionalScrollHeight !== undefined) {
|
||||
this.view.updateOptions(optionsUpdate);
|
||||
}
|
||||
this.view.updateOptions(optionsUpdate);
|
||||
}
|
||||
|
||||
get options(): IListOptions<T> {
|
||||
@@ -1363,7 +1376,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
|
||||
set ariaLabel(value: string) {
|
||||
this._ariaLabel = value;
|
||||
this.view.domNode.setAttribute('aria-label', localize('aria list', "{0}. Use the navigation keys to navigate.", value));
|
||||
this.view.domNode.setAttribute('aria-label', value);
|
||||
}
|
||||
|
||||
domFocus(): void {
|
||||
|
||||
@@ -88,6 +88,9 @@
|
||||
padding: 0.5em 0 0 0;
|
||||
margin-bottom: 0.5em;
|
||||
width: 100%;
|
||||
height: 0px !important;
|
||||
margin-left: .8em !important;
|
||||
margin-right: .8em !important;
|
||||
}
|
||||
|
||||
.monaco-menu .monaco-action-bar.vertical .action-label.separator.text {
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface IMenuOptions {
|
||||
enableMnemonics?: boolean;
|
||||
anchorAlignment?: AnchorAlignment;
|
||||
expandDirection?: Direction;
|
||||
useEventAsContext?: boolean;
|
||||
}
|
||||
|
||||
export interface IMenuStyles {
|
||||
@@ -316,7 +317,7 @@ export class Menu extends ActionBar {
|
||||
|
||||
return menuActionViewItem;
|
||||
} else {
|
||||
const menuItemOptions: IMenuItemOptions = { enableMnemonics: options.enableMnemonics };
|
||||
const menuItemOptions: IMenuItemOptions = { enableMnemonics: options.enableMnemonics, useEventAsContext: options.useEventAsContext };
|
||||
if (options.getKeyBinding) {
|
||||
const keybinding = options.getKeyBinding(action);
|
||||
if (keybinding) {
|
||||
|
||||
@@ -140,8 +140,8 @@ export class MenuBar extends Disposable {
|
||||
eventHandled = false;
|
||||
}
|
||||
|
||||
// Never allow default tab behavior
|
||||
if (event.equals(KeyCode.Tab | KeyMod.Shift) || event.equals(KeyCode.Tab)) {
|
||||
// Never allow default tab behavior when not compact
|
||||
if (this.options.compactMode === undefined && (event.equals(KeyCode.Tab | KeyMod.Shift) || event.equals(KeyCode.Tab))) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ export class MenuBar extends Disposable {
|
||||
createOverflowMenu(): void {
|
||||
const label = this.options.compactMode !== undefined ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', 'More');
|
||||
const title = this.options.compactMode !== undefined ? label : undefined;
|
||||
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': -1, 'aria-label': label, 'title': title, 'aria-haspopup': true });
|
||||
const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.options.compactMode !== undefined ? 0 : -1, 'aria-label': label, 'title': title, 'aria-haspopup': true });
|
||||
const titleElement = $('div.menubar-menu-title.toolbar-toggle-more' + menuBarMoreIcon.cssSelector, { 'role': 'none', 'aria-hidden': true });
|
||||
|
||||
buttonElement.appendChild(titleElement);
|
||||
@@ -326,7 +326,7 @@ export class MenuBar extends Disposable {
|
||||
let event = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
let eventHandled = true;
|
||||
|
||||
if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter)) && !this.isOpen) {
|
||||
if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter) || (this.options.compactMode !== undefined && event.equals(KeyCode.Space))) && !this.isOpen) {
|
||||
this.focusedMenu = { index: MenuBar.OVERFLOW_INDEX };
|
||||
this.openedViaKeyboard = true;
|
||||
this.focusState = MenubarState.OPEN;
|
||||
@@ -945,7 +945,8 @@ export class MenuBar extends Disposable {
|
||||
actionRunner: this.actionRunner,
|
||||
enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics),
|
||||
ariaLabel: withNullAsUndefined(customMenu.buttonElement.getAttribute('aria-label')),
|
||||
expandDirection: this.options.compactMode !== undefined ? this.options.compactMode : Direction.Right
|
||||
expandDirection: this.options.compactMode !== undefined ? this.options.compactMode : Direction.Right,
|
||||
useEventAsContext: true
|
||||
};
|
||||
|
||||
let menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions));
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-mouse-cursor-text {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* The following selector looks a bit funny, but that is needed to cover all the workbench and the editor!! */
|
||||
.vs-dark .mac .monaco-mouse-cursor-text, .hc-black .mac .monaco-mouse-cursor-text,
|
||||
.vs-dark.mac .monaco-mouse-cursor-text, .hc-black.mac .monaco-mouse-cursor-text {
|
||||
cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./mouseCursor';
|
||||
|
||||
export const MOUSE_CURSOR_TEXT_CSS_CLASS_NAME = `monaco-mouse-cursor-text`;
|
||||
@@ -13,13 +13,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.monaco-sash.vertical {
|
||||
cursor: ew-resize;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-sash.mac.vertical {
|
||||
cursor: col-resize;
|
||||
}
|
||||
@@ -32,13 +25,6 @@
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.monaco-sash.horizontal {
|
||||
cursor: ns-resize;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.monaco-sash.mac.horizontal {
|
||||
cursor: row-resize;
|
||||
}
|
||||
@@ -51,52 +37,11 @@
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.monaco-sash:not(.disabled).orthogonal-start::before,
|
||||
.monaco-sash:not(.disabled).orthogonal-end::after {
|
||||
content: ' ';
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
z-index: 100;
|
||||
display: block;
|
||||
cursor: all-scroll;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.monaco-sash.orthogonal-start.vertical::before {
|
||||
left: -2px;
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
.monaco-sash.orthogonal-end.vertical::after {
|
||||
left: -2px;
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.monaco-sash.orthogonal-start.horizontal::before {
|
||||
top: -2px;
|
||||
left: -4px;
|
||||
}
|
||||
|
||||
.monaco-sash.orthogonal-end.horizontal::after {
|
||||
top: -2px;
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.monaco-sash.disabled {
|
||||
cursor: default !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/** Touch **/
|
||||
|
||||
.monaco-sash.touch.vertical {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.monaco-sash.touch.horizontal {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/** Debug **/
|
||||
|
||||
.monaco-sash.debug {
|
||||
@@ -110,4 +55,4 @@
|
||||
.monaco-sash.debug:not(.disabled).orthogonal-start::before,
|
||||
.monaco-sash.debug:not(.disabled).orthogonal-end::after {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import 'vs/css!./sash';
|
||||
import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { isIPad } from 'vs/base/browser/browser';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { EventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
@@ -39,9 +38,18 @@ export interface ISashEvent {
|
||||
}
|
||||
|
||||
export interface ISashOptions {
|
||||
orientation?: Orientation;
|
||||
orthogonalStartSash?: Sash;
|
||||
orthogonalEndSash?: Sash;
|
||||
readonly orientation: Orientation;
|
||||
readonly orthogonalStartSash?: Sash;
|
||||
readonly orthogonalEndSash?: Sash;
|
||||
readonly size?: number;
|
||||
}
|
||||
|
||||
export interface IVerticalSashOptions extends ISashOptions {
|
||||
readonly orientation: Orientation.VERTICAL;
|
||||
}
|
||||
|
||||
export interface IHorizontalSashOptions extends ISashOptions {
|
||||
readonly orientation: Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
export const enum Orientation {
|
||||
@@ -56,12 +64,20 @@ export const enum SashState {
|
||||
Enabled
|
||||
}
|
||||
|
||||
let globalSize = 4;
|
||||
const onDidChangeGlobalSize = new Emitter<number>();
|
||||
export function setGlobalSashSize(size: number): void {
|
||||
globalSize = size;
|
||||
onDidChangeGlobalSize.fire(size);
|
||||
}
|
||||
|
||||
export class Sash extends Disposable {
|
||||
|
||||
private el: HTMLElement;
|
||||
private layoutProvider: ISashLayoutProvider;
|
||||
private hidden: boolean;
|
||||
private orientation!: Orientation;
|
||||
private size: number;
|
||||
|
||||
private _state: SashState = SashState.Enabled;
|
||||
get state(): SashState { return this._state; }
|
||||
@@ -127,7 +143,9 @@ export class Sash extends Disposable {
|
||||
this._orthogonalEndSash = sash;
|
||||
}
|
||||
|
||||
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions = {}) {
|
||||
constructor(container: HTMLElement, layoutProvider: IVerticalSashLayoutProvider, options: ISashOptions);
|
||||
constructor(container: HTMLElement, layoutProvider: IHorizontalSashLayoutProvider, options: ISashOptions);
|
||||
constructor(container: HTMLElement, layoutProvider: ISashLayoutProvider, options: ISashOptions) {
|
||||
super();
|
||||
|
||||
this.el = append(container, $('.monaco-sash'));
|
||||
@@ -142,12 +160,21 @@ export class Sash extends Disposable {
|
||||
this._register(Gesture.addTarget(this.el));
|
||||
this._register(domEvent(this.el, EventType.Start)(this.onTouchStart, this));
|
||||
|
||||
if (isIPad) {
|
||||
// see also https://ux.stackexchange.com/questions/39023/what-is-the-optimum-button-size-of-touch-screen-applications
|
||||
addClass(this.el, 'touch');
|
||||
}
|
||||
if (typeof options.size === 'number') {
|
||||
this.size = options.size;
|
||||
|
||||
this.setOrientation(options.orientation || Orientation.VERTICAL);
|
||||
if (options.orientation === Orientation.VERTICAL) {
|
||||
this.el.style.width = `${this.size}px`;
|
||||
} else {
|
||||
this.el.style.height = `${this.size}px`;
|
||||
}
|
||||
} else {
|
||||
this.size = globalSize;
|
||||
this._register(onDidChangeGlobalSize.event(size => {
|
||||
this.size = size;
|
||||
this.layout();
|
||||
}));
|
||||
}
|
||||
|
||||
this.hidden = false;
|
||||
this.layoutProvider = layoutProvider;
|
||||
@@ -155,11 +182,7 @@ export class Sash extends Disposable {
|
||||
this.orthogonalStartSash = options.orthogonalStartSash;
|
||||
this.orthogonalEndSash = options.orthogonalEndSash;
|
||||
|
||||
toggleClass(this.el, 'debug', DEBUG);
|
||||
}
|
||||
|
||||
setOrientation(orientation: Orientation): void {
|
||||
this.orientation = orientation;
|
||||
this.orientation = options.orientation || Orientation.VERTICAL;
|
||||
|
||||
if (this.orientation === Orientation.HORIZONTAL) {
|
||||
addClass(this.el, 'horizontal');
|
||||
@@ -169,9 +192,9 @@ export class Sash extends Disposable {
|
||||
addClass(this.el, 'vertical');
|
||||
}
|
||||
|
||||
if (this.layoutProvider) {
|
||||
this.layout();
|
||||
}
|
||||
toggleClass(this.el, 'debug', DEBUG);
|
||||
|
||||
this.layout();
|
||||
}
|
||||
|
||||
private onMouseDown(e: MouseEvent): void {
|
||||
@@ -331,11 +354,9 @@ export class Sash extends Disposable {
|
||||
}
|
||||
|
||||
layout(): void {
|
||||
const size = isIPad ? 20 : 4;
|
||||
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
const verticalProvider = (<IVerticalSashLayoutProvider>this.layoutProvider);
|
||||
this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (size / 2) + 'px';
|
||||
this.el.style.left = verticalProvider.getVerticalSashLeft(this) - (this.size / 2) + 'px';
|
||||
|
||||
if (verticalProvider.getVerticalSashTop) {
|
||||
this.el.style.top = verticalProvider.getVerticalSashTop(this) + 'px';
|
||||
@@ -346,7 +367,7 @@ export class Sash extends Disposable {
|
||||
}
|
||||
} else {
|
||||
const horizontalProvider = (<IHorizontalSashLayoutProvider>this.layoutProvider);
|
||||
this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (size / 2) + 'px';
|
||||
this.el.style.top = horizontalProvider.getHorizontalSashTop(this) - (this.size / 2) + 'px';
|
||||
|
||||
if (horizontalProvider.getHorizontalSashLeft) {
|
||||
this.el.style.left = horizontalProvider.getHorizontalSashLeft(this) + 'px';
|
||||
@@ -384,15 +405,15 @@ export class Sash extends Disposable {
|
||||
|
||||
private getOrthogonalSash(e: MouseEvent): Sash | undefined {
|
||||
if (this.orientation === Orientation.VERTICAL) {
|
||||
if (e.offsetY <= 4) {
|
||||
if (e.offsetY <= this.size) {
|
||||
return this.orthogonalStartSash;
|
||||
} else if (e.offsetY >= this.el.clientHeight - 4) {
|
||||
} else if (e.offsetY >= this.el.clientHeight - this.size) {
|
||||
return this.orthogonalEndSash;
|
||||
}
|
||||
} else {
|
||||
if (e.offsetX <= 4) {
|
||||
if (e.offsetX <= this.size) {
|
||||
return this.orthogonalStartSash;
|
||||
} else if (e.offsetX >= this.el.clientWidth - 4) {
|
||||
} else if (e.offsetX >= this.el.clientWidth - this.size) {
|
||||
return this.orthogonalEndSash;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,6 +533,14 @@ export class SmoothScrollableElement extends AbstractScrollableElement {
|
||||
super(element, options, scrollable);
|
||||
}
|
||||
|
||||
public setScrollPosition(update: INewScrollPosition): void {
|
||||
this._scrollable.setScrollPositionNow(update);
|
||||
}
|
||||
|
||||
public getScrollPosition(): IScrollPosition {
|
||||
return this._scrollable.getCurrentScrollPosition();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DomScrollableElement extends ScrollableElement {
|
||||
|
||||
@@ -6,3 +6,9 @@
|
||||
.monaco-select-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-select-box-dropdown-container {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface ISelectBoxOptions {
|
||||
useCustomDrawn?: boolean;
|
||||
ariaLabel?: string;
|
||||
minBottomMargin?: number;
|
||||
optionsAsChildren?: boolean;
|
||||
}
|
||||
|
||||
// Utilize optionItem interface to capture all option parameters
|
||||
|
||||
@@ -90,8 +90,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
|
||||
private _isVisible: boolean;
|
||||
private selectBoxOptions: ISelectBoxOptions;
|
||||
|
||||
public selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}}
|
||||
public selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}}
|
||||
private container?: HTMLElement;
|
||||
private options: ISelectOptionItem[] = [];
|
||||
private selected: number;
|
||||
private readonly _onDidSelect: Emitter<ISelectData>;
|
||||
@@ -310,6 +310,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
dom.addClass(container, 'select-container');
|
||||
container.appendChild(this.selectElement);
|
||||
this.applyStyles();
|
||||
@@ -454,7 +455,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
dom.toggleClass(this.selectElement, 'synthetic-focus', false);
|
||||
},
|
||||
anchorPosition: this._dropDownPosition
|
||||
});
|
||||
}, this.selectBoxOptions.optionsAsChildren ? this.container : undefined);
|
||||
|
||||
// Hide so we can relay out
|
||||
this._isVisible = true;
|
||||
@@ -469,7 +470,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
dom.toggleClass(this.selectElement, 'synthetic-focus', false);
|
||||
},
|
||||
anchorPosition: this._dropDownPosition
|
||||
});
|
||||
}, this.selectBoxOptions.optionsAsChildren ? this.container : undefined);
|
||||
|
||||
// Track initial selection the case user escape, blur
|
||||
this._currentSelection = this.selected;
|
||||
@@ -739,7 +740,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
|
||||
mouseSupport: false,
|
||||
accessibilityProvider: {
|
||||
getAriaLabel: (element) => element.text,
|
||||
getWidgetAriaLabel: () => localize('selectBox', "Select Box"),
|
||||
getWidgetAriaLabel: () => localize({ key: 'selectBox', comment: ['Behave like native select dropdown element.'] }, "Select Box"),
|
||||
getRole: () => 'option',
|
||||
getWidgetRole: () => 'listbox'
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header {
|
||||
height: 22px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
@@ -26,6 +31,12 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header > .twisties {
|
||||
width: 20px;
|
||||
display: flex;
|
||||
@@ -36,6 +47,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .twisties {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.monaco-pane-view .pane > .pane-header.expanded > .twisties::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
@@ -132,6 +148,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 22px;
|
||||
min-width: 19px;
|
||||
|
||||
pointer-events: none; /* very important to not take events away from the parent */
|
||||
transition: opacity 150ms ease-out;
|
||||
|
||||
@@ -106,7 +106,7 @@ export abstract class Pane extends Disposable implements IView {
|
||||
get minimumSize(): number {
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
const minimumBodySize = expanded ? this.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
|
||||
const minimumBodySize = expanded ? this.minimumBodySize : 0;
|
||||
|
||||
return headerSize + minimumBodySize;
|
||||
}
|
||||
@@ -114,7 +114,7 @@ export abstract class Pane extends Disposable implements IView {
|
||||
get maximumSize(): number {
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
const maximumBodySize = expanded ? this.maximumBodySize : this._orientation === Orientation.HORIZONTAL ? 50 : 0;
|
||||
const maximumBodySize = expanded ? this.maximumBodySize : 0;
|
||||
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
@@ -126,7 +126,7 @@ export abstract class Pane extends Disposable implements IView {
|
||||
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
|
||||
this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation;
|
||||
this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title);
|
||||
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
|
||||
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 200 : 120;
|
||||
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
|
||||
|
||||
this.element = $('.pane');
|
||||
@@ -141,6 +141,10 @@ export abstract class Pane extends Disposable implements IView {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.element) {
|
||||
toggleClass(this.element, 'expanded', expanded);
|
||||
}
|
||||
|
||||
this._expanded = !!expanded;
|
||||
this.updateHeader();
|
||||
|
||||
@@ -185,12 +189,21 @@ export abstract class Pane extends Disposable implements IView {
|
||||
|
||||
this._orientation = orientation;
|
||||
|
||||
if (this.element) {
|
||||
toggleClass(this.element, 'horizontal', this.orientation === Orientation.HORIZONTAL);
|
||||
toggleClass(this.element, 'vertical', this.orientation === Orientation.VERTICAL);
|
||||
}
|
||||
|
||||
if (this.header) {
|
||||
this.updateHeader();
|
||||
}
|
||||
}
|
||||
|
||||
render(): void {
|
||||
toggleClass(this.element, 'expanded', this.isExpanded());
|
||||
toggleClass(this.element, 'horizontal', this.orientation === Orientation.HORIZONTAL);
|
||||
toggleClass(this.element, 'vertical', this.orientation === Orientation.VERTICAL);
|
||||
|
||||
this.header = $('.pane-header');
|
||||
append(this.element, this.header);
|
||||
this.header.setAttribute('tabindex', '0');
|
||||
@@ -250,8 +263,6 @@ export abstract class Pane extends Disposable implements IView {
|
||||
style(styles: IPaneStyles): void {
|
||||
this.styles = styles;
|
||||
|
||||
this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : '';
|
||||
|
||||
if (!this.header) {
|
||||
return;
|
||||
}
|
||||
@@ -262,7 +273,6 @@ export abstract class Pane extends Disposable implements IView {
|
||||
protected updateHeader(): void {
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
|
||||
this.header.style.height = `${this.headerSize}px`;
|
||||
this.header.style.lineHeight = `${this.headerSize}px`;
|
||||
toggleClass(this.header, 'hidden', !this.headerVisible);
|
||||
toggleClass(this.header, 'expanded', expanded);
|
||||
@@ -272,6 +282,7 @@ export abstract class Pane extends Disposable implements IView {
|
||||
this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : '';
|
||||
this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : '';
|
||||
this._dropBackground = this.styles.dropBackground;
|
||||
this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : '';
|
||||
}
|
||||
|
||||
protected abstract renderHeader(container: HTMLElement): void;
|
||||
|
||||
@@ -331,8 +331,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
addView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length): void {
|
||||
this.doAddView(view, size, index, false);
|
||||
addView(view: IView<TLayoutContext>, size: number | Sizing, index = this.viewItems.length, skipLayout?: boolean): void {
|
||||
this.doAddView(view, size, index, skipLayout);
|
||||
}
|
||||
|
||||
removeView(index: number, sizing?: Sizing): IView<TLayoutContext> {
|
||||
@@ -689,13 +689,17 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
|
||||
// Add sash
|
||||
if (this.viewItems.length > 1) {
|
||||
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) } : { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) };
|
||||
const sash = new Sash(this.sashContainer, layoutProvider, {
|
||||
orientation,
|
||||
orthogonalStartSash: this.orthogonalStartSash,
|
||||
orthogonalEndSash: this.orthogonalEndSash
|
||||
});
|
||||
const sash = this.orientation === Orientation.VERTICAL
|
||||
? new Sash(this.sashContainer, { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) }, {
|
||||
orientation: Orientation.HORIZONTAL,
|
||||
orthogonalStartSash: this.orthogonalStartSash,
|
||||
orthogonalEndSash: this.orthogonalEndSash
|
||||
})
|
||||
: new Sash(this.sashContainer, { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }, {
|
||||
orientation: Orientation.VERTICAL,
|
||||
orthogonalStartSash: this.orthogonalStartSash,
|
||||
orthogonalEndSash: this.orthogonalEndSash
|
||||
});
|
||||
|
||||
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey })
|
||||
@@ -951,7 +955,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
|
||||
position += this.viewItems[i].size;
|
||||
|
||||
if (this.sashItems[i].sash === sash) {
|
||||
return Math.min(position, this.contentSize - 2);
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -707,7 +707,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
|
||||
.map(e => new StandardKeyboardEvent(e))
|
||||
.filter(this.keyboardNavigationEventFilter || (() => true))
|
||||
.filter(() => this.automaticKeyboardNavigation || this.triggered)
|
||||
.filter(e => this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
|
||||
.filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
|
||||
.forEach(e => { e.stopPropagation(); e.preventDefault(); })
|
||||
.event;
|
||||
|
||||
@@ -962,6 +962,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
|
||||
readonly simpleKeyboardNavigation?: boolean;
|
||||
readonly filterOnType?: boolean;
|
||||
readonly openOnSingleClick?: boolean;
|
||||
readonly smoothScrolling?: boolean;
|
||||
}
|
||||
|
||||
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
|
||||
@@ -1360,7 +1361,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
|
||||
this.view.updateOptions({
|
||||
enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
|
||||
automaticKeyboardNavigation: this._options.automaticKeyboardNavigation
|
||||
automaticKeyboardNavigation: this._options.automaticKeyboardNavigation,
|
||||
smoothScrolling: this._options.smoothScrolling
|
||||
});
|
||||
|
||||
if (this.typeFilterController) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export class CollapseAllAction<TInput, T, TFilterData = void> extends Action {
|
||||
super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'collapse-all', enabled);
|
||||
}
|
||||
|
||||
async run(context?: any): Promise<any> {
|
||||
async run(): Promise<any> {
|
||||
this.viewer.collapseAll();
|
||||
this.viewer.setSelection([]);
|
||||
this.viewer.setFocus([]);
|
||||
|
||||
@@ -764,7 +764,7 @@ export class IdleValue<T> {
|
||||
this._handle.dispose();
|
||||
}
|
||||
|
||||
getValue(): T {
|
||||
get value(): T {
|
||||
if (!this._didRun) {
|
||||
this._handle.dispose();
|
||||
this._executor();
|
||||
|
||||
@@ -342,7 +342,17 @@ export const enum CharCode {
|
||||
* Unicode Character 'LINE SEPARATOR' (U+2028)
|
||||
* http://www.fileformat.info/info/unicode/char/2028/index.htm
|
||||
*/
|
||||
LINE_SEPARATOR_2028 = 8232,
|
||||
LINE_SEPARATOR = 0x2028,
|
||||
/**
|
||||
* Unicode Character 'PARAGRAPH SEPARATOR' (U+2029)
|
||||
* http://www.fileformat.info/info/unicode/char/2029/index.htm
|
||||
*/
|
||||
PARAGRAPH_SEPARATOR = 0x2029,
|
||||
/**
|
||||
* Unicode Character 'NEXT LINE' (U+0085)
|
||||
* http://www.fileformat.info/info/unicode/char/0085/index.htm
|
||||
*/
|
||||
NEXT_LINE = 0x0085,
|
||||
|
||||
// http://www.fileformat.info/info/unicode/category/Sk/list.htm
|
||||
U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX
|
||||
@@ -422,4 +432,4 @@ export const enum CharCode {
|
||||
* http://www.fileformat.info/info/unicode/char/feff/index.htm
|
||||
*/
|
||||
UTF8_BOM = 65279
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export namespace Codicon {
|
||||
export const collapseAll = new Codicon('collapse-all', { character: '\\eac5' });
|
||||
export const colorMode = new Codicon('color-mode', { character: '\\eac6' });
|
||||
export const commentDiscussion = new Codicon('comment-discussion', { character: '\\eac7' });
|
||||
export const compareChanges = new Codicon('compare-changes', { character: '\\eac8' });
|
||||
export const compareChanges = new Codicon('compare-changes', { character: '\\eafd' });
|
||||
export const creditCard = new Codicon('credit-card', { character: '\\eac9' });
|
||||
export const dash = new Codicon('dash', { character: '\\eacc' });
|
||||
export const dashboard = new Codicon('dashboard', { character: '\\eacd' });
|
||||
@@ -448,7 +448,6 @@ export namespace Codicon {
|
||||
export const debugReverseContinue = new Codicon('debug-reverse-continue', { character: '\\eb8e' });
|
||||
export const debugStepBack = new Codicon('debug-step-back', { character: '\\eb8f' });
|
||||
export const debugRestartFrame = new Codicon('debug-restart-frame', { character: '\\eb90' });
|
||||
export const debugAlternate = new Codicon('debug-alternate', { character: '\\eb91' });
|
||||
export const callIncoming = new Codicon('call-incoming', { character: '\\eb92' });
|
||||
export const callOutgoing = new Codicon('call-outgoing', { character: '\\eb93' });
|
||||
export const menu = new Codicon('menu', { character: '\\eb94' });
|
||||
@@ -465,8 +464,13 @@ export namespace Codicon {
|
||||
export const syncIgnored = new Codicon('sync-ignored', { character: '\\eb9f' });
|
||||
export const pinned = new Codicon('pinned', { character: '\\eba0' });
|
||||
export const githubInverted = new Codicon('github-inverted', { character: '\\eba1' });
|
||||
export const debugAlt2 = new Codicon('debug-alt-2', { character: '\\f101' });
|
||||
export const debugAlt = new Codicon('debug-alt', { character: '\\f102' });
|
||||
export const debugAlt = new Codicon('debug-alt', { character: '\\eb91' });
|
||||
export const serverProcess = new Codicon('server-process', { character: '\\eba2' });
|
||||
export const serverEnvironment = new Codicon('server-environment', { character: '\\eba3' });
|
||||
export const pass = new Codicon('pass', { character: '\\eba4' });
|
||||
export const stopCircle = new Codicon('stop-circle', { character: '\\eba5' });
|
||||
export const playCircle = new Codicon('play-circle', { character: '\\eba6' });
|
||||
export const record = new Codicon('record', { character: '\\eba7' });
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
import { sep } from 'vs/base/common/path';
|
||||
import { IdleValue } from 'vs/base/common/async';
|
||||
|
||||
const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
|
||||
// When comparing large numbers of strings, such as in sorting large arrays, is better for
|
||||
// performance to create an Intl.Collator object and use the function provided by its compare
|
||||
// property than it is to use String.prototype.localeCompare()
|
||||
|
||||
// A collator with numeric sorting enabled, and no sensitivity to case or to accents
|
||||
const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }> = new IdleValue(() => {
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
||||
return {
|
||||
collator: collator,
|
||||
@@ -14,20 +19,64 @@ const intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumer
|
||||
};
|
||||
});
|
||||
|
||||
// A collator with numeric sorting enabled.
|
||||
const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
||||
const collator = new Intl.Collator(undefined, { numeric: true });
|
||||
return {
|
||||
collator: collator
|
||||
};
|
||||
});
|
||||
|
||||
// A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case.
|
||||
const intlFileNameCollatorNumericCaseInsenstive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => {
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' });
|
||||
return {
|
||||
collator: collator
|
||||
};
|
||||
});
|
||||
|
||||
export function compareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
const a = one || '';
|
||||
const b = other || '';
|
||||
const result = intlFileNameCollator.getValue().collator.compare(a, b);
|
||||
const result = intlFileNameCollatorBaseNumeric.value.collator.compare(a, b);
|
||||
|
||||
// Using the numeric option in the collator will
|
||||
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||
if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && a !== b) {
|
||||
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && a !== b) {
|
||||
return a < b ? -1 : 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Compares filenames by name then extension, sorting numbers numerically instead of alphabetically. */
|
||||
export function compareFileNamesNumeric(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||
let result;
|
||||
|
||||
// Check for name differences, comparing numbers numerically instead of alphabetically.
|
||||
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for case insensitive extension differences, comparing numbers numerically instead of alphabetically.
|
||||
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Disambiguate the extension case if needed.
|
||||
if (oneExtension !== otherExtension) {
|
||||
return collatorNumeric.compare(oneExtension, otherExtension);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const FileNameMatch = /^(.*?)(\.([^.]*))?$/;
|
||||
|
||||
export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number {
|
||||
@@ -54,19 +103,19 @@ export function compareFileExtensions(one: string | null, other: string | null):
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other);
|
||||
|
||||
let result = intlFileNameCollator.getValue().collator.compare(oneExtension, otherExtension);
|
||||
let result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneExtension, otherExtension);
|
||||
|
||||
if (result === 0) {
|
||||
// Using the numeric option in the collator will
|
||||
// make compare(`foo1`, `foo01`) === 0. We must disambiguate.
|
||||
if (intlFileNameCollator.getValue().collatorIsNumeric && oneExtension !== otherExtension) {
|
||||
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && oneExtension !== otherExtension) {
|
||||
return oneExtension < otherExtension ? -1 : 1;
|
||||
}
|
||||
|
||||
// Extensions are equal, compare filenames
|
||||
result = intlFileNameCollator.getValue().collator.compare(oneName, otherName);
|
||||
result = intlFileNameCollatorBaseNumeric.value.collator.compare(oneName, otherName);
|
||||
|
||||
if (intlFileNameCollator.getValue().collatorIsNumeric && result === 0 && oneName !== otherName) {
|
||||
if (intlFileNameCollatorBaseNumeric.value.collatorIsNumeric && result === 0 && oneName !== otherName) {
|
||||
return oneName < otherName ? -1 : 1;
|
||||
}
|
||||
}
|
||||
@@ -74,10 +123,63 @@ export function compareFileExtensions(one: string | null, other: string | null):
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractNameAndExtension(str?: string | null): [string, string] {
|
||||
/** Compares filenames by extenson, then by name. Sorts numbers numerically, not alphabetically. */
|
||||
export function compareFileExtensionsNumeric(one: string | null, other: string | null): number {
|
||||
const [oneName, oneExtension] = extractNameAndExtension(one, true);
|
||||
const [otherName, otherExtension] = extractNameAndExtension(other, true);
|
||||
const collatorNumeric = intlFileNameCollatorNumeric.value.collator;
|
||||
const collatorNumericCaseInsensitive = intlFileNameCollatorNumericCaseInsenstive.value.collator;
|
||||
let result;
|
||||
|
||||
// Check for extension differences, ignoring differences in case and comparing numbers numerically.
|
||||
result = compareAndDisambiguateByLength(collatorNumericCaseInsensitive, oneExtension, otherExtension);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Compare names.
|
||||
result = compareAndDisambiguateByLength(collatorNumeric, oneName, otherName);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Disambiguate extension case if needed.
|
||||
if (oneExtension !== otherExtension) {
|
||||
return collatorNumeric.compare(oneExtension, otherExtension);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Extracts the name and extension from a full filename, with optional special handling for dotfiles */
|
||||
function extractNameAndExtension(str?: string | null, dotfilesAsNames = false): [string, string] {
|
||||
const match = str ? FileNameMatch.exec(str) as Array<string> : ([] as Array<string>);
|
||||
|
||||
return [(match && match[1]) || '', (match && match[3]) || ''];
|
||||
let result: [string, string] = [(match && match[1]) || '', (match && match[3]) || ''];
|
||||
|
||||
// if the dotfilesAsNames option is selected, treat an empty filename with an extension,
|
||||
// or a filename that starts with a dot, as a dotfile name
|
||||
if (dotfilesAsNames && (!result[0] && result[1] || result[0] && result[0].charAt(0) === '.')) {
|
||||
result = [result[0] + '.' + result[1], ''];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function compareAndDisambiguateByLength(collator: Intl.Collator, one: string, other: string) {
|
||||
// Check for differences
|
||||
let result = collator.compare(one, other);
|
||||
if (result !== 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// In a numeric comparison, `foo1` and `foo01` will compare as equivalent.
|
||||
// Disambiguate by sorting the shorter string first.
|
||||
if (one.length !== other.length) {
|
||||
return one.length < other.length ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function comparePathComponents(one: string, other: string, caseSensitive = false): number {
|
||||
|
||||
@@ -328,8 +328,8 @@ export namespace Event {
|
||||
}
|
||||
|
||||
export interface NodeEventEmitter {
|
||||
on(event: string | symbol, listener: Function): this;
|
||||
removeListener(event: string | symbol, listener: Function): this;
|
||||
on(event: string | symbol, listener: Function): unknown;
|
||||
removeListener(event: string | symbol, listener: Function): unknown;
|
||||
}
|
||||
|
||||
export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { isWindows } from 'vs/base/common/platform';
|
||||
import { startsWithIgnoreCase, equalsIgnoreCase, rtrim } from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { sep, posix, isAbsolute, join, normalize } from 'vs/base/common/path';
|
||||
import { isNumber } from 'vs/base/common/types';
|
||||
|
||||
export function isPathSeparator(code: number) {
|
||||
return code === CharCode.Slash || code === CharCode.Backslash;
|
||||
@@ -300,3 +301,38 @@ export function indexOfPath(path: string, candidate: string, ignoreCase: boolean
|
||||
|
||||
return path.indexOf(candidate);
|
||||
}
|
||||
|
||||
export interface IPathWithLineAndColumn {
|
||||
path: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
}
|
||||
|
||||
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
|
||||
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
|
||||
|
||||
let path: string | undefined = undefined;
|
||||
let line: number | undefined = undefined;
|
||||
let column: number | undefined = undefined;
|
||||
|
||||
segments.forEach(segment => {
|
||||
const segmentAsNumber = Number(segment);
|
||||
if (!isNumber(segmentAsNumber)) {
|
||||
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
|
||||
} else if (line === undefined) {
|
||||
line = segmentAsNumber;
|
||||
} else if (column === undefined) {
|
||||
column = segmentAsNumber;
|
||||
}
|
||||
});
|
||||
|
||||
if (!path) {
|
||||
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
|
||||
}
|
||||
|
||||
return {
|
||||
path,
|
||||
line: line !== undefined ? line : undefined,
|
||||
column: column !== undefined ? column : line !== undefined ? 1 : undefined // if we have a line, make sure column is also set
|
||||
};
|
||||
}
|
||||
|
||||
@@ -833,7 +833,7 @@ export interface IPreparedQuery extends IPreparedQueryPiece {
|
||||
values: IPreparedQueryPiece[] | undefined;
|
||||
|
||||
/**
|
||||
* Wether the query contains path separator(s) or not.
|
||||
* Whether the query contains path separator(s) or not.
|
||||
*/
|
||||
containsPathSeparator: boolean;
|
||||
}
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ export function insane(
|
||||
readonly allowedSchemes?: readonly string[],
|
||||
readonly allowedTags?: readonly string[],
|
||||
readonly allowedAttributes?: { readonly [key: string]: string[] },
|
||||
readonly filter?: (token: { tag: string, attrs: { readonly [key: string]: string } }) => boolean,
|
||||
},
|
||||
strict?: boolean,
|
||||
): string;
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
export namespace Iterable {
|
||||
|
||||
export function is<T = any>(thing: any): thing is IterableIterator<T> {
|
||||
return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function';
|
||||
}
|
||||
|
||||
const _empty: Iterable<any> = Object.freeze([]);
|
||||
export function empty<T = any>(): Iterable<T> {
|
||||
return _empty;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
|
||||
/**
|
||||
* Enables logging of potentially leaked disposables.
|
||||
@@ -54,26 +55,26 @@ export function isDisposable<E extends object>(thing: E): thing is E & IDisposab
|
||||
|
||||
export function dispose<T extends IDisposable>(disposable: T): T;
|
||||
export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
|
||||
export function dispose<T extends IDisposable, A extends IterableIterator<T> = IterableIterator<T>>(disposables: IterableIterator<T>): A;
|
||||
export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
|
||||
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
|
||||
export function dispose<T extends IDisposable>(disposables: T | T[] | undefined): T | T[] | undefined {
|
||||
if (Array.isArray(disposables)) {
|
||||
disposables.forEach(d => {
|
||||
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
|
||||
if (Iterable.is(arg)) {
|
||||
for (let d of arg) {
|
||||
if (d) {
|
||||
markTracked(d);
|
||||
d.dispose();
|
||||
}
|
||||
});
|
||||
return [];
|
||||
} else if (disposables) {
|
||||
markTracked(disposables);
|
||||
disposables.dispose();
|
||||
return disposables;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return arg;
|
||||
} else if (arg) {
|
||||
markTracked(arg);
|
||||
arg.dispose();
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
|
||||
disposables.forEach(markTracked);
|
||||
return trackDisposable({ dispose: () => dispose(disposables) });
|
||||
|
||||
+69
-50
@@ -206,7 +206,7 @@ export class UriIterator implements IKeyIterator<URI> {
|
||||
|
||||
cmp(a: string): number {
|
||||
if (this._states[this._stateIdx] === UriIteratorState.Scheme) {
|
||||
return compareSubstringIgnoreCase(a, this._value.scheme);
|
||||
return compare(a, this._value.scheme);
|
||||
} else if (this._states[this._stateIdx] === UriIteratorState.Authority) {
|
||||
return compareSubstringIgnoreCase(a, this._value.authority);
|
||||
} else if (this._states[this._stateIdx] === UriIteratorState.Path) {
|
||||
@@ -486,11 +486,9 @@ export class ResourceMap<T> implements Map<URI, T> {
|
||||
readonly [Symbol.toStringTag] = 'ResourceMap';
|
||||
|
||||
protected readonly map: Map<string, T>;
|
||||
protected readonly ignoreCase?: boolean;
|
||||
|
||||
constructor() {
|
||||
this.map = new Map<string, T>();
|
||||
this.ignoreCase = false; // in the future this should be an uri-comparator
|
||||
constructor(other?: ResourceMap<T>) {
|
||||
this.map = other ? new Map(other.map) : new Map();
|
||||
}
|
||||
|
||||
set(resource: URI, value: T): this {
|
||||
@@ -550,20 +548,7 @@ export class ResourceMap<T> implements Map<URI, T> {
|
||||
}
|
||||
|
||||
private toKey(resource: URI): string {
|
||||
let key = resource.toString();
|
||||
if (this.ignoreCase) {
|
||||
key = key.toLowerCase();
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
clone(): ResourceMap<T> {
|
||||
const resourceMap = new ResourceMap<T>();
|
||||
|
||||
this.map.forEach((value, key) => resourceMap.map.set(key, value));
|
||||
|
||||
return resourceMap;
|
||||
return resource.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,18 +565,23 @@ export const enum Touch {
|
||||
AsNew = 2
|
||||
}
|
||||
|
||||
export class LinkedMap<K, V> {
|
||||
export class LinkedMap<K, V> implements Map<K, V> {
|
||||
|
||||
readonly [Symbol.toStringTag] = 'LinkedMap';
|
||||
|
||||
private _map: Map<K, Item<K, V>>;
|
||||
private _head: Item<K, V> | undefined;
|
||||
private _tail: Item<K, V> | undefined;
|
||||
private _size: number;
|
||||
|
||||
private _state: number;
|
||||
|
||||
constructor() {
|
||||
this._map = new Map<K, Item<K, V>>();
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._size = 0;
|
||||
this._state = 0;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
@@ -599,6 +589,7 @@ export class LinkedMap<K, V> {
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._size = 0;
|
||||
this._state++;
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
@@ -632,7 +623,7 @@ export class LinkedMap<K, V> {
|
||||
return item.value;
|
||||
}
|
||||
|
||||
set(key: K, value: V, touch: Touch = Touch.None): void {
|
||||
set(key: K, value: V, touch: Touch = Touch.None): this {
|
||||
let item = this._map.get(key);
|
||||
if (item) {
|
||||
item.value = value;
|
||||
@@ -658,6 +649,7 @@ export class LinkedMap<K, V> {
|
||||
this._map.set(key, item);
|
||||
this._size++;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(key: K): boolean {
|
||||
@@ -690,6 +682,7 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
|
||||
const state = this._state;
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
if (thisArg) {
|
||||
@@ -697,38 +690,25 @@ export class LinkedMap<K, V> {
|
||||
} else {
|
||||
callbackfn(current.value, current.key, this);
|
||||
}
|
||||
if (this._state !== state) {
|
||||
throw new Error(`LinkedMap got modified during iteration.`);
|
||||
}
|
||||
current = current.next;
|
||||
}
|
||||
}
|
||||
|
||||
values(): V[] {
|
||||
const result: V[] = [];
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
result.push(current.value);
|
||||
current = current.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
keys(): K[] {
|
||||
const result: K[] = [];
|
||||
let current = this._head;
|
||||
while (current) {
|
||||
result.push(current.key);
|
||||
current = current.next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* VS Code / Monaco editor runs on es5 which has no Symbol.iterator
|
||||
keys(): IterableIterator<K> {
|
||||
const current = this._head;
|
||||
const map = this;
|
||||
const state = this._state;
|
||||
let current = this._head;
|
||||
const iterator: IterableIterator<K> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next():IteratorResult<K> {
|
||||
next(): IteratorResult<K> {
|
||||
if (map._state !== state) {
|
||||
throw new Error(`LinkedMap got modified during iteration.`);
|
||||
}
|
||||
if (current) {
|
||||
const result = { value: current.key, done: false };
|
||||
current = current.next;
|
||||
@@ -742,12 +722,17 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
|
||||
values(): IterableIterator<V> {
|
||||
const current = this._head;
|
||||
const map = this;
|
||||
const state = this._state;
|
||||
let current = this._head;
|
||||
const iterator: IterableIterator<V> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next():IteratorResult<V> {
|
||||
next(): IteratorResult<V> {
|
||||
if (map._state !== state) {
|
||||
throw new Error(`LinkedMap got modified during iteration.`);
|
||||
}
|
||||
if (current) {
|
||||
const result = { value: current.value, done: false };
|
||||
current = current.next;
|
||||
@@ -759,7 +744,34 @@ export class LinkedMap<K, V> {
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
*/
|
||||
|
||||
entries(): IterableIterator<[K, V]> {
|
||||
const map = this;
|
||||
const state = this._state;
|
||||
let current = this._head;
|
||||
const iterator: IterableIterator<[K, V]> = {
|
||||
[Symbol.iterator]() {
|
||||
return iterator;
|
||||
},
|
||||
next(): IteratorResult<[K, V]> {
|
||||
if (map._state !== state) {
|
||||
throw new Error(`LinkedMap got modified during iteration.`);
|
||||
}
|
||||
if (current) {
|
||||
const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
|
||||
current = current.next;
|
||||
return result;
|
||||
} else {
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
}
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[K, V]> {
|
||||
return this.entries();
|
||||
}
|
||||
|
||||
protected trimOld(newSize: number) {
|
||||
if (newSize >= this.size) {
|
||||
@@ -781,6 +793,7 @@ export class LinkedMap<K, V> {
|
||||
if (current) {
|
||||
current.previous = undefined;
|
||||
}
|
||||
this._state++;
|
||||
}
|
||||
|
||||
private addItemFirst(item: Item<K, V>): void {
|
||||
@@ -794,6 +807,7 @@ export class LinkedMap<K, V> {
|
||||
this._head.previous = item;
|
||||
}
|
||||
this._head = item;
|
||||
this._state++;
|
||||
}
|
||||
|
||||
private addItemLast(item: Item<K, V>): void {
|
||||
@@ -807,6 +821,7 @@ export class LinkedMap<K, V> {
|
||||
this._tail.next = item;
|
||||
}
|
||||
this._tail = item;
|
||||
this._state++;
|
||||
}
|
||||
|
||||
private removeItem(item: Item<K, V>): void {
|
||||
@@ -843,6 +858,7 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
item.next = undefined;
|
||||
item.previous = undefined;
|
||||
this._state++;
|
||||
}
|
||||
|
||||
private touch(item: Item<K, V>, touch: Touch): void {
|
||||
@@ -879,6 +895,7 @@ export class LinkedMap<K, V> {
|
||||
item.next = this._head;
|
||||
this._head.previous = item;
|
||||
this._head = item;
|
||||
this._state++;
|
||||
} else if (touch === Touch.AsNew) {
|
||||
if (item === this._tail) {
|
||||
return;
|
||||
@@ -902,6 +919,7 @@ export class LinkedMap<K, V> {
|
||||
item.previous = this._tail;
|
||||
this._tail.next = item;
|
||||
this._tail = item;
|
||||
this._state++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,17 +971,18 @@ export class LRUCache<K, V> extends LinkedMap<K, V> {
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
return super.get(key, Touch.AsNew);
|
||||
get(key: K, touch: Touch = Touch.AsNew): V | undefined {
|
||||
return super.get(key, touch);
|
||||
}
|
||||
|
||||
peek(key: K): V | undefined {
|
||||
return super.get(key, Touch.None);
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
set(key: K, value: V): this {
|
||||
super.set(key, value, Touch.AsNew);
|
||||
this.checkTrim();
|
||||
return this;
|
||||
}
|
||||
|
||||
private checkTrim() {
|
||||
|
||||
@@ -335,3 +335,13 @@ export function getMediaMime(path: string): string | undefined {
|
||||
const ext = extname(path);
|
||||
return mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
|
||||
export function getExtensionForMimeType(mimeType: string): string | undefined {
|
||||
for (const extension in mapExtToMediaMimes) {
|
||||
if (mapExtToMediaMimes[extension] === mimeType) {
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ export namespace Schemas {
|
||||
export const vscodeSettings = 'vscode-settings';
|
||||
|
||||
export const webviewPanel = 'webview-panel';
|
||||
|
||||
export const vscodeWebviewResource = 'vscode-webview-resource';
|
||||
}
|
||||
|
||||
class RemoteAuthoritiesImpl {
|
||||
|
||||
+346
-231
@@ -6,7 +6,7 @@
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import { URI, uriToFsPath } from 'vs/base/common/uri';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { equalsIgnoreCase, compare as strCompare, compareIgnoreCase } from 'vs/base/common/strings';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
@@ -17,248 +17,363 @@ export function originalFSPath(uri: URI): string {
|
||||
return uriToFsPath(uri, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
|
||||
* URI queries are included, fragments are ignored.
|
||||
*/
|
||||
export function getComparisonKey(resource: URI, caseInsensitivePath = hasToIgnoreCase(resource)): string {
|
||||
let path = resource.path || '/';
|
||||
if (caseInsensitivePath) {
|
||||
path = path.toLowerCase();
|
||||
}
|
||||
return resource.with({ authority: resource.authority.toLowerCase(), path: path, fragment: null }).toString();
|
||||
//#region IExtUri
|
||||
|
||||
export interface IExtUri {
|
||||
|
||||
// --- identity
|
||||
|
||||
/**
|
||||
* Compares two uris.
|
||||
*
|
||||
* @param uri1 Uri
|
||||
* @param uri2 Uri
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
compare(uri1: URI, uri2: URI, ignoreFragment?: boolean): number;
|
||||
|
||||
/**
|
||||
* Tests whether two uris are equal
|
||||
*
|
||||
* @param uri1 Uri
|
||||
* @param uri2 Uri
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
|
||||
*
|
||||
* @param base A uri which is "longer"
|
||||
* @param parentCandidate A uri which is "shorter" then `base`
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment?: boolean): boolean;
|
||||
|
||||
/**
|
||||
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
|
||||
* @see ResourceMap
|
||||
* @param uri Uri
|
||||
* @param ignoreFragment Ignore the fragment (defaults to `false`)
|
||||
*/
|
||||
getComparisonKey(uri: URI, ignoreFragment?: boolean): string;
|
||||
|
||||
// --- path math
|
||||
|
||||
basenameOrAuthority(resource: URI): string;
|
||||
|
||||
/**
|
||||
* Returns the basename of the path component of an uri.
|
||||
* @param resource
|
||||
*/
|
||||
basename(resource: URI): string;
|
||||
|
||||
/**
|
||||
* Returns the extension of the path component of an uri.
|
||||
* @param resource
|
||||
*/
|
||||
extname(resource: URI): string;
|
||||
/**
|
||||
* Return a URI representing the directory of a URI path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @returns The URI representing the directory of the input URI.
|
||||
*/
|
||||
dirname(resource: URI): URI;
|
||||
/**
|
||||
* Join a URI path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @param pathFragment The path fragment to add to the URI path.
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
joinPath(resource: URI, ...pathFragment: string[]): URI
|
||||
/**
|
||||
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
|
||||
*
|
||||
* @param resource The URI to normalize the path.
|
||||
* @returns The URI with the normalized path.
|
||||
*/
|
||||
normalizePath(resource: URI): URI;
|
||||
/**
|
||||
*
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
relativePath(from: URI, to: URI): string | undefined;
|
||||
/**
|
||||
* Resolves an absolute or relative path against a base URI.
|
||||
* The path can be relative or absolute posix or a Windows path
|
||||
*/
|
||||
resolvePath(base: URI, path: string): URI;
|
||||
|
||||
// --- misc
|
||||
|
||||
/**
|
||||
* Returns true if the URI path is absolute.
|
||||
*/
|
||||
isAbsolutePath(resource: URI): boolean;
|
||||
/**
|
||||
* Tests whether the two authorities are the same
|
||||
*/
|
||||
isEqualAuthority(a1: string, a2: string): boolean;
|
||||
/**
|
||||
* Returns true if the URI path has a trailing path separator
|
||||
*/
|
||||
hasTrailingPathSeparator(resource: URI, sep?: string): boolean;
|
||||
/**
|
||||
* Removes a trailing path separator, if there's one.
|
||||
* Important: Doesn't remove the first slash, it would make the URI invalid
|
||||
*/
|
||||
removeTrailingPathSeparator(resource: URI, sep?: string): URI;
|
||||
/**
|
||||
* Adds a trailing path separator to the URI if there isn't one already.
|
||||
* For example, c:\ would be unchanged, but c:\users would become c:\users\
|
||||
*/
|
||||
addTrailingPathSeparator(resource: URI, sep?: string): URI;
|
||||
}
|
||||
|
||||
export function hasToIgnoreCase(resource: URI | undefined): boolean {
|
||||
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
|
||||
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
|
||||
return resource && resource.scheme === Schemas.file ? !isLinux : true;
|
||||
}
|
||||
export class ExtUri implements IExtUri {
|
||||
|
||||
export function basenameOrAuthority(resource: URI): string {
|
||||
return basename(resource) || resource.authority;
|
||||
}
|
||||
constructor(private _ignorePathCasing: (uri: URI) => boolean) { }
|
||||
|
||||
/**
|
||||
* Tests whether a `candidate` URI is a parent or equal of a given `base` URI.
|
||||
* URI queries must match, fragments are ignored.
|
||||
* @param base A uri which is "longer"
|
||||
* @param parentCandidate A uri which is "shorter" then `base`
|
||||
*/
|
||||
export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = hasToIgnoreCase(base)): boolean {
|
||||
if (base.scheme === parentCandidate.scheme) {
|
||||
if (base.scheme === Schemas.file) {
|
||||
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase) && base.query === parentCandidate.query;
|
||||
}
|
||||
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
|
||||
return extpath.isEqualOrParent(base.path || '/', parentCandidate.path || '/', ignoreCase, '/') && base.query === parentCandidate.query;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the two authorities are the same
|
||||
*/
|
||||
export function isEqualAuthority(a1: string, a2: string) {
|
||||
return a1 === a2 || equalsIgnoreCase(a1, a2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether two resources are the same. URI queries must match, fragments are ignored unless requested.
|
||||
*/
|
||||
export function isEqual(first: URI | undefined, second: URI | undefined, caseInsensitivePath = hasToIgnoreCase(first), ignoreFragment = true): boolean {
|
||||
if (first === second) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!first || !second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (first.scheme !== second.scheme || !isEqualAuthority(first.authority, second.authority)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const p1 = first.path || '/', p2 = second.path || '/';
|
||||
return (p1 === p2 || caseInsensitivePath && equalsIgnoreCase(p1, p2)) && first.query === second.query && (ignoreFragment || first.fragment === second.fragment);
|
||||
}
|
||||
|
||||
export function basename(resource: URI): string {
|
||||
return paths.posix.basename(resource.path);
|
||||
}
|
||||
|
||||
export function extname(resource: URI): string {
|
||||
return paths.posix.extname(resource.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a URI representing the directory of a URI path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @returns The URI representing the directory of the input URI.
|
||||
*/
|
||||
export function dirname(resource: URI): URI {
|
||||
if (resource.path.length === 0) {
|
||||
return resource;
|
||||
}
|
||||
let dirname;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
|
||||
} else {
|
||||
dirname = paths.posix.dirname(resource.path);
|
||||
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
|
||||
console.error(`dirname("${resource.toString})) resulted in a relative path`);
|
||||
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
|
||||
}
|
||||
}
|
||||
return resource.with({
|
||||
path: dirname
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a URI path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @param pathFragment The path fragment to add to the URI path.
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
export function joinPath(resource: URI, ...pathFragment: string[]): URI {
|
||||
let joinedPath: string;
|
||||
if (resource.scheme === 'file') {
|
||||
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
|
||||
} else {
|
||||
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
|
||||
}
|
||||
return resource.with({
|
||||
path: joinedPath
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names.
|
||||
*
|
||||
* @param resource The URI to normalize the path.
|
||||
* @returns The URI with the normalized path.
|
||||
*/
|
||||
export function normalizePath(resource: URI): URI {
|
||||
if (!resource.path.length) {
|
||||
return resource;
|
||||
}
|
||||
let normalizedPath: string;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
|
||||
} else {
|
||||
normalizedPath = paths.posix.normalize(resource.path);
|
||||
}
|
||||
return resource.with({
|
||||
path: normalizedPath
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the URI path is absolute.
|
||||
*/
|
||||
export function isAbsolutePath(resource: URI): boolean {
|
||||
return !!resource.path && resource.path[0] === '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the URI path has a trailing path separator
|
||||
*/
|
||||
export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
|
||||
} else {
|
||||
const p = resource.path;
|
||||
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a trailing path separator, if there's one.
|
||||
* Important: Doesn't remove the first slash, it would make the URI invalid
|
||||
*/
|
||||
export function removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
|
||||
if (hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a trailing path separator to the URI if there isn't one already.
|
||||
* For example, c:\ would be unchanged, but c:\users would become c:\users\
|
||||
*/
|
||||
export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
let isRootSep: boolean = false;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
|
||||
} else {
|
||||
sep = '/';
|
||||
const p = resource.path;
|
||||
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
|
||||
}
|
||||
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path + '/' });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned.
|
||||
* The returned relative path always uses forward slashes.
|
||||
*/
|
||||
export function relativePath(from: URI, to: URI, caseInsensitivePath = hasToIgnoreCase(from)): string | undefined {
|
||||
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
|
||||
return undefined;
|
||||
}
|
||||
if (from.scheme === Schemas.file) {
|
||||
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
|
||||
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
|
||||
}
|
||||
let fromPath = from.path || '/', toPath = to.path || '/';
|
||||
if (caseInsensitivePath) {
|
||||
// make casing of fromPath match toPath
|
||||
let i = 0;
|
||||
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
|
||||
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
|
||||
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
|
||||
break;
|
||||
compare(uri1: URI, uri2: URI, ignoreFragment: boolean = false): number {
|
||||
// scheme
|
||||
let ret = strCompare(uri1.scheme, uri2.scheme);
|
||||
if (ret === 0) {
|
||||
// authority
|
||||
ret = compareIgnoreCase(uri1.authority, uri2.authority);
|
||||
if (ret === 0) {
|
||||
// path
|
||||
ret = this._ignorePathCasing(uri1) ? compareIgnoreCase(uri1.path, uri2.path) : strCompare(uri1.path, uri2.path);
|
||||
// query
|
||||
if (ret === 0) {
|
||||
ret = strCompare(uri1.query, uri2.query);
|
||||
// fragment
|
||||
if (ret === 0 && !ignoreFragment) {
|
||||
ret = strCompare(uri1.fragment, uri2.fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPath = toPath.substr(0, i) + fromPath.substr(i);
|
||||
return ret;
|
||||
}
|
||||
|
||||
getComparisonKey(uri: URI, ignoreFragment: boolean = false): string {
|
||||
return uri.with({
|
||||
path: this._ignorePathCasing(uri) ? uri.path.toLowerCase() : undefined,
|
||||
fragment: ignoreFragment ? null : undefined
|
||||
}).toString();
|
||||
}
|
||||
|
||||
isEqual(uri1: URI | undefined, uri2: URI | undefined, ignoreFragment: boolean = false): boolean {
|
||||
if (uri1 === uri2) {
|
||||
return true;
|
||||
}
|
||||
if (!uri1 || !uri2) {
|
||||
return false;
|
||||
}
|
||||
if (uri1.scheme !== uri2.scheme || !isEqualAuthority(uri1.authority, uri2.authority)) {
|
||||
return false;
|
||||
}
|
||||
const p1 = uri1.path, p2 = uri2.path;
|
||||
return (p1 === p2 || this._ignorePathCasing(uri1) && equalsIgnoreCase(p1, p2)) && uri1.query === uri2.query && (ignoreFragment || uri1.fragment === uri2.fragment);
|
||||
}
|
||||
|
||||
isEqualOrParent(base: URI, parentCandidate: URI, ignoreFragment: boolean = false): boolean {
|
||||
if (base.scheme === parentCandidate.scheme) {
|
||||
if (base.scheme === Schemas.file) {
|
||||
return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), this._ignorePathCasing(base)) && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
|
||||
}
|
||||
if (isEqualAuthority(base.authority, parentCandidate.authority)) {
|
||||
return extpath.isEqualOrParent(base.path, parentCandidate.path, this._ignorePathCasing(base), '/') && base.query === parentCandidate.query && (ignoreFragment || base.fragment === parentCandidate.fragment);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// --- path math
|
||||
|
||||
joinPath(resource: URI, ...pathFragment: string[]): URI {
|
||||
return URI.joinPath(resource, ...pathFragment);
|
||||
}
|
||||
|
||||
basenameOrAuthority(resource: URI): string {
|
||||
return basename(resource) || resource.authority;
|
||||
}
|
||||
|
||||
basename(resource: URI): string {
|
||||
return paths.posix.basename(resource.path);
|
||||
}
|
||||
|
||||
extname(resource: URI): string {
|
||||
return paths.posix.extname(resource.path);
|
||||
}
|
||||
|
||||
dirname(resource: URI): URI {
|
||||
if (resource.path.length === 0) {
|
||||
return resource;
|
||||
}
|
||||
let dirname;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
dirname = URI.file(paths.dirname(originalFSPath(resource))).path;
|
||||
} else {
|
||||
dirname = paths.posix.dirname(resource.path);
|
||||
if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) {
|
||||
console.error(`dirname("${resource.toString})) resulted in a relative path`);
|
||||
dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character
|
||||
}
|
||||
}
|
||||
return resource.with({
|
||||
path: dirname
|
||||
});
|
||||
}
|
||||
|
||||
normalizePath(resource: URI): URI {
|
||||
if (!resource.path.length) {
|
||||
return resource;
|
||||
}
|
||||
let normalizedPath: string;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path;
|
||||
} else {
|
||||
normalizedPath = paths.posix.normalize(resource.path);
|
||||
}
|
||||
return resource.with({
|
||||
path: normalizedPath
|
||||
});
|
||||
}
|
||||
|
||||
relativePath(from: URI, to: URI): string | undefined {
|
||||
if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) {
|
||||
return undefined;
|
||||
}
|
||||
if (from.scheme === Schemas.file) {
|
||||
const relativePath = paths.relative(originalFSPath(from), originalFSPath(to));
|
||||
return isWindows ? extpath.toSlashes(relativePath) : relativePath;
|
||||
}
|
||||
let fromPath = from.path || '/', toPath = to.path || '/';
|
||||
if (this._ignorePathCasing(from)) {
|
||||
// make casing of fromPath match toPath
|
||||
let i = 0;
|
||||
for (const len = Math.min(fromPath.length, toPath.length); i < len; i++) {
|
||||
if (fromPath.charCodeAt(i) !== toPath.charCodeAt(i)) {
|
||||
if (fromPath.charAt(i).toLowerCase() !== toPath.charAt(i).toLowerCase()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fromPath = toPath.substr(0, i) + fromPath.substr(i);
|
||||
}
|
||||
return paths.posix.relative(fromPath, toPath);
|
||||
}
|
||||
|
||||
resolvePath(base: URI, path: string): URI {
|
||||
if (base.scheme === Schemas.file) {
|
||||
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
|
||||
return base.with({
|
||||
authority: newURI.authority,
|
||||
path: newURI.path
|
||||
});
|
||||
}
|
||||
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
|
||||
path = extpath.toSlashes(path);
|
||||
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
|
||||
path = '/' + path;
|
||||
}
|
||||
}
|
||||
return base.with({
|
||||
path: paths.posix.resolve(base.path, path)
|
||||
});
|
||||
}
|
||||
|
||||
// --- misc
|
||||
|
||||
isAbsolutePath(resource: URI): boolean {
|
||||
return !!resource.path && resource.path[0] === '/';
|
||||
}
|
||||
|
||||
isEqualAuthority(a1: string, a2: string) {
|
||||
return a1 === a2 || equalsIgnoreCase(a1, a2);
|
||||
}
|
||||
|
||||
hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep;
|
||||
} else {
|
||||
const p = resource.path;
|
||||
return (p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash) && !(/^[a-zA-Z]:(\/$|\\$)/.test(resource.fsPath)); // ignore the slash at offset 0
|
||||
}
|
||||
}
|
||||
|
||||
removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
// Make sure that the path isn't a drive letter. A trailing separator there is not removable.
|
||||
if (hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path.substr(0, resource.path.length - 1) });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI {
|
||||
let isRootSep: boolean = false;
|
||||
if (resource.scheme === Schemas.file) {
|
||||
const fsp = originalFSPath(resource);
|
||||
isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep));
|
||||
} else {
|
||||
sep = '/';
|
||||
const p = resource.path;
|
||||
isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash;
|
||||
}
|
||||
if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) {
|
||||
return resource.with({ path: resource.path + '/' });
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
return paths.posix.relative(fromPath, toPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves an absolute or relative path against a base URI.
|
||||
* The path can be relative or absolute posix or a Windows path
|
||||
* Unbiased utility that takes uris "as they are". This means it can be interchanged with
|
||||
* uri#toString() usages. The following is true
|
||||
* ```
|
||||
* assertEqual(aUri.toString() === bUri.toString(), exturi.isEqual(aUri, bUri))
|
||||
* ```
|
||||
*/
|
||||
export function resolvePath(base: URI, path: string): URI {
|
||||
if (base.scheme === Schemas.file) {
|
||||
const newURI = URI.file(paths.resolve(originalFSPath(base), path));
|
||||
return base.with({
|
||||
authority: newURI.authority,
|
||||
path: newURI.path
|
||||
});
|
||||
}
|
||||
if (path.indexOf('/') === -1) { // no slashes? it's likely a Windows path
|
||||
path = extpath.toSlashes(path);
|
||||
if (/^[a-zA-Z]:(\/|$)/.test(path)) { // starts with a drive letter
|
||||
path = '/' + path;
|
||||
}
|
||||
}
|
||||
return base.with({
|
||||
path: paths.posix.resolve(base.path, path)
|
||||
});
|
||||
}
|
||||
export const extUri = new ExtUri(() => false);
|
||||
|
||||
/**
|
||||
* BIASED utility that always ignores the casing of uris path. ONLY use these util if you
|
||||
* understand what you are doing.
|
||||
*
|
||||
* Note that `IUriIdentityService#extUri` is a better replacement for this because that utility
|
||||
* knows when path casing matters and when not.
|
||||
*/
|
||||
export const extUriIgnorePathCase = new ExtUri(_ => true);
|
||||
|
||||
const exturiBiasedIgnorePathCase = new ExtUri(uri => {
|
||||
// A file scheme resource is in the same platform as code, so ignore case for non linux platforms
|
||||
// Resource can be from another platform. Lowering the case as an hack. Should come from File system provider
|
||||
return uri && uri.scheme === Schemas.file ? !isLinux : true;
|
||||
});
|
||||
|
||||
export const isEqual = exturiBiasedIgnorePathCase.isEqual.bind(exturiBiasedIgnorePathCase);
|
||||
export const isEqualOrParent = exturiBiasedIgnorePathCase.isEqualOrParent.bind(exturiBiasedIgnorePathCase);
|
||||
export const getComparisonKey = exturiBiasedIgnorePathCase.getComparisonKey.bind(exturiBiasedIgnorePathCase);
|
||||
export const basenameOrAuthority = exturiBiasedIgnorePathCase.basenameOrAuthority.bind(exturiBiasedIgnorePathCase);
|
||||
export const basename = exturiBiasedIgnorePathCase.basename.bind(exturiBiasedIgnorePathCase);
|
||||
export const extname = exturiBiasedIgnorePathCase.extname.bind(exturiBiasedIgnorePathCase);
|
||||
export const dirname = exturiBiasedIgnorePathCase.dirname.bind(exturiBiasedIgnorePathCase);
|
||||
export const joinPath = extUri.joinPath.bind(extUri);
|
||||
export const normalizePath = exturiBiasedIgnorePathCase.normalizePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const relativePath = exturiBiasedIgnorePathCase.relativePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const resolvePath = exturiBiasedIgnorePathCase.resolvePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const isAbsolutePath = exturiBiasedIgnorePathCase.isAbsolutePath.bind(exturiBiasedIgnorePathCase);
|
||||
export const isEqualAuthority = exturiBiasedIgnorePathCase.isEqualAuthority.bind(exturiBiasedIgnorePathCase);
|
||||
export const hasTrailingPathSeparator = exturiBiasedIgnorePathCase.hasTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
|
||||
export const removeTrailingPathSeparator = exturiBiasedIgnorePathCase.removeTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
|
||||
export const addTrailingPathSeparator = exturiBiasedIgnorePathCase.addTrailingPathSeparator.bind(exturiBiasedIgnorePathCase);
|
||||
|
||||
//#endregion
|
||||
|
||||
export function distinctParents<T>(items: T[], resourceAccessor: (item: T) => URI): T[] {
|
||||
const distinctParents: T[] = [];
|
||||
|
||||
@@ -13,10 +13,18 @@ export const enum ScrollbarVisibility {
|
||||
}
|
||||
|
||||
export interface ScrollEvent {
|
||||
oldWidth: number;
|
||||
oldScrollWidth: number;
|
||||
oldScrollLeft: number;
|
||||
|
||||
width: number;
|
||||
scrollWidth: number;
|
||||
scrollLeft: number;
|
||||
|
||||
oldHeight: number;
|
||||
oldScrollHeight: number;
|
||||
oldScrollTop: number;
|
||||
|
||||
height: number;
|
||||
scrollHeight: number;
|
||||
scrollTop: number;
|
||||
@@ -134,10 +142,18 @@ export class ScrollState implements IScrollDimensions, IScrollPosition {
|
||||
const scrollTopChanged = (this.scrollTop !== previous.scrollTop);
|
||||
|
||||
return {
|
||||
oldWidth: previous.width,
|
||||
oldScrollWidth: previous.scrollWidth,
|
||||
oldScrollLeft: previous.scrollLeft,
|
||||
|
||||
width: this.width,
|
||||
scrollWidth: this.scrollWidth,
|
||||
scrollLeft: this.scrollLeft,
|
||||
|
||||
oldHeight: previous.height,
|
||||
oldScrollHeight: previous.scrollHeight,
|
||||
oldScrollTop: previous.scrollTop,
|
||||
|
||||
height: this.height,
|
||||
scrollHeight: this.scrollHeight,
|
||||
scrollTop: this.scrollTop,
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
class Node<K, V> {
|
||||
readonly forward: Node<K, V>[];
|
||||
constructor(readonly level: number, readonly key: K, public value: V) {
|
||||
this.forward = [];
|
||||
}
|
||||
}
|
||||
|
||||
const NIL: undefined = undefined;
|
||||
|
||||
interface Comparator<K> {
|
||||
(a: K, b: K): number;
|
||||
}
|
||||
|
||||
export class SkipList<K, V> implements Map<K, V> {
|
||||
|
||||
readonly [Symbol.toStringTag] = 'SkipList';
|
||||
|
||||
private _maxLevel: number;
|
||||
private _level: number = 1;
|
||||
private _header: Node<K, V>;
|
||||
private _size: number = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param capacity Capacity at which the list performs best
|
||||
*/
|
||||
constructor(
|
||||
readonly comparator: (a: K, b: K) => number,
|
||||
capacity: number = 2 ** 16
|
||||
) {
|
||||
this._maxLevel = Math.max(1, Math.log2(capacity) | 0);
|
||||
this._header = <any>new Node(this._maxLevel, NIL, NIL);
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this._size;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._header = <any>new Node(this._maxLevel, NIL, NIL);
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
return Boolean(SkipList._search(this, key, this.comparator));
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
return SkipList._search(this, key, this.comparator)?.value;
|
||||
}
|
||||
|
||||
set(key: K, value: V): this {
|
||||
if (SkipList._insert(this, key, value, this.comparator)) {
|
||||
this._size += 1;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
delete(key: K): boolean {
|
||||
const didDelete = SkipList._delete(this, key, this.comparator);
|
||||
if (didDelete) {
|
||||
this._size -= 1;
|
||||
}
|
||||
return didDelete;
|
||||
}
|
||||
|
||||
// --- iteration
|
||||
|
||||
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
|
||||
let node = this._header.forward[0];
|
||||
while (node) {
|
||||
callbackfn.call(thisArg, node.value, node.key, this);
|
||||
node = node.forward[0];
|
||||
}
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[K, V]> {
|
||||
return this.entries();
|
||||
}
|
||||
|
||||
*entries(): IterableIterator<[K, V]> {
|
||||
let node = this._header.forward[0];
|
||||
while (node) {
|
||||
yield [node.key, node.value];
|
||||
node = node.forward[0];
|
||||
}
|
||||
}
|
||||
|
||||
*keys(): IterableIterator<K> {
|
||||
let node = this._header.forward[0];
|
||||
while (node) {
|
||||
yield node.key;
|
||||
node = node.forward[0];
|
||||
}
|
||||
}
|
||||
|
||||
*values(): IterableIterator<V> {
|
||||
let node = this._header.forward[0];
|
||||
while (node) {
|
||||
yield node.value;
|
||||
node = node.forward[0];
|
||||
}
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
// debug string...
|
||||
let result = '[SkipList]:';
|
||||
let node = this._header.forward[0];
|
||||
while (node) {
|
||||
result += `node(${node.key}, ${node.value}, lvl:${node.level})`;
|
||||
node = node.forward[0];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// from https://www.epaperpress.com/sortsearch/download/skiplist.pdf
|
||||
|
||||
private static _search<K, V>(list: SkipList<K, V>, searchKey: K, comparator: Comparator<K>) {
|
||||
let x = list._header;
|
||||
for (let i = list._level; i >= 0; i--) {
|
||||
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
|
||||
x = x.forward[i];
|
||||
}
|
||||
}
|
||||
x = x.forward[0];
|
||||
if (x && comparator(x.key, searchKey) === 0) {
|
||||
return x;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private static _insert<K, V>(list: SkipList<K, V>, searchKey: K, value: V, comparator: Comparator<K>) {
|
||||
let update: Node<K, V>[] = [];
|
||||
let x = list._header;
|
||||
for (let i = list._level; i >= 0; i--) {
|
||||
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
|
||||
x = x.forward[i];
|
||||
}
|
||||
update[i] = x;
|
||||
}
|
||||
x = x.forward[0];
|
||||
if (x && comparator(x.key, searchKey) === 0) {
|
||||
// update
|
||||
x.value = value;
|
||||
return false;
|
||||
} else {
|
||||
// insert
|
||||
let lvl = SkipList._randomLevel(list);
|
||||
if (lvl > list._level) {
|
||||
for (let i = list._level + 1; i <= lvl; i++) {
|
||||
update[i] = list._header;
|
||||
}
|
||||
list._level = lvl;
|
||||
}
|
||||
x = new Node<K, V>(lvl, searchKey, value);
|
||||
for (let i = 0; i <= lvl; i++) {
|
||||
x.forward[i] = update[i].forward[i];
|
||||
update[i].forward[i] = x;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static _randomLevel(list: SkipList<any, any>, p: number = 0.5): number {
|
||||
let lvl = 1;
|
||||
while (Math.random() < p && lvl < list._maxLevel) {
|
||||
lvl += 1;
|
||||
}
|
||||
return lvl;
|
||||
}
|
||||
|
||||
private static _delete<K, V>(list: SkipList<K, V>, searchKey: K, comparator: Comparator<K>) {
|
||||
let update: Node<K, V>[] = [];
|
||||
let x = list._header;
|
||||
for (let i = list._level; i >= 0; i--) {
|
||||
while (x.forward[i] && comparator(x.forward[i].key, searchKey) < 0) {
|
||||
x = x.forward[i];
|
||||
}
|
||||
update[i] = x;
|
||||
}
|
||||
x = x.forward[0];
|
||||
if (!x || comparator(x.key, searchKey) !== 0) {
|
||||
// not found
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < list._level; i++) {
|
||||
if (update[i].forward[i] !== x) {
|
||||
break;
|
||||
}
|
||||
update[i].forward[i] = x.forward[i];
|
||||
}
|
||||
while (list._level >= 1 && list._header.forward[list._level] === NIL) {
|
||||
list._level -= 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -728,6 +728,14 @@ export function isBasicASCII(str: string): boolean {
|
||||
return IS_BASIC_ASCII.test(str);
|
||||
}
|
||||
|
||||
export const UNUSUAL_LINE_TERMINATORS = /[\u2028\u2029\u0085]/; // LINE SEPARATOR (LS), PARAGRAPH SEPARATOR (PS), NEXT LINE (NEL)
|
||||
/**
|
||||
* Returns true if `str` contains unusual line terminators, like LS, PS or NEL
|
||||
*/
|
||||
export function containsUnusualLineTerminators(str: string): boolean {
|
||||
return UNUSUAL_LINE_TERMINATORS.test(str);
|
||||
}
|
||||
|
||||
export function containsFullWidthCharacter(str: string): boolean {
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
if (isFullWidthCharacter(str.charCodeAt(i))) {
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcRenderer, Event } from 'electron';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { IContextMenuItem, ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions, IContextMenuEvent } from 'vs/base/parts/contextmenu/common/contextmenu';
|
||||
|
||||
let contextMenuIdPool = 0;
|
||||
@@ -13,7 +13,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void
|
||||
|
||||
const contextMenuId = contextMenuIdPool++;
|
||||
const onClickChannel = `vscode:onContextMenu${contextMenuId}`;
|
||||
const onClickChannelHandler = (_event: Event, itemId: number, context: IContextMenuEvent) => {
|
||||
const onClickChannelHandler = (event: unknown, itemId: number, context: IContextMenuEvent) => {
|
||||
const item = processedItems[itemId];
|
||||
if (item.click) {
|
||||
item.click(context);
|
||||
@@ -21,7 +21,7 @@ export function popup(items: IContextMenuItem[], options?: IPopupOptions): void
|
||||
};
|
||||
|
||||
ipcRenderer.once(onClickChannel, onClickChannelHandler);
|
||||
ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (_event: Event, closedContextMenuId: number) => {
|
||||
ipcRenderer.once(CONTEXT_MENU_CLOSE_CHANNEL, (event: unknown, closedContextMenuId: number) => {
|
||||
if (closedContextMenuId !== contextMenuId) {
|
||||
return;
|
||||
}
|
||||
+4
-4
@@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
export interface Sender {
|
||||
send(channel: string, msg: Buffer | null): void;
|
||||
send(channel: string, msg: unknown): void;
|
||||
}
|
||||
|
||||
export class Protocol implements IMessagePassingProtocol {
|
||||
@@ -17,13 +17,13 @@ export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
send(message: VSBuffer): void {
|
||||
try {
|
||||
this.sender.send('ipc:message', (<Buffer>message.buffer));
|
||||
this.sender.send('vscode:message', message.buffer);
|
||||
} catch (e) {
|
||||
// systems are going down
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.sender.send('ipc:disconnect', null);
|
||||
this.sender.send('vscode:disconnect', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { getRandomElement } from 'vs/base/common/arrays';
|
||||
import { isFunction } from 'vs/base/common/types';
|
||||
import { isFunction, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { isUpperAsciiLetter } from 'vs/base/common/strings';
|
||||
|
||||
/**
|
||||
* An `IChannel` is an abstraction over a collection of commands.
|
||||
@@ -919,3 +921,142 @@ export class StaticRouter<TContext = string> implements IClientRouter<TContext>
|
||||
return await this.route(hub);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#region createChannelReceiver / createChannelSender
|
||||
|
||||
/**
|
||||
* Use both `createChannelReceiver` and `createChannelSender`
|
||||
* for automated process <=> process communication over methods
|
||||
* and events. You do not need to spell out each method on both
|
||||
* sides, a proxy will take care of this.
|
||||
*
|
||||
* Rules:
|
||||
* - if marshalling is enabled, only `URI` and `RegExp` is converted
|
||||
* automatically for you
|
||||
* - events must follow the naming convention `onUppercase`
|
||||
* - `CancellationToken` is currently not supported
|
||||
* - if a context is provided, you can use `AddFirstParameterToFunctions`
|
||||
* utility to signal this in the receiving side type
|
||||
*/
|
||||
|
||||
export interface IBaseChannelOptions {
|
||||
|
||||
/**
|
||||
* Disables automatic marshalling of `URI`.
|
||||
* If marshalling is disabled, `UriComponents`
|
||||
* must be used instead.
|
||||
*/
|
||||
disableMarshalling?: boolean;
|
||||
}
|
||||
|
||||
export interface IChannelReceiverOptions extends IBaseChannelOptions { }
|
||||
|
||||
export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel {
|
||||
const handler = service as { [key: string]: unknown };
|
||||
const disableMarshalling = options && options.disableMarshalling;
|
||||
|
||||
// Buffer any event that should be supported by
|
||||
// iterating over all property keys and finding them
|
||||
const mapEventNameToEvent = new Map<string, Event<unknown>>();
|
||||
for (const key in handler) {
|
||||
if (propertyIsEvent(key)) {
|
||||
mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event<unknown>, true));
|
||||
}
|
||||
}
|
||||
|
||||
return new class implements IServerChannel {
|
||||
|
||||
listen<T>(_: unknown, event: string): Event<T> {
|
||||
const eventImpl = mapEventNameToEvent.get(event);
|
||||
if (eventImpl) {
|
||||
return eventImpl as Event<T>;
|
||||
}
|
||||
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, args?: any[]): Promise<any> {
|
||||
const target = handler[command];
|
||||
if (typeof target === 'function') {
|
||||
|
||||
// Revive unless marshalling disabled
|
||||
if (!disableMarshalling && Array.isArray(args)) {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
args[i] = revive(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return target.apply(handler, args);
|
||||
}
|
||||
|
||||
throw new Error(`Method not found: ${command}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface IChannelSenderOptions extends IBaseChannelOptions {
|
||||
|
||||
/**
|
||||
* If provided, will add the value of `context`
|
||||
* to each method call to the target.
|
||||
*/
|
||||
context?: unknown;
|
||||
|
||||
/**
|
||||
* If provided, will not proxy any of the properties
|
||||
* that are part of the Map but rather return that value.
|
||||
*/
|
||||
properties?: Map<string, unknown>;
|
||||
}
|
||||
|
||||
export function createChannelSender<T>(channel: IChannel, options?: IChannelSenderOptions): T {
|
||||
const disableMarshalling = options && options.disableMarshalling;
|
||||
|
||||
return new Proxy({}, {
|
||||
get(_target: T, propKey: PropertyKey) {
|
||||
if (typeof propKey === 'string') {
|
||||
|
||||
// Check for predefined values
|
||||
if (options?.properties?.has(propKey)) {
|
||||
return options.properties.get(propKey);
|
||||
}
|
||||
|
||||
// Event
|
||||
if (propertyIsEvent(propKey)) {
|
||||
return channel.listen(propKey);
|
||||
}
|
||||
|
||||
// Function
|
||||
return async function (...args: any[]) {
|
||||
|
||||
// Add context if any
|
||||
let methodArgs: any[];
|
||||
if (options && !isUndefinedOrNull(options.context)) {
|
||||
methodArgs = [options.context, ...args];
|
||||
} else {
|
||||
methodArgs = args;
|
||||
}
|
||||
|
||||
const result = await channel.call(propKey, methodArgs);
|
||||
|
||||
// Revive unless marshalling disabled
|
||||
if (!disableMarshalling) {
|
||||
return revive(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Property not found: ${String(propKey)}`);
|
||||
}
|
||||
}) as T;
|
||||
}
|
||||
|
||||
function propertyIsEvent(name: string): boolean {
|
||||
// Assume a property is an event if it has a form of "onSomething"
|
||||
return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/node/ipc.electron';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
@@ -27,7 +27,7 @@ export class Server extends IPCServer {
|
||||
private static readonly Clients = new Map<number, IDisposable>();
|
||||
|
||||
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
|
||||
const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'ipc:hello', ({ sender }) => sender);
|
||||
const onHello = Event.fromNodeEventEmitter<WebContents>(ipcMain, 'vscode:hello', ({ sender }) => sender);
|
||||
|
||||
return Event.map(onHello, webContents => {
|
||||
const id = webContents.id;
|
||||
@@ -40,8 +40,8 @@ export class Server extends IPCServer {
|
||||
const onDidClientReconnect = new Emitter<void>();
|
||||
Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire()));
|
||||
|
||||
const onMessage = createScopedOnMessageEvent(id, 'ipc:message') as Event<VSBuffer>;
|
||||
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'ipc:disconnect')), onDidClientReconnect.event);
|
||||
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<VSBuffer>;
|
||||
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event);
|
||||
const protocol = new Protocol(webContents, onMessage);
|
||||
|
||||
return { protocol, onDidClientDisconnect };
|
||||
|
||||
+5
-5
@@ -5,18 +5,18 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/node/ipc.electron';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
export class Client extends IPCClient implements IDisposable {
|
||||
|
||||
private protocol: Protocol;
|
||||
|
||||
private static createProtocol(): Protocol {
|
||||
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'ipc:message', (_, message: Buffer) => VSBuffer.wrap(message));
|
||||
ipcRenderer.send('ipc:hello');
|
||||
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'vscode:message', (_, message) => VSBuffer.wrap(message));
|
||||
ipcRenderer.send('vscode:hello');
|
||||
return new Protocol(ipcRenderer, onMessage);
|
||||
}
|
||||
|
||||
@@ -29,4 +29,4 @@ export class Client extends IPCClient implements IDisposable {
|
||||
dispose(): void {
|
||||
this.protocol.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { isUpperAsciiLetter } from 'vs/base/common/strings';
|
||||
|
||||
/**
|
||||
* Use both `createChannelReceiver` and `createChannelSender`
|
||||
* for automated process <=> process communication over methods
|
||||
* and events. You do not need to spell out each method on both
|
||||
* sides, a proxy will take care of this.
|
||||
*
|
||||
* Rules:
|
||||
* - if marshalling is enabled, only `URI` and `RegExp` is converted
|
||||
* automatically for you
|
||||
* - events must follow the naming convention `onUppercase`
|
||||
* - `CancellationToken` is currently not supported
|
||||
* - if a context is provided, you can use `AddFirstParameterToFunctions`
|
||||
* utility to signal this in the receiving side type
|
||||
*/
|
||||
|
||||
export interface IBaseChannelOptions {
|
||||
|
||||
/**
|
||||
* Disables automatic marshalling of `URI`.
|
||||
* If marshalling is disabled, `UriComponents`
|
||||
* must be used instead.
|
||||
*/
|
||||
disableMarshalling?: boolean;
|
||||
}
|
||||
|
||||
export interface IChannelReceiverOptions extends IBaseChannelOptions { }
|
||||
|
||||
export function createChannelReceiver(service: unknown, options?: IChannelReceiverOptions): IServerChannel {
|
||||
const handler = service as { [key: string]: unknown };
|
||||
const disableMarshalling = options && options.disableMarshalling;
|
||||
|
||||
// Buffer any event that should be supported by
|
||||
// iterating over all property keys and finding them
|
||||
const mapEventNameToEvent = new Map<string, Event<unknown>>();
|
||||
for (const key in handler) {
|
||||
if (propertyIsEvent(key)) {
|
||||
mapEventNameToEvent.set(key, Event.buffer(handler[key] as Event<unknown>, true));
|
||||
}
|
||||
}
|
||||
|
||||
return new class implements IServerChannel {
|
||||
|
||||
listen<T>(_: unknown, event: string): Event<T> {
|
||||
const eventImpl = mapEventNameToEvent.get(event);
|
||||
if (eventImpl) {
|
||||
return eventImpl as Event<T>;
|
||||
}
|
||||
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(_: unknown, command: string, args?: any[]): Promise<any> {
|
||||
const target = handler[command];
|
||||
if (typeof target === 'function') {
|
||||
|
||||
// Revive unless marshalling disabled
|
||||
if (!disableMarshalling && Array.isArray(args)) {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
args[i] = revive(args[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return target.apply(handler, args);
|
||||
}
|
||||
|
||||
throw new Error(`Method not found: ${command}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface IChannelSenderOptions extends IBaseChannelOptions {
|
||||
|
||||
/**
|
||||
* If provided, will add the value of `context`
|
||||
* to each method call to the target.
|
||||
*/
|
||||
context?: unknown;
|
||||
}
|
||||
|
||||
export function createChannelSender<T>(channel: IChannel, options?: IChannelSenderOptions): T {
|
||||
const disableMarshalling = options && options.disableMarshalling;
|
||||
|
||||
return new Proxy({}, {
|
||||
get(_target: T, propKey: PropertyKey) {
|
||||
if (typeof propKey === 'string') {
|
||||
|
||||
// Event
|
||||
if (propertyIsEvent(propKey)) {
|
||||
return channel.listen(propKey);
|
||||
}
|
||||
|
||||
// Function
|
||||
return async function (...args: any[]) {
|
||||
|
||||
// Add context if any
|
||||
let methodArgs: any[];
|
||||
if (options && !isUndefinedOrNull(options.context)) {
|
||||
methodArgs = [options.context, ...args];
|
||||
} else {
|
||||
methodArgs = args;
|
||||
}
|
||||
|
||||
const result = await channel.call(propKey, methodArgs);
|
||||
|
||||
// Revive unless marshalling disabled
|
||||
if (!disableMarshalling) {
|
||||
return revive(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Property not found: ${String(propKey)}`);
|
||||
}
|
||||
}) as T;
|
||||
}
|
||||
|
||||
function propertyIsEvent(name: string): boolean {
|
||||
// Assume a property is an event if it has a form of "onSomething"
|
||||
return name[0] === 'o' && name[1] === 'n' && isUpperAsciiLetter(name.charCodeAt(2));
|
||||
}
|
||||
+7
-8
@@ -4,8 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { IChannel, IServerChannel, IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
@@ -103,7 +102,7 @@ interface ITestService {
|
||||
error(message: string): Promise<void>;
|
||||
neverComplete(): Promise<void>;
|
||||
neverCompleteCT(cancellationToken: CancellationToken): Promise<void>;
|
||||
buffersLength(buffers: Buffer[]): Promise<number>;
|
||||
buffersLength(buffers: VSBuffer[]): Promise<number>;
|
||||
marshall(uri: URI): Promise<URI>;
|
||||
context(): Promise<unknown>;
|
||||
|
||||
@@ -135,8 +134,8 @@ class TestService implements ITestService {
|
||||
return new Promise((_, e) => cancellationToken.onCancellationRequested(() => e(canceled())));
|
||||
}
|
||||
|
||||
buffersLength(buffers: Buffer[]): Promise<number> {
|
||||
return Promise.resolve(buffers.reduce((r, b) => r + b.length, 0));
|
||||
buffersLength(buffers: VSBuffer[]): Promise<number> {
|
||||
return Promise.resolve(buffers.reduce((r, b) => r + b.buffer.length, 0));
|
||||
}
|
||||
|
||||
ping(msg: string): void {
|
||||
@@ -199,7 +198,7 @@ class TestChannelClient implements ITestService {
|
||||
return this.channel.call('neverCompleteCT', undefined, cancellationToken);
|
||||
}
|
||||
|
||||
buffersLength(buffers: Buffer[]): Promise<number> {
|
||||
buffersLength(buffers: VSBuffer[]): Promise<number> {
|
||||
return this.channel.call('buffersLength', buffers);
|
||||
}
|
||||
|
||||
@@ -317,7 +316,7 @@ suite('Base IPC', function () {
|
||||
});
|
||||
|
||||
test('buffers in arrays', async function () {
|
||||
const r = await ipcService.buffersLength([Buffer.allocUnsafe(2), Buffer.allocUnsafe(3)]);
|
||||
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
|
||||
return assert.equal(r, 5);
|
||||
});
|
||||
});
|
||||
@@ -383,7 +382,7 @@ suite('Base IPC', function () {
|
||||
});
|
||||
|
||||
test('buffers in arrays', async function () {
|
||||
const r = await ipcService.buffersLength([Buffer.allocUnsafe(2), Buffer.allocUnsafe(3)]);
|
||||
const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]);
|
||||
return assert.equal(r, 5);
|
||||
});
|
||||
});
|
||||
@@ -307,7 +307,7 @@ export interface IQuickInputButton {
|
||||
iconClass?: string;
|
||||
tooltip?: string;
|
||||
/**
|
||||
* Wether to always show the button. By default buttons
|
||||
* Whether to always show the button. By default buttons
|
||||
* are only visible when hovering over them with the mouse
|
||||
*/
|
||||
alwaysVisible?: boolean;
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
// #######################################################################
|
||||
// ### ###
|
||||
// ### electron.d.ts types we need in a common layer for reuse ###
|
||||
// ### (copied from Electron 7.x) ###
|
||||
// ### ###
|
||||
// #######################################################################
|
||||
|
||||
|
||||
export interface MessageBoxOptions {
|
||||
/**
|
||||
* Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"`. On Windows,
|
||||
* `"question"` displays the same icon as `"info"`, unless you set an icon using
|
||||
* the `"icon"` option. On macOS, both `"warning"` and `"error"` display the same
|
||||
* warning icon.
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* Array of texts for buttons. On Windows, an empty array will result in one button
|
||||
* labeled "OK".
|
||||
*/
|
||||
buttons?: string[];
|
||||
/**
|
||||
* Index of the button in the buttons array which will be selected by default when
|
||||
* the message box opens.
|
||||
*/
|
||||
defaultId?: number;
|
||||
/**
|
||||
* Title of the message box, some platforms will not show it.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* Content of the message box.
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* Extra information of the message.
|
||||
*/
|
||||
detail?: string;
|
||||
/**
|
||||
* If provided, the message box will include a checkbox with the given label.
|
||||
*/
|
||||
checkboxLabel?: string;
|
||||
/**
|
||||
* Initial checked state of the checkbox. `false` by default.
|
||||
*/
|
||||
checkboxChecked?: boolean;
|
||||
// icon?: NativeImage;
|
||||
/**
|
||||
* The index of the button to be used to cancel the dialog, via the `Esc` key. By
|
||||
* default this is assigned to the first button with "cancel" or "no" as the label.
|
||||
* If no such labeled buttons exist and this option is not set, `0` will be used as
|
||||
* the return value.
|
||||
*/
|
||||
cancelId?: number;
|
||||
/**
|
||||
* On Windows Electron will try to figure out which one of the `buttons` are common
|
||||
* buttons (like "Cancel" or "Yes"), and show the others as command links in the
|
||||
* dialog. This can make the dialog appear in the style of modern Windows apps. If
|
||||
* you don't like this behavior, you can set `noLink` to `true`.
|
||||
*/
|
||||
noLink?: boolean;
|
||||
/**
|
||||
* Normalize the keyboard access keys across platforms. Default is `false`.
|
||||
* Enabling this assumes `&` is used in the button labels for the placement of the
|
||||
* keyboard shortcut access key and labels will be converted so they work correctly
|
||||
* on each platform, `&` characters are removed on macOS, converted to `_` on
|
||||
* Linux, and left untouched on Windows. For example, a button label of `Vie&w`
|
||||
* will be converted to `Vie_w` on Linux and `View` on macOS and can be selected
|
||||
* via `Alt-W` on Windows and Linux.
|
||||
*/
|
||||
normalizeAccessKeys?: boolean;
|
||||
}
|
||||
|
||||
export interface MessageBoxReturnValue {
|
||||
/**
|
||||
* The index of the clicked button.
|
||||
*/
|
||||
response: number;
|
||||
/**
|
||||
* The checked state of the checkbox if `checkboxLabel` was set. Otherwise `false`.
|
||||
*/
|
||||
checkboxChecked: boolean;
|
||||
}
|
||||
|
||||
export interface OpenDevToolsOptions {
|
||||
/**
|
||||
* Opens the devtools with specified dock state, can be `right`, `bottom`,
|
||||
* `undocked`, `detach`. Defaults to last used dock state. In `undocked` mode it's
|
||||
* possible to dock back. In `detach` mode it's not.
|
||||
*/
|
||||
mode: ('right' | 'bottom' | 'undocked' | 'detach');
|
||||
/**
|
||||
* Whether to bring the opened devtools window to the foreground. The default is
|
||||
* `true`.
|
||||
*/
|
||||
activate?: boolean;
|
||||
}
|
||||
|
||||
export interface SaveDialogOptions {
|
||||
title?: string;
|
||||
/**
|
||||
* Absolute directory path, absolute file path, or file name to use by default.
|
||||
*/
|
||||
defaultPath?: string;
|
||||
/**
|
||||
* Custom label for the confirmation button, when left empty the default label will
|
||||
* be used.
|
||||
*/
|
||||
buttonLabel?: string;
|
||||
filters?: FileFilter[];
|
||||
/**
|
||||
* Message to display above text fields.
|
||||
*
|
||||
* @platform darwin
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* Custom label for the text displayed in front of the filename text field.
|
||||
*
|
||||
* @platform darwin
|
||||
*/
|
||||
nameFieldLabel?: string;
|
||||
/**
|
||||
* Show the tags input box, defaults to `true`.
|
||||
*
|
||||
* @platform darwin
|
||||
*/
|
||||
showsTagField?: boolean;
|
||||
/**
|
||||
* Create a security scoped bookmark when packaged for the Mac App Store. If this
|
||||
* option is enabled and the file doesn't already exist a blank file will be
|
||||
* created at the chosen path.
|
||||
*
|
||||
* @platform darwin,mas
|
||||
*/
|
||||
securityScopedBookmarks?: boolean;
|
||||
}
|
||||
|
||||
export interface OpenDialogOptions {
|
||||
title?: string;
|
||||
defaultPath?: string;
|
||||
/**
|
||||
* Custom label for the confirmation button, when left empty the default label will
|
||||
* be used.
|
||||
*/
|
||||
buttonLabel?: string;
|
||||
filters?: FileFilter[];
|
||||
/**
|
||||
* Contains which features the dialog should use. The following values are
|
||||
* supported:
|
||||
*/
|
||||
properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>;
|
||||
/**
|
||||
* Message to display above input boxes.
|
||||
*
|
||||
* @platform darwin
|
||||
*/
|
||||
message?: string;
|
||||
/**
|
||||
* Create security scoped bookmarks when packaged for the Mac App Store.
|
||||
*
|
||||
* @platform darwin,mas
|
||||
*/
|
||||
securityScopedBookmarks?: boolean;
|
||||
}
|
||||
|
||||
export interface OpenDialogReturnValue {
|
||||
/**
|
||||
* whether or not the dialog was canceled.
|
||||
*/
|
||||
canceled: boolean;
|
||||
/**
|
||||
* An array of file paths chosen by the user. If the dialog is cancelled this will
|
||||
* be an empty array.
|
||||
*/
|
||||
filePaths: string[];
|
||||
/**
|
||||
* An array matching the `filePaths` array of base64 encoded strings which contains
|
||||
* security scoped bookmark data. `securityScopedBookmarks` must be enabled for
|
||||
* this to be populated. (For return values, see table here.)
|
||||
*
|
||||
* @platform darwin,mas
|
||||
*/
|
||||
bookmarks?: string[];
|
||||
}
|
||||
|
||||
export interface SaveDialogReturnValue {
|
||||
/**
|
||||
* whether or not the dialog was canceled.
|
||||
*/
|
||||
canceled: boolean;
|
||||
/**
|
||||
* If the dialog is canceled, this will be `undefined`.
|
||||
*/
|
||||
filePath?: string;
|
||||
/**
|
||||
* Base64 encoded string which contains the security scoped bookmark data for the
|
||||
* saved file. `securityScopedBookmarks` must be enabled for this to be present.
|
||||
* (For return values, see table here.)
|
||||
*
|
||||
* @platform darwin,mas
|
||||
*/
|
||||
bookmark?: string;
|
||||
}
|
||||
|
||||
export interface CrashReporterStartOptions {
|
||||
companyName: string;
|
||||
/**
|
||||
* URL that crash reports will be sent to as POST.
|
||||
*/
|
||||
submitURL: string;
|
||||
/**
|
||||
* Defaults to `app.name`.
|
||||
*/
|
||||
productName?: string;
|
||||
/**
|
||||
* Whether crash reports should be sent to the server. Default is `true`.
|
||||
*/
|
||||
uploadToServer?: boolean;
|
||||
/**
|
||||
* Default is `false`.
|
||||
*/
|
||||
ignoreSystemCrashHandler?: boolean;
|
||||
/**
|
||||
* An object you can define that will be sent along with the report. Only string
|
||||
* properties are sent correctly. Nested objects are not supported. When using
|
||||
* Windows, the property names and values must be fewer than 64 characters.
|
||||
*/
|
||||
extra?: Record<string, string>;
|
||||
/**
|
||||
* Directory to store the crash reports temporarily (only used when the crash
|
||||
* reporter is started via `process.crashReporter.start`).
|
||||
*/
|
||||
crashesDirectory?: string;
|
||||
}
|
||||
|
||||
export interface FileFilter {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/file-filter
|
||||
|
||||
extensions: string[];
|
||||
name: string;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// @ts-check
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const { ipcRenderer, webFrame, crashReporter } = require('electron');
|
||||
|
||||
// @ts-ignore
|
||||
window.vscode = {
|
||||
|
||||
/**
|
||||
* A minimal set of methods exposed from ipcRenderer
|
||||
* to support communication to electron-main
|
||||
*
|
||||
* @type {typeof import('../electron-sandbox/globals').ipcRenderer}
|
||||
*/
|
||||
ipcRenderer: {
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {any[]} args
|
||||
*/
|
||||
send(channel, ...args) {
|
||||
validateIPC(channel);
|
||||
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
*/
|
||||
on(channel, listener) {
|
||||
validateIPC(channel);
|
||||
|
||||
ipcRenderer.on(channel, listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
*/
|
||||
once(channel, listener) {
|
||||
validateIPC(channel);
|
||||
|
||||
ipcRenderer.once(channel, listener);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {(event: import('electron').IpcRendererEvent, ...args: any[]) => void} listener
|
||||
*/
|
||||
removeListener(channel, listener) {
|
||||
validateIPC(channel);
|
||||
|
||||
ipcRenderer.removeListener(channel, listener);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Support for methods of webFrame type.
|
||||
*
|
||||
* @type {typeof import('../electron-sandbox/globals').webFrame}
|
||||
*/
|
||||
webFrame: {
|
||||
|
||||
getZoomFactor() {
|
||||
return webFrame.getZoomFactor();
|
||||
},
|
||||
|
||||
getZoomLevel() {
|
||||
return webFrame.getZoomLevel();
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {number} level
|
||||
*/
|
||||
setZoomLevel(level) {
|
||||
webFrame.setZoomLevel(level);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Support for methods of crashReporter type.
|
||||
*
|
||||
* @type {typeof import('../electron-sandbox/globals').crashReporter}
|
||||
*/
|
||||
crashReporter: {
|
||||
|
||||
/**
|
||||
* @param {Electron.CrashReporterStartOptions} options
|
||||
*/
|
||||
start(options) {
|
||||
crashReporter.start(options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//#region Utilities
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
*/
|
||||
function validateIPC(channel) {
|
||||
if (!channel || !channel.startsWith('vscode:')) {
|
||||
throw new Error(`Unsupported event IPC channel '${channel}'`);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}());
|
||||
@@ -0,0 +1,93 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CrashReporterStartOptions } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
|
||||
export const ipcRenderer = (window as any).vscode.ipcRenderer as {
|
||||
|
||||
/**
|
||||
* Listens to `channel`, when a new message arrives `listener` would be called with
|
||||
* `listener(event, args...)`.
|
||||
*/
|
||||
on(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
|
||||
|
||||
/**
|
||||
* Adds a one time `listener` function for the event. This `listener` is invoked
|
||||
* only the next time a message is sent to `channel`, after which it is removed.
|
||||
*/
|
||||
once(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
|
||||
|
||||
/**
|
||||
* Removes the specified `listener` from the listener array for the specified
|
||||
* `channel`.
|
||||
*/
|
||||
removeListener(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
|
||||
|
||||
/**
|
||||
* Send an asynchronous message to the main process via `channel`, along with
|
||||
* arguments. Arguments will be serialized with the Structured Clone Algorithm,
|
||||
* just like `postMessage`, so prototype chains will not be included. Sending
|
||||
* Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
|
||||
*
|
||||
* > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special
|
||||
* Electron objects is deprecated, and will begin throwing an exception starting
|
||||
* with Electron 9.
|
||||
*
|
||||
* The main process handles it by listening for `channel` with the `ipcMain`
|
||||
* module.
|
||||
*/
|
||||
send(channel: string, ...args: any[]): void;
|
||||
};
|
||||
|
||||
export const webFrame = (window as any).vscode.webFrame as {
|
||||
|
||||
/**
|
||||
* The current zoom factor.
|
||||
*/
|
||||
getZoomFactor(): number;
|
||||
|
||||
/**
|
||||
* The current zoom level.
|
||||
*/
|
||||
getZoomLevel(): number;
|
||||
|
||||
/**
|
||||
* Changes the zoom level to the specified level. The original size is 0 and each
|
||||
* increment above or below represents zooming 20% larger or smaller to default
|
||||
* limits of 300% and 50% of original size, respectively.
|
||||
*/
|
||||
setZoomLevel(level: number): void;
|
||||
};
|
||||
|
||||
export const crashReporter = (window as any).vscode.crashReporter as {
|
||||
|
||||
/**
|
||||
* You are required to call this method before using any other `crashReporter` APIs
|
||||
* and in each process (main/renderer) from which you want to collect crash
|
||||
* reports. You can pass different options to `crashReporter.start` when calling
|
||||
* from different processes.
|
||||
*
|
||||
* **Note** Child processes created via the `child_process` module will not have
|
||||
* access to the Electron modules. Therefore, to collect crash reports from them,
|
||||
* use `process.crashReporter.start` instead. Pass the same options as above along
|
||||
* with an additional one called `crashesDirectory` that should point to a
|
||||
* directory to store the crash reports temporarily. You can test this out by
|
||||
* calling `process.crash()` to crash the child process.
|
||||
*
|
||||
* **Note:** If you need send additional/updated `extra` parameters after your
|
||||
* first call `start` you can call `addExtraParameter` on macOS or call `start`
|
||||
* again with the new/updated `extra` parameters on Linux and Windows.
|
||||
*
|
||||
* **Note:** On macOS and windows, Electron uses a new `crashpad` client for crash
|
||||
* collection and reporting. If you want to enable crash reporting, initializing
|
||||
* `crashpad` from the main process using `crashReporter.start` is required
|
||||
* regardless of which process you want to collect crashes from. Once initialized
|
||||
* this way, the crashpad handler collects crashes from all processes. You still
|
||||
* have to call `crashReporter.start` from the renderer or child process, otherwise
|
||||
* crashes from them will get reported without `companyName`, `productName` or any
|
||||
* of the `extra` information.
|
||||
*/
|
||||
start(options: CrashReporterStartOptions): void;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ipcRenderer, crashReporter, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
suite('Sandbox', () => {
|
||||
test('globals', () => {
|
||||
assert.ok(ipcRenderer);
|
||||
assert.ok(crashReporter);
|
||||
assert.ok(webFrame);
|
||||
});
|
||||
});
|
||||
@@ -3,48 +3,294 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { compareFileNames, compareFileExtensions } from 'vs/base/common/comparers';
|
||||
import { compareFileNames, compareFileExtensions, compareFileNamesNumeric, compareFileExtensionsNumeric } from 'vs/base/common/comparers';
|
||||
import * as assert from 'assert';
|
||||
|
||||
const compareLocale = (a: string, b: string) => a.localeCompare(b);
|
||||
const compareLocaleNumeric = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true });
|
||||
|
||||
|
||||
suite('Comparers', () => {
|
||||
|
||||
test('compareFileNames', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same results as compareFileNamesNumeric
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileNames(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileNames(null, 'abc') < 0, 'null should be come before real values');
|
||||
assert(compareFileNames('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileNames('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileNames('.abc', '.abc') === 0, 'equal full names should be equal');
|
||||
assert(compareFileNames('.env', '.env.example') < 0, 'filenames with extensions should come after those without');
|
||||
assert(compareFileNames('.env.example', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
|
||||
assert(compareFileNames('z', 'A') > 0, 'z comes is after A regardless of case');
|
||||
assert(compareFileNames('Z', 'a') > 0, 'Z comes after a regardless of case');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileNames('bbb.aaa', 'aaa.bbb') > 0, 'files with extensions are compared first by filename');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileNames('.abc', '.abc') === 0, 'equal dotfile names should be equal');
|
||||
assert(compareFileNames('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
|
||||
assert(compareFileNames('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileNames('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
assert(compareFileNames('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileNames(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileNames('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileNames('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileNames('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
assert(compareFileNames('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNames('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileNames('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileNames('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileNames('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileNames('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileNames('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
|
||||
//
|
||||
// Comparisons with different results than compareFileNamesNumeric
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileNames('a', 'A') !== compareLocale('a', 'A'), 'the same letter does not sort by locale');
|
||||
assert(compareFileNames('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter does not sort by locale');
|
||||
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNames), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
|
||||
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNames), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents do not sort in locale order');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileNames('aggregate.go', 'aggregate_repo.go') > 0, 'compares the whole name all at once by locale');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNames('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
|
||||
assert(compareFileNames('abc.txt1', 'abc.txt01') > 0, 'same name plus extensions with equal numbers sort in unicode order');
|
||||
assert(compareFileNames('art01', 'Art01') !== 'art01'.localeCompare('Art01', undefined, { numeric: true }),
|
||||
'a numerically equivalent word of a different case does not compare numerically based on locale');
|
||||
|
||||
});
|
||||
|
||||
test('compareFileExtensions', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same results as compareFileExtensionsNumeric
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileExtensions(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileExtensions(null, '.abc') < 0, 'null should come before real files');
|
||||
assert(compareFileExtensions(null, 'abc') < 0, 'null should come before real files without extension');
|
||||
assert(compareFileExtensions('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileExtensions('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileExtensions('.abc', '.abc') === 0, 'equal full names should be equal');
|
||||
assert(compareFileExtensions('z', 'A') > 0, 'z comes after A');
|
||||
assert(compareFileExtensions('Z', 'a') > 0, 'Z comes after a');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensions('file.ext', 'file.ext') === 0, 'equal full names should be equal');
|
||||
assert(compareFileExtensions('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileExtensions('.ext', 'a.ext') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others');
|
||||
assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files should be compared by extensions');
|
||||
assert(compareFileExtensions('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileExtensions('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extensions even if filenames compare differently');
|
||||
assert(compareFileExtensions('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
|
||||
assert(compareFileExtensions('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore');
|
||||
assert(compareFileExtensions('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensions('.abc', '.abc') === 0, 'equal dotfiles should be equal');
|
||||
assert(compareFileExtensions('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileExtensions(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileExtensions('.env', 'aaa.env') < 0, 'if equal extensions, filenames should be compared, empty filename should come before others');
|
||||
assert(compareFileExtensions('.MD', 'a.md') < 0, 'if extensions differ in case, files sort by extension in unicode order');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileExtensions('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileExtensions('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileExtensions('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensions('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileExtensions('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileExtensions('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
assert(compareFileExtensions('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensions('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
|
||||
assert(compareFileExtensions('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensions('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileExtensions('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
|
||||
assert(compareFileExtensions('file2.ext2', 'file1.ext10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensions('file.ext01', 'file.ext1') < 0, 'extensions with equal numbers should be in alphabetical order');
|
||||
assert(compareFileExtensions('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
|
||||
|
||||
// Same extension comparison that has the same result as compareFileExtensionsNumeric, but a different result than compareFileNames
|
||||
// This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension.
|
||||
assert(compareFileExtensions('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
|
||||
|
||||
//
|
||||
// Comparisons with different results from compareFileExtensionsNumeric
|
||||
//
|
||||
|
||||
// name-only comparisions
|
||||
assert(compareFileExtensions('a', 'A') !== compareLocale('a', 'A'), 'the same letter of different case does not sort by locale');
|
||||
assert(compareFileExtensions('â', 'Â') !== compareLocale('â', 'Â'), 'the same accented letter of different case does not sort by locale');
|
||||
assert.notDeepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensions), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases do not sort in locale order');
|
||||
assert.notDeepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensions), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents do not sort in locale order');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensions('a.MD', 'a.md') !== compareLocale('MD', 'md'), 'case differences in extensions do not sort by locale');
|
||||
assert(compareFileExtensions('a.md', 'A.md') !== compareLocale('a', 'A'), 'case differences in names do not sort by locale');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensions('.env', '.aaa.env') < 0, 'a dotfile with an extension is treated as a name plus an extension - equal extensions');
|
||||
assert(compareFileExtensions('.env', '.env.aaa') > 0, 'a dotfile with an extension is treated as a name plus an extension - unequal extensions');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileExtensions('.env', 'aaa') > 0, 'filenames without extensions come before dotfiles');
|
||||
assert(compareFileExtensions('.md', 'A.MD') > 0, 'a file with an uppercase extension sorts before a dotfile of the same lowercase extension');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileExtensions('abc.txt01', 'abc.txt1') < 0, 'extensions with equal numbers sort in unicode order');
|
||||
assert(compareFileExtensions('art01', 'Art01') !== compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case does not compare by locale');
|
||||
assert(compareFileExtensions('abc02.txt', 'abc002.txt') > 0, 'filenames with equivalent numbers and leading zeros sort in unicode order');
|
||||
assert(compareFileExtensions('txt.abc01', 'txt.abc1') < 0, 'extensions with equivalent numbers sort in unicode order');
|
||||
|
||||
});
|
||||
|
||||
test('compareFileNamesNumeric', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same results as compareFileNames
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileNamesNumeric(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileNamesNumeric(null, 'abc') < 0, 'null should be come before real values');
|
||||
assert(compareFileNamesNumeric('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileNamesNumeric('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileNamesNumeric('z', 'A') > 0, 'z comes is after A regardless of case');
|
||||
assert(compareFileNamesNumeric('Z', 'a') > 0, 'Z comes after a regardless of case');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileNamesNumeric('file.ext', 'file.ext') === 0, 'equal full names should be equal');
|
||||
assert(compareFileNamesNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileNamesNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileNamesNumeric('bbb.aaa', 'aaa.bbb') > 0, 'files should be compared by names even if extensions compare differently');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileNamesNumeric('.abc', '.abc') === 0, 'equal dotfile names should be equal');
|
||||
assert(compareFileNamesNumeric('.env.', '.gitattributes') < 0, 'filenames starting with dots and with extensions should still sort properly');
|
||||
assert(compareFileNamesNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileNamesNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
assert(compareFileNamesNumeric('.aaa_env', '.aaa.env') < 0, 'and underscore in a dotfile name will sort before a dot');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileNamesNumeric(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileNamesNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileNamesNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileNamesNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
assert(compareFileNamesNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNamesNumeric('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileNamesNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileNamesNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileNamesNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileNamesNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileNamesNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
|
||||
//
|
||||
// Comparisons with different results than compareFileNames
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileNamesNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter sorts by locale');
|
||||
assert(compareFileNamesNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileNamesNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileNamesNumeric), ['email', 'Email', 'émail', 'Émail'].sort(compareLocale), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// name plus extensions comparisons
|
||||
assert(compareFileNamesNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'compares the name first, then the extension');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileNamesNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest number first');
|
||||
assert(compareFileNamesNumeric('abc.txt1', 'abc.txt01') < 0, 'same name plus extensions with equal numbers sort shortest number first');
|
||||
assert(compareFileNamesNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
|
||||
|
||||
});
|
||||
|
||||
test('compareFileExtensionsNumeric', () => {
|
||||
|
||||
//
|
||||
// Comparisons with the same result as compareFileExtensions
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileExtensionsNumeric(null, null) === 0, 'null should be equal');
|
||||
assert(compareFileExtensionsNumeric(null, 'abc') < 0, 'null should come before real files without extensions');
|
||||
assert(compareFileExtensionsNumeric('', '') === 0, 'empty should be equal');
|
||||
assert(compareFileExtensionsNumeric('abc', 'abc') === 0, 'equal names should be equal');
|
||||
assert(compareFileExtensionsNumeric('z', 'A') > 0, 'z comes after A');
|
||||
assert(compareFileExtensionsNumeric('Z', 'a') > 0, 'Z comes after a');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensionsNumeric('file.ext', 'file.ext') === 0, 'equal full filenames should be equal');
|
||||
assert(compareFileExtensionsNumeric('a.ext', 'b.ext') < 0, 'if equal extensions, filenames should be compared');
|
||||
assert(compareFileExtensionsNumeric('file.aaa', 'file.bbb') < 0, 'files with equal names should be compared by extensions');
|
||||
assert(compareFileExtensionsNumeric('bbb.aaa', 'aaa.bbb') < 0, 'files should be compared by extension first');
|
||||
assert(compareFileExtensionsNumeric('agg.go', 'aggrepo.go') < 0, 'shorter names sort before longer names');
|
||||
assert(compareFileExtensionsNumeric('agg.go', 'agg_repo.go') < 0, 'shorter names short before longer names even when the longer name contains an underscore');
|
||||
assert(compareFileExtensionsNumeric('a.MD', 'b.md') < 0, 'when extensions are the same except for case, the files sort by name');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric('.abc', '.abc') === 0, 'equal dotfiles should be equal');
|
||||
assert(compareFileExtensionsNumeric('.md', '.Gitattributes') > 0, 'dotfiles sort alphabetically regardless of case');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric(null, '.abc') < 0, 'null should come before dotfiles');
|
||||
assert(compareFileExtensionsNumeric('.env', 'aaa.env') < 0, 'dotfiles come before filenames with extensions');
|
||||
assert(compareFileExtensionsNumeric('.MD', 'a.md') < 0, 'dotfiles sort before lowercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileExtensionsNumeric('1', '1') === 0, 'numerically equal full names should be equal');
|
||||
assert(compareFileExtensionsNumeric('abc1.txt', 'abc1.txt') === 0, 'equal filenames with numbers should be equal');
|
||||
assert(compareFileExtensionsNumeric('abc1.txt', 'abc2.txt') < 0, 'filenames with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsNumeric('abc2.txt', 'abc10.txt') < 0, 'filenames with numbers should be in numerical order');
|
||||
assert(compareFileExtensionsNumeric('abc02.txt', 'abc010.txt') < 0, 'filenames with numbers that have leading zeros sort numerically');
|
||||
assert(compareFileExtensionsNumeric('abc1.10.txt', 'abc1.2.txt') > 0, 'numbers with dots between them are treated as two separate numbers, not one decimal number');
|
||||
assert(compareFileExtensionsNumeric('abc2.txt2', 'abc1.txt10') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc1') === 0, 'equal extensions with numbers should be equal');
|
||||
assert(compareFileExtensionsNumeric('txt.abc1', 'txt.abc2') < 0, 'extensions with numbers should be in numerical order, not alphabetical order');
|
||||
assert(compareFileExtensionsNumeric('txt.abc2', 'txt.abc10') < 0, 'extensions with numbers should be in numerical order even when they are multiple digits long');
|
||||
assert(compareFileExtensionsNumeric('a.ext1', 'b.ext1') < 0, 'if equal extensions with numbers, filenames should be compared');
|
||||
assert(compareFileExtensionsNumeric('a10.txt', 'A2.txt') > 0, 'filenames with number and case differences compare numerically');
|
||||
|
||||
// Same extension comparison that has the same result as compareFileExtensions, but a different result than compareFileNames
|
||||
// This is an edge case caused by compareFileNames comparing the whole name all at once instead of the name and then the extension.
|
||||
assert(compareFileExtensionsNumeric('aggregate.go', 'aggregate_repo.go') < 0, 'when extensions are equal, names sort in dictionary order');
|
||||
|
||||
//
|
||||
// Comparisons with different results than compareFileExtensions
|
||||
//
|
||||
|
||||
// name-only comparisons
|
||||
assert(compareFileExtensionsNumeric('a', 'A') === compareLocale('a', 'A'), 'the same letter of different case sorts by locale');
|
||||
assert(compareFileExtensionsNumeric('â', 'Â') === compareLocale('â', 'Â'), 'the same accented letter of different case sorts by locale');
|
||||
assert.deepEqual(['artichoke', 'Artichoke', 'art', 'Art'].sort(compareFileExtensionsNumeric), ['artichoke', 'Artichoke', 'art', 'Art'].sort(compareLocale), 'words with the same root and different cases sort in locale order');
|
||||
assert.deepEqual(['email', 'Email', 'émail', 'Émail'].sort(compareFileExtensionsNumeric), ['email', 'Email', 'émail', 'Émail'].sort((a, b) => a.localeCompare(b)), 'the same base characters with different case or accents sort in locale order');
|
||||
|
||||
// name plus extension comparisons
|
||||
assert(compareFileExtensionsNumeric('a.MD', 'a.md') === compareLocale('MD', 'md'), 'case differences in extensions sort by locale');
|
||||
assert(compareFileExtensionsNumeric('a.md', 'A.md') === compareLocale('a', 'A'), 'case differences in names sort by locale');
|
||||
|
||||
// dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric('.env', '.aaa.env') > 0, 'dotfiles sort alphabetically when they contain multiple dots');
|
||||
assert(compareFileExtensionsNumeric('.env', '.env.aaa') < 0, 'dotfiles with the same root sort shortest first');
|
||||
|
||||
// dotfile vs non-dotfile comparisons
|
||||
assert(compareFileExtensionsNumeric('.env', 'aaa') < 0, 'dotfiles come before filenames without extensions');
|
||||
assert(compareFileExtensionsNumeric('.md', 'A.MD') < 0, 'dotfiles sort before uppercase files');
|
||||
|
||||
// numeric comparisons
|
||||
assert(compareFileExtensionsNumeric('abc.txt01', 'abc.txt1') > 0, 'extensions with equal numbers should be in shortest-first order');
|
||||
assert(compareFileExtensionsNumeric('art01', 'Art01') === compareLocaleNumeric('art01', 'Art01'), 'a numerically equivalent word of a different case compares numerically based on locale');
|
||||
assert(compareFileExtensionsNumeric('abc02.txt', 'abc002.txt') < 0, 'filenames with equivalent numbers and leading zeros sort shortest string first');
|
||||
assert(compareFileExtensionsNumeric('txt.abc01', 'txt.abc1') > 0, 'extensions with equivalent numbers sort shortest extension first');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -130,4 +130,41 @@ suite('Paths', () => {
|
||||
assert.equal(extpath.indexOfPath('/some/long/path', '/some/long', false), 0);
|
||||
assert.equal(extpath.indexOfPath('/some/long/path', '/PATH', true), 10);
|
||||
});
|
||||
|
||||
test('parseLineAndColumnAware', () => {
|
||||
let res = extpath.parseLineAndColumnAware('/foo/bar');
|
||||
assert.equal(res.path, '/foo/bar');
|
||||
assert.equal(res.line, undefined);
|
||||
assert.equal(res.column, undefined);
|
||||
|
||||
res = extpath.parseLineAndColumnAware('/foo/bar:33');
|
||||
assert.equal(res.path, '/foo/bar');
|
||||
assert.equal(res.line, 33);
|
||||
assert.equal(res.column, 1);
|
||||
|
||||
res = extpath.parseLineAndColumnAware('/foo/bar:33:34');
|
||||
assert.equal(res.path, '/foo/bar');
|
||||
assert.equal(res.line, 33);
|
||||
assert.equal(res.column, 34);
|
||||
|
||||
res = extpath.parseLineAndColumnAware('C:\\foo\\bar');
|
||||
assert.equal(res.path, 'C:\\foo\\bar');
|
||||
assert.equal(res.line, undefined);
|
||||
assert.equal(res.column, undefined);
|
||||
|
||||
res = extpath.parseLineAndColumnAware('C:\\foo\\bar:33');
|
||||
assert.equal(res.path, 'C:\\foo\\bar');
|
||||
assert.equal(res.line, 33);
|
||||
assert.equal(res.column, 1);
|
||||
|
||||
res = extpath.parseLineAndColumnAware('C:\\foo\\bar:33:34');
|
||||
assert.equal(res.path, 'C:\\foo\\bar');
|
||||
assert.equal(res.line, 33);
|
||||
assert.equal(res.column, 34);
|
||||
|
||||
res = extpath.parseLineAndColumnAware('/foo/bar:abb');
|
||||
assert.equal(res.path, '/foo/bar:abb');
|
||||
assert.equal(res.line, undefined);
|
||||
assert.equal(res.column, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,8 +13,8 @@ suite('Map', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
assert.deepStrictEqual(map.keys(), ['ak', 'bk']);
|
||||
assert.deepStrictEqual(map.values(), ['av', 'bv']);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak', 'bk']);
|
||||
assert.deepStrictEqual([...map.values()], ['av', 'bv']);
|
||||
assert.equal(map.first, 'av');
|
||||
assert.equal(map.last, 'bv');
|
||||
});
|
||||
@@ -23,16 +23,16 @@ suite('Map', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('ak', 'av', Touch.AsOld);
|
||||
assert.deepStrictEqual(map.keys(), ['ak']);
|
||||
assert.deepStrictEqual(map.values(), ['av']);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New one', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('ak', 'av', Touch.AsNew);
|
||||
assert.deepStrictEqual(map.keys(), ['ak']);
|
||||
assert.deepStrictEqual(map.values(), ['av']);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old two', () => {
|
||||
@@ -40,8 +40,8 @@ suite('Map', () => {
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('bk', 'bv', Touch.AsOld);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av']);
|
||||
assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['bv', 'av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New two', () => {
|
||||
@@ -49,8 +49,8 @@ suite('Map', () => {
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('ak', 'av', Touch.AsNew);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av']);
|
||||
assert.deepStrictEqual([...map.keys()], ['bk', 'ak']);
|
||||
assert.deepStrictEqual([...map.values()], ['bv', 'av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old from middle', () => {
|
||||
@@ -59,8 +59,8 @@ suite('Map', () => {
|
||||
map.set('bk', 'bv');
|
||||
map.set('ck', 'cv');
|
||||
map.set('bk', 'bv', Touch.AsOld);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']);
|
||||
assert.deepStrictEqual([...map.keys()], ['bk', 'ak', 'ck']);
|
||||
assert.deepStrictEqual([...map.values()], ['bv', 'av', 'cv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New from middle', () => {
|
||||
@@ -69,8 +69,8 @@ suite('Map', () => {
|
||||
map.set('bk', 'bv');
|
||||
map.set('ck', 'cv');
|
||||
map.set('bk', 'bv', Touch.AsNew);
|
||||
assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']);
|
||||
assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']);
|
||||
assert.deepStrictEqual([...map.keys()], ['ak', 'ck', 'bk']);
|
||||
assert.deepStrictEqual([...map.values()], ['av', 'cv', 'bv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - basics', function () {
|
||||
@@ -129,6 +129,61 @@ suite('Map', () => {
|
||||
assert.ok(!map.has('1'));
|
||||
});
|
||||
|
||||
test('LinkedMap - Iterators', () => {
|
||||
const map = new LinkedMap<number, any>();
|
||||
map.set(1, 1);
|
||||
map.set(2, 2);
|
||||
map.set(3, 3);
|
||||
|
||||
for (const elem of map.keys()) {
|
||||
assert.ok(elem);
|
||||
}
|
||||
|
||||
for (const elem of map.values()) {
|
||||
assert.ok(elem);
|
||||
}
|
||||
|
||||
for (const elem of map.entries()) {
|
||||
assert.ok(elem);
|
||||
}
|
||||
|
||||
{
|
||||
const keys = map.keys();
|
||||
const values = map.values();
|
||||
const entries = map.entries();
|
||||
map.get(1);
|
||||
keys.next();
|
||||
values.next();
|
||||
entries.next();
|
||||
}
|
||||
|
||||
{
|
||||
const keys = map.keys();
|
||||
const values = map.values();
|
||||
const entries = map.entries();
|
||||
map.get(1, Touch.AsNew);
|
||||
|
||||
let exceptions: number = 0;
|
||||
try {
|
||||
keys.next();
|
||||
} catch (err) {
|
||||
exceptions++;
|
||||
}
|
||||
try {
|
||||
values.next();
|
||||
} catch (err) {
|
||||
exceptions++;
|
||||
}
|
||||
try {
|
||||
entries.next();
|
||||
} catch (err) {
|
||||
exceptions++;
|
||||
}
|
||||
|
||||
assert.strictEqual(exceptions, 3);
|
||||
}
|
||||
});
|
||||
|
||||
test('LinkedMap - LRU Cache simple', () => {
|
||||
const cache = new LRUCache<number, number>(5);
|
||||
|
||||
@@ -136,10 +191,10 @@ suite('Map', () => {
|
||||
assert.strictEqual(cache.size, 5);
|
||||
cache.set(6, 6);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]);
|
||||
assert.deepStrictEqual([...cache.keys()], [2, 3, 4, 5, 6]);
|
||||
cache.set(7, 7);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]);
|
||||
assert.deepStrictEqual([...cache.keys()], [3, 4, 5, 6, 7]);
|
||||
let values: number[] = [];
|
||||
[3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)!));
|
||||
assert.deepStrictEqual(values, [3, 4, 5, 6, 7]);
|
||||
@@ -150,11 +205,11 @@ suite('Map', () => {
|
||||
|
||||
[1, 2, 3, 4, 5].forEach(value => cache.set(value, value));
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [1, 2, 3, 4, 5]);
|
||||
assert.deepStrictEqual([...cache.keys()], [1, 2, 3, 4, 5]);
|
||||
cache.get(3);
|
||||
assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
|
||||
assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
|
||||
cache.peek(4);
|
||||
assert.deepStrictEqual(cache.keys(), [1, 2, 4, 5, 3]);
|
||||
assert.deepStrictEqual([...cache.keys()], [1, 2, 4, 5, 3]);
|
||||
let values: number[] = [];
|
||||
[1, 2, 3, 4, 5].forEach(key => values.push(cache.get(key)!));
|
||||
assert.deepStrictEqual(values, [1, 2, 3, 4, 5]);
|
||||
@@ -169,7 +224,7 @@ suite('Map', () => {
|
||||
assert.strictEqual(cache.size, 10);
|
||||
cache.limit = 5;
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]);
|
||||
assert.deepStrictEqual([...cache.keys()], [6, 7, 8, 9, 10]);
|
||||
cache.limit = 20;
|
||||
assert.strictEqual(cache.size, 5);
|
||||
for (let i = 11; i <= 20; i++) {
|
||||
@@ -181,7 +236,7 @@ suite('Map', () => {
|
||||
values.push(cache.get(i)!);
|
||||
assert.strictEqual(cache.get(i), i);
|
||||
}
|
||||
assert.deepStrictEqual(cache.values(), values);
|
||||
assert.deepStrictEqual([...cache.values()], values);
|
||||
});
|
||||
|
||||
test('LinkedMap - LRU Cache limit with ratio', () => {
|
||||
@@ -193,11 +248,11 @@ suite('Map', () => {
|
||||
assert.strictEqual(cache.size, 10);
|
||||
cache.set(11, 11);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]);
|
||||
assert.deepStrictEqual([...cache.keys()], [7, 8, 9, 10, 11]);
|
||||
let values: number[] = [];
|
||||
cache.keys().forEach(key => values.push(cache.get(key)!));
|
||||
[...cache.keys()].forEach(key => values.push(cache.get(key)!));
|
||||
assert.deepStrictEqual(values, [7, 8, 9, 10, 11]);
|
||||
assert.deepStrictEqual(cache.values(), values);
|
||||
assert.deepStrictEqual([...cache.values()], values);
|
||||
});
|
||||
|
||||
test('LinkedMap - toJSON / fromJSON', () => {
|
||||
@@ -222,7 +277,6 @@ suite('Map', () => {
|
||||
assert.equal(key, 'ck');
|
||||
assert.equal(value, 'cv');
|
||||
}
|
||||
|
||||
i++;
|
||||
});
|
||||
});
|
||||
@@ -237,7 +291,7 @@ suite('Map', () => {
|
||||
map.delete('1');
|
||||
assert.equal(map.get('1'), undefined);
|
||||
assert.equal(map.size, 0);
|
||||
assert.equal(map.keys().length, 0);
|
||||
assert.equal([...map.keys()].length, 0);
|
||||
});
|
||||
|
||||
test('LinkedMap - delete Head', function () {
|
||||
@@ -251,8 +305,8 @@ suite('Map', () => {
|
||||
map.delete('1');
|
||||
assert.equal(map.get('2'), 2);
|
||||
assert.equal(map.size, 1);
|
||||
assert.equal(map.keys().length, 1);
|
||||
assert.equal(map.keys()[0], 2);
|
||||
assert.equal([...map.keys()].length, 1);
|
||||
assert.equal([...map.keys()][0], 2);
|
||||
});
|
||||
|
||||
test('LinkedMap - delete Tail', function () {
|
||||
@@ -266,8 +320,8 @@ suite('Map', () => {
|
||||
map.delete('2');
|
||||
assert.equal(map.get('1'), 1);
|
||||
assert.equal(map.size, 1);
|
||||
assert.equal(map.keys().length, 1);
|
||||
assert.equal(map.keys()[0], 1);
|
||||
assert.equal([...map.keys()].length, 1);
|
||||
assert.equal([...map.keys()][0], 1);
|
||||
});
|
||||
|
||||
|
||||
@@ -317,7 +371,8 @@ suite('Map', () => {
|
||||
iter.reset(URI.parse('file:///usr/bin/file.txt'));
|
||||
|
||||
assert.equal(iter.value(), 'file');
|
||||
assert.equal(iter.cmp('FILE'), 0);
|
||||
// assert.equal(iter.cmp('FILE'), 0);
|
||||
assert.equal(iter.cmp('file'), 0);
|
||||
assert.equal(iter.hasNext(), true);
|
||||
iter.next();
|
||||
|
||||
@@ -337,7 +392,8 @@ suite('Map', () => {
|
||||
|
||||
// scheme
|
||||
assert.equal(iter.value(), 'file');
|
||||
assert.equal(iter.cmp('FILE'), 0);
|
||||
// assert.equal(iter.cmp('FILE'), 0);
|
||||
assert.equal(iter.cmp('file'), 0);
|
||||
assert.equal(iter.hasNext(), true);
|
||||
iter.next();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, getComparisonKey } from 'vs/base/common/resources';
|
||||
import { dirname, basename, distinctParents, joinPath, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator, extUri, extUriIgnorePathCase } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { toSlashes } from 'vs/base/common/extpath';
|
||||
@@ -235,16 +235,19 @@ suite('Resources', () => {
|
||||
}
|
||||
});
|
||||
|
||||
function assertEqualURI(actual: URI, expected: URI, message?: string) {
|
||||
if (!isEqual(expected, actual, hasToIgnoreCase(expected), false)) {
|
||||
function assertEqualURI(actual: URI, expected: URI, message?: string, ignoreCase?: boolean) {
|
||||
let util = ignoreCase ? extUriIgnorePathCase : extUri;
|
||||
if (!util.isEqual(expected, actual)) {
|
||||
assert.equal(actual.toString(), expected.toString(), message);
|
||||
}
|
||||
}
|
||||
|
||||
function assertRelativePath(u1: URI, u2: URI, expectedPath: string | undefined, ignoreJoin?: boolean, ignoreCase?: boolean) {
|
||||
assert.equal(relativePath(u1, u2, ignoreCase), expectedPath, `from ${u1.toString()} to ${u2.toString()}`);
|
||||
let util = ignoreCase ? extUriIgnorePathCase : extUri;
|
||||
|
||||
assert.equal(util.relativePath(u1, u2), expectedPath, `from ${u1.toString()} to ${u2.toString()}`);
|
||||
if (expectedPath !== undefined && !ignoreJoin) {
|
||||
assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal');
|
||||
assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal', ignoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,14 +257,14 @@ suite('Resources', () => {
|
||||
assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/bar/goo'), 'bar/goo');
|
||||
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/foo/bar/goo'), 'foo/bar/goo');
|
||||
assertRelativePath(URI.parse('foo://a/foo/xoo'), URI.parse('foo://a/foo/bar'), '../bar');
|
||||
assertRelativePath(URI.parse('foo://a/foo/xoo/yoo'), URI.parse('foo://a'), '../../..');
|
||||
assertRelativePath(URI.parse('foo://a/foo/xoo/yoo'), URI.parse('foo://a'), '../../..', true);
|
||||
assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/'), '');
|
||||
assertRelativePath(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo'), '');
|
||||
assertRelativePath(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo/'), '');
|
||||
assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo'), '');
|
||||
assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), '');
|
||||
assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), '', true);
|
||||
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/'), '');
|
||||
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), '');
|
||||
assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), '', true);
|
||||
assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar', true);
|
||||
assertRelativePath(URI.parse('foo://'), URI.parse('foo://a/b'), undefined);
|
||||
assertRelativePath(URI.parse('foo://a2/b'), URI.parse('foo://a/b'), undefined);
|
||||
@@ -346,10 +349,17 @@ suite('Resources', () => {
|
||||
|
||||
});
|
||||
|
||||
function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean, expected: boolean) {
|
||||
assert.equal(isEqual(u1, u2, ignoreCase), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
|
||||
assert.equal(getComparisonKey(u1, ignoreCase) === getComparisonKey(u2, ignoreCase), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
|
||||
assert.equal(isEqualOrParent(u1, u2, ignoreCase), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
|
||||
function assertIsEqual(u1: URI, u2: URI, ignoreCase: boolean | undefined, expected: boolean) {
|
||||
|
||||
let util = ignoreCase ? extUriIgnorePathCase : extUri;
|
||||
|
||||
assert.equal(util.isEqual(u1, u2), expected, `${u1.toString()}${expected ? '===' : '!=='}${u2.toString()}`);
|
||||
assert.equal(util.compare(u1, u2) === 0, expected);
|
||||
assert.equal(util.getComparisonKey(u1) === util.getComparisonKey(u2), expected, `comparison keys ${u1.toString()}, ${u2.toString()}`);
|
||||
assert.equal(util.isEqualOrParent(u1, u2), expected, `isEqualOrParent ${u1.toString()}, ${u2.toString()}`);
|
||||
if (!ignoreCase) {
|
||||
assert.equal(u1.toString() === u2.toString(), expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -358,7 +368,7 @@ suite('Resources', () => {
|
||||
let fileURI2 = isWindows ? URI.file('C:\\foo\\Bar') : URI.file('/foo/Bar');
|
||||
assertIsEqual(fileURI, fileURI, true, true);
|
||||
assertIsEqual(fileURI, fileURI, false, true);
|
||||
assertIsEqual(fileURI, fileURI, hasToIgnoreCase(fileURI), true);
|
||||
assertIsEqual(fileURI, fileURI, undefined, true);
|
||||
assertIsEqual(fileURI, fileURI2, true, true);
|
||||
assertIsEqual(fileURI, fileURI2, false, false);
|
||||
|
||||
@@ -366,13 +376,15 @@ suite('Resources', () => {
|
||||
let fileURI4 = URI.parse('foo://server:453/foo/Bar');
|
||||
assertIsEqual(fileURI3, fileURI3, true, true);
|
||||
assertIsEqual(fileURI3, fileURI3, false, true);
|
||||
assertIsEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3), true);
|
||||
assertIsEqual(fileURI3, fileURI3, undefined, true);
|
||||
assertIsEqual(fileURI3, fileURI4, true, true);
|
||||
assertIsEqual(fileURI3, fileURI4, false, false);
|
||||
|
||||
assertIsEqual(fileURI, fileURI3, true, false);
|
||||
|
||||
assertIsEqual(URI.parse('foo://server'), URI.parse('foo://server/'), true, true);
|
||||
assertIsEqual(URI.parse('file://server'), URI.parse('file://server/'), true, true);
|
||||
assertIsEqual(URI.parse('http://server'), URI.parse('http://server/'), true, true);
|
||||
assertIsEqual(URI.parse('foo://server'), URI.parse('foo://server/'), true, false); // only selected scheme have / as the default path
|
||||
assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo/'), true, false);
|
||||
assertIsEqual(URI.parse('foo://server/foo'), URI.parse('foo://server/foo?'), true, true);
|
||||
|
||||
@@ -383,38 +395,39 @@ suite('Resources', () => {
|
||||
assertIsEqual(fileURI5, fileURI3, true, false);
|
||||
assertIsEqual(fileURI6, fileURI6, true, true);
|
||||
assertIsEqual(fileURI6, fileURI5, true, false);
|
||||
assertIsEqual(fileURI6, fileURI3, true, true);
|
||||
assertIsEqual(fileURI6, fileURI3, true, false);
|
||||
});
|
||||
|
||||
test('isEqualOrParent', () => {
|
||||
|
||||
let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar');
|
||||
let fileURI2 = isWindows ? URI.file('c:\\foo') : URI.file('/foo');
|
||||
let fileURI2b = isWindows ? URI.file('C:\\Foo\\') : URI.file('/Foo/');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI, true), true, '1');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI, false), true, '2');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2, true), true, '3');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2, false), true, '4');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2b, true), true, '5');
|
||||
assert.equal(isEqualOrParent(fileURI, fileURI2b, false), false, '6');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI), true, '1');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI, fileURI), true, '2');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2), true, '3');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI, fileURI2), true, '4');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI, fileURI2b), true, '5');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI, fileURI2b), false, '6');
|
||||
|
||||
assert.equal(isEqualOrParent(fileURI2, fileURI, false), false, '7');
|
||||
assert.equal(isEqualOrParent(fileURI2b, fileURI2, true), true, '8');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI2, fileURI), false, '7');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI2b, fileURI2), true, '8');
|
||||
|
||||
let fileURI3 = URI.parse('foo://server:453/foo/bar/goo');
|
||||
let fileURI4 = URI.parse('foo://server:453/foo/');
|
||||
let fileURI5 = URI.parse('foo://server:453/foo');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI3, true), true, '11');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI3, false), true, '12');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI4, true), true, '13');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI4, false), true, '14');
|
||||
assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15');
|
||||
assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI3, true), true, '11');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI3, fileURI3), true, '12');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI4, true), true, '13');
|
||||
assert.equal(extUri.isEqualOrParent(fileURI3, fileURI4), true, '14');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI3, fileURI, true), false, '15');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI5, fileURI5, true), true, '16');
|
||||
|
||||
let fileURI6 = URI.parse('foo://server:453/foo?q=1');
|
||||
let fileURI7 = URI.parse('foo://server:453/foo/bar?q=1');
|
||||
assert.equal(isEqualOrParent(fileURI6, fileURI5, true), false, '17');
|
||||
assert.equal(isEqualOrParent(fileURI6, fileURI6, true), true, '18');
|
||||
assert.equal(isEqualOrParent(fileURI7, fileURI6, true), true, '19');
|
||||
assert.equal(isEqualOrParent(fileURI7, fileURI5, true), false, '20');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI5), false, '17');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI6, fileURI6), true, '18');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI6), true, '19');
|
||||
assert.equal(extUriIgnorePathCase.isEqualOrParent(fileURI7, fileURI5), false, '20');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { SkipList } from 'vs/base/common/skipList';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import { binarySearch } from 'vs/base/common/arrays';
|
||||
|
||||
|
||||
suite('SkipList', function () {
|
||||
|
||||
function assertValues<V>(list: SkipList<any, V>, expected: V[]) {
|
||||
assert.equal(list.size, expected.length);
|
||||
assert.deepEqual([...list.values()], expected);
|
||||
|
||||
let valuesFromEntries = [...list.entries()].map(entry => entry[1]);
|
||||
assert.deepEqual(valuesFromEntries, expected);
|
||||
|
||||
let valuesFromIter = [...list].map(entry => entry[1]);
|
||||
assert.deepEqual(valuesFromIter, expected);
|
||||
|
||||
let i = 0;
|
||||
list.forEach((value, _key, map) => {
|
||||
assert.ok(map === list);
|
||||
assert.deepEqual(value, expected[i++]);
|
||||
});
|
||||
}
|
||||
|
||||
function assertKeys<K>(list: SkipList<K, any>, expected: K[]) {
|
||||
assert.equal(list.size, expected.length);
|
||||
assert.deepEqual([...list.keys()], expected);
|
||||
|
||||
let keysFromEntries = [...list.entries()].map(entry => entry[0]);
|
||||
assert.deepEqual(keysFromEntries, expected);
|
||||
|
||||
let keysFromIter = [...list].map(entry => entry[0]);
|
||||
assert.deepEqual(keysFromIter, expected);
|
||||
|
||||
let i = 0;
|
||||
list.forEach((_value, key, map) => {
|
||||
assert.ok(map === list);
|
||||
assert.deepEqual(key, expected[i++]);
|
||||
});
|
||||
}
|
||||
|
||||
test('set/get/delete', function () {
|
||||
let list = new SkipList<number, number>((a, b) => a - b);
|
||||
|
||||
assert.equal(list.get(3), undefined);
|
||||
list.set(3, 1);
|
||||
assert.equal(list.get(3), 1);
|
||||
assertValues(list, [1]);
|
||||
|
||||
list.set(3, 3);
|
||||
assertValues(list, [3]);
|
||||
|
||||
list.set(1, 1);
|
||||
list.set(4, 4);
|
||||
assert.equal(list.get(3), 3);
|
||||
assert.equal(list.get(1), 1);
|
||||
assert.equal(list.get(4), 4);
|
||||
assertValues(list, [1, 3, 4]);
|
||||
|
||||
assert.equal(list.delete(17), false);
|
||||
|
||||
assert.equal(list.delete(1), true);
|
||||
assert.equal(list.get(1), undefined);
|
||||
assert.equal(list.get(3), 3);
|
||||
assert.equal(list.get(4), 4);
|
||||
|
||||
assertValues(list, [3, 4]);
|
||||
});
|
||||
|
||||
test('Figure 3', function () {
|
||||
let list = new SkipList<number, boolean>((a, b) => a - b);
|
||||
list.set(3, true);
|
||||
list.set(6, true);
|
||||
list.set(7, true);
|
||||
list.set(9, true);
|
||||
list.set(12, true);
|
||||
list.set(19, true);
|
||||
list.set(21, true);
|
||||
list.set(25, true);
|
||||
|
||||
assertKeys(list, [3, 6, 7, 9, 12, 19, 21, 25]);
|
||||
|
||||
list.set(17, true);
|
||||
assert.deepEqual(list.size, 9);
|
||||
assertKeys(list, [3, 6, 7, 9, 12, 17, 19, 21, 25]);
|
||||
});
|
||||
|
||||
test('capacity max', function () {
|
||||
let list = new SkipList<number, boolean>((a, b) => a - b, 10);
|
||||
list.set(1, true);
|
||||
list.set(2, true);
|
||||
list.set(3, true);
|
||||
list.set(4, true);
|
||||
list.set(5, true);
|
||||
list.set(6, true);
|
||||
list.set(7, true);
|
||||
list.set(8, true);
|
||||
list.set(9, true);
|
||||
list.set(10, true);
|
||||
list.set(11, true);
|
||||
list.set(12, true);
|
||||
|
||||
assertKeys(list, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
||||
});
|
||||
|
||||
const cmp = (a: number, b: number): number => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
function insertArraySorted(array: number[], element: number) {
|
||||
let idx = binarySearch(array, element, cmp);
|
||||
if (idx >= 0) {
|
||||
array[idx] = element;
|
||||
} else {
|
||||
idx = ~idx;
|
||||
// array = array.slice(0, idx).concat(element, array.slice(idx));
|
||||
array.splice(idx, 0, element);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function delArraySorted(array: number[], element: number) {
|
||||
let idx = binarySearch(array, element, cmp);
|
||||
if (idx >= 0) {
|
||||
// array = array.slice(0, idx).concat(array.slice(idx));
|
||||
array.splice(idx, 1);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
test('perf', function () {
|
||||
this.skip();
|
||||
|
||||
// data
|
||||
const max = 2 ** 16;
|
||||
const values = new Set<number>();
|
||||
for (let i = 0; i < max; i++) {
|
||||
let value = Math.floor(Math.random() * max);
|
||||
values.add(value);
|
||||
}
|
||||
console.log(values.size);
|
||||
|
||||
// init
|
||||
let list = new SkipList<number, boolean>(cmp, max);
|
||||
let sw = new StopWatch(true);
|
||||
values.forEach(value => list.set(value, true));
|
||||
sw.stop();
|
||||
console.log(`[LIST] ${list.size} elements after ${sw.elapsed()}ms`);
|
||||
let array: number[] = [];
|
||||
sw = new StopWatch(true);
|
||||
values.forEach(value => array = insertArraySorted(array, value));
|
||||
sw.stop();
|
||||
console.log(`[ARRAY] ${array.length} elements after ${sw.elapsed()}ms`);
|
||||
|
||||
// get
|
||||
sw = new StopWatch(true);
|
||||
let someValues = [...values].slice(0, values.size / 4);
|
||||
someValues.forEach(key => {
|
||||
let value = list.get(key); // find
|
||||
console.assert(value, '[LIST] must have ' + key);
|
||||
list.get(-key); // miss
|
||||
});
|
||||
sw.stop();
|
||||
console.log(`[LIST] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
|
||||
sw = new StopWatch(true);
|
||||
someValues.forEach(key => {
|
||||
let idx = binarySearch(array, key, cmp); // find
|
||||
console.assert(idx >= 0, '[ARRAY] must have ' + key);
|
||||
binarySearch(array, -key, cmp); // miss
|
||||
});
|
||||
sw.stop();
|
||||
console.log(`[ARRAY] retrieve ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
|
||||
|
||||
|
||||
// insert
|
||||
sw = new StopWatch(true);
|
||||
someValues.forEach(key => {
|
||||
list.set(-key, false);
|
||||
});
|
||||
sw.stop();
|
||||
console.log(`[LIST] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`);
|
||||
sw = new StopWatch(true);
|
||||
someValues.forEach(key => {
|
||||
array = insertArraySorted(array, -key);
|
||||
});
|
||||
sw.stop();
|
||||
console.log(`[ARRAY] insert ${sw.elapsed()}ms (${(sw.elapsed() / someValues.length).toPrecision(4)}ms/op)`);
|
||||
|
||||
// delete
|
||||
sw = new StopWatch(true);
|
||||
someValues.forEach(key => {
|
||||
list.delete(key); // find
|
||||
list.delete(-key); // miss
|
||||
});
|
||||
sw.stop();
|
||||
console.log(`[LIST] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
|
||||
sw = new StopWatch(true);
|
||||
someValues.forEach(key => {
|
||||
array = delArraySorted(array, key); // find
|
||||
array = delArraySorted(array, -key); // miss
|
||||
});
|
||||
sw.stop();
|
||||
console.log(`[ARRAY] delete ${sw.elapsed()}ms (${(sw.elapsed() / (someValues.length * 2)).toPrecision(4)}ms/op)`);
|
||||
});
|
||||
});
|
||||
@@ -32,7 +32,6 @@
|
||||
'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
||||
'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
|
||||
'@angular/core': `${window.location.origin}/static/remote/web/node_modules/@angular/core/bundles/core.umd.js`,
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
|
||||
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||
'xterm-addon-unicode11': `${window.location.origin}/static/node_modules/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`,
|
||||
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
||||
'xterm-addon-webgl': `${window.location.origin}/static/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
|
||||
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
|
||||
'@angular/core': `${window.location.origin}/static/node_modules/@angular/core/bundles/core.umd.js`,
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { clipboard, ipcRenderer, shell, webFrame } from 'electron';
|
||||
import 'vs/css!./media/issueReporter';
|
||||
import { ElectronService, IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import * as os from 'os';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { $, windowOpenNoOpener } from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codicons/codiconLabel';
|
||||
@@ -15,21 +17,19 @@ import { debounce } from 'vs/base/common/decorators';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { getDelayedChannel, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
|
||||
import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
|
||||
import 'vs/css!./media/issueReporter';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { EnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/node/issue';
|
||||
import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
|
||||
import { getLogLevel, ILogService } from 'vs/platform/log/common/log';
|
||||
import { FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
@@ -64,6 +64,7 @@ export function startup(configuration: IssueReporterConfiguration) {
|
||||
|
||||
export class IssueReporter extends Disposable {
|
||||
private environmentService!: INativeEnvironmentService;
|
||||
private electronService!: IElectronService;
|
||||
private telemetryService!: ITelemetryService;
|
||||
private logService!: ILogService;
|
||||
private readonly issueReporterModel: IssueReporterModel;
|
||||
@@ -324,6 +325,9 @@ export class IssueReporter extends Disposable {
|
||||
const mainProcessService = new MainProcessService(configuration.windowId);
|
||||
serviceCollection.set(IMainProcessService, mainProcessService);
|
||||
|
||||
this.electronService = new ElectronService(configuration.windowId, mainProcessService) as IElectronService;
|
||||
serviceCollection.set(IElectronService, this.electronService);
|
||||
|
||||
this.environmentService = new EnvironmentService(configuration, configuration.execPath);
|
||||
|
||||
const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService));
|
||||
@@ -462,7 +466,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
this.addEventListener('extensionBugsLink', 'click', (e: Event) => {
|
||||
const url = (<HTMLElement>e.target).innerText;
|
||||
shell.openExternal(url);
|
||||
windowOpenNoOpener(url);
|
||||
});
|
||||
|
||||
this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
|
||||
@@ -941,9 +945,9 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private async writeToClipboard(baseUrl: string, issueBody: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once('vscode:issueReporterClipboardResponse', (_: unknown, shouldWrite: boolean) => {
|
||||
ipcRenderer.once('vscode:issueReporterClipboardResponse', async (event: unknown, shouldWrite: boolean) => {
|
||||
if (shouldWrite) {
|
||||
clipboard.writeText(issueBody);
|
||||
await this.electronService.writeClipboardText(issueBody);
|
||||
resolve(baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`);
|
||||
} else {
|
||||
reject();
|
||||
@@ -1194,7 +1198,7 @@ export class IssueReporter extends Disposable {
|
||||
event.stopPropagation();
|
||||
// Exclude right click
|
||||
if (event.which < 3) {
|
||||
shell.openExternal((<HTMLAnchorElement>event.target).href);
|
||||
windowOpenNoOpener((<HTMLAnchorElement>event.target).href);
|
||||
this.telemetryService.publicLog2('issueReporterViewSimilarIssue');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/node/issue';
|
||||
import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue';
|
||||
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
|
||||
export interface IssueReporterData {
|
||||
|
||||
@@ -95,25 +95,25 @@ textarea, input, select {
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
color: #CCCCCC;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html:lang(zh-Hans) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(zh-Hant) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ja) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ko) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
|
||||
import { IssueType } from 'vs/platform/issue/node/issue';
|
||||
import { IssueType } from 'vs/platform/issue/common/issue';
|
||||
|
||||
suite('IssueReporter', () => {
|
||||
|
||||
|
||||
@@ -4,24 +4,24 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
html {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
html:lang(zh-Hans) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(zh-Hant) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ja) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif;
|
||||
}
|
||||
|
||||
html:lang(ko) {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -4,16 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/processExplorer';
|
||||
import { webFrame, ipcRenderer, clipboard } from 'electron';
|
||||
import { clipboard } from 'electron';
|
||||
import { webFrame, ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { repeat } from 'vs/base/common/strings';
|
||||
import { totalmem } from 'os';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/node/issue';
|
||||
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
|
||||
import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu';
|
||||
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
|
||||
import { ProcessItem } from 'vs/base/common/processes';
|
||||
import { addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
@@ -369,7 +370,7 @@ function requestProcessList(totalWaitTime: number): void {
|
||||
|
||||
// Wait at least a second between requests.
|
||||
if (waited > 1000) {
|
||||
ipcRenderer.send('windowsInfoRequest');
|
||||
ipcRenderer.send('vscode:windowsInfoRequest');
|
||||
ipcRenderer.send('vscode:listProcesses');
|
||||
} else {
|
||||
requestProcessList(waited);
|
||||
@@ -393,18 +394,18 @@ export function startup(data: ProcessExplorerData): void {
|
||||
createCloseListener();
|
||||
|
||||
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
|
||||
ipcRenderer.on('vscode:windowsInfoResponse', (_event: unknown, windows: any[]) => {
|
||||
ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => {
|
||||
mapPidToWindowTitle = new Map<number, string>();
|
||||
windows.forEach(window => mapPidToWindowTitle.set(window.pid, window.title));
|
||||
});
|
||||
|
||||
ipcRenderer.on('vscode:listProcessesResponse', (_event: Event, processRoots: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]) => {
|
||||
ipcRenderer.on('vscode:listProcessesResponse', (event: unknown, processRoots: [{ name: string, rootProcess: ProcessItem | IRemoteDiagnosticError }]) => {
|
||||
updateProcessInfo(processRoots);
|
||||
requestProcessList(0);
|
||||
});
|
||||
|
||||
lastRequestTime = Date.now();
|
||||
ipcRenderer.send('windowsInfoRequest');
|
||||
ipcRenderer.send('vscode:windowsInfoRequest');
|
||||
ipcRenderer.send('vscode:listProcesses');
|
||||
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
|
||||
content="default-src 'none'; img-src 'self' https: data:; media-src 'none'; child-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https:;">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif;
|
||||
font-size: 10pt;
|
||||
background-color: #F3F3F3;
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -27,7 +27,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper
|
||||
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { ILogService, LogLevel, ILoggerService } from 'vs/platform/log/common/log';
|
||||
import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
|
||||
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
|
||||
@@ -35,26 +35,25 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
|
||||
import { combinedDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DownloadService } from 'vs/platform/download/common/downloadService';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { IChannel, IServerChannel, StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner';
|
||||
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
|
||||
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
|
||||
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
|
||||
import { DiagnosticsChannel } from 'vs/platform/diagnostics/node/diagnosticsIpc';
|
||||
import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient, UserDataSyncMachinesServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
@@ -66,10 +65,11 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
|
||||
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService';
|
||||
import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc';
|
||||
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/electron-browser/authenticationIpc';
|
||||
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService';
|
||||
import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -89,7 +89,11 @@ interface ISharedProcessInitData {
|
||||
const eventPrefix = 'adsworkbench'; // {{ SQL CARBON EDIT }}
|
||||
|
||||
class MainProcessService implements IMainProcessService {
|
||||
constructor(private server: Server, private mainRouter: StaticRouter) { }
|
||||
|
||||
constructor(
|
||||
private server: Server,
|
||||
private mainRouter: StaticRouter
|
||||
) { }
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -109,7 +113,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
|
||||
const onExit = () => disposables.dispose();
|
||||
process.once('exit', onExit);
|
||||
ipcRenderer.once('electron-main->shared-process: exit', onExit);
|
||||
ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit);
|
||||
|
||||
disposables.add(server);
|
||||
|
||||
@@ -200,6 +204,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
|
||||
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
|
||||
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
|
||||
services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService));
|
||||
services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService));
|
||||
services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService));
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
@@ -225,12 +230,16 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService);
|
||||
server.registerChannel('extensionTipsService', extensionTipsChannel);
|
||||
|
||||
const userDataSyncMachinesService = accessor.get(IUserDataSyncMachinesService);
|
||||
const userDataSyncMachineChannel = new UserDataSyncMachinesServiceChannel(userDataSyncMachinesService);
|
||||
server.registerChannel('userDataSyncMachines', userDataSyncMachineChannel);
|
||||
|
||||
const authTokenService = accessor.get(IAuthenticationTokenService);
|
||||
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
|
||||
server.registerChannel('authToken', authTokenChannel);
|
||||
|
||||
const userDataSyncService = accessor.get(IUserDataSyncService);
|
||||
const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService);
|
||||
const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService, logService);
|
||||
server.registerChannel('userDataSync', userDataSyncChannel);
|
||||
|
||||
const userDataAutoSync = instantiationService2.createInstance(UserDataAutoSyncService);
|
||||
@@ -292,17 +301,17 @@ async function handshake(configuration: ISharedProcessConfiguration): Promise<vo
|
||||
|
||||
// receive payload from electron-main to start things
|
||||
const data = await new Promise<ISharedProcessInitData>(c => {
|
||||
ipcRenderer.once('electron-main->shared-process: payload', (_: any, r: ISharedProcessInitData) => c(r));
|
||||
ipcRenderer.once('vscode:electron-main->shared-process=payload', (event: unknown, r: ISharedProcessInitData) => c(r));
|
||||
|
||||
// tell electron-main we are ready to receive payload
|
||||
ipcRenderer.send('shared-process->electron-main: ready-for-payload');
|
||||
ipcRenderer.send('vscode:shared-process->electron-main=ready-for-payload');
|
||||
});
|
||||
|
||||
// await IPC connection and signal this back to electron-main
|
||||
const server = await setupIPC(data.sharedIPCHandle);
|
||||
ipcRenderer.send('shared-process->electron-main: ipc-ready');
|
||||
ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready');
|
||||
|
||||
// await initialization and signal this back to electron-main
|
||||
await main(server, data, configuration);
|
||||
ipcRenderer.send('shared-process->electron-main: init-done');
|
||||
ipcRenderer.send('vscode:shared-process->electron-main=init-done');
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https:; font-src 'self' https: vscode-remote-resource:;">
|
||||
</head>
|
||||
<body class="vs-dark" aria-label="">
|
||||
<body aria-label="">
|
||||
</body>
|
||||
|
||||
<!-- Startup via workbench.js -->
|
||||
|
||||
@@ -50,6 +50,7 @@ bootstrapWindow.load([
|
||||
* @param {{
|
||||
* partsSplashPath?: string,
|
||||
* highContrast?: boolean,
|
||||
* defaultThemeType?: string,
|
||||
* extensionDevelopmentPath?: string[],
|
||||
* folderUri?: object,
|
||||
* workspace?: object
|
||||
@@ -78,13 +79,27 @@ function showPartsSplash(configuration) {
|
||||
}
|
||||
|
||||
// minimal color configuration (works with or without persisted data)
|
||||
const baseTheme = data ? data.baseTheme : configuration.highContrast ? 'hc-black' : 'vs-dark';
|
||||
const shellBackground = data ? data.colorInfo.editorBackground : configuration.highContrast ? '#000000' : '#1E1E1E';
|
||||
const shellForeground = data ? data.colorInfo.foreground : configuration.highContrast ? '#FFFFFF' : '#CCCCCC';
|
||||
let baseTheme, shellBackground, shellForeground;
|
||||
if (data) {
|
||||
baseTheme = data.baseTheme;
|
||||
shellBackground = data.colorInfo.editorBackground;
|
||||
shellForeground = data.colorInfo.foreground;
|
||||
} else if (configuration.highContrast || configuration.defaultThemeType === 'hc') {
|
||||
baseTheme = 'hc-black';
|
||||
shellBackground = '#000000';
|
||||
shellForeground = '#FFFFFF';
|
||||
} else if (configuration.defaultThemeType === 'vs') {
|
||||
baseTheme = 'vs';
|
||||
shellBackground = '#FFFFFF';
|
||||
shellForeground = '#000000';
|
||||
} else {
|
||||
baseTheme = 'vs-dark';
|
||||
shellBackground = '#1E1E1E';
|
||||
shellForeground = '#CCCCCC';
|
||||
}
|
||||
const style = document.createElement('style');
|
||||
style.className = 'initialShellColors';
|
||||
document.head.appendChild(style);
|
||||
document.body.className = baseTheme;
|
||||
style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`;
|
||||
|
||||
if (data && data.layoutInfo) {
|
||||
@@ -92,6 +107,7 @@ function showPartsSplash(configuration) {
|
||||
const { id, layoutInfo, colorInfo } = data;
|
||||
const splash = document.createElement('div');
|
||||
splash.id = id;
|
||||
splash.className = baseTheme;
|
||||
|
||||
if (layoutInfo.windowBorder) {
|
||||
splash.style.position = 'relative';
|
||||
|
||||
@@ -8,7 +8,6 @@ import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/plat
|
||||
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { OpenContext } from 'vs/platform/windows/node/window';
|
||||
import { ActiveWindowManager } from 'vs/code/node/activeWindowTracker';
|
||||
import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { getShellEnvironment } from 'vs/code/node/shellEnv';
|
||||
import { IUpdateService } from 'vs/platform/update/common/update';
|
||||
@@ -32,12 +31,12 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { createChannelReceiver } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { getDelayedChannel, StaticRouter, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
|
||||
import { ActiveWindowManager } from 'vs/platform/windows/electron-main/windowTracker';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { WorkspacesService } from 'vs/platform/workspaces/electron-main/workspacesService';
|
||||
@@ -45,14 +44,12 @@ import { getMachineId } from 'vs/base/node/id';
|
||||
import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32';
|
||||
import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux';
|
||||
import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin';
|
||||
import { IIssueService } from 'vs/platform/issue/node/issue';
|
||||
import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService';
|
||||
import { IssueMainService, IIssueMainService } from 'vs/platform/issue/electron-main/issueMainService';
|
||||
import { LoggerChannel } from 'vs/platform/log/common/logIpc';
|
||||
import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
|
||||
import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver';
|
||||
import { IMenubarService } from 'vs/platform/menubar/node/menubar';
|
||||
import { MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService';
|
||||
import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
|
||||
import { homedir } from 'os';
|
||||
@@ -65,7 +62,7 @@ import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIp
|
||||
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
|
||||
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
|
||||
import { WorkspacesHistoryMainService, IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
|
||||
import { URLService } from 'vs/platform/url/node/urlService';
|
||||
import { NativeURLService } from 'vs/platform/url/common/urlService';
|
||||
import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { statSync } from 'fs';
|
||||
import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc';
|
||||
@@ -81,6 +78,8 @@ import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common
|
||||
import { StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels';
|
||||
import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService';
|
||||
import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
@@ -125,7 +124,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Mac only event: open new window when we get activated
|
||||
if (!hasVisibleWindows && this.windowsMainService) {
|
||||
this.windowsMainService.openEmptyWindow(OpenContext.DOCK);
|
||||
this.windowsMainService.openEmptyWindow({ context: OpenContext.DOCK });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -258,7 +257,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
app.on('new-window-for-tab', () => {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.openEmptyWindow(OpenContext.DESKTOP); //macOS native tab "+" button
|
||||
this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
|
||||
}
|
||||
});
|
||||
|
||||
@@ -467,10 +466,11 @@ export class CodeApplication extends Disposable {
|
||||
const diagnosticsChannel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics')));
|
||||
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel]));
|
||||
|
||||
services.set(IIssueService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
|
||||
services.set(IIssueMainService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv]));
|
||||
services.set(IElectronMainService, new SyncDescriptor(ElectronMainService));
|
||||
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
|
||||
services.set(IWorkspacesService, new SyncDescriptor(WorkspacesService));
|
||||
services.set(IMenubarService, new SyncDescriptor(MenubarMainService));
|
||||
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
|
||||
|
||||
const storageMainService = new StorageMainService(this.logService, this.environmentService);
|
||||
services.set(IStorageMainService, storageMainService);
|
||||
@@ -480,7 +480,7 @@ export class CodeApplication extends Disposable {
|
||||
services.set(IBackupMainService, backupMainService);
|
||||
|
||||
services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService));
|
||||
services.set(IURLService, new SyncDescriptor(URLService));
|
||||
services.set(IURLService, new SyncDescriptor(NativeURLService));
|
||||
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
|
||||
|
||||
// Telemetry
|
||||
@@ -551,8 +551,8 @@ export class CodeApplication extends Disposable {
|
||||
const updateChannel = new UpdateChannel(updateService);
|
||||
electronIpcServer.registerChannel('update', updateChannel);
|
||||
|
||||
const issueService = accessor.get(IIssueService);
|
||||
const issueChannel = createChannelReceiver(issueService);
|
||||
const issueMainService = accessor.get(IIssueMainService);
|
||||
const issueChannel = createChannelReceiver(issueMainService);
|
||||
electronIpcServer.registerChannel('issue', issueChannel);
|
||||
|
||||
const electronMainService = accessor.get(IElectronMainService);
|
||||
@@ -568,14 +568,18 @@ export class CodeApplication extends Disposable {
|
||||
const workspacesChannel = createChannelReceiver(workspacesService);
|
||||
electronIpcServer.registerChannel('workspaces', workspacesChannel);
|
||||
|
||||
const menubarService = accessor.get(IMenubarService);
|
||||
const menubarChannel = createChannelReceiver(menubarService);
|
||||
const menubarMainService = accessor.get(IMenubarMainService);
|
||||
const menubarChannel = createChannelReceiver(menubarMainService);
|
||||
electronIpcServer.registerChannel('menubar', menubarChannel);
|
||||
|
||||
const urlService = accessor.get(IURLService);
|
||||
const urlChannel = createChannelReceiver(urlService);
|
||||
electronIpcServer.registerChannel('url', urlChannel);
|
||||
|
||||
const webviewManagerService = accessor.get(IWebviewManagerService);
|
||||
const webviewChannel = createChannelReceiver(webviewManagerService);
|
||||
electronIpcServer.registerChannel('webview', webviewChannel);
|
||||
|
||||
const storageMainService = accessor.get(IStorageMainService);
|
||||
const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService));
|
||||
electronIpcServer.registerChannel('storage', storageChannel);
|
||||
|
||||
@@ -59,7 +59,8 @@ export class ProxyAuthHandler extends Disposable {
|
||||
title: 'VS Code',
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
webviewTag: true
|
||||
webviewTag: true,
|
||||
enableWebSQL: false
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import 'vs/platform/update/common/update.config.contribution';
|
||||
import { app, dialog } from 'electron';
|
||||
import { isWindows, IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
|
||||
import { isWindows, IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { parseMainProcessArgv, addArg } from 'vs/platform/environment/node/argvHelper';
|
||||
import { createWaitMarkerFile } from 'vs/platform/environment/node/waitMarkerFile';
|
||||
@@ -13,7 +13,7 @@ import { mkdirp } from 'vs/base/node/pfs';
|
||||
import { validatePaths } from 'vs/code/node/paths';
|
||||
import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
@@ -226,11 +226,6 @@ class CodeMain {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Since we are the second instance, we do not want to show the dock
|
||||
if (isMacintosh) {
|
||||
app.dock.hide();
|
||||
}
|
||||
|
||||
// there's a running instance, let's connect to it
|
||||
let client: Client<string>;
|
||||
try {
|
||||
@@ -330,11 +325,6 @@ class CodeMain {
|
||||
throw new ExpectedError('Terminating...');
|
||||
}
|
||||
|
||||
// dock might be hidden at this case due to a retry
|
||||
if (isMacintosh) {
|
||||
app.dock.show();
|
||||
}
|
||||
|
||||
// Set the VSCODE_PID variable here when we are sure we are the first
|
||||
// instance to startup. Otherwise we would wrongly overwrite the PID
|
||||
process.env['VSCODE_PID'] = String(process.pid);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { BrowserWindow, ipcMain, WebContents, Event as ElectronEvent } from 'electron';
|
||||
@@ -32,7 +33,7 @@ export class SharedProcess implements ISharedProcess {
|
||||
@IThemeMainService private readonly themeMainService: IThemeMainService
|
||||
) {
|
||||
// overall ready promise when shared process signals initialization is done
|
||||
this._whenReady = new Promise<void>(c => ipcMain.once('shared-process->electron-main: init-done', () => c(undefined)));
|
||||
this._whenReady = new Promise<void>(c => ipcMain.once('vscode:shared-process->electron-main=init-done', () => c(undefined)));
|
||||
}
|
||||
|
||||
@memoize
|
||||
@@ -41,9 +42,11 @@ export class SharedProcess implements ISharedProcess {
|
||||
show: false,
|
||||
backgroundColor: this.themeMainService.getBackgroundColor(),
|
||||
webPreferences: {
|
||||
preload: URI.parse(require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js')).fsPath,
|
||||
images: false,
|
||||
nodeIntegration: true,
|
||||
webgl: false,
|
||||
enableWebSQL: false,
|
||||
disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer
|
||||
}
|
||||
});
|
||||
@@ -104,18 +107,18 @@ export class SharedProcess implements ISharedProcess {
|
||||
|
||||
return new Promise<void>(c => {
|
||||
// send payload once shared process is ready to receive it
|
||||
disposables.add(Event.once(Event.fromNodeEventEmitter(ipcMain, 'shared-process->electron-main: ready-for-payload', ({ sender }: { sender: WebContents }) => sender))(sender => {
|
||||
sender.send('electron-main->shared-process: payload', {
|
||||
disposables.add(Event.once(Event.fromNodeEventEmitter(ipcMain, 'vscode:shared-process->electron-main=ready-for-payload', ({ sender }: { sender: WebContents }) => sender))(sender => {
|
||||
sender.send('vscode:electron-main->shared-process=payload', {
|
||||
sharedIPCHandle: this.environmentService.sharedIPCHandle,
|
||||
args: this.environmentService.args,
|
||||
logLevel: this.logService.getLevel()
|
||||
});
|
||||
|
||||
// signal exit to shared process when we get disposed
|
||||
disposables.add(toDisposable(() => sender.send('electron-main->shared-process: exit')));
|
||||
disposables.add(toDisposable(() => sender.send('vscode:electron-main->shared-process=exit')));
|
||||
|
||||
// complete IPC-ready promise when shared process signals this to us
|
||||
ipcMain.once('shared-process->electron-main: ipc-ready', () => c(undefined));
|
||||
ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => c(undefined));
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -163,9 +163,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
show: !isFullscreenOrMaximized,
|
||||
title: product.nameLong,
|
||||
webPreferences: {
|
||||
preload: URI.parse(this.doGetPreloadUrl()).fsPath,
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: RUN_TEXTMATE_IN_WORKER,
|
||||
webviewTag: true
|
||||
webviewTag: true,
|
||||
enableWebSQL: false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -599,9 +601,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
// Do not set to empty configuration at startup if setting is empty to not override configuration through CLI options:
|
||||
const env = process.env;
|
||||
const newHttpProxy = (this.configurationService.getValue<string>('http.proxy') || '').trim()
|
||||
let newHttpProxy = (this.configurationService.getValue<string>('http.proxy') || '').trim()
|
||||
|| (env.https_proxy || process.env.HTTPS_PROXY || process.env.http_proxy || process.env.HTTP_PROXY || '').trim() // Not standardized.
|
||||
|| undefined;
|
||||
if (newHttpProxy?.endsWith('/')) {
|
||||
newHttpProxy = newHttpProxy.substr(0, newHttpProxy.length - 1);
|
||||
}
|
||||
const newNoProxy = (env.no_proxy || env.NO_PROXY || '').trim() || undefined; // Not standardized.
|
||||
if ((newHttpProxy || '').indexOf('@') === -1 && (newHttpProxy !== this.currentHttpProxy || newNoProxy !== this.currentNoProxy)) {
|
||||
this.currentHttpProxy = newHttpProxy;
|
||||
@@ -776,6 +781,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`;
|
||||
}
|
||||
|
||||
private doGetPreloadUrl(): string {
|
||||
return require.toUrl('vs/base/parts/sandbox/electron-browser/preload.js');
|
||||
}
|
||||
|
||||
serializeWindowState(): IWindowState {
|
||||
if (!this._win) {
|
||||
return defaultWindowState();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { raceTimeout } from 'vs/base/common/async';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as semver from 'semver-umd';
|
||||
@@ -85,7 +86,7 @@ export class Main {
|
||||
} else if (argv['list-extensions']) {
|
||||
await this.listExtensions(!!argv['show-versions'], argv['category']);
|
||||
} else if (argv['install-extension']) {
|
||||
await this.installExtensions(argv['install-extension'], !!argv['force']);
|
||||
await this.installExtensions(argv['install-extension'], !!argv['force'], !!argv['donot-sync']);
|
||||
} else if (argv['uninstall-extension']) {
|
||||
await this.uninstallExtension(argv['uninstall-extension']);
|
||||
} else if (argv['locate-extension']) {
|
||||
@@ -125,7 +126,7 @@ export class Main {
|
||||
extensions.forEach(e => console.log(getId(e.manifest, showVersions)));
|
||||
}
|
||||
|
||||
private async installExtensions(extensions: string[], force: boolean): Promise<void> {
|
||||
private async installExtensions(extensions: string[], force: boolean, donotSync: boolean): Promise<void> {
|
||||
const failed: string[] = [];
|
||||
const installedExtensionsManifests: IExtensionManifest[] = [];
|
||||
if (extensions.length) {
|
||||
@@ -134,7 +135,7 @@ export class Main {
|
||||
|
||||
for (const extension of extensions) {
|
||||
try {
|
||||
const manifest = await this.installExtension(extension, force);
|
||||
const manifest = await this.installExtension(extension, force, donotSync);
|
||||
if (manifest) {
|
||||
installedExtensionsManifests.push(manifest);
|
||||
}
|
||||
@@ -149,7 +150,7 @@ export class Main {
|
||||
return failed.length ? Promise.reject(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))) : Promise.resolve();
|
||||
}
|
||||
|
||||
private async installExtension(extension: string, force: boolean): Promise<IExtensionManifest | null> {
|
||||
private async installExtension(extension: string, force: boolean, donotSync: boolean): Promise<IExtensionManifest | null> {
|
||||
if (/\.vsix$/i.test(extension)) {
|
||||
extension = path.isAbsolute(extension) ? extension : path.join(process.cwd(), extension);
|
||||
|
||||
@@ -157,7 +158,7 @@ export class Main {
|
||||
const valid = await this.validate(manifest, force);
|
||||
|
||||
if (valid) {
|
||||
return this.extensionManagementService.install(URI.file(extension)).then(id => {
|
||||
return this.extensionManagementService.install(URI.file(extension), donotSync).then(id => {
|
||||
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(extension)));
|
||||
return manifest;
|
||||
}, error => {
|
||||
@@ -204,7 +205,7 @@ export class Main {
|
||||
}
|
||||
console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, extension.version));
|
||||
}
|
||||
await this.installFromGallery(id, extension);
|
||||
await this.installFromGallery(id, extension, donotSync);
|
||||
return manifest;
|
||||
}));
|
||||
}
|
||||
@@ -226,11 +227,11 @@ export class Main {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async installFromGallery(id: string, extension: IGalleryExtension): Promise<void> {
|
||||
private async installFromGallery(id: string, extension: IGalleryExtension, donotSync: boolean): Promise<void> {
|
||||
console.log(localize('installing', "Installing extension '{0}' v{1}...", id, extension.version));
|
||||
|
||||
try {
|
||||
await this.extensionManagementService.installFromGallery(extension);
|
||||
await this.extensionManagementService.installFromGallery(extension, donotSync);
|
||||
console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, extension.version));
|
||||
} catch (error) {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
@@ -361,8 +362,10 @@ export async function main(argv: ParsedArgs): Promise<void> {
|
||||
|
||||
try {
|
||||
await main.run(argv);
|
||||
|
||||
// Flush the remaining data in AI adapter.
|
||||
await combinedAppender(...appenders).flush();
|
||||
// If it does not complete in 1 second, exit the process.
|
||||
await raceTimeout(combinedAppender(...appenders).flush(), 1000);
|
||||
} finally {
|
||||
disposables.dispose();
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] {
|
||||
const result = args.map(arg => {
|
||||
let pathCandidate = String(arg);
|
||||
|
||||
let parsedPath: IPathWithLineAndColumn | undefined = undefined;
|
||||
let parsedPath: extpath.IPathWithLineAndColumn | undefined = undefined;
|
||||
if (gotoLineMode) {
|
||||
parsedPath = parseLineAndColumnAware(pathCandidate);
|
||||
parsedPath = extpath.parseLineAndColumnAware(pathCandidate);
|
||||
pathCandidate = parsedPath.path;
|
||||
}
|
||||
|
||||
@@ -87,42 +87,7 @@ function preparePath(cwd: string, p: string): string {
|
||||
return p;
|
||||
}
|
||||
|
||||
export interface IPathWithLineAndColumn {
|
||||
path: string;
|
||||
line?: number;
|
||||
column?: number;
|
||||
}
|
||||
|
||||
export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn {
|
||||
const segments = rawPath.split(':'); // C:\file.txt:<line>:<column>
|
||||
|
||||
let path: string | null = null;
|
||||
let line: number | null = null;
|
||||
let column: number | null = null;
|
||||
|
||||
segments.forEach(segment => {
|
||||
const segmentAsNumber = Number(segment);
|
||||
if (!types.isNumber(segmentAsNumber)) {
|
||||
path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...)
|
||||
} else if (line === null) {
|
||||
line = segmentAsNumber;
|
||||
} else if (column === null) {
|
||||
column = segmentAsNumber;
|
||||
}
|
||||
});
|
||||
|
||||
if (!path) {
|
||||
throw new Error('Format for `--goto` should be: `FILE:LINE(:COLUMN)`');
|
||||
}
|
||||
|
||||
return {
|
||||
path: path,
|
||||
line: line !== null ? line : undefined,
|
||||
column: column !== null ? column : line !== null ? 1 : undefined // if we have a line, make sure column is also set
|
||||
};
|
||||
}
|
||||
|
||||
function toPath(p: IPathWithLineAndColumn): string {
|
||||
function toPath(p: extpath.IPathWithLineAndColumn): string {
|
||||
const segments = [p.path];
|
||||
|
||||
if (types.isNumber(p.line)) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Command, EditorCommand, ICommandOptions, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ColumnSelection, IColumnSelectResult } from 'vs/editor/common/controller/cursorColumnSelection';
|
||||
import { CursorContext, CursorState, EditOperationType, IColumnSelectData, ICursors, PartialCursorState, RevealTarget } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { CursorState, EditOperationType, IColumnSelectData, PartialCursorState } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
|
||||
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { CursorMove as CursorMove_, CursorMoveCommands } from 'vs/editor/common/controller/cursorMoveCommands';
|
||||
@@ -26,20 +26,21 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
|
||||
const CORE_WEIGHT = KeybindingWeight.EditorCore;
|
||||
|
||||
export abstract class CoreEditorCommand extends EditorCommand {
|
||||
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
|
||||
const cursors = editor._getCursors();
|
||||
if (!cursors) {
|
||||
const viewModel = editor._getViewModel();
|
||||
if (!viewModel) {
|
||||
// the editor has no view => has no cursors
|
||||
return;
|
||||
}
|
||||
this.runCoreEditorCommand(cursors, args || {});
|
||||
this.runCoreEditorCommand(viewModel, args || {});
|
||||
}
|
||||
|
||||
public abstract runCoreEditorCommand(cursors: ICursors, args: any): void;
|
||||
public abstract runCoreEditorCommand(viewModel: IViewModel, args: any): void;
|
||||
}
|
||||
|
||||
export namespace EditorScroll_ {
|
||||
@@ -286,16 +287,16 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
CursorMoveCommands.moveTo(cursors.context, cursors.getPrimaryCursor(), this._inSelectionMode, args.position, args.viewPosition)
|
||||
CursorMoveCommands.moveTo(viewModel, viewModel.getPrimaryCursorState(), this._inSelectionMode, args.position, args.viewPosition)
|
||||
]
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,21 +313,25 @@ export namespace CoreNavigationCommands {
|
||||
}));
|
||||
|
||||
abstract class ColumnSelectCommand extends CoreEditorCommand {
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
const result = this._getColumnSelectResult(cursors.context, cursors.getPrimaryCursor(), cursors.getColumnSelectData(), args);
|
||||
cursors.setStates(args.source, CursorChangeReason.Explicit, result.viewStates.map((viewState) => CursorState.fromViewState(viewState)));
|
||||
cursors.setColumnSelectData({
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
const result = this._getColumnSelectResult(viewModel, viewModel.getPrimaryCursorState(), viewModel.getCursorColumnSelectData(), args);
|
||||
viewModel.setCursorStates(args.source, CursorChangeReason.Explicit, result.viewStates.map((viewState) => CursorState.fromViewState(viewState)));
|
||||
viewModel.setCursorColumnSelectData({
|
||||
isReal: true,
|
||||
fromViewLineNumber: result.fromLineNumber,
|
||||
fromViewVisualColumn: result.fromVisualColumn,
|
||||
toViewLineNumber: result.toLineNumber,
|
||||
toViewVisualColumn: result.toVisualColumn
|
||||
});
|
||||
cursors.reveal(args.source, true, (result.reversed ? RevealTarget.TopMost : RevealTarget.BottomMost), ScrollType.Smooth);
|
||||
if (result.reversed) {
|
||||
viewModel.revealTopMostCursor(args.source);
|
||||
} else {
|
||||
viewModel.revealBottomMostCursor(args.source);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult;
|
||||
protected abstract _getColumnSelectResult(viewModel: IViewModel, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult;
|
||||
|
||||
}
|
||||
|
||||
@@ -338,15 +343,15 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
protected _getColumnSelectResult(viewModel: IViewModel, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
|
||||
// validate `args`
|
||||
const validatedPosition = context.model.validatePosition(args.position);
|
||||
const validatedViewPosition = context.validateViewPosition(new Position(args.viewPosition.lineNumber, args.viewPosition.column), validatedPosition);
|
||||
const validatedPosition = viewModel.model.validatePosition(args.position);
|
||||
const validatedViewPosition = viewModel.coordinatesConverter.validateViewPosition(new Position(args.viewPosition.lineNumber, args.viewPosition.column), validatedPosition);
|
||||
|
||||
let fromViewLineNumber = args.doColumnSelect ? prevColumnSelectData.fromViewLineNumber : validatedViewPosition.lineNumber;
|
||||
let fromViewVisualColumn = args.doColumnSelect ? prevColumnSelectData.fromViewVisualColumn : args.mouseColumn - 1;
|
||||
return ColumnSelection.columnSelect(context.config, context.viewModel, fromViewLineNumber, fromViewVisualColumn, validatedViewPosition.lineNumber, args.mouseColumn - 1);
|
||||
return ColumnSelection.columnSelect(viewModel.cursorConfig, viewModel, fromViewLineNumber, fromViewVisualColumn, validatedViewPosition.lineNumber, args.mouseColumn - 1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -364,8 +369,8 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectLeft(context.config, context.viewModel, prevColumnSelectData);
|
||||
protected _getColumnSelectResult(viewModel: IViewModel, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectLeft(viewModel.cursorConfig, viewModel, prevColumnSelectData);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -383,8 +388,8 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectRight(context.config, context.viewModel, prevColumnSelectData);
|
||||
protected _getColumnSelectResult(viewModel: IViewModel, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectRight(viewModel.cursorConfig, viewModel, prevColumnSelectData);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -397,8 +402,8 @@ export namespace CoreNavigationCommands {
|
||||
this._isPaged = opts.isPaged;
|
||||
}
|
||||
|
||||
protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectUp(context.config, context.viewModel, prevColumnSelectData, this._isPaged);
|
||||
protected _getColumnSelectResult(viewModel: IViewModel, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectUp(viewModel.cursorConfig, viewModel, prevColumnSelectData, this._isPaged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,8 +440,8 @@ export namespace CoreNavigationCommands {
|
||||
this._isPaged = opts.isPaged;
|
||||
}
|
||||
|
||||
protected _getColumnSelectResult(context: CursorContext, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectDown(context.config, context.viewModel, prevColumnSelectData, this._isPaged);
|
||||
protected _getColumnSelectResult(viewModel: IViewModel, primary: CursorState, prevColumnSelectData: IColumnSelectData, args: any): IColumnSelectResult {
|
||||
return ColumnSelection.columnSelectDown(viewModel.cursorConfig, viewModel, prevColumnSelectData, this._isPaged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,23 +478,49 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
const parsed = CursorMove_.parse(args);
|
||||
if (!parsed) {
|
||||
// illegal arguments
|
||||
return;
|
||||
}
|
||||
this._runCursorMove(cursors, args.source, parsed);
|
||||
this._runCursorMove(viewModel, args.source, parsed);
|
||||
}
|
||||
|
||||
_runCursorMove(cursors: ICursors, source: string, args: CursorMove_.ParsedArguments): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
private _runCursorMove(viewModel: IViewModel, source: string | null | undefined, args: CursorMove_.ParsedArguments): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.move(cursors.context, cursors.getAll(), args)
|
||||
CursorMoveImpl._move(viewModel, viewModel.getCursorStates(), args)
|
||||
);
|
||||
cursors.reveal(source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(source, true);
|
||||
}
|
||||
|
||||
private static _move(viewModel: IViewModel, cursors: CursorState[], args: CursorMove_.ParsedArguments): PartialCursorState[] | null {
|
||||
const inSelectionMode = args.select;
|
||||
const value = args.value;
|
||||
|
||||
switch (args.direction) {
|
||||
case CursorMove_.Direction.Left:
|
||||
case CursorMove_.Direction.Right:
|
||||
case CursorMove_.Direction.Up:
|
||||
case CursorMove_.Direction.Down:
|
||||
case CursorMove_.Direction.WrappedLineStart:
|
||||
case CursorMove_.Direction.WrappedLineFirstNonWhitespaceCharacter:
|
||||
case CursorMove_.Direction.WrappedLineColumnCenter:
|
||||
case CursorMove_.Direction.WrappedLineEnd:
|
||||
case CursorMove_.Direction.WrappedLineLastNonWhitespaceCharacter:
|
||||
return CursorMoveCommands.simpleMove(viewModel, cursors, args.direction, inSelectionMode, value, args.unit);
|
||||
|
||||
case CursorMove_.Direction.ViewPortTop:
|
||||
case CursorMove_.Direction.ViewPortBottom:
|
||||
case CursorMove_.Direction.ViewPortCenter:
|
||||
case CursorMove_.Direction.ViewPortIfOutside:
|
||||
return CursorMoveCommands.viewportMove(viewModel, cursors, args.direction, inSelectionMode, value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,14 +532,14 @@ export namespace CoreNavigationCommands {
|
||||
|
||||
class CursorMoveBasedCommand extends CoreEditorCommand {
|
||||
|
||||
private readonly _staticArgs: CursorMove_.ParsedArguments;
|
||||
private readonly _staticArgs: CursorMove_.SimpleMoveArguments;
|
||||
|
||||
constructor(opts: ICommandOptions & { args: CursorMove_.ParsedArguments }) {
|
||||
constructor(opts: ICommandOptions & { args: CursorMove_.SimpleMoveArguments }) {
|
||||
super(opts);
|
||||
this._staticArgs = opts.args;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, dynamicArgs: any): void {
|
||||
public runCoreEditorCommand(viewModel: IViewModel, dynamicArgs: any): void {
|
||||
let args = this._staticArgs;
|
||||
if (this._staticArgs.value === Constants.PAGE_SIZE_MARKER) {
|
||||
// -1 is a marker for page size
|
||||
@@ -516,10 +547,17 @@ export namespace CoreNavigationCommands {
|
||||
direction: this._staticArgs.direction,
|
||||
unit: this._staticArgs.unit,
|
||||
select: this._staticArgs.select,
|
||||
value: cursors.context.config.pageSize
|
||||
value: viewModel.cursorConfig.pageSize
|
||||
};
|
||||
}
|
||||
CursorMove._runCursorMove(cursors, dynamicArgs.source, args);
|
||||
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
dynamicArgs.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.simpleMove(viewModel, viewModel.getCursorStates(), args.direction, args.select, args.value, args.unit)
|
||||
);
|
||||
viewModel.revealPrimaryCursor(dynamicArgs.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,17 +771,15 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
const context = cursors.context;
|
||||
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
let newState: PartialCursorState;
|
||||
if (args.wholeLine) {
|
||||
newState = CursorMoveCommands.line(context, cursors.getPrimaryCursor(), false, args.position, args.viewPosition);
|
||||
newState = CursorMoveCommands.line(viewModel, viewModel.getPrimaryCursorState(), false, args.position, args.viewPosition);
|
||||
} else {
|
||||
newState = CursorMoveCommands.moveTo(context, cursors.getPrimaryCursor(), false, args.position, args.viewPosition);
|
||||
newState = CursorMoveCommands.moveTo(viewModel, viewModel.getPrimaryCursorState(), false, args.position, args.viewPosition);
|
||||
}
|
||||
|
||||
const states: PartialCursorState[] = cursors.getAll();
|
||||
const states: PartialCursorState[] = viewModel.getCursorStates();
|
||||
|
||||
// Check if we should remove a cursor (sort of like a toggle)
|
||||
if (states.length > 1) {
|
||||
@@ -764,8 +800,8 @@ export namespace CoreNavigationCommands {
|
||||
// => Remove the cursor
|
||||
states.splice(i, 1);
|
||||
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
states
|
||||
@@ -777,8 +813,8 @@ export namespace CoreNavigationCommands {
|
||||
// => Add the new cursor
|
||||
states.push(newState);
|
||||
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
states
|
||||
@@ -794,17 +830,15 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
const context = cursors.context;
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
const lastAddedCursorIndex = viewModel.getLastAddedCursorIndex();
|
||||
|
||||
const lastAddedCursorIndex = cursors.getLastAddedCursorIndex();
|
||||
|
||||
const states = cursors.getAll();
|
||||
const states = viewModel.getCursorStates();
|
||||
const newStates: PartialCursorState[] = states.slice(0);
|
||||
newStates[lastAddedCursorIndex] = CursorMoveCommands.moveTo(context, states[lastAddedCursorIndex], true, args.position, args.viewPosition);
|
||||
newStates[lastAddedCursorIndex] = CursorMoveCommands.moveTo(viewModel, states[lastAddedCursorIndex], true, args.position, args.viewPosition);
|
||||
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
newStates
|
||||
@@ -821,14 +855,14 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.moveToBeginningOfLine(cursors.context, cursors.getAll(), this._inSelectionMode)
|
||||
CursorMoveCommands.moveToBeginningOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -865,17 +899,17 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
this._exec(cursors.context, cursors.getAll())
|
||||
this._exec(viewModel.getCursorStates())
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
|
||||
private _exec(context: CursorContext, cursors: CursorState[]): PartialCursorState[] {
|
||||
private _exec(cursors: CursorState[]): PartialCursorState[] {
|
||||
const result: PartialCursorState[] = [];
|
||||
for (let i = 0, len = cursors.length; i < len; i++) {
|
||||
const cursor = cursors[i];
|
||||
@@ -919,14 +953,14 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.moveToEndOfLine(cursors.context, cursors.getAll(), this._inSelectionMode)
|
||||
CursorMoveCommands.moveToEndOfLine(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -963,22 +997,22 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
this._exec(cursors.context, cursors.getAll())
|
||||
this._exec(viewModel, viewModel.getCursorStates())
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
|
||||
private _exec(context: CursorContext, cursors: CursorState[]): PartialCursorState[] {
|
||||
private _exec(viewModel: IViewModel, cursors: CursorState[]): PartialCursorState[] {
|
||||
const result: PartialCursorState[] = [];
|
||||
for (let i = 0, len = cursors.length; i < len; i++) {
|
||||
const cursor = cursors[i];
|
||||
const lineNumber = cursor.modelState.position.lineNumber;
|
||||
const maxColumn = context.model.getLineMaxColumn(lineNumber);
|
||||
const maxColumn = viewModel.model.getLineMaxColumn(lineNumber);
|
||||
result[i] = CursorState.fromModelState(cursor.modelState.move(this._inSelectionMode, lineNumber, maxColumn, 0));
|
||||
}
|
||||
return result;
|
||||
@@ -1018,14 +1052,14 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.moveToBeginningOfBuffer(cursors.context, cursors.getAll(), this._inSelectionMode)
|
||||
CursorMoveCommands.moveToBeginningOfBuffer(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1062,14 +1096,14 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.moveToEndOfBuffer(cursors.context, cursors.getAll(), this._inSelectionMode)
|
||||
CursorMoveCommands.moveToEndOfBuffer(viewModel, viewModel.getCursorStates(), this._inSelectionMode)
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1106,39 +1140,40 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
const parsed = EditorScroll_.parse(args);
|
||||
if (!parsed) {
|
||||
// illegal arguments
|
||||
return;
|
||||
}
|
||||
this._runEditorScroll(cursors, args.source, parsed);
|
||||
this._runEditorScroll(viewModel, args.source, parsed);
|
||||
}
|
||||
|
||||
_runEditorScroll(cursors: ICursors, source: string, args: EditorScroll_.ParsedArguments): void {
|
||||
_runEditorScroll(viewModel: IViewModel, source: string | null | undefined, args: EditorScroll_.ParsedArguments): void {
|
||||
|
||||
const desiredScrollTop = this._computeDesiredScrollTop(cursors.context, args);
|
||||
const desiredScrollTop = this._computeDesiredScrollTop(viewModel, args);
|
||||
|
||||
if (args.revealCursor) {
|
||||
// must ensure cursor is in new visible range
|
||||
const desiredVisibleViewRange = cursors.context.getCompletelyVisibleViewRangeAtScrollTop(desiredScrollTop);
|
||||
cursors.setStates(
|
||||
const desiredVisibleViewRange = viewModel.getCompletelyVisibleViewRangeAtScrollTop(desiredScrollTop);
|
||||
viewModel.setCursorStates(
|
||||
source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
CursorMoveCommands.findPositionInViewportIfOutside(cursors.context, cursors.getPrimaryCursor(), desiredVisibleViewRange, args.select)
|
||||
CursorMoveCommands.findPositionInViewportIfOutside(viewModel, viewModel.getPrimaryCursorState(), desiredVisibleViewRange, args.select)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
cursors.scrollTo(desiredScrollTop);
|
||||
viewModel.setScrollTop(desiredScrollTop, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
private _computeDesiredScrollTop(context: CursorContext, args: EditorScroll_.ParsedArguments): number {
|
||||
private _computeDesiredScrollTop(viewModel: IViewModel, args: EditorScroll_.ParsedArguments): number {
|
||||
|
||||
if (args.unit === EditorScroll_.Unit.Line) {
|
||||
// scrolling by model lines
|
||||
const visibleModelRange = context.getCompletelyVisibleModelRange();
|
||||
const visibleViewRange = viewModel.getCompletelyVisibleViewRange();
|
||||
const visibleModelRange = viewModel.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange);
|
||||
|
||||
let desiredTopModelLineNumber: number;
|
||||
if (args.direction === EditorScroll_.Direction.Up) {
|
||||
@@ -1146,23 +1181,23 @@ export namespace CoreNavigationCommands {
|
||||
desiredTopModelLineNumber = Math.max(1, visibleModelRange.startLineNumber - args.value);
|
||||
} else {
|
||||
// must go x model lines down
|
||||
desiredTopModelLineNumber = Math.min(context.model.getLineCount(), visibleModelRange.startLineNumber + args.value);
|
||||
desiredTopModelLineNumber = Math.min(viewModel.model.getLineCount(), visibleModelRange.startLineNumber + args.value);
|
||||
}
|
||||
|
||||
const desiredTopViewPosition = context.convertModelPositionToViewPosition(new Position(desiredTopModelLineNumber, 1));
|
||||
return context.getVerticalOffsetForViewLine(desiredTopViewPosition.lineNumber);
|
||||
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(new Position(desiredTopModelLineNumber, 1));
|
||||
return viewModel.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
|
||||
}
|
||||
|
||||
let noOfLines: number;
|
||||
if (args.unit === EditorScroll_.Unit.Page) {
|
||||
noOfLines = context.config.pageSize * args.value;
|
||||
noOfLines = viewModel.cursorConfig.pageSize * args.value;
|
||||
} else if (args.unit === EditorScroll_.Unit.HalfPage) {
|
||||
noOfLines = Math.round(context.config.pageSize / 2) * args.value;
|
||||
noOfLines = Math.round(viewModel.cursorConfig.pageSize / 2) * args.value;
|
||||
} else {
|
||||
noOfLines = args.value;
|
||||
}
|
||||
const deltaLines = (args.direction === EditorScroll_.Direction.Up ? -1 : 1) * noOfLines;
|
||||
return context.getCurrentScrollTop() + deltaLines * context.config.lineHeight;
|
||||
return viewModel.getScrollTop() + deltaLines * viewModel.cursorConfig.lineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1182,8 +1217,8 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
EditorScroll._runEditorScroll(cursors, args.source, {
|
||||
runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
EditorScroll._runEditorScroll(viewModel, args.source, {
|
||||
direction: EditorScroll_.Direction.Up,
|
||||
unit: EditorScroll_.Unit.WrappedLine,
|
||||
value: 1,
|
||||
@@ -1208,8 +1243,8 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
EditorScroll._runEditorScroll(cursors, args.source, {
|
||||
runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
EditorScroll._runEditorScroll(viewModel, args.source, {
|
||||
direction: EditorScroll_.Direction.Up,
|
||||
unit: EditorScroll_.Unit.Page,
|
||||
value: 1,
|
||||
@@ -1233,8 +1268,8 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
EditorScroll._runEditorScroll(cursors, args.source, {
|
||||
runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
EditorScroll._runEditorScroll(viewModel, args.source, {
|
||||
direction: EditorScroll_.Direction.Down,
|
||||
unit: EditorScroll_.Unit.WrappedLine,
|
||||
value: 1,
|
||||
@@ -1259,8 +1294,8 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
EditorScroll._runEditorScroll(cursors, args.source, {
|
||||
runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
EditorScroll._runEditorScroll(viewModel, args.source, {
|
||||
direction: EditorScroll_.Direction.Down,
|
||||
unit: EditorScroll_.Unit.Page,
|
||||
value: 1,
|
||||
@@ -1279,16 +1314,16 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
CursorMoveCommands.word(cursors.context, cursors.getPrimaryCursor(), this._inSelectionMode, args.position)
|
||||
CursorMoveCommands.word(viewModel, viewModel.getPrimaryCursorState(), this._inSelectionMode, args.position)
|
||||
]
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1312,18 +1347,16 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
const context = cursors.context;
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
const lastAddedCursorIndex = viewModel.getLastAddedCursorIndex();
|
||||
|
||||
const lastAddedCursorIndex = cursors.getLastAddedCursorIndex();
|
||||
|
||||
const states = cursors.getAll();
|
||||
const states = viewModel.getCursorStates();
|
||||
const newStates: PartialCursorState[] = states.slice(0);
|
||||
const lastAddedState = states[lastAddedCursorIndex];
|
||||
newStates[lastAddedCursorIndex] = CursorMoveCommands.word(context, lastAddedState, lastAddedState.modelState.hasSelection(), args.position);
|
||||
newStates[lastAddedCursorIndex] = CursorMoveCommands.word(viewModel, lastAddedState, lastAddedState.modelState.hasSelection(), args.position);
|
||||
|
||||
context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
newStates
|
||||
@@ -1339,16 +1372,16 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
CursorMoveCommands.line(cursors.context, cursors.getPrimaryCursor(), this._inSelectionMode, args.position, args.viewPosition)
|
||||
CursorMoveCommands.line(viewModel, viewModel.getPrimaryCursorState(), this._inSelectionMode, args.position, args.viewPosition)
|
||||
]
|
||||
);
|
||||
cursors.reveal(args.source, false, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1372,15 +1405,15 @@ export namespace CoreNavigationCommands {
|
||||
this._inSelectionMode = opts.inSelectionMode;
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
const lastAddedCursorIndex = cursors.getLastAddedCursorIndex();
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
const lastAddedCursorIndex = viewModel.getLastAddedCursorIndex();
|
||||
|
||||
const states = cursors.getAll();
|
||||
const states = viewModel.getCursorStates();
|
||||
const newStates: PartialCursorState[] = states.slice(0);
|
||||
newStates[lastAddedCursorIndex] = CursorMoveCommands.line(cursors.context, states[lastAddedCursorIndex], this._inSelectionMode, args.position, args.viewPosition);
|
||||
newStates[lastAddedCursorIndex] = CursorMoveCommands.line(viewModel, states[lastAddedCursorIndex], this._inSelectionMode, args.position, args.viewPosition);
|
||||
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
newStates
|
||||
@@ -1413,14 +1446,14 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
CursorMoveCommands.expandLineSelection(cursors.context, cursors.getAll())
|
||||
CursorMoveCommands.expandLineSelection(viewModel, viewModel.getCursorStates())
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1439,16 +1472,16 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
CursorMoveCommands.cancelSelection(cursors.context, cursors.getPrimaryCursor())
|
||||
CursorMoveCommands.cancelSelection(viewModel, viewModel.getPrimaryCursorState())
|
||||
]
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1466,16 +1499,16 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
cursors.getPrimaryCursor()
|
||||
viewModel.getPrimaryCursorState()
|
||||
]
|
||||
);
|
||||
cursors.reveal(args.source, true, RevealTarget.Primary, ScrollType.Smooth);
|
||||
viewModel.revealPrimaryCursor(args.source, true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1488,20 +1521,20 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
const revealLineArg = <RevealLine_.RawArguments>args;
|
||||
let lineNumber = (revealLineArg.lineNumber || 0) + 1;
|
||||
if (lineNumber < 1) {
|
||||
lineNumber = 1;
|
||||
}
|
||||
const lineCount = cursors.context.model.getLineCount();
|
||||
const lineCount = viewModel.model.getLineCount();
|
||||
if (lineNumber > lineCount) {
|
||||
lineNumber = lineCount;
|
||||
}
|
||||
|
||||
const range = new Range(
|
||||
lineNumber, 1,
|
||||
lineNumber, cursors.context.model.getLineMaxColumn(lineNumber)
|
||||
lineNumber, viewModel.model.getLineMaxColumn(lineNumber)
|
||||
);
|
||||
|
||||
let revealAt = VerticalRevealType.Simple;
|
||||
@@ -1521,9 +1554,9 @@ export namespace CoreNavigationCommands {
|
||||
}
|
||||
}
|
||||
|
||||
const viewRange = cursors.context.convertModelRangeToViewRange(range);
|
||||
const viewRange = viewModel.coordinatesConverter.convertModelRangeToViewRange(range);
|
||||
|
||||
cursors.revealRange(args.source, false, viewRange, revealAt, ScrollType.Smooth);
|
||||
viewModel.revealRange(args.source, false, viewRange, revealAt, ScrollType.Smooth);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1535,13 +1568,13 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
CursorMoveCommands.selectAll(cursors.context, cursors.getPrimaryCursor())
|
||||
CursorMoveCommands.selectAll(viewModel, viewModel.getPrimaryCursorState())
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1555,9 +1588,9 @@ export namespace CoreNavigationCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditorCommand(cursors: ICursors, args: any): void {
|
||||
cursors.context.model.pushStackElement();
|
||||
cursors.setStates(
|
||||
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
|
||||
viewModel.model.pushStackElement();
|
||||
viewModel.setCursorStates(
|
||||
args.source,
|
||||
CursorChangeReason.Explicit,
|
||||
[
|
||||
@@ -1688,15 +1721,15 @@ export namespace CoreEditingCommands {
|
||||
|
||||
export abstract class CoreEditingCommand extends EditorCommand {
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
|
||||
const cursors = editor._getCursors();
|
||||
if (!cursors) {
|
||||
const viewModel = editor._getViewModel();
|
||||
if (!viewModel) {
|
||||
// the editor has no view => has no cursors
|
||||
return;
|
||||
}
|
||||
this.runCoreEditingCommand(editor, cursors, args || {});
|
||||
this.runCoreEditingCommand(editor, viewModel, args || {});
|
||||
}
|
||||
|
||||
public abstract runCoreEditingCommand(editor: ICodeEditor, cursors: ICursors, args: any): void;
|
||||
public abstract runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void;
|
||||
}
|
||||
|
||||
export const LineBreakInsert: EditorCommand = registerEditorCommand(new class extends CoreEditingCommand {
|
||||
@@ -1713,9 +1746,9 @@ export namespace CoreEditingCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditingCommand(editor: ICodeEditor, cursors: ICursors, args: any): void {
|
||||
public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void {
|
||||
editor.pushUndoStop();
|
||||
editor.executeCommands(this.id, TypeOperations.lineBreakInsert(cursors.context.config, cursors.context.model, cursors.getAll().map(s => s.modelState.selection)));
|
||||
editor.executeCommands(this.id, TypeOperations.lineBreakInsert(viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection)));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1735,9 +1768,9 @@ export namespace CoreEditingCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditingCommand(editor: ICodeEditor, cursors: ICursors, args: any): void {
|
||||
public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void {
|
||||
editor.pushUndoStop();
|
||||
editor.executeCommands(this.id, TypeOperations.outdent(cursors.context.config, cursors.context.model, cursors.getAll().map(s => s.modelState.selection)));
|
||||
editor.executeCommands(this.id, TypeOperations.outdent(viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection)));
|
||||
editor.pushUndoStop();
|
||||
}
|
||||
});
|
||||
@@ -1758,9 +1791,9 @@ export namespace CoreEditingCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditingCommand(editor: ICodeEditor, cursors: ICursors, args: any): void {
|
||||
public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void {
|
||||
editor.pushUndoStop();
|
||||
editor.executeCommands(this.id, TypeOperations.tab(cursors.context.config, cursors.context.model, cursors.getAll().map(s => s.modelState.selection)));
|
||||
editor.executeCommands(this.id, TypeOperations.tab(viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection)));
|
||||
editor.pushUndoStop();
|
||||
}
|
||||
});
|
||||
@@ -1780,13 +1813,13 @@ export namespace CoreEditingCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditingCommand(editor: ICodeEditor, cursors: ICursors, args: any): void {
|
||||
const [shouldPushStackElementBefore, commands] = DeleteOperations.deleteLeft(cursors.getPrevEditOperationType(), cursors.context.config, cursors.context.model, cursors.getAll().map(s => s.modelState.selection));
|
||||
public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void {
|
||||
const [shouldPushStackElementBefore, commands] = DeleteOperations.deleteLeft(viewModel.getPrevEditOperationType(), viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection));
|
||||
if (shouldPushStackElementBefore) {
|
||||
editor.pushUndoStop();
|
||||
}
|
||||
editor.executeCommands(this.id, commands);
|
||||
cursors.setPrevEditOperationType(EditOperationType.DeletingLeft);
|
||||
viewModel.setPrevEditOperationType(EditOperationType.DeletingLeft);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1804,13 +1837,13 @@ export namespace CoreEditingCommands {
|
||||
});
|
||||
}
|
||||
|
||||
public runCoreEditingCommand(editor: ICodeEditor, cursors: ICursors, args: any): void {
|
||||
const [shouldPushStackElementBefore, commands] = DeleteOperations.deleteRight(cursors.getPrevEditOperationType(), cursors.context.config, cursors.context.model, cursors.getAll().map(s => s.modelState.selection));
|
||||
public runCoreEditingCommand(editor: ICodeEditor, viewModel: IViewModel, args: any): void {
|
||||
const [shouldPushStackElementBefore, commands] = DeleteOperations.deleteRight(viewModel.getPrevEditOperationType(), viewModel.cursorConfig, viewModel.model, viewModel.getCursorStates().map(s => s.modelState.selection));
|
||||
if (shouldPushStackElementBefore) {
|
||||
editor.pushUndoStop();
|
||||
}
|
||||
editor.executeCommands(this.id, commands);
|
||||
cursors.setPrevEditOperationType(EditOperationType.DeletingRight);
|
||||
viewModel.setPrevEditOperationType(EditOperationType.DeletingRight);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -420,7 +420,7 @@ class HitTestRequest extends BareHitTestRequest {
|
||||
let mouseColumn = this.mouseColumn;
|
||||
if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) {
|
||||
// Most likely, the line contains foreign decorations...
|
||||
mouseColumn = CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getOptions().tabSize) + 1;
|
||||
mouseColumn = CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getTextModelOptions().tabSize) + 1;
|
||||
}
|
||||
return new MouseTarget(this.target, type, mouseColumn, position, range, detail);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ class StandardPointerHandler extends MouseHandler implements IDisposable {
|
||||
}
|
||||
|
||||
private _onGestureChange(e: IThrottledGestureEvent): void {
|
||||
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
this._context.model.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -177,7 +177,7 @@ export class PointerEventHandler extends MouseHandler {
|
||||
|
||||
private onChange(e: GestureEvent): void {
|
||||
if (this._lastPointerType === 'touch') {
|
||||
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
this._context.model.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ class TouchHandler extends MouseHandler {
|
||||
}
|
||||
|
||||
private onChange(e: GestureEvent): void {
|
||||
this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
this._context.model.deltaScrollNow(-e.translationX, -e.translationY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import { ViewContext } from 'vs/editor/common/view/viewContext';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IEditorAriaOptions } from 'vs/editor/browser/editorBrowser';
|
||||
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
|
||||
|
||||
export interface ITextAreaHandlerHelper {
|
||||
visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalPosition | null;
|
||||
@@ -117,7 +118,7 @@ export class TextAreaHandler extends ViewPart {
|
||||
// Text Area (The focus will always be in the textarea when the cursor is blinking)
|
||||
this.textArea = createFastDomNode(document.createElement('textarea'));
|
||||
PartFingerprints.write(this.textArea, PartFingerprint.TextArea);
|
||||
this.textArea.setClassName('inputarea');
|
||||
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
|
||||
this.textArea.setAttribute('wrap', 'off');
|
||||
this.textArea.setAttribute('autocorrect', 'off');
|
||||
this.textArea.setAttribute('autocapitalize', 'off');
|
||||
@@ -234,37 +235,36 @@ export class TextAreaHandler extends ViewPart {
|
||||
multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null);
|
||||
mode = e.metadata.mode;
|
||||
}
|
||||
this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText, mode);
|
||||
this._viewController.paste(e.text, pasteOnNewLine, multicursorText, mode);
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onCut(() => {
|
||||
this._viewController.cut('keyboard');
|
||||
this._viewController.cut();
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onType((e: ITypeData) => {
|
||||
if (e.replaceCharCnt) {
|
||||
this._viewController.replacePreviousChar('keyboard', e.text, e.replaceCharCnt);
|
||||
this._viewController.replacePreviousChar(e.text, e.replaceCharCnt);
|
||||
} else {
|
||||
this._viewController.type('keyboard', e.text);
|
||||
this._viewController.type(e.text);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onSelectionChangeRequest((modelSelection: Selection) => {
|
||||
this._viewController.setSelection('keyboard', modelSelection);
|
||||
this._viewController.setSelection(modelSelection);
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onCompositionStart((e) => {
|
||||
const lineNumber = this._selections[0].startLineNumber;
|
||||
const column = this._selections[0].startColumn - (e.moveOneCharacterLeft ? 1 : 0);
|
||||
|
||||
this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent(
|
||||
this._context.model.revealRange(
|
||||
'keyboard',
|
||||
new Range(lineNumber, column, lineNumber, column),
|
||||
null,
|
||||
viewEvents.VerticalRevealType.Simple,
|
||||
true,
|
||||
new Range(lineNumber, column, lineNumber, column),
|
||||
viewEvents.VerticalRevealType.Simple,
|
||||
ScrollType.Immediate
|
||||
));
|
||||
);
|
||||
|
||||
// Find range pixel position
|
||||
const visibleRange = this._viewHelper.visibleRangeForPositionRelativeToEditor(lineNumber, column);
|
||||
@@ -279,9 +279,9 @@ export class TextAreaHandler extends ViewPart {
|
||||
}
|
||||
|
||||
// Show the textarea
|
||||
this.textArea.setClassName('inputarea ime-input');
|
||||
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ime-input`);
|
||||
|
||||
this._viewController.compositionStart('keyboard');
|
||||
this._viewController.compositionStart();
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onCompositionUpdate((e: ICompositionData) => {
|
||||
@@ -301,16 +301,16 @@ export class TextAreaHandler extends ViewPart {
|
||||
this._visibleTextArea = null;
|
||||
this._render();
|
||||
|
||||
this.textArea.setClassName('inputarea');
|
||||
this._viewController.compositionEnd('keyboard');
|
||||
this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
|
||||
this._viewController.compositionEnd();
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onFocus(() => {
|
||||
this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(true));
|
||||
this._context.model.setHasFocus(true);
|
||||
}));
|
||||
|
||||
this._register(this._textAreaInput.onBlur(() => {
|
||||
this._context.privateViewEventBus.emit(new viewEvents.ViewFocusChangedEvent(false));
|
||||
this._context.model.setHasFocus(false);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { IMouseEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { ICursors } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
@@ -19,6 +18,7 @@ import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/linesLayout';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
|
||||
/**
|
||||
* A view zone is a full horizontal rectangle that 'pushes' text down.
|
||||
@@ -616,15 +616,15 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
/**
|
||||
* Change the scrollLeft of the editor's viewport.
|
||||
*/
|
||||
setScrollLeft(newScrollLeft: number): void;
|
||||
setScrollLeft(newScrollLeft: number, scrollType?: editorCommon.ScrollType): void;
|
||||
/**
|
||||
* Change the scrollTop of the editor's viewport.
|
||||
*/
|
||||
setScrollTop(newScrollTop: number): void;
|
||||
setScrollTop(newScrollTop: number, scrollType?: editorCommon.ScrollType): void;
|
||||
/**
|
||||
* Change the scroll position of the editor's viewport.
|
||||
*/
|
||||
setScrollPosition(position: editorCommon.INewScrollPosition): void;
|
||||
setScrollPosition(position: editorCommon.INewScrollPosition, scrollType?: editorCommon.ScrollType): void;
|
||||
|
||||
/**
|
||||
* Get an action that is a contribution to this editor.
|
||||
@@ -639,7 +639,7 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
* @param source The source of the call.
|
||||
* @param command The command to execute
|
||||
*/
|
||||
executeCommand(source: string, command: editorCommon.ICommand): void;
|
||||
executeCommand(source: string | null | undefined, command: editorCommon.ICommand): void;
|
||||
|
||||
/**
|
||||
* Push an "undo stop" in the undo-redo stack.
|
||||
@@ -653,19 +653,19 @@ export interface ICodeEditor extends editorCommon.IEditor {
|
||||
* @param edits The edits to execute.
|
||||
* @param endCursorState Cursor state after the edits were applied.
|
||||
*/
|
||||
executeEdits(source: string, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean;
|
||||
executeEdits(source: string | null | undefined, edits: IIdentifiedSingleEditOperation[], endCursorState?: ICursorStateComputer | Selection[]): boolean;
|
||||
|
||||
/**
|
||||
* Execute multiple (concomitant) commands on the editor.
|
||||
* @param source The source of the call.
|
||||
* @param command The commands to execute
|
||||
*/
|
||||
executeCommands(source: string, commands: (editorCommon.ICommand | null)[]): void;
|
||||
executeCommands(source: string | null | undefined, commands: (editorCommon.ICommand | null)[]): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_getCursors(): ICursors | null;
|
||||
_getViewModel(): IViewModel | null;
|
||||
|
||||
/**
|
||||
* Get all the decorations on a line (filtering out decorations from other editors).
|
||||
@@ -858,7 +858,7 @@ export interface IActiveCodeEditor extends ICodeEditor {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_getCursors(): ICursors;
|
||||
_getViewModel(): IViewModel;
|
||||
|
||||
/**
|
||||
* Get all the decorations on a line (filtering out decorations from other editors).
|
||||
|
||||
@@ -217,7 +217,9 @@ function renderLine(lineContent: string, initialVisibleColumn: number, tabSize:
|
||||
break;
|
||||
|
||||
case CharCode.UTF8_BOM:
|
||||
case CharCode.LINE_SEPARATOR_2028:
|
||||
case CharCode.LINE_SEPARATOR:
|
||||
case CharCode.PARAGRAPH_SEPARATOR:
|
||||
case CharCode.NEXT_LINE:
|
||||
sb.write1(0xFFFD);
|
||||
break;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { CoreEditorCommand, CoreNavigationCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { IEditorMouseEvent, IPartialEditorMouseEvent } from 'vs/editor/browser/editorBrowser';
|
||||
import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents';
|
||||
import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { IConfiguration } from 'vs/editor/common/editorCommon';
|
||||
@@ -37,30 +37,30 @@ export interface IMouseDispatchData {
|
||||
export interface ICommandDelegate {
|
||||
executeEditorCommand(editorCommand: CoreEditorCommand, args: any): void;
|
||||
|
||||
paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void;
|
||||
type(source: string, text: string): void;
|
||||
replacePreviousChar(source: string, text: string, replaceCharCnt: number): void;
|
||||
compositionStart(source: string): void;
|
||||
compositionEnd(source: string): void;
|
||||
cut(source: string): void;
|
||||
paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void;
|
||||
type(text: string): void;
|
||||
replacePreviousChar(text: string, replaceCharCnt: number): void;
|
||||
startComposition(): void;
|
||||
endComposition(): void;
|
||||
cut(): void;
|
||||
}
|
||||
|
||||
export class ViewController {
|
||||
|
||||
private readonly configuration: IConfiguration;
|
||||
private readonly viewModel: IViewModel;
|
||||
private readonly outgoingEvents: ViewOutgoingEvents;
|
||||
private readonly userInputEvents: ViewUserInputEvents;
|
||||
private readonly commandDelegate: ICommandDelegate;
|
||||
|
||||
constructor(
|
||||
configuration: IConfiguration,
|
||||
viewModel: IViewModel,
|
||||
outgoingEvents: ViewOutgoingEvents,
|
||||
userInputEvents: ViewUserInputEvents,
|
||||
commandDelegate: ICommandDelegate
|
||||
) {
|
||||
this.configuration = configuration;
|
||||
this.viewModel = viewModel;
|
||||
this.outgoingEvents = outgoingEvents;
|
||||
this.userInputEvents = userInputEvents;
|
||||
this.commandDelegate = commandDelegate;
|
||||
}
|
||||
|
||||
@@ -69,33 +69,33 @@ export class ViewController {
|
||||
this.commandDelegate.executeEditorCommand(editorCommand, args);
|
||||
}
|
||||
|
||||
public paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void {
|
||||
this.commandDelegate.paste(source, text, pasteOnNewLine, multicursorText, mode);
|
||||
public paste(text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void {
|
||||
this.commandDelegate.paste(text, pasteOnNewLine, multicursorText, mode);
|
||||
}
|
||||
|
||||
public type(source: string, text: string): void {
|
||||
this.commandDelegate.type(source, text);
|
||||
public type(text: string): void {
|
||||
this.commandDelegate.type(text);
|
||||
}
|
||||
|
||||
public replacePreviousChar(source: string, text: string, replaceCharCnt: number): void {
|
||||
this.commandDelegate.replacePreviousChar(source, text, replaceCharCnt);
|
||||
public replacePreviousChar(text: string, replaceCharCnt: number): void {
|
||||
this.commandDelegate.replacePreviousChar(text, replaceCharCnt);
|
||||
}
|
||||
|
||||
public compositionStart(source: string): void {
|
||||
this.commandDelegate.compositionStart(source);
|
||||
public compositionStart(): void {
|
||||
this.commandDelegate.startComposition();
|
||||
}
|
||||
|
||||
public compositionEnd(source: string): void {
|
||||
this.commandDelegate.compositionEnd(source);
|
||||
public compositionEnd(): void {
|
||||
this.commandDelegate.endComposition();
|
||||
}
|
||||
|
||||
public cut(source: string): void {
|
||||
this.commandDelegate.cut(source);
|
||||
public cut(): void {
|
||||
this.commandDelegate.cut();
|
||||
}
|
||||
|
||||
public setSelection(source: string, modelSelection: Selection): void {
|
||||
public setSelection(modelSelection: Selection): void {
|
||||
this.commandDelegate.executeEditorCommand(CoreNavigationCommands.SetSelection, {
|
||||
source: source,
|
||||
source: 'keyboard',
|
||||
selection: modelSelection
|
||||
});
|
||||
}
|
||||
@@ -289,42 +289,42 @@ export class ViewController {
|
||||
}
|
||||
|
||||
public emitKeyDown(e: IKeyboardEvent): void {
|
||||
this.outgoingEvents.emitKeyDown(e);
|
||||
this.userInputEvents.emitKeyDown(e);
|
||||
}
|
||||
|
||||
public emitKeyUp(e: IKeyboardEvent): void {
|
||||
this.outgoingEvents.emitKeyUp(e);
|
||||
this.userInputEvents.emitKeyUp(e);
|
||||
}
|
||||
|
||||
public emitContextMenu(e: IEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitContextMenu(e);
|
||||
this.userInputEvents.emitContextMenu(e);
|
||||
}
|
||||
|
||||
public emitMouseMove(e: IEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitMouseMove(e);
|
||||
this.userInputEvents.emitMouseMove(e);
|
||||
}
|
||||
|
||||
public emitMouseLeave(e: IPartialEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitMouseLeave(e);
|
||||
this.userInputEvents.emitMouseLeave(e);
|
||||
}
|
||||
|
||||
public emitMouseUp(e: IEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitMouseUp(e);
|
||||
this.userInputEvents.emitMouseUp(e);
|
||||
}
|
||||
|
||||
public emitMouseDown(e: IEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitMouseDown(e);
|
||||
this.userInputEvents.emitMouseDown(e);
|
||||
}
|
||||
|
||||
public emitMouseDrag(e: IEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitMouseDrag(e);
|
||||
this.userInputEvents.emitMouseDrag(e);
|
||||
}
|
||||
|
||||
public emitMouseDrop(e: IPartialEditorMouseEvent): void {
|
||||
this.outgoingEvents.emitMouseDrop(e);
|
||||
this.userInputEvents.emitMouseDrop(e);
|
||||
}
|
||||
|
||||
public emitMouseWheel(e: IMouseWheelEvent): void {
|
||||
this.outgoingEvents.emitMouseWheel(e);
|
||||
this.userInputEvents.emitMouseWheel(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
@@ -13,7 +14,7 @@ import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler';
|
||||
import { ITextAreaHandlerHelper, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler';
|
||||
import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController';
|
||||
import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents';
|
||||
import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents';
|
||||
import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays';
|
||||
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||
import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets';
|
||||
@@ -36,13 +37,11 @@ import { ScrollDecorationViewPart } from 'vs/editor/browser/viewParts/scrollDeco
|
||||
import { SelectionsOverlay } from 'vs/editor/browser/viewParts/selections/selections';
|
||||
import { ViewCursors } from 'vs/editor/browser/viewParts/viewCursors/viewCursors';
|
||||
import { ViewZones } from 'vs/editor/browser/viewParts/viewZones/viewZones';
|
||||
import { Cursor } from 'vs/editor/common/controller/cursor';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IConfiguration } from 'vs/editor/common/editorCommon';
|
||||
import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { RenderingContext } from 'vs/editor/common/view/renderingContext';
|
||||
import { ViewContext } from 'vs/editor/common/view/viewContext';
|
||||
import { ViewEventDispatcher } from 'vs/editor/common/view/viewEventDispatcher';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler';
|
||||
@@ -64,31 +63,27 @@ export interface IOverlayWidgetData {
|
||||
|
||||
export class View extends ViewEventHandler {
|
||||
|
||||
private readonly eventDispatcher: ViewEventDispatcher;
|
||||
|
||||
private _scrollbar: EditorScrollbar;
|
||||
private readonly _scrollbar: EditorScrollbar;
|
||||
private readonly _context: ViewContext;
|
||||
private readonly _cursor: Cursor;
|
||||
private _selections: Selection[];
|
||||
|
||||
// The view lines
|
||||
private viewLines: ViewLines;
|
||||
private readonly _viewLines: ViewLines;
|
||||
|
||||
// These are parts, but we must do some API related calls on them, so we keep a reference
|
||||
private viewZones: ViewZones;
|
||||
private contentWidgets: ViewContentWidgets;
|
||||
private overlayWidgets: ViewOverlayWidgets;
|
||||
private viewCursors: ViewCursors;
|
||||
private viewParts: ViewPart[];
|
||||
private readonly _viewZones: ViewZones;
|
||||
private readonly _contentWidgets: ViewContentWidgets;
|
||||
private readonly _overlayWidgets: ViewOverlayWidgets;
|
||||
private readonly _viewCursors: ViewCursors;
|
||||
private readonly _viewParts: ViewPart[];
|
||||
|
||||
private readonly _textAreaHandler: TextAreaHandler;
|
||||
private readonly pointerHandler: PointerHandler;
|
||||
|
||||
private readonly outgoingEvents: ViewOutgoingEvents;
|
||||
private readonly _pointerHandler: PointerHandler;
|
||||
|
||||
// Dom nodes
|
||||
private linesContent: FastDomNode<HTMLElement>;
|
||||
public domNode: FastDomNode<HTMLElement>;
|
||||
private overflowGuardContainer: FastDomNode<HTMLElement>;
|
||||
private readonly _linesContent: FastDomNode<HTMLElement>;
|
||||
public readonly domNode: FastDomNode<HTMLElement>;
|
||||
private readonly _overflowGuardContainer: FastDomNode<HTMLElement>;
|
||||
|
||||
// Actual mutable state
|
||||
private _renderAnimationFrame: IDisposable | null;
|
||||
@@ -98,78 +93,73 @@ export class View extends ViewEventHandler {
|
||||
configuration: IConfiguration,
|
||||
themeService: IThemeService,
|
||||
model: IViewModel,
|
||||
cursor: Cursor,
|
||||
outgoingEvents: ViewOutgoingEvents
|
||||
userInputEvents: ViewUserInputEvents
|
||||
) {
|
||||
super();
|
||||
this._cursor = cursor;
|
||||
this._selections = [new Selection(1, 1, 1, 1)];
|
||||
this._renderAnimationFrame = null;
|
||||
this.outgoingEvents = outgoingEvents;
|
||||
|
||||
const viewController = new ViewController(configuration, model, this.outgoingEvents, commandDelegate);
|
||||
|
||||
// The event dispatcher will always go through _renderOnce before dispatching any events
|
||||
this.eventDispatcher = new ViewEventDispatcher((callback: () => void) => this._renderOnce(callback));
|
||||
|
||||
// Ensure the view is the first event handler in order to update the layout
|
||||
this.eventDispatcher.addEventHandler(this);
|
||||
const viewController = new ViewController(configuration, model, userInputEvents, commandDelegate);
|
||||
|
||||
// The view context is passed on to most classes (basically to reduce param. counts in ctors)
|
||||
this._context = new ViewContext(configuration, themeService.getColorTheme(), model, this.eventDispatcher);
|
||||
this._context = new ViewContext(configuration, themeService.getColorTheme(), model);
|
||||
|
||||
// Ensure the view is the first event handler in order to update the layout
|
||||
this._context.addEventHandler(this);
|
||||
|
||||
this._register(themeService.onDidColorThemeChange(theme => {
|
||||
this._context.theme.update(theme);
|
||||
this.eventDispatcher.emit(new viewEvents.ViewThemeChangedEvent());
|
||||
this._context.model.onDidColorThemeChange();
|
||||
this.render(true, false);
|
||||
}));
|
||||
|
||||
this.viewParts = [];
|
||||
this._viewParts = [];
|
||||
|
||||
// Keyboard handler
|
||||
this._textAreaHandler = new TextAreaHandler(this._context, viewController, this.createTextAreaHandlerHelper());
|
||||
this.viewParts.push(this._textAreaHandler);
|
||||
this._textAreaHandler = new TextAreaHandler(this._context, viewController, this._createTextAreaHandlerHelper());
|
||||
this._viewParts.push(this._textAreaHandler);
|
||||
|
||||
// These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.)
|
||||
this.linesContent = createFastDomNode(document.createElement('div'));
|
||||
this.linesContent.setClassName('lines-content' + ' monaco-editor-background');
|
||||
this.linesContent.setPosition('absolute');
|
||||
this._linesContent = createFastDomNode(document.createElement('div'));
|
||||
this._linesContent.setClassName('lines-content' + ' monaco-editor-background');
|
||||
this._linesContent.setPosition('absolute');
|
||||
|
||||
this.domNode = createFastDomNode(document.createElement('div'));
|
||||
this.domNode.setClassName(this.getEditorClassName());
|
||||
this.domNode.setClassName(this._getEditorClassName());
|
||||
// Set role 'code' for better screen reader support https://github.com/microsoft/vscode/issues/93438
|
||||
this.domNode.setAttribute('role', 'code');
|
||||
|
||||
this.overflowGuardContainer = createFastDomNode(document.createElement('div'));
|
||||
PartFingerprints.write(this.overflowGuardContainer, PartFingerprint.OverflowGuard);
|
||||
this.overflowGuardContainer.setClassName('overflow-guard');
|
||||
this._overflowGuardContainer = createFastDomNode(document.createElement('div'));
|
||||
PartFingerprints.write(this._overflowGuardContainer, PartFingerprint.OverflowGuard);
|
||||
this._overflowGuardContainer.setClassName('overflow-guard');
|
||||
|
||||
this._scrollbar = new EditorScrollbar(this._context, this.linesContent, this.domNode, this.overflowGuardContainer);
|
||||
this.viewParts.push(this._scrollbar);
|
||||
this._scrollbar = new EditorScrollbar(this._context, this._linesContent, this.domNode, this._overflowGuardContainer);
|
||||
this._viewParts.push(this._scrollbar);
|
||||
|
||||
// View Lines
|
||||
this.viewLines = new ViewLines(this._context, this.linesContent);
|
||||
this._viewLines = new ViewLines(this._context, this._linesContent);
|
||||
|
||||
// View Zones
|
||||
this.viewZones = new ViewZones(this._context);
|
||||
this.viewParts.push(this.viewZones);
|
||||
this._viewZones = new ViewZones(this._context);
|
||||
this._viewParts.push(this._viewZones);
|
||||
|
||||
// Decorations overview ruler
|
||||
const decorationsOverviewRuler = new DecorationsOverviewRuler(this._context);
|
||||
this.viewParts.push(decorationsOverviewRuler);
|
||||
this._viewParts.push(decorationsOverviewRuler);
|
||||
|
||||
|
||||
const scrollDecoration = new ScrollDecorationViewPart(this._context);
|
||||
this.viewParts.push(scrollDecoration);
|
||||
this._viewParts.push(scrollDecoration);
|
||||
|
||||
const contentViewOverlays = new ContentViewOverlays(this._context);
|
||||
this.viewParts.push(contentViewOverlays);
|
||||
this._viewParts.push(contentViewOverlays);
|
||||
contentViewOverlays.addDynamicOverlay(new CurrentLineHighlightOverlay(this._context));
|
||||
contentViewOverlays.addDynamicOverlay(new SelectionsOverlay(this._context));
|
||||
contentViewOverlays.addDynamicOverlay(new IndentGuidesOverlay(this._context));
|
||||
contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context));
|
||||
|
||||
const marginViewOverlays = new MarginViewOverlays(this._context);
|
||||
this.viewParts.push(marginViewOverlays);
|
||||
this._viewParts.push(marginViewOverlays);
|
||||
marginViewOverlays.addDynamicOverlay(new CurrentLineMarginHighlightOverlay(this._context));
|
||||
marginViewOverlays.addDynamicOverlay(new GlyphMarginOverlay(this._context));
|
||||
marginViewOverlays.addDynamicOverlay(new MarginViewLineDecorationsOverlay(this._context));
|
||||
@@ -177,26 +167,26 @@ export class View extends ViewEventHandler {
|
||||
marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context));
|
||||
|
||||
const margin = new Margin(this._context);
|
||||
margin.getDomNode().appendChild(this.viewZones.marginDomNode);
|
||||
margin.getDomNode().appendChild(this._viewZones.marginDomNode);
|
||||
margin.getDomNode().appendChild(marginViewOverlays.getDomNode());
|
||||
this.viewParts.push(margin);
|
||||
this._viewParts.push(margin);
|
||||
|
||||
// Content widgets
|
||||
this.contentWidgets = new ViewContentWidgets(this._context, this.domNode);
|
||||
this.viewParts.push(this.contentWidgets);
|
||||
this._contentWidgets = new ViewContentWidgets(this._context, this.domNode);
|
||||
this._viewParts.push(this._contentWidgets);
|
||||
|
||||
this.viewCursors = new ViewCursors(this._context);
|
||||
this.viewParts.push(this.viewCursors);
|
||||
this._viewCursors = new ViewCursors(this._context);
|
||||
this._viewParts.push(this._viewCursors);
|
||||
|
||||
// Overlay widgets
|
||||
this.overlayWidgets = new ViewOverlayWidgets(this._context);
|
||||
this.viewParts.push(this.overlayWidgets);
|
||||
this._overlayWidgets = new ViewOverlayWidgets(this._context);
|
||||
this._viewParts.push(this._overlayWidgets);
|
||||
|
||||
const rulers = new Rulers(this._context);
|
||||
this.viewParts.push(rulers);
|
||||
this._viewParts.push(rulers);
|
||||
|
||||
const minimap = new Minimap(this._context);
|
||||
this.viewParts.push(minimap);
|
||||
this._viewParts.push(minimap);
|
||||
|
||||
// -------------- Wire dom nodes up
|
||||
|
||||
@@ -205,82 +195,74 @@ export class View extends ViewEventHandler {
|
||||
overviewRulerData.parent.insertBefore(decorationsOverviewRuler.getDomNode(), overviewRulerData.insertBefore);
|
||||
}
|
||||
|
||||
this.linesContent.appendChild(contentViewOverlays.getDomNode());
|
||||
this.linesContent.appendChild(rulers.domNode);
|
||||
this.linesContent.appendChild(this.viewZones.domNode);
|
||||
this.linesContent.appendChild(this.viewLines.getDomNode());
|
||||
this.linesContent.appendChild(this.contentWidgets.domNode);
|
||||
this.linesContent.appendChild(this.viewCursors.getDomNode());
|
||||
this.overflowGuardContainer.appendChild(margin.getDomNode());
|
||||
this.overflowGuardContainer.appendChild(this._scrollbar.getDomNode());
|
||||
this.overflowGuardContainer.appendChild(scrollDecoration.getDomNode());
|
||||
this.overflowGuardContainer.appendChild(this._textAreaHandler.textArea);
|
||||
this.overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover);
|
||||
this.overflowGuardContainer.appendChild(this.overlayWidgets.getDomNode());
|
||||
this.overflowGuardContainer.appendChild(minimap.getDomNode());
|
||||
this.domNode.appendChild(this.overflowGuardContainer);
|
||||
this.domNode.appendChild(this.contentWidgets.overflowingContentWidgetsDomNode);
|
||||
this._linesContent.appendChild(contentViewOverlays.getDomNode());
|
||||
this._linesContent.appendChild(rulers.domNode);
|
||||
this._linesContent.appendChild(this._viewZones.domNode);
|
||||
this._linesContent.appendChild(this._viewLines.getDomNode());
|
||||
this._linesContent.appendChild(this._contentWidgets.domNode);
|
||||
this._linesContent.appendChild(this._viewCursors.getDomNode());
|
||||
this._overflowGuardContainer.appendChild(margin.getDomNode());
|
||||
this._overflowGuardContainer.appendChild(this._scrollbar.getDomNode());
|
||||
this._overflowGuardContainer.appendChild(scrollDecoration.getDomNode());
|
||||
this._overflowGuardContainer.appendChild(this._textAreaHandler.textArea);
|
||||
this._overflowGuardContainer.appendChild(this._textAreaHandler.textAreaCover);
|
||||
this._overflowGuardContainer.appendChild(this._overlayWidgets.getDomNode());
|
||||
this._overflowGuardContainer.appendChild(minimap.getDomNode());
|
||||
this.domNode.appendChild(this._overflowGuardContainer);
|
||||
this.domNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode);
|
||||
|
||||
this._applyLayout();
|
||||
|
||||
// Pointer handler
|
||||
this.pointerHandler = this._register(new PointerHandler(this._context, viewController, this.createPointerHandlerHelper()));
|
||||
|
||||
this._register(model.addEventListener((events: viewEvents.ViewEvent[]) => {
|
||||
this.eventDispatcher.emitMany(events);
|
||||
}));
|
||||
|
||||
this._register(this._cursor.addEventListener((events: viewEvents.ViewEvent[]) => {
|
||||
this.eventDispatcher.emitMany(events);
|
||||
}));
|
||||
this._pointerHandler = this._register(new PointerHandler(this._context, viewController, this._createPointerHandlerHelper()));
|
||||
}
|
||||
|
||||
private _flushAccumulatedAndRenderNow(): void {
|
||||
this._renderNow();
|
||||
}
|
||||
|
||||
private createPointerHandlerHelper(): IPointerHandlerHelper {
|
||||
private _createPointerHandlerHelper(): IPointerHandlerHelper {
|
||||
return {
|
||||
viewDomNode: this.domNode.domNode,
|
||||
linesContentDomNode: this.linesContent.domNode,
|
||||
linesContentDomNode: this._linesContent.domNode,
|
||||
|
||||
focusTextArea: () => {
|
||||
this.focus();
|
||||
},
|
||||
|
||||
getLastRenderData: (): PointerHandlerLastRenderData => {
|
||||
const lastViewCursorsRenderData = this.viewCursors.getLastRenderData() || [];
|
||||
const lastViewCursorsRenderData = this._viewCursors.getLastRenderData() || [];
|
||||
const lastTextareaPosition = this._textAreaHandler.getLastRenderData();
|
||||
return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition);
|
||||
},
|
||||
shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => {
|
||||
return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
|
||||
return this._viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
|
||||
},
|
||||
shouldSuppressMouseDownOnWidget: (widgetId: string) => {
|
||||
return this.contentWidgets.shouldSuppressMouseDownOnWidget(widgetId);
|
||||
return this._contentWidgets.shouldSuppressMouseDownOnWidget(widgetId);
|
||||
},
|
||||
getPositionFromDOMInfo: (spanNode: HTMLElement, offset: number) => {
|
||||
this._flushAccumulatedAndRenderNow();
|
||||
return this.viewLines.getPositionFromDOMInfo(spanNode, offset);
|
||||
return this._viewLines.getPositionFromDOMInfo(spanNode, offset);
|
||||
},
|
||||
|
||||
visibleRangeForPosition: (lineNumber: number, column: number) => {
|
||||
this._flushAccumulatedAndRenderNow();
|
||||
return this.viewLines.visibleRangeForPosition(new Position(lineNumber, column));
|
||||
return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column));
|
||||
},
|
||||
|
||||
getLineWidth: (lineNumber: number) => {
|
||||
this._flushAccumulatedAndRenderNow();
|
||||
return this.viewLines.getLineWidth(lineNumber);
|
||||
return this._viewLines.getLineWidth(lineNumber);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private createTextAreaHandlerHelper(): ITextAreaHandlerHelper {
|
||||
private _createTextAreaHandlerHelper(): ITextAreaHandlerHelper {
|
||||
return {
|
||||
visibleRangeForPositionRelativeToEditor: (lineNumber: number, column: number) => {
|
||||
this._flushAccumulatedAndRenderNow();
|
||||
return this.viewLines.visibleRangeForPosition(new Position(lineNumber, column));
|
||||
return this._viewLines.visibleRangeForPosition(new Position(lineNumber, column));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -292,45 +274,38 @@ export class View extends ViewEventHandler {
|
||||
this.domNode.setWidth(layoutInfo.width);
|
||||
this.domNode.setHeight(layoutInfo.height);
|
||||
|
||||
this.overflowGuardContainer.setWidth(layoutInfo.width);
|
||||
this.overflowGuardContainer.setHeight(layoutInfo.height);
|
||||
this._overflowGuardContainer.setWidth(layoutInfo.width);
|
||||
this._overflowGuardContainer.setHeight(layoutInfo.height);
|
||||
|
||||
this.linesContent.setWidth(1000000);
|
||||
this.linesContent.setHeight(1000000);
|
||||
this._linesContent.setWidth(1000000);
|
||||
this._linesContent.setHeight(1000000);
|
||||
}
|
||||
|
||||
private getEditorClassName() {
|
||||
private _getEditorClassName() {
|
||||
const focused = this._textAreaHandler.isFocused() ? ' focused' : '';
|
||||
return this._context.configuration.options.get(EditorOption.editorClassName) + ' ' + getThemeTypeSelector(this._context.theme.type) + focused;
|
||||
}
|
||||
|
||||
// --- begin event handlers
|
||||
|
||||
public handleEvents(events: viewEvents.ViewEvent[]): void {
|
||||
super.handleEvents(events);
|
||||
this._scheduleRender();
|
||||
}
|
||||
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
|
||||
this.domNode.setClassName(this.getEditorClassName());
|
||||
this.domNode.setClassName(this._getEditorClassName());
|
||||
this._applyLayout();
|
||||
return false;
|
||||
}
|
||||
public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean {
|
||||
this.outgoingEvents.emitContentSizeChange(e);
|
||||
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
|
||||
this._selections = e.selections;
|
||||
return false;
|
||||
}
|
||||
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
|
||||
this.domNode.setClassName(this.getEditorClassName());
|
||||
this._context.model.setHasFocus(e.isFocused);
|
||||
if (e.isFocused) {
|
||||
this.outgoingEvents.emitViewFocusGained();
|
||||
} else {
|
||||
this.outgoingEvents.emitViewFocusLost();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
|
||||
this.outgoingEvents.emitScrollChanged(e);
|
||||
this.domNode.setClassName(this._getEditorClassName());
|
||||
return false;
|
||||
}
|
||||
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
|
||||
this.domNode.setClassName(this.getEditorClassName());
|
||||
this.domNode.setClassName(this._getEditorClassName());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -342,26 +317,18 @@ export class View extends ViewEventHandler {
|
||||
this._renderAnimationFrame = null;
|
||||
}
|
||||
|
||||
this.eventDispatcher.removeEventHandler(this);
|
||||
this.outgoingEvents.dispose();
|
||||
this._context.removeEventHandler(this);
|
||||
|
||||
this.viewLines.dispose();
|
||||
this._viewLines.dispose();
|
||||
|
||||
// Destroy view parts
|
||||
for (let i = 0, len = this.viewParts.length; i < len; i++) {
|
||||
this.viewParts[i].dispose();
|
||||
for (let i = 0, len = this._viewParts.length; i < len; i++) {
|
||||
this._viewParts[i].dispose();
|
||||
}
|
||||
this.viewParts = [];
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _renderOnce<T>(callback: () => T): T {
|
||||
const r = safeInvokeNoArg(callback);
|
||||
this._scheduleRender();
|
||||
return r;
|
||||
}
|
||||
|
||||
private _scheduleRender(): void {
|
||||
if (this._renderAnimationFrame === null) {
|
||||
this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), 100);
|
||||
@@ -379,8 +346,8 @@ export class View extends ViewEventHandler {
|
||||
|
||||
private _getViewPartsToRender(): ViewPart[] {
|
||||
let result: ViewPart[] = [], resultLen = 0;
|
||||
for (let i = 0, len = this.viewParts.length; i < len; i++) {
|
||||
const viewPart = this.viewParts[i];
|
||||
for (let i = 0, len = this._viewParts.length; i < len; i++) {
|
||||
const viewPart = this._viewParts[i];
|
||||
if (viewPart.shouldRender()) {
|
||||
result[resultLen++] = viewPart;
|
||||
}
|
||||
@@ -395,7 +362,7 @@ export class View extends ViewEventHandler {
|
||||
|
||||
let viewPartsToRender = this._getViewPartsToRender();
|
||||
|
||||
if (!this.viewLines.shouldRender() && viewPartsToRender.length === 0) {
|
||||
if (!this._viewLines.shouldRender() && viewPartsToRender.length === 0) {
|
||||
// Nothing to render
|
||||
return;
|
||||
}
|
||||
@@ -404,26 +371,26 @@ export class View extends ViewEventHandler {
|
||||
this._context.model.setViewport(partialViewportData.startLineNumber, partialViewportData.endLineNumber, partialViewportData.centeredLineNumber);
|
||||
|
||||
const viewportData = new ViewportData(
|
||||
this._cursor.getViewSelections(),
|
||||
this._selections,
|
||||
partialViewportData,
|
||||
this._context.viewLayout.getWhitespaceViewportData(),
|
||||
this._context.model
|
||||
);
|
||||
|
||||
if (this.contentWidgets.shouldRender()) {
|
||||
if (this._contentWidgets.shouldRender()) {
|
||||
// Give the content widgets a chance to set their max width before a possible synchronous layout
|
||||
this.contentWidgets.onBeforeRender(viewportData);
|
||||
this._contentWidgets.onBeforeRender(viewportData);
|
||||
}
|
||||
|
||||
if (this.viewLines.shouldRender()) {
|
||||
this.viewLines.renderText(viewportData);
|
||||
this.viewLines.onDidRender();
|
||||
if (this._viewLines.shouldRender()) {
|
||||
this._viewLines.renderText(viewportData);
|
||||
this._viewLines.onDidRender();
|
||||
|
||||
// Rendering of viewLines might cause scroll events to occur, so collect view parts to render again
|
||||
viewPartsToRender = this._getViewPartsToRender();
|
||||
}
|
||||
|
||||
const renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this.viewLines);
|
||||
const renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this._viewLines);
|
||||
|
||||
// Render the rest of the parts
|
||||
for (let i = 0, len = viewPartsToRender.length; i < len; i++) {
|
||||
@@ -445,11 +412,11 @@ export class View extends ViewEventHandler {
|
||||
}
|
||||
|
||||
public restoreState(scrollPosition: { scrollLeft: number; scrollTop: number; }): void {
|
||||
this._context.viewLayout.setScrollPositionNow({ scrollTop: scrollPosition.scrollTop });
|
||||
this._context.model.setScrollPosition({ scrollTop: scrollPosition.scrollTop }, ScrollType.Immediate);
|
||||
this._context.model.tokenizeViewport();
|
||||
this._renderNow();
|
||||
this.viewLines.updateLineWidths();
|
||||
this._context.viewLayout.setScrollPositionNow({ scrollLeft: scrollPosition.scrollLeft });
|
||||
this._viewLines.updateLineWidths();
|
||||
this._context.model.setScrollPosition({ scrollLeft: scrollPosition.scrollLeft }, ScrollType.Immediate);
|
||||
}
|
||||
|
||||
public getOffsetForColumn(modelLineNumber: number, modelColumn: number): number {
|
||||
@@ -459,7 +426,7 @@ export class View extends ViewEventHandler {
|
||||
});
|
||||
const viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
|
||||
this._flushAccumulatedAndRenderNow();
|
||||
const visibleRange = this.viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column));
|
||||
const visibleRange = this._viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column));
|
||||
if (!visibleRange) {
|
||||
return -1;
|
||||
}
|
||||
@@ -467,34 +434,28 @@ export class View extends ViewEventHandler {
|
||||
}
|
||||
|
||||
public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {
|
||||
const mouseTarget = this.pointerHandler.getTargetAtClientPoint(clientX, clientY);
|
||||
const mouseTarget = this._pointerHandler.getTargetAtClientPoint(clientX, clientY);
|
||||
if (!mouseTarget) {
|
||||
return null;
|
||||
}
|
||||
return ViewOutgoingEvents.convertViewToModelMouseTarget(mouseTarget, this._context.model.coordinatesConverter);
|
||||
return ViewUserInputEvents.convertViewToModelMouseTarget(mouseTarget, this._context.model.coordinatesConverter);
|
||||
}
|
||||
|
||||
public createOverviewRuler(cssClassName: string): OverviewRuler {
|
||||
return new OverviewRuler(this._context, cssClassName);
|
||||
}
|
||||
|
||||
public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): boolean {
|
||||
return this._renderOnce(() => {
|
||||
const zonesHaveChanged = this.viewZones.changeViewZones(callback);
|
||||
if (zonesHaveChanged) {
|
||||
this._context.viewLayout.onHeightMaybeChanged();
|
||||
this._context.privateViewEventBus.emit(new viewEvents.ViewZonesChangedEvent());
|
||||
}
|
||||
return zonesHaveChanged;
|
||||
});
|
||||
public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): void {
|
||||
this._viewZones.changeViewZones(callback);
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
public render(now: boolean, everything: boolean): void {
|
||||
if (everything) {
|
||||
// Force everything to render...
|
||||
this.viewLines.forceShouldRender();
|
||||
for (let i = 0, len = this.viewParts.length; i < len; i++) {
|
||||
const viewPart = this.viewParts[i];
|
||||
this._viewLines.forceShouldRender();
|
||||
for (let i = 0, len = this._viewParts.length; i < len; i++) {
|
||||
const viewPart = this._viewParts[i];
|
||||
viewPart.forceShouldRender();
|
||||
}
|
||||
}
|
||||
@@ -522,7 +483,7 @@ export class View extends ViewEventHandler {
|
||||
}
|
||||
|
||||
public addContentWidget(widgetData: IContentWidgetData): void {
|
||||
this.contentWidgets.addWidget(widgetData.widget);
|
||||
this._contentWidgets.addWidget(widgetData.widget);
|
||||
this.layoutContentWidget(widgetData);
|
||||
this._scheduleRender();
|
||||
}
|
||||
@@ -536,31 +497,31 @@ export class View extends ViewEventHandler {
|
||||
}
|
||||
}
|
||||
const newPreference = widgetData.position ? widgetData.position.preference : null;
|
||||
this.contentWidgets.setWidgetPosition(widgetData.widget, newRange, newPreference);
|
||||
this._contentWidgets.setWidgetPosition(widgetData.widget, newRange, newPreference);
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
public removeContentWidget(widgetData: IContentWidgetData): void {
|
||||
this.contentWidgets.removeWidget(widgetData.widget);
|
||||
this._contentWidgets.removeWidget(widgetData.widget);
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
public addOverlayWidget(widgetData: IOverlayWidgetData): void {
|
||||
this.overlayWidgets.addWidget(widgetData.widget);
|
||||
this._overlayWidgets.addWidget(widgetData.widget);
|
||||
this.layoutOverlayWidget(widgetData);
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
public layoutOverlayWidget(widgetData: IOverlayWidgetData): void {
|
||||
const newPreference = widgetData.position ? widgetData.position.preference : null;
|
||||
const shouldRender = this.overlayWidgets.setWidgetPosition(widgetData.widget, newPreference);
|
||||
const shouldRender = this._overlayWidgets.setWidgetPosition(widgetData.widget, newPreference);
|
||||
if (shouldRender) {
|
||||
this._scheduleRender();
|
||||
}
|
||||
}
|
||||
|
||||
public removeOverlayWidget(widgetData: IOverlayWidgetData): void {
|
||||
this.overlayWidgets.removeWidget(widgetData.widget);
|
||||
this._overlayWidgets.removeWidget(widgetData.widget);
|
||||
this._scheduleRender();
|
||||
}
|
||||
|
||||
|
||||
+13
-45
@@ -4,66 +4,34 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { MouseTarget } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { IEditorMouseEvent, IMouseTarget, IPartialEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IScrollEvent, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { IViewModel, ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
||||
export interface EventCallback<T> {
|
||||
(event: T): void;
|
||||
}
|
||||
|
||||
export class ViewOutgoingEvents extends Disposable {
|
||||
export class ViewUserInputEvents {
|
||||
|
||||
public onDidContentSizeChange: EventCallback<IContentSizeChangedEvent> | null = null;
|
||||
public onDidScroll: EventCallback<IScrollEvent> | null = null;
|
||||
public onDidGainFocus: EventCallback<void> | null = null;
|
||||
public onDidLoseFocus: EventCallback<void> | null = null;
|
||||
public onKeyDown: EventCallback<IKeyboardEvent> | null = null;
|
||||
public onKeyUp: EventCallback<IKeyboardEvent> | null = null;
|
||||
public onContextMenu: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseMove: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseLeave: EventCallback<IPartialEditorMouseEvent> | null = null;
|
||||
public onMouseUp: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseDown: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseUp: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseDrag: EventCallback<IEditorMouseEvent> | null = null;
|
||||
public onMouseDrop: EventCallback<IPartialEditorMouseEvent> | null = null;
|
||||
public onMouseWheel: EventCallback<IMouseWheelEvent> | null = null;
|
||||
|
||||
private readonly _viewModel: IViewModel;
|
||||
private readonly _coordinatesConverter: ICoordinatesConverter;
|
||||
|
||||
constructor(viewModel: IViewModel) {
|
||||
super();
|
||||
this._viewModel = viewModel;
|
||||
}
|
||||
|
||||
public emitContentSizeChange(e: viewEvents.ViewContentSizeChangedEvent): void {
|
||||
if (this.onDidContentSizeChange) {
|
||||
this.onDidContentSizeChange(e);
|
||||
}
|
||||
}
|
||||
|
||||
public emitScrollChanged(e: viewEvents.ViewScrollChangedEvent): void {
|
||||
if (this.onDidScroll) {
|
||||
this.onDidScroll(e);
|
||||
}
|
||||
}
|
||||
|
||||
public emitViewFocusGained(): void {
|
||||
if (this.onDidGainFocus) {
|
||||
this.onDidGainFocus(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
public emitViewFocusLost(): void {
|
||||
if (this.onDidLoseFocus) {
|
||||
this.onDidLoseFocus(undefined);
|
||||
}
|
||||
constructor(coordinatesConverter: ICoordinatesConverter) {
|
||||
this._coordinatesConverter = coordinatesConverter;
|
||||
}
|
||||
|
||||
public emitKeyDown(e: IKeyboardEvent): void {
|
||||
@@ -96,18 +64,18 @@ export class ViewOutgoingEvents extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public emitMouseUp(e: IEditorMouseEvent): void {
|
||||
if (this.onMouseUp) {
|
||||
this.onMouseUp(this._convertViewToModelMouseEvent(e));
|
||||
}
|
||||
}
|
||||
|
||||
public emitMouseDown(e: IEditorMouseEvent): void {
|
||||
if (this.onMouseDown) {
|
||||
this.onMouseDown(this._convertViewToModelMouseEvent(e));
|
||||
}
|
||||
}
|
||||
|
||||
public emitMouseUp(e: IEditorMouseEvent): void {
|
||||
if (this.onMouseUp) {
|
||||
this.onMouseUp(this._convertViewToModelMouseEvent(e));
|
||||
}
|
||||
}
|
||||
|
||||
public emitMouseDrag(e: IEditorMouseEvent): void {
|
||||
if (this.onMouseDrag) {
|
||||
this.onMouseDrag(this._convertViewToModelMouseEvent(e));
|
||||
@@ -139,7 +107,7 @@ export class ViewOutgoingEvents extends Disposable {
|
||||
}
|
||||
|
||||
private _convertViewToModelMouseTarget(target: IMouseTarget): IMouseTarget {
|
||||
return ViewOutgoingEvents.convertViewToModelMouseTarget(target, this._viewModel.coordinatesConverter);
|
||||
return ViewUserInputEvents.convertViewToModelMouseTarget(target, this._coordinatesConverter);
|
||||
}
|
||||
|
||||
public static convertViewToModelMouseTarget(target: IMouseTarget, coordinatesConverter: ICoordinatesConverter): IMouseTarget {
|
||||
@@ -9,7 +9,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IOverviewRulerLayoutInfo, SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollableElementChangeOptions, ScrollableElementCreationOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
|
||||
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||
import { INewScrollPosition } from 'vs/editor/common/editorCommon';
|
||||
import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
|
||||
import { ViewContext } from 'vs/editor/common/view/viewContext';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
@@ -88,7 +88,7 @@ export class EditorScrollbar extends ViewPart {
|
||||
}
|
||||
}
|
||||
|
||||
this._context.viewLayout.setScrollPositionNow(newScrollPosition);
|
||||
this._context.model.setScrollPosition(newScrollPosition, ScrollType.Immediate);
|
||||
};
|
||||
|
||||
// I've seen this happen both on the view dom node & on the lines content dom node.
|
||||
|
||||
@@ -112,7 +112,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
|
||||
|
||||
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
|
||||
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
|
||||
const { indentSize } = this._context.model.getOptions();
|
||||
const { indentSize } = this._context.model.getTextModelOptions();
|
||||
const indentWidth = indentSize * this._spaceWidth;
|
||||
const scrollWidth = ctx.scrollWidth;
|
||||
const lineHeight = this._lineHeight;
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
}
|
||||
|
||||
.monaco-editor .view-lines {
|
||||
cursor: text;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'
|
||||
import { Viewport } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
|
||||
|
||||
class LastRenderedData {
|
||||
|
||||
@@ -134,7 +135,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
|
||||
this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type);
|
||||
|
||||
PartFingerprints.write(this.domNode, PartFingerprint.ViewLines);
|
||||
this.domNode.setClassName('view-lines');
|
||||
this.domNode.setClassName(`view-lines ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
|
||||
Configuration.applyFontInfo(this.domNode, fontInfo);
|
||||
|
||||
// --- width & height
|
||||
@@ -280,11 +281,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
|
||||
}
|
||||
|
||||
const scrollTopDelta = Math.abs(this._context.viewLayout.getCurrentScrollTop() - newScrollPosition.scrollTop);
|
||||
if (e.scrollType === ScrollType.Smooth && scrollTopDelta > this._lineHeight) {
|
||||
this._context.viewLayout.setScrollPositionSmooth(newScrollPosition);
|
||||
} else {
|
||||
this._context.viewLayout.setScrollPositionNow(newScrollPosition);
|
||||
}
|
||||
const scrollType = (scrollTopDelta <= this._lineHeight ? ScrollType.Immediate : e.scrollType);
|
||||
this._context.model.setScrollPosition(newScrollPosition, scrollType);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -309,7 +307,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
|
||||
return this._visibleLines.onTokensChanged(e);
|
||||
}
|
||||
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
|
||||
this._context.viewLayout.onMaxLineWidthChanged(this._maxLineWidth);
|
||||
this._context.model.setMaxLineWidth(this._maxLineWidth);
|
||||
return this._visibleLines.onZonesChanged(e);
|
||||
}
|
||||
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
|
||||
@@ -589,15 +587,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
|
||||
this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset);
|
||||
}
|
||||
// set `scrollLeft`
|
||||
if (horizontalRevealRequest.scrollType === ScrollType.Smooth) {
|
||||
this._context.viewLayout.setScrollPositionSmooth({
|
||||
scrollLeft: newScrollLeft.scrollLeft
|
||||
});
|
||||
} else {
|
||||
this._context.viewLayout.setScrollPositionNow({
|
||||
scrollLeft: newScrollLeft.scrollLeft
|
||||
});
|
||||
}
|
||||
this._context.model.setScrollPosition({
|
||||
scrollLeft: newScrollLeft.scrollLeft
|
||||
}, horizontalRevealRequest.scrollType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,11 +626,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
|
||||
const iLineWidth = Math.ceil(lineWidth);
|
||||
if (this._maxLineWidth < iLineWidth) {
|
||||
this._maxLineWidth = iLineWidth;
|
||||
this._context.viewLayout.onMaxLineWidthChanged(this._maxLineWidth);
|
||||
this._context.model.setMaxLineWidth(this._maxLineWidth);
|
||||
}
|
||||
}
|
||||
|
||||
private _computeScrollTopToRevealRange(viewport: Viewport, source: string, range: Range | null, selections: Selection[] | null, verticalType: viewEvents.VerticalRevealType): number {
|
||||
private _computeScrollTopToRevealRange(viewport: Viewport, source: string | null | undefined, range: Range | null, selections: Selection[] | null, verticalType: viewEvents.VerticalRevealType): number {
|
||||
const viewportStartY = viewport.top;
|
||||
const viewportHeight = viewport.height;
|
||||
const viewportEndY = viewportStartY + viewportHeight;
|
||||
|
||||
@@ -1004,27 +1004,26 @@ export class Minimap extends ViewPart implements IMinimapModel {
|
||||
}
|
||||
|
||||
public getOptions(): TextModelResolvedOptions {
|
||||
return this._context.model.getOptions();
|
||||
return this._context.model.getTextModelOptions();
|
||||
}
|
||||
|
||||
public revealLineNumber(lineNumber: number): void {
|
||||
if (this._samplingState) {
|
||||
lineNumber = this._samplingState.minimapLines[lineNumber - 1];
|
||||
}
|
||||
this._context.privateViewEventBus.emit(new viewEvents.ViewRevealRangeRequestEvent(
|
||||
this._context.model.revealRange(
|
||||
'mouse',
|
||||
new Range(lineNumber, 1, lineNumber, 1),
|
||||
null,
|
||||
viewEvents.VerticalRevealType.Center,
|
||||
false,
|
||||
new Range(lineNumber, 1, lineNumber, 1),
|
||||
viewEvents.VerticalRevealType.Center,
|
||||
ScrollType.Smooth
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
public setScrollTop(scrollTop: number): void {
|
||||
this._context.viewLayout.setScrollPositionNow({
|
||||
this._context.model.setScrollPosition({
|
||||
scrollTop: scrollTop
|
||||
});
|
||||
}, ScrollType.Immediate);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -64,7 +64,7 @@ export class Rulers extends ViewPart {
|
||||
}
|
||||
|
||||
if (currentCount < desiredCount) {
|
||||
const { tabSize } = this._context.model.getOptions();
|
||||
const { tabSize } = this._context.model.getTextModelOptions();
|
||||
const rulerWidth = tabSize;
|
||||
let addCount = desiredCount - currentCount;
|
||||
while (addCount > 0) {
|
||||
|
||||
@@ -419,7 +419,7 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .selected-text { background-color: ${editorInactiveSelectionColor}; }`);
|
||||
}
|
||||
const editorSelectionForegroundColor = theme.getColor(editorSelectionForeground);
|
||||
if (editorSelectionForegroundColor) {
|
||||
if (editorSelectionForegroundColor && !editorSelectionForegroundColor.isTransparent()) {
|
||||
collector.addRule(`.monaco-editor .view-line span.inline-selected-text { color: ${editorSelectionForegroundColor}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext';
|
||||
import { ViewContext } from 'vs/editor/common/view/viewContext';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
|
||||
|
||||
export interface IViewCursorRenderData {
|
||||
domNode: HTMLElement;
|
||||
@@ -63,7 +64,7 @@ export class ViewCursor {
|
||||
|
||||
// Create the dom node
|
||||
this._domNode = createFastDomNode(document.createElement('div'));
|
||||
this._domNode.setClassName('cursor');
|
||||
this._domNode.setClassName(`cursor ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`);
|
||||
this._domNode.setHeight(this._lineHeight);
|
||||
this._domNode.setTop(0);
|
||||
this._domNode.setLeft(0);
|
||||
@@ -200,7 +201,7 @@ export class ViewCursor {
|
||||
this._domNode.domNode.textContent = this._lastRenderedContent;
|
||||
}
|
||||
|
||||
this._domNode.setClassName('cursor ' + this._renderData.textContentClassName);
|
||||
this._domNode.setClassName(`cursor ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ${this._renderData.textContentClassName}`);
|
||||
|
||||
this._domNode.setDisplay('block');
|
||||
this._domNode.setTop(this._renderData.top);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user