src/search/QuickOpen.js
/*
* Copyright (c) 2012 - 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.
*
*/
/*unittests: QuickOpen*/
/*
* Displays a search bar where the user can quickly navigate to a different file by searching file names in
* the project, and/or jump to a line number. Providers can plug in to offer additional search modes.
*/
define(function (require, exports, module) {
"use strict";
var DocumentManager = require("document/DocumentManager"),
EditorManager = require("editor/EditorManager"),
MainViewManager = require("view/MainViewManager"),
MainViewFactory = require("view/MainViewFactory"),
CommandManager = require("command/CommandManager"),
Strings = require("strings"),
StringUtils = require("utils/StringUtils"),
Commands = require("command/Commands"),
ProjectManager = require("project/ProjectManager"),
LanguageManager = require("language/LanguageManager"),
ModalBar = require("widgets/ModalBar").ModalBar,
QuickSearchField = require("search/QuickSearchField").QuickSearchField,
StringMatch = require("utils/StringMatch"),
ProviderRegistrationHandler = require("features/PriorityBasedRegistration").RegistrationHandler;
var _providerRegistrationHandler = new ProviderRegistrationHandler(),
_registerQuickOpenProvider = _providerRegistrationHandler.registerProvider.bind(_providerRegistrationHandler);
var SymbolKind = {
"1": "File",
"2": "Module",
"3": "Namespace",
"4": "Package",
"5": "Class",
"6": "Method",
"7": "Property",
"8": "Field",
"9": "Constructor",
"10": "Enum",
"11": "Interface",
"12": "Function",
"13": "Variable",
"14": "Constant",
"15": "String",
"16": "Number",
"17": "Boolean",
"18": "Array",
"19": "Object",
"20": "Key",
"21": "Null",
"22": "EnumMember",
"23": "Struct",
"24": "Event",
"25": "Operator",
"26": "TypeParameter"
};
/**
* The regular expression to check the cursor position
* @const {RegExp}
*/
var CURSOR_POS_EXP = new RegExp(":([^,]+)?(,(.+)?)?");
/**
* Current plugin
* @type {QuickOpenPlugin}
*/
var currentPlugin = null;
/** @type {Array.<File>} */
var fileList;
/** @type {$.Promise} */
var fileListPromise;
/**
* The currently open (or last open) QuickNavigateDialog
* @type {?QuickNavigateDialog}
*/
var _curDialog;
/**
* Helper function to get the plugins based on the type of the current document.
* @private
* @returns {Array} Returns the plugings based on the languageId of the current document.
*/
function _getPluginsForCurrentContext() {
var curDoc = DocumentManager.getCurrentDocument();
if (curDoc) {
var languageId = curDoc.getLanguage().getId();
return _providerRegistrationHandler.getProvidersForLanguageId(languageId);
}
return _providerRegistrationHandler.getProvidersForLanguageId(); //plugins registered for all
}
/**
* Defines API for new QuickOpen plug-ins
*/
function QuickOpenPlugin(name, languageIds, done, search, match, itemFocus, itemSelect, resultsFormatter, matcherOptions, label) {
this.name = name;
this.languageIds = languageIds;
this.done = done;
this.search = search;
this.match = match;
this.itemFocus = itemFocus;
this.itemSelect = itemSelect;
this.resultsFormatter = resultsFormatter;
this.matcherOptions = matcherOptions;
this.label = label;
}
/**
* Creates and registers a new QuickOpenPlugin
*
* @param { name: string,
* languageIds: !Array.<string>,
* done: ?function(),
* search: function(string, !StringMatch.StringMatcher):(!Array.<SearchResult|string>|$.Promise),
* match: function(string):boolean,
* itemFocus: ?function(?SearchResult|string, string, boolean),
* itemSelect: function(?SearchResult|string, string),
* resultsFormatter: ?function(SearchResult|string, string):string,
* matcherOptions: ?Object,
* label: ?string
* } pluginDef
*
* Parameter Documentation:
*
* name - plug-in name, **must be unique**
* languageIds - language Ids array. Example: ["javascript", "css", "html"]. To allow any language, pass []. Required.
* done - called when quick open is complete. Plug-in should clear its internal state. Optional.
* search - takes a query string and a StringMatcher (the use of which is optional but can speed up your searches) and returns
* an array of strings or result objects that match the query; or a Promise that resolves to such an array. Required.
* match - takes a query string and returns true if this plug-in wants to provide
* results for this query. Required.
* itemFocus - performs an action when a result has been highlighted (via arrow keys, or by becoming top of the list).
* Passed the highlighted search result item (as returned by search()), the current query string, and a flag that is true
* if the item was highlighted explicitly (arrow keys), not implicitly (at top of list after last search()). Optional.
* itemSelect - performs an action when a result is chosen.
* Passed the highlighted search result item (as returned by search()), and the current query string. Required.
* resultsFormatter - takes a query string and an item string and returns
* a <LI> item to insert into the displayed search results. Optional.
* matcherOptions - options to pass along to the StringMatcher (see StringMatch.StringMatcher
* for available options). Optional.
* label - if provided, the label to show before the query field. Optional.
*
* If itemFocus() makes changes to the current document or cursor/scroll position and then the user
* cancels Quick Open (via Esc), those changes are automatically reverted.
*/
function addQuickOpenPlugin(pluginDef) {
var quickOpenProvider = new QuickOpenPlugin(
pluginDef.name,
pluginDef.languageIds,
pluginDef.done,
pluginDef.search,
pluginDef.match,
pluginDef.itemFocus,
pluginDef.itemSelect,
pluginDef.resultsFormatter,
pluginDef.matcherOptions,
pluginDef.label
),
providerLanguageIds = pluginDef.languageIds.length ? pluginDef.languageIds : ["all"],
providerPriority = pluginDef.priority || 0;
_registerQuickOpenProvider(quickOpenProvider, providerLanguageIds, providerPriority);
}
/**
* QuickNavigateDialog class
* @constructor
*/
function QuickNavigateDialog() {
this.$searchField = undefined; // defined when showDialog() is called
// ModalBar event handlers & callbacks
this._handleCloseBar = this._handleCloseBar.bind(this);
// QuickSearchField callbacks
this._handleItemSelect = this._handleItemSelect.bind(this);
this._handleItemHighlight = this._handleItemHighlight.bind(this);
this._filterCallback = this._filterCallback.bind(this);
this._resultsFormatterCallback = this._resultsFormatterCallback.bind(this);
// StringMatchers that cache in-progress query data.
this._filenameMatcher = new StringMatch.StringMatcher({
segmentedSearch: true
});
this._matchers = {};
}
/**
* True if the search bar is currently open. Note that this is set to false immediately
* when the bar starts closing; it doesn't wait for the ModalBar animation to finish.
* @type {boolean}
*/
QuickNavigateDialog.prototype.isOpen = false;
/**
* @private
* Handles caching of filename search information for the lifetime of a
* QuickNavigateDialog (a single search until the dialog is dismissed)
*
* @type {StringMatch.StringMatcher}
*/
QuickNavigateDialog.prototype._filenameMatcher = null;
/**
* @private
* StringMatcher caches for each QuickOpen plugin that keep track of search
* information for the lifetime of a QuickNavigateDialog (a single search
* until the dialog is dismissed)
*
* @type {Object.<string, StringMatch.StringMatcher>}
*/
QuickNavigateDialog.prototype._matchers = null;
/**
* @private
* If the dialog is closing, this will contain a deferred that is resolved
* when it's done closing.
* @type {$.Deferred}
*/
QuickNavigateDialog.prototype._closeDeferred = null;
/**
* @private
* Remembers the current document that was displayed when showDialog() was called.
* @type {?string} full path
*/
QuickNavigateDialog.prototype._origDocPath = null;
/**
* @private
* Remembers the selection state in origDocPath that was present when showDialog() was called. Focusing on an
* item can change the selection; we restore this original selection if the user presses Escape. Null if
* no document was open when Quick Open was invoked.
* @type {?Array.<{{start:{line:number, ch:number}, end:{line:number, ch:number}, primary:boolean, reversed:boolean}}>}
*/
QuickNavigateDialog.prototype._origSelections = null;
/**
* @private
* Remembers the scroll position in origDocPath when showDialog() was called (see origSelection above).
* @type {?{x:number, y:number}}
*/
QuickNavigateDialog.prototype._origScrollPos = null;
function _filenameFromPath(path, includeExtension) {
var end;
if (includeExtension) {
end = path.length;
} else {
end = path.lastIndexOf(".");
if (end === -1) {
end = path.length;
}
}
return path.slice(path.lastIndexOf("/") + 1, end);
}
/**
* Attempts to extract a line number from the query where the line number
* is followed by a colon. Callers should explicitly test result with isNaN()
*
* @param {string} query string to extract line number from
* @return {{query: string, local: boolean, line: number, ch: number}} An object with
* the extracted line and column numbers, and two additional fields: query with the original position
* string and local indicating if the cursor position should be applied to the current file.
* Or null if the query is invalid
*/
function extractCursorPos(query) {
var regInfo = query.match(CURSOR_POS_EXP);
if (query.length <= 1 || !regInfo ||
(regInfo[1] && isNaN(regInfo[1])) ||
(regInfo[3] && isNaN(regInfo[3]))) {
return null;
}
return {
query: regInfo[0],
local: query[0] === ":",
line: regInfo[1] - 1 || 0,
ch: regInfo[3] - 1 || 0
};
}
/**
* Navigates to the appropriate file and file location given the selected item
* and closes the dialog.
*
* Note, if selectedItem is null quick search should inspect $searchField for text
* that may have not matched anything in the list, but may have information
* for carrying out an action (e.g. go to line).
*/
QuickNavigateDialog.prototype._handleItemSelect = function (selectedItem, query) {
var doClose = true,
self = this;
// Delegate to current plugin
if (currentPlugin) {
currentPlugin.itemSelect(selectedItem, query);
} else {
// Extract line/col number, if any
var cursorPos = extractCursorPos(query);
// Navigate to file and line number
var fullPath = selectedItem && selectedItem.fullPath;
if (fullPath) {
// We need to fix up the current editor's scroll pos before switching to the next one. But if
// we run the full close() now, ModalBar's animate-out won't be smooth (gets starved of cycles
// during creation of the new editor). So we call prepareClose() to do *only* the scroll pos
// fixup, and let the full close() be triggered later when the new editor takes focus.
doClose = false;
this.modalBar.prepareClose();
CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, {fullPath: fullPath})
.done(function () {
if (cursorPos) {
var editor = EditorManager.getCurrentFullEditor();
editor.setCursorPos(cursorPos.line, cursorPos.ch, true);
}
})
.always(function () {
// Ensure we finish closing even if file failed to open, or file was already current (in
// either case, we may not get a blur to trigger ModalBar to auto-close)
self.close();
});
} else if (cursorPos) {
EditorManager.getCurrentFullEditor().setCursorPos(cursorPos.line, cursorPos.ch, true);
}
}
if (doClose) {
this.close();
MainViewManager.focusActivePane();
}
};
/**
* Opens the file specified by selected item if there is no current plug-in, otherwise defers handling
* to the currentPlugin
*/
QuickNavigateDialog.prototype._handleItemHighlight = function (selectedItem, query, explicit) {
if (currentPlugin && currentPlugin.itemFocus) {
currentPlugin.itemFocus(selectedItem, query, explicit);
}
};
/**
* Closes the search bar; if search bar is already closing, returns the Promise that is tracking the
* existing close activity.
* @return {$.Promise} Resolved when the search bar is entirely closed.
*/
QuickNavigateDialog.prototype.close = function () {
if (!this.isOpen) {
return this.closePromise;
}
this.modalBar.close(); // triggers _handleCloseBar(), setting closePromise
return this.closePromise;
};
QuickNavigateDialog.prototype._handleCloseBar = function (event, reason, modalBarClosePromise) {
console.assert(!this.closePromise);
this.closePromise = modalBarClosePromise;
this.isOpen = false;
var i,
plugins = _getPluginsForCurrentContext();
for (i = 0; i < plugins.length; i++) {
var plugin = plugins[i].provider;
if (plugin.done) {
plugin.done();
}
}
// Close popup & ensure we ignore any still-pending result promises
this.searchField.destroy();
// Restore original selection / scroll pos if closed via Escape
if (reason === ModalBar.CLOSE_ESCAPE) {
// We can reset the scroll position synchronously on ModalBar's "close" event (before close animation
// completes) since ModalBar has already resized the editor and done its own scroll adjustment before
// this event fired - so anything we set here will override the pos that was (re)set by ModalBar.
var editor = EditorManager.getCurrentFullEditor();
if (editor && this._origSelections) {
editor.setSelections(this._origSelections);
}
if (editor && this._origScrollPos) {
editor.setScrollPos(this._origScrollPos.x, this._origScrollPos.y);
}
}
};
function _doSearchFileList(query, matcher) {
// Strip off line/col number suffix so it doesn't interfere with filename search
var cursorPos = extractCursorPos(query);
if (cursorPos && !cursorPos.local && cursorPos.query !== "") {
query = query.replace(cursorPos.query, "");
}
// First pass: filter based on search string; convert to SearchResults containing extra info
// for sorting & display
var filteredList = $.map(fileList, function (fileInfo) {
// Is it a match at all?
// match query against the full path (with gaps between query characters allowed)
var searchResult;
searchResult = matcher.match(ProjectManager.makeProjectRelativeIfPossible(fileInfo.fullPath), query);
if (searchResult) {
searchResult.label = fileInfo.name;
searchResult.fullPath = fileInfo.fullPath;
searchResult.filenameWithoutExtension = _filenameFromPath(fileInfo.name, false);
}
return searchResult;
});
// Sort by "match goodness" tier first, then within each tier sort alphabetically - first by filename
// sans extension, (so that "abc.js" comes before "abc-d.js"), then by filename, and finally (for
// identically-named files) by full path
StringMatch.multiFieldSort(filteredList, { matchGoodness: 0, filenameWithoutExtension: 1, label: 2, fullPath: 3 });
return filteredList;
}
function searchFileList(query, matcher) {
// The file index may still be loading asynchronously - if so, can't return a result yet
if (!fileList) {
var asyncResult = new $.Deferred();
fileListPromise.done(function () {
// Synchronously re-run the search call and resolve with its results
asyncResult.resolve(_doSearchFileList(query, matcher));
});
return asyncResult.promise();
} else {
return _doSearchFileList(query, matcher);
}
}
/**
* Handles changes to the current query in the search field.
* @param {string} query The new query.
* @return {$.Promise|Array.<*>|{error:?string}} The filtered list of results, an error object, or a Promise that
* yields one of those
*/
QuickNavigateDialog.prototype._filterCallback = function (query) {
// Re-evaluate which plugin is active each time query string changes
currentPlugin = null;
// "Go to line" mode is special-cased
var cursorPos = extractCursorPos(query);
if (cursorPos && cursorPos.local) {
// Bare Go to Line (no filename search) - can validate & jump to it now, without waiting for Enter/commit
var editor = EditorManager.getCurrentFullEditor();
// Validate (could just use 0 and lineCount() here, but in future might want this to work for inline editors too)
if (cursorPos && editor && cursorPos.line >= editor.getFirstVisibleLine() && cursorPos.line <= editor.getLastVisibleLine()) {
var from = {line: cursorPos.line, ch: cursorPos.ch},
to = {line: cursorPos.line};
EditorManager.getCurrentFullEditor().setSelection(from, to, true);
return { error: null }; // no error even though no results listed
} else {
return []; // red error highlight: line number out of range, or no editor open
}
}
if (query === ":") { // treat blank ":" query as valid, but no-op
return { error: null };
}
var i,
plugins = _getPluginsForCurrentContext();
for (i = 0; i < plugins.length; i++) {
var plugin = plugins[i].provider;
if(plugin.match(query)) {
currentPlugin = plugin;
// Look up the StringMatcher for this plugin.
var matcher = this._matchers[currentPlugin.name];
if (!matcher) {
matcher = new StringMatch.StringMatcher(plugin.matcherOptions);
this._matchers[currentPlugin.name] = matcher;
}
this._updateDialogLabel(plugin, query);
return plugin.search(query, matcher);
}
}
// Reflect current search mode in UI
this._updateDialogLabel(null, query);
// No matching plugin: use default file search mode
return searchFileList(query, this._filenameMatcher);
};
/**
* Formats item's label as properly escaped HTML text, highlighting sections that match 'query'.
* If item is a SearchResult generated by stringMatch(), uses its metadata about which string ranges
* matched; else formats the label with no highlighting.
* @param {!string|SearchResult} item
* @param {?string} matchClass CSS class for highlighting matched text
* @param {?function(boolean, string):string} rangeFilter
* @return {!string} bolded, HTML-escaped result
*/
function highlightMatch(item, matchClass, rangeFilter) {
var label = item.label || item;
matchClass = matchClass || "quicksearch-namematch";
var stringRanges = item.stringRanges;
if (!stringRanges) {
// If result didn't come from stringMatch(), highlight nothing
stringRanges = [{
text: label,
matched: false,
includesLastSegment: true
}];
}
var displayName = "";
if (item.scoreDebug) {
var sd = item.scoreDebug;
displayName += '<span title="sp:' + sd.special + ', m:' + sd.match +
', ls:' + sd.lastSegment + ', b:' + sd.beginning +
', ld:' + sd.lengthDeduction + ', c:' + sd.consecutive + ', nsos: ' +
sd.notStartingOnSpecial + ', upper: ' + sd.upper + '">(' + item.matchGoodness + ') </span>';
}
// Put the path pieces together, highlighting the matched parts
stringRanges.forEach(function (range) {
if (range.matched) {
displayName += "<span class='" + matchClass + "'>";
}
var rangeText = rangeFilter ? rangeFilter(range.includesLastSegment, range.text) : range.text;
displayName += StringUtils.breakableUrl(rangeText);
if (range.matched) {
displayName += "</span>";
}
});
return displayName;
}
function defaultResultsFormatter(item, query) {
query = query.slice(query.indexOf("@") + 1, query.length);
var displayName = highlightMatch(item);
return "<li>" + displayName + "</li>";
}
function _filenameResultsFormatter(item, query) {
// For main label, we just want filename: drop most of the string
function fileNameFilter(includesLastSegment, rangeText) {
if (includesLastSegment) {
var rightmostSlash = rangeText.lastIndexOf('/');
return rangeText.substring(rightmostSlash + 1); // safe even if rightmostSlash is -1
} else {
return "";
}
}
var displayName = highlightMatch(item, null, fileNameFilter);
var displayPath = highlightMatch(item, "quicksearch-pathmatch");
return "<li>" + displayName + "<br /><span class='quick-open-path'>" + displayPath + "</span></li>";
}
/**
* Formats the entry for the given item to be displayed in the dropdown.
* @param {Object} item The item to be displayed.
* @return {string} The HTML to be displayed.
*/
QuickNavigateDialog.prototype._resultsFormatterCallback = function (item, query) {
var formatter;
if (currentPlugin) {
// Plugins use their own formatter or the default formatter
formatter = currentPlugin.resultsFormatter || defaultResultsFormatter;
} else {
// No plugin: default file search mode uses a special formatter
formatter = _filenameResultsFormatter;
}
return formatter(item, query);
};
/**
* Sets the value in the search field, updating the current mode and label based on the
* given prefix.
* @param {string} prefix The prefix that determines which mode we're in: must be empty (for file search),
* "@" for go to definition, or ":" for go to line.
* @param {string} initialString The query string to search for (without the prefix).
*/
QuickNavigateDialog.prototype.setSearchFieldValue = function (prefix, initialString) {
prefix = prefix || "";
initialString = initialString || "";
initialString = prefix + initialString;
this.searchField.setText(initialString);
// Select just the text after the prefix
this.$searchField[0].setSelectionRange(prefix.length, initialString.length);
};
/**
* Sets the dialog label based on the current plugin (if any) and the current query.
* @param {Object} plugin The current Quick Open plugin, or none if there is none.
* @param {string} query The user's current query.
*/
QuickNavigateDialog.prototype._updateDialogLabel = function (plugin, query) {
var dialogLabel = "";
if (plugin && plugin.label) {
dialogLabel = plugin.label;
} else {
var prefix = (query.length > 0 ? query.charAt(0) : "");
// Update the dialog label based on the current prefix.
switch (prefix) {
case ":":
dialogLabel = Strings.CMD_GOTO_LINE + "\u2026";
break;
case "@":
dialogLabel = Strings.CMD_GOTO_DEFINITION + "\u2026";
break;
case "#":
dialogLabel = Strings.CMD_GOTO_DEFINITION_PROJECT + "\u2026";
break;
default:
dialogLabel = "";
break;
}
}
$(".find-dialog-label", this.dialog).text(dialogLabel);
};
/**
* Shows the search dialog and initializes the auto suggestion list with filenames from the current project
*/
QuickNavigateDialog.prototype.showDialog = function (prefix, initialString) {
if (this.isOpen) {
return;
}
this.isOpen = true;
// Record current document & cursor pos so we can restore it if search is canceled
// We record scroll pos *before* modal bar is opened since we're going to restore it *after* it's closed
var curDoc = DocumentManager.getCurrentDocument();
this._origDocPath = curDoc ? curDoc.file.fullPath : null;
if (curDoc) {
this._origSelections = EditorManager.getCurrentFullEditor().getSelections();
this._origScrollPos = EditorManager.getCurrentFullEditor().getScrollPos();
} else {
this._origSelections = null;
this._origScrollPos = null;
}
// Show the search bar
var searchBarHTML = "<div align='right'><input type='text' autocomplete='off' id='quickOpenSearch' placeholder='" + Strings.CMD_QUICK_OPEN + "\u2026' style='width: 30em'><span class='find-dialog-label'></span></div>";
this.modalBar = new ModalBar(searchBarHTML, true);
this.modalBar.on("close", this._handleCloseBar);
this.$searchField = $("input#quickOpenSearch");
this.searchField = new QuickSearchField(this.$searchField, {
maxResults: 20,
firstHighlightIndex: 0,
verticalAdjust: this.modalBar.getRoot().outerHeight(),
resultProvider: this._filterCallback,
formatter: this._resultsFormatterCallback,
onCommit: this._handleItemSelect,
onHighlight: this._handleItemHighlight
});
// Return files that are non-binary, or binary files that have a custom viewer
function _filter(file) {
return !LanguageManager.getLanguageForPath(file.fullPath).isBinary() ||
MainViewFactory.findSuitableFactoryForPath(file.fullPath);
}
// Start prefetching the file list, which will be needed the first time the user enters an un-prefixed query. If file index
// caches are out of date, this list might take some time to asynchronously build, forcing searchFileList() to wait. In the
// meantime we show our old, stale fileList (unless the user has switched projects and we cleared it).
fileListPromise = ProjectManager.getAllFiles(_filter, true)
.done(function (files) {
fileList = files;
fileListPromise = null;
this._filenameMatcher.reset();
}.bind(this));
// Prepopulated query
this.$searchField.focus();
this.setSearchFieldValue(prefix, initialString);
};
function getCurrentEditorSelectedText() {
var currentEditor = EditorManager.getActiveEditor();
return (currentEditor && currentEditor.getSelectedText()) || "";
}
/**
* Opens the Quick Open bar prepopulated with the given prefix (to select a mode) and optionally
* with the given query text too. Updates text field contents if Quick Open already open.
* @param {?string} prefix
* @param {?string} initialString
*/
function beginSearch(prefix, initialString) {
function createDialog() {
_curDialog = new QuickNavigateDialog();
_curDialog.showDialog(prefix, initialString);
}
if (_curDialog) {
if (_curDialog.isOpen) {
// Just start a search using the existing dialog.
_curDialog.setSearchFieldValue(prefix, initialString);
} else {
// The dialog is already closing. Wait till it's done closing,
// then open a new dialog. (Calling close() again returns the
// promise for the deferred that was already kicked off when it
// started closing.)
_curDialog.close().done(createDialog);
}
} else {
createDialog();
}
}
function doFileSearch() {
beginSearch("", getCurrentEditorSelectedText());
}
function doGotoLine() {
// TODO: Brackets doesn't support disabled menu items right now, when it does goto line and
// goto definition should be disabled when there is not a current document
if (DocumentManager.getCurrentDocument()) {
beginSearch(":", "");
}
}
function doDefinitionSearch() {
if (DocumentManager.getCurrentDocument()) {
beginSearch("@", getCurrentEditorSelectedText());
}
}
function doDefinitionSearchInProject() {
if (DocumentManager.getCurrentDocument()) {
beginSearch("#", getCurrentEditorSelectedText());
}
}
function _canHandleTrigger(trigger, plugins) {
var retval = false;
plugins.some(function (plugin, index) {
var provider = plugin.provider;
if (provider.match(trigger)) {
retval = true;
return true;
}
});
return retval;
}
function _setMenuItemStateForLanguage(languageId) {
var plugins = _providerRegistrationHandler.getProvidersForLanguageId(languageId);
if (_canHandleTrigger("@", plugins)) {
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(true);
} else {
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(false);
}
if (_canHandleTrigger("#", plugins)) {
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(true);
} else {
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(false);
}
}
// Listen for a change of project to invalidate our file list
ProjectManager.on("projectOpen", function () {
fileList = null;
});
MainViewManager.on("currentFileChange", function (event, newFile, newPaneId, oldFile, oldPaneId) {
if (!newFile) {
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION).setEnabled(false);
CommandManager.get(Commands.NAVIGATE_GOTO_DEFINITION_PROJECT).setEnabled(false);
return;
}
var newFilePath = newFile.fullPath,
newLanguageId = LanguageManager.getLanguageForPath(newFilePath).getId();
_setMenuItemStateForLanguage(newLanguageId);
DocumentManager.getDocumentForPath(newFilePath)
.done(function (newDoc) {
newDoc.on("languageChanged.quickFindDefinition", function () {
var changedLanguageId = LanguageManager.getLanguageForPath(newDoc.file.fullPath).getId();
_setMenuItemStateForLanguage(changedLanguageId);
});
}).fail(function (err) {
console.error(err);
});
if (!oldFile) {
return;
}
var oldFilePath = oldFile.fullPath;
DocumentManager.getDocumentForPath(oldFilePath)
.done(function (oldDoc) {
oldDoc.off("languageChanged.quickFindDefinition");
}).fail(function (err) {
console.error(err);
});
});
CommandManager.register(Strings.CMD_QUICK_OPEN, Commands.NAVIGATE_QUICK_OPEN, doFileSearch);
CommandManager.register(Strings.CMD_GOTO_DEFINITION, Commands.NAVIGATE_GOTO_DEFINITION, doDefinitionSearch);
CommandManager.register(Strings.CMD_GOTO_DEFINITION_PROJECT, Commands.NAVIGATE_GOTO_DEFINITION_PROJECT, doDefinitionSearchInProject);
CommandManager.register(Strings.CMD_GOTO_LINE, Commands.NAVIGATE_GOTO_LINE, doGotoLine);
exports.beginSearch = beginSearch;
exports.addQuickOpenPlugin = addQuickOpenPlugin;
exports.highlightMatch = highlightMatch;
exports.SymbolKind = SymbolKind;
// Convenience exports for functions that most QuickOpen plugins would need.
exports.stringMatch = StringMatch.stringMatch;
exports.SearchResult = StringMatch.SearchResult;
exports.basicMatchSort = StringMatch.basicMatchSort;
exports.multiFieldSort = StringMatch.multiFieldSort;
});