diff --git a/packages/core/src/browser/icons/CollapseAll.svg b/packages/core/src/browser/icons/CollapseAll.svg
new file mode 100644
index 0000000000000..f44a3b03142ad
--- /dev/null
+++ b/packages/core/src/browser/icons/CollapseAll.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/CollapseAll_inverse.svg b/packages/core/src/browser/icons/CollapseAll_inverse.svg
new file mode 100644
index 0000000000000..0d65cd8ba6095
--- /dev/null
+++ b/packages/core/src/browser/icons/CollapseAll_inverse.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/Refresh.svg b/packages/core/src/browser/icons/Refresh.svg
new file mode 100644
index 0000000000000..82ac7408b2a74
--- /dev/null
+++ b/packages/core/src/browser/icons/Refresh.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/Refresh_inverse.svg b/packages/core/src/browser/icons/Refresh_inverse.svg
new file mode 100644
index 0000000000000..b0f180ce1563b
--- /dev/null
+++ b/packages/core/src/browser/icons/Refresh_inverse.svg
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/case-sensitive-dark.svg b/packages/core/src/browser/icons/case-sensitive-dark.svg
new file mode 100644
index 0000000000000..f4b6fcd6d81d1
--- /dev/null
+++ b/packages/core/src/browser/icons/case-sensitive-dark.svg
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/case-sensitive.svg b/packages/core/src/browser/icons/case-sensitive.svg
new file mode 100644
index 0000000000000..94f9b32503fa5
--- /dev/null
+++ b/packages/core/src/browser/icons/case-sensitive.svg
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/clear-search-results-dark.svg b/packages/core/src/browser/icons/clear-search-results-dark.svg
new file mode 100644
index 0000000000000..ae0e6809a8c85
--- /dev/null
+++ b/packages/core/src/browser/icons/clear-search-results-dark.svg
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/clear-search-results.svg b/packages/core/src/browser/icons/clear-search-results.svg
new file mode 100644
index 0000000000000..24a8a8b1cc6dc
--- /dev/null
+++ b/packages/core/src/browser/icons/clear-search-results.svg
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/regex-dark.svg b/packages/core/src/browser/icons/regex-dark.svg
new file mode 100644
index 0000000000000..5b0a7c7a8908c
--- /dev/null
+++ b/packages/core/src/browser/icons/regex-dark.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/regex.svg b/packages/core/src/browser/icons/regex.svg
new file mode 100644
index 0000000000000..b83e6965ec6e2
--- /dev/null
+++ b/packages/core/src/browser/icons/regex.svg
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/replace-all-inverse.svg b/packages/core/src/browser/icons/replace-all-inverse.svg
new file mode 100644
index 0000000000000..cb1b7fca836fd
--- /dev/null
+++ b/packages/core/src/browser/icons/replace-all-inverse.svg
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/replace-all.svg b/packages/core/src/browser/icons/replace-all.svg
new file mode 100644
index 0000000000000..edd6cc8ef7049
--- /dev/null
+++ b/packages/core/src/browser/icons/replace-all.svg
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/replace-inverse.svg b/packages/core/src/browser/icons/replace-inverse.svg
new file mode 100644
index 0000000000000..ec99722f487f3
--- /dev/null
+++ b/packages/core/src/browser/icons/replace-inverse.svg
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/replace.svg b/packages/core/src/browser/icons/replace.svg
new file mode 100644
index 0000000000000..82b26f264bfa4
--- /dev/null
+++ b/packages/core/src/browser/icons/replace.svg
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/whole-word-dark.svg b/packages/core/src/browser/icons/whole-word-dark.svg
new file mode 100644
index 0000000000000..ae27a3eb68b61
--- /dev/null
+++ b/packages/core/src/browser/icons/whole-word-dark.svg
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/packages/core/src/browser/icons/whole-word.svg b/packages/core/src/browser/icons/whole-word.svg
new file mode 100644
index 0000000000000..10878ec28a6bb
--- /dev/null
+++ b/packages/core/src/browser/icons/whole-word.svg
@@ -0,0 +1,19 @@
+
+
+
+
diff --git a/packages/core/src/browser/shell/application-shell.ts b/packages/core/src/browser/shell/application-shell.ts
index 3d55e5e124c00..b7d7b742634dd 100644
--- a/packages/core/src/browser/shell/application-shell.ts
+++ b/packages/core/src/browser/shell/application-shell.ts
@@ -1114,6 +1114,13 @@ export class ApplicationShell extends Widget {
return toArray(this.bottomPanel.tabBars());
}
+ /**
+ * The tab bars contained in all shell areas.
+ */
+ get allTabBars(): TabBar[] {
+ return [...this.mainAreaTabBars, ...this.bottomAreaTabBars, this.leftPanelHandler.tabBar, this.rightPanelHandler.tabBar];
+ }
+
/*
* Activate the next tab in the current tab bar.
*/
diff --git a/packages/core/src/browser/style/index.css b/packages/core/src/browser/style/index.css
index 414cadfb1fccf..4e33fb9da100d 100644
--- a/packages/core/src/browser/style/index.css
+++ b/packages/core/src/browser/style/index.css
@@ -87,6 +87,13 @@ body {
:focus {
outline: none;
+ border-style: solid;
+ border-width: 1px;
+ border-color: var(--theia-accent-color3);
+}
+
+.p-Widget:focus {
+ border: none;
}
button, .theia-button {
diff --git a/packages/core/src/browser/style/tree.css b/packages/core/src/browser/style/tree.css
index b26f4450bb758..549d95e2c1383 100644
--- a/packages/core/src/browser/style/tree.css
+++ b/packages/core/src/browser/style/tree.css
@@ -23,6 +23,7 @@
line-height: var(--theia-private-horizontal-tab-height);
display: flex;
align-items: baseline;
+ margin-right: 10px;
}
.theia-TreeNode:hover {
diff --git a/packages/core/src/browser/style/variables-bright.useable.css b/packages/core/src/browser/style/variables-bright.useable.css
index b019e179c5c5d..a1519e7cabd5a 100644
--- a/packages/core/src/browser/style/variables-bright.useable.css
+++ b/packages/core/src/browser/style/variables-bright.useable.css
@@ -151,11 +151,24 @@ is not optimized for dense, information rich UIs.
--theia-highlight-background-color: var(--md-purple-A100);
--theia-highlight-color: var(--theia-content-font-color0);
+ /* Colors to highlight words in widgets like tree or editors */
+
+ --theia-word-highlight-color0: rgba(168, 172, 148, 0.7);
+ --theia-word-highlight-color1: rgba(253, 255, 0, 0.2);
+
/* Icons */
--theia-icon-close: url(../icons/close-bright.svg);
--theia-sprite-y-offset: 0px;
--theia-icon-circle: url(../icons/circle-bright.svg);
--theia-preloader: url(../icons/spinner.gif);
+ --theia-icon-case-sensitive: url(../icons/case-sensitive.svg);
+ --theia-icon-regex: url(../icons/regex.svg);
+ --theia-icon-whole-word: url(../icons/whole-word.svg);
+ --theia-icon-refresh: url(../icons/Refresh.svg);
+ --theia-icon-collapse-all: url(../icons/CollapseAll.svg);
+ --theia-icon-clear: url(../icons/clear-search-results.svg);
+ --theia-icon-replace: url(../icons/replace.svg);
+ --theia-icon-replace-all: url(../icons/replace-all.svg);
/* Scrollbars */
--theia-scrollbar-width: 6px;
diff --git a/packages/core/src/browser/style/variables-dark.useable.css b/packages/core/src/browser/style/variables-dark.useable.css
index 065a115c38dbe..7311b946b987a 100644
--- a/packages/core/src/browser/style/variables-dark.useable.css
+++ b/packages/core/src/browser/style/variables-dark.useable.css
@@ -151,11 +151,24 @@ is not optimized for dense, information rich UIs.
--theia-highlight-background-color: var(--md-purple-A400);
--theia-highlight-color: var(--theia-content-font-color0);
+ /* Colors to highlight words in widgets like tree or editors */
+
+ --theia-word-highlight-color0: rgba(81, 92, 106, 0.7);
+ --theia-word-highlight-color1: rgba(255, 255, 255, 0.04);
+
/* Icons */
--theia-icon-close: url(../icons/close-dark.svg);
--theia-sprite-y-offset: -20px;
--theia-icon-circle: url(../icons/circle-dark.svg);
--theia-preloader: url(../icons/spinner.gif);
+ --theia-icon-case-sensitive: url(../icons/case-sensitive-dark.svg);
+ --theia-icon-regex: url(../icons/regex-dark.svg);
+ --theia-icon-whole-word: url(../icons/whole-word-dark.svg);
+ --theia-icon-refresh: url(../icons/Refresh_inverse.svg);
+ --theia-icon-collapse-all: url(../icons/CollapseAll_inverse.svg);
+ --theia-icon-clear: url(../icons/clear-search-results-dark.svg);
+ --theia-icon-replace: url(../icons/replace-inverse.svg);
+ --theia-icon-replace-all: url(../icons/replace-all-inverse.svg);
/* Scrollbars */
--theia-scrollbar-width: 6px;
diff --git a/packages/editor/src/browser/editor.ts b/packages/editor/src/browser/editor.ts
index d991551d5abbe..b8073c94c2bbf 100644
--- a/packages/editor/src/browser/editor.ts
+++ b/packages/editor/src/browser/editor.ts
@@ -81,6 +81,12 @@ export interface TextEditor extends Disposable, TextEditorSelection, Navigatable
deltaDecorations(params: DeltaDecorationParams): string[];
getVisibleColumn(position: Position): number;
+
+ /**
+ * Replaces the text of source given in ReplacetextParams.
+ * @param params: ReplaceTextParams
+ */
+ replaceText(params: ReplaceTextParams): Promise;
}
export interface Dimension {
@@ -115,6 +121,28 @@ export interface DeltaDecorationParams {
newDecorations: EditorDecoration[];
}
+export interface ReplaceTextParams {
+ /**
+ * the source to edit
+ */
+ source: string;
+ /**
+ * the replace operations
+ */
+ replaceOperations: ReplaceOperation[];
+}
+
+export interface ReplaceOperation {
+ /**
+ * the position that shall be replaced
+ */
+ range: Range;
+ /**
+ * the text to replace with
+ */
+ text: string;
+}
+
export namespace TextEditorSelection {
// tslint:disable-next-line:no-any
export function is(e: any): e is TextEditorSelection {
diff --git a/packages/monaco/src/browser/monaco-editor.ts b/packages/monaco/src/browser/monaco-editor.ts
index 5558717b94808..102092f6a0736 100644
--- a/packages/monaco/src/browser/monaco-editor.ts
+++ b/packages/monaco/src/browser/monaco-editor.ts
@@ -22,6 +22,7 @@ import {
RevealPositionOptions,
EditorDecorationsService,
DeltaDecorationParams,
+ ReplaceTextParams,
} from '@theia/editor/lib/browser';
import { MonacoEditorModel } from "./monaco-editor-model";
@@ -29,6 +30,7 @@ import IEditorConstructionOptions = monaco.editor.IEditorConstructionOptions;
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import IEditorOverrideServices = monaco.editor.IEditorOverrideServices;
import IStandaloneCodeEditor = monaco.editor.IStandaloneCodeEditor;
+import IIdentifiedSingleEditOperation = monaco.editor.IIdentifiedSingleEditOperation;
import IBoxSizing = ElementExt.IBoxSizing;
import IEditorReference = monaco.editor.IEditorReference;
import SuggestController = monaco.suggestController.SuggestController;
@@ -338,6 +340,34 @@ export class MonacoEditor implements TextEditor, IEditorReference {
lineNumber: position.line + 1
});
}
+
+ async replaceText(params: ReplaceTextParams): Promise {
+ const edits: IIdentifiedSingleEditOperation[] = params.replaceOperations.map(param => {
+ const startPos = param.range.start;
+ const endPos = param.range.end;
+ const range = this.p2m.asRange({
+ start: {
+ line: startPos.line - 1,
+ character: startPos.character - 1
+ },
+ end: {
+ line: endPos.line - 1,
+ character: endPos.character - 1
+ }
+ });
+ return {
+ forceMoveMarkers: true,
+ identifier: {
+ major: range.startLineNumber,
+ minor: range.startColumn
+ },
+ range,
+ text: param.text
+ };
+ });
+ return this.editor.executeEdits(params.source, edits);
+ }
+
}
export namespace MonacoEditor {
diff --git a/packages/search-in-workspace/src/browser/in-memory-text-resource.ts b/packages/search-in-workspace/src/browser/in-memory-text-resource.ts
new file mode 100644
index 0000000000000..7f519979cbbe2
--- /dev/null
+++ b/packages/search-in-workspace/src/browser/in-memory-text-resource.ts
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 TypeFox and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import { injectable } from "inversify";
+import { ResourceResolver, Resource } from "@theia/core";
+import URI from "@theia/core/lib/common/uri";
+
+export const MEMORY_TEXT = "mem-txt";
+
+export class InMemoryTextResource implements Resource {
+
+ constructor(readonly uri: URI) { }
+
+ async readContents(options?: { encoding?: string | undefined; } | undefined): Promise {
+ return this.uri.query;
+ }
+
+ dispose(): void { }
+}
+
+@injectable()
+export class InMemoryTextResourceResolver implements ResourceResolver {
+ resolve(uri: URI): Resource | Promise {
+ if (uri.scheme !== MEMORY_TEXT) {
+ throw new Error(`Expected a URI with ${MEMORY_TEXT} scheme. Was: ${uri}.`);
+ }
+ return new InMemoryTextResource(uri);
+ }
+}
diff --git a/packages/search-in-workspace/src/browser/quick-search-in-workspace.ts b/packages/search-in-workspace/src/browser/quick-search-in-workspace.ts
deleted file mode 100644
index 57407d6101963..0000000000000
--- a/packages/search-in-workspace/src/browser/quick-search-in-workspace.ts
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2017-2018 Ericsson and others.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- */
-
-import URI from '@theia/core/lib/common/uri';
-import { QuickOpenService, QuickOpenModel, QuickOpenItem, QuickOpenItemOptions } from '@theia/core/lib/browser/quick-open/';
-import { injectable, inject } from 'inversify';
-import { MenuModelRegistry, MenuContribution, CommandContribution, CommandRegistry, ILogger } from '@theia/core';
-import {
- CommonMenus, QuickOpenMode, OpenerService, open, Highlight, QuickOpenOptions,
- KeybindingContribution, KeybindingRegistry
-} from '@theia/core/lib/browser';
-import { SearchInWorkspaceService } from './search-in-workspace-service';
-import { SearchInWorkspaceResult, SearchInWorkspaceOptions } from '../common/search-in-workspace-interface';
-import { Range } from '@theia/editor/lib/browser';
-import { LabelProvider } from '@theia/core/lib/browser/label-provider';
-
-@injectable()
-export class QuickSearchInWorkspace implements QuickOpenModel {
- private currentSearchId: number = -1;
- protected MAX_RESULTS = 100;
-
- constructor(
- @inject(QuickOpenService) protected readonly quickOpenService: QuickOpenService,
- @inject(SearchInWorkspaceService) protected readonly searchInWorkspaceService: SearchInWorkspaceService,
- @inject(OpenerService) protected readonly openerService: OpenerService,
- @inject(LabelProvider) protected readonly labelProvider: LabelProvider,
- @inject(ILogger) protected readonly logger: ILogger,
- ) { }
-
- isEnabled(): boolean {
- return this.searchInWorkspaceService.isEnabled();
- }
-
- onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): void {
- // If we have a search pending, it's not relevant anymore, cancel it.
- this.cancelCurrentSeach();
-
- if (lookFor.length === 0) {
- // The user has emptied the search box, call acceptor to
- // remove any previously shown results.
- acceptor([]);
- return;
- }
-
- // Options passed to the search service.
- const opts: SearchInWorkspaceOptions = {
- maxResults: this.MAX_RESULTS,
- };
-
- // The array in which we'll keep accumulating search results.
- const items: QuickSearchInWorkspaceResultItem[] = [];
-
- this.searchInWorkspaceService.search(lookFor, {
-
- onResult: (searchId: number, result: SearchInWorkspaceResult) => {
- // Is this result from a previous search?
- if (searchId !== this.currentSearchId) {
- return;
- }
-
- items.push(new QuickSearchInWorkspaceResultItem(result, this.openerService, this.labelProvider));
- },
-
- onDone: (searchId: number, error?: string) => {
- if (searchId !== this.currentSearchId) {
- this.logger.debug('Search ' + this.currentSearchId + ' has completed, but it\'s not the current search.');
- return;
- }
- this.logger.debug('Search ' + this.currentSearchId + ' has completed and is the current search.');
- this.currentSearchId = -1;
-
- if (error) {
- this.showFakeResult(error, acceptor);
- } else if (items.length !== 0) {
- items.sort((a, b) => SearchInWorkspaceResult.compare(a.getResult(), b.getResult()));
- acceptor(items);
- } else {
- this.showFakeResult('No matches :(', acceptor);
- }
-
- },
- }, opts).then(searchId => {
- this.currentSearchId = searchId;
- });
- }
-
- showFakeResult(label: string, acceptor: (items: QuickOpenItem[]) => void) {
- acceptor([
- new QuickOpenItem({
- label: label,
- }),
- ]);
- }
-
- // If we have an ongoing search, cancel it.
- cancelCurrentSeach() {
- if (this.currentSearchId >= 0) {
- this.logger.debug('Cancelling search ' + this.currentSearchId);
- this.searchInWorkspaceService.cancel(this.currentSearchId);
- this.currentSearchId = -1;
- }
- }
-
- // Open the quick search in workspace popup.
- open() {
- const opts: QuickOpenOptions = {
- onClose: cancelled => this.cancelCurrentSeach(),
- placeholder: 'Search in workspace by regular expression...',
- };
- this.quickOpenService.open(this, opts);
- }
-}
-
-class QuickSearchInWorkspaceResultItem extends QuickOpenItem {
-
- private result: SearchInWorkspaceResult;
- private openerService: OpenerService;
-
- constructor(result: SearchInWorkspaceResult, openerService: OpenerService, labelProvider: LabelProvider) {
- const resultHl: Highlight = {
- start: result.character - 1,
- end: result.character + result.length - 1,
- };
-
- // Show the path relative to the workspace.
- const uri = new URI('file://' + result.file);
- const file = labelProvider.getName(uri);
- const dir = labelProvider.getLongName(uri.parent) + '/';
-
- const filenameHl: Highlight = {
- start: 0,
- end: file.length,
- };
-
- const opts: QuickOpenItemOptions = {
- detail: result.lineText,
- detailHighlights: [resultHl],
- label: `${file}:${result.line} - ${dir}`,
- labelHighlights: [filenameHl],
- };
- super(opts);
-
- this.result = result;
- this.openerService = openerService;
- }
-
- run(mode: QuickOpenMode): boolean {
- if (mode !== QuickOpenMode.OPEN) {
- return false;
- }
-
- // Search results are 1-based, positions in editors are 0-based.
- const line = this.result.line - 1;
- const character = this.result.character - 1;
- const uri = new URI('file://' + this.result.file);
- const r = Range.create(line, character, line, character + this.result.length);
- open(this.openerService, uri, { selection: r });
-
- return true;
- }
-
- getResult(): SearchInWorkspaceResult {
- return this.result;
- }
-}
-
-const OpenQuickSearchInWorkspaceCommand = {
- id: 'QuickSearchInWorkspace.open',
- label: "Search in Workspace..."
-};
-
-@injectable()
-export class SearchInWorkspaceContributions implements CommandContribution, MenuContribution, KeybindingContribution {
- constructor(
- @inject(QuickSearchInWorkspace) protected readonly quickSeachInWorkspace: QuickSearchInWorkspace,
- ) { }
-
- registerCommands(registry: CommandRegistry): void {
- registry.registerCommand(OpenQuickSearchInWorkspaceCommand, {
- execute: what => this.quickSeachInWorkspace.open(),
- isEnabled: () => this.quickSeachInWorkspace.isEnabled(),
- });
- }
-
- registerMenus(menus: MenuModelRegistry): void {
- menus.registerMenuAction(CommonMenus.EDIT_FIND, {
- commandId: OpenQuickSearchInWorkspaceCommand.id,
- label: OpenQuickSearchInWorkspaceCommand.label,
- });
- }
-
- registerKeybindings(keybindings: KeybindingRegistry): void {
- keybindings.registerKeybinding({
- command: OpenQuickSearchInWorkspaceCommand.id,
- keybinding: 'ctrlcmd+shift+f',
- });
- }
-}
diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts
new file mode 100644
index 0000000000000..085a958ded68f
--- /dev/null
+++ b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-contribution.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 TypeFox and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import { AbstractViewContribution } from "@theia/core/lib/browser";
+import { SearchInWorkspaceWidget } from "./search-in-workspace-widget";
+import { injectable } from "inversify";
+
+export namespace SearchInWorkspaceCommands {
+ export const OPEN_SIW_WIDGET = {
+ id: "search-in-workspace.open"
+ };
+}
+
+@injectable()
+export class SearchInWorkspaceFrontendContribution extends AbstractViewContribution {
+
+ constructor() {
+ super({
+ widgetId: SearchInWorkspaceWidget.ID,
+ widgetName: SearchInWorkspaceWidget.LABEL,
+ defaultWidgetOptions: {
+ area: "left"
+ },
+ toggleCommandId: SearchInWorkspaceCommands.OPEN_SIW_WIDGET.id,
+ toggleKeybinding: "ctrlcmd+shift+f"
+ });
+ }
+}
diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts
index c770202854c3b..0150393e631d0 100644
--- a/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts
+++ b/packages/search-in-workspace/src/browser/search-in-workspace-frontend-module.ts
@@ -5,19 +5,30 @@
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
-import { ContainerModule } from "inversify";
+import { ContainerModule, interfaces } from "inversify";
import { SearchInWorkspaceService, SearchInWorkspaceClientImpl } from './search-in-workspace-service';
import { SearchInWorkspaceServer } from '../common/search-in-workspace-interface';
-import { WebSocketConnectionProvider, KeybindingContribution } from '@theia/core/lib/browser';
-import { QuickSearchInWorkspace, SearchInWorkspaceContributions } from './quick-search-in-workspace';
-import { CommandContribution, MenuContribution } from "@theia/core";
+import { WebSocketConnectionProvider, KeybindingContribution, WidgetFactory, createTreeContainer, TreeWidget } from '@theia/core/lib/browser';
+import { CommandContribution, MenuContribution, ResourceResolver } from "@theia/core";
+import { SearchInWorkspaceWidget } from "./search-in-workspace-widget";
+import { SearchInWorkspaceResultTreeWidget } from "./search-in-workspace-result-tree-widget";
+import { SearchInWorkspaceFrontendContribution } from "./search-in-workspace-frontend-contribution";
+import { InMemoryTextResourceResolver } from "./in-memory-text-resource";
+
+import "../../src/browser/styles/index.css";
export default new ContainerModule(bind => {
- bind(QuickSearchInWorkspace).toSelf().inSingletonScope();
+ bind(SearchInWorkspaceWidget).toSelf();
+ bind(WidgetFactory).toDynamicValue(ctx => ({
+ id: SearchInWorkspaceWidget.ID,
+ createWidget: () => ctx.container.get(SearchInWorkspaceWidget)
+ }));
+ bind(SearchInWorkspaceResultTreeWidget).toDynamicValue(ctx => createSearchTreeWidget(ctx.container));
- bind(CommandContribution).to(SearchInWorkspaceContributions).inSingletonScope();
- bind(MenuContribution).to(SearchInWorkspaceContributions).inSingletonScope();
- bind(KeybindingContribution).to(SearchInWorkspaceContributions).inSingletonScope();
+ bind(SearchInWorkspaceFrontendContribution).toSelf().inSingletonScope();
+ for (const identifier of [CommandContribution, MenuContribution, KeybindingContribution]) {
+ bind(identifier).toService(SearchInWorkspaceFrontendContribution);
+ }
// The object that gets notified of search results.
bind(SearchInWorkspaceClientImpl).toSelf().inSingletonScope();
@@ -29,4 +40,16 @@ export default new ContainerModule(bind => {
const client = ctx.container.get(SearchInWorkspaceClientImpl);
return WebSocketConnectionProvider.createProxy(ctx.container, '/search-in-workspace', client);
}).inSingletonScope();
+
+ bind(InMemoryTextResourceResolver).toSelf().inSingletonScope();
+ bind(ResourceResolver).toService(InMemoryTextResourceResolver);
});
+
+export function createSearchTreeWidget(parent: interfaces.Container): SearchInWorkspaceResultTreeWidget {
+ const child = createTreeContainer(parent);
+
+ child.unbind(TreeWidget);
+ child.bind(SearchInWorkspaceResultTreeWidget).toSelf();
+
+ return child.get(SearchInWorkspaceResultTreeWidget);
+}
diff --git a/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.ts b/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.ts
new file mode 100644
index 0000000000000..ec16b958385b6
--- /dev/null
+++ b/packages/search-in-workspace/src/browser/search-in-workspace-result-tree-widget.ts
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2018 TypeFox and others.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import {
+ TreeWidget,
+ ContextMenuRenderer,
+ CompositeTreeNode,
+ ExpandableTreeNode,
+ SelectableTreeNode,
+ TreeModel,
+ TreeNode,
+ NodeProps,
+ LabelProvider,
+ TreeExpansionService,
+ ApplicationShell,
+ DiffUris
+} from "@theia/core/lib/browser";
+import { SearchInWorkspaceResult, SearchInWorkspaceOptions } from "../common/search-in-workspace-interface";
+import { SearchInWorkspaceService } from "./search-in-workspace-service";
+import { TreeProps } from "@theia/core/lib/browser";
+import { EditorManager, EditorDecoration, TrackedRangeStickiness, OverviewRulerLane, EditorWidget, ReplaceOperation } from "@theia/editor/lib/browser";
+import { inject, injectable, postConstruct } from "inversify";
+import URI from "@theia/core/lib/common/uri";
+import { Path, CancellationTokenSource, Emitter, Event } from "@theia/core";
+import { WorkspaceService } from "@theia/workspace/lib/browser";
+import { h } from "@phosphor/virtualdom";
+import { MEMORY_TEXT } from "./in-memory-text-resource";
+import { FileResourceResolver } from "@theia/filesystem/lib/browser";
+
+export interface SearchInWorkspaceResultNode extends ExpandableTreeNode, SelectableTreeNode {
+ children: SearchInWorkspaceResultLineNode[];
+ path: string;
+ file: string;
+}
+export namespace SearchInWorkspaceResultNode {
+ export function is(node: any): node is SearchInWorkspaceResultNode {
+ return ExpandableTreeNode.is(node) && SelectableTreeNode.is(node) && "path" in node;
+ }
+}
+
+export type SearchInWorkspaceResultLineNode = SelectableTreeNode & SearchInWorkspaceResult;
+export namespace SearchInWorkspaceResultLineNode {
+ export function is(node: any): node is SearchInWorkspaceResultLineNode {
+ return SelectableTreeNode.is(node) && "line" in node && "character" in node && "lineText" in node;
+ }
+}
+
+@injectable()
+export class SearchInWorkspaceResultTreeWidget extends TreeWidget {
+
+ protected resultTree: Map;
+ protected workspaceRoot: string = "";
+
+ protected _showReplaceButtons = false;
+ protected _replaceTerm = "";
+ protected searchTerm = "";
+
+ protected appliedDecorations = new Map();
+
+ private cancelIndicator = new CancellationTokenSource();
+
+ protected changeEmitter: Emitter