Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

Commit

Permalink
Added 'All' button to FindReplace functionality.
Browse files Browse the repository at this point in the history
Work in progress, problem with htmlReady and createBottomPanel.

Still work in progress, version with some UI to review.

Work in progress, brackets freezing on document.replaceRange

Fixed freezing issue and close panel after replacing.

Added document change handler to close the replace panel if it's open.

Replace all panel now automatically refreshes on document change.

Refactoring and fixing JSLint errors.

Replace button is now switched to Search button after a document is modified.

Deleting wrongly added file.

Added test for simple find-replace case.

Changes according to comments.

Added limit of 300 search results.

Modified string to show when limit is reached.

Refactoring after code-review #1

Refactoring after code-review #2

Refactoring after code-review #3

Refactoring after code-review #4

Click on the stop button so the replace bar is dismissed.

Fixed failing testcase.
  • Loading branch information
zaggino committed Aug 17, 2013
1 parent c4601cc commit 3509df9
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 6 deletions.
9 changes: 9 additions & 0 deletions src/htmlContent/search-replace-panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div id="replace-all-results" class="bottom-panel vert-resizable top-resizer no-focus">
<div class="toolbar simple-toolbar-layout">
<input type="checkbox" class="check-all" />
<div class="title"></div>
<button class="replace-checked">{{BUTTON_REPLACE}}</button>
<a href="#" class="close">&times;</a>
</div>
<div class="table-container resizable-content"></div>
</div>
11 changes: 11 additions & 0 deletions src/htmlContent/search-replace-results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<table class="table table-striped table-condensed row-highlight">
<tbody>
{{#searchResults}}
<tr class="replace-row" data-match="{{index}}">
<td class="checkbox-column"><input type="checkbox" class="check-one" checked="true" /></td>
<td class="line-column">{{line}}</td>
<td>{{pre}}<span class="highlight">{{highlight}}</span>{{post}}</td>
</tr>
{{/searchResults}}
</tbody>
</table>
6 changes: 5 additions & 1 deletion src/nls/root/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ define({
"WITH" : "With",
"BUTTON_YES" : "Yes",
"BUTTON_NO" : "No",
"BUTTON_ALL" : "All",
"BUTTON_STOP" : "Stop",
"BUTTON_REPLACE" : "Replace",

"OPEN_FILE" : "Open File",
"SAVE_FILE_AS" : "Save File",
Expand All @@ -124,7 +126,9 @@ define({
"RELEASE_NOTES" : "Release Notes",
"NO_UPDATE_TITLE" : "You're up to date!",
"NO_UPDATE_MESSAGE" : "You are running the latest version of {APP_NAME}.",


"FIND_REPLACE_TITLE" : "Replace \"{0}\" with \"{1}\" &mdash; {3} {2} matches",

"FIND_IN_FILES_TITLE" : "\"{4}\" found {5} &mdash; {0} {1} in {2} {3}",
"FIND_IN_FILES_SCOPED" : "in <span class='dialog-filename'>{0}</span>",
"FIND_IN_FILES_NO_SCOPE" : "in project",
Expand Down
158 changes: 153 additions & 5 deletions src/search/FindReplace.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*/

/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global define, $, doReplace */
/*global define, $, doReplace, Mustache */
/*unittests: FindReplace*/


Expand All @@ -35,17 +35,38 @@ define(function (require, exports, module) {
"use strict";

var CommandManager = require("command/CommandManager"),
AppInit = require("utils/AppInit"),
Commands = require("command/Commands"),
DocumentManager = require("document/DocumentManager"),
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
Editor = require("editor/Editor"),
EditorManager = require("editor/EditorManager"),
ModalBar = require("widgets/ModalBar").ModalBar,
PanelManager = require("view/PanelManager"),
Resizer = require("utils/Resizer"),
StatusBar = require("widgets/StatusBar"),
ViewUtils = require("utils/ViewUtils");

var searchReplacePanelTemplate = require("text!htmlContent/search-replace-panel.html"),
searchReplaceResultsTemplate = require("text!htmlContent/search-replace-results.html");

/** @cost Constant used to define the maximum results to show */
var FIND_REPLACE_MAX = 300;

/** @type {!Panel} Panel that shows results of replaceAll action */
var replaceAllPanel = null;

/** @type {?Document} Instance of the currently opened document when replaceAllPanel is visible */
var currentDocument = null;

/** @type {$.Element} jQuery elements used in the replaceAll panel */
var $replaceAllContainer,
$replaceAllTable;

var modalBar,
isFindFirst = false;

function SearchState() {
this.posFrom = this.posTo = this.query = null;
this.marked = [];
Expand Down Expand Up @@ -281,6 +302,110 @@ define(function (require, exports, module) {
}
}

/**
* @private
* Closes a panel with search-replace results.
* Main purpose is to make sure that events are correctly detached from current document.
*/
function _closeReplaceAllPanel() {
if (replaceAllPanel !== null && replaceAllPanel.isVisible()) {
replaceAllPanel.hide();
}
$(currentDocument).off("change.replaceAll");
}

/**
* @private
* Shows a panel with search results and offers to replace them,
* user can use checkboxes to select which results he wishes to replace.
* @param {Editor} editor - Currently active editor that was used to invoke this action.
* @param {string|RegExp} replaceWhat - Query that will be passed into CodeMirror Cursor to search for results.
* @param {string} replaceWith - String that should be used to replace chosen results.
* @param {?function} replaceFunction - If replaceWhat is a RegExp, than this function is used to replace matches in that RegExp.
*/
function _showReplaceAllPanel(editor, replaceWhat, replaceWith, replaceFunction) {
var results = [],
cm = editor._codeMirror,
cursor = getSearchCursor(cm, replaceWhat),
from,
to,
line,
multiLine;

// Collect all results from document
while (cursor.findNext()) {
from = cursor.from();
to = cursor.to();
line = editor.document.getLine(from.line);
multiLine = from.line !== to.line;

results.push({
index: results.length, // add indexes to array
from: from,
to: to,
line: StringUtils.format(Strings.FIND_IN_FILES_LINE, from.line + 1),
pre: line.slice(0, from.ch),
highlight: line.slice(from.ch, multiLine ? undefined : to.ch),
post: multiLine ? "\u2026" : line.slice(to.ch)
});

if (results.length >= FIND_REPLACE_MAX) {
break;
}
}

// This text contains some formatting, so all the strings are assumed to be already escaped
var summary = StringUtils.format(
Strings.FIND_REPLACE_TITLE,
replaceWhat.toString(),
replaceWith.toString(),
results.length,
results.length >= FIND_REPLACE_MAX ? Strings.FIND_IN_FILES_MORE_THAN : ""
);

// Insert the search summary
$replaceAllContainer.find(".title").html(summary);

// All checkboxes are checked by default
$replaceAllContainer.find(".check-all").prop("checked", true);

// Attach event to replace button
$replaceAllContainer.find("button.replace-checked").off().on("click", function (e) {
$replaceAllTable.find(".check-one:checked")
.closest(".replace-row")
.toArray()
.reverse()
.forEach(function (checkedRow) {
var match = results[$(checkedRow).data("match")],
rw = typeof replaceWhat === "string" ? replaceWith : replaceWith.replace(/\$(\d)/, replaceFunction);
editor.document.replaceRange(rw, match.from, match.to, "+replaceAll");
});
_closeReplaceAllPanel();
});

// Insert the search results
$replaceAllTable
.empty()
.append(Mustache.render(searchReplaceResultsTemplate, {searchResults: results}))
.off()
.on("click", ".check-one", function (e) {
e.stopPropagation();
})
.on("click", ".replace-row", function (e) {
var match = results[$(e.currentTarget).data("match")];
editor.setSelection(match.from, match.to, true);
});

// we can't safely replace after document has been modified
// this handler is only attached, when replaceAllPanel is visible
currentDocument = DocumentManager.getCurrentDocument();
$(currentDocument).on("change.replaceAll", function () {
_closeReplaceAllPanel();
});

replaceAllPanel.show();
}

var replaceQueryDialog = Strings.CMD_REPLACE +
': <input type="text" style="width: 10em"/> <div class="message"><span style="color: #888">(' +
Strings.SEARCH_REGEXP_INFO + ')</span></div><div class="error"></div>';
Expand All @@ -289,6 +414,7 @@ define(function (require, exports, module) {
var doReplaceConfirm = Strings.CMD_REPLACE +
'? <button id="replace-yes" class="btn">' + Strings.BUTTON_YES +
'</button> <button id="replace-no" class="btn">' + Strings.BUTTON_NO +
'</button> <button id="replace-all" class="btn">' + Strings.BUTTON_ALL +
'</button> <button id="replace-stop" class="btn">' + Strings.BUTTON_STOP + '</button>';

function replace(editor, all) {
Expand All @@ -298,7 +424,7 @@ define(function (require, exports, module) {
if (!query) {
return;
}

query = parseQuery(query);
createModalBar(replacementQueryDialog, true);
$(modalBar).on("closeOk", function (e, text) {
Expand Down Expand Up @@ -343,6 +469,8 @@ define(function (require, exports, module) {
doReplace(match);
} else if (e.target.id === "replace-no") {
advance();
} else if (e.target.id === "replace-all") {
_showReplaceAllPanel(editor, query, text, fnMatch);
} else if (e.target.id === "replace-stop") {
// Destroy modalBar on stop
modalBar = null;
Expand All @@ -358,13 +486,13 @@ define(function (require, exports, module) {
}
});
});

// Prepopulate the replace field with the current selection, if any
getDialogTextField()
.val(cm.getSelection())
.get(0).select();
}

function _launchFind() {
var editor = EditorManager.getActiveEditor();
if (editor) {
Expand Down Expand Up @@ -397,6 +525,26 @@ define(function (require, exports, module) {
}
}

// Initialize items dependent on HTML DOM
AppInit.htmlReady(function () {
var panelHtml = Mustache.render(searchReplacePanelTemplate, Strings);
replaceAllPanel = PanelManager.createBottomPanel("findReplace-all.panel", $(panelHtml), 100);
$replaceAllContainer = replaceAllPanel.$panel;
$replaceAllTable = $replaceAllContainer.children(".table-container");

// Attach events to the panel
replaceAllPanel.$panel
.on("click", ".close", function () {
_closeReplaceAllPanel();
})
.on("click", ".check-all", function (e) {
var isChecked = $(this).is(":checked");
replaceAllPanel.$panel.find(".check-one").prop("checked", isChecked);
});
});

$(DocumentManager).on("currentDocumentChange", _closeReplaceAllPanel);

CommandManager.register(Strings.CMD_FIND, Commands.EDIT_FIND, _launchFind);
CommandManager.register(Strings.CMD_FIND_NEXT, Commands.EDIT_FIND_NEXT, _findNext);
CommandManager.register(Strings.CMD_REPLACE, Commands.EDIT_REPLACE, _replace);
Expand Down
20 changes: 20 additions & 0 deletions src/styles/brackets.less
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,26 @@ a, img {
vertical-align: baseline;
}

/* Find-Replace All UI */

#replace-all-results {
.check-all {
margin: 0px 22px 0px 6px;
}
.title {
margin-right: 25px;
}
table {
td.checkbox-column {
width: 20px;
text-align: center;
}
td.line-column {
width: 100px;
}
}
}

/* Modal bar for Find/Quick Open */

.modal-bar.modal-bar-hide {
Expand Down
33 changes: 33 additions & 0 deletions test/spec/FindReplace-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,39 @@ define(function (require, exports, module) {
expectSelection({start: {line: 0, ch: 0}, end: {line: 0, ch: 18}});
});
});

describe("Search -> Replace", function () {
beforeEach(setupFullEditor);

it("should find and replace one string", function () {
runs(function () {
CommandManager.execute(Commands.EDIT_REPLACE);
enterSearchText("foo");
pressEnter();
});

waitsForSearchBarReopen();

runs(function () {
enterSearchText("bar");
pressEnter();
});

waitsForSearchBarReopen();

runs(function () {
expectSelection(fooExpectedMatches[0]);
expect(/foo/i.test(myEditor.getSelectedText())).toBe(true);

expect($("#replace-yes").is(":visible")).toBe(true);
$("#replace-yes").click();
$("#replace-stop").click();

myEditor.setSelection(fooExpectedMatches[0].start, fooExpectedMatches[0].end);
expect(/bar/i.test(myEditor.getSelectedText())).toBe(true);
});
});
});
});

});

0 comments on commit 3509df9

Please sign in to comment.