@opensumi/ide-addons 2.21.13 → 2.22.0
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/lib/browser/chrome-devtools.contribution.js.map +1 -1
- package/lib/browser/connection-rtt-contribution.js.map +1 -1
- package/lib/browser/connection-rtt-service.js.map +1 -1
- package/lib/browser/file-content-update-time.contribution.js +4 -4
- package/lib/browser/file-content-update-time.contribution.js.map +1 -1
- package/lib/browser/file-drop.contribution.js.map +1 -1
- package/lib/browser/file-drop.service.js.map +1 -1
- package/lib/browser/file-search.contribution.d.ts.map +1 -1
- package/lib/browser/file-search.contribution.js +15 -6
- package/lib/browser/file-search.contribution.js.map +1 -1
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/language-change.contribution.js +1 -1
- package/lib/browser/language-change.contribution.js.map +1 -1
- package/lib/browser/status-bar-contribution.js.map +1 -1
- package/lib/browser/toolbar-customize/toolbar-customize.contribution.js.map +1 -1
- package/lib/browser/toolbar-customize/toolbar-customize.js +1 -1
- package/lib/browser/toolbar-customize/toolbar-customize.js.map +1 -1
- package/lib/node/connection-rtt-service.js.map +1 -1
- package/lib/node/file-drop.service.js.map +1 -1
- package/lib/node/index.js.map +1 -1
- package/package.json +17 -16
- package/src/browser/chrome-devtools.contribution.ts +66 -0
- package/src/browser/connection-rtt-contribution.ts +82 -0
- package/src/browser/connection-rtt-service.ts +15 -0
- package/src/browser/file-content-update-time.contribution.ts +256 -0
- package/src/browser/file-drop.contribution.ts +17 -0
- package/src/browser/file-drop.service.ts +165 -0
- package/src/browser/file-search.contribution.ts +612 -0
- package/src/browser/index.ts +44 -0
- package/src/browser/language-change.contribution.ts +45 -0
- package/src/browser/status-bar-contribution.ts +28 -0
- package/src/browser/toolbar-customize/style.module.less +52 -0
- package/src/browser/toolbar-customize/toolbar-customize.contribution.ts +59 -0
- package/src/browser/toolbar-customize/toolbar-customize.tsx +147 -0
- package/src/common/index.ts +46 -0
- package/src/index.ts +1 -0
- package/src/node/connection-rtt-service.ts +10 -0
- package/src/node/file-drop.service.ts +34 -0
- package/src/node/index.ts +37 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用于快速打开,检索文件
|
|
3
|
+
*/
|
|
4
|
+
import fuzzy from 'fuzzy';
|
|
5
|
+
|
|
6
|
+
import { Injectable, Autowired, INJECTOR_TOKEN, Injector } from '@opensumi/di';
|
|
7
|
+
import {
|
|
8
|
+
localize,
|
|
9
|
+
formatLocalize,
|
|
10
|
+
CommandService,
|
|
11
|
+
URI,
|
|
12
|
+
EDITOR_COMMANDS,
|
|
13
|
+
QuickOpenActionProvider,
|
|
14
|
+
QuickOpenItem,
|
|
15
|
+
PreferenceService,
|
|
16
|
+
getSymbolIcon,
|
|
17
|
+
Highlight,
|
|
18
|
+
Mode,
|
|
19
|
+
} from '@opensumi/ide-core-browser';
|
|
20
|
+
import { KeybindingContribution, KeybindingRegistry, ILogger } from '@opensumi/ide-core-browser';
|
|
21
|
+
import { getIcon } from '@opensumi/ide-core-browser';
|
|
22
|
+
import { RecentFilesManager } from '@opensumi/ide-core-browser';
|
|
23
|
+
import { LabelService } from '@opensumi/ide-core-browser/lib/services';
|
|
24
|
+
import {
|
|
25
|
+
CommandContribution,
|
|
26
|
+
CommandRegistry,
|
|
27
|
+
Command,
|
|
28
|
+
CancellationTokenSource,
|
|
29
|
+
Schemes,
|
|
30
|
+
CancellationToken,
|
|
31
|
+
IRange,
|
|
32
|
+
IReporterService,
|
|
33
|
+
REPORT_NAME,
|
|
34
|
+
} from '@opensumi/ide-core-common';
|
|
35
|
+
import { Domain } from '@opensumi/ide-core-common/lib/di-helper';
|
|
36
|
+
import { EditorGroupSplitAction, WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
37
|
+
import {
|
|
38
|
+
DocumentSymbolStore,
|
|
39
|
+
IDummyRoot,
|
|
40
|
+
INormalizedDocumentSymbol,
|
|
41
|
+
} from '@opensumi/ide-editor/lib/browser/breadcrumb/document-symbol';
|
|
42
|
+
import { FileSearchServicePath, IFileSearchService } from '@opensumi/ide-file-search/lib/common';
|
|
43
|
+
import {
|
|
44
|
+
QuickOpenModel,
|
|
45
|
+
QuickOpenOptions,
|
|
46
|
+
PrefixQuickOpenService,
|
|
47
|
+
QuickOpenBaseAction,
|
|
48
|
+
} from '@opensumi/ide-quick-open';
|
|
49
|
+
import {
|
|
50
|
+
QuickOpenContribution,
|
|
51
|
+
QuickOpenHandlerRegistry,
|
|
52
|
+
} from '@opensumi/ide-quick-open/lib/browser/prefix-quick-open.service';
|
|
53
|
+
import { IWorkspaceService } from '@opensumi/ide-workspace';
|
|
54
|
+
import { matchesFuzzy } from '@opensumi/monaco-editor-core/esm/vs/base/common/filters';
|
|
55
|
+
import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';
|
|
56
|
+
|
|
57
|
+
const DEFAULT_FILE_SEARCH_LIMIT = 200;
|
|
58
|
+
|
|
59
|
+
export const quickFileOpen: Command = {
|
|
60
|
+
id: 'workbench.action.quickOpen',
|
|
61
|
+
category: 'File',
|
|
62
|
+
label: 'Open File...',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const quickGoToSymbol: Command = {
|
|
66
|
+
id: 'workbench.action.gotoSymbol',
|
|
67
|
+
category: 'File',
|
|
68
|
+
label: 'Open File Symbol...',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// support /some/file.js(73,84)
|
|
72
|
+
// support /some/file.js#73,84
|
|
73
|
+
// support /some/file.js#L73
|
|
74
|
+
// support /some/file.js:73
|
|
75
|
+
// support /some/file.js:73:84
|
|
76
|
+
export const matchLineReg = /^([^:#(]*)[:#(]?L?(\d+)?[:,]?(\d+)?\)?/;
|
|
77
|
+
|
|
78
|
+
function getRangeByInput(input = ''): monaco.Range | undefined {
|
|
79
|
+
const matchList = input.match(matchLineReg) || [];
|
|
80
|
+
|
|
81
|
+
if (matchList.length < 2) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const lineInfo = {
|
|
86
|
+
line: Number(matchList[2] || 0),
|
|
87
|
+
start: Number(matchList[3] || 0),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return new monaco.Range(lineInfo.line, lineInfo.start, lineInfo.line, lineInfo.start);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getValidateInput(input: string) {
|
|
94
|
+
return input.replace(matchLineReg, '$1');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Injectable()
|
|
98
|
+
class FileSearchActionLeftRight extends QuickOpenBaseAction {
|
|
99
|
+
@Autowired(CommandService)
|
|
100
|
+
private readonly commandService: CommandService;
|
|
101
|
+
|
|
102
|
+
@Autowired(INJECTOR_TOKEN)
|
|
103
|
+
private readonly injector: Injector;
|
|
104
|
+
|
|
105
|
+
constructor() {
|
|
106
|
+
super({
|
|
107
|
+
id: 'file-search:splitToRight',
|
|
108
|
+
tooltip: localize('file-search.quickOpen.leftRight'),
|
|
109
|
+
class: getIcon('embed'),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async run(item: QuickOpenItem): Promise<void> {
|
|
114
|
+
await this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, item.getUri(), {
|
|
115
|
+
preview: false,
|
|
116
|
+
split: EditorGroupSplitAction.Right,
|
|
117
|
+
range: getRangeByInput(this.injector.get(FileSearchQuickCommandHandler).currentLookFor),
|
|
118
|
+
focus: true,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Injectable()
|
|
124
|
+
class FileSearchActionProvider implements QuickOpenActionProvider {
|
|
125
|
+
@Autowired()
|
|
126
|
+
private readonly fileSearchActionLeftRight: FileSearchActionLeftRight;
|
|
127
|
+
|
|
128
|
+
hasActions(): boolean {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getActions() {
|
|
133
|
+
return [this.fileSearchActionLeftRight];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getValidateInput(lookFor: string) {
|
|
137
|
+
return getValidateInput(lookFor);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Injectable()
|
|
142
|
+
export class FileSearchQuickCommandHandler {
|
|
143
|
+
@Autowired(CommandService)
|
|
144
|
+
private readonly commandService: CommandService;
|
|
145
|
+
|
|
146
|
+
@Autowired(FileSearchServicePath)
|
|
147
|
+
private readonly fileSearchService: IFileSearchService;
|
|
148
|
+
|
|
149
|
+
@Autowired(WorkbenchEditorService)
|
|
150
|
+
private readonly workbenchEditorService: WorkbenchEditorService;
|
|
151
|
+
|
|
152
|
+
@Autowired(DocumentSymbolStore)
|
|
153
|
+
private documentSymbolStore: DocumentSymbolStore;
|
|
154
|
+
|
|
155
|
+
@Autowired()
|
|
156
|
+
private readonly labelService: LabelService;
|
|
157
|
+
|
|
158
|
+
@Autowired(IWorkspaceService)
|
|
159
|
+
private readonly workspaceService: IWorkspaceService;
|
|
160
|
+
|
|
161
|
+
@Autowired(RecentFilesManager)
|
|
162
|
+
private readonly recentFilesManager: RecentFilesManager;
|
|
163
|
+
|
|
164
|
+
@Autowired(ILogger)
|
|
165
|
+
private readonly logger: ILogger;
|
|
166
|
+
|
|
167
|
+
@Autowired()
|
|
168
|
+
private readonly fileSearchActionProvider: FileSearchActionProvider;
|
|
169
|
+
|
|
170
|
+
@Autowired(PreferenceService)
|
|
171
|
+
private readonly preferenceService: PreferenceService;
|
|
172
|
+
|
|
173
|
+
@Autowired(IReporterService)
|
|
174
|
+
reporterService: IReporterService;
|
|
175
|
+
|
|
176
|
+
private cancelIndicator = new CancellationTokenSource();
|
|
177
|
+
readonly default: boolean = true;
|
|
178
|
+
readonly prefix: string = '...';
|
|
179
|
+
readonly description: string = localize('file-search.command.fileOpen.description');
|
|
180
|
+
private prevEditorState: { uri?: URI; range?: IRange } = {};
|
|
181
|
+
private prevSelected: URI | undefined;
|
|
182
|
+
|
|
183
|
+
currentLookFor = '';
|
|
184
|
+
|
|
185
|
+
getModel(): QuickOpenModel {
|
|
186
|
+
return {
|
|
187
|
+
onType: async (lookFor, acceptor) => {
|
|
188
|
+
this.cancelIndicator.cancel();
|
|
189
|
+
this.cancelIndicator = new CancellationTokenSource();
|
|
190
|
+
const token = this.cancelIndicator.token;
|
|
191
|
+
const alreadyCollected = new Set<string>();
|
|
192
|
+
let findResults: QuickOpenItem[] = [];
|
|
193
|
+
|
|
194
|
+
lookFor = lookFor.trim().replace(/\s/g, '');
|
|
195
|
+
this.currentLookFor = lookFor;
|
|
196
|
+
const validLookFor = getValidateInput(lookFor);
|
|
197
|
+
const timer = this.reporterService.time(REPORT_NAME.QUICK_OPEN_MEASURE);
|
|
198
|
+
const recentlyResultList: QuickOpenItem[] = await this.getRecentlyItems(alreadyCollected, validLookFor, token);
|
|
199
|
+
|
|
200
|
+
if (lookFor) {
|
|
201
|
+
this.logger.debug('lookFor', lookFor, validLookFor);
|
|
202
|
+
findResults = await this.getFindOutItems(alreadyCollected, validLookFor, token);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (token.isCancellationRequested) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const concatResults = recentlyResultList.concat(findResults);
|
|
210
|
+
acceptor(concatResults, concatResults.length ? this.fileSearchActionProvider : undefined);
|
|
211
|
+
|
|
212
|
+
timer.timeEnd(lookFor.indexOf('@') > -1 ? 'file-symbol' : 'file', {
|
|
213
|
+
lookFor,
|
|
214
|
+
stat: {
|
|
215
|
+
recently: recentlyResultList.length,
|
|
216
|
+
find: findResults.length,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getOptions(): QuickOpenOptions {
|
|
224
|
+
return {
|
|
225
|
+
placeholder: localize('file-search.command.fileOpen.placeholder'),
|
|
226
|
+
fuzzyMatchLabel: {
|
|
227
|
+
enableSeparateSubstringMatching: true,
|
|
228
|
+
},
|
|
229
|
+
showItemsWithoutHighlight: true,
|
|
230
|
+
fuzzyMatchDescription: {
|
|
231
|
+
enableSeparateSubstringMatching: true,
|
|
232
|
+
},
|
|
233
|
+
getPlaceholderItem: (lookFor: string) =>
|
|
234
|
+
new QuickOpenItem({
|
|
235
|
+
label: localize(
|
|
236
|
+
lookFor.indexOf('@') > -1 ? 'search.fileSymbolResults.notfound' : 'search.fileResults.notfound',
|
|
237
|
+
),
|
|
238
|
+
run: () => false,
|
|
239
|
+
}),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
onClose(canceled?: boolean) {
|
|
244
|
+
if (canceled && this.prevEditorState.uri) {
|
|
245
|
+
// 取消时恢复打开quickOpen时的编辑器状态
|
|
246
|
+
this.workbenchEditorService.open(this.prevEditorState.uri, {
|
|
247
|
+
range: this.prevEditorState.range,
|
|
248
|
+
});
|
|
249
|
+
this.prevEditorState = {};
|
|
250
|
+
}
|
|
251
|
+
this.prevSelected = undefined;
|
|
252
|
+
this.commandService.executeCommand(EDITOR_COMMANDS.FOCUS.id);
|
|
253
|
+
this.cancelIndicator.cancel();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
onToggle() {
|
|
257
|
+
this.cancelIndicator.cancel();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 保存此时的编辑器uri和光标位置
|
|
261
|
+
private trySaveEditorState() {
|
|
262
|
+
if (this.workbenchEditorService.currentResource) {
|
|
263
|
+
let currentRange = { startColumn: 1, startLineNumber: 1, endColumn: 1, endLineNumber: 1 };
|
|
264
|
+
const selections = this.workbenchEditorService.currentEditor?.getSelections();
|
|
265
|
+
if (selections) {
|
|
266
|
+
const { selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn } = selections[0];
|
|
267
|
+
currentRange = new monaco.Range(
|
|
268
|
+
selectionStartLineNumber,
|
|
269
|
+
selectionStartColumn,
|
|
270
|
+
positionLineNumber,
|
|
271
|
+
positionColumn,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
this.prevEditorState = {
|
|
275
|
+
uri: this.workbenchEditorService.currentResource.uri,
|
|
276
|
+
range: currentRange,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private async getFindOutItems(alreadyCollected: Set<string>, lookFor: string, token: CancellationToken) {
|
|
282
|
+
let results: QuickOpenItem[];
|
|
283
|
+
// 有@时进入查找symbol逻辑
|
|
284
|
+
if (lookFor.indexOf('@') > -1) {
|
|
285
|
+
// save current editor state
|
|
286
|
+
this.trySaveEditorState();
|
|
287
|
+
results = [];
|
|
288
|
+
// 拆分文件查找和symbol查找query
|
|
289
|
+
const [fileQuery, symbolQuery] = lookFor.split('@');
|
|
290
|
+
let targetFile: URI | undefined;
|
|
291
|
+
// 默认采用缓存的结果,无缓存的结果则分析输入查找(直接粘贴 fileName@symbolName 场景)
|
|
292
|
+
if (this.prevSelected) {
|
|
293
|
+
targetFile = this.prevSelected.withoutFragment();
|
|
294
|
+
} else if (fileQuery) {
|
|
295
|
+
const files = await this.getQueryFiles(fileQuery, alreadyCollected, token);
|
|
296
|
+
if (files.length) {
|
|
297
|
+
targetFile = files[0].getUri();
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
targetFile = this.workbenchEditorService.currentResource?.uri;
|
|
301
|
+
}
|
|
302
|
+
if (targetFile) {
|
|
303
|
+
const symbols = (await this.documentSymbolStore.getDocumentSymbolAsync(targetFile)) || [];
|
|
304
|
+
// 将symbol tree节点展开
|
|
305
|
+
const flatSymbols: INormalizedDocumentSymbol[] = [];
|
|
306
|
+
this.flattenSymbols({ children: symbols }, flatSymbols);
|
|
307
|
+
const items: QuickOpenItem[] = flatSymbols
|
|
308
|
+
.filter((item) => {
|
|
309
|
+
// 手动匹配symbol并高亮
|
|
310
|
+
const matchRange: Highlight[] = matchesFuzzy(symbolQuery, item.name, true) || [];
|
|
311
|
+
if (matchRange) {
|
|
312
|
+
(item as any).labelHighlights = matchRange;
|
|
313
|
+
}
|
|
314
|
+
return matchRange && matchRange.length;
|
|
315
|
+
})
|
|
316
|
+
.map(
|
|
317
|
+
(symbol, index) =>
|
|
318
|
+
new QuickOpenItem({
|
|
319
|
+
uri: targetFile,
|
|
320
|
+
label: symbol.name,
|
|
321
|
+
iconClass: getSymbolIcon(symbol.kind),
|
|
322
|
+
description: (symbol.parent as INormalizedDocumentSymbol)?.name,
|
|
323
|
+
labelHighlights: (symbol as any).labelHighlights,
|
|
324
|
+
groupLabel: index === 0 ? formatLocalize('search.fileSymbolResults', flatSymbols.length) : '',
|
|
325
|
+
showBorder: false,
|
|
326
|
+
run: (mode: Mode) => {
|
|
327
|
+
if (mode === Mode.PREVIEW) {
|
|
328
|
+
this.locateSymbol(targetFile!, symbol);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
if (mode === Mode.OPEN) {
|
|
332
|
+
this.locateSymbol(targetFile!, symbol);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
},
|
|
337
|
+
}),
|
|
338
|
+
);
|
|
339
|
+
results = items;
|
|
340
|
+
} else {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
results = await this.getQueryFiles(lookFor, alreadyCollected, token);
|
|
345
|
+
// 排序后设置第一个元素的样式
|
|
346
|
+
if (results[0]) {
|
|
347
|
+
const newItems = await this.getItems([results[0].getUri()!.toString()], {
|
|
348
|
+
groupLabel: localize('search.fileResults'),
|
|
349
|
+
showBorder: true,
|
|
350
|
+
});
|
|
351
|
+
results[0] = newItems[0];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return results;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
protected async getQueryFiles(fileQuery: string, alreadyCollected: Set<string>, token: CancellationToken) {
|
|
358
|
+
const roots = await this.workspaceService.roots;
|
|
359
|
+
const rootUris: string[] = [];
|
|
360
|
+
roots.forEach((stat) => {
|
|
361
|
+
const uri = new URI(stat.uri);
|
|
362
|
+
if (uri.scheme !== Schemes.file) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
this.logger.debug('file-search.contribution rootUri', uri.toString());
|
|
366
|
+
return rootUris.push(uri.toString());
|
|
367
|
+
});
|
|
368
|
+
const files = await this.fileSearchService.find(
|
|
369
|
+
fileQuery,
|
|
370
|
+
{
|
|
371
|
+
rootUris,
|
|
372
|
+
fuzzyMatch: true,
|
|
373
|
+
limit: DEFAULT_FILE_SEARCH_LIMIT,
|
|
374
|
+
useGitIgnore: true,
|
|
375
|
+
noIgnoreParent: true,
|
|
376
|
+
excludePatterns: this.getPreferenceSearchExcludes(),
|
|
377
|
+
},
|
|
378
|
+
token,
|
|
379
|
+
);
|
|
380
|
+
const results = await this.getItems(
|
|
381
|
+
files.filter((uri: string) => {
|
|
382
|
+
if (alreadyCollected.has(uri) || token.isCancellationRequested) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
alreadyCollected.add(uri);
|
|
386
|
+
return true;
|
|
387
|
+
}),
|
|
388
|
+
{},
|
|
389
|
+
);
|
|
390
|
+
results.sort(this.compareItems.bind(this));
|
|
391
|
+
return results;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private async flattenSymbols(symbol: INormalizedDocumentSymbol | IDummyRoot, list: INormalizedDocumentSymbol[]) {
|
|
395
|
+
symbol.children!.forEach((item) => {
|
|
396
|
+
list.push(item);
|
|
397
|
+
if (item.children) {
|
|
398
|
+
this.flattenSymbols(item, list);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private async getRecentlyItems(alreadyCollected, lookFor, token) {
|
|
404
|
+
const recentlyOpenedFiles = (await this.recentFilesManager.getMostRecentlyOpenedFiles(true)) || [];
|
|
405
|
+
|
|
406
|
+
return await this.getItems(
|
|
407
|
+
recentlyOpenedFiles.filter((uri: string) => {
|
|
408
|
+
const _uri = new URI(uri);
|
|
409
|
+
if (alreadyCollected.has(uri) || !fuzzy.test(lookFor, _uri.displayName) || token.isCancellationRequested) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
alreadyCollected.add(uri);
|
|
413
|
+
return true;
|
|
414
|
+
}),
|
|
415
|
+
{
|
|
416
|
+
groupLabel: localize('search.historyMatches'),
|
|
417
|
+
},
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private async getItems(uriList: string[], options: { [key: string]: any }) {
|
|
422
|
+
const items: QuickOpenItem[] = [];
|
|
423
|
+
|
|
424
|
+
for (const [index, strUri] of uriList.entries()) {
|
|
425
|
+
const uri = new URI(strUri);
|
|
426
|
+
const icon = `file-icon ${await this.labelService.getIcon(uri.withoutFragment())}`;
|
|
427
|
+
let description = '';
|
|
428
|
+
const relative = await this.workspaceService.asRelativePath(uri.parent);
|
|
429
|
+
if (relative) {
|
|
430
|
+
if (this.workspaceService.isMultiRootWorkspaceOpened) {
|
|
431
|
+
description = `${new URI(relative.root).displayName}${relative.path ? ` ・ ${relative.path}` : ''}`;
|
|
432
|
+
} else {
|
|
433
|
+
description = relative.path || '';
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const item = new QuickOpenItem({
|
|
437
|
+
uri,
|
|
438
|
+
label: uri.displayName,
|
|
439
|
+
tooltip: strUri,
|
|
440
|
+
iconClass: icon,
|
|
441
|
+
description,
|
|
442
|
+
groupLabel: index === 0 ? options.groupLabel : '',
|
|
443
|
+
showBorder: uriList.length > 0 && index === 0 ? options.showBorder : false,
|
|
444
|
+
run: (mode: Mode) => {
|
|
445
|
+
if (mode === Mode.PREVIEW) {
|
|
446
|
+
this.prevSelected = uri;
|
|
447
|
+
}
|
|
448
|
+
if (mode === Mode.OPEN) {
|
|
449
|
+
this.openFile(uri);
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
return false;
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
items.push(item);
|
|
456
|
+
}
|
|
457
|
+
return items;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private openFile(uri: URI) {
|
|
461
|
+
const filePath = uri.path.toString();
|
|
462
|
+
// 优先从输入上获取 line 和 column
|
|
463
|
+
let range = getRangeByInput(this.currentLookFor);
|
|
464
|
+
if (!range || (!range.startLineNumber && !range.startColumn)) {
|
|
465
|
+
range = getRangeByInput(uri.fragment ? filePath + '#' + uri.fragment : filePath);
|
|
466
|
+
}
|
|
467
|
+
this.currentLookFor = '';
|
|
468
|
+
this.commandService.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, uri.withoutFragment(), {
|
|
469
|
+
preview: false,
|
|
470
|
+
range,
|
|
471
|
+
focus: true,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private locateSymbol(uri: URI, symbol: INormalizedDocumentSymbol) {
|
|
476
|
+
this.workbenchEditorService.open(uri, {
|
|
477
|
+
range: symbol.range,
|
|
478
|
+
preview: true,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Compare two `QuickOpenItem`.
|
|
484
|
+
*
|
|
485
|
+
* @param a `QuickOpenItem` for comparison.
|
|
486
|
+
* @param b `QuickOpenItem` for comparison.
|
|
487
|
+
* @param member the `QuickOpenItem` object member for comparison.
|
|
488
|
+
*/
|
|
489
|
+
private compareItems(a: QuickOpenItem, b: QuickOpenItem, member: 'getLabel' | 'getUri' = 'getLabel'): number {
|
|
490
|
+
/**
|
|
491
|
+
* Normalize a given string.
|
|
492
|
+
*
|
|
493
|
+
* @param str the raw string value.
|
|
494
|
+
* @returns the normalized string value.
|
|
495
|
+
*/
|
|
496
|
+
function normalize(str: string) {
|
|
497
|
+
return str.trim().toLowerCase();
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Normalize the user query.
|
|
501
|
+
const query: string = normalize(getValidateInput(this.currentLookFor));
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Score a given string.
|
|
505
|
+
*
|
|
506
|
+
* @param str the string to score on.
|
|
507
|
+
* @returns the score.
|
|
508
|
+
*/
|
|
509
|
+
function score(str: string): number {
|
|
510
|
+
const match = fuzzy.match(query, str);
|
|
511
|
+
return match === null ? 0 : match.score;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Some code copied and modified from https://github.com/eclipse-theia/theia/tree/v1.14.0/packages/file-search/src/browser/quick-file-open.ts
|
|
515
|
+
|
|
516
|
+
// Get the item's member values for comparison.
|
|
517
|
+
let itemA = a[member]()!;
|
|
518
|
+
let itemB = b[member]()!;
|
|
519
|
+
|
|
520
|
+
// If the `URI` is used as a comparison member, perform the necessary string conversions.
|
|
521
|
+
if (typeof itemA !== 'string') {
|
|
522
|
+
itemA = itemA.path.toString();
|
|
523
|
+
}
|
|
524
|
+
if (typeof itemB !== 'string') {
|
|
525
|
+
itemB = itemB.path.toString();
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Normalize the item labels.
|
|
529
|
+
itemA = normalize(itemA);
|
|
530
|
+
itemB = normalize(itemB);
|
|
531
|
+
|
|
532
|
+
// Score the item labels.
|
|
533
|
+
const scoreA: number = score(itemA);
|
|
534
|
+
const scoreB: number = score(itemB);
|
|
535
|
+
|
|
536
|
+
// If both label scores are identical, perform additional computation.
|
|
537
|
+
if (scoreA === scoreB) {
|
|
538
|
+
// Favor the label which have the smallest substring index.
|
|
539
|
+
const indexA: number = itemA.indexOf(query);
|
|
540
|
+
const indexB: number = itemB.indexOf(query);
|
|
541
|
+
|
|
542
|
+
if (indexA === indexB) {
|
|
543
|
+
// Favor the result with the shortest label length.
|
|
544
|
+
if (itemA.length !== itemB.length) {
|
|
545
|
+
return itemA.length < itemB.length ? -1 : 1;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Fallback to the alphabetical order.
|
|
549
|
+
const comparison = itemB.localeCompare(itemA);
|
|
550
|
+
|
|
551
|
+
// If the alphabetical comparison is equal, call `compareItems` recursively using the `URI` member instead.
|
|
552
|
+
if (comparison === 0) {
|
|
553
|
+
return member === 'getUri'
|
|
554
|
+
? 0 // Avoid infinite recursion if we have already compared by `uri`.
|
|
555
|
+
: this.compareItems(a, b, 'getUri');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return itemB.localeCompare(itemA);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return indexA - indexB;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return scoreB - scoreA;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private getPreferenceSearchExcludes(): string[] {
|
|
568
|
+
const excludes: string[] = [];
|
|
569
|
+
const fileExcludes = this.preferenceService.get<object>('files.exclude');
|
|
570
|
+
const searchExcludes = this.preferenceService.get<object>('search.exclude');
|
|
571
|
+
const allExcludes = Object.assign({}, fileExcludes, searchExcludes);
|
|
572
|
+
for (const key of Object.keys(allExcludes)) {
|
|
573
|
+
if (allExcludes[key]) {
|
|
574
|
+
excludes.push(key);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return excludes;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@Domain(CommandContribution, KeybindingContribution, QuickOpenContribution)
|
|
582
|
+
export class FileSearchContribution implements CommandContribution, KeybindingContribution, QuickOpenContribution {
|
|
583
|
+
@Autowired(FileSearchQuickCommandHandler)
|
|
584
|
+
protected fileSearchQuickCommandHandler: FileSearchQuickCommandHandler;
|
|
585
|
+
|
|
586
|
+
@Autowired(PrefixQuickOpenService)
|
|
587
|
+
protected readonly quickOpenService: PrefixQuickOpenService;
|
|
588
|
+
|
|
589
|
+
registerQuickOpenHandlers(quickOpenHandlerRegistry: QuickOpenHandlerRegistry) {
|
|
590
|
+
quickOpenHandlerRegistry.registerHandler(this.fileSearchQuickCommandHandler, {
|
|
591
|
+
title: localize('quickopen.tab.file'),
|
|
592
|
+
commandId: quickFileOpen.id,
|
|
593
|
+
order: 1,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
registerCommands(commands: CommandRegistry): void {
|
|
598
|
+
commands.registerCommand(quickFileOpen, {
|
|
599
|
+
execute: () => this.quickOpenService.open('...'),
|
|
600
|
+
});
|
|
601
|
+
commands.registerCommand(quickGoToSymbol, {
|
|
602
|
+
execute: () => this.quickOpenService.open('...@'),
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
registerKeybindings(keybindings: KeybindingRegistry): void {
|
|
607
|
+
keybindings.registerKeybinding({
|
|
608
|
+
command: quickFileOpen.id,
|
|
609
|
+
keybinding: 'ctrlcmd+p',
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Injectable } from '@opensumi/di';
|
|
2
|
+
import { BrowserModule } from '@opensumi/ide-core-browser';
|
|
3
|
+
|
|
4
|
+
import { IFileDropFrontendServiceToken, FileDropServicePath, ConnectionBackServicePath } from '../common';
|
|
5
|
+
|
|
6
|
+
import { ChromeDevtoolsContribution } from './chrome-devtools.contribution';
|
|
7
|
+
import { ConnectionRTTContribution } from './connection-rtt-contribution';
|
|
8
|
+
import { ConnectionRTTBrowserService, ConnectionRTTBrowserServiceToken } from './connection-rtt-service';
|
|
9
|
+
import { FileDropContribution } from './file-drop.contribution';
|
|
10
|
+
import { FileDropService } from './file-drop.service';
|
|
11
|
+
import { FileSearchContribution } from './file-search.contribution';
|
|
12
|
+
import { LanguageChangeHintContribution } from './language-change.contribution';
|
|
13
|
+
import { StatusBarContribution } from './status-bar-contribution';
|
|
14
|
+
import { ToolbarCustomizeContribution } from './toolbar-customize/toolbar-customize.contribution';
|
|
15
|
+
|
|
16
|
+
@Injectable()
|
|
17
|
+
export class ClientAddonModule extends BrowserModule {
|
|
18
|
+
providers = [
|
|
19
|
+
ChromeDevtoolsContribution,
|
|
20
|
+
LanguageChangeHintContribution,
|
|
21
|
+
FileSearchContribution,
|
|
22
|
+
StatusBarContribution,
|
|
23
|
+
ToolbarCustomizeContribution,
|
|
24
|
+
FileDropContribution,
|
|
25
|
+
ConnectionRTTContribution,
|
|
26
|
+
{
|
|
27
|
+
token: IFileDropFrontendServiceToken,
|
|
28
|
+
useClass: FileDropService,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
token: ConnectionRTTBrowserServiceToken,
|
|
32
|
+
useClass: ConnectionRTTBrowserService,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
backServices = [
|
|
37
|
+
{
|
|
38
|
+
servicePath: FileDropServicePath,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
servicePath: ConnectionBackServicePath,
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Autowired } from '@opensumi/di';
|
|
2
|
+
import {
|
|
3
|
+
Domain,
|
|
4
|
+
ClientAppContribution,
|
|
5
|
+
PreferenceService,
|
|
6
|
+
IClientApp,
|
|
7
|
+
localize,
|
|
8
|
+
GeneralSettingsId,
|
|
9
|
+
setLanguageId,
|
|
10
|
+
} from '@opensumi/ide-core-browser';
|
|
11
|
+
import { IDialogService } from '@opensumi/ide-overlay';
|
|
12
|
+
|
|
13
|
+
@Domain(ClientAppContribution)
|
|
14
|
+
export class LanguageChangeHintContribution implements ClientAppContribution {
|
|
15
|
+
@Autowired(PreferenceService)
|
|
16
|
+
preferenceService: PreferenceService;
|
|
17
|
+
|
|
18
|
+
@Autowired(IClientApp)
|
|
19
|
+
clientApp: IClientApp;
|
|
20
|
+
|
|
21
|
+
@Autowired(IDialogService)
|
|
22
|
+
dialogService: IDialogService;
|
|
23
|
+
|
|
24
|
+
onStart() {
|
|
25
|
+
this.preferenceService.onSpecificPreferenceChange(GeneralSettingsId.Language, async (change) => {
|
|
26
|
+
setLanguageId(change.newValue);
|
|
27
|
+
const shouldAsk = this.preferenceService.get('general.askReloadOnLanguageChange');
|
|
28
|
+
if (shouldAsk) {
|
|
29
|
+
const msg = await this.dialogService.info(
|
|
30
|
+
localize(
|
|
31
|
+
'preference.general.language.change.refresh.info',
|
|
32
|
+
'After changing the language, it should be restarted to take effect. Will it be refreshed immediately?',
|
|
33
|
+
),
|
|
34
|
+
[
|
|
35
|
+
localize('preference.general.language.change.refresh.later', 'Later'),
|
|
36
|
+
localize('preference.general.language.change.refresh.now', 'Now'),
|
|
37
|
+
],
|
|
38
|
+
);
|
|
39
|
+
if (msg === localize('preference.general.language.change.refresh.now', 'Now')) {
|
|
40
|
+
this.clientApp.fireOnReload();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|