diff --git a/src/command/Commands.js b/src/command/Commands.js index 896af77e8a1..a9476113590 100644 --- a/src/command/Commands.js +++ b/src/command/Commands.js @@ -58,6 +58,7 @@ define(function (require, exports, module) { exports.EDIT_SELECT_LINE = "edit.selectLine"; exports.EDIT_FIND = "edit.find"; exports.EDIT_FIND_IN_FILES = "edit.findInFiles"; + exports.EDIT_FIND_IN_SUBTREE = "edit.findInSubtree"; exports.EDIT_FIND_NEXT = "edit.findNext"; exports.EDIT_FIND_PREVIOUS = "edit.findPrevious"; exports.EDIT_REPLACE = "edit.replace"; diff --git a/src/command/Menus.js b/src/command/Menus.js index 1153862eab8..ebedf774ec1 100644 --- a/src/command/Menus.js +++ b/src/command/Menus.js @@ -993,6 +993,8 @@ define(function (require, exports, module) { project_cmenu.addMenuItem(Commands.FILE_NEW); project_cmenu.addMenuItem(Commands.FILE_NEW_FOLDER); project_cmenu.addMenuItem(Commands.FILE_RENAME, "F2"); + project_cmenu.addMenuDivider(); + project_cmenu.addMenuItem(Commands.EDIT_FIND_IN_SUBTREE); var working_set_cmenu = registerContextMenu(ContextMenuIds.WORKING_SET_MENU); working_set_cmenu.addMenuItem(Commands.FILE_CLOSE); @@ -1000,6 +1002,8 @@ define(function (require, exports, module) { working_set_cmenu.addMenuItem(Commands.FILE_RENAME); working_set_cmenu.addMenuItem(Commands.NAVIGATE_SHOW_IN_FILE_TREE); working_set_cmenu.addMenuDivider(); + working_set_cmenu.addMenuItem(Commands.EDIT_FIND_IN_SUBTREE); + working_set_cmenu.addMenuDivider(); working_set_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_ADDED); working_set_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_NAME); working_set_cmenu.addMenuItem(Commands.SORT_WORKINGSET_BY_TYPE); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 19f6c918984..51b4f34de82 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -107,14 +107,16 @@ define({ "NO_UPDATE_TITLE" : "You're up to date!", "NO_UPDATE_MESSAGE" : "You are running the latest version of {APP_NAME}.", - "FIND_IN_FILES_TITLE" : "for \"{4}\" - {0} {1} in {2} {3}", + "FIND_IN_FILES_TITLE" : "for \"{4}\" {5} - {0} {1} in {2} {3}", + "FIND_IN_FILES_SCOPED" : "in {0}", + "FIND_IN_FILES_NO_SCOPE" : "in project", "FIND_IN_FILES_FILE" : "file", "FIND_IN_FILES_FILES" : "files", "FIND_IN_FILES_MATCH" : "match", "FIND_IN_FILES_MATCHES" : "matches", "FIND_IN_FILES_MORE_THAN" : "More than ", "FIND_IN_FILES_MAX" : " (showing the first {0} matches)", - "FIND_IN_FILES_FILE_PATH" : "File: {0}", + "FIND_IN_FILES_FILE_PATH" : "File: {0}", "FIND_IN_FILES_LINE" : "line: {0}", "ERROR_FETCHING_UPDATE_INFO_TITLE" : "Error getting update info", @@ -178,6 +180,7 @@ define({ "CMD_SELECT_LINE" : "Select Line", "CMD_FIND" : "Find", "CMD_FIND_IN_FILES" : "Find in Files", + "CMD_FIND_IN_SUBTREE" : "Find in...", "CMD_FIND_NEXT" : "Find Next", "CMD_FIND_PREVIOUS" : "Find Previous", "CMD_REPLACE" : "Replace", diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 9369680e6dd..075b67b338e 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -269,6 +269,8 @@ define(function (require, exports, module) { * If absPath lies within the project, returns a project-relative path. Else returns absPath * unmodified. * Does not support paths containing ".." + * @param {!string} absPath + * @return {!string} */ function makeProjectRelativeIfPossible(absPath) { if (isWithinProject(absPath)) { diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index 00ea72bef8f..64edeff1f86 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -47,6 +47,7 @@ define(function (require, exports, module) { Commands = require("command/Commands"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), + ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), FileIndexManager = require("project/FileIndexManager"), @@ -86,6 +87,21 @@ define(function (require, exports, module) { return new RegExp(query, "gi"); } + /** + * Returns label text to indicate the search scope. Already HTML-escaped. + * @param {?Entry} scope + */ + function _labelForScope(scope) { + var projName = ProjectManager.getProjectRoot().name; + if (scope) { + var displayPath = StringUtils.htmlEscape(ProjectManager.makeProjectRelativeIfPossible(scope.fullPath)); + return StringUtils.format(Strings.FIND_IN_FILES_SCOPED, displayPath); + } else { + return Strings.FIND_IN_FILES_NO_SCOPE; + } + } + + // This dialog class was mostly copied from QuickOpen. We should have a common dialog // class that everyone can use. @@ -126,12 +142,14 @@ define(function (require, exports, module) { /** * Shows the search dialog * @param {?string} initialString Default text to prepopulate the search field with + * @param {?Entry} scope Search scope, or null to search whole proj * @returns {$.Promise} that is resolved with the string to search for */ - FindInFilesDialog.prototype.showDialog = function (initialString) { - var dialogHTML = Strings.CMD_FIND_IN_FILES + - ": (" + - Strings.SEARCH_REGEXP_INFO + ")"; + FindInFilesDialog.prototype.showDialog = function (initialString, scope) { + // Note the prefix label is a simple "Find:" - the "in ..." part comes after the text field + var dialogHTML = Strings.CMD_FIND + + ": " + + "(" + Strings.SEARCH_REGEXP_INFO + ")"; this.result = new $.Deferred(); this._createDialogDiv(dialogHTML); var $searchField = $("input#findInFilesInput"); @@ -140,6 +158,8 @@ define(function (require, exports, module) { $searchField.attr("value", initialString || ""); $searchField.get(0).select(); + $("#findInFilesScope").html(_labelForScope(scope)); + $searchField.bind("keydown", function (event) { if (event.keyCode === KeyEvent.DOM_VK_RETURN || event.keyCode === KeyEvent.DOM_VK_ESCAPE) { // Enter/Return key or Esc key event.stopPropagation(); @@ -209,7 +229,7 @@ define(function (require, exports, module) { return matches; } - function _showSearchResults(searchResults, query) { + function _showSearchResults(searchResults, query, scope) { var $searchResultsDiv = $("#search-results"); if (searchResults && searchResults.length) { @@ -229,17 +249,19 @@ define(function (require, exports, module) { } numMatchesStr += String(numMatches); + // This text contains some formatting, so all the strings are assumed to be already escaped var summary = StringUtils.format( Strings.FIND_IN_FILES_TITLE, numMatchesStr, (numMatches > 1) ? Strings.FIND_IN_FILES_MATCHES : Strings.FIND_IN_FILES_MATCH, searchResults.length, (searchResults.length > 1 ? Strings.FIND_IN_FILES_FILES : Strings.FIND_IN_FILES_FILE), - query + query, + scope ? _labelForScope(scope) : "" ); $("#search-result-summary") - .text(summary + + .html(summary + (numMatches > FIND_IN_FILES_MAX ? StringUtils.format(Strings.FIND_IN_FILES_MAX, FIND_IN_FILES_MAX) : "")) .prepend(" "); // putting a normal space before the "-" is not enough @@ -251,11 +273,8 @@ define(function (require, exports, module) { return $("