Skip to content

Commit

Permalink
[7576]: Support for vscode.workspace.findTextInFiles API
Browse files Browse the repository at this point in the history
Signed-off-by: Ignacio Moreno <ignacio@genuitec.com>
  • Loading branch information
nmorenor authored and akosyakov committed Jul 5, 2020
1 parent dd9cb7c commit 7a88d28
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 6 deletions.
6 changes: 6 additions & 0 deletions packages/core/src/browser/style/sidepanel.css
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,9 @@
overflow: hidden;
text-overflow: ellipsis;
}

.noWrapInfoTree {
white-space: pre;
overflow: hidden;
text-overflow: ellipsis;
}
18 changes: 18 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,21 @@ export interface RenameFilesEventDTO {
export interface DeleteFilesEventDTO {
files: UriComponents[]
}

export interface SearchInWorkspaceResult {
root: string;
fileUri: string;
matches: SearchMatch[];
}

export interface SearchMatch {
line: number;
character: number;
length: number;
lineText: string | LinePreview;

}
export interface LinePreview {
text: string;
character: number;
}
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
CreateFilesEventDTO,
RenameFilesEventDTO,
DeleteFilesEventDTO,
SearchInWorkspaceResult
} from './plugin-api-rpc-model';
import { ExtPluginApi } from './plugin-ext-api-contribution';
import { KeysToAnyValues, KeysToKeysToAnyValue } from './types';
Expand Down Expand Up @@ -526,6 +527,8 @@ export interface WorkspaceMain {
$pickWorkspaceFolder(options: WorkspaceFolderPickOptionsMain): Promise<theia.WorkspaceFolder | undefined>;
$startFileSearch(includePattern: string, includeFolder: string | undefined, excludePatternOrDisregardExcludes: string | false,
maxResults: number | undefined, token: theia.CancellationToken): PromiseLike<UriComponents[]>;
$findTextInFiles(query: theia.TextSearchQuery, options: theia.FindTextInFilesOptions, searchRequestId: number,
token?: theia.CancellationToken): Promise<theia.TextSearchComplete>
$registerTextDocumentContentProvider(scheme: string): Promise<void>;
$unregisterTextDocumentContentProvider(scheme: string): void;
$onTextDocumentContentChange(uri: string, content: string): void;
Expand All @@ -545,6 +548,7 @@ export interface WorkspaceExt {
$onDidRenameFiles(event: RenameFilesEventDTO): void;
$onWillDeleteFiles(event: DeleteFilesEventDTO): Promise<any[]>;
$onDidDeleteFiles(event: DeleteFilesEventDTO): void;
$onTextSearchResult(searchRequestId: number, done: boolean, result?: SearchInWorkspaceResult): void;
}

export interface DialogsMain {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export class TreeViewWidget extends TreeWidget {
});
}

return <div className='noWrapInfo'>
return <div className='noWrapInfoTree'>
{...nodes}
{work && <span>{work}</span>}
{description && <span className='theia-tree-view-description'>
Expand Down
71 changes: 70 additions & 1 deletion packages/plugin-ext/src/main/browser/workspace-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ import URI from '@theia/core/lib/common/uri';
import { WorkspaceService } from '@theia/workspace/lib/browser';
import { Resource } from '@theia/core/lib/common/resource';
import { Disposable, DisposableCollection } from '@theia/core/lib/common/disposable';
import { Emitter, Event, ResourceResolver } from '@theia/core';
import { Emitter, Event, ResourceResolver, CancellationToken } from '@theia/core';
import { FileWatcherSubscriberOptions } from '../../common/plugin-api-rpc-model';
import { InPluginFileSystemWatcherManager } from './in-plugin-filesystem-watcher-manager';
import { PluginServer } from '../../common/plugin-protocol';
import { FileSystemPreferences, FileSystemWatcher } from '@theia/filesystem/lib/browser';
import { SearchInWorkspaceService } from '@theia/search-in-workspace/lib/browser/search-in-workspace-service';

export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

Expand All @@ -44,6 +45,8 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

private fileSearchService: FileSearchService;

private searchInWorkspaceService: SearchInWorkspaceService;

private inPluginFileSystemWatcherManager: InPluginFileSystemWatcherManager;

private roots: FileStat[];
Expand All @@ -60,11 +63,14 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {

protected readonly toDispose = new DisposableCollection();

protected workspaceSearch: Set<number> = new Set<number>();

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.WORKSPACE_EXT);
this.storageProxy = rpc.getProxy(MAIN_RPC_CONTEXT.STORAGE_EXT);
this.quickOpenService = container.get(MonacoQuickOpenService);
this.fileSearchService = container.get(FileSearchService);
this.searchInWorkspaceService = container.get(SearchInWorkspaceService);
this.resourceResolver = container.get(TextContentResourceResolver);
this.pluginServer = container.get(PluginServer);
this.workspaceService = container.get(WorkspaceService);
Expand Down Expand Up @@ -227,6 +233,69 @@ export class WorkspaceMainImpl implements WorkspaceMain, Disposable {
return uriStrs.map(uriStr => Uri.parse(uriStr));
}

async $findTextInFiles(query: theia.TextSearchQuery, options: theia.FindTextInFilesOptions, searchRequestId: number,
token: theia.CancellationToken = CancellationToken.None): Promise<theia.TextSearchComplete> {
const maxHits = options.maxResults ? options.maxResults : 150;
const excludes = options.exclude ? (typeof options.exclude === 'string' ? options.exclude : (<theia.RelativePattern>options.exclude).pattern) : undefined;
const includes = options.include ? (typeof options.include === 'string' ? options.include : (<theia.RelativePattern>options.include).pattern) : undefined;
let canceledRequest = false;
return new Promise(resolve => {
let matches = 0;
const what: string = query.pattern;
const rootUris = this.roots.map(r => r.uri);
this.searchInWorkspaceService.searchWithCallback(what, rootUris, {
onResult: (searchId, result) => {
if (canceledRequest) {
return;
}
const hasSearch = this.workspaceSearch.has(searchId);
if (!hasSearch) {
this.workspaceSearch.add(searchId);
token.onCancellationRequested(() => {
this.searchInWorkspaceService.cancel(searchId);
canceledRequest = true;
});
}
if (token.isCancellationRequested) {
this.searchInWorkspaceService.cancel(searchId);
canceledRequest = true;
return;
}
if (result && result.matches && result.matches.length) {
while ((matches + result.matches.length) > maxHits) {
result.matches.splice(result.matches.length - 1, 1);
}
this.proxy.$onTextSearchResult(searchRequestId, false, result);
matches += result.matches.length;
if (maxHits <= matches) {
this.searchInWorkspaceService.cancel(searchId);
}
}
},
onDone: (searchId, _error) => {
const hasSearch = this.workspaceSearch.has(searchId);
if (hasSearch) {
this.searchInWorkspaceService.cancel(searchId);
this.workspaceSearch.delete(searchId);
}
this.proxy.$onTextSearchResult(searchRequestId, true);
if (maxHits <= matches) {
resolve({ limitHit: true });
} else {
resolve({ limitHit: false });
}
}
}, {
useRegExp: query.isRegExp,
matchCase: query.isCaseSensitive,
matchWholeWord: query.isWordMatch,
exclude: excludes ? [excludes] : undefined,
include: includes ? [includes] : undefined,
maxResults: maxHits
});
});
}

async $registerFileSystemWatcher(options: FileWatcherSubscriberOptions): Promise<string> {
const handle = this.inPluginFileSystemWatcherManager.registerFileWatchSubscription(options, this.proxy);
this.toDispose.push(Disposable.create(() => this.inPluginFileSystemWatcherManager.unregisterFileWatchSubscription(handle)));
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@ export function createAPIFactory(
findFiles(include: theia.GlobPattern, exclude?: theia.GlobPattern | null, maxResults?: number, token?: CancellationToken): PromiseLike<Uri[]> {
return workspaceExt.findFiles(include, exclude, maxResults, token);
},
findTextInFiles(query: theia.TextSearchQuery, optionsOrCallback: theia.FindTextInFilesOptions | ((result: theia.TextSearchResult) => void),
callbackOrToken?: CancellationToken | ((result: theia.TextSearchResult) => void), token?: CancellationToken): Promise<theia.TextSearchComplete> {
return workspaceExt.findTextInFiles(query, optionsOrCallback, callbackOrToken, token);
},
saveAll(includeUntitled?: boolean): PromiseLike<boolean> {
return editors.saveAll(includeUntitled);
},
Expand Down
70 changes: 69 additions & 1 deletion packages/plugin-ext/src/plugin/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,15 @@ import {
} from '../common/plugin-api-rpc';
import { Path } from '@theia/core/lib/common/path';
import { RPCProtocol } from '../common/rpc-protocol';
import { WorkspaceRootsChangeEvent, FileChangeEvent, CreateFilesEventDTO, RenameFilesEventDTO, DeleteFilesEventDTO } from '../common/plugin-api-rpc-model';
import {
WorkspaceRootsChangeEvent,
FileChangeEvent,
CreateFilesEventDTO,
RenameFilesEventDTO,
DeleteFilesEventDTO,
SearchInWorkspaceResult,
Range
} from '../common/plugin-api-rpc-model';
import { EditorsAndDocumentsExtImpl } from './editors-and-documents';
import { InPluginFileSystemWatcherProxy } from './in-plugin-filesystem-watcher-proxy';
import { URI } from 'vscode-uri';
Expand All @@ -42,6 +50,7 @@ import { relative } from '../common/paths-util';
import { Schemes } from '../common/uri-components';
import { toWorkspaceFolder } from './type-converters';
import { MessageRegistryExt } from './message-registry';
import * as Converter from './type-converters';

export class WorkspaceExtImpl implements WorkspaceExt {

Expand Down Expand Up @@ -71,6 +80,8 @@ export class WorkspaceExtImpl implements WorkspaceExt {

private folders: theia.WorkspaceFolder[] | undefined;
private documentContentProviders = new Map<string, theia.TextDocumentContentProvider>();
private searchInWorkspaceEmitter: Emitter<{ result?: theia.TextSearchResult, searchId: number }> = new Emitter<{ result?: theia.TextSearchResult, searchId: number }>();
protected workspaceSearchSequence: number = 0;

constructor(rpc: RPCProtocol,
private editorsAndDocuments: EditorsAndDocumentsExtImpl,
Expand Down Expand Up @@ -106,6 +117,31 @@ export class WorkspaceExtImpl implements WorkspaceExt {
this.workspaceFoldersChangedEmitter.fire(delta);
}

$onTextSearchResult(searchRequestId: number, done: boolean, result?: SearchInWorkspaceResult): void {
if (result) {
result.matches.map(next => {
const range: Range = {
endColumn: next.character + next.length,
endLineNumber: next.line + 1,
startColumn: next.character,
startLineNumber: next.line + 1
};
const tRange = <theia.Range>Converter.toRange(range);
const searchResult: theia.TextSearchMatch = {
uri: URI.parse(result.fileUri),
preview: {
text: typeof next.lineText === 'string' ? next.lineText : next.lineText.text,
matches: tRange
},
ranges: tRange
};
return searchResult;
}).forEach(next => this.searchInWorkspaceEmitter.fire({ result: next, searchId: searchRequestId }));
} else if (done) {
this.searchInWorkspaceEmitter.fire({ searchId: searchRequestId });
}
}

private deltaFolders(currentFolders: theia.WorkspaceFolder[] = [], newFolders: theia.WorkspaceFolder[] = []): {
added: theia.WorkspaceFolder[]
removed: theia.WorkspaceFolder[]
Expand Down Expand Up @@ -182,6 +218,38 @@ export class WorkspaceExtImpl implements WorkspaceExt {
.then(data => Array.isArray(data) ? data.map(uri => URI.revive(uri)) : []);
}

findTextInFiles(query: theia.TextSearchQuery, optionsOrCallback: theia.FindTextInFilesOptions | ((result: theia.TextSearchResult) => void),
callbackOrToken?: CancellationToken | ((result: theia.TextSearchResult) => void), token?: CancellationToken): Promise<theia.TextSearchComplete> {
let options: theia.FindTextInFilesOptions;
let callback: (result: theia.TextSearchResult) => void;

if (typeof optionsOrCallback === 'object') {
options = optionsOrCallback;
callback = callbackOrToken as (result: theia.TextSearchResult) => void;
} else {
options = {};
callback = optionsOrCallback;
token = callbackOrToken as CancellationToken;
}
const nextSearchID = this.workspaceSearchSequence + 1;
this.workspaceSearchSequence = nextSearchID;
const disposable = this.searchInWorkspaceEmitter.event(searchResult => {
if (searchResult.searchId === nextSearchID) {
if (searchResult.result) {
callback(searchResult.result);
} else {
disposable.dispose();
}
}
});
if (token) {
token.onCancellationRequested(() => {
disposable.dispose();
});
}
return this.proxy.$findTextInFiles(query, options || {}, nextSearchID, token);
}

createFileSystemWatcher(globPattern: theia.GlobPattern, ignoreCreateEvents?: boolean, ignoreChangeEvents?: boolean, ignoreDeleteEvents?: boolean): theia.FileSystemWatcher {
return this.fileSystemWatcherManager.createFileSystemWatcher(globPattern, ignoreCreateEvents, ignoreChangeEvents, ignoreDeleteEvents);
}
Expand Down
Loading

0 comments on commit 7a88d28

Please sign in to comment.