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:
|
||||
- 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/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-pattern.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 './react-hooks';
|
||||
export * from './icon';
|
||||
export * from './parse';
|
||||
export * from './json';
|
||||
export * from './label';
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import clx from 'classnames';
|
||||
import React, { CSSProperties } from 'react';
|
||||
|
||||
import { Icon } from '@opensumi/ide-components/lib/icon/icon';
|
||||
|
@ -7,30 +8,42 @@ const SEPERATOR = ' ';
|
|||
|
||||
export function transformLabelWithCodicon(
|
||||
label: string,
|
||||
iconStyles: CSSProperties = {},
|
||||
iconStyleProps: CSSProperties | string = {},
|
||||
transformer?: (str: string) => string | undefined,
|
||||
renderText?: (str: string, index: number) => React.ReactNode,
|
||||
) {
|
||||
const ICON_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) => {
|
||||
if (!transformer) {
|
||||
return e;
|
||||
}
|
||||
const icon = transformer(e);
|
||||
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)) {
|
||||
if (e.includes('~')) {
|
||||
const [, , icon, animate] = ICON_WITH_ANIMATE_REGX.exec(e) || [];
|
||||
if (animate && icon) {
|
||||
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}`);
|
||||
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 {
|
||||
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 { strings } from '@opensumi/ide-core-common';
|
||||
|
||||
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;
|
||||
}, '');
|
||||
import { useInjectable } from '@opensumi/ide-core-browser/lib/react-hooks';
|
||||
import { IIconService } from '@opensumi/ide-theme';
|
||||
|
||||
export interface HighlightLabelProp {
|
||||
text?: string;
|
||||
|
@ -33,8 +24,10 @@ export const HighlightLabel: React.FC<HighlightLabelProp> = ({
|
|||
hightLightClassName = '',
|
||||
OutElementType = 'span',
|
||||
}) => {
|
||||
const iconService = useInjectable<IIconService>(IIconService);
|
||||
|
||||
const renderLabel = React.useMemo(() => {
|
||||
const children: string[] = [];
|
||||
const children: ReactChild[] = [];
|
||||
let pos = 0;
|
||||
|
||||
for (const highlight of highlights) {
|
||||
|
@ -43,26 +36,36 @@ export const HighlightLabel: React.FC<HighlightLabelProp> = ({
|
|||
}
|
||||
if (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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (pos < text.length) {
|
||||
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]);
|
||||
return (
|
||||
<OutElementType
|
||||
// @ts-ignore
|
||||
title={text}
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{ __html: renderLabel }}
|
||||
></OutElementType>
|
||||
// @ts-ignore
|
||||
<OutElementType title={text} className={className}>
|
||||
{renderLabel}
|
||||
</OutElementType>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -257,6 +257,8 @@ const QuickOpenItemView: React.FC<IQuickOpenItemProps> = observer(({ data, index
|
|||
{iconClass && <span className={clx(styles.item_icon, iconClass)}></span>}
|
||||
<HighlightLabel
|
||||
className={styles.item_label_name}
|
||||
labelClassName={styles.label_icon_container}
|
||||
labelIconClassName={styles.item_label_name_icon}
|
||||
hightLightClassName={clx(styles.item_label_highlight)}
|
||||
text={label}
|
||||
highlights={labelHighlights}
|
||||
|
@ -265,7 +267,7 @@ const QuickOpenItemView: React.FC<IQuickOpenItemProps> = observer(({ data, index
|
|||
<HighlightLabel
|
||||
className={styles.item_label_description}
|
||||
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)}
|
||||
text={description}
|
||||
highlights={descriptionHighlights}
|
||||
|
@ -277,7 +279,7 @@ const QuickOpenItemView: React.FC<IQuickOpenItemProps> = observer(({ data, index
|
|||
OutElementType='div'
|
||||
className={styles.item_label_detail}
|
||||
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)}
|
||||
text={detail}
|
||||
highlights={detailHighlights}
|
||||
|
|
|
@ -117,6 +117,10 @@
|
|||
white-space: pre;
|
||||
}
|
||||
|
||||
.item_label_name_icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.item_label_description_label {
|
||||
color: var(--descriptionForeground);
|
||||
}
|
||||
|
@ -134,7 +138,9 @@
|
|||
}
|
||||
|
||||
.item_label_description,
|
||||
.item_label_detail {
|
||||
.item_label_detail,
|
||||
.itam_label_description_icon,
|
||||
.item_label_detail_icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue