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

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 './react-hooks';
export * from './icon';
export * from './parse';
export * from './json';
export * from './label';

View File

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

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 { 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>
<OutElementType title={text} className={className}>
{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>}
<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}

View File

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