From 3ca4f26b58ccf1577addd62b528a248fcb8a18be Mon Sep 17 00:00:00 2001 From: Martin Zagora Date: Thu, 12 Sep 2013 14:02:39 +1000 Subject: [PATCH] Expose code inspection providers for the extensions. First go at inspectFile method. Removed Async.withTimeout from inspectFile() Separated getProvider and inspectFile logic, added usage of DocumentManager.getOpenDocumentForPath to inspectFile Modified run() to use new methods. Moved $problemsPanelTable event handlers from run() to htmlReady() for optimization. Fix the case when getCurrentDocument returns null. Check if current document hasn't changed while inspectFile was running remove async handling of provider.scanFile Refactored according to comments. Made provider response nullable again, removed the warning. Refactored getProvider to getProviderForPath and updated docs. Remove getProvider from exports. --- src/language/CodeInspection.js | 165 +++++++++++++++++++++++---------- 1 file changed, 116 insertions(+), 49 deletions(-) diff --git a/src/language/CodeInspection.js b/src/language/CodeInspection.js index cf884c494f9..9fa2181a264 100644 --- a/src/language/CodeInspection.js +++ b/src/language/CodeInspection.js @@ -45,6 +45,7 @@ define(function (require, exports, module) { CommandManager = require("command/CommandManager"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), + FileUtils = require("file/FileUtils"), LanguageManager = require("language/LanguageManager"), PreferencesManager = require("preferences/PreferencesManager"), PerfUtils = require("utils/PerfUtils"), @@ -72,7 +73,6 @@ define(function (require, exports, module) { META: "problem_type_meta" }; - /** * @private * @type {PreferenceStorage} @@ -100,6 +100,12 @@ define(function (require, exports, module) { */ var $problemsPanel; + /** + * @private + * @type {$.Element} + */ + var $problemsPanelTable; + /** * @private * @type {boolean} @@ -145,7 +151,69 @@ define(function (require, exports, module) { } _providers[languageId] = provider; } - + + /** + * Returns a provider for given file path, if one is available. + * Decision is made depending on the file extension. + * + * @param {!string} filePath + * @return ?{{name:string, scanFile:function(string, string):?{!errors:Array, aborted:boolean}} provider + */ + function getProviderForPath(filePath) { + return _providers[LanguageManager.getLanguageForPath(filePath).getId()]; + } + + /** + * Runs a file inspection over passed file, specifying a provider is optional. + * This method doesn't update the Brackets UI, just provides inspection results. + * These results will reflect any unsaved changes present in the file that is currently opened. + * + * @param {!FileEntry} fileEntry File that will be inspected for errors. + * @param ?{{name:string, scanFile:function(string, string):?{!errors:Array, aborted:boolean}} provider + * @return {$.Promise} a jQuery promise that will be resolved with ?{!errors:Array, aborted:boolean} + */ + function inspectFile(fileEntry, provider) { + var response = new $.Deferred(); + provider = provider || getProviderForPath(fileEntry.fullPath); + + if (!provider) { + response.resolve(null); + return response.promise(); + } + + var doc = DocumentManager.getOpenDocumentForPath(fileEntry.fullPath), + fileTextPromise; + + if (doc) { + fileTextPromise = new $.Deferred().resolve(doc.getText()); + } else { + fileTextPromise = FileUtils.readAsText(fileEntry); + } + + fileTextPromise + .done(function (fileText) { + var result, + perfTimerInspector = PerfUtils.markStart("CodeInspection '" + provider.name + "':\t" + fileEntry.fullPath); + + try { + result = provider.scanFile(fileText, fileEntry.fullPath); + } catch (err) { + console.error("[CodeInspection] Provider " + provider.name + " threw an error: " + err); + response.reject(err); + return; + } + + PerfUtils.addMeasurement(perfTimerInspector); + response.resolve(result); + }) + .fail(function (err) { + console.error("[CodeInspection] Could not read file for inspection: " + fileEntry.fullPath); + response.reject(err); + }); + + return response.promise(); + } + /** * Run inspector applicable to current document. Updates status bar indicator and refreshes error list in * bottom panel. @@ -159,30 +227,31 @@ define(function (require, exports, module) { return; } - var currentDoc = DocumentManager.getCurrentDocument(); - - var perfTimerDOM, - perfTimerInspector; - - var language = currentDoc ? LanguageManager.getLanguageForPath(currentDoc.file.fullPath) : ""; - var languageId = language && language.getId(); - var provider = language && _providers[languageId]; + var currentDoc = DocumentManager.getCurrentDocument(), + provider = currentDoc && getProviderForPath(currentDoc.file.fullPath); if (provider) { - perfTimerInspector = PerfUtils.markStart("CodeInspection '" + languageId + "':\t" + currentDoc.file.fullPath); - - var result = provider.scanFile(currentDoc.getText(), currentDoc.file.fullPath); - _lastResult = result; - - PerfUtils.addMeasurement(perfTimerInspector); - perfTimerDOM = PerfUtils.markStart("ProblemsPanel render:\t" + currentDoc.file.fullPath); - - if (result && result.errors.length) { + inspectFile(currentDoc.file, provider).then(function (result) { + // check if current document wasn't changed while inspectFile was running + if (currentDoc !== DocumentManager.getCurrentDocument()) { + return; + } + + _lastResult = result; + + if (!result || !result.errors.length) { + Resizer.hide($problemsPanel); + StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-valid", StringUtils.format(Strings.NO_ERRORS, provider.name)); + setGotoEnabled(false); + return; + } + + var perfTimerDOM = PerfUtils.markStart("ProblemsPanel render:\t" + currentDoc.file.fullPath); + // Augment error objects with additional fields needed by Mustache template var numProblems = 0; result.errors.forEach(function (error) { error.friendlyLine = error.pos.line + 1; - error.codeSnippet = currentDoc.getLine(error.pos.line); error.codeSnippet = error.codeSnippet.substr(0, Math.min(175, error.codeSnippet.length)); // limit snippet width @@ -193,27 +262,11 @@ define(function (require, exports, module) { // Update results table var html = Mustache.render(ResultsTemplate, {reportList: result.errors}); - var $selectedRow; - $problemsPanel.find(".table-container") + $problemsPanelTable .empty() .append(html) - .scrollTop(0) // otherwise scroll pos from previous contents is remembered - .on("click", "tr", function (e) { - if ($selectedRow) { - $selectedRow.removeClass("selected"); - } - - $selectedRow = $(e.currentTarget); - $selectedRow.addClass("selected"); - var lineTd = $selectedRow.find(".line-number"); - var line = parseInt(lineTd.text(), 10) - 1; // convert friendlyLine back to pos.line - var character = lineTd.data("character"); - - var editor = EditorManager.getCurrentFullEditor(); - editor.setCursorPos(line, character, true); - EditorManager.focusEditor(); - }); + .scrollTop(0); // otherwise scroll pos from previous contents is remembered $problemsPanel.find(".title").text(StringUtils.format(Strings.ERRORS_PANEL_TITLE, provider.name)); if (!_collapsed) { @@ -231,19 +284,14 @@ define(function (require, exports, module) { StringUtils.format(Strings.MULTIPLE_ERRORS, provider.name, numProblems)); } setGotoEnabled(true); - - } else { - Resizer.hide($problemsPanel); - StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-valid", StringUtils.format(Strings.NO_ERRORS, provider.name)); - setGotoEnabled(false); - } - - PerfUtils.addMeasurement(perfTimerDOM); + PerfUtils.addMeasurement(perfTimerDOM); + }); } else { // No provider for current file _lastResult = null; Resizer.hide($problemsPanel); + var language = currentDoc && LanguageManager.getLanguageForPath(currentDoc.file.fullPath); if (language) { StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-disabled", StringUtils.format(Strings.NO_LINT_AVAILABLE, language.getName())); } else { @@ -339,6 +387,24 @@ define(function (require, exports, module) { var resultsPanel = PanelManager.createBottomPanel("errors", $(panelHtml), 100); $problemsPanel = $("#problems-panel"); + var $selectedRow; + $problemsPanelTable = $problemsPanel.find(".table-container") + .on("click", "tr", function (e) { + if ($selectedRow) { + $selectedRow.removeClass("selected"); + } + + $selectedRow = $(e.currentTarget); + $selectedRow.addClass("selected"); + var lineTd = $selectedRow.find(".line-number"); + var line = parseInt(lineTd.text(), 10) - 1; // convert friendlyLine back to pos.line + var character = lineTd.data("character"); + + var editor = EditorManager.getCurrentFullEditor(); + editor.setCursorPos(line, character, true); + EditorManager.focusEditor(); + }); + $("#problems-panel .close").click(function () { toggleCollapsed(true); }); @@ -363,7 +429,8 @@ define(function (require, exports, module) { // Public API - exports.register = register; - exports.Type = Type; - exports.toggleEnabled = toggleEnabled; + exports.register = register; + exports.Type = Type; + exports.toggleEnabled = toggleEnabled; + exports.inspectFile = inspectFile; });