From 05f2e07bb77637723e6720231342b9acce01a159 Mon Sep 17 00:00:00 2001 From: Vincent Fugnitto Date: Mon, 28 Oct 2019 09:06:28 -0400 Subject: [PATCH] Update the 'outline-view' documentation - updates the documentation for the `outline-view`. - in order to globally update the documentation for all `tree-widgets`, including the `outline-view-widget`, the `tree-widget.tsx` documentation was updated from `@theia/core`. Signed-off-by: Vincent Fugnitto --- .../core/src/browser/tree/tree-widget.tsx | 314 +++++++++++++++++- packages/core/src/browser/widget-manager.ts | 66 +++- .../core/src/browser/widgets/react-widget.tsx | 6 + .../src/browser/outline-view-contribution.ts | 10 + .../browser/outline-view-frontend-module.ts | 9 + .../src/browser/outline-view-service.ts | 5 + .../src/browser/outline-view-tree.ts | 8 +- .../src/browser/outline-view-widget.tsx | 39 +++ 8 files changed, 436 insertions(+), 21 deletions(-) diff --git a/packages/core/src/browser/tree/tree-widget.tsx b/packages/core/src/browser/tree/tree-widget.tsx index ad718dfcec1d5..9f99cdeb8048e 100644 --- a/packages/core/src/browser/tree/tree-widget.tsx +++ b/packages/core/src/browser/tree/tree-widget.tsx @@ -53,6 +53,10 @@ export const COMPOSITE_TREE_NODE_CLASS = 'theia-CompositeTreeNode'; export const TREE_NODE_CAPTION_CLASS = 'theia-TreeNodeCaption'; export const TreeProps = Symbol('TreeProps'); + +/** + * Representation of tree properties. + */ export interface TreeProps { /** @@ -72,17 +76,17 @@ export interface TreeProps { readonly multiSelect?: boolean; /** - * 'true' if the tree widget support searching. Otherwise, `false`. Defaults to `false`. + * `true` if the tree widget support searching. Otherwise, `false`. Defaults to `false`. */ readonly search?: boolean /** - * 'true' if the tree widget should be virtualized searching. Otherwise, `false`. Defaults to `true`. + * `true` if the tree widget should be virtualized searching. Otherwise, `false`. Defaults to `true`. */ readonly virtualized?: boolean /** - * 'true' if the selected node should be auto scrolled only if the widget is active. Otherwise, `false`. Defaults to `false`. + * `true` if the selected node should be auto scrolled only if the widget is active. Otherwise, `false`. Defaults to `false`. */ readonly scrollIfActive?: boolean @@ -92,6 +96,9 @@ export interface TreeProps { readonly globalSelection?: boolean; } +/** + * Representation of node properties. + */ export interface NodeProps { /** @@ -101,6 +108,9 @@ export interface NodeProps { } +/** + * The default tree properties. + */ export const defaultTreeProps: TreeProps = { leftPadding: 8 }; @@ -111,8 +121,17 @@ export namespace TreeWidget { * Bare minimum common interface of the keyboard and the mouse event with respect to the key maskings. */ export interface ModifierAwareEvent { + /** + * Determines if the modifier aware event has the `meta` key masking. + */ readonly metaKey: boolean; + /** + * Determines if the modifier aware event has the `ctrl` key masking. + */ readonly ctrlKey: boolean; + /** + * Determines if the modifier aware event has the `shift` key masking. + */ readonly shiftKey: boolean; } @@ -218,6 +237,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Update the global selection for the tree. + */ protected updateGlobalSelection(): void { this.selectionService.selection = TreeWidgetSelection.create(this); } @@ -252,12 +274,25 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { this.updateScrollToRow(); } + /** + * Row index to ensure visibility. + * - Used to forcefully scroll if necessary. + */ protected scrollToRow: number | undefined; + /** + * Update the `scrollToRow`. + * @param updateOptions the tree widget force update options. + */ protected updateScrollToRow(updateOptions?: TreeWidget.ForceUpdateOptions): void { this.scrollToRow = this.getScrollToRow(); this.forceUpdate(updateOptions); } + /** + * Get the `scrollToRow`. + * + * @returns the `scrollToRow` if available. + */ protected getScrollToRow(): number | undefined { if (!this.shouldScrollToRow) { return undefined; @@ -268,6 +303,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return row && row.index; } + /** + * Update tree decorations. + * - Updating decorations are debounced in order to limit the number of expensive updates. + */ protected readonly updateDecorations = debounce(() => this.doUpdateDecorations(), 150); protected async doUpdateDecorations(): Promise { this.decorations = await this.decoratorService.getDecorations(this.model); @@ -295,6 +334,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { this.node.focus(); } + /** + * Actually focus the tree node. + */ protected doFocus(): void { if (!this.model.selectedNodes.length) { const node = this.getNodeToFocus(); @@ -302,13 +344,18 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { this.model.selectNode(node); } } - // it has to be called after nodes are selected + // It has to be called after nodes are selected. if (this.props.globalSelection) { this.updateGlobalSelection(); } this.forceUpdate(); } + /** + * Get the tree node to focus. + * + * @returns the node to focus if available. + */ protected getNodeToFocus(): SelectableTreeNode | undefined { const root = this.model.root; if (SelectableTreeNode.isVisible(root)) { @@ -333,6 +380,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return React.createElement('div', this.createContainerAttributes(), this.renderTree(this.model)); } + /** + * Create the container attributes for the widget. + */ protected createContainerAttributes(): React.HTMLAttributes { const classNames = [TREE_CONTAINER_CLASS]; if (!this.rows.size) { @@ -343,11 +393,20 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { onContextMenu: event => this.handleContextMenuEvent(this.getContainerTreeNode(), event) }; } + /** + * Get the container tree node. + * + * @returns the tree node for the container if available. + */ protected getContainerTreeNode(): TreeNode | undefined { return this.model.root; } protected view: TreeWidget.View | undefined; + /** + * Render the tree widget. + * @param model the tree model. + */ protected renderTree(model: TreeModel): React.ReactNode { if (model.root) { const rows = Array.from(this.rows.values()); @@ -370,6 +429,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } scrollArea: Element = this.node; + /** + * Scroll to the selected tree node. + */ protected scrollToSelected(): void { if (this.props.scrollIfActive === true && !this.node.contains(document.activeElement)) { return; @@ -385,21 +447,42 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Handle the scroll event. + */ protected readonly handleScroll = (info: ScrollParams) => { this.node.scrollTop = info.scrollTop; } + /** + * Render the node row. + */ protected readonly renderNodeRow = (row: TreeWidget.NodeRow) => this.doRenderNodeRow(row); + /** + * Actually render the node row. + */ protected doRenderNodeRow({ index, node, depth }: TreeWidget.NodeRow): React.ReactNode { return this.renderNode(node, { depth }); } + /** + * Render the tree node given the node properties. + * @param node the tree node. + * @param props the node properties. + */ protected renderIcon(node: TreeNode, props: NodeProps): React.ReactNode { // tslint:disable-next-line:no-null-keyword return null; } + /** + * Toggle the node. + */ protected readonly toggle = (event: React.MouseEvent) => this.doToggle(event); + /** + * Actually toggle the tree node. + * @param event the mouse click event. + */ protected doToggle(event: React.MouseEvent): void { const nodeId = event.currentTarget.getAttribute('data-node-id'); if (nodeId) { @@ -409,6 +492,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { event.stopPropagation(); } + /** + * Render the node expansion toggle. + * @param node the tree node. + * @param props the node properties. + */ protected renderExpansionToggle(node: TreeNode, props: NodeProps): React.ReactNode { if (!this.isExpandable(node)) { // tslint:disable-next-line:no-null-keyword @@ -427,6 +515,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { ; } + /** + * Render the tree node caption given the node properties. + * @param node the tree node. + * @param props the node properties. + */ protected renderCaption(node: TreeNode, props: NodeProps): React.ReactNode { const tooltip = this.getDecorationData(node, 'tooltip').filter(notEmpty).join(' • '); const classes = [TREE_NODE_SEGMENT_CLASS]; @@ -458,6 +551,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return React.createElement('div', attrs, ...children); } + /** + * Update the node given the caption and highlight. + * @param caption the caption. + * @param highlight the tree decoration caption highlight. + */ protected toReactNode(caption: string, highlight: TreeDecoration.CaptionHighlight): React.ReactNode[] { let style: React.CSSProperties = {}; if (highlight.color) { @@ -483,6 +581,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return TreeDecoration.CaptionHighlight.split(caption, highlight).map(createChildren); } + /** + * Decorate the tree caption. + * @param node the tree node. + * @param attrs the additional attributes. + */ protected decorateCaption(node: TreeNode, attrs: React.HTMLAttributes): React.Attributes & React.HTMLAttributes { const style = this.getDecorationData(node, 'fontData').filter(notEmpty).reverse().map(fontData => this.applyFontStyles({}, fontData)).reduce((acc, current) => ({ @@ -496,10 +599,21 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { }; } + /** + * Determine if the tree node contains trailing suffixes. + * @param node the tree node. + * + * @returns `true` if the tree node contains trailing suffices. + */ protected hasTrailingSuffixes(node: TreeNode): boolean { return this.getDecorationData(node, 'captionSuffixes').filter(notEmpty).reduce((acc, current) => acc.concat(current), []).length > 0; } + /** + * Apply font styles to the tree. + * @param original the original css properties. + * @param fontData the optional `fontData`. + */ protected applyFontStyles(original: React.CSSProperties, fontData: TreeDecoration.FontData | undefined): React.CSSProperties { if (fontData === undefined) { return original; @@ -544,6 +658,12 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return modified; } + /** + * Render caption affixes for the given tree node. + * @param node the tree node. + * @param props the node properties. + * @param affixKey the affix key. + */ protected renderCaptionAffixes(node: TreeNode, props: NodeProps, affixKey: 'captionPrefixes' | 'captionSuffixes'): React.ReactNode { const suffix = affixKey === 'captionSuffixes'; const affixClass = suffix ? TreeDecoration.Styles.CAPTION_SUFFIX_CLASS : TreeDecoration.Styles.CAPTION_PREFIX_CLASS; @@ -568,6 +688,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return {children}; } + /** + * Decorate the tree node icon. + * @param node the tree node. + * @param icon the icon. + */ protected decorateIcon(node: TreeNode, icon: React.ReactNode | null): React.ReactNode { if (icon === null) { // tslint:disable-next-line:no-null-keyword @@ -595,6 +720,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return icon; } + /** + * Render the tree node tail decorations. + * @param node the tree node. + * @param props the node properties. + */ protected renderTailDecorations(node: TreeNode, props: NodeProps): React.ReactNode { return {this.getDecorationData(node, 'tailDecorations').filter(notEmpty).reduce((acc, current) => acc.concat(current), []).map((decoration, index) => { @@ -612,13 +742,24 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { ; } - // Determine the classes to use for an icon - // Assumes a Font Awesome name when passed a single string, otherwise uses the passed string array + /** + * Determine the classes to use for an icon + * - Assumes a Font Awesome name when passed a single string, otherwise uses the passed string array + * @param iconName the icon name or list of icon names. + * @param additionalClasses additional CSS classes. + * + * @returns the icon class name. + */ private getIconClass(iconName: string | string[], additionalClasses: string[] = []): string { const iconClass = (typeof iconName === 'string') ? ['a', 'fa', `fa-${iconName}`] : ['a'].concat(iconName); return iconClass.concat(additionalClasses).join(' '); } + /** + * Render the node given the tree node and node properties. + * @param node the tree node. + * @param props the node properties. + */ protected renderNode(node: TreeNode, props: NodeProps): React.ReactNode { if (!TreeNode.isVisible(node)) { return undefined; @@ -635,6 +776,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return React.createElement('div', attributes, content); } + /** + * Create node attributes for the tree node given the node properties. + * @param node the tree node. + * @param props the node properties. + */ protected createNodeAttributes(node: TreeNode, props: NodeProps): React.Attributes & React.HTMLAttributes { const className = this.createNodeClassNames(node, props).join(' '); const style = this.createNodeStyle(node, props); @@ -647,6 +793,13 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { }; } + /** + * Create the node class names. + * @param node the tree node. + * @param props the node properties. + * + * @returns the list of tree node class names. + */ protected createNodeClassNames(node: TreeNode, props: NodeProps): string[] { const classNames = [TREE_NODE_CLASS]; if (CompositeTreeNode.is(node)) { @@ -664,6 +817,13 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return classNames; } + /** + * Get the default node style. + * @param node the tree node. + * @param props the node properties. + * + * @returns the CSS properties if available. + */ protected getDefaultNodeStyle(node: TreeNode, props: NodeProps): React.CSSProperties | undefined { // If the node is a composite, a toggle will be rendered. Otherwise we need to add the width and the left, right padding => 18px const paddingLeft = `${props.depth * this.props.leftPadding + (this.isExpandable(node) ? 0 : 18)}px`; @@ -672,10 +832,22 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { }; } + /** + * Create the tree node style. + * @param node the tree node. + * @param props the node properties. + */ protected createNodeStyle(node: TreeNode, props: NodeProps): React.CSSProperties | undefined { return this.decorateNodeStyle(node, this.getDefaultNodeStyle(node, props)); } + /** + * Decorate the node style. + * @param node the tree node. + * @param style the optional CSS properties. + * + * @returns the CSS styles if available. + */ protected decorateNodeStyle(node: TreeNode, style: React.CSSProperties | undefined): React.CSSProperties | undefined { const backgroundColor = this.getDecorationData(node, 'backgroundColor').filter(notEmpty).shift(); if (backgroundColor) { @@ -687,10 +859,22 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return style; } + /** + * Determine if the tree node is expandable. + * @param node the tree node. + * + * @returns `true` if the tree node is expandable. + */ protected isExpandable(node: TreeNode): node is ExpandableTreeNode { return ExpandableTreeNode.is(node); } + /** + * Get the tree node decorations. + * @param node the tree node. + * + * @returns the list of tree decoration data. + */ protected getDecorations(node: TreeNode): TreeDecoration.Data[] { const decorations: TreeDecoration.Data[] = []; if (DecoratedTreeNode.is(node)) { @@ -702,15 +886,34 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return decorations.sort(TreeDecoration.Data.comparePriority); } + /** + * Get the tree decoration data for the given key. + * @param node the tree node. + * @param key the tree decoration data key. + * + * @returns the tree decoration data at the given key. + */ protected getDecorationData(node: TreeNode, key: K): TreeDecoration.Data[K][] { return this.getDecorations(node).filter(data => data[key] !== undefined).map(data => data[key]).filter(notEmpty); } + /** + * Store the last scroll state. + */ protected lastScrollState: { + /** + * The scroll top value. + */ scrollTop: number, + /** + * The scroll left value. + */ scrollLeft: number } | undefined; + /** + * Get the scroll container. + */ protected getScrollContainer(): MaybePromise { this.toDisposeOnDetach.push(Disposable.create(() => { const { scrollTop, scrollLeft } = this.node; @@ -759,6 +962,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { this.addEventListener(this.node, 'focus', () => this.doFocus()); } + /** + * Handle the `left arrow` keyboard event. + * @param event the `left arrow` keyboard event. + */ protected async handleLeft(event: KeyboardEvent): Promise { if (!!this.props.multiSelect && (this.hasCtrlCmdMask(event) || this.hasShiftMask(event))) { return; @@ -768,6 +975,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Handle the `right arrow` keyboard event. + * @param event the `right arrow` keyboard event. + */ protected async handleRight(event: KeyboardEvent): Promise { if (!!this.props.multiSelect && (this.hasCtrlCmdMask(event) || this.hasShiftMask(event))) { return; @@ -777,6 +988,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Handle the `up arrow` keyboard event. + * @param event the `up arrow` keyboard event. + */ protected handleUp(event: KeyboardEvent): void { if (!!this.props.multiSelect && this.hasShiftMask(event)) { this.model.selectPrevNode(TreeSelection.SelectionType.RANGE); @@ -785,6 +1000,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Handle the `down arrow` keyboard event. + * @param event the `down arrow` keyboard event. + */ protected handleDown(event: KeyboardEvent): void { if (!!this.props.multiSelect && this.hasShiftMask(event)) { this.model.selectNextNode(TreeSelection.SelectionType.RANGE); @@ -793,10 +1012,20 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Handle the `enter key` keyboard event. + * - `enter` opens the tree node. + * @param event the `enter key` keyboard event. + */ protected handleEnter(event: KeyboardEvent): void { this.model.openNode(); } + /** + * Handle the single-click mouse event. + * @param node the tree node if available. + * @param event the mouse single-click event. + */ protected handleClickEvent(node: TreeNode | undefined, event: React.MouseEvent): void { if (node) { if (!!this.props.multiSelect) { @@ -826,11 +1055,22 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } } + /** + * Handle the double-click mouse event. + * @param node the tree node if available. + * @param event the double-click mouse event. + */ protected handleDblClickEvent(node: TreeNode | undefined, event: React.MouseEvent): void { this.model.openNode(node); event.stopPropagation(); } + /** + * Handle the context menu click event. + * - The context menu click event is triggered by the right-click. + * @param node the tree node if available. + * @param event the right-click mouse event. + */ protected handleContextMenuEvent(node: TreeNode | undefined, event: React.MouseEvent): void { if (SelectableTreeNode.is(node)) { // Keep the selection for the context menu, if the widget support multi-selection and the right click happens on an already selected node. @@ -856,16 +1096,32 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { event.preventDefault(); } + /** + * Convert the tree node to context menu arguments. + * @param node the selectable tree node. + */ // tslint:disable-next-line:no-any protected toContextMenuArgs(node: SelectableTreeNode): any[] | undefined { return undefined; } + /** + * Determine if the tree modifier aware event has a `ctrlcmd` mask. + * @param event the tree modifier aware event. + * + * @returns `true` if the tree modifier aware event contains the `ctrlcmd` mask. + */ protected hasCtrlCmdMask(event: TreeWidget.ModifierAwareEvent): boolean { const { metaKey, ctrlKey } = event; return (isOSX && metaKey) || ctrlKey; } + /** + * Determine if the tree modifier aware event has a `shift` mask. + * @param event the tree modifier aware event. + * + * @returns `true` if the tree modifier aware event contains the `shift` mask. + */ protected hasShiftMask(event: TreeWidget.ModifierAwareEvent): boolean { // Ctrl/Cmd mask overrules the Shift mask. if (this.hasCtrlCmdMask(event)) { @@ -874,6 +1130,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return event.shiftKey; } + /** + * Deflate the tree node for storage. + * @param node the tree node. + */ protected deflateForStorage(node: TreeNode): object { // tslint:disable-next-line:no-any const copy = Object.assign({}, node) as any; @@ -895,6 +1155,11 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return copy; } + /** + * Inflate the tree node from storage. + * @param node the tree node. + * @param parent the optional tree node. + */ // tslint:disable-next-line:no-any protected inflateFromStorage(node: any, parent?: TreeNode): TreeNode { if (node.selected) { @@ -911,6 +1176,9 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return node; } + /** + * Store the tree state. + */ storeState(): object { const decorations = this.decoratorService.deflateDecorators(this.decorations); let state: object = { @@ -927,6 +1195,10 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { return state; } + /** + * Restore the state. + * @param oldState the old state object. + */ restoreState(oldState: object): void { // tslint:disable-next-line:no-any const { root, decorations, model } = (oldState as any); @@ -943,21 +1215,51 @@ export class TreeWidget extends ReactWidget implements StatefulWidget { } export namespace TreeWidget { + /** + * Representation of the tree force update options. + */ export interface ForceUpdateOptions { + /** + * Controls whether to force a resize of the widget. + */ resize: boolean } + /** + * Representation of a tree node row. + */ export interface NodeRow { + /** + * The node row index. + */ index: number + /** + * The actual node. + */ node: TreeNode /** * A root relative number representing the hierarchical depth of the actual node. Root is `0`, its children have `1` and so on. */ depth: number } + /** + * Representation of the tree view properties. + */ export interface ViewProps { + /** + * The width property. + */ width: number + /** + * The height property. + */ height: number + /** + * The scroll to row value. + */ scrollToRow?: number + /** + * The list of node rows. + */ rows: NodeRow[] handleScroll: (info: ScrollParams) => void renderNodeRow: (row: NodeRow) => React.ReactNode diff --git a/packages/core/src/browser/widget-manager.ts b/packages/core/src/browser/widget-manager.ts index ec1cb61ad5bb8..082714bd92551 100644 --- a/packages/core/src/browser/widget-manager.ts +++ b/packages/core/src/browser/widget-manager.ts @@ -25,35 +25,45 @@ export const WidgetFactory = Symbol('WidgetFactory'); */ export interface WidgetFactory { - /* - * the factory's id + /** + * The factory id. */ readonly id: string; /** - * Creates a widget and attaches it to the shell - * The options need to be serializable JSON data. + * Creates a widget and attaches it to the application shell. + * @param options serializable JSON data. */ createWidget(options?: any): MaybePromise; } -/* - * a serializable description to create a widget +/** + * Representation of the `WidgetConstructionOptions`. + * Defines a serializable description to create widgets. */ export interface WidgetConstructionOptions { /** - * the id of the widget factory to use. + * The id of the widget factory to use. */ factoryId: string, - /* - * widget factory specific information + /** + * The widget factory specific information. */ options?: any } +/** + * Representation of a `didCreateWidgetEvent`. + */ export interface DidCreateWidgetEvent { + /** + * The widget which was created. + */ readonly widget: Widget; + /** + * The widget factory id. + */ readonly factoryId: string; } @@ -77,6 +87,12 @@ export class WidgetManager { protected readonly onDidCreateWidgetEmitter = new Emitter(); readonly onDidCreateWidget: Event = this.onDidCreateWidgetEmitter.event; + /** + * Get the list of widgets created for the given factory id. + * @param factoryId the widget factory id. + * + * @returns the list of widgets created for the given factory id. + */ getWidgets(factoryId: string): Widget[] { const result: Widget[] = []; for (const [key, widget] of this.widgets.entries()) { @@ -87,6 +103,11 @@ export class WidgetManager { return result; } + /** + * Try and get the widget. + * + * @returns the widget if available, else `undefined`. + */ tryGetWidget(factoryId: string, options?: any): T | undefined { const key = this.toKey({ factoryId, options }); const existing = this.widgetPromises.get(key); @@ -97,7 +118,9 @@ export class WidgetManager { } /** - * return the widget for the given description + * Get the widget for the given description. + * + * @returns a promise resolving to the widget if available, else `undefined. */ async getWidget(factoryId: string, options?: any): Promise { const key = this.toKey({ factoryId, options }); @@ -114,8 +137,8 @@ export class WidgetManager { return undefined; } - /* - * creates or returns the widget for the given description. + /** + * Creates or returns the widget for the given description. */ async getOrCreateWidget(factoryId: string, options?: any): Promise { const key = this.toKey({ factoryId, options }); @@ -146,8 +169,11 @@ export class WidgetManager { } } - /* - * returns the construction description for the given widget, or undefined if the widget was not created through this manager. + /** + * Get the widget construction options. + * @param widget the widget. + * + * @returns the widget construction options if the widget was created through the manager, else `undefined`. */ getDescription(widget: Widget): WidgetConstructionOptions | undefined { for (const [key, aWidget] of this.widgets.entries()) { @@ -158,10 +184,22 @@ export class WidgetManager { return undefined; } + /** + * Convert the widget construction options to string. + * @param options the widget construction options. + * + * @returns the widget construction options represented as a string. + */ protected toKey(options: WidgetConstructionOptions): string { return JSON.stringify(options); } + /** + * Convert the key into the widget construction options object. + * @param key the key. + * + * @returns the widget construction options object. + */ protected fromKey(key: string): WidgetConstructionOptions { return JSON.parse(key); } diff --git a/packages/core/src/browser/widgets/react-widget.tsx b/packages/core/src/browser/widgets/react-widget.tsx index 9c0dde62f0549..6065c56aaa8c3 100644 --- a/packages/core/src/browser/widgets/react-widget.tsx +++ b/packages/core/src/browser/widgets/react-widget.tsx @@ -41,5 +41,11 @@ export abstract class ReactWidget extends BaseWidget { ReactDOM.render({this.render()}, this.node, () => this.onRender.dispose()); } + /** + * Render the React widget in the DOM. + * - If the widget has been previously rendered, + * any subsequent calls will perform an update and only + * change the DOM if absolutely necessary. + */ protected abstract render(): React.ReactNode; } diff --git a/packages/outline-view/src/browser/outline-view-contribution.ts b/packages/outline-view/src/browser/outline-view-contribution.ts index 3ac25de1e25c4..0770477885099 100644 --- a/packages/outline-view/src/browser/outline-view-contribution.ts +++ b/packages/outline-view/src/browser/outline-view-contribution.ts @@ -25,7 +25,14 @@ import { CompositeTreeNode } from '@theia/core/lib/browser/tree'; export const OUTLINE_WIDGET_FACTORY_ID = 'outline-view'; +/** + * Collection of `outline-view` commands. + */ export namespace OutlineViewCommands { + /** + * Command which collapses all nodes + * from the `outline-view` tree. + */ export const COLLAPSE_ALL: Command = { id: 'outlineView.collapse.all', iconClass: 'collapse-all' @@ -81,6 +88,9 @@ export class OutlineViewContribution extends AbstractViewContribution(widget: Widget | undefined = this.tryGetWidget(), cb: (widget: OutlineViewWidget) => T): T | false { if (widget instanceof OutlineViewWidget && widget.id === OUTLINE_WIDGET_FACTORY_ID) { return cb(widget); diff --git a/packages/outline-view/src/browser/outline-view-frontend-module.ts b/packages/outline-view/src/browser/outline-view-frontend-module.ts index 0558e338ffabd..8c54193dd75c1 100644 --- a/packages/outline-view/src/browser/outline-view-frontend-module.ts +++ b/packages/outline-view/src/browser/outline-view-frontend-module.ts @@ -49,6 +49,15 @@ export default new ContainerModule(bind => { bind(TabBarToolbarContribution).toService(OutlineViewContribution); }); +/** + * Create an `OutlineViewWidget`. + * - The creation of the `OutlineViewWidget` includes: + * - The creation of the tree widget itself with it's own customized props. + * - The binding of necessary components into the container. + * @param parent the Inversify container. + * + * @returns the `OutlineViewWidget`. + */ function createOutlineViewWidget(parent: interfaces.Container): OutlineViewWidget { const child = createTreeContainer(parent); diff --git a/packages/outline-view/src/browser/outline-view-service.ts b/packages/outline-view/src/browser/outline-view-service.ts index 0252677b17b54..c2c21999a2ea2 100644 --- a/packages/outline-view/src/browser/outline-view-service.ts +++ b/packages/outline-view/src/browser/outline-view-service.ts @@ -53,6 +53,11 @@ export class OutlineViewService implements WidgetFactory { return this.widget !== undefined && this.widget.isVisible; } + /** + * Publish the collection of outline view symbols. + * - Publishing includes setting the `OutlineViewWidget` tree with symbol information. + * @param roots the list of outline symbol information nodes. + */ publish(roots: OutlineSymbolInformationNode[]): void { if (this.widget) { this.widget.setOutlineTree(roots); diff --git a/packages/outline-view/src/browser/outline-view-tree.ts b/packages/outline-view/src/browser/outline-view-tree.ts index 31fd0937b97bb..36d112b547796 100644 --- a/packages/outline-view/src/browser/outline-view-tree.ts +++ b/packages/outline-view/src/browser/outline-view-tree.ts @@ -22,8 +22,14 @@ export class OutlineViewTreeModel extends TreeModelImpl { @inject(TreeExpansionService) protected readonly expansionService: TreeExpansionService; + /** + * Handle the expansion of the tree node. + * - The method is a no-op in order to preserve focus on the editor + * after attempting to perform a `collapse-all`. + * @param node the expandable tree node. + */ protected handleExpansion(node: Readonly): void { - // Do not select anything in order to preserve focus on the editor. + // no-op } async collapseAll(raw?: Readonly): Promise { diff --git a/packages/outline-view/src/browser/outline-view-widget.tsx b/packages/outline-view/src/browser/outline-view-widget.tsx index 1848760160039..b93541574c0e7 100644 --- a/packages/outline-view/src/browser/outline-view-widget.tsx +++ b/packages/outline-view/src/browser/outline-view-widget.tsx @@ -31,11 +31,30 @@ import { Emitter } from '@theia/core'; import { CompositeTreeNode } from '@theia/core/lib/browser'; import * as React from 'react'; +/** + * Representation of an outline symbol information node. + */ export interface OutlineSymbolInformationNode extends CompositeTreeNode, SelectableTreeNode, ExpandableTreeNode { + /** + * The `iconClass` for the given tree node. + */ iconClass: string; } +/** + * Collection of outline symbol information node functions. + */ export namespace OutlineSymbolInformationNode { + /** + * Determine if the given tree node is an `OutlineSymbolInformationNode`. + * - The tree node is an `OutlineSymbolInformationNode` if: + * - The node exists. + * - The node is selectable. + * - The node contains a defined `iconClass` property. + * @param node the tree node. + * + * @returns `true` if the given node is an `OutlineSymbolInformationNode`. + */ export function is(node: TreeNode): node is OutlineSymbolInformationNode { return !!node && SelectableTreeNode.is(node) && 'iconClass' in node; } @@ -64,8 +83,14 @@ export class OutlineViewWidget extends TreeWidget { this.addClass('theia-outline-view'); } + /** + * Set the outline tree with the list of `OutlineSymbolInformationNode`. + * @param roots the list of `OutlineSymbolInformationNode`. + */ public setOutlineTree(roots: OutlineSymbolInformationNode[]): void { + // Gather the list of available nodes. const nodes = this.reconcileTreeState(roots); + // Update the model root node, appending the outline symbol information nodes as children. this.model.root = { id: 'outline-view-root', name: 'Outline Root', @@ -75,6 +100,12 @@ export class OutlineViewWidget extends TreeWidget { } as CompositeTreeNode; } + /** + * Reconcile the outline tree state, gathering all available nodes. + * @param nodes the list of `TreeNode`. + * + * @returns the list of tree nodes. + */ protected reconcileTreeState(nodes: TreeNode[]): TreeNode[] { nodes.forEach(node => { if (OutlineSymbolInformationNode.is(node)) { @@ -114,6 +145,14 @@ export class OutlineViewWidget extends TreeWidget { }; } + /** + * Get the tooltip for the given tree node. + * - The tooltip is discovered when hovering over a tree node. + * - If available, the tooltip is the concatenation of the node name, and it's type. + * @param node the tree node. + * + * @returns the tooltip for the tree node if available, else `undefined`. + */ protected getNodeTooltip(node: TreeNode): string | undefined { if (OutlineSymbolInformationNode.is(node)) { return node.name + ` (${node.iconClass})`;