提交 2cc6cf6f 编写于 作者: S Sandeep Somavarapu

Search and Replace implementation

上级 c73e2d9c
......@@ -106,10 +106,20 @@
/* Action bar support */
.monaco-inputbox .monaco-action-bar {
position: absolute;
right: 0px;
right: 2px;
top: 4px;
}
.monaco-inputbox .monaco-action-bar .action-item {
margin-left: 2px;
}
.monaco-inputbox .monaco-action-bar .action-item .icon {
background-repeat: no-repeat;
width: 16px;
height: 16px;
}
/* Theming */
.monaco-inputbox.idle {
......
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
\ No newline at end of file
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<path fill="#C5C5C5" d="M11,15V9H1v6H11z M2,14v-2h1v-1H2v-1h3v4H2z M10,11H8v2h2v1H7v-4h3V11z M3,13v-1h1v1H3z M13,7v6h-1V8H5V7
H13z M13,2V1h-1v5h3V2H13z M14,5h-1V3h1V5z M11,2v4H8V4h1v1h1V4H9V3H8V2H11z"/>
</g>
<g id="color_x5F_action">
<path fill="#75BEFF" d="M1.979,3.5L2,6L1,5v1.5L2.5,8L4,6.5V5L3,6L2.979,3.5c0-0.275,0.225-0.5,0.5-0.5H7V2H3.479
C2.651,2,1.979,2.673,1.979,3.5z"/>
</g>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<path fill="#424242" d="M11,15V9H1v6H11z M2,14v-2h1v-1H2v-1h3v4H2z M10,11H8v2h2v1H7v-4h3V11z M3,13v-1h1v1H3z M13,7v6h-1V8H5V7
H13z M13,2V1h-1v5h3V2H13z M14,5h-1V3h1V5z M11,2v4H8V4h1v1h1V4H9V3H8V2H11z"/>
</g>
<g id="color_x5F_action">
<path fill="#00539C" d="M1.979,3.5L2,6L1,5v1.5L2.5,8L4,6.5V5L3,6L2.979,3.5c0-0.275,0.225-0.5,0.5-0.5H7V2H3.479
C2.651,2,1.979,2.673,1.979,3.5z"/>
</g>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<g>
<path fill="#C5C5C5" d="M11,3V1h-1v5v1h1h2h1V4V3H11z M13,6h-2V4h2V6z"/>
<path fill="#C5C5C5" d="M2,15h7V9H2V15z M4,10h3v1H5v2h2v1H4V10z"/>
</g>
</g>
<g id="color_x5F_importance">
<path fill="#75BEFF" d="M3.979,3.5L4,6L3,5v1.5L4.5,8L6,6.5V5L5,6L4.979,3.5c0-0.275,0.225-0.5,0.5-0.5H9V2H5.479
C4.651,2,3.979,2.673,3.979,3.5z"/>
</g>
</svg>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px"
height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="icon_x5F_bg">
<g>
<path fill="#424242" d="M11,3V1h-1v5v1h1h2h1V4V3H11z M13,6h-2V4h2V6z"/>
<path fill="#424242" d="M2,15h7V9H2V15z M4,10h3v1H5v2h2v1H4V10z"/>
</g>
</g>
<g id="color_x5F_importance">
<path fill="#00539C" d="M3.979,3.5L4,6L3,5v1.5L4.5,8L6,6.5V5L5,6L4.979,3.5c0-0.275,0.225-0.5,0.5-0.5H9V2H5.479
C4.651,2,3.979,2.673,3.979,3.5z"/>
</g>
</svg>
......@@ -3,20 +3,45 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.search-viewlet .highlight {
color: black;
background-color: rgba(234, 92, 0, 0.3);
.search-viewlet .search-widgets-container {
margin: 6px 17px 0 2px;
}
.search-viewlet .search-widget .toggle-replace-button {
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 100%;
-webkit-box-sizing: border-box;
-o-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
background-position: center center;
background-repeat: no-repeat;
cursor: pointer;
}
.search-viewlet .query-box {
margin: 6px 17px 0 17px;
.search-viewlet .search-widget .input-box {
margin-left: 17px;
}
.search-viewlet .query-box .monaco-findInput {
.search-viewlet .search-widget .monaco-findInput {
display: inline-block;
vertical-align: middle;
}
.search-viewlet .search-widget .replace-box {
margin-top: 6px;
}
.search-viewlet .search-widget .replace-box.disabled {
display: none;
}
.search-viewlet .query-clear {
width: 20px;
height: 20px;
......@@ -29,14 +54,13 @@
.search-viewlet .query-details {
min-height: 1em;
position: relative;
margin: 0 17px 6px 17px;
padding: 0 4px;
margin: 0 0 6px 17px;
}
.search-viewlet .query-details .more {
position: absolute;
right: 0;
margin-right: 0.5em;
right: 0;
cursor: pointer;
width: 16px;
height: 13px;
......@@ -163,6 +187,24 @@
background: url("action-remove.svg") center center no-repeat;
}
.search-viewlet .action-replace {
background-image: url('replace.svg');
}
.search-viewlet .action-replace-all {
background-image: url('replace-all.svg');
}
.monaco-editor.hc-black .search-viewlet .action-replace,
.monaco-editor.vs-dark .search-viewlet .action-replace {
background-image: url('replace-inverse.svg');
}
.monaco-editor.hc-black .search-viewlet .action-replace-all,
.monaco-editor.vs-dark .search-viewlet .action-replace-all {
background-image: url('replace-all-inverse.svg');
}
.search-viewlet .label {
font-style: italic;
}
......@@ -248,8 +290,28 @@
box-sizing: border-box;
}
.search-viewlet .replaceMatch {
background-color: rgba(234, 92, 0, 0.45);
}
.search-viewlet .removeMatch {
text-decoration: line-through;
}
.hc-black .monaco-workbench .search-viewlet .replaceMatch,
.monaco-editor.hc-black .replaceMatch {
border-color: #F44336;
}
/* Theming */
.vs .search-viewlet .query-box,
.search-viewlet .highlight {
color: black;
background-color: rgba(234, 92, 0, 0.3);
}
.vs .search-viewlet .input-box,
.vs .search-viewlet .file-types .monaco-inputbox {
background-color: white;
}
......@@ -262,7 +324,15 @@
color: #FFF;
}
.vs-dark .search-viewlet .query-box,
.vs .search-viewlet .search-widget .toggle-replace-button.collapse {
background-image: url('expando-collapsed.svg');
}
.vs .search-viewlet .search-widget .toggle-replace-button.expand {
background-image: url('expando-expanded.svg');
}
.vs-dark .search-viewlet .input-box,
.vs-dark .search-viewlet .file-types .monaco-inputbox {
background-color: #3C3C3C;
}
......@@ -287,8 +357,18 @@
padding: 0;
}
.vs-dark .search-viewlet .search-widget .toggle-replace-button.expand,
.hc-black .search-viewlet .search-widget .toggle-replace-button.expand {
background-image: url('expando-expanded-dark.svg');
}
.vs-dark .search-viewlet .search-widget .toggle-replace-button.collapse,
.hc-black .search-viewlet .search-widget .toggle-replace-button.collapse {
background-image: url('expando-collapsed-dark.svg');
}
/* High Contrast Theming */
.hc-black .monaco-workbench .search-viewlet .query-box,
.hc-black .monaco-workbench .search-viewlet .input-box,
.hc-black .monaco-workbench .search-viewlet .file-types .monaco-inputbox {
background-color: #000;
}
......@@ -331,11 +411,11 @@
width: 16px;
}
.hc-black .monaco-workbench .query-box {
.hc-black .monaco-workbench .input-box {
border: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench .query-box .monaco-inputbox.idle {
.hc-black .monaco-workbench .input-box .monaco-inputbox.idle {
border: none;
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { IEditorService } from 'vs/platform/editor/common/editor';
import { IEventService } from 'vs/platform/event/common/event';
import { Match, FileMatch } from 'vs/workbench/parts/search/common/searchModel';
import { BulkEdit, IResourceEdit, createBulkEdit } from 'vs/editor/common/services/bulkEdit';
import { IProgressRunner } from 'vs/platform/progress/common/progress';
export class ReplaceService implements IReplaceService {
public serviceId= IReplaceService;
constructor(@IEventService private eventService: IEventService, @IEditorService private editorService) {
}
public replace(match: Match, text: string): TPromise<any>
public replace(files: FileMatch[], text: string, progress?: IProgressRunner): TPromise<any>
public replace(arg: any, text: string, progress: IProgressRunner= null): TPromise<any> {
let bulkEdit: BulkEdit = createBulkEdit(this.eventService, this.editorService, null);
bulkEdit.progress(progress);
if (arg instanceof Match) {
bulkEdit.add([this.createEdit(arg, text)]);
}
if (arg instanceof Array) {
arg.forEach(element => {
let fileMatch = <FileMatch>element;
fileMatch.matches().forEach(match => {
bulkEdit.add([this.createEdit(match, text)]);
});
});
}
return bulkEdit.finish();
}
private createEdit(match: Match, text: string): IResourceEdit {
let fileMatch: FileMatch= match.parent();
let resourceEdit: IResourceEdit= {
resource: fileMatch.resource(),
range: match.range(),
newText: text
};
return resourceEdit;
}
}
\ No newline at end of file
......@@ -27,6 +27,11 @@ import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletServi
import {KeyMod, KeyCode} from 'vs/base/common/keyCodes';
import {OpenSearchViewletAction} from 'vs/workbench/parts/search/browser/searchActions';
import {VIEWLET_ID} from 'vs/workbench/parts/search/common/constants';
import {registerSingleton} from 'vs/platform/instantiation/common/extensions';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import { ReplaceService } from 'vs/workbench/parts/search/browser/replaceService';
registerSingleton(IReplaceService, ReplaceService);
KeybindingsRegistry.registerCommandDesc({
id: 'workbench.action.search.toggleQueryDetails',
......@@ -185,4 +190,4 @@ configurationRegistry.registerConfiguration({
}
}
}
});
});
\ No newline at end of file
......@@ -12,12 +12,13 @@ import { ToggleViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { SearchViewlet } from 'vs/workbench/parts/search/browser/searchViewlet';
import { Match, FileMatch } from 'vs/workbench/parts/search/common/searchModel';
import { SearchResult, Match, FileMatch, FileMatchOrMatch } from 'vs/workbench/parts/search/common/searchModel';
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import * as Constants from 'vs/workbench/parts/search/common/constants';
import { CollapseAllAction as TreeCollapseAction } from 'vs/base/parts/tree/browser/treeDefaults';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { OpenGlobalSettingsAction } from 'vs/workbench/browser/actions/openSettings';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
export class OpenSearchViewletAction extends ToggleViewletAction {
......@@ -27,6 +28,7 @@ export class OpenSearchViewletAction extends ToggleViewletAction {
constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IWorkbenchEditorService editorService: IWorkbenchEditorService) {
super(id, label, Constants.VIEWLET_ID, viewletService, editorService);
}
}
export class FindInFolderAction extends Action {
......@@ -88,82 +90,53 @@ export class ClearSearchResultsAction extends Action {
}
}
export class SelectOrRemoveAction extends Action {
private selectMode: boolean;
private viewlet: SearchViewlet;
constructor(viewlet: SearchViewlet) {
super('selectOrRemove');
export class RemoveAction extends Action {
this.label = nls.localize('SelectOrRemoveAction.selectLabel', "Select");
this.enabled = false;
this.selectMode = true;
this.viewlet = viewlet;
constructor(private viewer: ITree, private element: FileMatchOrMatch) {
super('remove', nls.localize('RemoveAction.label', "Remove"), 'action-remove');
}
public run(): TPromise<any> {
let result: TPromise<any>;
if (this.selectMode) {
result = this.runAsSelect();
if (this.element instanceof FileMatch) {
let parent:SearchResult = <SearchResult>this.element.parent();
parent.remove(<FileMatch>this.element);
} else {
result = this.runAsRemove();
let parent:FileMatch = <FileMatch>this.element.parent();
parent.remove(<Match>this.element);
}
this.selectMode = !this.selectMode;
this.label = this.selectMode ? nls.localize('SelectOrRemoveAction.selectLabel', "Select") : nls.localize('SelectOrRemoveAction.removeLabel', "Remove");
return result;
return this.viewer.refresh(parent);
}
}
private runAsSelect(): TPromise<void> {
this.viewlet.getResults().addClass('select');
export class ReplaceAllAction extends Action {
return TPromise.as(null);
constructor(private viewer: ITree, private fileMatch: FileMatch, private viewlet: SearchViewlet,
@IReplaceService private replaceService: IReplaceService) {
super('file-action-replace-all', nls.localize('file.replaceAll.label', "Replace All"), 'action-replace-all');
}
private runAsRemove(): TPromise<void> {
let elements: any[] = [];
let tree: ITree = this.viewlet.getControl();
tree.getInput().matches().forEach((fileMatch: FileMatch) => {
fileMatch.matches().filter((lineMatch: Match) => {
return (<any>lineMatch).$checked;
}).forEach((lineMatch: Match) => {
lineMatch.parent().remove(lineMatch);
elements.push(lineMatch.parent());
});
public run(): TPromise<any> {
return this.replaceService.replace([this.fileMatch], this.fileMatch.parent().replaceText).then(() => {
this.viewlet.open(this.fileMatch).done(() => {
new RemoveAction(this.viewer, this.fileMatch).run();
}, errors.onUnexpectedError);
});
this.viewlet.getResults().removeClass('select');
if (elements.length > 0) {
return tree.refreshAll(elements).then(() => {
return tree.refresh();
});
}
return TPromise.as(null);
}
}
export class RemoveAction extends Action {
export class ReplaceAction extends Action {
private viewer: ITree;
private fileMatch: FileMatch;
constructor(viewer: ITree, element: FileMatch) {
super('remove', nls.localize('RemoveAction.label', "Remove"), 'action-remove');
this.viewer = viewer;
this.fileMatch = element;
constructor(private viewer: ITree, private element: Match, private viewlet: SearchViewlet,
@IReplaceService private replaceService: IReplaceService) {
super('action-replace', nls.localize('match.replace.label', "Replace"), 'action-replace');
}
public run(): TPromise<any> {
let parent = this.fileMatch.parent();
parent.remove(this.fileMatch);
return this.viewer.refresh(parent);
return this.replaceService.replace(this.element, this.element.parent().parent().replaceText).then(() => {
this.viewlet.open(this.element).done(() => {
new RemoveAction(this.viewer, this.element).run();
}, errors.onUnexpectedError);
});
}
}
......
......@@ -19,15 +19,14 @@ import { LeftRightWidget, IRenderer } from 'vs/base/browser/ui/leftRightWidget/l
import { ITree, IElementCallback, IDataSource, ISorter, IAccessibilityProvider, IFilter } from 'vs/base/parts/tree/browser/tree';
import {ClickBehavior, DefaultController} from 'vs/base/parts/tree/browser/treeDefaults';
import { ContributableActionProvider } from 'vs/workbench/browser/actionBarRegistry';
import { Match, EmptyMatch, SearchResult, FileMatch} from 'vs/workbench/parts/search/common/searchModel';
import { Match, EmptyMatch, SearchResult, FileMatch, FileMatchOrMatch } from 'vs/workbench/parts/search/common/searchModel';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { Range } from 'vs/editor/common/core/range';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { CommonKeybindings} from 'vs/base/common/keyCodes';
import { SearchViewlet } from 'vs/workbench/parts/search/browser/searchViewlet';
import { RemoveAction } from 'vs/workbench/parts/search/browser/searchActions';
export type FileMatchOrMatch = FileMatch | Match;
import { RemoveAction, ReplaceAllAction, ReplaceAction } from 'vs/workbench/parts/search/browser/searchActions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class SearchDataSource implements IDataSource {
......@@ -87,14 +86,26 @@ export class SearchSorter implements ISorter {
class SearchActionProvider extends ContributableActionProvider {
constructor(private viewlet: SearchViewlet, @IInstantiationService private instantiationService: IInstantiationService) {
super();
}
public hasActions(tree: ITree, element: any): boolean {
return element instanceof FileMatch || super.hasActions(tree, element);
return element instanceof FileMatch || (tree.getInput().isReplaceActive() || element instanceof Match) || super.hasActions(tree, element);
}
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
return super.getActions(tree, element).then(actions => {
if (element instanceof FileMatch) {
actions.unshift(new RemoveAction(tree, element));
if (tree.getInput().isReplaceActive() && element.count() > 0) {
actions.unshift(this.instantiationService.createInstance(ReplaceAllAction, tree, element, this.viewlet));
}
}
if (element instanceof Match && !(element instanceof EmptyMatch)) {
if (tree.getInput().isReplaceActive()) {
actions.unshift(this.instantiationService.createInstance(ReplaceAction, tree, element, this.viewlet), new RemoveAction(tree, element));
}
}
return actions;
......@@ -104,9 +115,10 @@ class SearchActionProvider extends ContributableActionProvider {
export class SearchRenderer extends ActionsRenderer {
constructor(actionRunner: IActionRunner, @IWorkspaceContextService private contextService: IWorkspaceContextService) {
constructor(actionRunner: IActionRunner, viewlet: SearchViewlet, @IWorkspaceContextService private contextService: IWorkspaceContextService,
@IInstantiationService private instantiationService: IInstantiationService) {
super({
actionProvider: new SearchActionProvider(),
actionProvider: instantiationService.createInstance(SearchActionProvider, viewlet),
actionRunner: actionRunner
});
}
......@@ -159,8 +171,21 @@ export class SearchRenderer extends ActionsRenderer {
elements.push('<span>');
elements.push(strings.escape(preview.before));
elements.push('</span><span class="findInFileMatch">');
elements.push(strings.escape(preview.inside));
let input= <SearchResult>tree.getInput();
if (input.isReplaceActive()) {
let replaceValue= input.replaceText;
if (replaceValue) {
elements.push('</span><span class="findInFileMatch replaceMatch">');
elements.push(strings.escape(replaceValue));
} else {
elements.push('</span><span class="findInFileMatch replaceMatch removeMatch">');
elements.push(strings.escape(preview.inside));
}
} else {
elements.push('</span><span class="findInFileMatch">');
elements.push(strings.escape(preview.inside));
}
elements.push('</span><span>');
elements.push(strings.escape(preview.after));
elements.push('</span>');
......@@ -220,13 +245,22 @@ export class SearchController extends DefaultController {
private onDelete(tree: ITree, event: IKeyboardEvent): boolean {
let result = false;
let element = tree.getFocus();
if (element instanceof FileMatch) {
if (element instanceof FileMatch ||
(element instanceof Match && tree.getInput().isReplaceActive() && !(element instanceof EmptyMatch))) {
new RemoveAction(tree, element).run().done(null, errors.onUnexpectedError);
result = true;
}
return result;
}
protected onUp(tree: ITree, event: IKeyboardEvent): boolean {
if (tree.getNavigator().first() === tree.getFocus()) {
this.viewlet.moveFocusFromResults();
return true;
}
return super.onUp(tree, event);
}
}
export class SearchFilter implements IFilter {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import strings = require('vs/base/common/strings');
import dom = require('vs/base/browser/dom');
import { TPromise } from 'vs/base/common/winjs.base';
import { Widget } from 'vs/base/browser/ui/widget';
import { Action } from 'vs/base/common/actions';
import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Button } from 'vs/base/browser/ui/button/button';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { KeyCode } from 'vs/base/common/keyCodes';
import Event, { Emitter } from 'vs/base/common/event';
import { Builder } from 'vs/base/browser/builder';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export interface ISearchWidgetOptions {
value?:string;
isRegex?:boolean;
isCaseSensitive?:boolean;
isWholeWords?:boolean;
}
export class SearchWidget extends Widget {
public static REPLACE_PLACE_HOLD= nls.localize('search.replace.placeHolder', "Replace");
public domNode: HTMLElement;
public searchInput: FindInput;
private replaceInput: InputBox;
private replaceInputContainer: HTMLElement;
private toggleReplaceButton: Button;
private replaceAllAction: Action;
private _onSearchSubmit = this._register(new Emitter<boolean>());
public onSearchSubmit: Event<boolean> = this._onSearchSubmit.event;
private _onSearchCancel = this._register(new Emitter<void>());
public onSearchCancel: Event<void> = this._onSearchCancel.event;
private _onReplaceToggled = this._register(new Emitter<void>());
public onReplaceToggled: Event<void> = this._onReplaceToggled.event;
private _onReplaceState = this._register(new Emitter<void>());
public onReplaceStateChange: Event<void> = this._onReplaceState.event;
private _onReplaceValueChanged = this._register(new Emitter<string>());
public onReplaceValueChanged: Event<string> = this._onReplaceValueChanged.event;
private _onKeyDownArrow = this._register(new Emitter<void>());
public onKeyDownArrow: Event<void> = this._onKeyDownArrow.event;
private _onReplaceAll = this._register(new Emitter<void>());
public onReplaceAll: Event<void> = this._onReplaceAll.event;
constructor(container: Builder, private contextViewService: IContextViewService, options: ISearchWidgetOptions= Object.create(null),
@IInstantiationService private instantiationService: IInstantiationService) {
super();
this.render(container, options);
}
public focus(select:boolean= true, focusReplace: boolean= false):void {
if (this.searchInput.inputBox.hasFocus() || this.replaceInput.hasFocus()) {
return;
}
if (focusReplace && this.isReplaceShown()) {
this.replaceInput.focus();
if (select) {
this.replaceInput.select();
}
} else {
this.searchInput.focus();
if (select) {
this.searchInput.select();
}
}
}
public setWidth(width: number) {
this.searchInput.setWidth(width - 2);
this.replaceInput.width= width - 28;
}
public clear() {
this.searchInput.clear();
this.replaceInput.value= '';
}
public isReplaceActive(): boolean {
return this.isReplaceShown() && this.replaceAllAction.enabled;
}
public isReplaceShown(): boolean {
return !dom.hasClass(this.replaceInputContainer, 'disabled');
}
public getReplaceValue():string {
return this.isReplaceActive() ? this.replaceInput.value : null;
}
private render(container: Builder, options: ISearchWidgetOptions): void {
this.domNode = container.div({ 'class': 'search-widget' }).style({ position: 'relative' }).getHTMLElement();
this.renderToggleReplaceButton(this.domNode);
this.renderSearchInput(this.domNode, options);
this.renderReplaceInput(this.domNode);
}
private renderToggleReplaceButton(parent: HTMLElement): void {
this.toggleReplaceButton= this._register(new Button(parent));
this.toggleReplaceButton.icon= 'toggle-replace-button collapse';
this.toggleReplaceButton.addListener2('click', () => this.onToggleReplaceButton());
this.toggleReplaceButton.getElement().title= nls.localize('search.replace.toggle.button.title', "Toggle Replace");
}
private renderSearchInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
let inputOptions: IFindInputOptions = {
label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search or Escape to cancel'),
validation: (value: string) => this.validatSearchInput(value),
placeholder: nls.localize('search.placeHolder', "Search")
};
let searchInputContainer= dom.append(parent, dom.emmet('.search-box.input-box'));
this.searchInput = this._register(new FindInput(searchInputContainer, this.contextViewService, inputOptions));
this.searchInput.onKeyUp((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyUp(keyboardEvent));
this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent));
this.searchInput.setValue(options.value || '');
this.searchInput.setRegex(!!options.isRegex);
this.searchInput.setCaseSensitive(!!options.isCaseSensitive);
this.searchInput.setWholeWords(!!options.isWholeWords);
this._register(dom.addDisposableListener(this.searchInput.inputBox.inputElement, dom.EventType.FOCUS, () => this.updateReplaceActionState()));
this._register(dom.addDisposableListener(this.searchInput.inputBox.inputElement, dom.EventType.BLUR, () => this.updateReplaceActionState()));
}
private renderReplaceInput(parent: HTMLElement): void {
this.replaceAllAction = new Action('action-replace-all', nls.localize('file.replaceAll.label', "Replace All"), 'action-replace-all', false, () => {
this._onReplaceAll.fire();
return TPromise.as(null);
});
this.replaceInputContainer= dom.append(parent, dom.emmet('.replace-box.input-box.disabled'));
this.replaceInput = this._register(new InputBox(this.replaceInputContainer, this.contextViewService, {
placeholder: SearchWidget.REPLACE_PLACE_HOLD,
actions: [this.replaceAllAction]
}));
this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent));
this.onkeyup(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyUp(keyboardEvent));
this.replaceInput.onDidChange(() => this._onReplaceValueChanged.fire());
}
private onToggleReplaceButton():void {
dom.toggleClass(this.replaceInputContainer, 'disabled');
dom.toggleClass(this.toggleReplaceButton.getElement(), 'collapse');
dom.toggleClass(this.toggleReplaceButton.getElement(), 'expand');
this._onReplaceToggled.fire();
this.updateReplaceActionState();
}
private updateReplaceActionState():boolean {
let enabled= this.isReplaceShown() && !this.searchInput.inputBox.hasFocus();
if (this.replaceAllAction.enabled !== enabled) {
this.replaceAllAction.enabled= enabled;
this._onReplaceState.fire();
return true;
}
return false;
}
private validatSearchInput(value: string): any {
if (value.length === 0) {
return null;
}
if (!this.searchInput.getRegex()) {
return null;
}
let regExp: RegExp;
try {
regExp = new RegExp(value);
} catch (e) {
return { content: e.message };
}
if (strings.regExpLeadsToEndlessLoop(regExp)) {
return { content: nls.localize('regexp.validationFailure', "Expression matches everything") };
}
}
private onSearchInputKeyUp(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.Enter:
this.submitSearch();
return;
case KeyCode.Escape:
this._onSearchCancel.fire();
return;
default:
return;
}
}
private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.DownArrow:
if (this.isReplaceShown()) {
this.replaceInput.focus();
keyboardEvent.stopPropagation();
} else {
this._onKeyDownArrow.fire();
}
return;
default:
return;
}
}
private onReplaceInputKeyUp(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.Enter:
this.submitSearch();
return;
case KeyCode.Escape:
this.onToggleReplaceButton();
this.searchInput.focus();
return;
default:
return;
}
}
private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.UpArrow:
this.searchInput.focus();
return;
case KeyCode.DownArrow:
this._onKeyDownArrow.fire();
return;
default:
return;
}
}
private submitSearch(refresh: boolean= true): void {
if (this.searchInput.getValue()) {
this._onSearchSubmit.fire(refresh);
}
}
public dispose(): void {
super.dispose();
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { Match, FileMatch } from 'vs/workbench/parts/search/common/searchModel';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { IProgressRunner } from 'vs/platform/progress/common/progress';
export var IReplaceService = createDecorator<IReplaceService>('replaceService');
export interface IReplaceService {
serviceId : ServiceIdentifier<any>;
/**
* Replace the match with the given text.
*/
replace(match: Match, text: string): TPromise<any>;
/**
* Replace all the matches in the given file matches with provided text.
* You can also pass the progress runner to update the progress of replacing.
*/
replace(files: FileMatch[], text: string, progress?: IProgressRunner): TPromise<any>;
}
......@@ -123,6 +123,8 @@ export class FileMatch implements lifecycle.IDisposable {
}
}
export type FileMatchOrMatch = FileMatch | Match;
export class LiveFileMatch extends FileMatch implements lifecycle.IDisposable {
private static DecorationOption: IModelDecorationOptions = {
......@@ -199,12 +201,21 @@ export class LiveFileMatch extends FileMatch implements lifecycle.IDisposable {
private _isTextModelDisposed(): boolean {
return !this._model || (<ITextModel>this._model).isDisposed();
}
public remove(match: Match): void {
super.remove(match);
if (this.count() === 0) {
this.add(new EmptyMatch(this));
}
}
}
export class SearchResult extends EventEmitter {
private _modelService: IModelService;
private _query: Search.IPatternInfo;
private _replace: string= null;
private _disposables: lifecycle.IDisposable[] = [];
private _matches: { [key: string]: FileMatch; } = Object.create(null);
......@@ -221,6 +232,26 @@ export class SearchResult extends EventEmitter {
}
}
/**
* Return true if replace is enabled otherwise false
*/
public isReplaceActive():boolean {
return this.replaceText !== null && this.replaceText !== void 0;
}
/**
* Returns the text to replace.
* Can be null if replace is not enabled. Use replace() before.
* Can be empty.
*/
public get replaceText(): string {
return this._replace;
}
public set replaceText(replace: string) {
this._replace= replace;
}
private _onModelAdded(model: IModel): void {
let resource = model.uri,
fileMatch = this._matches[resource.toString()];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册