Skip to content

Commit 3f631ed

Browse files
author
winjo
committed
feat: 支持代码搜索文件匹配
1 parent 5007e7e commit 3f631ed

2 files changed

Lines changed: 240 additions & 97 deletions

File tree

packages/code-api/src/common/search/content-search.service.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Injectable, Autowired } from '@ali/common-di';
22
import { SupportLogNamespace, ILogService, URI } from '@ali/ide-core-common';
3+
import { parse } from '@ali/ide-core-common/lib/utils/glob';
34
import { ILogServiceManager, AppConfig } from '@alipay/alex-core';
45
import {
56
IContentSearchServer,
@@ -8,6 +9,7 @@ import {
89
SEARCH_STATE,
910
cutShortSearchResult,
1011
DEFAULT_SEARCH_IN_WORKSPACE_LIMIT,
12+
anchorGlob
1113
} from '@ali/ide-search/lib/common';
1214
import { ContentSearchClientService } from '@ali/ide-search/lib/browser/search.service';
1315
import { ICodeAPIService } from '@alipay/alex-code-service';
@@ -144,9 +146,21 @@ export class ContentSearchService implements IContentSearchServer {
144146

145147
const results: ContentSearchResult[] = [];
146148

149+
const includeMatcherList = opts?.include?.map((str: string) => parse(anchorGlob(str))) || [];
150+
const excludeMatcherList = opts?.exclude?.map((str: string) => parse(anchorGlob(str))) || [];
151+
147152
requestResults.forEach(({ path, line, content }) => {
148153
const searchStringLen = searchString.length;
149154
const textLength = content.length;
155+
const fileUri = URI.file(paths.join(this.appConfig.workspaceDir, path)).toString()
156+
157+
if (includeMatcherList.length > 0 && !includeMatcherList.some(matcher => matcher(fileUri))) {
158+
return
159+
}
160+
161+
if (excludeMatcherList.length > 0 && excludeMatcherList.some(matcher => matcher(fileUri))) {
162+
return
163+
}
150164

151165
if (simpleSearch) {
152166
let lastMatchIndex = -searchStringLen;
@@ -162,7 +176,7 @@ export class ContentSearchService implements IContentSearchServer {
162176
) {
163177
results.push(
164178
cutShortSearchResult({
165-
fileUri: URI.file(paths.join(this.appConfig.workspaceDir, path)).toString(),
179+
fileUri,
166180
line,
167181
matchStart: lastMatchIndex + 1,
168182
matchLength: searchStringLen,
@@ -187,7 +201,7 @@ export class ContentSearchService implements IContentSearchServer {
187201
) {
188202
results.push(
189203
cutShortSearchResult({
190-
fileUri: URI.file(paths.join(this.appConfig.workspaceDir, path)).toString(),
204+
fileUri,
191205
line,
192206
matchStart: matchStartIndex + 1,
193207
matchLength: searchStringLen,

packages/code-api/src/common/search/search.view.tsx

Lines changed: 224 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -2,116 +2,245 @@ import * as React from 'react';
22
import { observer } from 'mobx-react-lite';
33
import { ConfigContext, localize, getIcon } from '@ali/ide-core-browser';
44
import { ProgressBar } from '@ali/ide-core-browser/lib/components/progressbar';
5-
import { ValidateInput } from '@ali/ide-components';
5+
import { Input, ValidateInput, CheckBox, Popover, PopoverTriggerType } from '@ali/ide-components';
66
import { ViewState } from '@ali/ide-core-browser';
77
import * as cls from 'classnames';
88
import * as styles from '@ali/ide-search/lib/browser/search.module.less';
99
import { SEARCH_STATE } from '@ali/ide-search/lib/common';
1010
import { ContentSearchClientService } from '@ali/ide-search/lib/browser/search.service';
1111
import { SearchTree } from './search-tree.view';
1212

13-
export const Search = observer(
14-
({ viewState }: React.PropsWithChildren<{ viewState: ViewState }>) => {
15-
const searchOptionRef = React.createRef<HTMLDivElement>();
16-
const configContext = React.useContext(ConfigContext);
17-
const { injector } = configContext;
18-
const searchBrowserService = injector.get(ContentSearchClientService);
13+
function getIncludeRuleContent() {
14+
return (
15+
<div className={cls(styles.include_rule_content)}>
16+
<ul>
17+
<li>, : {localize('search.help.concatRule')}</li>
18+
<li>* : {localize('search.help.matchOneOrMoreRule')}</li>
19+
<li>? : {localize('search.help.matchOne')}</li>
20+
<li>** : {localize('search.help.matchAny')}</li>
21+
<li>{} : {localize('search.help.matchWithGroup')}</li>
22+
<li>[] : {localize('search.help.matchRange')}</li>
23+
</ul>
24+
</div>
25+
);
26+
}
1927

20-
const [searchPanelLayout, setSearchPanelLayout] = React.useState({ height: 0, width: 0 });
21-
const searchTreeRef = React.useRef();
28+
function getExcludeRuleContent(excludeList: string[]) {
29+
return (
30+
<div className={cls(styles.exclude_rule_content)}>
31+
<div>
32+
{excludeList.map((exclude, index) => {
33+
if (index === excludeList.length - 1) {
34+
return exclude;
35+
}
36+
return `${exclude}, `;
37+
})}
38+
</div>
39+
</div>
40+
);
41+
}
2242

23-
const searchResults = searchBrowserService.searchResults;
24-
const resultTotal = searchBrowserService.resultTotal;
25-
const searchState = searchBrowserService.searchState;
26-
const doReplaceAll = searchBrowserService.doReplaceAll;
27-
const updateUIState = searchBrowserService.updateUIState;
28-
const UIState = searchBrowserService.UIState;
29-
const searchError = searchBrowserService.searchError;
30-
const isSearchDoing = searchBrowserService.isSearchDoing;
31-
const validateMessage = searchBrowserService.validateMessage;
32-
const isShowValidateMessage = searchBrowserService.isShowValidateMessage;
43+
export const Search = observer(({
44+
viewState,
45+
}: React.PropsWithChildren<{ viewState: ViewState }>,
46+
) => {
47+
const searchOptionRef = React.createRef<HTMLDivElement>();
48+
const configContext = React.useContext(ConfigContext);
49+
const { injector } = configContext;
50+
const searchBrowserService = injector.get(ContentSearchClientService);
3351

34-
React.useEffect(() => {
35-
setSearchPanelLayout({
36-
width: (searchOptionRef.current && searchOptionRef.current.clientWidth) || 0,
37-
height: (searchOptionRef.current && searchOptionRef.current.clientHeight) || 0,
38-
});
39-
}, [UIState, searchOptionRef.current, searchResults.size > 0]);
52+
const [searchPanelLayout, setSearchPanelLayout] = React.useState({ height: 0, width: 0 });
53+
const searchTreeRef = React.useRef();
4054

41-
const collapsePanelContainerStyle = {
42-
width: viewState.width || '100%',
43-
height: viewState.height,
44-
};
55+
const searchResults = searchBrowserService.searchResults;
56+
const resultTotal = searchBrowserService.resultTotal;
57+
const searchState = searchBrowserService.searchState;
58+
const doReplaceAll = searchBrowserService.doReplaceAll;
59+
const updateUIState = searchBrowserService.updateUIState;
60+
const UIState = searchBrowserService.UIState;
61+
const searchError = searchBrowserService.searchError;
62+
const isSearchDoing = searchBrowserService.isSearchDoing;
63+
const validateMessage = searchBrowserService.validateMessage;
64+
const isShowValidateMessage = searchBrowserService.isShowValidateMessage;
4565

46-
return (
47-
<div className={styles.wrap} style={collapsePanelContainerStyle}>
48-
<div className={styles['loading-wrap']}>
49-
<ProgressBar loading={isSearchDoing} />
50-
</div>
51-
<div className={styles.search_options} ref={searchOptionRef}>
52-
<div className={styles.search_and_replace_container}>
53-
<div className={styles.search_and_replace_fields}>
54-
<div className={styles.search_field_container}>
55-
<p className={styles.search_input_title}>{localize('search.input.title')}</p>
56-
<div
57-
className={cls(styles.search_field, { [styles.focus]: UIState.isSearchFocus })}
58-
>
59-
<ValidateInput
60-
id="search-input-field"
61-
title={localize('search.input.placeholder')}
62-
type="text"
63-
value={searchBrowserService.searchValue}
64-
placeholder={localize('search.input.placeholder')}
65-
onFocus={() => updateUIState({ isSearchFocus: true })}
66-
onBlur={() => updateUIState({ isSearchFocus: false })}
67-
onKeyUp={searchBrowserService.search}
68-
onChange={searchBrowserService.onSearchInputChange}
69-
ref={searchBrowserService.searchInputEl}
70-
validateMessage={isShowValidateMessage ? validateMessage : undefined}
71-
addonAfter={[
72-
<span
73-
key={localize('caseDescription')}
74-
className={cls(getIcon('ab'), styles['match-case'], styles.option, {
75-
[styles.select]: UIState.isMatchCase,
76-
})}
77-
title={localize('caseDescription')}
78-
onClick={(e) => updateUIState({ isMatchCase: !UIState.isMatchCase }, e)}
79-
/>,
80-
<span
81-
key={localize('wordsDescription')}
82-
className={cls(getIcon('abl'), styles['whole-word'], styles.option, {
83-
[styles.select]: UIState.isWholeWord,
84-
})}
85-
title={localize('wordsDescription')}
86-
onClick={(e) => updateUIState({ isWholeWord: !UIState.isWholeWord }, e)}
87-
/>,
88-
]}
89-
/>
90-
</div>
66+
React.useEffect(() => {
67+
setSearchPanelLayout({
68+
width: searchOptionRef.current && searchOptionRef.current.clientWidth || 0,
69+
height: searchOptionRef.current && searchOptionRef.current.clientHeight || 0,
70+
});
71+
}, [UIState, searchOptionRef.current, searchResults.size > 0]);
72+
73+
const collapsePanelContainerStyle = {
74+
width: viewState.width || '100%',
75+
height: viewState.height,
76+
};
77+
78+
return (
79+
<div className={styles.wrap} style={collapsePanelContainerStyle}>
80+
<div className={styles['loading-wrap']}>
81+
<ProgressBar loading={isSearchDoing} />
82+
</div>
83+
<div className={styles.search_options} ref={searchOptionRef}>
84+
<div className={styles.search_and_replace_container}>
85+
<div className={styles.search_and_replace_fields}>
86+
<div className={styles.search_field_container}>
87+
<p className={styles.search_input_title}>
88+
{localize('search.input.title')}
89+
<CheckBox
90+
insertClass={cls(styles.checkbox)}
91+
label={localize('search.input.checkbox')}
92+
checked={UIState.isDetailOpen}
93+
id='search-input'
94+
onChange={() => { updateUIState({ isDetailOpen: !UIState.isDetailOpen }); }}
95+
/>
96+
</p>
97+
<div className={cls(styles.search_field, { [styles.focus]: UIState.isSearchFocus })}>
98+
<ValidateInput
99+
id='search-input-field'
100+
title={localize('search.input.placeholder')}
101+
type='text'
102+
value={searchBrowserService.searchValue}
103+
placeholder={localize('search.input.placeholder')}
104+
onFocus={() => updateUIState({ isSearchFocus: true })}
105+
onBlur={() => updateUIState({ isSearchFocus: false })}
106+
onKeyUp={searchBrowserService.search}
107+
onChange={searchBrowserService.onSearchInputChange}
108+
ref={searchBrowserService.searchInputEl}
109+
validateMessage={isShowValidateMessage ? validateMessage : undefined }
110+
addonAfter={[
111+
<span
112+
key={localize('caseDescription')}
113+
className={cls(getIcon('ab'), styles['match-case'], styles.option, { [styles.select]: UIState.isMatchCase })}
114+
title={localize('caseDescription')}
115+
onClick={(e) => updateUIState({ isMatchCase: !UIState.isMatchCase }, e)}
116+
></span>,
117+
<span
118+
key={localize('wordsDescription')}
119+
className={cls(getIcon('abl'), styles['whole-word'], styles.option, { [styles.select]: UIState.isWholeWord })}
120+
title={localize('wordsDescription')}
121+
onClick={(e) => updateUIState({ isWholeWord: !UIState.isWholeWord }, e)}
122+
></span>,
123+
]}
124+
/>
91125
</div>
126+
{/* <div className='search-notification '>
127+
<div>This is only a subset of all results. Use a more specific search term to narrow down the result list.</div>
128+
</div> */}
92129
</div>
93130
</div>
94131
</div>
95-
{searchResults && searchResults.size > 0 && !searchError ? (
96-
<SearchTree
97-
searchPanelLayout={searchPanelLayout}
98-
viewState={viewState}
99-
ref={searchTreeRef}
100-
/>
101-
) : (
102-
<div
103-
className={cls(
104-
{ [styles.result_describe]: searchState === SEARCH_STATE.done },
105-
{ [styles.result_error]: searchState === SEARCH_STATE.error || searchError }
106-
)}
107-
>
108-
{searchState === SEARCH_STATE.done && !searchError
109-
? localize('noResultsFound').replace('-', '')
110-
: ''}
111-
{searchError}
132+
133+
<div className={cls(styles.search_details)}>
134+
{UIState.isDetailOpen ?
135+
<div className='glob_field-container'>
136+
<div className={cls(styles.glob_field)}>
137+
<div className={cls(styles.label)}>
138+
<span className={styles.limit}>{localize('search.includes')}</span>
139+
<span className={cls(styles.include_rule)}>
140+
<Popover
141+
id={'show_include_rule'}
142+
title={localize('search.help.supportRule')}
143+
content={getIncludeRuleContent()}
144+
trigger={PopoverTriggerType.hover}
145+
delay={500}
146+
>
147+
{localize('search.help.showIncludeRule')}
148+
</Popover>
149+
</span>
150+
</div>
151+
<Input
152+
value={searchBrowserService.includeValue}
153+
type='text'
154+
placeholder={localize('search.includes.description')}
155+
onKeyUp={searchBrowserService.search}
156+
onChange={searchBrowserService.onSearchIncludeChange}
157+
/>
158+
</div>
159+
<div className={cls(styles.glob_field, styles.search_excludes)}>
160+
<div className={styles.label}>
161+
<span className={styles.limit}>{localize('search.excludes')}</span>
162+
<div className={styles.checkbox_wrap}>
163+
<CheckBox
164+
insertClass={cls(styles.checkbox)}
165+
label={localize('search.excludes.default.enable')}
166+
checked={!UIState.isIncludeIgnored}
167+
id='search-input-isIncludeIgnored'
168+
onChange={() => { updateUIState({ isIncludeIgnored: !UIState.isIncludeIgnored }); }}
169+
/>
170+
<Popover
171+
title={localize('search.help.excludeList')}
172+
insertClass={cls(styles.search_excludes_description)}
173+
id={'search_excludes'}
174+
action={localize('search.help.modify')}
175+
onClickAction={searchBrowserService.openPreference}
176+
content={getExcludeRuleContent(searchBrowserService.getPreferenceSearchExcludes())}
177+
trigger={PopoverTriggerType.hover}
178+
delay={500}
179+
>
180+
<span className={cls(getIcon('question-circle'))}></span>
181+
</Popover>
182+
</div>
183+
184+
</div>
185+
<Input
186+
type='text'
187+
value={searchBrowserService.excludeValue}
188+
placeholder={localize('search.includes.description')}
189+
onKeyUp={searchBrowserService.search}
190+
onChange={searchBrowserService.onSearchExcludeChange}
191+
/>
192+
</div>
193+
</div> : ''
194+
}
195+
</div>
196+
197+
{/* <div className={styles.search_and_replace_container}>
198+
<div className={styles.search_and_replace_fields}>
199+
<p className={styles.search_input_title}>
200+
{localize('search.replace.title')}
201+
<span
202+
className={styles.replace_all}
203+
onClick={doReplaceAll}
204+
>
205+
{resultTotal.resultNum > 0 ? localize('search.replaceAll.label') : ''}
206+
</span>
207+
</p>
208+
<div className={styles.replace_field}>
209+
<Input
210+
value={searchBrowserService.replaceValue}
211+
id='replace-input-field'
212+
title={localize('search.replace.label')}
213+
type='text'
214+
placeholder={localize('search.replace.label')}
215+
onKeyUp={searchBrowserService.search}
216+
onChange={searchBrowserService.onReplaceInputChange}
217+
ref={searchBrowserService.replaceInputEl}
218+
/>
219+
<div className={styles['replace-all-button_container']}>
220+
<span title={localize('replaceAll.confirmation.title')} className={`${styles['replace-all-button']} ${styles.disabled}`}></span>
221+
</div>
222+
</div>
112223
</div>
113-
)}
224+
</div> */}
225+
114226
</div>
115-
);
116-
}
117-
);
227+
{
228+
(searchResults && searchResults.size > 0 && !searchError ) ? <SearchTree
229+
searchPanelLayout={searchPanelLayout}
230+
viewState={viewState}
231+
ref={searchTreeRef}
232+
/> : <div
233+
className={cls(
234+
{ [styles.result_describe]: searchState === SEARCH_STATE.done },
235+
{ [styles.result_error]: searchState === SEARCH_STATE.error || searchError },
236+
)}
237+
>
238+
{
239+
searchState === SEARCH_STATE.done && !searchError ? localize('noResultsFound').replace('-', '') : ''
240+
}
241+
{ searchError }
242+
</div>
243+
}
244+
</div >
245+
);
246+
});

0 commit comments

Comments
 (0)