提交 5082e729 编写于 作者: R Rob Lourens

Avoid cell execution icon flickering due to rerendering spinner

上级 201063aa
......@@ -66,6 +66,24 @@ export function groupBy<T>(data: T[], groupFn: (element: T) => string): IStringD
return result;
}
/**
* Groups the collection into a dictionary based on the provided
* group function.
*/
export function groupByNumber<T>(data: T[], groupFn: (element: T) => number): Map<number, T[]> {
const result = new Map<number, T[]>();
for (const element of data) {
const key = groupFn(element);
let target = result.get(key);
if (!target) {
target = [];
result.set(key, target);
}
target.push(element);
}
return result;
}
export function fromMap<T>(original: Map<string, T>): IStringDictionary<T> {
const result: IStringDictionary<T> = Object.create(null);
if (original) {
......
......@@ -53,4 +53,26 @@ suite('Collections', () => {
assert.strictEqual(grouped[group2].length, 1);
assert.strictEqual(grouped[group2][0].value, value3);
});
test('groupByNumber', () => {
const group1 = 1, group2 = 2;
const value1 = 'a', value2 = 'b', value3 = 'c';
let source = [
{ key: group1, value: value1 },
{ key: group1, value: value2 },
{ key: group2, value: value3 },
];
let grouped = collections.groupByNumber(source, x => x.key);
// Group 1
assert.strictEqual(grouped.get(group1)!.length, 2);
assert.strictEqual(grouped.get(group1)![0].value, value1);
assert.strictEqual(grouped.get(group1)![1].value, value2);
// Group 2
assert.strictEqual(grouped.get(group2)!.length, 1);
assert.strictEqual(grouped.get(group2)![0].value, value3);
});
});
......@@ -11,7 +11,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
import { stripIcons } from 'vs/base/common/iconLabels';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
import { IDimension, isThemeColor } from 'vs/editor/common/editorCommon';
import { ICommandService } from 'vs/platform/commands/common/commands';
......@@ -39,11 +39,12 @@ export const enum ClickTargetType {
export class CellEditorStatusBar extends Disposable {
readonly statusBarContainer: HTMLElement;
private readonly leftContributedItemsContainer: HTMLElement;
private readonly rightContributedItemsContainer: HTMLElement;
private readonly leftItemsContainer: HTMLElement;
private readonly rightItemsContainer: HTMLElement;
private readonly itemsDisposable: DisposableStore;
private items: CellStatusBarItem[] = [];
private leftItems: CellStatusBarItem[] = [];
private rightItems: CellStatusBarItem[] = [];
private width: number = 0;
private currentContext: INotebookCellActionContext | undefined;
......@@ -60,8 +61,8 @@ export class CellEditorStatusBar extends Disposable {
this.statusBarContainer.tabIndex = -1;
const leftItemsContainer = DOM.append(this.statusBarContainer, $('.cell-status-left'));
const rightItemsContainer = DOM.append(this.statusBarContainer, $('.cell-status-right'));
this.leftContributedItemsContainer = DOM.append(leftItemsContainer, $('.cell-contributed-items.cell-contributed-items-left'));
this.rightContributedItemsContainer = DOM.append(rightItemsContainer, $('.cell-contributed-items.cell-contributed-items-right'));
this.leftItemsContainer = DOM.append(leftItemsContainer, $('.cell-contributed-items.cell-contributed-items-left'));
this.rightItemsContainer = DOM.append(rightItemsContainer, $('.cell-contributed-items.cell-contributed-items-right'));
this.itemsDisposable = this._register(new DisposableStore());
......@@ -107,7 +108,8 @@ export class CellEditorStatusBar extends Disposable {
this.statusBarContainer.style.width = `${width}px`;
const maxItemWidth = this.getMaxItemWidth();
this.items.forEach(item => item.maxWidth = maxItemWidth);
this.leftItems.forEach(item => item.maxWidth = maxItemWidth);
this.rightItems.forEach(item => item.maxWidth = maxItemWidth);
}
private getMaxItemWidth() {
......@@ -136,22 +138,44 @@ export class CellEditorStatusBar extends Disposable {
}
private updateRenderedItems(): void {
DOM.clearNode(this.leftContributedItemsContainer);
DOM.clearNode(this.rightContributedItemsContainer);
const items = this.currentContext!.cell.getCellStatusBarItems();
items.sort((itemA, itemB) => {
return (itemB.priority ?? 0) - (itemA.priority ?? 0);
});
const maxItemWidth = this.getMaxItemWidth();
const leftItems = items.filter(item => item.alignment === CellStatusbarAlignment.Left)
.map(item => this.itemsDisposable.add(this._instantiationService.createInstance(CellStatusBarItem, this.currentContext!, item, maxItemWidth)));
const rightItems = items.filter(item => item.alignment === CellStatusbarAlignment.Right).reverse()
.map(item => this.itemsDisposable.add(this._instantiationService.createInstance(CellStatusBarItem, this.currentContext!, item, maxItemWidth)));
leftItems.forEach(itemView => this.leftContributedItemsContainer.appendChild(itemView.container));
rightItems.forEach(itemView => this.rightContributedItemsContainer.appendChild(itemView.container));
this.items = [...leftItems, ...rightItems];
const newLeftItems = items.filter(item => item.alignment === CellStatusbarAlignment.Left);
const newRightItems = items.filter(item => item.alignment === CellStatusbarAlignment.Right).reverse();
const updateItems = (renderedItems: CellStatusBarItem[], newItems: INotebookCellStatusBarItem[], container: HTMLElement) => {
if (renderedItems.length > newItems.length) {
const deleted = renderedItems.splice(newItems.length, renderedItems.length - newItems.length);
for (let deletedItem of deleted) {
container.removeChild(deletedItem.container);
deletedItem.dispose();
}
}
newItems.forEach((newLeftItem, i) => {
const existingItem = renderedItems[i];
if (existingItem) {
existingItem.updateItem(newLeftItem, maxItemWidth);
} else {
const item = this._instantiationService.createInstance(CellStatusBarItem, this.currentContext!, newLeftItem, maxItemWidth);
renderedItems.push(item);
container.appendChild(item.container);
}
});
};
updateItems(this.leftItems, newLeftItems, this.leftItemsContainer);
updateItems(this.rightItems, newRightItems, this.rightItemsContainer);
}
override dispose() {
super.dispose();
dispose(this.leftItems);
dispose(this.rightItems);
}
}
......@@ -163,9 +187,12 @@ class CellStatusBarItem extends Disposable {
this.container.style.maxWidth = v + 'px';
}
private _currentItem!: INotebookCellStatusBarItem;
private _itemDisposables = this._register(new DisposableStore());
constructor(
private readonly _context: INotebookCellActionContext,
private readonly _itemModel: INotebookCellStatusBarItem,
itemModel: INotebookCellStatusBarItem,
maxWidth: number | undefined,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
@ICommandService private readonly _commandService: ICommandService,
......@@ -174,32 +201,27 @@ class CellStatusBarItem extends Disposable {
) {
super();
const resolveColor = (color: ThemeColor | string) => {
return isThemeColor(color) ?
this._themeService.getColorTheme().getColor(color.id)?.toString() :
color;
};
this.updateItem(itemModel, maxWidth);
}
if (this._itemModel.text) {
new SimpleIconLabel(this.container).text = this._itemModel.text.replace(/\n/g, ' ');
}
updateItem(item: INotebookCellStatusBarItem, maxWidth: number | undefined) {
this._itemDisposables.clear();
if (this._itemModel.color) {
this.container.style.color = resolveColor(this._itemModel.color) || '';
if (!this._currentItem || this._currentItem.text !== item.text) {
new SimpleIconLabel(this.container).text = item.text.replace(/\n/g, ' ');
}
if (this._itemModel.backgroundColor) {
const colorResult = resolveColor(this._itemModel.backgroundColor);
this.container.style.backgroundColor = colorResult || '';
}
const resolveColor = (color: ThemeColor | string) => {
return isThemeColor(color) ?
(this._themeService.getColorTheme().getColor(color.id)?.toString() || '') :
color;
};
if (this._itemModel.opacity) {
this.container.style.opacity = this._itemModel.opacity;
}
this.container.style.color = item.color ? resolveColor(item.color) : '';
this.container.style.backgroundColor = item.backgroundColor ? resolveColor(item.backgroundColor) : '';
this.container.style.opacity = item.opacity ? item.opacity : '';
if (this._itemModel.onlyShowWhenActive) {
this.container.classList.add('cell-status-item-show-when-active');
}
this.container.classList.toggle('cell-status-item-show-when-active', !!item.onlyShowWhenActive);
if (typeof maxWidth === 'number') {
this.maxWidth = maxWidth;
......@@ -207,41 +229,39 @@ class CellStatusBarItem extends Disposable {
let ariaLabel: string;
let role: string | undefined;
if (this._itemModel.accessibilityInformation) {
ariaLabel = this._itemModel.accessibilityInformation.label;
role = this._itemModel.accessibilityInformation.role;
if (item.accessibilityInformation) {
ariaLabel = item.accessibilityInformation.label;
role = item.accessibilityInformation.role;
} else {
ariaLabel = this._itemModel.text ? stripIcons(this._itemModel.text).trim() : '';
}
if (ariaLabel) {
this.container.setAttribute('aria-label', ariaLabel);
ariaLabel = item.text ? stripIcons(item.text).trim() : '';
}
if (role) {
this.container.setAttribute('role', role);
}
this.container.setAttribute('aria-label', ariaLabel);
this.container.setAttribute('role', role || '');
this.container.title = item.tooltip ?? '';
this.container.title = this._itemModel.tooltip ?? '';
if (this._itemModel.command) {
this.container.classList.add('cell-status-item-has-command');
this.container.classList.toggle('cell-status-item-has-command', !!item.command);
if (item.command) {
this.container.tabIndex = 0;
this._register(DOM.addDisposableListener(this.container, DOM.EventType.CLICK, _e => {
this._itemDisposables.add(DOM.addDisposableListener(this.container, DOM.EventType.CLICK, _e => {
this.executeCommand();
}));
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_DOWN, e => {
this._itemDisposables.add(DOM.addDisposableListener(this.container, DOM.EventType.KEY_DOWN, e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {
this.executeCommand();
}
}));
} else {
this.container.tabIndex = -1;
}
this._currentItem = item;
}
private async executeCommand(): Promise<void> {
const command = this._itemModel.command;
const command = this._currentItem.command;
if (!command) {
return;
}
......
......@@ -35,6 +35,7 @@ import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/edit
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
import { NotebookCellSelectionCollection } from 'vs/workbench/contrib/notebook/browser/viewModel/cellSelectionCollection';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { groupByNumber } from 'vs/base/common/collections';
export interface INotebookEditorViewState {
editingCells: { [key: number]: boolean };
......@@ -732,20 +733,14 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
}
deltaCellStatusBarItems(oldItems: string[], newItems: INotebookDeltaCellStatusBarItems[]): string[] {
oldItems.forEach(id => {
const handle = this._statusBarItemIdToCellMap.get(id);
if (handle !== undefined) {
const cell = this.getCellByHandle(handle);
cell?.deltaCellStatusBarItems([id], []);
}
});
const deletesByHandle = groupByNumber(oldItems, id => this._statusBarItemIdToCellMap.get(id) ?? -1);
const result: string[] = [];
newItems.forEach(itemDelta => {
const cell = this.getCellByHandle(itemDelta.handle);
const ret = cell?.deltaCellStatusBarItems([], itemDelta.items) || [];
const deleted = deletesByHandle.get(itemDelta.handle) ?? [];
deletesByHandle.delete(itemDelta.handle);
const ret = cell?.deltaCellStatusBarItems(deleted, itemDelta.items) || [];
ret.forEach(id => {
this._statusBarItemIdToCellMap.set(id, itemDelta.handle);
});
......@@ -753,6 +748,11 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
result.push(...ret);
});
deletesByHandle.forEach((ids, handle) => {
const cell = this.getCellByHandle(handle);
cell?.deltaCellStatusBarItems(ids, []);
});
return result;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册