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:
YanXinyu 2023-03-30 10:14:45 +08:00 committed by GitHub
parent 416fb7cba5
commit f2db574931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 55 additions and 149 deletions

View File

@ -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

View File

@ -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();
});
});

View File

@ -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';

View File

@ -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>;
} }

View File

@ -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;
}

View File

@ -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>
); );
}; };

View File

@ -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}

View File

@ -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;
} }