/
githubmirror
/
brackets
Обзор
Документация
Войти
/
githubmirror
/
brackets
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/search/FindInFilesUI.js
512 строк
21 KB
Sietse Brouwer
Public methods for closing Search Results and References bottom panel
20 янв 2020, 18:25
20 янв 2020, 18:25
75d3947
Код
Авторство
О чём код?
/* * Copyright (c) 2014 - present Adobe Systems Incorporated. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * */ /* * UI and controller logic for find/replace across multiple files within the project. * * FUTURE: * - Handle matches that span multiple lines */ define(function (require, exports, module) { "use strict"; var AppInit = require("utils/AppInit"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), EditorManager = require("editor/EditorManager"), WorkspaceManager = require("view/WorkspaceManager"), FileFilters = require("search/FileFilters"), FileUtils = require("file/FileUtils"), FindBar = require("search/FindBar").FindBar, FindInFiles = require("search/FindInFiles"), FindUtils = require("search/FindUtils"), InMemoryFile = require("document/InMemoryFile"), ProjectManager = require("project/ProjectManager"), SearchResultsView = require("search/SearchResultsView").SearchResultsView, StatusBar = require("widgets/StatusBar"), Strings = require("strings"), StringUtils = require("utils/StringUtils"), HealthLogger = require("utils/HealthLogger"), _ = require("thirdparty/lodash"); /** @const Maximum number of files to do replacements in-memory instead of on disk. */ var MAX_IN_MEMORY = 20; /** @type {SearchResultsView} The results view. Initialized in htmlReady() */ var _resultsView = null; /** @type {FindBar} Find bar containing the search UI. */ var _findBar = null; /** * @private * Forward declaration for JSLint. * @type {Function} */ var _finishReplaceBatch; /** * Does a search in the given scope with the given filter. Shows the result list once the search is complete. * @param {{query: string, caseSensitive: boolean, isRegexp: boolean}} queryInfo Query info object * @param {?Entry} scope Project file/subfolder to search within; else searches whole project. * @param {?string} filter A "compiled" filter as returned by FileFilters.compile(), or null for no filter * @param {?string} replaceText If this is a replacement, the text to replace matches with. * @param {?$.Promise} candidateFilesPromise If specified, a promise that should resolve with the same set of files that * getCandidateFiles(scope) would return. * @return {$.Promise} A promise that's resolved with the search results or rejected when the find competes. */ function searchAndShowResults(queryInfo, scope, filter, replaceText, candidateFilesPromise) { return FindInFiles.doSearchInScope(queryInfo, scope, filter, replaceText, candidateFilesPromise) .done(function (zeroFilesToken) { // Done searching all files: show results if (FindInFiles.searchModel.hasResults()) { _resultsView.open(); if (_findBar) { _findBar.enable(true); _findBar.focus(); } } else { _resultsView.close(); if (_findBar) { var showMessage = false; _findBar.enable(true); if (zeroFilesToken === FindInFiles.ZERO_FILES_TO_SEARCH) { _findBar.showError(StringUtils.format(Strings.FIND_IN_FILES_ZERO_FILES, FindUtils.labelForScope(FindInFiles.searchModel.scope)), true); } else { showMessage = true; } _findBar.showNoResults(true, showMessage); } } StatusBar.hideBusyIndicator(); }) .fail(function (err) { console.log("find in files failed: ", err); StatusBar.hideBusyIndicator(); }); } /** * Does a search in the given scope with the given filter. Replace the result list once the search is complete. * @param {{query: string, caseSensitive: boolean, isRegexp: boolean}} queryInfo Query info object * @param {?Entry} scope Project file/subfolder to search within; else searches whole project. * @param {?string} filter A "compiled" filter as returned by FileFilters.compile(), or null for no filter * @param {?string} replaceText If this is a replacement, the text to replace matches with. * @param {?$.Promise} candidateFilesPromise If specified, a promise that should resolve with the same set of files that * getCandidateFiles(scope) would return. * @return {$.Promise} A promise that's resolved with the search results or rejected when the find competes. */ function searchAndReplaceResults(queryInfo, scope, filter, replaceText, candidateFilesPromise) { return FindInFiles.doSearchInScope(queryInfo, scope, filter, replaceText, candidateFilesPromise) .done(function (zeroFilesToken) { // Done searching all files: replace all if (FindInFiles.searchModel.hasResults()) { _finishReplaceBatch(FindInFiles.searchModel); if (_findBar) { _findBar.enable(true); _findBar.focus(); } } StatusBar.hideBusyIndicator(); }) .fail(function (err) { console.log("replace all failed: ", err); StatusBar.hideBusyIndicator(); }); } /** * @private * Displays a non-modal embedded dialog above the code mirror editor that allows the user to do * a find operation across all files in the project. * @param {?Entry} scope Project file/subfolder to search within; else searches whole project. * @param {boolean=} showReplace If true, show the Replace controls. */ function _showFindBar(scope, showReplace) { FindUtils.notifySearchScopeChanged(); // If the scope is a file with a custom viewer, then we // don't show find in files dialog. if (scope && !EditorManager.canOpenPath(scope.fullPath)) { return; } if (scope instanceof InMemoryFile) { CommandManager.execute(Commands.FILE_OPEN, { fullPath: scope.fullPath }).done(function () { CommandManager.execute(Commands.CMD_FIND); }); return; } // Get initial query/replace text var currentEditor = EditorManager.getActiveEditor(), initialQuery = FindBar.getInitialQuery(_findBar, currentEditor); // Close our previous find bar, if any. (The open() of the new _findBar will // take care of closing any other find bar instances.) if (_findBar) { _findBar.close(); } _findBar = new FindBar({ multifile: true, replace: showReplace, initialQuery: initialQuery.query, initialReplaceText: initialQuery.replaceText, queryPlaceholder: Strings.FIND_QUERY_PLACEHOLDER, scopeLabel: FindUtils.labelForScope(scope) }); _findBar.open(); // TODO Should push this state into ModalBar (via a FindBar API) instead of installing a callback like this. // Custom closing behavior: if in the middle of executing search, blur shouldn't close ModalBar yet. And // don't close bar when opening Edit Filter dialog either. _findBar._modalBar.isLockedOpen = function () { // TODO: should have state for whether the search is executing instead of looking at find bar state // TODO: should have API on filterPicker to figure out if dialog is open return !_findBar.isEnabled() || $(".modal.instance .exclusions-editor").length > 0; }; var candidateFilesPromise = FindInFiles.getCandidateFiles(scope), // used for eventual search, and in exclusions editor UI filterPicker; function handleQueryChange() { // Check the query expression on every input event. This way the user is alerted // to any RegEx syntax errors immediately. var queryInfo = _findBar.getQueryInfo(), queryResult = FindUtils.parseQueryInfo(queryInfo); // Enable the replace button appropriately. _findBar.enableReplace(queryResult.valid); if (queryResult.valid || queryResult.empty) { _findBar.showNoResults(false); _findBar.showError(null); } else { _findBar.showNoResults(true, false); _findBar.showError(queryResult.error); } } function startSearch(replaceText) { var queryInfo = _findBar.getQueryInfo(), disableFindBar = FindUtils.isNodeSearchDisabled() || (replaceText ? true : false); if (queryInfo && queryInfo.query) { _findBar.enable(!disableFindBar); StatusBar.showBusyIndicator(disableFindBar); if (queryInfo.isRegexp) { HealthLogger.searchDone(HealthLogger.SEARCH_REGEXP); } if (queryInfo.isCaseSensitive) { HealthLogger.searchDone(HealthLogger.SEARCH_CASE_SENSITIVE); } var filter; if (filterPicker) { filter = FileFilters.commitPicker(filterPicker); } else { // Single-file scope: don't use any file filters filter = null; } searchAndShowResults(queryInfo, scope, filter, replaceText, candidateFilesPromise); } return null; } function startReplace() { startSearch(_findBar.getReplaceText()); } _findBar .on("doFind.FindInFiles", function () { // Subtle issue: we can't just pass startSearch directly as the handler, because // we don't want it to get the event object as an argument. startSearch(); }) .on("queryChange.FindInFiles", handleQueryChange) .on("close.FindInFiles", function (e) { _findBar.off(".FindInFiles"); _findBar = null; }); if (showReplace) { // We shouldn't get a "doReplace" in this case, since the Replace button // is hidden when we set options.multifile. _findBar.on("doReplaceBatch.FindInFiles", startReplace); } var oldModalBarHeight = _findBar._modalBar.height(); // Show file-exclusion UI *unless* search scope is just a single file if (!scope || scope.isDirectory) { var exclusionsContext = { label: FindUtils.labelForScope(scope), promise: candidateFilesPromise }; filterPicker = FileFilters.createFilterPicker(exclusionsContext); // TODO: include in FindBar? (and disable it when FindBar is disabled) _findBar._modalBar.getRoot().find(".scope-group").append(filterPicker); } handleQueryChange(); // Appending FilterPicker and query text can change height of modal bar, so resize editor. // Preserve scroll position of the current full editor across the editor refresh, adjusting // for the height of the modal bar so the code doesn't appear to shift if possible. var fullEditor = EditorManager.getCurrentFullEditor(), scrollPos; if (fullEditor) { scrollPos = fullEditor.getScrollPos(); scrollPos.y -= oldModalBarHeight; // modalbar already showing, adjust for old height } WorkspaceManager.recomputeLayout(); if (fullEditor) { fullEditor._codeMirror.scrollTo(scrollPos.x, scrollPos.y + _findBar._modalBar.height()); } } /** * @private * Finish a replace across files operation when the user clicks "Replace" on the results panel. * @param {SearchModel} model The model for the search associated with ths replace. */ function _finishReplaceBatch(model) { var replaceText = model.replaceText; if (replaceText === null) { return; } // Clone the search results so that they don't get updated in the middle of the replacement. var resultsClone = _.cloneDeep(model.results), replacedFiles = Object.keys(resultsClone).filter(function (path) { return FindUtils.hasCheckedMatches(resultsClone[path]); }), isRegexp = model.queryInfo.isRegexp; function processReplace(forceFilesOpen) { StatusBar.showBusyIndicator(true); FindInFiles.doReplace(resultsClone, replaceText, { forceFilesOpen: forceFilesOpen, isRegexp: isRegexp }) .fail(function (errors) { var message = Strings.REPLACE_IN_FILES_ERRORS + FileUtils.makeDialogFileList( errors.map(function (errorInfo) { return ProjectManager.makeProjectRelativeIfPossible(errorInfo.item); }) ); Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.REPLACE_IN_FILES_ERRORS_TITLE, message, [ { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.BUTTON_REPLACE_WITHOUT_UNDO } ] ); }) .always(function () { StatusBar.hideBusyIndicator(); }); } if (replacedFiles.length <= MAX_IN_MEMORY) { // Just do the replacements in memory. _resultsView.close(); processReplace(true); } else { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_INFO, Strings.REPLACE_WITHOUT_UNDO_WARNING_TITLE, StringUtils.format(Strings.REPLACE_WITHOUT_UNDO_WARNING, MAX_IN_MEMORY), [ { className : Dialogs.DIALOG_BTN_CLASS_NORMAL, id : Dialogs.DIALOG_BTN_CANCEL, text : Strings.CANCEL }, { className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, id : Dialogs.DIALOG_BTN_OK, text : Strings.BUTTON_REPLACE_WITHOUT_UNDO } ] ) .done(function (id) { if (id === Dialogs.DIALOG_BTN_OK) { _resultsView.close(); processReplace(false); } }); } } // Command handlers /** * @private * Bring up the Find in Files UI with the replace options. */ function _showReplaceBar() { FindUtils.notifySearchScopeChanged(); _showFindBar(null, true); } /** * @private * Search within the file/subtree defined by the sidebar selection */ function _showFindBarForSubtree() { FindUtils.notifySearchScopeChanged(); var selectedEntry = ProjectManager.getSelectedItem(); _showFindBar(selectedEntry); } /** * @private * Search within the file/subtree defined by the sidebar selection */ function _showReplaceBarForSubtree() { FindUtils.notifySearchScopeChanged(); var selectedEntry = ProjectManager.getSelectedItem(); _showFindBar(selectedEntry, true); } /** * @private * Close the open search bar, if any. For unit tests. */ function _closeFindBar() { if (_findBar) { _findBar.close(); } } /** * When the search indexing is started, we need to show the indexing status on the find bar if present. */ function _searchIndexingStarted() { if (_findBar && _findBar._options.multifile && FindUtils.isIndexingInProgress()) { _findBar.showIndexingSpinner(); } } /** * Once the indexing has finished, clear the indexing spinner */ function _searchIndexingFinished() { if (_findBar) { _findBar.hideIndexingSpinner(); } } /** * Issues a search if find bar is visible and is multi file search and not instant search */ function _defferedSearch() { if (_findBar && _findBar._options.multifile && !_findBar._options.replace) { _findBar.redoInstantSearch(); } } /** * Schedules a search on search scope/filter changes. Have to schedule as when we listen to this event, the file filters * might not have been updated yet. */ function _searchIfRequired() { if (!FindUtils.isInstantSearchDisabled() && _findBar && _findBar._options.multifile && !_findBar._options.replace) { setTimeout(_defferedSearch, 100); } } /** * @public * Closes the search results panel */ function closeResultsPanel() { _resultsView.close(); _closeFindBar(); } // Initialize items dependent on HTML DOM AppInit.htmlReady(function () { var model = FindInFiles.searchModel; _resultsView = new SearchResultsView(model, "find-in-files-results", "find-in-files.results"); _resultsView .on("replaceBatch", function () { _finishReplaceBatch(model); }) .on("close", function () { FindInFiles.clearSearch(); }) .on("getNextPage", function () { FindInFiles.getNextPageofSearchResults().done(function () { if (FindInFiles.searchModel.hasResults()) { _resultsView.showNextPage(); } }); }) .on("getLastPage", function () { FindInFiles.getAllSearchResults().done(function () { if (FindInFiles.searchModel.hasResults()) { _resultsView.showLastPage(); } }); }); }); // Initialize: register listeners ProjectManager.on("beforeProjectClose", function () { _resultsView.close(); }); // Initialize: command handlers CommandManager.register(Strings.CMD_FIND_IN_FILES, Commands.CMD_FIND_IN_FILES, _showFindBar); CommandManager.register(Strings.CMD_FIND_IN_SUBTREE, Commands.CMD_FIND_IN_SUBTREE, _showFindBarForSubtree); CommandManager.register(Strings.CMD_REPLACE_IN_FILES, Commands.CMD_REPLACE_IN_FILES, _showReplaceBar); CommandManager.register(Strings.CMD_REPLACE_IN_SUBTREE, Commands.CMD_REPLACE_IN_SUBTREE, _showReplaceBarForSubtree); FindUtils.on(FindUtils.SEARCH_INDEXING_STARTED, _searchIndexingStarted); FindUtils.on(FindUtils.SEARCH_INDEXING_FINISHED, _searchIndexingFinished); FindUtils.on(FindUtils.SEARCH_FILE_FILTERS_CHANGED, _searchIfRequired); FindUtils.on(FindUtils.SEARCH_SCOPE_CHANGED, _searchIfRequired); // Public exports exports.searchAndShowResults = searchAndShowResults; exports.searchAndReplaceResults = searchAndReplaceResults; exports.closeResultsPanel = closeResultsPanel; // For unit testing exports._showFindBar = _showFindBar; exports._closeFindBar = _closeFindBar; });