зеркало из https://github.com/mozilla/brackets.git
Adding only FileIndexerManager. Separating out QuickFileOpen
This commit is contained in:
Родитель
915c3a663e
Коммит
bdf4d7b008
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* Copyright 2012 Adobe Systems Incorporated. All Rights Reserved.
|
||||
*/
|
||||
|
||||
/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
|
||||
/*global define: false, $: false, brackets, PathUtils */
|
||||
|
||||
/*
|
||||
* Manages a collection of FileIndexes where each index maintains a list of information about
|
||||
* files that meet the criteria specified by the index. The indexes are created lazily when
|
||||
* they are queried and marked dirty when Brackets becomes active.
|
||||
*
|
||||
* TODO (issue 325 ) - FileIndexer doesn't currently add a file to the index when the user createa
|
||||
* a new file within brackets.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
define(function (require, exports, module) {
|
||||
'use strict';
|
||||
|
||||
var NativeFileSystem = require("NativeFileSystem").NativeFileSystem,
|
||||
PerfUtils = require("PerfUtils"),
|
||||
ProjectManager = require("ProjectManager"),
|
||||
Strings = require("strings");
|
||||
|
||||
/**
|
||||
* All the indexes are stored in this object. The key is the name of the index
|
||||
* and the value is a FileIndex.
|
||||
*/
|
||||
var _indexList = {};
|
||||
|
||||
/**
|
||||
* Tracks whether _indexList should be considered dirty and invalid. Calls that access
|
||||
* any data in _indexList should call syncFileIndex prior to accessing the data.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var _indexListDirty = true;
|
||||
|
||||
/** class FileIndex
|
||||
*
|
||||
* A FileIndex contains an array of fileInfos that meet the criteria specified by
|
||||
* the filterFunction. FileInfo's in the fileInfo array should unique map to one file.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!string} indexname
|
||||
* @param {function({!entry})} filterFunction returns true to indicate the entry
|
||||
* should be included in the index
|
||||
*/
|
||||
function FileIndex(indexName, filterFunction) {
|
||||
this.name = indexName;
|
||||
this.fileInfos = [];
|
||||
this.filterFunction = filterFunction;
|
||||
}
|
||||
|
||||
/** class FileInfo
|
||||
*
|
||||
* Class to hold info about a file that a FileIndex wishes to retain.
|
||||
*
|
||||
* @constructor
|
||||
* @param {!string}
|
||||
*/
|
||||
function FileInfo(entry) {
|
||||
this.name = entry.name;
|
||||
this.fullPath = entry.fullPath;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds a new index to _indexList and marks the list dirty
|
||||
*
|
||||
* A future performance optimization is to only build the new index rather than
|
||||
* marking them all dirty
|
||||
*
|
||||
* @private
|
||||
* @param {!string} indexName must be unque
|
||||
* @param {!function({entry} filterFunction should return true to include an
|
||||
* entry in the index
|
||||
|
||||
*/
|
||||
function _addIndex(indexName, filterFunction) {
|
||||
if (_indexList.hasOwnProperty(indexName)) {
|
||||
throw new Error("Duplicate index name");
|
||||
}
|
||||
if (typeof filterFunction !== "function") {
|
||||
throw new Error("Invalid arguments");
|
||||
}
|
||||
|
||||
_indexList[indexName] = new FileIndex(indexName, filterFunction);
|
||||
|
||||
_indexListDirty = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks the entry against the filterFunction for each index and adds
|
||||
* a fileInfo to the index if the entry meets the criteria. FileInfo's are
|
||||
* shared between indexes.
|
||||
*
|
||||
* @private
|
||||
* @param {!entry} entry to be added to the indexes
|
||||
*/
|
||||
// future use when files are incrementally added
|
||||
//
|
||||
function _addFileToIndexes(entry) {
|
||||
|
||||
// skip invisible files on mac
|
||||
if (brackets.platform === "mac" && entry.name.charAt(0) === ".") {
|
||||
return;
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(entry);
|
||||
//console.log(entry.name);
|
||||
|
||||
$.each(_indexList, function (indexName, index) {
|
||||
if (index.filterFunction(entry)) {
|
||||
index.fileInfos.push(fileInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Error dialog when max files in index is hit
|
||||
*/
|
||||
function _showMaxFilesDialog() {
|
||||
return brackets.showModalDialog(
|
||||
brackets.DIALOG_ID_ERROR,
|
||||
Strings.ERROR_MAX_FILES_TITLE,
|
||||
Strings.ERROR_MAX_FILES
|
||||
);
|
||||
}
|
||||
|
||||
/* Recursively visits all files that are descendent of dirEntry and adds
|
||||
* files files to each index when the file matches the filter critera
|
||||
* @private
|
||||
* @param {!DirectoryEntry} dirEntry
|
||||
* @returns {$.Promise}
|
||||
*/
|
||||
function _scanDirectorySubTree(dirEntry) {
|
||||
if (!dirEntry) {
|
||||
throw new Error("Bad dirEntry passed to _scanDirectorySubTree");
|
||||
}
|
||||
|
||||
// keep track of directories as they are asynchronously read. We know we are done
|
||||
// when dirInProgress becomes empty again.
|
||||
var state = { fileCount: 0,
|
||||
dirInProgress: {}, // directory names that are in progress of being read
|
||||
dirError: {}, // directory names with read errors. key=dir path, value=error
|
||||
maxFilesHit: false // used to show warning dialog only once
|
||||
};
|
||||
|
||||
var deferred = new $.Deferred();
|
||||
|
||||
// inner helper function
|
||||
function _dirScanDone() {
|
||||
var key;
|
||||
for (key in state.dirInProgress) {
|
||||
if (state.dirInProgress.hasOwnProperty(key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function _finishDirScan(dirEntry) {
|
||||
//console.log("finished: " + dirEntry.fullPath);
|
||||
delete state.dirInProgress[dirEntry.fullPath];
|
||||
|
||||
if (_dirScanDone()) {
|
||||
//console.log("dir scan completly done");
|
||||
deferred.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// inner helper function
|
||||
function _scanDirectoryRecurse(dirEntry) {
|
||||
state.dirInProgress[dirEntry.fullPath] = true;
|
||||
//console.log("started dir: " + dirEntry.fullPath);
|
||||
|
||||
dirEntry.createReader().readEntries(
|
||||
// success callback
|
||||
function (entries) {
|
||||
// inspect all children of dirEntry
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isFile) {
|
||||
_addFileToIndexes(entry);
|
||||
state.fileCount++;
|
||||
|
||||
// For now limit the number of files that are indexed. This limit could be increased
|
||||
// if files were indexed in a worker thread so scanning didn't block the UI
|
||||
if (state.fileCount === 10000) {
|
||||
if (!state.maxFilesHit) {
|
||||
state.maxFilesHit = true;
|
||||
_showMaxFilesDialog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (entry.isDirectory) {
|
||||
_scanDirectoryRecurse(entry);
|
||||
}
|
||||
});
|
||||
|
||||
_finishDirScan(dirEntry);
|
||||
},
|
||||
// error callback
|
||||
function (error) {
|
||||
state.dirError[dirEntry.fullPath] = error;
|
||||
_finishDirScan(dirEntry);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_scanDirectoryRecurse(dirEntry);
|
||||
|
||||
return deferred.promise();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// debug
|
||||
function _logFileList(list) {
|
||||
list.forEach(function (fileInfo) {
|
||||
console.log(fileInfo.name);
|
||||
});
|
||||
console.log("length: " + list.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears the fileInfo array for all the indexes in _indexList
|
||||
* @private
|
||||
*/
|
||||
function _clearIndexes() {
|
||||
$.each(_indexList, function (indexName, index) {
|
||||
index.fileInfos = [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Markes all file indexes dirty
|
||||
*/
|
||||
function markDirty() {
|
||||
_indexListDirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by syncFileIndex function to prevent reentrancy
|
||||
* @private
|
||||
*/
|
||||
var _syncFileIndexReentracyGuard = false;
|
||||
|
||||
/**
|
||||
* Clears and rebuilds all of the fileIndexes and sets _indexListDirty to false
|
||||
* @return {$.Promise} resolved when index has been updated
|
||||
*/
|
||||
function syncFileIndex() {
|
||||
if (_syncFileIndexReentracyGuard) {
|
||||
throw new Error("syncFileIndex cannot be called Recursively");
|
||||
}
|
||||
|
||||
_syncFileIndexReentracyGuard = true;
|
||||
|
||||
var rootDir = ProjectManager.getProjectRoot();
|
||||
if (_indexListDirty) {
|
||||
PerfUtils.markStart("FileIndexManager.syncFileIndex(): " + rootDir.fullPath);
|
||||
|
||||
_clearIndexes();
|
||||
|
||||
return _scanDirectorySubTree(rootDir)
|
||||
.done(function () {
|
||||
PerfUtils.addMeasurement("FileIndexManager.syncFileIndex(): " + rootDir.fullPath);
|
||||
_indexListDirty = false;
|
||||
_syncFileIndexReentracyGuard = false;
|
||||
|
||||
//_logFileList(_indexList["all"].fileInfos);
|
||||
//_logFileList(_indexList["css"].fileInfos);
|
||||
});
|
||||
} else {
|
||||
_syncFileIndexReentracyGuard = false;
|
||||
return $.Deferred().resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the FileInfo array for the specified index
|
||||
* @param {!string} indexname
|
||||
* @return {$.Promise} a promise that is resolved with an Array of FileInfo's
|
||||
*/
|
||||
function getFileInfoList(indexName) {
|
||||
var result = new $.Deferred();
|
||||
|
||||
if (!_indexList.hasOwnProperty(indexName)) {
|
||||
throw new Error("indexName not found");
|
||||
}
|
||||
|
||||
syncFileIndex()
|
||||
.done(function () {
|
||||
result.resolve(_indexList[indexName].fileInfos);
|
||||
});
|
||||
|
||||
return result.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the filterFunction on every in the index specified by indexName
|
||||
* and return a a new list of FileInfo's
|
||||
* @param {!string}
|
||||
* @param {function({string})} filterFunction
|
||||
* @return {$.Promise} a promise that is resolved with an Array of FileInfo's
|
||||
*/
|
||||
function getFilteredList(indexName, filterFunction) {
|
||||
var result = new $.Deferred();
|
||||
|
||||
if (!_indexList.hasOwnProperty(indexName)) {
|
||||
throw new Error("indexName not found");
|
||||
}
|
||||
|
||||
syncFileIndex()
|
||||
.done(function () {
|
||||
var resultList = [];
|
||||
getFileInfoList(indexName)
|
||||
.done(function (fileList) {
|
||||
resultList = fileList.filter(function (fileInfo) {
|
||||
return filterFunction(fileInfo.name);
|
||||
});
|
||||
|
||||
result.resolve(resultList);
|
||||
});
|
||||
});
|
||||
|
||||
return result.promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an array of fileInfo's that match the filename parameter
|
||||
* @param {!string} indexName
|
||||
* @param {!filename}
|
||||
* @return {$.Promise} a promise that is resolved with an Array of FileInfo's
|
||||
*/
|
||||
function getFilenameMatches(indexName, filename) {
|
||||
return getFilteredList(indexName, function (item) {
|
||||
return item === filename;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the indexes
|
||||
*/
|
||||
|
||||
_addIndex(
|
||||
"all",
|
||||
function (entry) {
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
_addIndex(
|
||||
"css",
|
||||
function (entry) {
|
||||
var filename = entry.name;
|
||||
return PathUtils.filenameExtension(filename) === ".css";
|
||||
}
|
||||
);
|
||||
|
||||
$(ProjectManager).on("projectRootChanged", function (event, projectRoot) {
|
||||
markDirty();
|
||||
});
|
||||
|
||||
exports.markDirty = markDirty;
|
||||
exports.getFileInfoList = getFileInfoList;
|
||||
exports.getFilenameMatches = getFilenameMatches;
|
||||
|
||||
|
||||
});
|
|
@ -10,9 +10,10 @@
|
|||
* creating and updating the project tree when projects are opened and when changes occur to
|
||||
* the file tree.
|
||||
*
|
||||
* This module dispatches 1 event:
|
||||
* This module dispatches these events:
|
||||
* - initializeComplete -- When the ProjectManager initializes the first
|
||||
* project at application start-up.
|
||||
* - projectRootChanged -- when _projectRoot changes
|
||||
*
|
||||
* These are jQuery events, so to listen for them you do something like this:
|
||||
* $(ProjectManager).on("eventname", handler);
|
||||
|
@ -409,6 +410,9 @@ define(function (require, exports, module) {
|
|||
// Point at a real folder structure on local disk
|
||||
NativeFileSystem.requestNativeFileSystem(rootPath,
|
||||
function (rootEntry) {
|
||||
var projectRootChanged = (!_projectRoot || !rootEntry)
|
||||
|| _projectRoot.fullPath !== rootEntry.fullPath;
|
||||
|
||||
// Success!
|
||||
_projectRoot = rootEntry;
|
||||
|
||||
|
@ -426,6 +430,10 @@ define(function (require, exports, module) {
|
|||
if (isFirstProjectOpen) {
|
||||
$(exports).triggerHandler("initializeComplete", _projectRoot);
|
||||
}
|
||||
|
||||
if (projectRootChanged) {
|
||||
$(exports).triggerHandler("projectRootChanged", _projectRoot);
|
||||
}
|
||||
});
|
||||
resultRenderTree.fail(function () {
|
||||
result.reject();
|
||||
|
|
|
@ -57,6 +57,10 @@ define(function (require, exports, module) {
|
|||
exports.ERROR_CREATING_FILE_TITLE = "Error creating file";
|
||||
exports.ERROR_CREATING_FILE = "An error occurred when trying to create the file \"{0}\". {1}";
|
||||
|
||||
// FileIndexManager error string
|
||||
exports.ERROR_MAX_FILES_TITLE = "Error Indexing Files";
|
||||
exports.ERROR_MAX_FILES = "The maximum number of files have been indexed. Actions that look up files in the index may function incorrectly.";
|
||||
|
||||
exports.SAVE_CLOSE_TITLE = "Save Changes";
|
||||
exports.SAVE_CLOSE_MESSAGE = "Do you want to save the changes you made in the document \"{0}\"?";
|
||||
exports.SAVE_CLOSE_MULTI_MESSAGE = "Do you want to save your changes to the following files?";
|
||||
|
|
|
@ -24,6 +24,7 @@ define(function (require, exports, module) {
|
|||
require("spec/ProjectManager-test.js");
|
||||
require("spec/WorkingSetView-test.js");
|
||||
require("spec/KeyMap-test.js");
|
||||
require("spec/FileIndexManager-test.js");
|
||||
require("spec/CodeHintUtils-test.js");
|
||||
require("spec/CSSManager-test.js");
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче