Skip to content

Commit

Permalink
siw: Add support for search in outside workspace content
Browse files Browse the repository at this point in the history
+ Search results from outside workspace editor are displayed and grouped under `Other
files ` node in search view.

Signed-off-by: DukeNgn <[email protected]>
  • Loading branch information
DucNgn committed Nov 2, 2020
1 parent bdc9aa2 commit 08b478f
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 12 deletions.
16 changes: 16 additions & 0 deletions packages/editor/src/browser/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,22 @@ export interface TextEditor extends Disposable, TextEditorSelection, Navigatable
setEncoding(encoding: string, mode: EncodingMode): void;

readonly onEncodingChanged: Event<string>;

/**
* Find all matches in an editor for the given options.
* @param options the options for finding matches.
*
* @returns the list of matches.
*/
findMatches?(options: FindMatchesOptions): FindMatch[];

/**
* Get the text for a certain line.
* @param lineNumber the line number.
*
* @returns the text at the given line.
*/
getLineContent?(lineNumber: number): string;
}

export interface Dimension {
Expand Down
34 changes: 33 additions & 1 deletion packages/monaco/src/browser/monaco-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
ReplaceTextParams,
EditorDecoration,
EditorMouseEvent,
EncodingMode
EncodingMode, FindMatchesOptions, FindMatch
} from '@theia/editor/lib/browser';
import { MonacoEditorModel } from './monaco-editor-model';
import { MonacoToProtocolConverter } from './monaco-to-protocol-converter';
Expand Down Expand Up @@ -121,6 +121,38 @@ export class MonacoEditor extends MonacoEditorServices implements TextEditor {
return this.document.setEncoding(encoding, mode);
}

findMatches(options: FindMatchesOptions): FindMatch[] {
const model = this.editor.getModel();
if (!model) {
return [];
}
const results: monaco.editor.FindMatch[] = model.findMatches(
options.searchString,
false,
options.isRegex,
options.matchCase,
// eslint-disable-next-line no-null/no-null
options.matchWholeWord ? options.searchString : null,
true,
options.limitResultCount
);
const extractedMatches: FindMatch[] = [];
results.forEach(r => {
if (r.matches) {
extractedMatches.push({
matches: r.matches,
range: Range.create(r.range.startLineNumber, r.range.startColumn, r.range.endLineNumber, r.range.endColumn)
});
}
});
return extractedMatches;
}

getLineContent(lineNumber: number): string {
const model = this.editor.getModel();
return model ? model.getLineContent(lineNumber) : '';
}

protected create(options?: IStandaloneEditorConstructionOptions, override?: monaco.editor.IEditorOverrideServices): Disposable {
return this.editor = monaco.editor.create(this.node, {
...options,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
protected _showReplaceButtons = false;
protected _replaceTerm = '';
protected searchTerm = '';
protected outsideWorkspaceRootUri = 'Other files';
protected hasOutsideWorkspaceResult = false;

protected appliedDecorations = new Map<string, string[]>();

Expand All @@ -121,10 +123,10 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
@inject(ApplicationShell) protected readonly shell: ApplicationShell;
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService;
@inject(TreeExpansionService) protected readonly expansionService: TreeExpansionService;
@inject(FileSystemPreferences) protected readonly fileSystemPreferences: FileSystemPreferences;
@inject(SearchInWorkspacePreferences) protected readonly searchInWorkspacePreferences: SearchInWorkspacePreferences;
@inject(ProgressService) protected readonly progressService: ProgressService;
@inject(ColorRegistry) protected readonly colorRegistry: ColorRegistry;
@inject(FileSystemPreferences) protected readonly filesystemPreferences: FileSystemPreferences;

constructor(
@inject(TreeProps) readonly props: TreeProps,
Expand Down Expand Up @@ -285,6 +287,13 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
return editors;
}

/**
* @returns whether there are open editors.
*/
hasOpenEditors(): boolean {
return (this.editorManager.all.length > 0);
}

/**
* Perform a search in all open editors.
* @param searchTerm the search term.
Expand All @@ -305,8 +314,11 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
const matches = this.findMatches(searchTerm, widget, searchOptions);
numberOfResults += matches.length;
const fileUri: string = widget.editor.uri.toString();
const root: string = this.workspaceService.getWorkspaceRootUri(widget.editor.uri)!.toString();
searchResults.push({ root, fileUri, matches });
const root: string | undefined = this.workspaceService.getWorkspaceRootUri(widget.editor.uri)?.toString();
searchResults.push({
root: root ? root : this.outsideWorkspaceRootUri,
fileUri,
matches });
});

return {
Expand All @@ -324,7 +336,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
return;
}
const collapseValue: string = this.searchInWorkspacePreferences['search.collapseResults'];
const { path } = this.filenameAndPath(result.root, result.fileUri);
const { path } = this.filenameAndPath(result.root, result.fileUri, result.root === this.outsideWorkspaceRootUri);
const tree = this.resultTree;
let rootFolderNode = tree.get(result.root);
if (!rootFolderNode) {
Expand Down Expand Up @@ -352,6 +364,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
exclude: this.getExcludeGlobs(searchOptions.exclude)
};
this.resultTree.clear();
this.hasOutsideWorkspaceResult = false;
if (this.cancelIndicator) {
this.cancelIndicator.cancel();
}
Expand All @@ -374,6 +387,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {

// Collect search results for opened editors which otherwise may not be found by ripgrep (ex: dirty editors).
const { numberOfResults: monacoNumberOfResults, matches: monacoMatches } = this.searchInOpenEditors(searchTerm, searchOptions);
this.hasOutsideWorkspaceResult = monacoMatches.some(m => m.root === this.outsideWorkspaceRootUri);
monacoMatches.forEach(m => {
this.appendToResultTree(m);
// Exclude pattern beginning with './' works after the fix of #8469.
Expand Down Expand Up @@ -491,7 +505,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
expanded: true,
id: rootUri,
parent: this.model.root as SearchInWorkspaceRoot,
visible: this.workspaceService.isMultiRootWorkspaceOpened
visible: this.hasOutsideWorkspaceResult || this.workspaceService.isMultiRootWorkspaceOpened
};
}

Expand Down Expand Up @@ -534,11 +548,27 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
return nodes;
}

protected filenameAndPath(rootUriStr: string, uriStr: string): { name: string, path: string } {
/**
* Get file name and construct a path of a file relatively to its root.
* @param rootUriStr the string of root uri.
* @param uriStr the string of the file uri.
* @param getAbsolutePath option to get absolute path instead of relative path.
*/
protected filenameAndPath(rootUriStr: string, uriStr: string, getAbsolutePath?: boolean): {
name: string,
path: string
} {
const uri: URI = new URI(uriStr);
const name = uri.displayName;
if (getAbsolutePath) {
return {
name,
path: this.labelProvider.getLongName(uri)
};
}
const relativePath = new URI(rootUriStr).relative(uri.parent);
return {
name: uri.displayName,
name,
path: relativePath ? relativePath.toString() : ''
};
}
Expand Down Expand Up @@ -740,9 +770,11 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
<span className={'file-name'}>
{this.toNodeName(node)}
</span>
<span className={'file-path'}>
{node.path}
</span>
{node.path !== '/' + this.outsideWorkspaceRootUri &&
<span className={'file-path'}>
{node.path}
</span>
}
</div>
</div>
<span className='notification-count-container highlighted-count-container'>
Expand Down Expand Up @@ -929,7 +961,7 @@ export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
* @returns the list of exclude globs.
*/
protected getExcludeGlobs(excludeOptions?: string[]): string[] {
const excludePreferences = this.filesystemPreferences['files.exclude'];
const excludePreferences = this.fileSystemPreferences['files.exclude'];
const excludePreferencesGlobs = Object.keys(excludePreferences).filter(key => !!excludePreferences[key]);
return [...new Set([...excludePreferencesGlobs, ...excludeOptions])];
}
Expand Down

0 comments on commit 08b478f

Please sign in to comment.