Archived
1
0

Update to VS Code 1.52.1

This commit is contained in:
Asher
2021-02-09 16:08:37 +00:00
1351 changed files with 56560 additions and 38990 deletions

View File

@ -5,7 +5,7 @@
import { IAction, IActionRunner, IActionViewItem } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview';
export interface IContextMenuEvent {
readonly shiftKey?: boolean;
@ -26,6 +26,7 @@ export interface IContextMenuDelegate {
actionRunner?: IActionRunner;
autoSelectFirstItem?: boolean;
anchorAlignment?: AnchorAlignment;
anchorAxisAlignment?: AnchorAxisAlignment;
domForShadowRoot?: HTMLElement;
}

View File

@ -661,6 +661,48 @@ export function isAncestor(testChild: Node | null, testAncestor: Node | null): b
return false;
}
const parentFlowToDataKey = 'parentFlowToElementId';
/**
* Set an explicit parent to use for nodes that are not part of the
* regular dom structure.
*/
export function setParentFlowTo(fromChildElement: HTMLElement, toParentElement: Element): void {
fromChildElement.dataset[parentFlowToDataKey] = toParentElement.id;
}
function getParentFlowToElement(node: HTMLElement): HTMLElement | null {
const flowToParentId = node.dataset[parentFlowToDataKey];
if (typeof flowToParentId === 'string') {
return document.getElementById(flowToParentId);
}
return null;
}
/**
* Check if `testAncestor` is an ancessor of `testChild`, observing the explicit
* parents set by `setParentFlowTo`.
*/
export function isAncestorUsingFlowTo(testChild: Node, testAncestor: Node): boolean {
let node: Node | null = testChild;
while (node) {
if (node === testAncestor) {
return true;
}
if (node instanceof HTMLElement) {
const flowToParentElement = getParentFlowToElement(node);
if (flowToParentElement) {
node = flowToParentElement;
continue;
}
}
node = node.parentNode;
}
return false;
}
export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClazzOrNode?: string | HTMLElement): HTMLElement | null {
while (node && node.nodeType === node.ELEMENT_NODE) {
if (node.classList.contains(clazz)) {
@ -1331,8 +1373,8 @@ export function safeInnerHtml(node: HTMLElement, value: string): void {
const options = _extInsaneOptions({
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
allowedAttributes: {
'a': ['href'],
'button': ['data-href'],
'a': ['href', 'x-dispatch'],
'button': ['data-href', 'x-dispatch'],
'input': ['type', 'placeholder', 'checked', 'required'],
'label': ['for'],
'select': ['required'],

View File

@ -4,11 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import * as platform from 'vs/base/common/platform';
import { IframeUtils } from 'vs/base/browser/iframe';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { BrowserFeatures } from 'vs/base/browser/canIUse';
export interface IStandardMouseMoveEventData {
leftButton: boolean;
@ -26,7 +24,7 @@ export interface IMouseMoveCallback<R> {
}
export interface IOnStopCallback {
(): void;
(browserEvent?: MouseEvent | KeyboardEvent): void;
}
export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData | null, currentEvent: MouseEvent): IStandardMouseMoveEventData {
@ -52,7 +50,7 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
this._hooks.dispose();
}
public stopMonitoring(invokeStopCallback: boolean): void {
public stopMonitoring(invokeStopCallback: boolean, browserEvent?: MouseEvent | KeyboardEvent): void {
if (!this.isMonitoring()) {
// Not monitoring
return;
@ -66,7 +64,7 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
this._onStopCallback = null;
if (invokeStopCallback && onStopCallback) {
onStopCallback();
onStopCallback(browserEvent);
}
}
@ -90,8 +88,8 @@ export class GlobalMouseMoveMonitor<R extends { buttons: number; }> implements I
this._onStopCallback = onStopCallback;
const windowChain = IframeUtils.getSameOriginWindowChain();
const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
const mouseMove = 'mousemove';
const mouseUp = 'mouseup';
const listenTo: (Document | ShadowRoot)[] = windowChain.map(element => element.window.document);
const shadowRoot = dom.getShadowRoot(initialElement);

View File

@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { VSBuffer } from 'vs/base/common/buffer';
import { StringSHA1, toHexString } from 'vs/base/common/hash';
export async function sha1Hex(str: string): Promise<string> {
// Prefer to use browser's crypto module
if (globalThis?.crypto?.subtle) {
const hash = await globalThis.crypto.subtle.digest({ name: 'sha-1' }, VSBuffer.fromString(str).buffer);
return toHexString(hash);
}
// Otherwise fallback to `StringSHA1`
else {
const computer = new StringSHA1();
computer.update(str);
return computer.digest();
}
}

View File

@ -28,7 +28,7 @@ export interface MarkedOptions extends marked.MarkedOptions {
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
codeBlockRenderer?: (modeId: string, value: string) => Promise<HTMLElement>;
codeBlockRenderCallback?: () => void;
asyncRenderCallback?: () => void;
baseUrl?: URI;
}
@ -177,8 +177,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
// ignore
});
if (options.codeBlockRenderCallback) {
promise.then(options.codeBlockRenderCallback);
if (options.asyncRenderCallback) {
promise.then(options.asyncRenderCallback);
}
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
@ -215,11 +215,15 @@ 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.
// We always pass the output through insane after this so that we don't rely on
// marked for sanitization.
markedOptions.sanitizer = (html: string): string => {
const match = markdown.isTrusted ? html.match(/^(<span[^<]+>)|(<\/\s*span>)$/) : undefined;
return match ? html : '';
};
markedOptions.sanitize = true;
markedOptions.silent = true;
markedOptions.renderer = renderer;
// values that are too long will freeze the UI
@ -240,6 +244,17 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
// signal that async code blocks can be now be inserted
signalInnerHTML!();
// signal size changes for image tags
if (options.asyncRenderCallback) {
for (const img of element.getElementsByTagName('img')) {
const listener = DOM.addDisposableListener(img, 'load', () => {
listener.dispose();
options.asyncRenderCallback!();
});
}
}
return element;
}
@ -301,3 +316,76 @@ function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOpti
};
}
/**
* Strips all markdown from `markdown`. For example `# Header` would be output as `Header`.
*/
export function renderMarkdownAsPlaintext(markdown: IMarkdownString) {
const renderer = new marked.Renderer();
renderer.code = (code: string): string => {
return code;
};
renderer.blockquote = (quote: string): string => {
return quote;
};
renderer.html = (_html: string): string => {
return '';
};
renderer.heading = (text: string, _level: 1 | 2 | 3 | 4 | 5 | 6, _raw: string): string => {
return text + '\n';
};
renderer.hr = (): string => {
return '';
};
renderer.list = (body: string, _ordered: boolean): string => {
return body;
};
renderer.listitem = (text: string): string => {
return text + '\n';
};
renderer.paragraph = (text: string): string => {
return text + '\n';
};
renderer.table = (header: string, body: string): string => {
return header + body + '\n';
};
renderer.tablerow = (content: string): string => {
return content;
};
renderer.tablecell = (content: string, _flags: {
header: boolean;
align: 'center' | 'left' | 'right' | null;
}): string => {
return content + ' ';
};
renderer.strong = (text: string): string => {
return text;
};
renderer.em = (text: string): string => {
return text;
};
renderer.codespan = (code: string): string => {
return code;
};
renderer.br = (): string => {
return '\n';
};
renderer.del = (text: string): string => {
return text;
};
renderer.image = (_href: string, _title: string, _text: string): string => {
return '';
};
renderer.text = (text: string): string => {
return text;
};
renderer.link = (_href: string, _title: string, text: string): string => {
return text;
};
// values that are too long will freeze the UI
let value = markdown.value ?? '';
if (value.length > 100_000) {
value = `${value.substr(0, 100_000)}`;
}
return sanitizeRenderedMarkdown({ isTrusted: false }, marked.parse(value, { renderer })).toString();
}

View File

@ -60,6 +60,9 @@ export class ActionBar extends Disposable implements IActionRunner {
protected focusedItem?: number;
private focusTracker: DOM.IFocusTracker;
// Trigger Key Tracking
private triggerKeyDown: boolean = false;
// Elements
domNode: HTMLElement;
protected actionsList: HTMLElement;
@ -74,8 +77,8 @@ export class ActionBar extends Disposable implements IActionRunner {
private _onDidRun = this._register(new Emitter<IRunEvent>());
readonly onDidRun = this._onDidRun.event;
private _onDidBeforeRun = this._register(new Emitter<IRunEvent>());
readonly onDidBeforeRun = this._onDidBeforeRun.event;
private _onBeforeRun = this._register(new Emitter<IRunEvent>());
readonly onBeforeRun = this._onBeforeRun.event;
constructor(container: HTMLElement, options: IActionBarOptions = {}) {
super();
@ -96,7 +99,7 @@ export class ActionBar extends Disposable implements IActionRunner {
}
this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e)));
this._register(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e)));
this._register(this._actionRunner.onBeforeRun(e => this._onBeforeRun.fire(e)));
this._actionIds = [];
this.viewItems = [];
@ -148,6 +151,8 @@ export class ActionBar extends Disposable implements IActionRunner {
// Staying out of the else branch even if not triggered
if (this._triggerKeys.keyDown) {
this.doTrigger(event);
} else {
this.triggerKeyDown = true;
}
} else {
eventHandled = false;
@ -164,7 +169,8 @@ export class ActionBar extends Disposable implements IActionRunner {
// Run action on Enter/Space
if (this.isTriggerKeyEvent(event)) {
if (!this._triggerKeys.keyDown) {
if (!this._triggerKeys.keyDown && this.triggerKeyDown) {
this.triggerKeyDown = false;
this.doTrigger(event);
}
@ -183,6 +189,7 @@ export class ActionBar extends Disposable implements IActionRunner {
if (DOM.getActiveElement() === this.domNode || !DOM.isAncestor(DOM.getActiveElement(), this.domNode)) {
this._onDidBlur.fire();
this.focusedItem = undefined;
this.triggerKeyDown = false;
}
}));

View File

@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
import 'vs/css!./breadcrumbsWidget';
export abstract class BreadcrumbsItem {
@ -56,7 +56,7 @@ export interface IBreadcrumbsItemEvent {
payload: any;
}
const breadcrumbSeparatorIcon = registerIcon('breadcrumb-separator', Codicon.chevronRight);
const breadcrumbSeparatorIcon = registerCodicon('breadcrumb-separator', Codicon.chevronRight);
export class BreadcrumbsWidget {

View File

@ -10,7 +10,6 @@
padding: 4px;
text-align: center;
cursor: pointer;
outline-offset: 2px !important;
justify-content: center;
align-items: center;
}
@ -24,7 +23,15 @@
cursor: default;
}
.monaco-button > .codicon {
.monaco-text-button > .codicon {
margin: 0 0.2em;
color: inherit !important;
}
.monaco-button-dropdown {
display: flex;
}
.monaco-button-dropdown > .monaco-dropdown-button {
margin-left: 1px;
}

View File

@ -9,10 +9,13 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects';
import { Event as BaseEvent, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';
import { renderCodicons } from 'vs/base/browser/codicons';
import { addDisposableListener, IFocusTracker, EventType, EventHelper, trackFocus, reset, removeTabIndexAndUpdateFocus } from 'vs/base/browser/dom';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { IAction, IActionRunner } from 'vs/base/common/actions';
import { CSSIcon, Codicon } from 'vs/base/common/codicons';
export interface IButtonOptions extends IButtonStyles {
readonly title?: boolean | string;
@ -36,7 +39,18 @@ const defaultOptions: IButtonStyles = {
buttonForeground: Color.white
};
export class Button extends Disposable {
export interface IButton extends IDisposable {
readonly element: HTMLElement;
readonly onDidClick: BaseEvent<Event>;
label: string;
icon: CSSIcon;
enabled: boolean;
style(styles: IButtonStyles): void;
focus(): void;
hasFocus(): boolean;
}
export class Button extends Disposable implements IButton {
private _element: HTMLElement;
private options: IButtonOptions;
@ -188,8 +202,8 @@ export class Button extends Disposable {
}
}
set icon(iconClassName: string) {
this._element.classList.add(iconClassName);
set icon(icon: CSSIcon) {
this._element.classList.add(...icon.classNames.split(' '));
}
set enabled(value: boolean) {
@ -217,47 +231,124 @@ export class Button extends Disposable {
}
}
export class ButtonGroup extends Disposable {
private _buttons: Button[] = [];
export interface IButtonWithDropdownOptions extends IButtonOptions {
readonly contextMenuProvider: IContextMenuProvider;
readonly actions: IAction[];
readonly actionRunner?: IActionRunner;
}
constructor(container: HTMLElement, count: number, options?: IButtonOptions) {
export class ButtonWithDropdown extends Disposable implements IButton {
private readonly button: Button;
private readonly dropdownButton: Button;
readonly element: HTMLElement;
readonly onDidClick: BaseEvent<Event>;
constructor(container: HTMLElement, options: IButtonWithDropdownOptions) {
super();
this.create(container, count, options);
this.element = document.createElement('div');
this.element.classList.add('monaco-button-dropdown');
container.appendChild(this.element);
this.button = this._register(new Button(this.element, options));
this.onDidClick = this.button.onDidClick;
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportCodicons: true }));
this.dropdownButton.element.classList.add('monaco-dropdown-button');
this.dropdownButton.icon = Codicon.dropDownButton;
this._register(this.dropdownButton.onDidClick(() => {
options.contextMenuProvider.showContextMenu({
getAnchor: () => this.dropdownButton.element,
getActions: () => options.actions,
actionRunner: options.actionRunner,
onHide: () => this.dropdownButton.element.setAttribute('aria-expanded', 'false')
});
this.dropdownButton.element.setAttribute('aria-expanded', 'true');
}));
}
get buttons(): Button[] {
set label(value: string) {
this.button.label = value;
}
set icon(icon: CSSIcon) {
this.button.icon = icon;
}
set enabled(enabled: boolean) {
this.button.enabled = enabled;
this.dropdownButton.enabled = enabled;
}
get enabled(): boolean {
return this.button.enabled;
}
style(styles: IButtonStyles): void {
this.button.style(styles);
this.dropdownButton.style(styles);
}
focus(): void {
this.button.focus();
}
hasFocus(): boolean {
return this.button.hasFocus() || this.dropdownButton.hasFocus();
}
}
export class ButtonBar extends Disposable {
private _buttons: IButton[] = [];
constructor(private readonly container: HTMLElement) {
super();
}
get buttons(): IButton[] {
return this._buttons;
}
private create(container: HTMLElement, count: number, options?: IButtonOptions): void {
for (let index = 0; index < count; index++) {
const button = this._register(new Button(container, options));
this._buttons.push(button);
// Implement keyboard access in buttons if there are multiple
if (count > 1) {
this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
// Next / Previous Button
let buttonIndexToFocus: number | undefined;
if (event.equals(KeyCode.LeftArrow)) {
buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1;
} else if (event.equals(KeyCode.RightArrow)) {
buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1;
} else {
eventHandled = false;
}
if (eventHandled && typeof buttonIndexToFocus === 'number') {
this._buttons[buttonIndexToFocus].focus();
EventHelper.stop(e, true);
}
}));
}
}
addButton(options?: IButtonOptions): IButton {
const button = this._register(new Button(this.container, options));
this.pushButton(button);
return button;
}
addButtonWithDropdown(options: IButtonWithDropdownOptions): IButton {
const button = this._register(new ButtonWithDropdown(this.container, options));
this.pushButton(button);
return button;
}
private pushButton(button: IButton): void {
this._buttons.push(button);
const index = this._buttons.length - 1;
this._register(addDisposableListener(button.element, EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
let eventHandled = true;
// Next / Previous Button
let buttonIndexToFocus: number | undefined;
if (event.equals(KeyCode.LeftArrow)) {
buttonIndexToFocus = index > 0 ? index - 1 : this._buttons.length - 1;
} else if (event.equals(KeyCode.RightArrow)) {
buttonIndexToFocus = index === this._buttons.length - 1 ? 0 : index + 1;
} else {
eventHandled = false;
}
if (eventHandled && typeof buttonIndexToFocus === 'number') {
this._buttons[buttonIndexToFocus].focus();
EventHelper.stop(e, true);
}
}));
}
}

View File

@ -11,12 +11,12 @@ import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Codicon } from 'vs/base/common/codicons';
import { Codicon, CSSIcon } from 'vs/base/common/codicons';
import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
export interface ICheckboxOpts extends ICheckboxStyles {
readonly actionClassName?: string;
readonly icon?: Codicon;
readonly icon?: CSSIcon;
readonly title: string;
readonly isChecked: boolean;
}

View File

@ -7,18 +7,7 @@ import 'vs/css!./codicon/codicon';
import 'vs/css!./codicon/codicon-modifications';
import 'vs/css!./codicon/codicon-animations';
import { Codicon, iconRegistry } from 'vs/base/common/codicons';
export const CodiconStyles = new class {
onDidChange = iconRegistry.onDidRegister;
public getCSS(): string {
const rules = [];
for (let c of iconRegistry.all) {
rules.push(formatRule(c));
}
return rules.join('\n');
}
};
import { Codicon } from 'vs/base/common/codicons';
export function formatRule(c: Codicon) {
let def = c.definition;

View File

@ -31,6 +31,10 @@ export const enum AnchorPosition {
BELOW, ABOVE
}
export const enum AnchorAxisAlignment {
VERTICAL, HORIZONTAL
}
export interface IDelegate {
getAnchor(): HTMLElement | IAnchor;
render(container: HTMLElement): IDisposable | null;
@ -38,6 +42,7 @@ export interface IDelegate {
layout?(): void;
anchorAlignment?: AnchorAlignment; // default: left
anchorPosition?: AnchorPosition; // default: below
anchorAxisAlignment?: AnchorAxisAlignment; // default: vertical
canRelayout?: boolean; // default: true
onDOMEvent?(e: Event, activeElement: HTMLElement): void;
onHide?(data?: any): void;
@ -66,9 +71,15 @@ export const enum LayoutAnchorPosition {
After
}
export enum LayoutAnchorMode {
AVOID,
ALIGN
}
export interface ILayoutAnchor {
offset: number;
size: number;
mode?: LayoutAnchorMode; // default: AVOID
position: LayoutAnchorPosition;
}
@ -78,25 +89,26 @@ export interface ILayoutAnchor {
* @returns The view offset within the viewport.
*/
export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): number {
const anchorEnd = anchor.offset + anchor.size;
const layoutAfterAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset : anchor.offset + anchor.size;
const layoutBeforeAnchorBoundary = anchor.mode === LayoutAnchorMode.ALIGN ? anchor.offset + anchor.size : anchor.offset;
if (anchor.position === LayoutAnchorPosition.Before) {
if (viewSize <= viewportSize - anchorEnd) {
return anchorEnd; // happy case, lay it out after the anchor
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
return layoutAfterAnchorBoundary; // happy case, lay it out after the anchor
}
if (viewSize <= anchor.offset) {
return anchor.offset - viewSize; // ok case, lay it out before the anchor
if (viewSize <= layoutBeforeAnchorBoundary) {
return layoutBeforeAnchorBoundary - viewSize; // ok case, lay it out before the anchor
}
return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor
} else {
if (viewSize <= anchor.offset) {
return anchor.offset - viewSize; // happy case, lay it out before the anchor
if (viewSize <= layoutBeforeAnchorBoundary) {
return layoutBeforeAnchorBoundary - viewSize; // happy case, lay it out before the anchor
}
if (viewSize <= viewportSize - anchorEnd) {
return anchorEnd; // ok case, lay it out after the anchor
if (viewSize <= viewportSize - layoutAfterAnchorBoundary) {
return layoutAfterAnchorBoundary; // ok case, lay it out after the anchor
}
return 0; // sad case, lay it over the anchor
@ -270,28 +282,36 @@ export class ContextView extends Disposable {
const anchorPosition = this.delegate!.anchorPosition || AnchorPosition.BELOW;
const anchorAlignment = this.delegate!.anchorAlignment || AnchorAlignment.LEFT;
const anchorAxisAlignment = this.delegate!.anchorAxisAlignment || AnchorAxisAlignment.VERTICAL;
const verticalAnchor: ILayoutAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
let top: number;
let left: number;
let horizontalAnchor: ILayoutAnchor;
if (anchorAxisAlignment === AnchorAxisAlignment.VERTICAL) {
const verticalAnchor: ILayoutAnchor = { offset: around.top - window.pageYOffset, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
if (anchorAlignment === AnchorAlignment.LEFT) {
horizontalAnchor = { offset: around.left, size: 0, position: LayoutAnchorPosition.Before };
} else {
horizontalAnchor = { offset: around.left + around.width, size: 0, position: LayoutAnchorPosition.After };
}
top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
const top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
// if view intersects vertically with anchor, shift it horizontally
if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
horizontalAnchor.size = around.width;
if (anchorAlignment === AnchorAlignment.RIGHT) {
horizontalAnchor.offset = around.left;
// if view intersects vertically with anchor, we must avoid the anchor
if (Range.intersects({ start: top, end: top + viewSizeHeight }, { start: verticalAnchor.offset, end: verticalAnchor.offset + verticalAnchor.size })) {
horizontalAnchor.mode = LayoutAnchorMode.AVOID;
}
}
const left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
} else {
const horizontalAnchor: ILayoutAnchor = { offset: around.left, size: around.width, position: anchorAlignment === AnchorAlignment.LEFT ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After };
const verticalAnchor: ILayoutAnchor = { offset: around.top, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After, mode: LayoutAnchorMode.ALIGN };
left = layout(window.innerWidth, viewSizeWidth, horizontalAnchor);
// if view intersects horizontally with anchor, we must avoid the anchor
if (Range.intersects({ start: left, end: left + viewSizeWidth }, { start: horizontalAnchor.offset, end: horizontalAnchor.offset + horizontalAnchor.size })) {
verticalAnchor.mode = LayoutAnchorMode.AVOID;
}
top = layout(window.innerHeight, viewSizeHeight, verticalAnchor) + window.pageYOffset;
}
this.view.classList.remove('top', 'bottom', 'left', 'right');
this.view.classList.add(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top');

View File

@ -148,7 +148,8 @@
width: fit-content;
width: -moz-fit-content;
padding: 5px 10px;
margin: 4px 5px; /* allows button focus outline to be visible */
overflow: hidden;
text-overflow: ellipsis;
margin: 4px 5px; /* allows button focus outline to be visible */
outline-offset: 2px !important;
}

View File

@ -11,13 +11,13 @@ import { domEvent } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Color } from 'vs/base/common/color';
import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button';
import { ButtonBar, IButtonStyles } from 'vs/base/browser/ui/button/button';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { isMacintosh, isLinux } from 'vs/base/common/platform';
import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
export interface IDialogInputOptions {
@ -60,10 +60,10 @@ interface ButtonMapEntry {
readonly index: number;
}
const dialogErrorIcon = registerIcon('dialog-error', Codicon.error);
const dialogWarningIcon = registerIcon('dialog-warning', Codicon.warning);
const dialogInfoIcon = registerIcon('dialog-info', Codicon.info);
const dialogCloseIcon = registerIcon('dialog-close', Codicon.close);
const dialogErrorIcon = registerCodicon('dialog-error', Codicon.error);
const dialogWarningIcon = registerCodicon('dialog-warning', Codicon.warning);
const dialogInfoIcon = registerCodicon('dialog-info', Codicon.info);
const dialogCloseIcon = registerCodicon('dialog-close', Codicon.close);
export class Dialog extends Disposable {
private readonly element: HTMLElement;
@ -74,7 +74,7 @@ export class Dialog extends Disposable {
private readonly iconElement: HTMLElement;
private readonly checkbox: SimpleCheckbox | undefined;
private readonly toolbarContainer: HTMLElement;
private buttonGroup: ButtonGroup | undefined;
private buttonBar: ButtonBar | undefined;
private styles: IDialogStyles | undefined;
private focusToReturn: HTMLElement | undefined;
private readonly inputs: InputBox[];
@ -173,11 +173,12 @@ export class Dialog extends Disposable {
return new Promise<IDialogResult>((resolve) => {
clearNode(this.buttonsContainer);
const buttonGroup = this.buttonGroup = this._register(new ButtonGroup(this.buttonsContainer, this.buttons.length, { title: true }));
const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer));
const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId);
// Handle button clicks
buttonGroup.buttons.forEach((button, index) => {
buttonMap.forEach((entry, index) => {
const button = this._register(buttonBar.addButton({ title: true }));
button.label = mnemonicButtonLabel(buttonMap[index].label, true);
this._register(button.onDidClick(e => {
@ -237,8 +238,8 @@ export class Dialog extends Disposable {
}
}
if (this.buttonGroup) {
for (const button of this.buttonGroup.buttons) {
if (this.buttonBar) {
for (const button of this.buttonBar.buttons) {
focusableElements.push(button);
if (button.hasFocus()) {
focusedIndex = focusableElements.length - 1;
@ -349,7 +350,7 @@ export class Dialog extends Disposable {
} else {
buttonMap.forEach((value, index) => {
if (value.index === 0) {
buttonGroup.buttons[index].focus();
buttonBar.buttons[index].focus();
}
});
}
@ -371,8 +372,8 @@ export class Dialog extends Disposable {
this.element.style.backgroundColor = bgColor?.toString() ?? '';
this.element.style.border = border;
if (this.buttonGroup) {
this.buttonGroup.buttons.forEach(button => button.style(style));
if (this.buttonBar) {
this.buttonBar.buttons.forEach(button => button.style(style));
}
if (this.checkbox) {

View File

@ -12,3 +12,7 @@
cursor: pointer;
height: 100%;
}
.monaco-dropdown > .dropdown-label > .action-label.disabled {
cursor: default;
}

View File

@ -13,6 +13,7 @@ import { Emitter } from 'vs/base/common/event';
import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { IActionProvider, DropdownMenu, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { Codicon } from 'vs/base/common/codicons';
export interface IKeybindingProvider {
(action: IAction): ResolvedKeybinding | undefined;
@ -35,6 +36,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
private menuActionsOrProvider: readonly IAction[] | IActionProvider;
private dropdownMenu: DropdownMenu | undefined;
private contextMenuProvider: IContextMenuProvider;
private actionItem: HTMLElement | null = null;
private _onDidChangeVisibility = this._register(new Emitter<boolean>());
readonly onDidChangeVisibility = this._onDidChangeVisibility.event;
@ -56,6 +58,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
}
render(container: HTMLElement): void {
this.actionItem = container;
const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => {
this.element = append(el, $('a.action-label'));
@ -115,6 +119,8 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
}
};
}
this.updateEnabled();
}
setActionContext(newContext: unknown): void {
@ -134,6 +140,12 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem {
this.dropdownMenu.show();
}
}
protected updateEnabled(): void {
const disabled = !this.getAction().enabled;
this.actionItem?.classList.toggle('disabled', disabled);
this.element?.classList.toggle('disabled', disabled);
}
}
export interface IActionWithDropdownActionViewItemOptions extends IActionViewItemOptions {
@ -158,7 +170,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem {
super.render(container);
if (this.element) {
this.element.classList.add('action-dropdown-item');
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(new Action('dropdownAction', undefined), (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider, this.contextMenuProvider, { classNames: ['dropdown', 'codicon-chevron-down', ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(new Action('dropdownAction', undefined), (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });
this.dropdownMenuActionViewItem.render(this.element);
}
}

View File

@ -175,7 +175,7 @@ function getGridLocation(element: HTMLElement): number[] {
}
const index = indexInParent(parentElement);
const ancestor = parentElement.parentElement!.parentElement!.parentElement!;
const ancestor = parentElement.parentElement!.parentElement!.parentElement!.parentElement!;
return [...getGridLocation(ancestor), index];
}
@ -215,6 +215,8 @@ export class Grid<T extends IView = IView> extends Disposable {
get boundarySashes(): IBoundarySashes { return this.gridview.boundarySashes; }
set boundarySashes(boundarySashes: IBoundarySashes) { this.gridview.boundarySashes = boundarySashes; }
set edgeSnapping(edgeSnapping: boolean) { this.gridview.edgeSnapping = edgeSnapping; }
get element(): HTMLElement { return this.gridview.element; }
private didLayout = false;

View File

@ -170,6 +170,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
private absoluteOffset: number = 0;
private absoluteOrthogonalOffset: number = 0;
private absoluteOrthogonalSize: number = 0;
private _styles: IGridViewStyles;
get styles(): IGridViewStyles { return this._styles; }
@ -270,6 +271,24 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
}
}
private _edgeSnapping = false;
get edgeSnapping(): boolean { return this._edgeSnapping; }
set edgeSnapping(edgeSnapping: boolean) {
if (this._edgeSnapping === edgeSnapping) {
return;
}
this._edgeSnapping = edgeSnapping;
for (const child of this.children) {
if (child instanceof BranchNode) {
child.edgeSnapping = edgeSnapping;
}
}
this.updateSplitviewEdgeSnappingEnablement();
}
constructor(
readonly orientation: Orientation,
readonly layoutController: ILayoutController,
@ -277,6 +296,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
readonly proportionalLayout: boolean,
size: number = 0,
orthogonalSize: number = 0,
edgeSnapping: boolean = false,
childDescriptors?: INodeDescriptor[]
) {
this._styles = styles;
@ -355,6 +375,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
this._orthogonalSize = size;
this.absoluteOffset = ctx.absoluteOffset + offset;
this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset;
this.absoluteOrthogonalSize = ctx.absoluteOrthogonalSize;
this.splitview.layout(ctx.orthogonalSize, {
orthogonalSize: size,
@ -364,9 +385,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
absoluteOrthogonalSize: ctx.absoluteSize
});
// Disable snapping on views which sit on the edges of the grid
this.splitview.startSnappingEnabled = this.absoluteOrthogonalOffset > 0;
this.splitview.endSnappingEnabled = this.absoluteOrthogonalOffset + ctx.orthogonalSize < ctx.absoluteOrthogonalSize;
this.updateSplitviewEdgeSnappingEnablement();
}
setVisible(visible: boolean): void {
@ -607,6 +626,11 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable {
});
}
private updateSplitviewEdgeSnappingEnablement(): void {
this.splitview.startSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset > 0;
this.splitview.endSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset + this._size < this.absoluteOrthogonalSize;
}
dispose(): void {
for (const child of this.children) {
child.dispose();
@ -775,7 +799,7 @@ export interface INodeDescriptor {
function flipNode<T extends Node>(node: T, size: number, orthogonalSize: number): T {
if (node instanceof BranchNode) {
const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize);
const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.proportionalLayout, size, orthogonalSize, node.edgeSnapping);
let totalSize = 0;
@ -863,6 +887,10 @@ export class GridView implements IDisposable {
this.root.boundarySashes = fromAbsoluteBoundarySashes(boundarySashes, this.orientation);
}
set edgeSnapping(edgeSnapping: boolean) {
this.root.edgeSnapping = edgeSnapping;
}
/**
* The first layout controller makes sure layout only propagates
* to the views after the very first call to gridview.layout()
@ -932,7 +960,7 @@ export class GridView implements IDisposable {
grandParent.removeChild(parentIndex);
const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize);
const newParent = new BranchNode(parent.orientation, parent.layoutController, this.styles, this.proportionalLayout, parent.size, parent.orthogonalSize, grandParent.edgeSnapping);
grandParent.addChild(newParent, parent.size, parentIndex);
const newSibling = new LeafNode(parent.view, grandParent.orientation, this.layoutController, parent.size);
@ -1205,7 +1233,7 @@ export class GridView implements IDisposable {
} as INodeDescriptor;
});
result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, children);
result = new BranchNode(orientation, this.layoutController, this.styles, this.proportionalLayout, node.size, orthogonalSize, undefined, children);
} else {
result = new LeafNode(deserializer.fromJSON(node.data), orientation, this.layoutController, orthogonalSize, node.size);
}

View File

@ -87,7 +87,6 @@
.monaco-hover .monaco-tokenized-source {
white-space: pre-wrap;
word-break: break-all;
}
.monaco-hover .hover-row.status-bar {

View File

@ -14,7 +14,7 @@ import { isMacintosh } from 'vs/base/common/platform';
import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { isString } from 'vs/base/common/types';
import { isFunction, isString } from 'vs/base/common/types';
import { domEvent } from 'vs/base/browser/event';
export interface IIconLabelCreationOptions {
@ -24,8 +24,13 @@ export interface IIconLabelCreationOptions {
hoverDelegate?: IHoverDelegate;
}
export interface IIconLabelMarkdownString {
markdown: IMarkdownString | string | undefined | (() => Promise<IMarkdownString | string | undefined>);
markdownNotSupportedFallback: string | undefined;
}
export interface IIconLabelValueOptions {
title?: string | IMarkdownString | Promise<IMarkdownString | string | undefined>;
title?: string | IIconLabelMarkdownString;
descriptionTitle?: string;
hideIcon?: boolean;
extraClasses?: string[];
@ -93,6 +98,8 @@ export class IconLabel extends Disposable {
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
private labelContainer: HTMLElement;
private hoverDelegate: IHoverDelegate | undefined = undefined;
private readonly customHovers: Map<HTMLElement, IDisposable> = new Map();
@ -101,10 +108,10 @@ export class IconLabel extends Disposable {
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
this.labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
const nameContainer = dom.append(this.labelContainer, dom.$('span.monaco-icon-name-container'));
this.descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container'))));
if (options?.supportHighlights) {
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
@ -144,7 +151,7 @@ export class IconLabel extends Disposable {
}
this.domNode.className = classes.join(' ');
this.setupHover(this.domNode.element, options?.title);
this.setupHover(this.labelContainer, options?.title);
this.nameNode.setLabel(label, options);
@ -164,7 +171,7 @@ export class IconLabel extends Disposable {
}
}
private setupHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
private setupHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void {
const previousCustomHover = this.customHovers.get(htmlElement);
if (previousCustomHover) {
previousCustomHover.dispose();
@ -183,22 +190,39 @@ export class IconLabel extends Disposable {
}
}
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
private setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, markdownTooltip: string | IIconLabelMarkdownString): void {
htmlElement.removeAttribute('title');
let tooltip: () => Promise<string | IMarkdownString | undefined>;
if (isString(markdownTooltip)) {
tooltip = async () => markdownTooltip;
} else if (isFunction(markdownTooltip.markdown)) {
tooltip = markdownTooltip.markdown;
} else {
const markdown = markdownTooltip.markdown;
tooltip = async () => markdown;
}
// Testing has indicated that on Windows and Linux 500 ms matches the native hovers most closely.
// On Mac, the delay is 1500.
const hoverDelay = isMacintosh ? 1500 : 500;
let hoverOptions: IHoverDelegateOptions | undefined;
let mouseX: number | undefined;
let isHovering = false;
function mouseOver(this: HTMLElement, e: MouseEvent): any {
let isHovering = true;
if (isHovering) {
return;
}
function mouseLeaveOrDown(this: HTMLElement, e: MouseEvent): any {
isHovering = false;
mouseLeaveDisposable.dispose();
mouseDownDisposable.dispose();
}
const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeaveOrDown.bind(htmlElement));
const mouseDownDisposable = domEvent(htmlElement, dom.EventType.MOUSE_DOWN, true)(mouseLeaveOrDown.bind(htmlElement));
isHovering = true;
function mouseMove(this: HTMLElement, e: MouseEvent): any {
mouseX = e.x;
}
function mouseLeave(this: HTMLElement, e: MouseEvent): any {
isHovering = false;
}
const mouseLeaveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_LEAVE, true)(mouseLeave.bind(htmlElement));
const mouseMoveDisposable = domEvent(htmlElement, dom.EventType.MOUSE_MOVE, true)(mouseMove.bind(htmlElement));
setTimeout(async () => {
if (isHovering && tooltip) {
@ -208,7 +232,7 @@ export class IconLabel extends Disposable {
targetElements: [this],
dispose: () => { }
};
const resolvedTooltip = await tooltip;
const resolvedTooltip = await tooltip();
if (resolvedTooltip) {
hoverOptions = {
text: resolvedTooltip,
@ -217,7 +241,8 @@ export class IconLabel extends Disposable {
};
}
}
if (hoverOptions) {
// awaiting the tooltip could take a while. Make sure we're still hovering.
if (hoverOptions && isHovering) {
if (mouseX !== undefined) {
(<IHoverDelegateTarget>hoverOptions.target).x = mouseX + 10;
}
@ -225,15 +250,20 @@ export class IconLabel extends Disposable {
}
}
mouseMoveDisposable.dispose();
mouseLeaveDisposable.dispose();
}, hoverDelay);
}
const mouseOverDisposable = this._register(domEvent(htmlElement, dom.EventType.MOUSE_OVER, true)(mouseOver.bind(htmlElement)));
this.customHovers.set(htmlElement, mouseOverDisposable);
}
private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IMarkdownString | Promise<IMarkdownString | string | undefined> | undefined): void {
htmlElement.title = isString(tooltip) ? tooltip : '';
private setupNativeHover(htmlElement: HTMLElement, tooltip: string | IIconLabelMarkdownString | undefined): void {
let stringTooltip: string = '';
if (isString(tooltip)) {
stringTooltip = tooltip;
} else if (tooltip?.markdownNotSupportedFallback) {
stringTooltip = tooltip.markdownNotSupportedFallback;
}
htmlElement.title = stringTooltip;
}
}

View File

@ -60,12 +60,12 @@
}
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
font-style: italic;
}
.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
.monaco-icon-label.strikethrough > .monaco-icon-description-container > .label-description {
.monaco-icon-label.strikethrough > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
text-decoration: line-through;
}

View File

@ -258,6 +258,10 @@ export class PagedList<T> implements IThemable, IDisposable {
return this.list.getSelection();
}
getSelectedElements(): T[] {
return this.getSelection().map(i => this.model.get(i));
}
layout(height?: number, width?: number): void {
this.list.layout(height, width);
}

View File

@ -327,7 +327,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.scrollable = new Scrollable(getOrDefault(options, o => o.smoothScrolling, false) ? 125 : 0, cb => scheduleAtNextAnimationFrame(cb));
this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, {
alwaysConsumeMouseWheel: true,
horizontal: ScrollbarVisibility.Auto,
vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode),
useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows),
@ -1323,6 +1322,9 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
if (item.row) {
const renderer = this.renderers.get(item.row.templateId);
if (renderer) {
if (renderer.disposeElement) {
renderer.disposeElement(item.element, -1, item.row.templateData, undefined);
}
renderer.disposeTemplate(item.row.templateData);
}
}

View File

@ -318,10 +318,12 @@ class KeyboardController<T> implements IDisposable {
}
private onEscape(e: StandardKeyboardEvent): void {
e.preventDefault();
e.stopPropagation();
this.list.setSelection([], e.browserEvent);
this.view.domNode.focus();
if (this.list.getSelection().length) {
e.preventDefault();
e.stopPropagation();
this.list.setSelection([], e.browserEvent);
this.view.domNode.focus();
}
}
dispose() {
@ -1460,6 +1462,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.view.setScrollTop(previousScrollTop + this.view.renderHeight - this.view.elementHeight(lastPageIndex));
if (this.view.getScrollTop() !== previousScrollTop) {
this.setFocus([]);
// Let the scroll event listener run
setTimeout(() => this.focusNextPage(browserEvent, filter), 0);
}
@ -1492,6 +1496,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.view.setScrollTop(scrollTop - this.view.renderHeight);
if (this.view.getScrollTop() !== previousScrollTop) {
this.setFocus([]);
// Let the scroll event listener run
setTimeout(() => this.focusPreviousPage(browserEvent, filter), 0);
}

View File

@ -18,7 +18,7 @@ import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
import { Event } from 'vs/base/common/event';
import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons';
import { Codicon, registerCodicon, stripCodicons } from 'vs/base/common/codicons';
import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles';
import { isFirefox } from 'vs/base/browser/browser';
@ -27,8 +27,8 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&amp;)?(&amp;)([^\s&])/g;
const menuSelectionIcon = registerIcon('menu-selection', Codicon.check);
const menuSubmenuIcon = registerIcon('menu-submenu', Codicon.chevronRight);
const menuSelectionIcon = registerCodicon('menu-selection', Codicon.check);
const menuSubmenuIcon = registerCodicon('menu-submenu', Codicon.chevronRight);
export enum Direction {
Right,
@ -728,6 +728,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
if (this.item) {
this.item.classList.add('monaco-submenu-item');
this.item.tabIndex = 0;
this.item.setAttribute('aria-haspopup', 'true');
this.updateAriaExpanded('false');
this.submenuIndicator = append(this.item, $('span.submenu-indicator' + menuSubmenuIcon.cssSelector));
@ -777,6 +778,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem {
}));
}
updateEnabled(): void {
// override on submenu entry
// native menus do not observe enablement on sumbenus
// we mimic that behavior
}
open(selectFirst?: boolean): void {
this.cleanupExistingSubmenu(false);
this.createSubmenu(selectFirst);
@ -1189,6 +1196,7 @@ ${formatRule(menuSubmenuIcon)}
outline: 0;
border: none;
animation: fadeIn 0.083s linear;
-webkit-app-region: no-drag;
}
.context-view.monaco-menu-container :focus,

View File

@ -21,11 +21,11 @@ import { asArray } from 'vs/base/common/arrays';
import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode';
import { isMacintosh } from 'vs/base/common/platform';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
const $ = DOM.$;
const menuBarMoreIcon = registerIcon('menubar-more', Codicon.more);
const menuBarMoreIcon = registerCodicon('menubar-more', Codicon.more);
export interface IMenuBarOptions {
enableMnemonics?: boolean;
@ -115,7 +115,7 @@ export class MenuBar extends Disposable {
this.menuUpdater = this._register(new RunOnceScheduler(() => this.update(), 200));
this.actionRunner = this._register(new ActionRunner());
this._register(this.actionRunner.onDidBeforeRun(() => {
this._register(this.actionRunner.onBeforeRun(() => {
this.setUnfocusedState();
}));

View File

@ -42,7 +42,10 @@
* The progress bit has a width: 2% (1/50) of the parent container. The animation moves it from 0% to 100% of
* that container. Since translateX is relative to the progress bit size, we have to multiple it with
* its relative size to the parent container:
* 50%: 50 * 50 = 2500%
* 100%: 50 * 100 - 50 (do not overflow): 4950%
* parent width: 5000%
* bit width: 100%
* translateX should be as follow:
* 50%: 5000% * 50% - 50% (set to center) = 2450%
* 100%: 5000% * 100% - 100% (do not overflow) = 4900%
*/
@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4950%) scaleX(1) } }
@keyframes progress { from { transform: translateX(0%) scaleX(1) } 50% { transform: translateX(2500%) scaleX(3) } to { transform: translateX(4900%) scaleX(1) } }

View File

@ -58,6 +58,7 @@ export class ProgressBar extends Disposable {
this.element = document.createElement('div');
this.element.classList.add('monaco-progress-container');
this.element.setAttribute('role', 'progressbar');
this.element.setAttribute('aria-valuemin', '0');
container.appendChild(this.element);
this.bit = document.createElement('div');

View File

@ -60,19 +60,48 @@
height: var(--sash-size);
}
.monaco-sash:not(.disabled).orthogonal-start::before, .monaco-sash:not(.disabled).orthogonal-end::after {
content: ' ';
.monaco-sash:not(.disabled).orthogonal-start::before,
.monaco-sash:not(.disabled).orthogonal-end::after {
content: " ";
height: calc(var(--sash-size) * 2);
width: calc(var(--sash-size) * 2);
z-index: 100;
display: block;
cursor: all-scroll; position: absolute;
cursor: all-scroll;
position: absolute;
}
.monaco-sash.orthogonal-start.vertical::before { left: -calc(var(--sash-size) / 2); top: calc(var(--sash-size) * -1); }
.monaco-sash.orthogonal-end.vertical::after { left: -calc(var(--sash-size) / 2); bottom: calc(var(--sash-size) * -1); }
.monaco-sash.orthogonal-start.horizontal::before { top: -calc(var(--sash-size) / 2); left: calc(var(--sash-size) * -1); }
.monaco-sash.orthogonal-end.horizontal::after { top: -calc(var(--sash-size) / 2); right: calc(var(--sash-size) * -1); }
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-start::before,
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-end::after {
cursor: nwse-resize;
}
.monaco-sash.horizontal.orthogonal-edge-north:not(.disabled).orthogonal-end::after,
.monaco-sash.horizontal.orthogonal-edge-south:not(.disabled).orthogonal-start::before {
cursor: nesw-resize;
}
.monaco-sash.orthogonal-start.vertical::before {
left: -calc(var(--sash-size) / 2);
top: calc(var(--sash-size) * -1);
}
.monaco-sash.orthogonal-end.vertical::after {
left: -calc(var(--sash-size) / 2);
bottom: calc(var(--sash-size) * -1);
}
.monaco-sash.orthogonal-start.horizontal::before {
top: -calc(var(--sash-size) / 2);
left: calc(var(--sash-size) * -1);
}
.monaco-sash.orthogonal-end.horizontal::after {
top: -calc(var(--sash-size) / 2);
right: calc(var(--sash-size) * -1);
}
.monaco-sash {
transition: background-color 0.1s ease-out;
background: transparent;
}
/** Debug **/

View File

@ -37,11 +37,19 @@ export interface ISashEvent {
altKey: boolean;
}
export enum OrthogonalEdge {
North = 'north',
South = 'south',
East = 'east',
West = 'west'
}
export interface ISashOptions {
readonly orientation: Orientation;
readonly orthogonalStartSash?: Sash;
readonly orthogonalEndSash?: Sash;
readonly size?: number;
readonly orthogonalEdge?: OrthogonalEdge;
}
export interface IVerticalSashOptions extends ISashOptions {
@ -150,6 +158,10 @@ export class Sash extends Disposable {
this.el = append(container, $('.monaco-sash'));
if (options.orthogonalEdge) {
this.el.classList.add(`orthogonal-edge-${options.orthogonalEdge}`);
}
if (isMacintosh) {
this.el.classList.add('mac');
}

View File

@ -38,12 +38,14 @@ export interface AbstractScrollbarOptions {
visibility: ScrollbarVisibility;
extraScrollbarClassName: string;
scrollable: Scrollable;
scrollByPage: boolean;
}
export abstract class AbstractScrollbar extends Widget {
protected _host: ScrollbarHost;
protected _scrollable: Scrollable;
protected _scrollByPage: boolean;
private _lazyRender: boolean;
protected _scrollbarState: ScrollbarState;
private _visibilityController: ScrollbarVisibilityController;
@ -59,6 +61,7 @@ export abstract class AbstractScrollbar extends Widget {
this._lazyRender = opts.lazyRender;
this._host = opts.host;
this._scrollable = opts.scrollable;
this._scrollByPage = opts.scrollByPage;
this._scrollbarState = opts.scrollbarState;
this._visibilityController = this._register(new ScrollbarVisibilityController(opts.visibility, 'visible scrollbar ' + opts.extraScrollbarClassName, 'invisible scrollbar ' + opts.extraScrollbarClassName));
this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded());
@ -210,7 +213,14 @@ export abstract class AbstractScrollbar extends Widget {
offsetX = e.posx - domNodePosition.left;
offsetY = e.posy - domNodePosition.top;
}
this._setDesiredScrollPositionNow(this._scrollbarState.getDesiredScrollPositionFromOffset(this._mouseDownRelativePosition(offsetX, offsetY)));
const offset = this._mouseDownRelativePosition(offsetX, offsetY);
this._setDesiredScrollPositionNow(
this._scrollByPage
? this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(offset)
: this._scrollbarState.getDesiredScrollPositionFromOffset(offset)
);
if (e.leftButton) {
e.preventDefault();
this._sliderMouseDown(e, () => { /*nothing to do*/ });

View File

@ -9,11 +9,11 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
const scrollbarButtonLeftIcon = registerIcon('scrollbar-button-left', Codicon.triangleLeft);
const scrollbarButtonRightIcon = registerIcon('scrollbar-button-right', Codicon.triangleRight);
const scrollbarButtonLeftIcon = registerCodicon('scrollbar-button-left', Codicon.triangleLeft);
const scrollbarButtonRightIcon = registerCodicon('scrollbar-button-right', Codicon.triangleRight);
export class HorizontalScrollbar extends AbstractScrollbar {
@ -33,7 +33,8 @@ export class HorizontalScrollbar extends AbstractScrollbar {
),
visibility: options.horizontal,
extraScrollbarClassName: 'horizontal',
scrollable: scrollable
scrollable: scrollable,
scrollByPage: options.scrollByPage
});
if (options.horizontalHasArrows) {

View File

@ -361,6 +361,8 @@ export abstract class AbstractScrollableElement extends Widget {
// console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`);
let didScroll = false;
if (e.deltaY || e.deltaX) {
let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity;
let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity;
@ -419,11 +421,12 @@ export abstract class AbstractScrollableElement extends Widget {
} else {
this._scrollable.setScrollPositionNow(desiredScrollPosition);
}
this._shouldRender = true;
didScroll = true;
}
}
if (this._options.alwaysConsumeMouseWheel || this._shouldRender) {
if (this._options.alwaysConsumeMouseWheel || didScroll) {
e.preventDefault();
e.stopPropagation();
}
@ -614,7 +617,9 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme
vertical: (typeof opts.vertical !== 'undefined' ? opts.vertical : ScrollbarVisibility.Auto),
verticalScrollbarSize: (typeof opts.verticalScrollbarSize !== 'undefined' ? opts.verticalScrollbarSize : 10),
verticalHasArrows: (typeof opts.verticalHasArrows !== 'undefined' ? opts.verticalHasArrows : false),
verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0)
verticalSliderSize: (typeof opts.verticalSliderSize !== 'undefined' ? opts.verticalSliderSize : 0),
scrollByPage: (typeof opts.scrollByPage !== 'undefined' ? opts.scrollByPage : false)
};
result.horizontalSliderSize = (typeof opts.horizontalSliderSize !== 'undefined' ? opts.horizontalSliderSize : result.horizontalScrollbarSize);

View File

@ -114,6 +114,11 @@ export interface ScrollableElementCreationOptions {
* Defaults to false.
*/
verticalHasArrows?: boolean;
/**
* Scroll gutter clicks move by page vs. jump to position.
* Defaults to false.
*/
scrollByPage?: boolean;
}
export interface ScrollableElementChangeOptions {
@ -146,4 +151,5 @@ export interface ScrollableElementResolvedOptions {
verticalScrollbarSize: number;
verticalSliderSize: number;
verticalHasArrows: boolean;
scrollByPage: boolean;
}

View File

@ -202,6 +202,28 @@ export class ScrollbarState {
return Math.round(desiredSliderPosition / this._computedSliderRatio);
}
/**
* Compute a desired `scrollPosition` from if offset is before or after the slider position.
* If offset is before slider, treat as a page up (or left). If after, page down (or right).
* `offset` and `_computedSliderPosition` are based on the same coordinate system.
* `_visibleSize` corresponds to a "page" of lines in the returned coordinate system.
*/
public getDesiredScrollPositionFromOffsetPaged(offset: number): number {
if (!this._computedIsNeeded) {
// no need for a slider
return 0;
}
let correctedOffset = offset - this._arrowSize; // compensate if has arrows
let desiredScrollPosition = this._scrollPosition;
if (correctedOffset < this._computedSliderPosition) {
desiredScrollPosition -= this._visibleSize; // page up/left
} else {
desiredScrollPosition += this._visibleSize; // page down/right
}
return desiredScrollPosition;
}
/**
* Compute a desired `scrollPosition` such that the slider moves by `delta`.
*/

View File

@ -9,10 +9,10 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
const scrollbarButtonUpIcon = registerIcon('scrollbar-button-up', Codicon.triangleUp);
const scrollbarButtonDownIcon = registerIcon('scrollbar-button-down', Codicon.triangleDown);
const scrollbarButtonUpIcon = registerCodicon('scrollbar-button-up', Codicon.triangleUp);
const scrollbarButtonDownIcon = registerCodicon('scrollbar-button-down', Codicon.triangleDown);
export class VerticalScrollbar extends AbstractScrollbar {
@ -33,7 +33,8 @@ export class VerticalScrollbar extends AbstractScrollbar {
),
visibility: options.vertical,
extraScrollbarClassName: 'vertical',
scrollable: scrollable
scrollable: scrollable,
scrollByPage: options.scrollByPage
});
if (options.verticalHasArrows) {

View File

@ -46,6 +46,7 @@ export interface ISelectBoxOptions {
// Utilize optionItem interface to capture all option parameters
export interface ISelectOptionItem {
text: string;
detail?: string;
decoratorRight?: string;
description?: string;
descriptionIsMarkdown?: boolean;

View File

@ -75,6 +75,15 @@
float: left;
}
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-detail {
text-overflow: ellipsis;
overflow: hidden;
padding-left: 3.5px;
white-space: nowrap;
float: left;
opacity: 0.7;
}
.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-decorator-right {
text-overflow: ellipsis;
overflow: hidden;

View File

@ -29,6 +29,7 @@ const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template';
interface ISelectListTemplateData {
root: HTMLElement;
text: HTMLElement;
detail: HTMLElement;
decoratorRight: HTMLElement;
disposables: IDisposable[];
}
@ -42,6 +43,7 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
data.disposables = [];
data.root = container;
data.text = dom.append(container, $('.option-text'));
data.detail = dom.append(container, $('.option-detail'));
data.decoratorRight = dom.append(container, $('.option-decorator-right'));
return data;
@ -49,12 +51,16 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList
renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void {
const data: ISelectListTemplateData = templateData;
const text = element.text;
const detail = element.detail;
const decoratorRight = element.decoratorRight;
const isDisabled = element.isDisabled;
data.text.textContent = text;
data.decoratorRight.innerText = (!!decoratorRight ? decoratorRight : '');
data.detail.textContent = !!detail ? detail : '';
data.decoratorRight.innerText = !!decoratorRight ? decoratorRight : '';
// pseudo-select disabled option
if (isDisabled) {
@ -662,7 +668,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
let longestLength = 0;
this.options.forEach((option, index) => {
const len = option.text.length + (!!option.decoratorRight ? option.decoratorRight.length : 0);
const detailLength = !!option.detail ? option.detail.length : 0;
const rightDecoratorLength = !!option.decoratorRight ? option.decoratorRight.length : 0;
const len = option.text.length + detailLength + rightDecoratorLength;
if (len > longestLength) {
longest = index;
longestLength = len;
@ -697,6 +706,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi
accessibilityProvider: {
getAriaLabel: element => {
let label = element.text;
if (element.detail) {
label += `. ${element.detail}`;
}
if (element.decoratorRight) {
label += `. ${element.decoratorRight}`;
}

View File

@ -38,23 +38,12 @@
width: 22px;
}
.monaco-pane-view .pane > .pane-header > .twisties {
width: 20px;
display: flex;
align-items: center;
justify-content: center;
transform-origin: center;
color: inherit;
flex-shrink: 0;
.monaco-pane-view .pane > .pane-header > .codicon:first-of-type {
margin: 0 2px;
}
.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);
.monaco-pane-view .pane.horizontal:not(.expanded) > .pane-header > .codicon:first-of-type {
margin: 2px;
}
/* TODO: actions should be part of the pane, but they aren't yet */

View File

@ -20,31 +20,36 @@
pointer-events: initial;
}
.monaco-split-view2 > .split-view-container {
.monaco-split-view2 > .monaco-scrollable-element {
width: 100%;
height: 100%;
}
.monaco-split-view2 > .monaco-scrollable-element > .split-view-container {
width: 100%;
height: 100%;
white-space: nowrap;
position: relative;
}
.monaco-split-view2 > .split-view-container > .split-view-view {
.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view {
white-space: initial;
position: absolute;
}
.monaco-split-view2 > .split-view-container > .split-view-view:not(.visible) {
.monaco-split-view2 > .monaco-scrollable-element > .split-view-container > .split-view-view:not(.visible) {
display: none;
}
.monaco-split-view2.vertical > .split-view-container > .split-view-view {
.monaco-split-view2.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view {
width: 100%;
}
.monaco-split-view2.horizontal > .split-view-container > .split-view-view {
.monaco-split-view2.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view {
height: 100%;
}
.monaco-split-view2.separator-border > .split-view-container > .split-view-view:not(:first-child)::before {
.monaco-split-view2.separator-border > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before {
content: ' ';
position: absolute;
top: 0;
@ -54,12 +59,12 @@
background-color: var(--separator-border);
}
.monaco-split-view2.separator-border.horizontal > .split-view-container > .split-view-view:not(:first-child)::before {
.monaco-split-view2.separator-border.horizontal > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before {
height: 100%;
width: 1px;
}
.monaco-split-view2.separator-border.vertical > .split-view-container > .split-view-view:not(:first-child)::before {
.monaco-split-view2.separator-border.vertical > .monaco-scrollable-element > .split-view-container > .split-view-view:not(:first-child)::before {
height: 1px;
width: 100%;
}

View File

@ -12,7 +12,9 @@ import { range, pushToStart, pushToEnd } from 'vs/base/common/arrays';
import { Sash, Orientation, ISashEvent as IBaseSashEvent, SashState } from 'vs/base/browser/ui/sash/sash';
import { Color } from 'vs/base/common/color';
import { domEvent } from 'vs/base/browser/event';
import { $, append } from 'vs/base/browser/dom';
import { $, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
export interface ISplitViewStyles {
@ -213,6 +215,8 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
readonly el: HTMLElement;
private sashContainer: HTMLElement;
private viewContainer: HTMLElement;
private scrollable: Scrollable;
private scrollableElement: SmoothScrollableElement;
private size = 0;
private layoutContext: TLayoutContext | undefined;
private contentSize = 0;
@ -301,7 +305,20 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
container.appendChild(this.el);
this.sashContainer = append(this.el, $('.sash-container'));
this.viewContainer = append(this.el, $('.split-view-container'));
this.viewContainer = $('.split-view-container');
this.scrollable = new Scrollable(125, scheduleAtNextAnimationFrame);
this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, {
vertical: this.orientation === Orientation.VERTICAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden,
horizontal: this.orientation === Orientation.HORIZONTAL ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
}, this.scrollable));
this._register(this.scrollableElement.onScroll(e => {
this.viewContainer.scrollTop = e.scrollTop;
this.viewContainer.scrollLeft = e.scrollLeft;
}));
append(this.el, this.scrollableElement.getDomNode());
this.style(options.styles || defaultStyles);
@ -897,6 +914,21 @@ export class SplitView<TLayoutContext = undefined> extends Disposable {
// Layout sashes
this.sashItems.forEach(item => item.sash.layout());
this.updateSashEnablement();
this.updateScrollableElement();
}
private updateScrollableElement(): void {
if (this.orientation === Orientation.VERTICAL) {
this.scrollableElement.setScrollDimensions({
height: this.size,
scrollHeight: this.contentSize
});
} else {
this.scrollableElement.setScrollDimensions({
width: this.size,
scrollWidth: this.contentSize
});
}
}
private updateSashEnablement(): void {

View File

@ -11,12 +11,12 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, CSSIcon, registerCodicon } from 'vs/base/common/codicons';
import { EventMultiplexer } from 'vs/base/common/event';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more);
const toolBarMoreIcon = registerCodicon('toolbar-more', Codicon.more);
export interface IToolBarOptions {
orientation?: ActionsOrientation;
@ -27,6 +27,7 @@ export interface IToolBarOptions {
toggleMenuTitle?: string;
anchorAlignmentProvider?: () => AnchorAlignment;
renderDropdownAsChildElement?: boolean;
moreIcon?: CSSIcon;
}
/**
@ -72,7 +73,7 @@ export class ToolBar extends Disposable {
actionViewItemProvider: this.options.actionViewItemProvider,
actionRunner: this.actionRunner,
keybindingProvider: this.options.getKeyBinding,
classNames: toolBarMoreIcon.classNamesArray,
classNames: (options.moreIcon ?? toolBarMoreIcon).classNames,
anchorAlignmentProvider: this.options.anchorAlignmentProvider,
menuAsChild: !!this.options.renderDropdownAsChildElement
}

View File

@ -400,15 +400,23 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
}
private renderTwistie(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>) {
templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray);
let twistieRendered = false;
if (this.renderer.renderTwistie) {
this.renderer.renderTwistie(node.element, templateData.twistie);
twistieRendered = this.renderer.renderTwistie(node.element, templateData.twistie);
}
if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) {
templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray, 'collapsible');
if (!twistieRendered) {
templateData.twistie.classList.add(...treeItemExpandedIcon.classNamesArray);
}
templateData.twistie.classList.add('collapsible');
templateData.twistie.classList.toggle('collapsed', node.collapsed);
} else {
templateData.twistie.classList.remove(...treeItemExpandedIcon.classNamesArray, 'collapsible', 'collapsed');
templateData.twistie.classList.remove('collapsible', 'collapsed');
}
if (node.collapsible) {
@ -811,7 +819,7 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
const onDragOver = (event: DragEvent) => {
event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
const x = event.screenX - left;
const x = event.clientX - left;
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'none';
}
@ -954,6 +962,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
readonly expandOnlyOnDoubleClick?: boolean;
readonly expandOnlyOnTwistieClick?: boolean | ((e: any) => boolean); // e is T
}
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
@ -961,7 +970,6 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
readonly filter?: ITreeFilter<T, TFilterData>;
readonly dnd?: ITreeDragAndDrop<T>;
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean);
readonly additionalScrollHeight?: number;
}
@ -1109,7 +1117,7 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController<
expandOnlyOnTwistieClick = !!this.tree.expandOnlyOnTwistieClick;
}
if (expandOnlyOnTwistieClick && !onTwistie) {
if (expandOnlyOnTwistieClick && !onTwistie && e.browserEvent.detail !== 2) {
return super.onViewPointer(e);
}

View File

@ -110,10 +110,11 @@ class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements IT
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
if (element.slow) {
twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray);
return true;
} else {
twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray);
return false;
}
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
@ -1053,10 +1054,11 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean {
if (element.slow) {
twistieElement.classList.add(...treeItemLoadingIcon.classNamesArray);
return true;
} else {
twistieElement.classList.remove(...treeItemLoadingIcon.classNamesArray);
return false;
}
return false;
}
disposeElement(node: ITreeNode<IAsyncDataTreeNode<TInput, T>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {

View File

@ -38,8 +38,8 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
const elements = [element.element];
const incompressible = element.incompressible || false;
let childrenIterator: Iterable<ITreeElement<T>>;
let children: ITreeElement<T>[];
let childrenIterator: Iterable<ICompressedTreeElement<T>>;
let children: ICompressedTreeElement<T>[];
while (true) {
[children, childrenIterator] = Iterable.consume(Iterable.from(element.children), 2);
@ -48,12 +48,11 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC
break;
}
element = children[0];
if (element.incompressible) {
if (children[0].incompressible) {
break;
}
element = children[0];
elements.push(element.element);
}

View File

@ -128,10 +128,11 @@ class CompressibleRenderer<T extends NonNullable<any>, TFilterData, TTemplateDat
this.renderer.disposeTemplate(templateData.data);
}
renderTwistie?(element: T, twistieElement: HTMLElement): void {
renderTwistie?(element: T, twistieElement: HTMLElement): boolean {
if (this.renderer.renderTwistie) {
this.renderer.renderTwistie(element, twistieElement);
return this.renderer.renderTwistie(element, twistieElement);
}
return false;
}
}

View File

@ -129,7 +129,7 @@ export interface ITreeModel<T, TFilterData, TRef> {
}
export interface ITreeRenderer<T, TFilterData = void, TTemplateData = void> extends IListRenderer<ITreeNode<T, TFilterData>, TTemplateData> {
renderTwistie?(element: T, twistieElement: HTMLElement): void;
renderTwistie?(element: T, twistieElement: HTMLElement): boolean;
onDidChangeTwistieState?: Event<T>;
}

View File

@ -3,12 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
export const treeItemExpandedIcon = registerIcon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation
export const treeItemExpandedIcon = registerCodicon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation
export const treeFilterOnTypeOnIcon = registerIcon('tree-filter-on-type-on', Codicon.listFilter);
export const treeFilterOnTypeOffIcon = registerIcon('tree-filter-on-type-off', Codicon.listSelection);
export const treeFilterClearIcon = registerIcon('tree-filter-clear', Codicon.close);
export const treeFilterOnTypeOnIcon = registerCodicon('tree-filter-on-type-on', Codicon.listFilter);
export const treeFilterOnTypeOffIcon = registerCodicon('tree-filter-on-type-off', Codicon.listSelection);
export const treeFilterClearIcon = registerCodicon('tree-filter-clear', Codicon.close);
export const treeItemLoadingIcon = registerIcon('tree-item-loading', Codicon.loading);
export const treeItemLoadingIcon = registerCodicon('tree-item-loading', Codicon.loading);