@theia/search-in-workspace 1.53.0-next.4 → 1.53.0-next.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -40
- package/lib/browser/search-in-workspace-frontend-contribution.js +4 -4
- package/lib/node/ripgrep-search-in-workspace-server.slow-spec.js +28 -28
- package/package.json +9 -9
- package/src/browser/components/search-in-workspace-input.tsx +139 -139
- package/src/browser/components/search-in-workspace-textarea.tsx +153 -153
- package/src/browser/search-in-workspace-context-key-service.ts +93 -93
- package/src/browser/search-in-workspace-factory.ts +59 -59
- package/src/browser/search-in-workspace-frontend-contribution.ts +510 -510
- package/src/browser/search-in-workspace-frontend-module.ts +83 -83
- package/src/browser/search-in-workspace-label-provider.ts +48 -48
- package/src/browser/search-in-workspace-preferences.ts +96 -96
- package/src/browser/search-in-workspace-result-tree-widget.tsx +1318 -1318
- package/src/browser/search-in-workspace-service.ts +152 -152
- package/src/browser/search-in-workspace-widget.tsx +727 -727
- package/src/browser/search-layout-migrations.ts +53 -53
- package/src/browser/styles/index.css +400 -400
- package/src/browser/styles/search.svg +6 -6
- package/src/common/search-in-workspace-interface.ts +153 -153
- package/src/node/ripgrep-search-in-workspace-server.slow-spec.ts +1073 -1073
- package/src/node/ripgrep-search-in-workspace-server.ts +490 -490
- package/src/node/search-in-workspace-backend-module.ts +33 -33
package/README.md
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
<div align='center'>
|
|
2
|
-
|
|
3
|
-
<br />
|
|
4
|
-
|
|
5
|
-
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
|
|
6
|
-
|
|
7
|
-
<h2>ECLIPSE THEIA - SEARCH-IN-WORKSPACE EXTENSION</h2>
|
|
8
|
-
|
|
9
|
-
<hr />
|
|
10
|
-
|
|
11
|
-
</div>
|
|
12
|
-
|
|
13
|
-
## Description
|
|
14
|
-
|
|
15
|
-
The `@theia/search-in-workspace` extension provides the ability to perform searches over all files in a given workspace using different search techniques.
|
|
16
|
-
|
|
17
|
-
## Search Widget
|
|
18
|
-
|
|
19
|
-
The `@theia/search-in-workspace` extension contributes the `Search` widget which is capable of performing different types of searches include the possibility to:
|
|
20
|
-
- Perform standard searches
|
|
21
|
-
- Perform searches using regular expressions
|
|
22
|
-
- Perform searches within an `include` list (search for specific types of files (ex: `*.ts`))
|
|
23
|
-
- Perform searches excluding files or directories (using `exclude`)
|
|
24
|
-
- Perform searches ignoring hidden or excluded files/folders
|
|
25
|
-
- Perform search and replace (to quickly update multiple occurrences of a search term)
|
|
26
|
-
|
|
27
|
-
## Additional Information
|
|
28
|
-
|
|
29
|
-
- [API documentation for `@theia/search-in-workspace`](https://eclipse-theia.github.io/theia/docs/next/modules/search_in_workspace.html)
|
|
30
|
-
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
|
|
31
|
-
- [Theia - Website](https://theia-ide.org/)
|
|
32
|
-
|
|
33
|
-
## License
|
|
34
|
-
|
|
35
|
-
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
|
36
|
-
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
|
37
|
-
|
|
38
|
-
## Trademark
|
|
39
|
-
"Theia" is a trademark of the Eclipse Foundation
|
|
40
|
-
https://www.eclipse.org/theia
|
|
1
|
+
<div align='center'>
|
|
2
|
+
|
|
3
|
+
<br />
|
|
4
|
+
|
|
5
|
+
<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />
|
|
6
|
+
|
|
7
|
+
<h2>ECLIPSE THEIA - SEARCH-IN-WORKSPACE EXTENSION</h2>
|
|
8
|
+
|
|
9
|
+
<hr />
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
## Description
|
|
14
|
+
|
|
15
|
+
The `@theia/search-in-workspace` extension provides the ability to perform searches over all files in a given workspace using different search techniques.
|
|
16
|
+
|
|
17
|
+
## Search Widget
|
|
18
|
+
|
|
19
|
+
The `@theia/search-in-workspace` extension contributes the `Search` widget which is capable of performing different types of searches include the possibility to:
|
|
20
|
+
- Perform standard searches
|
|
21
|
+
- Perform searches using regular expressions
|
|
22
|
+
- Perform searches within an `include` list (search for specific types of files (ex: `*.ts`))
|
|
23
|
+
- Perform searches excluding files or directories (using `exclude`)
|
|
24
|
+
- Perform searches ignoring hidden or excluded files/folders
|
|
25
|
+
- Perform search and replace (to quickly update multiple occurrences of a search term)
|
|
26
|
+
|
|
27
|
+
## Additional Information
|
|
28
|
+
|
|
29
|
+
- [API documentation for `@theia/search-in-workspace`](https://eclipse-theia.github.io/theia/docs/next/modules/search_in_workspace.html)
|
|
30
|
+
- [Theia - GitHub](https://github.com/eclipse-theia/theia)
|
|
31
|
+
- [Theia - Website](https://theia-ide.org/)
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
|
|
36
|
+
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)
|
|
37
|
+
|
|
38
|
+
## Trademark
|
|
39
|
+
"Theia" is a trademark of the Eclipse Foundation
|
|
40
|
+
https://www.eclipse.org/theia
|
|
@@ -459,10 +459,10 @@ let SearchInWorkspaceFrontendContribution = class SearchInWorkspaceFrontendContr
|
|
|
459
459
|
registerThemeStyle(theme, collector) {
|
|
460
460
|
const contrastBorder = theme.getColor('contrastBorder');
|
|
461
461
|
if (contrastBorder && (0, theme_1.isHighContrast)(theme.type)) {
|
|
462
|
-
collector.addRule(`
|
|
463
|
-
.t-siw-search-container .searchHeader .search-field-container {
|
|
464
|
-
border-color: ${contrastBorder};
|
|
465
|
-
}
|
|
462
|
+
collector.addRule(`
|
|
463
|
+
.t-siw-search-container .searchHeader .search-field-container {
|
|
464
|
+
border-color: ${contrastBorder};
|
|
465
|
+
}
|
|
466
466
|
`);
|
|
467
467
|
}
|
|
468
468
|
}
|
|
@@ -101,62 +101,62 @@ before(() => {
|
|
|
101
101
|
contents += ('long-' + pad.substring(0, pad.length - str.length) + str);
|
|
102
102
|
}
|
|
103
103
|
createTestFile('long-line', contents);
|
|
104
|
-
createTestFile('carrots', `\
|
|
105
|
-
This is a carrot.
|
|
106
|
-
Most carrots are orange, but some carrots are not.
|
|
107
|
-
Once capitalized, the word carrot looks like this: CARROT.
|
|
108
|
-
Carrot is a funny word.
|
|
104
|
+
createTestFile('carrots', `\
|
|
105
|
+
This is a carrot.
|
|
106
|
+
Most carrots are orange, but some carrots are not.
|
|
107
|
+
Once capitalized, the word carrot looks like this: CARROT.
|
|
108
|
+
Carrot is a funny word.
|
|
109
109
|
`);
|
|
110
|
-
createTestFile('potatoes', `\
|
|
111
|
-
Potatoes, unlike carrots, are generally not orange. But sweet potatoes are,
|
|
112
|
-
it's very confusing.
|
|
110
|
+
createTestFile('potatoes', `\
|
|
111
|
+
Potatoes, unlike carrots, are generally not orange. But sweet potatoes are,
|
|
112
|
+
it's very confusing.
|
|
113
113
|
`);
|
|
114
114
|
createTestFile('pastas', 'pasta pasta');
|
|
115
|
-
createTestFile('regexes', `\
|
|
116
|
-
aaa hello. x h3lo y hell0h3lllo
|
|
117
|
-
hello1
|
|
115
|
+
createTestFile('regexes', `\
|
|
116
|
+
aaa hello. x h3lo y hell0h3lllo
|
|
117
|
+
hello1
|
|
118
118
|
`);
|
|
119
119
|
const smallDirPath = rootDirA + '/small';
|
|
120
120
|
fs.mkdirSync(smallDirPath);
|
|
121
121
|
createTestFile('small', 'A small file.\n');
|
|
122
|
-
const copyrightLine = '\
|
|
122
|
+
const copyrightLine = '\
|
|
123
123
|
Copyright (C) 2021 <Company> and others.';
|
|
124
124
|
fs.mkdirSync(smallDirPath + '/test');
|
|
125
125
|
createTestFile('small/test/test-spec.ts', copyrightLine);
|
|
126
126
|
fs.mkdirSync(rootDirA + '/test');
|
|
127
127
|
createTestFile('test/test-spec.ts', copyrightLine);
|
|
128
128
|
fs.mkdirSync(rootDirA + '/orange');
|
|
129
|
-
createTestFile('orange/hamlin', '\
|
|
129
|
+
createTestFile('orange/hamlin', '\
|
|
130
130
|
Hamlin orange is one of our most cold-hardy sweet oranges. Grown since 1885');
|
|
131
|
-
createTestFile('orange/navel', '\
|
|
131
|
+
createTestFile('orange/navel', '\
|
|
132
132
|
Most well known orange type');
|
|
133
133
|
if (!core_1.isWindows) {
|
|
134
|
-
createTestFile('file:with:some:colons', `\
|
|
135
|
-
Are you looking for this: --foobar?
|
|
134
|
+
createTestFile('file:with:some:colons', `\
|
|
135
|
+
Are you looking for this: --foobar?
|
|
136
136
|
`);
|
|
137
137
|
}
|
|
138
|
-
createTestFile('file with spaces', `\
|
|
139
|
-
Are you looking for this: --foobar?
|
|
138
|
+
createTestFile('file with spaces', `\
|
|
139
|
+
Are you looking for this: --foobar?
|
|
140
140
|
`);
|
|
141
|
-
createTestFile('utf8-file', `\
|
|
142
|
-
Var är jag? Varför är jag här?
|
|
141
|
+
createTestFile('utf8-file', `\
|
|
142
|
+
Var är jag? Varför är jag här?
|
|
143
143
|
`);
|
|
144
|
-
createTestFile('special shell characters', `\
|
|
145
|
-
If one uses \`salut";\' echo foo && echo bar; "\` as a search term it should not be a problem to find here.
|
|
144
|
+
createTestFile('special shell characters', `\
|
|
145
|
+
If one uses \`salut";\' echo foo && echo bar; "\` as a search term it should not be a problem to find here.
|
|
146
146
|
`);
|
|
147
|
-
createTestFile('glob.txt', `\
|
|
148
|
-
test -glob patterns
|
|
147
|
+
createTestFile('glob.txt', `\
|
|
148
|
+
test -glob patterns
|
|
149
149
|
`);
|
|
150
|
-
createTestFile('glob', `\
|
|
151
|
-
test --glob patterns
|
|
150
|
+
createTestFile('glob', `\
|
|
151
|
+
test --glob patterns
|
|
152
152
|
`);
|
|
153
153
|
let lotsOfMatchesText = '';
|
|
154
154
|
for (let i = 0; i < 100000; i++) {
|
|
155
155
|
lotsOfMatchesText += 'lots-of-matches\n';
|
|
156
156
|
}
|
|
157
157
|
createTestFile('lots-of-matches', lotsOfMatchesText);
|
|
158
|
-
createTestFile('orange', `\
|
|
159
|
-
the oranges' orange looks slightly different from carrots' orange.
|
|
158
|
+
createTestFile('orange', `\
|
|
159
|
+
the oranges' orange looks slightly different from carrots' orange.
|
|
160
160
|
`);
|
|
161
161
|
createTestFile('folderSubfolder', 'a file in the subfolder of a folder.');
|
|
162
162
|
});
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theia/search-in-workspace",
|
|
3
|
-
"version": "1.53.0-next.
|
|
3
|
+
"version": "1.53.0-next.55+d1a989a68c",
|
|
4
4
|
"description": "Theia - Search in workspace",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@theia/core": "1.53.0-next.
|
|
7
|
-
"@theia/editor": "1.53.0-next.
|
|
8
|
-
"@theia/filesystem": "1.53.0-next.
|
|
9
|
-
"@theia/navigator": "1.53.0-next.
|
|
10
|
-
"@theia/process": "1.53.0-next.
|
|
11
|
-
"@theia/workspace": "1.53.0-next.
|
|
6
|
+
"@theia/core": "1.53.0-next.55+d1a989a68c",
|
|
7
|
+
"@theia/editor": "1.53.0-next.55+d1a989a68c",
|
|
8
|
+
"@theia/filesystem": "1.53.0-next.55+d1a989a68c",
|
|
9
|
+
"@theia/navigator": "1.53.0-next.55+d1a989a68c",
|
|
10
|
+
"@theia/process": "1.53.0-next.55+d1a989a68c",
|
|
11
|
+
"@theia/workspace": "1.53.0-next.55+d1a989a68c",
|
|
12
12
|
"@vscode/ripgrep": "^1.14.2",
|
|
13
13
|
"minimatch": "^5.1.0",
|
|
14
14
|
"react-autosize-textarea": "^7.0.0",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"watch": "theiaext watch"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@theia/ext-scripts": "1.
|
|
51
|
+
"@theia/ext-scripts": "1.53.0"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "d1a989a68c1b5ec1f9098e9126653c6346844769"
|
|
54
54
|
}
|
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
// *****************************************************************************
|
|
2
|
-
// Copyright (C) 2021 Ericsson and others.
|
|
3
|
-
//
|
|
4
|
-
// This program and the accompanying materials are made available under the
|
|
5
|
-
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
//
|
|
8
|
-
// This Source Code may also be made available under the following Secondary
|
|
9
|
-
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
// with the GNU Classpath Exception which is available at
|
|
12
|
-
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
//
|
|
14
|
-
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
// *****************************************************************************
|
|
16
|
-
|
|
17
|
-
import * as React from '@theia/core/shared/react';
|
|
18
|
-
import { Key, KeyCode } from '@theia/core/lib/browser';
|
|
19
|
-
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
20
|
-
|
|
21
|
-
interface HistoryState {
|
|
22
|
-
history: string[];
|
|
23
|
-
index: number;
|
|
24
|
-
};
|
|
25
|
-
type InputAttributes = React.InputHTMLAttributes<HTMLInputElement>;
|
|
26
|
-
|
|
27
|
-
export class SearchInWorkspaceInput extends React.Component<InputAttributes, HistoryState> {
|
|
28
|
-
static LIMIT = 100;
|
|
29
|
-
|
|
30
|
-
private input = React.createRef<HTMLInputElement>();
|
|
31
|
-
|
|
32
|
-
constructor(props: InputAttributes) {
|
|
33
|
-
super(props);
|
|
34
|
-
this.state = {
|
|
35
|
-
history: [],
|
|
36
|
-
index: 0,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
updateState(index: number, history?: string[]): void {
|
|
41
|
-
this.value = history ? history[index] : this.state.history[index];
|
|
42
|
-
this.setState(prevState => {
|
|
43
|
-
const newState = {
|
|
44
|
-
...prevState,
|
|
45
|
-
index,
|
|
46
|
-
};
|
|
47
|
-
if (history) {
|
|
48
|
-
newState.history = history;
|
|
49
|
-
}
|
|
50
|
-
return newState;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get value(): string {
|
|
55
|
-
return this.input.current?.value ?? '';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
set value(value: string) {
|
|
59
|
-
if (this.input.current) {
|
|
60
|
-
this.input.current.value = value;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Handle history navigation without overriding the parent's onKeyDown handler, if any.
|
|
66
|
-
*/
|
|
67
|
-
protected readonly onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
68
|
-
if (Key.ARROW_UP.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode) {
|
|
69
|
-
e.preventDefault();
|
|
70
|
-
this.previousValue();
|
|
71
|
-
} else if (Key.ARROW_DOWN.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode) {
|
|
72
|
-
e.preventDefault();
|
|
73
|
-
this.nextValue();
|
|
74
|
-
}
|
|
75
|
-
this.props.onKeyDown?.(e);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Switch the input's text to the previous value, if any.
|
|
80
|
-
*/
|
|
81
|
-
previousValue(): void {
|
|
82
|
-
const { history, index } = this.state;
|
|
83
|
-
if (!this.value) {
|
|
84
|
-
this.value = history[index];
|
|
85
|
-
} else if (index > 0 && index < history.length) {
|
|
86
|
-
this.updateState(index - 1);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Switch the input's text to the next value, if any.
|
|
92
|
-
*/
|
|
93
|
-
nextValue(): void {
|
|
94
|
-
const { history, index } = this.state;
|
|
95
|
-
if (index === history.length - 1) {
|
|
96
|
-
this.value = '';
|
|
97
|
-
} else if (!this.value) {
|
|
98
|
-
this.value = history[index];
|
|
99
|
-
} else if (index >= 0 && index < history.length - 1) {
|
|
100
|
-
this.updateState(index + 1);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Handle history collection without overriding the parent's onChange handler, if any.
|
|
106
|
-
*/
|
|
107
|
-
protected readonly onChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
108
|
-
this.addToHistory();
|
|
109
|
-
this.props.onChange?.(e);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Add a nonempty current value to the history, if not already present. (Debounced, 1 second delay.)
|
|
114
|
-
*/
|
|
115
|
-
readonly addToHistory = debounce(this.doAddToHistory, 1000);
|
|
116
|
-
|
|
117
|
-
private doAddToHistory(): void {
|
|
118
|
-
if (!this.value) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const history = this.state.history
|
|
122
|
-
.filter(term => term !== this.value)
|
|
123
|
-
.concat(this.value)
|
|
124
|
-
.slice(-SearchInWorkspaceInput.LIMIT);
|
|
125
|
-
this.updateState(history.length - 1, history);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
override render(): React.ReactNode {
|
|
129
|
-
return (
|
|
130
|
-
<input
|
|
131
|
-
{...this.props}
|
|
132
|
-
onKeyDown={this.onKeyDown}
|
|
133
|
-
onChange={this.onChange}
|
|
134
|
-
spellCheck={false}
|
|
135
|
-
ref={this.input}
|
|
136
|
-
/>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2021 Ericsson and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import * as React from '@theia/core/shared/react';
|
|
18
|
+
import { Key, KeyCode } from '@theia/core/lib/browser';
|
|
19
|
+
import debounce = require('@theia/core/shared/lodash.debounce');
|
|
20
|
+
|
|
21
|
+
interface HistoryState {
|
|
22
|
+
history: string[];
|
|
23
|
+
index: number;
|
|
24
|
+
};
|
|
25
|
+
type InputAttributes = React.InputHTMLAttributes<HTMLInputElement>;
|
|
26
|
+
|
|
27
|
+
export class SearchInWorkspaceInput extends React.Component<InputAttributes, HistoryState> {
|
|
28
|
+
static LIMIT = 100;
|
|
29
|
+
|
|
30
|
+
private input = React.createRef<HTMLInputElement>();
|
|
31
|
+
|
|
32
|
+
constructor(props: InputAttributes) {
|
|
33
|
+
super(props);
|
|
34
|
+
this.state = {
|
|
35
|
+
history: [],
|
|
36
|
+
index: 0,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
updateState(index: number, history?: string[]): void {
|
|
41
|
+
this.value = history ? history[index] : this.state.history[index];
|
|
42
|
+
this.setState(prevState => {
|
|
43
|
+
const newState = {
|
|
44
|
+
...prevState,
|
|
45
|
+
index,
|
|
46
|
+
};
|
|
47
|
+
if (history) {
|
|
48
|
+
newState.history = history;
|
|
49
|
+
}
|
|
50
|
+
return newState;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get value(): string {
|
|
55
|
+
return this.input.current?.value ?? '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set value(value: string) {
|
|
59
|
+
if (this.input.current) {
|
|
60
|
+
this.input.current.value = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Handle history navigation without overriding the parent's onKeyDown handler, if any.
|
|
66
|
+
*/
|
|
67
|
+
protected readonly onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
68
|
+
if (Key.ARROW_UP.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
this.previousValue();
|
|
71
|
+
} else if (Key.ARROW_DOWN.keyCode === KeyCode.createKeyCode(e.nativeEvent).key?.keyCode) {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
this.nextValue();
|
|
74
|
+
}
|
|
75
|
+
this.props.onKeyDown?.(e);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Switch the input's text to the previous value, if any.
|
|
80
|
+
*/
|
|
81
|
+
previousValue(): void {
|
|
82
|
+
const { history, index } = this.state;
|
|
83
|
+
if (!this.value) {
|
|
84
|
+
this.value = history[index];
|
|
85
|
+
} else if (index > 0 && index < history.length) {
|
|
86
|
+
this.updateState(index - 1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Switch the input's text to the next value, if any.
|
|
92
|
+
*/
|
|
93
|
+
nextValue(): void {
|
|
94
|
+
const { history, index } = this.state;
|
|
95
|
+
if (index === history.length - 1) {
|
|
96
|
+
this.value = '';
|
|
97
|
+
} else if (!this.value) {
|
|
98
|
+
this.value = history[index];
|
|
99
|
+
} else if (index >= 0 && index < history.length - 1) {
|
|
100
|
+
this.updateState(index + 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Handle history collection without overriding the parent's onChange handler, if any.
|
|
106
|
+
*/
|
|
107
|
+
protected readonly onChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
108
|
+
this.addToHistory();
|
|
109
|
+
this.props.onChange?.(e);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Add a nonempty current value to the history, if not already present. (Debounced, 1 second delay.)
|
|
114
|
+
*/
|
|
115
|
+
readonly addToHistory = debounce(this.doAddToHistory, 1000);
|
|
116
|
+
|
|
117
|
+
private doAddToHistory(): void {
|
|
118
|
+
if (!this.value) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const history = this.state.history
|
|
122
|
+
.filter(term => term !== this.value)
|
|
123
|
+
.concat(this.value)
|
|
124
|
+
.slice(-SearchInWorkspaceInput.LIMIT);
|
|
125
|
+
this.updateState(history.length - 1, history);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
override render(): React.ReactNode {
|
|
129
|
+
return (
|
|
130
|
+
<input
|
|
131
|
+
{...this.props}
|
|
132
|
+
onKeyDown={this.onKeyDown}
|
|
133
|
+
onChange={this.onChange}
|
|
134
|
+
spellCheck={false}
|
|
135
|
+
ref={this.input}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|