mirror of https://github.com/opensumi/core
refactor: replace quickopen label render function from parseLabel to transformLabelWithCodicon (#2498)
* refactor: replace parseLabel to transformLabelWithCodicon * refactor: delete unuse file * fix: remove parse imoprt * fix: remove parse description in NOTICE.md * fix: optimize label icon style
This commit is contained in:
parent
416fb7cba5
commit
f2db574931
|
@ -52,7 +52,6 @@ Code copied from project eclipse-theia/theia
|
||||||
- Source:
|
- Source:
|
||||||
- https://github.com/opensumi/core/blob/main/packages/core-browser/src/keyboard/keyboard-layout-service.ts
|
- https://github.com/opensumi/core/blob/main/packages/core-browser/src/keyboard/keyboard-layout-service.ts
|
||||||
- https://github.com/opensumi/core/blob/main/packages/core-browser/src/keyboard/keys.ts
|
- https://github.com/opensumi/core/blob/main/packages/core-browser/src/keyboard/keys.ts
|
||||||
- https://github.com/opensumi/core/blob/main/packages/core-browser/src/utils/parse.ts
|
|
||||||
- https://github.com/opensumi/core/blob/main/packages/core-common/src/problem-matcher.ts
|
- https://github.com/opensumi/core/blob/main/packages/core-common/src/problem-matcher.ts
|
||||||
- https://github.com/opensumi/core/blob/main/packages/core-common/src/problem-pattern.ts
|
- https://github.com/opensumi/core/blob/main/packages/core-common/src/problem-pattern.ts
|
||||||
- https://github.com/opensumi/core/blob/main/packages/core-common/src/keyboard/keyboard-layout-provider.ts
|
- https://github.com/opensumi/core/blob/main/packages/core-common/src/keyboard/keyboard-layout-provider.ts
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { LabelIcon, parseLabel } from '../../src/utils/parse';
|
|
||||||
|
|
||||||
describe('it can parse', () => {
|
|
||||||
it('can parse label with icon', () => {
|
|
||||||
const labels = parseLabel('$(hello) world');
|
|
||||||
expect(labels).toHaveLength(2);
|
|
||||||
expect((labels[0] as LabelIcon).name).toEqual('hello');
|
|
||||||
});
|
|
||||||
it('can parse label with multiple icon', () => {
|
|
||||||
const labels = parseLabel('$(hello) world $(qwq)');
|
|
||||||
expect(labels).toHaveLength(3);
|
|
||||||
expect((labels[0] as LabelIcon).name).toEqual('hello');
|
|
||||||
expect((labels[2] as LabelIcon).name).toEqual('qwq');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can parse label with icon animation', () => {
|
|
||||||
const labels = parseLabel('$(hello~spin) world $(qwq)');
|
|
||||||
expect(labels).toHaveLength(3);
|
|
||||||
expect((labels[0] as LabelIcon).name).toEqual('hello');
|
|
||||||
expect((labels[0] as LabelIcon).owner).toBeUndefined();
|
|
||||||
expect((labels[0] as LabelIcon).animation).toEqual('spin');
|
|
||||||
expect((labels[2] as LabelIcon).name).toEqual('qwq');
|
|
||||||
expect((labels[2] as LabelIcon).owner).toBeUndefined();
|
|
||||||
});
|
|
||||||
it('can parse label with icon owner', () => {
|
|
||||||
const labels = parseLabel('$(kt/hello~spin) world $(qwq)');
|
|
||||||
expect(labels).toHaveLength(3);
|
|
||||||
expect((labels[0] as LabelIcon).name).toEqual('hello');
|
|
||||||
expect((labels[0] as LabelIcon).animation).toEqual('spin');
|
|
||||||
expect((labels[0] as LabelIcon).owner).toEqual('kt');
|
|
||||||
expect((labels[2] as LabelIcon).name).toEqual('qwq');
|
|
||||||
expect((labels[2] as LabelIcon).owner).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -2,6 +2,5 @@ export * from './env';
|
||||||
export * from './electron';
|
export * from './electron';
|
||||||
export * from './react-hooks';
|
export * from './react-hooks';
|
||||||
export * from './icon';
|
export * from './icon';
|
||||||
export * from './parse';
|
|
||||||
export * from './json';
|
export * from './json';
|
||||||
export * from './label';
|
export * from './label';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import clx from 'classnames';
|
||||||
import React, { CSSProperties } from 'react';
|
import React, { CSSProperties } from 'react';
|
||||||
|
|
||||||
import { Icon } from '@opensumi/ide-components/lib/icon/icon';
|
import { Icon } from '@opensumi/ide-components/lib/icon/icon';
|
||||||
|
@ -7,30 +8,42 @@ const SEPERATOR = ' ';
|
||||||
|
|
||||||
export function transformLabelWithCodicon(
|
export function transformLabelWithCodicon(
|
||||||
label: string,
|
label: string,
|
||||||
iconStyles: CSSProperties = {},
|
iconStyleProps: CSSProperties | string = {},
|
||||||
transformer?: (str: string) => string | undefined,
|
transformer?: (str: string) => string | undefined,
|
||||||
renderText?: (str: string, index: number) => React.ReactNode,
|
renderText?: (str: string, index: number) => React.ReactNode,
|
||||||
) {
|
) {
|
||||||
const ICON_REGX = /\$\(([a-z.]+\/)?([a-z-]+)(~[a-z]+)?\)/gi;
|
const ICON_REGX = /\$\(([a-z.]+\/)?([a-z-]+)(~[a-z]+)?\)/gi;
|
||||||
const ICON_WITH_ANIMATE_REGX = /\$\(([a-z.]+\/)?([a-z-]+)~([a-z]+)\)/gi;
|
const ICON_WITH_ANIMATE_REGX = /\$\(([a-z.]+\/)?([a-z-]+)~([a-z]+)\)/gi;
|
||||||
|
// some string like $() $(~spin)
|
||||||
|
const ICON_ERROR_REGX = /\$\(([a-z.]+\/)?([a-z-]+)?(~[a-z]+)?\)/gi;
|
||||||
|
|
||||||
|
const generateIconStyle = (icon?: string, styleProps?: CSSProperties | string) =>
|
||||||
|
typeof styleProps === 'string' ? { className: clx(icon, styleProps) } : { className: icon, style: styleProps };
|
||||||
|
|
||||||
return label.split(SEPERATOR).map((e, index) => {
|
return label.split(SEPERATOR).map((e, index) => {
|
||||||
if (!transformer) {
|
if (!transformer) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
const icon = transformer(e);
|
const icon = transformer(e);
|
||||||
if (icon) {
|
if (icon) {
|
||||||
return <Icon className={icon} style={iconStyles} key={`${index}-${icon}`} />;
|
return <Icon {...generateIconStyle(icon, iconStyleProps)} key={`${index}-${icon}`} />;
|
||||||
} else if (ICON_REGX.test(e)) {
|
} else if (ICON_REGX.test(e)) {
|
||||||
if (e.includes('~')) {
|
if (e.includes('~')) {
|
||||||
const [, , icon, animate] = ICON_WITH_ANIMATE_REGX.exec(e) || [];
|
const [, , icon, animate] = ICON_WITH_ANIMATE_REGX.exec(e) || [];
|
||||||
if (animate && icon) {
|
if (animate && icon) {
|
||||||
return (
|
return (
|
||||||
<Icon className={transformer(`$(${icon})`)} style={iconStyles} animate={animate} key={`${index}-${icon}`} />
|
<Icon
|
||||||
|
{...generateIconStyle(transformer(`$(${icon})`), iconStyleProps)}
|
||||||
|
animate={animate}
|
||||||
|
key={`${index}-${icon}`}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newStr = e.replaceAll(ICON_REGX, (e) => `${SEPERATOR}${e}${SEPERATOR}`);
|
const newStr = e.replaceAll(ICON_REGX, (e) => `${SEPERATOR}${e}${SEPERATOR}`);
|
||||||
return transformLabelWithCodicon(newStr, iconStyles, transformer);
|
return transformLabelWithCodicon(newStr, iconStyleProps, transformer);
|
||||||
|
} else if (ICON_ERROR_REGX.test(e)) {
|
||||||
|
return transformLabelWithCodicon(e.replaceAll(ICON_ERROR_REGX, ''), iconStyleProps, transformer, renderText);
|
||||||
} else {
|
} else {
|
||||||
return isFunction(renderText) ? renderText(e, index) : <span key={`${index}-${e}`}>{e}</span>;
|
return isFunction(renderText) ? renderText(e, index) : <span key={`${index}-${e}`}>{e}</span>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/** ******************************************************************************
|
|
||||||
* Copyright (C) 2018 Red Hat, Inc. 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
|
|
||||||
********************************************************************************/
|
|
||||||
// Some code copied and modified from https://github.com/eclipse-theia/theia/tree/v1.14.0/packages/core/src/browser/label-parser.ts
|
|
||||||
|
|
||||||
export interface LabelIcon {
|
|
||||||
name: string;
|
|
||||||
owner?: string;
|
|
||||||
animation?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export namespace LabelIcon {
|
|
||||||
export function is(val: object): val is LabelIcon {
|
|
||||||
return 'name' in val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LabelPart = string | LabelIcon;
|
|
||||||
|
|
||||||
export function parseLabel(text: string): LabelPart[] {
|
|
||||||
const parserArray: LabelPart[] = [];
|
|
||||||
let arrPointer = 0;
|
|
||||||
let potentialIcon = '';
|
|
||||||
|
|
||||||
for (let idx = 0; idx < text.length; idx++) {
|
|
||||||
const char = text.charAt(idx);
|
|
||||||
parserArray[arrPointer] = parserArray[arrPointer] || '';
|
|
||||||
if (potentialIcon === '') {
|
|
||||||
if (char === '$') {
|
|
||||||
potentialIcon += char;
|
|
||||||
} else {
|
|
||||||
parserArray[arrPointer] += char;
|
|
||||||
}
|
|
||||||
} else if (potentialIcon === '$') {
|
|
||||||
if (char === '(') {
|
|
||||||
potentialIcon += char;
|
|
||||||
} else {
|
|
||||||
parserArray[arrPointer] += potentialIcon + char;
|
|
||||||
potentialIcon = '';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (char === ')') {
|
|
||||||
const iconClassArr = potentialIcon.substring(2, potentialIcon.length).split('~');
|
|
||||||
let name = iconClassArr[0];
|
|
||||||
let owner: string | undefined;
|
|
||||||
if (name) {
|
|
||||||
const index = name.indexOf('/');
|
|
||||||
if (index !== -1) {
|
|
||||||
owner = name.substring(0, index);
|
|
||||||
name = name.substring(index + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parserArray[arrPointer] !== '') {
|
|
||||||
arrPointer++;
|
|
||||||
}
|
|
||||||
parserArray[arrPointer] = { name, owner, animation: iconClassArr[1] };
|
|
||||||
arrPointer++;
|
|
||||||
potentialIcon = '';
|
|
||||||
} else {
|
|
||||||
potentialIcon += char;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialIcon !== '') {
|
|
||||||
parserArray[arrPointer] += potentialIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parserArray;
|
|
||||||
}
|
|
|
@ -1,18 +1,9 @@
|
||||||
import React from 'react';
|
import React, { ReactChild } from 'react';
|
||||||
|
|
||||||
import { getExternalIcon, LabelIcon, LabelPart, parseLabel } from '@opensumi/ide-core-browser';
|
import { transformLabelWithCodicon } from '@opensumi/ide-core-browser';
|
||||||
import { Highlight } from '@opensumi/ide-core-browser/lib/quick-open';
|
import { Highlight } from '@opensumi/ide-core-browser/lib/quick-open';
|
||||||
import { strings } from '@opensumi/ide-core-common';
|
import { useInjectable } from '@opensumi/ide-core-browser/lib/react-hooks';
|
||||||
|
import { IIconService } from '@opensumi/ide-theme';
|
||||||
const { escape } = strings;
|
|
||||||
|
|
||||||
const labelWithIcons = (str: string, iconClassName?: string) =>
|
|
||||||
parseLabel(escape(str)).reduce((pre: string | LabelPart, cur: LabelPart) => {
|
|
||||||
if (!(typeof cur === 'string') && LabelIcon.is(cur)) {
|
|
||||||
return pre + `<span class='${getExternalIcon(cur.name)} ${iconClassName || ''}'></span>`;
|
|
||||||
}
|
|
||||||
return pre + cur;
|
|
||||||
}, '');
|
|
||||||
|
|
||||||
export interface HighlightLabelProp {
|
export interface HighlightLabelProp {
|
||||||
text?: string;
|
text?: string;
|
||||||
|
@ -33,8 +24,10 @@ export const HighlightLabel: React.FC<HighlightLabelProp> = ({
|
||||||
hightLightClassName = '',
|
hightLightClassName = '',
|
||||||
OutElementType = 'span',
|
OutElementType = 'span',
|
||||||
}) => {
|
}) => {
|
||||||
|
const iconService = useInjectable<IIconService>(IIconService);
|
||||||
|
|
||||||
const renderLabel = React.useMemo(() => {
|
const renderLabel = React.useMemo(() => {
|
||||||
const children: string[] = [];
|
const children: ReactChild[] = [];
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
|
|
||||||
for (const highlight of highlights) {
|
for (const highlight of highlights) {
|
||||||
|
@ -43,26 +36,36 @@ export const HighlightLabel: React.FC<HighlightLabelProp> = ({
|
||||||
}
|
}
|
||||||
if (pos < highlight.start) {
|
if (pos < highlight.start) {
|
||||||
const substring = text.substring(pos, highlight.start);
|
const substring = text.substring(pos, highlight.start);
|
||||||
children.push(`<span class='${labelClassName}'>${labelWithIcons(substring, labelIconClassName)}</span>`);
|
children.push(
|
||||||
|
<span className={labelClassName}>
|
||||||
|
{transformLabelWithCodicon(substring, labelIconClassName, iconService.fromString.bind(iconService))}
|
||||||
|
</span>,
|
||||||
|
);
|
||||||
pos = highlight.end;
|
pos = highlight.end;
|
||||||
}
|
}
|
||||||
const substring = text.substring(highlight.start, highlight.end);
|
const substring = text.substring(highlight.start, highlight.end);
|
||||||
children.push(`<span class='${hightLightClassName}'>${labelWithIcons(substring, labelIconClassName)}</span>`);
|
children.push(
|
||||||
|
<span className={hightLightClassName}>
|
||||||
|
{transformLabelWithCodicon(substring, labelIconClassName, iconService.fromString.bind(iconService))}
|
||||||
|
</span>,
|
||||||
|
);
|
||||||
pos = highlight.end;
|
pos = highlight.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pos < text.length) {
|
if (pos < text.length) {
|
||||||
const substring = text.substring(pos);
|
const substring = text.substring(pos);
|
||||||
children.push(`<span class='${labelClassName}'>${labelWithIcons(substring, labelIconClassName)}</span>`);
|
children.push(
|
||||||
|
<span className={labelClassName}>
|
||||||
|
{transformLabelWithCodicon(substring, labelIconClassName, iconService.fromString.bind(iconService))}
|
||||||
|
</span>,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return children.join('');
|
return children;
|
||||||
}, [text, highlights]);
|
}, [text, highlights]);
|
||||||
return (
|
return (
|
||||||
<OutElementType
|
// @ts-ignore
|
||||||
// @ts-ignore
|
<OutElementType title={text} className={className}>
|
||||||
title={text}
|
{renderLabel}
|
||||||
className={className}
|
</OutElementType>
|
||||||
dangerouslySetInnerHTML={{ __html: renderLabel }}
|
|
||||||
></OutElementType>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -257,6 +257,8 @@ const QuickOpenItemView: React.FC<IQuickOpenItemProps> = observer(({ data, index
|
||||||
{iconClass && <span className={clx(styles.item_icon, iconClass)}></span>}
|
{iconClass && <span className={clx(styles.item_icon, iconClass)}></span>}
|
||||||
<HighlightLabel
|
<HighlightLabel
|
||||||
className={styles.item_label_name}
|
className={styles.item_label_name}
|
||||||
|
labelClassName={styles.label_icon_container}
|
||||||
|
labelIconClassName={styles.item_label_name_icon}
|
||||||
hightLightClassName={clx(styles.item_label_highlight)}
|
hightLightClassName={clx(styles.item_label_highlight)}
|
||||||
text={label}
|
text={label}
|
||||||
highlights={labelHighlights}
|
highlights={labelHighlights}
|
||||||
|
@ -265,7 +267,7 @@ const QuickOpenItemView: React.FC<IQuickOpenItemProps> = observer(({ data, index
|
||||||
<HighlightLabel
|
<HighlightLabel
|
||||||
className={styles.item_label_description}
|
className={styles.item_label_description}
|
||||||
labelClassName={clx(styles.label_icon_container, styles.item_label_description_label)}
|
labelClassName={clx(styles.label_icon_container, styles.item_label_description_label)}
|
||||||
labelIconClassName={styles.label_has_icon}
|
labelIconClassName={clx(styles.label_has_icon, styles.item_label_description_icon)}
|
||||||
hightLightClassName={clx(styles.item_label_description_highlight)}
|
hightLightClassName={clx(styles.item_label_description_highlight)}
|
||||||
text={description}
|
text={description}
|
||||||
highlights={descriptionHighlights}
|
highlights={descriptionHighlights}
|
||||||
|
@ -277,7 +279,7 @@ const QuickOpenItemView: React.FC<IQuickOpenItemProps> = observer(({ data, index
|
||||||
OutElementType='div'
|
OutElementType='div'
|
||||||
className={styles.item_label_detail}
|
className={styles.item_label_detail}
|
||||||
labelClassName={clx(styles.label_icon_container, styles.item_label_description_label)}
|
labelClassName={clx(styles.label_icon_container, styles.item_label_description_label)}
|
||||||
labelIconClassName={styles.label_has_icon}
|
labelIconClassName={clx(styles.label_has_icon, styles.item_label_detail_icon)}
|
||||||
hightLightClassName={clx(styles.item_label_description_highlight)}
|
hightLightClassName={clx(styles.item_label_description_highlight)}
|
||||||
text={detail}
|
text={detail}
|
||||||
highlights={detailHighlights}
|
highlights={detailHighlights}
|
||||||
|
|
|
@ -117,6 +117,10 @@
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item_label_name_icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.item_label_description_label {
|
.item_label_description_label {
|
||||||
color: var(--descriptionForeground);
|
color: var(--descriptionForeground);
|
||||||
}
|
}
|
||||||
|
@ -134,7 +138,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.item_label_description,
|
.item_label_description,
|
||||||
.item_label_detail {
|
.item_label_detail,
|
||||||
|
.itam_label_description_icon,
|
||||||
|
.item_label_detail_icon {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue