deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
/********************************************************************************
* Copyright (C) 2020 RedHat and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
********************************************************************************/
.theia-timeline {
box-sizing: border-box;
height: 100%;
display: flex;
flex-direction: column;
}
.theia-timeline .timeline-outer-container {
overflow-y: auto;
width: 100%;
flex-grow: 1;
}
.theia-timeline .timeline-item {
display: flex;
align-items: center;
}
.theia-timeline .timeline-item-icon,
.theia-timeline .timeline-item-label {
margin-right: 6px;
}
.theia-timeline .timeline-item-description {
font-size: var(--theia-ui-font-size0);
}

View File

@@ -0,0 +1,36 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import { ContextKeyService, ContextKey } from '@theia/core/lib/browser/context-key-service';
@injectable()
export class TimelineContextKeyService {
@inject(ContextKeyService)
protected readonly contextKeyService: ContextKeyService;
protected _timelineItem: ContextKey<string | undefined>;
get timelineItem(): ContextKey<string | undefined> {
return this._timelineItem;
}
@postConstruct()
protected init(): void {
this._timelineItem = this.contextKeyService.createKey<string | undefined>('timelineItem', undefined);
}
}

View File

@@ -0,0 +1,112 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject } from '@theia/core/shared/inversify';
import {
ViewContainer,
WidgetManager,
Widget,
ApplicationShell,
Navigatable,
codicon
} from '@theia/core/lib/browser';
import { EXPLORER_VIEW_CONTAINER_ID } from '@theia/navigator/lib/browser';
import { TimelineWidget } from './timeline-widget';
import { TimelineService } from './timeline-service';
import { CommandContribution, CommandRegistry, nls } from '@theia/core/lib/common';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { toArray } from '@theia/core/shared/@lumino/algorithm';
import { LOAD_MORE_COMMAND } from './timeline-tree-model';
@injectable()
export class TimelineContribution implements CommandContribution, TabBarToolbarContribution {
@inject(WidgetManager)
protected readonly widgetManager: WidgetManager;
@inject(TimelineService)
protected readonly timelineService: TimelineService;
@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry;
@inject(TabBarToolbarRegistry)
protected readonly tabBarToolbar: TabBarToolbarRegistry;
@inject(ApplicationShell)
protected readonly shell: ApplicationShell;
/** @deprecated @since 1.28.0. Import from timeline-tree-model instead */
public static readonly LOAD_MORE_COMMAND = LOAD_MORE_COMMAND;
private readonly toolbarItem = {
id: 'timeline-refresh-toolbar-item',
command: 'timeline-refresh',
tooltip: nls.localizeByDefault('Refresh'),
icon: codicon('refresh')
};
registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem(this.toolbarItem);
}
registerCommands(commands: CommandRegistry): void {
const attachTimeline = async (explorer: Widget) => {
const timeline = await this.widgetManager.getOrCreateWidget(TimelineWidget.ID);
if (explorer instanceof ViewContainer && explorer.getTrackableWidgets().indexOf(timeline) === -1) {
explorer.addWidget(timeline, { initiallyCollapsed: true });
}
};
this.widgetManager.onWillCreateWidget(async event => {
if (event.widget.id === EXPLORER_VIEW_CONTAINER_ID && this.timelineService.getSources().length > 0) {
event.waitUntil(attachTimeline(event.widget));
}
});
this.timelineService.onDidChangeProviders(async event => {
const explorer = await this.widgetManager.getWidget(EXPLORER_VIEW_CONTAINER_ID);
if (explorer && event.added && event.added.length > 0) {
attachTimeline(explorer);
} else if (event.removed && this.timelineService.getSources().length === 0) {
const timeline = await this.widgetManager.getWidget(TimelineWidget.ID);
if (timeline) {
timeline.close();
}
}
});
commands.registerCommand(LOAD_MORE_COMMAND, {
execute: async () => {
const widget = toArray(this.shell.mainPanel.widgets()).find(w => Navigatable.is(w) && w.isVisible && !w.isHidden);
if (Navigatable.is(widget)) {
const uri = widget.getResourceUri();
const timeline = await this.widgetManager.getWidget<TimelineWidget>(TimelineWidget.ID);
if (uri && timeline) {
timeline.loadTimeline(uri, false);
}
}
}
});
commands.registerCommand({ id: this.toolbarItem.command }, {
execute: widget => this.checkWidget(widget, async () => {
const timeline = await this.widgetManager.getWidget(TimelineWidget.ID);
if (timeline) {
timeline.update();
}
}),
isEnabled: widget => this.checkWidget(widget, () => true),
isVisible: widget => this.checkWidget(widget, () => true)
});
}
private checkWidget<T>(widget: Widget, cb: () => T): T | false {
if (widget instanceof TimelineWidget && widget.id === TimelineWidget.ID) {
return cb();
}
return false;
}
}

View File

@@ -0,0 +1,41 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable } from '@theia/core/shared/inversify';
import { ReactWidget } from '@theia/core/lib/browser';
import { AlertMessage } from '@theia/core/lib/browser/widgets/alert-message';
import * as React from '@theia/core/shared/react';
import { nls } from '@theia/core';
@injectable()
export class TimelineEmptyWidget extends ReactWidget {
static ID = 'timeline-empty-widget';
constructor() {
super();
this.addClass('theia-timeline-empty');
this.id = TimelineEmptyWidget.ID;
}
protected render(): React.ReactNode {
return <AlertMessage
type='WARNING'
header={nls.localizeByDefault('The active editor cannot provide timeline information.')}
/>;
}
}

View File

@@ -0,0 +1,71 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { Container, ContainerModule, interfaces } from '@theia/core/shared/inversify';
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
import { TimelineService } from './timeline-service';
import { TimelineWidget } from './timeline-widget';
import { TimelineTreeWidget } from './timeline-tree-widget';
import { createTreeContainer, } from '@theia/core/lib/browser';
import { TimelineTreeModel } from './timeline-tree-model';
import { TimelineEmptyWidget } from './timeline-empty-widget';
import { TimelineContextKeyService } from './timeline-context-key-service';
import { TimelineContribution } from './timeline-contribution';
import '../../src/browser/style/index.css';
import { CommandContribution } from '@theia/core/lib/common';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
export default new ContainerModule(bind => {
bind(TimelineContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(TimelineContribution);
bind(TabBarToolbarContribution).toService(TimelineContribution);
bind(TimelineContextKeyService).toSelf().inSingletonScope();
bind(TimelineService).toSelf().inSingletonScope();
bind(TimelineWidget).toSelf();
bind(WidgetFactory).toDynamicValue(({ container }) => ({
id: TimelineWidget.ID,
createWidget: () => container.get(TimelineWidget)
})).inSingletonScope();
bind(TimelineTreeWidget).toDynamicValue(ctx => {
const child = createTimelineTreeContainer(ctx.container);
return child.get(TimelineTreeWidget);
});
bind(WidgetFactory).toDynamicValue(({ container }) => ({
id: TimelineTreeWidget.ID,
createWidget: () => container.get(TimelineTreeWidget)
})).inSingletonScope();
bind(TimelineEmptyWidget).toSelf();
bind(WidgetFactory).toDynamicValue(({ container }) => ({
id: TimelineEmptyWidget.ID,
createWidget: () => container.get(TimelineEmptyWidget)
})).inSingletonScope();
});
export function createTimelineTreeContainer(parent: interfaces.Container): Container {
const child = createTreeContainer(parent, {
props: {
virtualized: true,
search: true
},
widget: TimelineTreeWidget,
model: TimelineTreeModel
});
return child;
}

View File

@@ -0,0 +1,124 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable } from '@theia/core/shared/inversify';
import { Disposable, Emitter, Event } from '@theia/core/lib/common';
import { URI } from '@theia/core/shared/vscode-uri';
import {
InternalTimelineOptions,
Timeline,
TimelineChangeEvent, TimelineItem, TimelineOptions,
TimelineProvider,
TimelineProvidersChangeEvent,
TimelineSource
} from '../common/timeline-model';
@injectable()
export class TimelineService {
private readonly providers = new Map<string, TimelineProvider>();
private readonly onDidChangeProvidersEmitter = new Emitter<TimelineProvidersChangeEvent>();
readonly onDidChangeProviders: Event<TimelineProvidersChangeEvent> = this.onDidChangeProvidersEmitter.event;
private readonly onDidChangeTimelineEmitter = new Emitter<TimelineChangeEvent>();
readonly onDidChangeTimeline: Event<TimelineChangeEvent> = this.onDidChangeTimelineEmitter.event;
registerTimelineProvider(provider: TimelineProvider): Disposable {
const id = provider.id;
this.providers.set(id, provider);
if (provider.onDidChange) {
provider.onDidChange(e => this.onDidChangeTimelineEmitter.fire(e));
}
this.onDidChangeProvidersEmitter.fire({ added: [id] });
return Disposable.create(() => this.unregisterTimelineProvider(id));
}
unregisterTimelineProvider(id: string): void {
const provider = this.providers.get(id);
if (provider) {
provider.dispose();
this.providers.delete(id);
this.onDidChangeProvidersEmitter.fire({ removed: [id] });
}
}
getSources(): TimelineSource[] {
return [...this.providers.values()].map(p => ({ id: p.id, label: p.label }));
}
getSchemas(): string[] {
const result: string[] = [];
Array.from(this.providers.values()).forEach(provider => {
const scheme = provider.scheme;
if (typeof scheme === 'string') {
result.push(scheme);
} else {
scheme.forEach(s => result.push(s));
}
});
return result;
}
getTimeline(id: string, uri: URI, options: TimelineOptions, internalOptions?: InternalTimelineOptions): Promise<Timeline | undefined> {
const provider = this.providers.get(id);
if (!provider) {
return Promise.resolve(undefined);
}
if (typeof provider.scheme === 'string') {
if (provider.scheme !== '*' && provider.scheme !== uri.scheme) {
return Promise.resolve(undefined);
}
}
return provider.provideTimeline(uri, options, internalOptions)
.then(result => {
if (!result) {
return undefined;
}
result.items = result.items.map(item => ({ ...item, source: provider.id }));
return result;
});
}
}
export class TimelineAggregate {
readonly items: TimelineItem[];
readonly source: string;
readonly uri: string;
private _cursor?: string;
get cursor(): string | undefined {
return this._cursor;
}
set cursor(cursor: string | undefined) {
this._cursor = cursor;
}
constructor(timeline: Timeline) {
this.source = timeline.source;
this.items = timeline.items;
this._cursor = timeline.paging?.cursor;
}
add(items: TimelineItem[]): void {
this.items.push(...items);
this.items.sort((a, b) => b.timestamp - a.timestamp);
}
}

View File

@@ -0,0 +1,79 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable } from '@theia/core/shared/inversify';
import {
CompositeTreeNode,
SelectableTreeNode,
TreeModelImpl,
} from '@theia/core/lib/browser/tree';
import { TimelineItem } from '../common/timeline-model';
import { Command, nls } from '@theia/core';
export const LOAD_MORE_COMMAND: Command = {
id: 'timeline-load-more'
};
export interface TimelineNode extends SelectableTreeNode {
timelineItem: TimelineItem;
}
@injectable()
export class TimelineTreeModel extends TreeModelImpl {
updateTree(items: TimelineItem[], hasMoreItems: boolean): void {
const root = {
id: 'timeline-tree-root',
parent: undefined,
visible: false,
children: []
} as CompositeTreeNode;
const children = items.map(item =>
({
timelineItem: item,
id: item.id ? item.id : item.timestamp.toString(),
parent: root,
detail: item.tooltip,
selected: false,
visible: true
} as TimelineNode)
);
let loadMore;
if (hasMoreItems) {
const loadMoreNode: TimelineItem = {
label: nls.localizeByDefault('Load more'),
timestamp: 0,
handle: '',
uri: '',
source: '',
icon: 'blank'
};
loadMoreNode.command = LOAD_MORE_COMMAND;
loadMore = {
timelineItem: loadMoreNode,
id: 'load-more',
parent: root,
selected: true
} as TimelineNode;
children.push(loadMore);
}
root.children = children;
this.root = root;
if (loadMore) {
this.selectionService.addSelection(loadMore);
}
}
}

View File

@@ -0,0 +1,157 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { injectable, inject } from '@theia/core/shared/inversify';
import { CommandRegistry, MenuModelRegistry, MenuPath } from '@theia/core/lib/common';
import { TreeWidget, TreeProps, NodeProps, TREE_NODE_SEGMENT_GROW_CLASS, TREE_NODE_INFO_CLASS } from '@theia/core/lib/browser/tree';
import { codicon, ContextMenuRenderer, HoverService } from '@theia/core/lib/browser';
import { TimelineNode, TimelineTreeModel } from './timeline-tree-model';
import { TimelineService } from './timeline-service';
import { TimelineContextKeyService } from './timeline-context-key-service';
import * as React from '@theia/core/shared/react';
import { TimelineItem } from '../common/timeline-model';
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
import { isThemeIcon } from '@theia/core/lib/common/theme';
export const TIMELINE_ITEM_CONTEXT_MENU: MenuPath = ['timeline-item-context-menu'];
@injectable()
export class TimelineTreeWidget extends TreeWidget {
static ID = 'timeline-tree-widget';
static PAGE_SIZE = 20;
@inject(MenuModelRegistry) protected readonly menus: MenuModelRegistry;
@inject(TimelineContextKeyService) protected readonly contextKeys: TimelineContextKeyService;
@inject(TimelineService) protected readonly timelineService: TimelineService;
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
@inject(HoverService) protected readonly hoverService: HoverService;
constructor(
@inject(TreeProps) props: TreeProps,
@inject(TimelineTreeModel) override readonly model: TimelineTreeModel,
@inject(ContextMenuRenderer) contextMenuRenderer: ContextMenuRenderer,
) {
super(props, model, contextMenuRenderer);
this.id = TimelineTreeWidget.ID;
this.addClass('timeline-outer-container');
}
protected override renderNode(node: TimelineNode, props: NodeProps): React.ReactNode {
const attributes = this.createNodeAttributes(node, props);
const content = <TimelineItemNode
timelineItem={node.timelineItem}
commandRegistry={this.commandRegistry}
contextKeys={this.contextKeys}
contextMenuRenderer={this.contextMenuRenderer}
hoverService={this.hoverService} />;
return React.createElement('div', attributes, content);
}
protected override handleEnter(event: KeyboardEvent): void {
const node = this.model.getFocusedNode() as TimelineNode;
const command = node?.timelineItem?.command;
if (command) {
this.commandRegistry.executeCommand(command.id, ...(command.arguments ? command.arguments : []));
}
}
protected override async handleLeft(event: KeyboardEvent): Promise<void> {
this.model.selectPrevNode();
}
}
export namespace TimelineItemNode {
export interface Props {
timelineItem: TimelineItem;
commandRegistry: CommandRegistry;
contextKeys: TimelineContextKeyService;
contextMenuRenderer: ContextMenuRenderer;
hoverService: HoverService;
}
}
export class TimelineItemNode extends React.Component<TimelineItemNode.Props> {
override render(): JSX.Element | undefined {
const { label, description, tooltip, accessibilityInformation, icon } = this.props.timelineItem;
let iconString: string = '';
if (icon) {
if (typeof icon === 'string') {
iconString = codicon(icon);
} else if (isThemeIcon(icon)) {
iconString = codicon(icon.id);
}
}
return <div className='theia-TreeNodeContent'
onContextMenu={this.renderContextMenu}
onClick={this.open}
onMouseEnter={e => this.requestHover(e, tooltip)}
aria-label={accessibilityInformation?.label}
role={accessibilityInformation?.role}
>
<div className={`timeline-item noWrapInfo ${TREE_NODE_SEGMENT_GROW_CLASS} no-select`}>
<span className={`${iconString} timeline-item-icon`} />
<div className='noWrapInfo'>
<span className='timeline-item-label'>
{label}
</span>
<span className={`timeline-item-description ${TREE_NODE_INFO_CLASS}`}>
{description}
</span>
</div>
</div>
</div >;
}
protected open = () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const command: any = this.props.timelineItem.command;
if (command) {
this.props.commandRegistry.executeCommand(command.id, ...command.arguments ? command.arguments : []);
}
};
protected requestHover(e: React.MouseEvent<HTMLElement, MouseEvent>, content?: string | MarkdownString): void {
if (content) {
this.props.hoverService.requestHover({
content,
target: e.currentTarget,
position: 'right',
interactive: MarkdownString.is(content),
});
}
}
protected renderContextMenu = (event: React.MouseEvent<HTMLElement>) => {
event.preventDefault();
event.stopPropagation();
const { timelineItem, contextKeys, contextMenuRenderer } = this.props;
const currentTimelineItem = contextKeys.timelineItem.get();
contextKeys.timelineItem.set(timelineItem.contextValue);
try {
contextMenuRenderer.render({
menuPath: TIMELINE_ITEM_CONTEXT_MENU,
anchor: event.nativeEvent,
args: [timelineItem],
context: event.currentTarget
});
} finally {
contextKeys.timelineItem.set(currentTimelineItem);
}
};
}

View File

@@ -0,0 +1,179 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { Message } from '@theia/core/shared/@lumino/messaging';
import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
import {
ApplicationShell,
BaseWidget,
MessageLoop, Navigatable,
NavigatableWidget,
Panel,
PanelLayout
} from '@theia/core/lib/browser';
import { TimelineTreeWidget } from './timeline-tree-widget';
import { TimelineService, TimelineAggregate } from './timeline-service';
import { CommandRegistry, SelectionService } from '@theia/core/lib/common';
import { TimelineEmptyWidget } from './timeline-empty-widget';
import { toArray } from '@theia/core/shared/@lumino/algorithm';
import URI from '@theia/core/lib/common/uri';
import { URI as CodeURI } from '@theia/core/shared/vscode-uri';
import { nls } from '@theia/core/lib/common/nls';
@injectable()
export class TimelineWidget extends BaseWidget {
protected panel: Panel;
static ID = 'timeline-view';
private readonly timelinesBySource = new Map<string, TimelineAggregate>();
@inject(TimelineTreeWidget) protected readonly resourceWidget: TimelineTreeWidget;
@inject(TimelineService) protected readonly timelineService: TimelineService;
@inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry;
@inject(ApplicationShell) protected readonly applicationShell: ApplicationShell;
@inject(TimelineEmptyWidget) protected readonly timelineEmptyWidget: TimelineEmptyWidget;
@inject(SelectionService) protected readonly selectionService: SelectionService;
constructor() {
super();
this.id = TimelineWidget.ID;
this.title.label = nls.localizeByDefault('Timeline');
this.title.caption = this.title.label;
this.addClass('theia-timeline');
}
@postConstruct()
protected init(): void {
const layout = new PanelLayout();
this.layout = layout;
this.panel = new Panel({ layout: new PanelLayout({}) });
this.panel.node.tabIndex = -1;
layout.addWidget(this.panel);
this.containerLayout!.addWidget(this.resourceWidget);
this.containerLayout!.addWidget(this.timelineEmptyWidget);
this.refresh();
this.toDispose.push(this.timelineService.onDidChangeTimeline(event => {
const currentWidgetUri = this.getCurrentWidgetUri();
if (currentWidgetUri) {
this.loadTimeline(currentWidgetUri, event.reset);
}
})
);
this.toDispose.push(this.selectionService.onSelectionChanged(selection => {
if (Array.isArray(selection) && !!selection[0] && 'uri' in selection[0]) {
this.refresh(selection[0].uri);
}
}));
this.toDispose.push(this.applicationShell.onDidChangeCurrentWidget(async e => {
if ((e.newValue && Navigatable.is(e.newValue)) || !this.suitableWidgetsOpened()) {
this.refresh();
}
}));
this.toDispose.push(this.applicationShell.onDidRemoveWidget(widget => {
if (NavigatableWidget.is(widget)) {
this.refresh();
}
}));
this.toDispose.push(this.timelineService.onDidChangeProviders(() => this.refresh()));
}
protected async loadTimelineForSource(source: string, uri: CodeURI, reset: boolean): Promise<void> {
if (reset) {
this.timelinesBySource.delete(source);
}
let timeline = this.timelinesBySource.get(source);
const cursor = timeline?.cursor;
const options = { cursor: reset ? undefined : cursor, limit: TimelineTreeWidget.PAGE_SIZE };
const timelineResult = await this.timelineService.getTimeline(source, uri, options, { cacheResults: true, resetCache: reset });
if (timelineResult) {
const items = timelineResult.items;
if (items) {
if (timeline) {
timeline.add(items);
timeline.cursor = timelineResult.paging?.cursor;
} else {
timeline = new TimelineAggregate(timelineResult);
}
this.timelinesBySource.set(source, timeline);
this.resourceWidget.model.updateTree(timeline.items, !!timeline.cursor);
}
}
}
async loadTimeline(uri: URI, reset: boolean): Promise<void> {
for (const source of this.timelineService.getSources().map(s => s.id)) {
this.loadTimelineForSource(source, CodeURI.parse(uri.toString()), reset);
}
}
refresh(uri?: URI): void {
if (!uri) {
uri = this.getCurrentWidgetUri();
}
if (uri) {
this.timelineEmptyWidget.hide();
this.resourceWidget.show();
this.loadTimeline(uri, true);
} else if (!this.suitableWidgetsOpened()) {
this.timelineEmptyWidget.show();
this.resourceWidget.hide();
}
}
private suitableWidgetsOpened(): boolean {
return !!toArray(this.applicationShell.mainPanel.widgets()).find(widget => {
if (NavigatableWidget.is(widget)) {
const uri = widget.getResourceUri();
if (uri?.scheme && this.timelineService.getSchemas().indexOf(uri?.scheme) > -1) {
return true;
}
}
});
}
private getCurrentWidgetUri(): URI | undefined {
let current = this.applicationShell.currentWidget;
if (!NavigatableWidget.is(current)) {
current = toArray(this.applicationShell.mainPanel.widgets()).find(widget => {
if (widget.isVisible && !widget.isHidden) {
return widget;
}
});
}
return NavigatableWidget.is(current) ? current.getResourceUri() : undefined;
}
protected get containerLayout(): PanelLayout | undefined {
return this.panel.layout as PanelLayout;
}
protected override onUpdateRequest(msg: Message): void {
MessageLoop.sendMessage(this.resourceWidget, msg);
MessageLoop.sendMessage(this.timelineEmptyWidget, msg);
this.refresh();
super.onUpdateRequest(msg);
}
protected override onAfterAttach(msg: Message): void {
this.node.appendChild(this.resourceWidget.node);
this.node.appendChild(this.timelineEmptyWidget.node);
super.onAfterAttach(msg);
this.update();
}
}

View File

@@ -0,0 +1,89 @@
// *****************************************************************************
// Copyright (C) 2020 RedHat and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// some code copied and modified from https://github.com/microsoft/vscode/blob/3aab025eaebde6c9544293b6c7554f3f583e15d0/src/vs/workbench/contrib/timeline/common/timeline.ts
import { Command, Disposable, Event } from '@theia/core/lib/common';
import { URI } from '@theia/core/shared/vscode-uri';
import { ThemeIcon } from '@theia/core/lib/common/theme';
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
import { AccessibilityInformation } from '@theia/core/lib/common/accessibility';
export interface TimelineItem {
source: string;
uri: string;
handle: string;
timestamp: number;
label: string;
id?: string;
icon?: string | { light: string; dark: string } | ThemeIcon
description?: string;
tooltip?: string | MarkdownString | undefined;
command?: Command & { arguments?: unknown[] };
contextValue?: string;
accessibilityInformation?: AccessibilityInformation;
}
export interface TimelineChangeEvent {
id: string;
uri: URI | undefined;
reset: boolean
}
export interface TimelineProvidersChangeEvent {
readonly added?: string[];
readonly removed?: string[];
}
export interface TimelineOptions {
cursor?: string;
limit?: number | { timestamp: number; id?: string };
}
export interface InternalTimelineOptions {
cacheResults: boolean;
resetCache: boolean;
}
export interface Timeline {
source: string;
paging?: {
readonly cursor: string | undefined;
}
items: TimelineItem[];
}
export interface TimelineProviderDescriptor {
id: string;
label: string;
scheme: string | string[];
}
export interface TimelineProvider extends TimelineProviderDescriptor, Disposable {
onDidChange?: Event<TimelineChangeEvent>;
provideTimeline(uri: URI, options: TimelineOptions, internalOptions?: InternalTimelineOptions): Promise<Timeline | undefined>;
}
export interface TimelineSource {
id: string;
label: string;
}

View File

@@ -0,0 +1,29 @@
// *****************************************************************************
// Copyright (C) 2019 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/* note: this bogus test file is required so that
we are able to run mocha unit tests on this
package, without having any actual unit tests in it.
This way a coverage report will be generated,
showing 0% coverage, instead of no report.
This file can be removed once we have real unit
tests in place. */
describe('timeline package', () => {
it('support code coverage statistics', () => true);
});