Skip to content

Commit

Permalink
Fixed #1278: implemented tabbar decorator & supported error marker in…
Browse files Browse the repository at this point in the history
… editor tabs

- Implemented `TabBarDecorator` that provides tabs with decorations, similar to what we already had for tree nodes.
- Supported diagnostic problem markers (error, warning, ...) in editor tabs in the main area. Tabs in side bars can be decorated as well in the future using the same code.
- Refactored `TreeDecoration` to a more generic `NodeDecoration`, which is currently used for decorating tree nodes and tabbar tabs.

Signed-off-by: fangnx <naxin.fang@ericsson.com>
  • Loading branch information
fangnx authored and fangnx committed Aug 14, 2019
1 parent e3dfbea commit f26e104
Show file tree
Hide file tree
Showing 18 changed files with 809 additions and 521 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [task] displayed the customized tasks as "configured tasks" in the task quick open [#5777](https://github.com/theia-ide/theia/pull/5777)
- [task] allowed users to override any task properties other than the ones used in the task definition [#5777](https://github.com/theia-ide/theia/pull/5777)
- [outline] added `OutlineViewTreeModel` for the outline view tree widget [#5687](https://github.com/theia-ide/theia/pull/5687)
- [core] supported diagnostic marker in the tab bar [#5845](https://github.com/theia-ide/theia/pull/5845)

Breaking changes:

Expand All @@ -21,6 +22,7 @@ Breaking changes:
- `Source Control` and `Explorer` are view containers now and previous layout data cannot be loaded for them. Because of it the layout is completely reset.
- [vscode] complete support of variable substitution [#5835](https://github.com/theia-ide/theia/pull/5835)
- inline `VariableQuickOpenItem`
- [core] refactored `TreeDecoration` to `NodeDecoration` and moved it to shell, since it is a generic decoration that can be used by different types of nodes (currently by tree nodes and tabbar tabs) [#5845](https://github.com/theia-ide/theia/pull/5845)

## v0.9.0
- [core] added `theia-widget-noInfo` css class to be used by widgets when displaying no information messages [#5717](https://github.com/theia-ide/theia/pull/5717)
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import { ViewContainer, ViewContainerIdentifier } from './view-container';
import { QuickViewService } from './quick-view-service';
import { QuickTitleBar } from './quick-open/quick-title-bar';
import { DialogOverlayService } from './dialogs';
import { TabBarDecoratorService, TabBarDecorator } from './shell/tab-bar-decorator';

export const frontendApplicationModule = new ContainerModule((bind, unbind, isBound, rebind) => {
const themeService = ThemeService.get();
Expand Down Expand Up @@ -114,9 +115,13 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
bind(DockPanelRenderer).toSelf();
bind(TabBarRendererFactory).toFactory(context => () => {
const contextMenuRenderer = context.container.get<ContextMenuRenderer>(ContextMenuRenderer);
return new TabBarRenderer(contextMenuRenderer);
const decoratorService = context.container.get<TabBarDecoratorService>(TabBarDecoratorService);
return new TabBarRenderer(contextMenuRenderer, decoratorService);
});

bindContributionProvider(bind, TabBarDecorator);
bind(TabBarDecoratorService).toSelf().inSingletonScope();

bindContributionProvider(bind, OpenHandler);
bind(DefaultOpenerService).toSelf().inSingletonScope();
bind(OpenerService).toService(DefaultOpenerService);
Expand Down
355 changes: 355 additions & 0 deletions packages/core/src/browser/node-decoration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,355 @@
/********************************************************************************
* Copyright (C) 2019 Ericsson 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 WITH Classpath-exception-2.0
********************************************************************************/

import { TreeNode } from './tree/tree';

/**
* Namespace for the decoration data and the styling refinements for the decorated nodes.
*/
export namespace NodeDecoration {

/**
* CSS styles for the decorators.
*/
export namespace Styles {
export const CAPTION_HIGHLIGHT_CLASS = 'theia-caption-highlight';
export const CAPTION_PREFIX_CLASS = 'theia-caption-prefix';
export const CAPTION_SUFFIX_CLASS = 'theia-caption-suffix';
export const ICON_WRAPPER_CLASS = 'theia-icon-wrapper';
export const DECORATOR_SIZE_CLASS = 'theia-decorator-size';
export const TOP_RIGHT_CLASS = 'theia-top-right';
export const BOTTOM_RIGHT_CLASS = 'theia-bottom-right';
export const BOTTOM_LEFT_CLASS = 'theia-bottom-left';
export const TOP_LEFT_CLASS = 'theia-top-left';
}
/**
* For the sake of simplicity, we have merged the `font-style`, `font-weight`, and the `text-decoration` together.
*/
export type FontStyle = 'normal' | 'bold' | 'italic' | 'oblique' | 'underline' | 'line-through';
/**
* A string that could be:
*
* - one of the browser colors, (E.g.: `blue`, `red`, `magenta`),
* - the case insensitive hexadecimal color code, (for instance, `#ee82ee`, `#20B2AA`, `#f09` ), or
* - either the `rgb()` or the `rgba()` functions.
*
* For more details, see: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value.
*
* Note, it is highly recommended to use one of the predefined colors of Theia, so the desired color will
* look nice with both the `light` and the `dark` theme too.
*/
export type Color = string;
/**
* Encapsulates styling information of the font.
*/
export interface FontData {
/**
* Zero to any font style.
*/
readonly style?: FontStyle | FontStyle[];
/**
* The color of the font.
*/
readonly color?: Color;
}
/**
* Arbitrary information that has to be shown either before or after the caption as a prefix or a suffix.
*/
export interface CaptionAffix {
/**
* The text content of the prefix or the suffix.
*/
readonly data: string;
/**
* Font data for customizing the prefix of the suffix.
*/
readonly fontData?: FontData;
}
export interface BaseTailDecoration {
/**
* Optional tooltip for the tail decoration.
*/
readonly tooltip?: string;
}
/**
* Unlike caption suffixes, tail decorations appears right-aligned after the caption and the caption suffixes (is any).
*/
export interface TailDecoration extends BaseTailDecoration {
/**
* The text content of the tail decoration.
*/
readonly data: string;
/**
* Font data for customizing the content.
*/
readonly fontData?: FontData;
}
export interface TailDecorationIcon extends BaseTailDecoration {
/**
* This should be the name of the Font Awesome icon with out the `fa fa-` prefix, just the name, for instance `paw`.
* For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/.
*/
readonly icon: string;
/**
* The color of the icon.
*/
readonly color?: Color;
}
export interface TailDecorationIconClass extends BaseTailDecoration {
/**
* This should be the entire Font Awesome class array, for instance ['fa', 'fa-paw']
* For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/.
*/
readonly iconClass: string[];
/**
* The color of the icon.
*/
readonly color?: Color;
}
/**
* Enumeration for the quadrant to overlay the image on.
*/
export enum IconOverlayPosition {
/**
* Overlays the top right quarter of the original image.
*/
TOP_RIGHT,
/**
* Overlays the bottom right of the original image.
*/
BOTTOM_RIGHT,
/**
* Overlays the bottom left segment of the original image.
*/
BOTTOM_LEFT,
/**
* Occupies the top left quarter of the original icon.
*/
TOP_LEFT
}
export namespace IconOverlayPosition {
/**
* Returns with the CSS class style for the enum.
*/
export function getStyle(position: IconOverlayPosition): string {
switch (position) {
case IconOverlayPosition.TOP_RIGHT: return NodeDecoration.Styles.TOP_RIGHT_CLASS;
case IconOverlayPosition.BOTTOM_RIGHT: return NodeDecoration.Styles.BOTTOM_RIGHT_CLASS;
case IconOverlayPosition.BOTTOM_LEFT: return NodeDecoration.Styles.BOTTOM_LEFT_CLASS;
case IconOverlayPosition.TOP_LEFT: return NodeDecoration.Styles.TOP_LEFT_CLASS;
}
}
}
/**
* A shape that can be optionally rendered behind the overlay icon. Can be used to further refine colors.
*/
export interface IconOverlayBackground {
/**
* Either `circle` or `square`.
*/
readonly shape: 'circle' | 'square';
/**
* The color of the background shape.
*/
readonly color?: Color;
}
/**
* Has not effect if the node being decorated has no associated icon.
*/
export interface BaseOverlay {
/**
* The position where the decoration will be placed on the top of the original icon.
*/
readonly position: IconOverlayPosition;
/**
* The color of the overlaying icon. If not specified, then the default icon color will be used.
*/
readonly color?: Color;
/**
* The optional background color of the overlay icon.
*/
readonly background?: IconOverlayBackground;
}
export interface IconOverlay extends BaseOverlay {
/**
* This should be the name of the Font Awesome icon with out the `fa fa-` prefix, just the name, for instance `paw`.
* For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/.
*/
readonly icon: string;
}
export interface IconClassOverlay extends BaseOverlay {
/**
* This should be the entire Font Awesome class array, for instance ['fa', 'fa-paw']
* For the existing icons, see here: https://fontawesome.com/v4.7.0/icons/.
*/
readonly iconClass: string[];
}
/**
* The caption highlighting with the highlighted ranges and an optional background color.
*/
export interface CaptionHighlight {
/**
* The ranges to highlight in the caption.
*/
readonly ranges: CaptionHighlight.Range[];
/**
* The optional color of the text data that is being highlighted. Falls back to the default `mark` color values defined under a node segment class.
*/
readonly color?: Color;
/**
* The optional background color of the text data that is being highlighted.
*/
readonly backgroundColor?: Color;
}
export namespace CaptionHighlight {
/**
* A pair of offset and length that has to be highlighted as a range.
*/
export interface Range {
/**
* Zero based offset of the highlighted region.
*/
readonly offset: number;
/**
* The length of the highlighted region.
*/
readonly length: number;
}
export namespace Range {
/**
* `true` if the `arg` is contained in the range. The ranges are closed ranges, hence the check is inclusive.
*/
export function contains(arg: number, range: Range): boolean {
return arg >= range.offset && arg <= (range.offset + range.length);
}
}
/**
* The result of a caption splitting based on the highlighting information.
*/
export interface Fragment {
/**
* The text data of the fragment.
*/
readonly data: string;
/**
* Has to be highlighted if defined.
*/
readonly highligh?: true;
}
/**
* Splits the `caption` argument based on the ranges from the `highlight` argument.
*/
export function split(caption: string, highlight: CaptionHighlight): Fragment[] {
const result: Fragment[] = [];
const ranges = highlight.ranges.slice();
const containerOf = (index: number) => ranges.findIndex(range => Range.contains(index, range));
let data = '';
for (let i = 0; i < caption.length; i++) {
const containerIndex = containerOf(i);
if (containerIndex === -1) {
data += caption[i];
} else {
if (data.length > 0) {
result.push({ data });
}
const { length } = ranges.splice(containerIndex, 1).shift()!;
result.push({ data: caption.substr(i, length), highligh: true });
data = '';
i = i + length - 1;
}
}
if (data.length > 0) {
result.push({ data });
}
if (ranges.length !== 0) {
throw new Error('Error occurred when splitting the caption. There was a mismatch between the caption and the corresponding highlighting ranges.');
}
return result;
}
}
/**
* Encapsulates styling information that has to be applied on the node which we decorate.
*/
export interface Data {
/**
* The higher number has higher priority. If not specified, treated as `0`.
* When multiple decorators are available for the same item, and decoration data cannot be merged together,
* then the higher priority item will be applied on the decorated element and the lower priority will be ignored.
*/
readonly priority?: number;
/**
* The font data for the caption.
*/
readonly fontData?: FontData;
/**
* The background color of the entire row.
*/
readonly backgroundColor?: Color;
/**
* Optional, leading prefixes right before the caption.
*/
readonly captionPrefixes?: CaptionAffix[];
/**
* Suffixes that might come after the caption as an additional information.
*/
readonly captionSuffixes?: CaptionAffix[];
/**
* Optional right-aligned decorations that appear after the node caption and after the caption suffixes (is any).
*/
readonly tailDecorations?: Array<TailDecoration | TailDecorationIcon | TailDecorationIconClass>;
/**
* Custom tooltip for the decorated item. Tooltip will be appended to the original tooltip, if any.
*/
readonly tooltip?: string;
/**
* Sets the color of the icon. Ignored if the decorated item has no icon.
*/
readonly iconColor?: Color;
/**
* Has not effect if given, but the node does not have an associated image.
*/
readonly iconOverlay?: IconOverlay | IconClassOverlay;
/**
* An array of ranges to highlight the caption.
*/
readonly highlight?: CaptionHighlight;
}
export namespace Data {
/**
* Compares the decoration data based on the priority. Lowest priorities come first.
*/
export const comparePriority = (left: Data, right: Data): number => (left.priority || 0) - (right.priority || 0);
}

/**
* Specific to tree node decoration.
* Tree node that can be decorated explicitly, without the tree decorators.
*/
export interface DecoratedTreeNode extends TreeNode {
/**
* The additional tree decoration data attached to the tree node itself.
*/
readonly decorationData: Data;
}
export namespace DecoratedTreeNode {
/**
* Type-guard for decorated tree nodes.
*/
export function is(node: TreeNode | undefined): node is DecoratedTreeNode {
return !!node && 'decorationData' in node;
}
}
}
Loading

0 comments on commit f26e104

Please sign in to comment.