Permalink
Browse files

Merge branch 'master' into PreferencesModel

Conflicts:
	src/language/CodeInspection.js
  • Loading branch information...
2 parents 84debd1 + c2bd6ac commit f3c30fff0f8812626ac08940044ba2fd7f0bb65d @dangoor dangoor committed Jan 10, 2014
Showing with 1,376 additions and 243 deletions.
  1. +13 −7 src/command/Menus.js
  2. +10 −6 src/editor/CSSInlineEditor.js
  3. +4 −8 src/extensions/default/CSSCodeHints/main.js
  4. +11 −9 src/extensions/default/DebugCommands/htmlContent/perf-dialog.html
  5. +1 −1 src/extensions/default/HTMLCodeHints/unittests.js
  6. +19 −6 src/extensions/default/QuickView/main.js
  7. +1 −1 src/extensions/default/WebPlatformDocs/main.js
  8. +1 −1 src/filesystem/impls/appshell/AppshellFileSystem.js
  9. +7 −2 src/htmlContent/problems-panel-table.html
  10. +2 −2 src/htmlContent/problems-panel.html
  11. +237 −118 src/language/CodeInspection.js
  12. +3 −3 src/nls/cs/strings.js
  13. +4 −2 src/nls/de/strings.js
  14. +5 −1 src/nls/fr/strings.js
  15. +5 −1 src/nls/ja/strings.js
  16. +14 −14 src/nls/nl/strings.js
  17. +5 −6 src/nls/root/strings.js
  18. +93 −19 src/project/ProjectManager.js
  19. +16 −2 src/styles/brackets.less
  20. +22 −3 src/styles/brackets_codemirror_override.less
  21. +22 −1 src/styles/brackets_patterns_override.less
  22. +33 −2 src/styles/brackets_scrollbars.less
  23. +7 −2 src/styles/brackets_theme_default.less
  24. +11 −2 src/utils/Async.js
  25. +55 −7 src/utils/NodeConnection.js
  26. +60 −15 src/utils/ViewUtils.js
  27. +1 −0 test/UnitTestSuite.js
  28. +3 −0 test/spec/CodeInspection-test-files/errors.css
  29. +2 −0 test/spec/CodeInspection-test-files/errors.js
  30. +3 −0 test/spec/CodeInspection-test-files/no-errors.js
  31. +539 −0 test/spec/CodeInspection-test.js
  32. +99 −0 test/spec/NodeConnection-test-files/BinaryTestCommands.js
  33. +68 −2 test/spec/NodeConnection-test.js
View
20 src/command/Menus.js
@@ -1051,18 +1051,24 @@ define(function (require, exports, module) {
closeAll();
// adjust positioning so menu is not clipped off bottom or right
- var bottomOverhang = posTop + 25 + $menuWindow.height() - $window.height();
- if (bottomOverhang > 0) {
- posTop = Math.max(0, posTop - bottomOverhang);
+ var elementRect = {
+ top: posTop,
+ left: posLeft,
+ height: $menuWindow.height() + 25,
+ width: $menuWindow.width()
+ },
+ clip = ViewUtils.getElementClipSize($window, elementRect);
+
+ if (clip.bottom > 0) {
+ posTop = Math.max(0, posTop - clip.bottom);
}
posTop -= 30; // shift top for hidden parent element
posLeft += 5;
- var rightOverhang = posLeft + $menuWindow.width() - $window.width();
- if (rightOverhang > 0) {
- posLeft = Math.max(0, posLeft - rightOverhang);
+ if (clip.right > 0) {
+ posLeft = Math.max(0, posLeft - clip.right);
}
-
+
// open the context menu at final location
$menuAnchor.addClass("open")
.css({"left": posLeft, "top": posTop});
View
16 src/editor/CSSInlineEditor.js
@@ -248,20 +248,24 @@ define(function (require, exports, module) {
.appendTo($("body"));
var toggleOffset = $newRuleButton.offset(),
- $window = $(window),
posLeft = toggleOffset.left,
posTop = toggleOffset.top + $newRuleButton.outerHeight(),
- bottomOverhang = posTop + $dropdown.height() - $window.height(),
- rightOverhang = posLeft + $dropdown.width() - $window.width();
+ elementRect = {
+ top: posTop,
+ left: posLeft,
+ height: $dropdown.height(),
+ width: $dropdown.width()
+ },
+ clip = ViewUtils.getElementClipSize($(window), elementRect);
- if (bottomOverhang > 0) {
+ if (clip.bottom > 0) {
// Bottom is clipped, so move entire menu above button
posTop = Math.max(0, toggleOffset.top - $dropdown.height() - 4);
}
- if (rightOverhang > 0) {
+ if (clip.right > 0) {
// Right is clipped, so adjust left to fit menu in editor
- posLeft = Math.max(0, posLeft - rightOverhang);
+ posLeft = Math.max(0, posLeft - clip.right);
}
$dropdown.css({
View
12 src/extensions/default/CSSCodeHints/main.js
@@ -45,7 +45,7 @@ define(function (require, exports, module) {
*/
function CssPropHints() {
this.primaryTriggerKeys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-()";
- this.secondaryTriggerKeys = ":";
+ this.secondaryTriggerKeys = ": ";
this.exclusion = null;
}
@@ -211,10 +211,6 @@ define(function (require, exports, module) {
// Clear the exclusion if the user moves the cursor with left/right arrow key.
this.updateExclusion(true);
-
- if (this.info.offset === 0 && lastContext !== null) {
- return null;
- }
if (context === CSSUtils.PROP_VALUE) {
@@ -321,7 +317,7 @@ define(function (require, exports, module) {
if (this.info.name.length === 0 || CodeHintManager.hasValidExclusion(this.exclusion, textAfterCursor)) {
// It's a new insertion, so append a colon and set keepHints
// to show property value hints.
- hint += ":";
+ hint += ": ";
end.ch = start.ch;
end.ch += offset;
@@ -346,12 +342,12 @@ define(function (require, exports, module) {
// before we locate the colon following it.
TokenUtils.moveNextToken(ctx);
}
- if (TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && ctx.token.string === ":") {
+ if (TokenUtils.moveSkippingWhitespace(TokenUtils.moveNextToken, ctx) && ctx.token.string === ": ") {
adjustCursor = true;
newCursor = { line: cursor.line,
ch: cursor.ch + (hint.length - this.info.name.length) };
} else {
- hint += ":";
+ hint += ": ";
}
}
} else {
View
20 src/extensions/default/DebugCommands/htmlContent/perf-dialog.html
@@ -1,23 +1,25 @@
<div class="modal">
<div class="modal-header">
- <a href="#" class="close" data-dismiss="modal">&times;</a>
<h1 class="dialog-title">Performance Data</h1>
- <div style="text-align: right">
- Raw data (copy paste out):
- <textarea rows="1" style="width:30px; height:16px; overflow: hidden; resize: none" id="brackets-perf-raw-data">{{delimitedPerfData}}</textarea>
- </div>
</div>
- <div class="modal-body" style="padding: 0; max-height: 500px; overflow: auto; width: 592px">
- <table class="zebra-striped condensed-table">
- <thead><th>Operation</th><th>Time (ms)</th></thead>
+ <div class="modal-body no-padding">
+ <table class="table table-striped">
+ <thead><th>Operation</th><th class="right">Time (ms)</th></thead>
<tbody>
{{#perfData}}
<tr>
<td>{{{testName}}}</td>
- <td>{{value}}</td>
+ <td class="right">{{value}}</td>
</tr>
{{/perfData}}
</tbody>
</table>
</div>
+ <div class="modal-footer">
+ <div class="left">
+ <label for="brackets-perf-raw-data" class="inline">Raw data (copy paste out):</label>
+ <textarea rows="1" id="brackets-perf-raw-data" style="margin: 0; width: 50px;">{{delimitedPerfData}}</textarea>
+ </div>
+ <button class="dialog-button btn primary" data-button-id="ok">Close</button>
+ </div>
</div>
View
2 src/extensions/default/HTMLCodeHints/unittests.js
@@ -478,7 +478,7 @@ define(function (require, exports, module) {
verifyAttrHints(hintList, "application/msexcel");
});
- xit("should NOT list attribute value hints when the cursor is after the end quote of an attribute value", function () {
+ it("should NOT list attribute value hints when the cursor is after the end quote of an attribute value", function () {
testDocument.replaceRange(" <input checked accept='' >\n", { line: 9, ch: 0 }); // insert new line
// Set cursor after the closing quote of accept attribute
View
25 src/extensions/default/QuickView/main.js
@@ -36,7 +36,8 @@ define(function (require, exports, module) {
FileUtils = brackets.getModule("file/FileUtils"),
Menus = brackets.getModule("command/Menus"),
PreferencesManager = brackets.getModule("preferences/PreferencesManager"),
- Strings = brackets.getModule("strings");
+ Strings = brackets.getModule("strings"),
+ ViewUtils = brackets.getModule("utils/ViewUtils");
var previewContainerHTML = require("text!QuickViewTemplate.html");
@@ -108,12 +109,23 @@ define(function (require, exports, module) {
top = ypos - $previewContainer.outerHeight() - POINTER_HEIGHT,
left = xpos - previewWidth / 2,
$editorHolder = $("#editor-holder"),
- editorLeft = $editorHolder.offset().left;
+ elementRect = {
+ top: top,
+ left: left - POPOVER_HORZ_MARGIN,
+ height: $previewContainer.outerHeight() + POINTER_HEIGHT,
+ width: previewWidth + 2 * POPOVER_HORZ_MARGIN
+ },
+ clip = ViewUtils.getElementClipSize($editorHolder, elementRect);
+
+ // Prevent horizontal clipping
+ if (clip.left > 0) {
+ left += clip.left;
+ } else if (clip.right > 0) {
+ left -= clip.right;
+ }
- left = Math.max(left, editorLeft + POPOVER_HORZ_MARGIN);
- left = Math.min(left, editorLeft + $editorHolder.width() - previewWidth - POPOVER_HORZ_MARGIN);
-
- if (top < 0) {
+ // If clipped on top, flip popover below line
+ if (clip.top > 0) {
top = ybot + POINTER_HEIGHT;
$previewContainer
.removeClass("preview-bubble-above")
@@ -123,6 +135,7 @@ define(function (require, exports, module) {
.removeClass("preview-bubble-below")
.addClass("preview-bubble-above");
}
+
$previewContainer
.css({
left: left,
View
2 src/extensions/default/WebPlatformDocs/main.js
@@ -87,7 +87,7 @@ define(function (require, exports, module) {
function inlineProvider(hostEditor, pos) {
var langId = hostEditor.getLanguageForSelection().getId();
// Only provide docs when cursor is in CSS content
- if (langId !== "css" && langId !== "scss") {
+ if (langId !== "css" && langId !== "scss" && langId !== "less") {
return null;
}
View
2 src/filesystem/impls/appshell/AppshellFileSystem.js
@@ -421,7 +421,7 @@ define(function (require, exports, module) {
}
if (options.hasOwnProperty("expectedHash") && options.expectedHash !== stats._hash) {
- console.warn("Blind write attempted: ", path, stats._hash, options.expectedHash);
+ console.error("Blind write attempted: ", path, stats._hash, options.expectedHash);
callback(FileSystemError.CONTENTS_MODIFIED);
return;
}
View
9 src/htmlContent/problems-panel-table.html
@@ -1,11 +1,16 @@
<table class="bottom-panel-table table table-striped table-condensed row-highlight">
<tbody>
{{#reportList}}
+ <tr class="inspector-section">
+ <td colspan="3"><span class="disclosure-triangle expanded"></span>{{providerName}} ({{results.length}})</td>
+ </tr>
+ {{#results}}
<tr>
<td class="line-number" data-character="{{pos.ch}}">{{friendlyLine}}</td>
- <td>{{message}}</td>
- <td>{{codeSnippet}}</td>
+ <td class="line-text">{{message}}</td>
+ <td class="line-snippet">{{codeSnippet}}</td>
</tr>
+ {{/results}}
{{/reportList}}
</tbody>
</table>
View
4 src/htmlContent/problems-panel.html
@@ -1,7 +1,7 @@
<div id="problems-panel" class="bottom-panel vert-resizable top-resizer">
<div class="toolbar simple-toolbar-layout">
- <div class="title"><!-- filled in programmatically with active linter's name --></div>
+ <div class="title"><!-- filled in programmatically --></div>
<a href="#" class="close">&times;</a>
</div>
<div class="table-container resizable-content"></div>
-</div>
+</div>
View
355 src/language/CodeInspection.js
@@ -1,24 +1,24 @@
/*
* Copyright (c) 2013 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
+ * 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,
+ * 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
+ * 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.
- *
+ *
*/
@@ -38,7 +38,9 @@
*/
define(function (require, exports, module) {
"use strict";
-
+
+ var _ = require("thirdparty/lodash");
+
// Load dependent modules
var Commands = require("command/Commands"),
PanelManager = require("view/PanelManager"),
@@ -54,10 +56,12 @@ define(function (require, exports, module) {
AppInit = require("utils/AppInit"),
Resizer = require("utils/Resizer"),
StatusBar = require("widgets/StatusBar"),
+ Async = require("utils/Async"),
PanelTemplate = require("text!htmlContent/problems-panel.html"),
ResultsTemplate = require("text!htmlContent/problems-panel-table.html");
var INDICATOR_ID = "status-inspection";
+
/** Values for problem's 'type' property */
var Type = {
/** Unambiguous error, such as a syntax error */
@@ -67,34 +71,34 @@ define(function (require, exports, module) {
/** Inspector unable to continue, code too complex for static analysis, etc. Not counted in error/warning tally. */
META: "problem_type_meta"
};
-
+
/**
* Constants for the preferences defined in this file.
*/
var PREF_ENABLED = "linting.enabled",
PREF_COLLAPSED = "linting.collapsed";
-
+
/**
* When disabled, the errors panel is closed and the status bar icon is grayed out.
* Takes precedence over _collapsed.
* @private
* @type {boolean}
*/
var _enabled = true;
-
+
/**
* When collapsed, the errors panel is closed but the status bar icon is kept up to date.
* @private
* @type {boolean}
*/
var _collapsed = false;
-
+
/**
* @private
* @type {$.Element}
*/
var $problemsPanel;
-
+
/**
* @private
* @type {$.Element}
@@ -106,19 +110,19 @@ define(function (require, exports, module) {
* @type {boolean}
*/
var _gotoEnabled = false;
-
+
/**
* @private
- * @type {Object.<string, {name:string, scanFile:function(string, string):Object}>}
+ * @type {Object.<languageId:string, Array.<{name:string, scanFile:function(string, string):Object}>>}
*/
var _providers = {};
-
+
/**
* @private
- * @type {?Array.<Object>}
+ * @type {boolean}
*/
- var _lastResult;
-
+ var _hasErrors;
+
/**
* Enable or disable the "Go to First Error" command
* @param {boolean} gotoEnabled Whether it is enabled.
@@ -127,52 +131,70 @@ define(function (require, exports, module) {
CommandManager.get(Commands.NAVIGATE_GOTO_FIRST_PROBLEM).setEnabled(gotoEnabled);
_gotoEnabled = gotoEnabled;
}
-
-
+
+ function _unregisterAll() {
+ _providers = {};
+ }
+
/**
- * Returns a provider for given file path, if one is available.
+ * Returns a list of provider for given file path, if available.
* Decision is made depending on the file extension.
*
* @param {!string} filePath
- * @return ?{{name:string, scanFile:function(string, string):?{!errors:Array, aborted:boolean}} provider
+ * @return ?{Array.<{name:string, scanFile:function(string, string):?{errors:!Array, aborted:boolean}}>} provider
*/
- function getProviderForPath(filePath) {
+ function getProvidersForPath(filePath) {
return _providers[LanguageManager.getLanguageForPath(filePath).getId()];
}
/**
- * Runs a file inspection over passed file, specifying a provider is optional.
+ * Runs a file inspection over passed file. Uses the given list of providers if specified, otherwise uses
+ * the set of providers that are registered for the file's language.
* 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.
+ * These results will reflect any unsaved changes present in the file if currently open.
+ *
+ * The Promise yields an array of provider-result pair objects (the result is the return value of the
+ * provider's scanFile() - see register() for details). The result object may be null if there were no
+ * errors from that provider.
+ * If there are no providers registered for this file, the Promise yields null instead.
*
* @param {!File} file 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}
+ * @param {?Array.<{name:string, scanFile:function(string, string):?{errors:!Array, aborted:boolean}}>} providerList
+ * @return {$.Promise} a jQuery promise that will be resolved with ?Array.<{provider:Object, result: ?{errors:!Array, aborted:boolean}}>
*/
- function inspectFile(file, provider) {
- var response = new $.Deferred();
- provider = provider || getProviderForPath(file.fullPath);
+ function inspectFile(file, providerList) {
+ var response = new $.Deferred(),
+ results = [];
+
+ providerList = (providerList || getProvidersForPath(file.fullPath)) || [];
- if (!provider) {
+ if (!providerList.length) {
response.resolve(null);
return response.promise();
}
DocumentManager.getDocumentText(file)
.done(function (fileText) {
- var result,
- perfTimerInspector = PerfUtils.markStart("CodeInspection '" + provider.name + "':\t" + file.fullPath);
-
- try {
- result = provider.scanFile(fileText, file.fullPath);
- } catch (err) {
- console.error("[CodeInspection] Provider " + provider.name + " threw an error: " + err);
- response.reject(err);
- return;
- }
+ var perfTimerInspector = PerfUtils.markStart("CodeInspection:\t" + file.fullPath);
+
+ providerList.forEach(function (provider) {
+ var perfTimerProvider = PerfUtils.markStart("CodeInspection '" + provider.name + "':\t" + file.fullPath);
+
+ try {
+ var scanResult = provider.scanFile(fileText, file.fullPath);
+ results.push({provider: provider, result: scanResult});
+ } catch (err) {
+ console.error("[CodeInspection] Provider " + provider.name + " threw an error: " + err);
+ response.reject(err);
+ return;
+ }
+
+ PerfUtils.addMeasurement(perfTimerProvider);
+ });
PerfUtils.addMeasurement(perfTimerInspector);
- response.resolve(result);
+
+ response.resolve(results);
})
.fail(function (err) {
console.error("[CodeInspection] Could not read file for inspection: " + file.fullPath);
@@ -183,84 +205,154 @@ define(function (require, exports, module) {
}
/**
+ * Update the title of the problem panel and the tooltip of the status bar icon. The title and the tooltip will
+ * change based on the number of problems reported and how many provider reported problems.
+ *
+ * @param {Number} numProblems - total number of problems across all providers
+ * @param {Array.<{name:string, scanFile:function(string, string):Object}>} providersReportingProblems - providers that reported problems
+ * @param {boolean} aborted - true if any provider returned a result with the 'aborted' flag set
+ */
+ function updatePanelTitleAndStatusBar(numProblems, providersReportingProblems, aborted) {
+ var message;
+
+ if (providersReportingProblems.length === 1) {
+ // don't show a header if there is only one provider available for this file type
+ $problemsPanelTable.find(".inspector-section").hide();
+
+ if (numProblems === 1 && !aborted) {
+ message = StringUtils.format(Strings.SINGLE_ERROR, providersReportingProblems[0].name);
+ } else {
+ if (aborted) {
+ numProblems += "+";
+ }
+
+ message = StringUtils.format(Strings.MULTIPLE_ERRORS, providersReportingProblems[0].name, numProblems);
+ }
+
+ $problemsPanel.find(".title").text(message);
+ StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-errors", message);
+ } else if (providersReportingProblems.length > 1) {
+ $problemsPanelTable.find(".inspector-section").show();
+
+ if (aborted) {
+ numProblems += "+";
+ }
+
+ message = StringUtils.format(Strings.ERRORS_PANEL_TITLE_MULTIPLE, numProblems);
+ $problemsPanel.find(".title").text(message);
+ StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-errors", message);
+ }
+ }
+
+ /**
* Run inspector applicable to current document. Updates status bar indicator and refreshes error list in
* bottom panel. Does not run if inspection is disabled or if a providerName is given and does not
* match the current doc's provider name.
*
* @param {?string} providerName name of the provider that is requesting a run
*/
- function run(providerName) {
- var currentDoc = DocumentManager.getCurrentDocument(),
- provider = currentDoc && getProviderForPath(currentDoc.file.fullPath);
-
- if (!_enabled || (provider && providerName && providerName !== provider.name)) {
- _lastResult = null;
+ function run() {
+ if (!_enabled) {
+ _hasErrors = false;
Resizer.hide($problemsPanel);
StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-disabled", Strings.LINT_DISABLED);
setGotoEnabled(false);
return;
}
-
- if (provider) {
- inspectFile(currentDoc.file, provider).then(function (result) {
+
+ var currentDoc = DocumentManager.getCurrentDocument(),
+ providerList = currentDoc && getProvidersForPath(currentDoc.file.fullPath);
+
+ if (providerList && providerList.length) {
+ var numProblems = 0;
+ var aborted = false;
+ var allErrors = [];
+ var html;
+ var providersReportingProblems = [];
+
+ // run all the providers registered for this file type
+ inspectFile(currentDoc.file, providerList).then(function (results) {
// check if current document wasn't changed while inspectFile was running
if (currentDoc !== DocumentManager.getCurrentDocument()) {
return;
}
- _lastResult = result;
+ // how many errors in total?
+ var errors = results.reduce(function (a, item) { return a + (item.result ? item.result.errors.length : 0); }, 0);
- if (!result || !result.errors.length) {
+ _hasErrors = Boolean(errors);
+
+ if (!errors) {
Resizer.hide($problemsPanel);
- StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-valid", StringUtils.format(Strings.NO_ERRORS, provider.name));
+
+ var message = Strings.NO_ERRORS_MULTIPLE_PROVIDER;
+ if (providerList.length === 1) {
+ message = StringUtils.format(Strings.NO_ERRORS, providerList[0].name);
+ }
+
+ StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-valid", message);
+
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
-
- if (error.type !== Type.META) {
- numProblems++;
+ results.forEach(function (inspectionResult) {
+ var provider = inspectionResult.provider;
+
+ if (inspectionResult.result) {
+ inspectionResult.result.errors.forEach(function (error) {
+ // some inspectors don't always provide a line number
+ if (!isNaN(error.pos.line)) {
+ 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
+ }
+
+ if (error.type !== Type.META) {
+ numProblems++;
+ }
+ });
+
+ // if the code inspector was unable to process the whole file, we keep track to show a different status
+ if (inspectionResult.result.aborted) {
+ aborted = true;
+ }
+
+ if (inspectionResult.result.errors) {
+ allErrors.push({
+ providerName: provider.name,
+ results: inspectionResult.result.errors
+ });
+
+ providersReportingProblems.push(provider);
+ }
}
});
// Update results table
- var html = Mustache.render(ResultsTemplate, {reportList: result.errors});
+ html = Mustache.render(ResultsTemplate, {reportList: allErrors});
$problemsPanelTable
.empty()
.append(html)
.scrollTop(0); // otherwise scroll pos from previous contents is remembered
-
- $problemsPanel.find(".title").text(StringUtils.format(Strings.ERRORS_PANEL_TITLE, provider.name));
+
if (!_collapsed) {
Resizer.show($problemsPanel);
}
-
- if (numProblems === 1 && !result.aborted) {
- StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-errors", StringUtils.format(Strings.SINGLE_ERROR, provider.name));
- } else {
- // If inspector was unable to process the whole file, number of errors is indeterminate; indicate with a "+"
- if (result.aborted) {
- numProblems += "+";
- }
- StatusBar.updateIndicator(INDICATOR_ID, true, "inspection-errors",
- StringUtils.format(Strings.MULTIPLE_ERRORS, provider.name, numProblems));
- }
+
+ updatePanelTitleAndStatusBar(numProblems, providersReportingProblems, aborted);
setGotoEnabled(true);
PerfUtils.addMeasurement(perfTimerDOM);
});
+
} else {
// No provider for current file
- _lastResult = null;
+ _hasErrors = false;
Resizer.hide($problemsPanel);
var language = currentDoc && LanguageManager.getLanguageForPath(currentDoc.file.fullPath);
if (language) {
@@ -271,23 +363,36 @@ define(function (require, exports, module) {
setGotoEnabled(false);
}
}
-
+
/**
* The provider is passed the text of the file and its fullPath. Providers should not assume
* that the file is open (i.e. DocumentManager.getOpenDocumentForPath() may return null) or
* that the file on disk matches the text given (file may have unsaved changes).
+ *
+ * Registering any provider for the "javascript" language automatically unregisters the built-in
+ * Brackets JSLint provider. This is a temporary convenience until UI exists for disabling
+ * registered providers.
*
* @param {string} languageId
- * @param {{name:string, scanFile:function(string, string):?{!errors:Array, aborted:boolean}} provider
+ * @param {{name:string, scanFile:function(string, string):?{errors:!Array, aborted:boolean}} provider
*
* Each error is: { pos:{line,ch}, endPos:?{line,ch}, message:string, type:?Type }
* If type is unspecified, Type.WARNING is assumed.
*/
function register(languageId, provider) {
- if (_providers[languageId]) {
- console.warn("Overwriting existing inspection/linting provider for language " + languageId);
+ if (!_providers[languageId]) {
+ _providers[languageId] = [];
}
- _providers[languageId] = provider;
+
+ if (languageId === "javascript") {
+ // This is a special case to enable extension provider to replace the JSLint provider
+ // in favor of their own implementation
+ _.remove(_providers[languageId], function (registeredProvider) {
+ return registeredProvider.name === "JSLint";
+ });
+ }
+
+ _providers[languageId].push(provider);
run(); // in case a file of this type is open currently
}
@@ -311,7 +416,7 @@ define(function (require, exports, module) {
$(DocumentManager).off(".codeInspection");
}
}
-
+
/**
* Enable or disable all inspection.
* @param {?boolean} enabled Enabled state. If omitted, the state is toggled.
@@ -322,7 +427,7 @@ define(function (require, exports, module) {
enabled = !_enabled;
}
_enabled = enabled;
-
+
CommandManager.get(Commands.VIEW_TOGGLE_INSPECTION).setChecked(_enabled);
updateListeners();
if (!doNotSave) {
@@ -332,21 +437,20 @@ define(function (require, exports, module) {
// run immediately
run();
}
-
-
- /**
+
+ /**
* Toggle the collapsed state for the panel. This explicitly collapses the panel (as opposed to
* the auto collapse due to files with no errors & filetypes with no provider). When explicitly
* collapsed, the panel will not reopen automatically on switch files or save.
- *
+ *
* @param {?boolean} collapsed Collapsed state. If omitted, the state is toggled.
* @param {?boolean} doNotSave true if the preference should not be saved to user settings. This is generally for events triggered by project-level settings.
*/
function toggleCollapsed(collapsed, doNotSave) {
if (collapsed === undefined) {
collapsed = !_collapsed;
}
-
+
_collapsed = collapsed;
if (!doNotSave) {
PreferencesManager.set("user", PREF_COLLAPSED, _collapsed);
@@ -355,21 +459,20 @@ define(function (require, exports, module) {
if (_collapsed) {
Resizer.hide($problemsPanel);
} else {
- if (_lastResult && _lastResult.errors.length) {
+ if (_hasErrors) {
Resizer.show($problemsPanel);
}
}
}
-
+
/** Command to go to the first Error/Warning */
function handleGotoFirstProblem() {
run();
if (_gotoEnabled) {
$problemsPanel.find("tr:first-child").trigger("click");
}
}
-
-
+
// Register command handlers
CommandManager.register(Strings.CMD_VIEW_TOGGLE_INSPECTION, Commands.VIEW_TOGGLE_INSPECTION, toggleEnabled);
CommandManager.register(Strings.CMD_GOTO_FIRST_PROBLEM, Commands.NAVIGATE_GOTO_FIRST_PROBLEM, handleGotoFirstProblem);
@@ -393,7 +496,7 @@ define(function (require, exports, module) {
var panelHtml = Mustache.render(PanelTemplate, Strings);
var resultsPanel = PanelManager.createBottomPanel("errors", $(panelHtml), 100);
$problemsPanel = $("#problems-panel");
-
+
var $selectedRow;
$problemsPanelTable = $problemsPanel.find(".table-container")
.on("click", "tr", function (e) {
@@ -403,41 +506,57 @@ define(function (require, exports, module) {
$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();
+ // This is a inspector title row, expand/collapse on click
+ if ($selectedRow.hasClass("inspector-section")) {
+ // Clicking the inspector title section header collapses/expands result rows
+ $selectedRow.nextUntil(".inspector-section").toggle();
+
+ var $triangle = $(".disclosure-triangle", $selectedRow);
+ $triangle.toggleClass("expanded").toggleClass("collapsed");
+ } else {
+ // This is a problem marker row, show the result on click
+ // Grab the required position data
+ var lineTd = $selectedRow.find(".line-number");
+ var line = parseInt(lineTd.text(), 10) - 1; // convert friendlyLine back to pos.line
+ // if there is no line number available, don't do anything
+ if (!isNaN(line)) {
+ var character = lineTd.data("character");
+
+ var editor = EditorManager.getCurrentFullEditor();
+ editor.setCursorPos(line, character, true);
+ EditorManager.focusEditor();
+ }
+ }
});
$("#problems-panel .close").click(function () {
toggleCollapsed(true);
});
-
+
// Status bar indicator - icon & tooltip updated by run()
var statusIconHtml = Mustache.render("<div id=\"status-inspection\">&nbsp;</div>", Strings);
StatusBar.addIndicator(INDICATOR_ID, $(statusIconHtml), true, "", "", "status-indent");
-
+
$("#status-inspection").click(function () {
// Clicking indicator toggles error panel, if any errors in current file
- if (_lastResult && _lastResult.errors.length) {
+ if (_hasErrors) {
toggleCollapsed();
}
});
-
-
+
// Set initial UI state
toggleEnabled(PreferencesManager.get(PREF_ENABLED), true);
toggleCollapsed(PreferencesManager.get(PREF_COLLAPSED), true);
});
-
-
+
+ // Testing
+ exports._unregisterAll = _unregisterAll;
+
// Public API
- exports.register = register;
- exports.Type = Type;
- exports.toggleEnabled = toggleEnabled;
- exports.inspectFile = inspectFile;
+ exports.register = register;
+ exports.Type = Type;
+ exports.toggleEnabled = toggleEnabled;
+ exports.inspectFile = inspectFile;
exports.requestRun = run;
});
View
6 src/nls/cs/strings.js
@@ -111,21 +111,21 @@ define({
"EXT_DELETED_MESSAGE" : "<span class='dialog-filename'>{0}</span> byl smazán z disku, ale změny nebyly uloženy v {APP_NAME}.<br /><br />Chcete uložit změny?",
// Najít, Nahradit, Nahradit v souborech
- "SEARCH_REGEXP_INFO" : "Použijte /re/ syntax pro regexp hledání",
"FIND_RESULT_COUNT" : "{0} výsledků",
"FIND_RESULT_COUNT_SINGLE" : "1 výsledek",
"FIND_NO_RESULTS" : "Žádné výsledky",
- "WITH" : "S",
+ "REPLACE_PLACEHOLDER" : "Nahradit s\u2026",
"BUTTON_YES" : "Ano",
"BUTTON_NO" : "Ne",
"BUTTON_REPLACE_ALL" : "Vše\u2026",
- "BUTTON_STOP" : "Stop",
"BUTTON_REPLACE" : "Nahradit",
"BUTTON_NEXT" : "\u25B6",
"BUTTON_PREV" : "\u25C0",
"BUTTON_NEXT_HINT" : "Další shoda",
"BUTTON_PREV_HINT" : "Předchozí shoda",
+ "BUTTON_CASESENSITIVE_HINT" : "Rozlišovat velká a malá písmena",
+ "BUTTON_REGEXP_HINT" : "Regulární výraz",
"OPEN_FILE" : "Otevřít soubor",
"SAVE_FILE_AS" : "Uložit soubor",
View
6 src/nls/de/strings.js
@@ -126,7 +126,6 @@ define({
"REPLACE_PLACEHOLDER" : "Ersetzen mit\u2026",
"BUTTON_REPLACE_ALL" : "Alle\u2026",
"BUTTON_REPLACE" : "Ersetzen",
-
"BUTTON_NEXT" : "\u25B6",
"BUTTON_PREV" : "\u25C0",
"BUTTON_NEXT_HINT" : "Nächster Treffer",
@@ -160,7 +159,6 @@ define({
"FIND_IN_FILES_MORE_THAN" : "Über ",
"FIND_IN_FILES_PAGING" : "{0}&mdash;{1}",
"FIND_IN_FILES_FILE_PATH" : "<span class='dialog-filename'>{0}</span> {2} <span class='dialog-path'>{1}</span>",
-
"ERROR_FETCHING_UPDATE_INFO_TITLE" : "Fehler beim Abrufen der Update-Info",
"ERROR_FETCHING_UPDATE_INFO_MSG" : "Beim Abrufen der neusten Update-Informationen vom Server ist ein Problem aufgetreten. Bitte stellen Sie sicher, dass Sie mit dem Internet verbunden sind, und probieren Sie es erneut.",
@@ -194,6 +192,7 @@ define({
"STATUSBAR_TAB_SIZE" : "Tab-Schrittweite:",
"STATUSBAR_LINE_COUNT_SINGULAR" : "\u2014 {0} Zeile",
"STATUSBAR_LINE_COUNT_PLURAL" : "\u2014 {0} Zeilen",
+ "STATUSBAR_USER_EXTENSIONS_DISABLED" : "Erweiterungen deaktiviert",
// CodeInspection: errors/warnings
"ERRORS_PANEL_TITLE" : "{0} Fehler",
@@ -206,6 +205,7 @@ define({
"NO_LINT_AVAILABLE" : "Es ist kein Linter für {0} verfügbar",
"NOTHING_TO_LINT" : "Es gibt nichts zum Linten",
+
/**
* Command Name Constants
*/
@@ -437,6 +437,7 @@ define({
"DEBUG_MENU" : "Debug",
"CMD_SHOW_DEV_TOOLS" : "Entwicklungswerkzeuge zeigen",
"CMD_REFRESH_WINDOW" : "{APP_NAME} neu laden",
+ "CMD_RELOAD_WITHOUT_USER_EXTS" : "Ohne Erweiterungen neu laden",
"CMD_NEW_BRACKETS_WINDOW" : "Neues {APP_NAME}-Fenster",
"CMD_SWITCH_LANGUAGE" : "Sprache wechseln",
"CMD_RUN_UNIT_TESTS" : "Tests durchführen",
@@ -475,6 +476,7 @@ define({
"LOCALE_TR" : "Türkisch",
"LOCALE_ZH_CN" : "Chinesisch, vereinfacht",
"LOCALE_HU" : "Ungarisch",
+ "LOCALE_KO" : "Koreanisch",
// extensions/default/InlineTimingFunctionEditor
"INLINE_TIMING_EDITOR_TIME" : "Zeit",
View
6 src/nls/fr/strings.js
@@ -36,6 +36,7 @@ define({
"NOT_READABLE_ERR": "Impossible de lire le fichier.",
"NO_MODIFICATION_ALLOWED_ERR": "Le répertoire cible ne peut pas être modifié.",
"NO_MODIFICATION_ALLOWED_ERR_FILE": "Vous n’êtes pas autorisé à effectuer des modifications.",
+ "CONTENTS_MODIFIED_ERR": "Le fichier a été modifié dans une application autre que {APP_NAME}.",
"FILE_EXISTS_ERR": "Le fichier ou le répertoire existe déjà.",
"FILE": "fichier",
"DIRECTORY": "répertoire",
@@ -107,6 +108,7 @@ define({
"CONFIRM_FOLDER_DELETE_TITLE": "Confirmer la suppression",
"CONFIRM_FOLDER_DELETE": "Voulez-vous vraiment supprimer le dossier <span class='dialog-filename'>{0}</span> ?",
"FILE_DELETED_TITLE": "Fichier supprimé",
+ "EXT_MODIFIED_WARNING": "<span class='dialog-filename'>{0}</span> a été modifié sur le disque.<br /><br />Voulez-vous enregistrer le fichier et remplacer ces modifications ?",
"EXT_MODIFIED_MESSAGE": "Le fichier <span class='dialog-filename'>{0}</span> a été modifié sur le disque mais présente également des modifications non enregistrées dans {APP_NAME}.<br /><br />Quelle version souhaitez-vous conserver ?",
"EXT_DELETED_MESSAGE": "Le fichier <span class='dialog-filename'>{0}</span> a été supprimé sur le disque mais présente des modifications non enregistrées dans {APP_NAME}.<br /><br />Souhaitez-vous conserver vos modifications ?",
@@ -115,10 +117,12 @@ define({
"CANCEL": "Annuler",
"DONT_SAVE": "Ne pas enregistrer",
"SAVE": "Enregistrer",
+ "SAVE_AS": "Enregistrer sous\u2026",
+ "SAVE_AND_OVERWRITE": "Remplacer",
"DELETE": "Supprimer",
"BUTTON_YES": "Oui",
"BUTTON_NO": "Non",
-
+
// Find, Replace, Find in Files
"FIND_RESULT_COUNT": "{0} résultats",
"FIND_RESULT_COUNT_SINGLE": "1 résultat",
View
6 src/nls/ja/strings.js
@@ -36,6 +36,7 @@ define({
"NOT_READABLE_ERR": "ファイルを読み取れません。",
"NO_MODIFICATION_ALLOWED_ERR": "対象ディレクトリは変更できません。",
"NO_MODIFICATION_ALLOWED_ERR_FILE": "ファイルを変更する権限がありません。",
+ "CONTENTS_MODIFIED_ERR": "このファイルは {APP_NAME} 以外で変更されています。",
"FILE_EXISTS_ERR": "ファイルまたはディレクトリは既に存在しています。",
"FILE": "ファイル",
"DIRECTORY": "ディレクトリ",
@@ -107,6 +108,7 @@ define({
"CONFIRM_FOLDER_DELETE_TITLE": "削除の確認",
"CONFIRM_FOLDER_DELETE": "<span class='dialog-filename'>{0}</span> フォルダーを削除してもよろしいですか?",
"FILE_DELETED_TITLE": "ファイルは削除されました",
+ "EXT_MODIFIED_WARNING": "<span class='dialog-filename'>{0}</span> はディスク上で変更されています。<br /><br />ファイルを保存し、これらの変更を上書きしますか。",
"EXT_MODIFIED_MESSAGE": "<span class='dialog-filename'>{0}</span> はディスク上で変更されていますが、{APP_NAME} 内にも保存されていない変更があります。<br /><br />どちらのバージョンを保持しますか?",
"EXT_DELETED_MESSAGE": "<span class='dialog-filename'>{0}</span> はディスク上で削除されていますが、{APP_NAME} 内に保存されていない変更があります。<br /><br />変更を保持しますか?",
@@ -115,10 +117,12 @@ define({
"CANCEL": "キャンセル",
"DONT_SAVE": "保存しない",
"SAVE": "保存",
+ "SAVE_AS": "名前を付けて保存\u2026",
+ "SAVE_AND_OVERWRITE": "上書き",
"DELETE": "削除",
"BUTTON_YES": "はい",
"BUTTON_NO": "いいえ",
-
+
// Find, Replace, Find in Files
"FIND_RESULT_COUNT": "{0} 件",
"FIND_RESULT_COUNT_SINGLE": "1 件",
View
28 src/nls/nl/strings.js
@@ -92,8 +92,8 @@ define({
"LIVE_DEV_STATUS_TIP_PROGRESS1" : "Live Voorbeeld: Bezig met verbinden\u2026",
"LIVE_DEV_STATUS_TIP_PROGRESS2" : "Live Voorbeeld: Initialiseren\u2026",
"LIVE_DEV_STATUS_TIP_CONNECTED" : "Verbreek verbinding met Live Voorbeeld",
- "LIVE_DEV_STATUS_TIP_OUT_OF_SYNC" : "Live Voorbeeld (slaag bestand op om te verversen)",
- "LIVE_DEV_STATUS_TIP_SYNC_ERROR" : "Live Voorbeeld (niet bezig met opdaten door een verkeerde syntax)",
+ "LIVE_DEV_STATUS_TIP_OUT_OF_SYNC" : "Live Voorbeeld (sla bestand op om te verversen)",
+ "LIVE_DEV_STATUS_TIP_SYNC_ERROR" : "Live Voorbeeld (niet bezig met updaten door een verkeerde syntax)",
"LIVE_DEV_DETACHED_REPLACED_WITH_DEVTOOLS" : "Live Voorbeeld is geannuleerd omdat de developer tools in de browser zijn geopend",
"LIVE_DEV_DETACHED_TARGET_CLOSED" : "Live Voorbeeld is geannuleerd omdat de pagina gesloten werd in de browser",
@@ -152,7 +152,7 @@ define({
"FIND_IN_FILES_PAGING" : "{0}&mdash;{1}",
"FIND_IN_FILES_FILE_PATH" : "<span class='dialog-filename'>{0}</span> {2} <span class='dialog-path'>{1}</span>", // We shoudl use normal dashes on Windows instead of em dash eventually
"ERROR_FETCHING_UPDATE_INFO_TITLE" : "Probleem bij het ophalen van update informatie",
- "ERROR_FETCHING_UPDATE_INFO_MSG" : "Er is een fout opgetreden bij het ophalen van de laatste updat informatie van de server. Zorg ervoor dat je verbonden bent met het internet en probeer opnieuw.",
+ "ERROR_FETCHING_UPDATE_INFO_MSG" : "Er is een fout opgetreden bij het ophalen van de laatste update informatie van de server. Zorg ervoor dat je verbonden bent met het internet en probeer opnieuw.",
/**
* ProjectManager
@@ -355,11 +355,11 @@ define({
"INVALID_VERSION_NUMBER" : "Het pakket versienummer ({0}) is ongeldig.",
"INVALID_BRACKETS_VERSION" : "De {APP_NAME} compatibiliteit string ({0}) is ongeldig.",
"DISALLOWED_WORDS" : "De woorden ({1}) zijn niet toegelaten in het {0} veld.",
- "API_NOT_COMPATIBLE" : "De uitbreiding is niet compatibel met deze versie van {APP_NAME}. Het werd geinstalleerd in de map met uitgeschakelde uitbreidingen.",
+ "API_NOT_COMPATIBLE" : "De uitbreiding is niet compatibel met deze versie van {APP_NAME}. Het werd geïnstalleerd in de map met uitgeschakelde uitbreidingen.",
"MISSING_MAIN" : "Het pakket heeft geen main.js bestand.",
- "EXTENSION_ALREADY_INSTALLED" : "Het installeren van dit pakket zal een vroeger geinstalleerde uitbreiding overschrijven. Overschrijf de oudere uitbreiding?",
- "EXTENSION_SAME_VERSION" : "Dit pakket is dezelfde versie als degene die op dit moment is geinstalleerd. Overschrijf de bestaande installatie?",
- "EXTENSION_OLDER_VERSION" : "Dit pakket is versie {0} dewelke ouder is dan de op dit moment geinstalleerde ({1}). Overschrijf de bestaande installatie?",
+ "EXTENSION_ALREADY_INSTALLED" : "Het installeren van dit pakket zal een vroeger geïnstalleerde uitbreiding overschrijven. Overschrijf de oudere uitbreiding?",
+ "EXTENSION_SAME_VERSION" : "Dit pakket is dezelfde versie als degene die op dit moment is geïnstalleerd. Overschrijf de bestaande installatie?",
+ "EXTENSION_OLDER_VERSION" : "Dit pakket is versie {0} dewelke ouder is dan de op dit moment geïnstalleerde ({1}). Overschrijf de bestaande installatie?",
"DOWNLOAD_ID_IN_USE" : "Interne fout: download ID is reeds in gebruik.",
"NO_SERVER_RESPONSE" : "Kan niet verbinden met de server.",
"BAD_HTTP_STATUS" : "Bestand niet gevonden op server (HTTP {0}).",
@@ -380,8 +380,8 @@ define({
"EXTENSION_MORE_INFO" : "Meer info...",
"EXTENSION_ERROR" : "Uitbreiding fout",
"EXTENSION_KEYWORDS" : "Sleutelwoorden",
- "EXTENSION_INSTALLED" : "Geinstalleerd",
- "EXTENSION_UPDATE_INSTALLED" : "Deze uitbreiding is gedownload en zal geinstalleerd worden wanneer je {APP_NAME} stopt.",
+ "EXTENSION_INSTALLED" : "Geïnstalleerd",
+ "EXTENSION_UPDATE_INSTALLED" : "Deze uitbreiding is gedownload en zal geïnstalleerd worden wanneer je {APP_NAME} stopt.",
"EXTENSION_SEARCH_PLACEHOLDER" : "Zoek",
"EXTENSION_MORE_INFO_LINK" : "Meer",
"BROWSE_EXTENSIONS" : "Blader door Uitbreidingen",
@@ -398,11 +398,11 @@ define({
"REMOVE_AND_QUIT" : "Verwijder Uitbreidingen en stop",
"CHANGE_AND_QUIT" : "Wijzig Uitbreidingen en Stop",
"UPDATE_AND_QUIT" : "Update Uitbreidingen en Stop",
- "EXTENSION_NOT_INSTALLED" : "Kon de uitbreiding {0} niet verwijderen omdat ze niet was geinstalleerd",
- "NO_EXTENSIONS" : "Nog geen uitbreidingen geinstalleerd.<br>Klik op de Beschikbaar tab hierboven om te starten.",
+ "EXTENSION_NOT_INSTALLED" : "Kon de uitbreiding {0} niet verwijderen omdat ze niet was geïnstalleerd",
+ "NO_EXTENSIONS" : "Nog geen uitbreidingen geïnstalleerd.<br>Klik op de Beschikbaar tab hierboven om te starten.",
"NO_EXTENSION_MATCHES" : "Geen uitbreidingen komen overeen met je zoekopdracht.",
"REGISTRY_SANITY_CHECK_WARNING" : "Wees voorzichtig bij het installeren van uitbreidingen van een onbekende bron.",
- "EXTENSIONS_INSTALLED_TITLE" : "Geinstalleerd",
+ "EXTENSIONS_INSTALLED_TITLE" : "Geïnstalleerd",
"EXTENSIONS_AVAILABLE_TITLE" : "Beschikbaar",
"EXTENSIONS_UPDATES_TITLE" : "Updates",
@@ -438,9 +438,9 @@ define({
"LOCALE_ES" : "Spaans",
"LOCALE_FI" : "Fins",
"LOCALE_FR" : "Frans",
- "LOCALE_IT" : "Italiaan",
+ "LOCALE_IT" : "Italiaans",
"LOCALE_JA" : "Japans",
- "LOCALE_NB" : "Norwegian",
+ "LOCALE_NB" : "Noors",
"LOCALE_NL" : "Nederlands",
"LOCALE_PL" : "Pools",
"LOCALE_PT_BR" : "Portugees, Brazilië",
View
11 src/nls/root/strings.js
@@ -199,12 +199,11 @@ define({
"STATUSBAR_USER_EXTENSIONS_DISABLED" : "User Extensions Disabled",
// CodeInspection: errors/warnings
- "ERRORS_PANEL_TITLE" : "{0} Errors",
- "ERRORS_PANEL_TITLE_SINGLE" : "{0} Issues",
- "ERRORS_PANEL_TITLE_MULTI" : "Lint Issues",
- "SINGLE_ERROR" : "1 {0} Error",
- "MULTIPLE_ERRORS" : "{1} {0} Errors",
- "NO_ERRORS" : "No {0} errors - good job!",
+ "ERRORS_PANEL_TITLE_MULTIPLE" : "{0} Problems",
+ "SINGLE_ERROR" : "1 {0} Problem",
+ "MULTIPLE_ERRORS" : "{1} {0} Problems",
+ "NO_ERRORS" : "No {0} problems found - good job!",
+ "NO_ERRORS_MULTIPLE_PROVIDER" : "No problems found - good job!",
"LINT_DISABLED" : "Linting is disabled",
"NO_LINT_AVAILABLE" : "No linter available for {0}",
"NOTHING_TO_LINT" : "Nothing to lint",
View
112 src/project/ProjectManager.js
@@ -602,7 +602,7 @@ define(function (require, exports, module) {
if (_hasFileSelectionFocus() && curDoc && data) {
var entry = data.rslt.obj.data("entry");
- if (curDoc.file.fullPath.indexOf(entry.fullPath) === 0) {
+ if (entry && curDoc.file.fullPath.indexOf(entry.fullPath) === 0) {
_forceSelection(data.rslt.obj, _lastSelected);
} else {
_redraw(true, false);
@@ -1156,13 +1156,18 @@ define(function (require, exports, module) {
}
/**
- * Reloads the project's file tree, maintaining the current selection.
+ * Internal function to refresh the project's file tree, maintaining the
+ * current selection. This function is expensive and not concurrency-safe,
+ * so most callers should use the synchronized and throttled version below,
+ * refreshFileTree.
+ *
+ * @private
* @return {$.Promise} A promise object that will be resolved when the
* project tree is reloaded, or rejected if the project path
* fails to reload. If the previous selected entry is not found,
* the promise is still resolved.
*/
- function refreshFileTree() {
+ function _refreshFileTreeInternal() {
var selectedEntry,
deferred = new $.Deferred();
@@ -1191,15 +1196,61 @@ define(function (require, exports, module) {
}
/**
- * A debounced version of refreshFileTree that executes on the leading edge
- * and also on the trailing edge after a short timeout if called more than
- * once.
* @private
+ * @type {?jQuery.Promise} Resolves when the currently running instance of
+ * _refreshFileTreeInternal completes, or null if there is no currently
+ * running instance.
*/
- var _refreshFileTreeDebounced = _.debounce(refreshFileTree, 100, {
- leading: true,
- trailing: true
- });
+ var _refreshFileTreePromise = null;
+
+ /**
+ * @type {boolean} If refreshFileTree is called before _refreshFileTreePromise
+ * has resolved then _refreshPending is set, which indicates that
+ * refreshFileTree should be called again once the promise resolves.
+ */
+ var _refreshPending = false;
+
+ /**
+ * @const
+ * @private
+ * @type {number} Minimum delay in milliseconds between calls to refreshFileTree
+ */
+ var _refreshDelay = 1000;
+
+ /**
+ * Refresh the project's file tree, maintaining the current selection.
+ *
+ * @return {$.Promise} A promise object that will be resolved when the
+ * project tree is reloaded, or rejected if the project path
+ * fails to reload. If the previous selected entry is not found,
+ * the promise is still resolved.
+ */
+ function refreshFileTree() {
+ if (!_refreshFileTreePromise) {
+ var internalRefreshPromise = _refreshFileTreeInternal(),
+ deferred = new $.Deferred();
+
+ _refreshFileTreePromise = deferred.promise();
+
+ _refreshFileTreePromise.always(function () {
+ _refreshFileTreePromise = null;
+
+ if (_refreshPending) {
+ _refreshPending = false;
+ refreshFileTree();
+ }
+ });
+
+ // Wait at least one second before resolving the promise
+ window.setTimeout(function () {
+ internalRefreshPromise.then(deferred.resolve, deferred.reject);
+ }, _refreshDelay);
+ } else {
+ _refreshPending = true;
+ }
+
+ return _refreshFileTreePromise;
+ }
/**
* Expands tree nodes to show the given file or folder and selects it. Silently no-ops if the
@@ -1796,9 +1847,11 @@ define(function (require, exports, module) {
includeWorkingSet = filter;
filter = null;
}
-
+
+ var filteredFilesDeferred = new $.Deferred();
+
// First gather all files in project proper
- return _getAllFilesCache().then(function (result) {
+ _getAllFilesCache().done(function (result) {
// Add working set entries, if requested
if (includeWorkingSet) {
DocumentManager.getWorkingSet().forEach(function (file) {
@@ -1813,8 +1866,18 @@ define(function (require, exports, module) {
result = result.filter(filter);
}
- return result;
+ // If a done handler attached to the returned filtered files promise
+ // throws an exception that isn't handled here then it will leave
+ // _allFilesCachePromise in an inconsistent state such that no
+ // additional done handlers will ever be called!
+ try {
+ filteredFilesDeferred.resolve(result);
+ } catch (e) {
+ console.warn("Unhandled exception in getAllFiles handler: ", e);
+ }
});
+
+ return filteredFilesDeferred.promise();
}
/**
@@ -1848,7 +1911,9 @@ define(function (require, exports, module) {
// A whole-sale change event; refresh the entire file tree
if (!entry) {
_fileTreeChangeQueue.removeAll();
- _refreshFileTreeDebounced();
+ _fileTreeChangeQueue.add(function () {
+ return refreshFileTree();
+ });
return;
}
@@ -1858,12 +1923,21 @@ define(function (require, exports, module) {
}
if (entry.isDirectory) {
- // A change event with unknown added and removed sets
- if (!added || !removed) {
- // TODO: just update children of entry in this case.
+ // If there is a change event with unknown added and removed sets,
+ // or if there are too many pending file tree fixups to deal with
+ // in a timely manner, just clear the queue and refresh the tree.
+ //
+ // TODO: in the former case we really should just refresh the affected
+ // directory instead of refreshing the entire tree.
+ if (!added || !removed || _fileTreeChangeQueue.length > 100) {
_fileTreeChangeQueue.removeAll();
- _refreshFileTreeDebounced();
- return;
+ return _fileTreeChangeQueue.add(function () {
+ return _findTreeNode(entry, true).then(function ($directoryNode) {
+ if ($directoryNode && !$directoryNode.hasClass("jstree-closed")) {
+ return refreshFileTree();
+ }
+ });
+ });
}
// Directory contents removed
View
18 src/styles/brackets.less
@@ -982,7 +982,7 @@ a, img {
}
}
-#search-results .disclosure-triangle {
+#search-results .disclosure-triangle, #problems-panel .disclosure-triangle {
.jstree-sprite;
display: inline-block;
&.expanded {
@@ -1334,8 +1334,22 @@ a, img {
.line {
text-align: right; // make line number line up with editor line numbers
}
-}
+ .line-text {
+ white-space: nowrap;
+ width: auto;
+ }
+
+ .line-snippet {
+ white-space: nowrap;
+ width: 100%;
+ padding-left: 10px;
+ }
+
+ .inspector-section > td {
+ padding-left: 5px;
+ }
+}
/* Line up label text and input text */
label input {
View
25 src/styles/brackets_codemirror_override.less
@@ -120,9 +120,7 @@
background-color: @background-color-3;
border-right: none;
}
- .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
- background-color: #ebebeb;
- }
+
.platform-mac & {
.CodeMirror-scrollbar-filler {
background-image: url(images/scrollbar-mac-corner.png);
@@ -131,6 +129,27 @@
background-image: url(images/scrollbar-mac-bg.png);
}
}
+ .platform-win & {
+ .CodeMirror-scrollbar-filler,
+ .CodeMirror-gutter-filler {
+ background-color: @win-scrollbar-track;
+ height: 12px !important;
+ }
+ .CodeMirror-scrollbar-filler {
+ width: 12px !important;
+ }
+ }
+ .platform-linux & {
+ .CodeMirror-scrollbar-filler,
+ .CodeMirror-gutter-filler {
+ background-color: @background-color-3;
+ height: 12px !important;
+ }
+ .CodeMirror-scrollbar-filler {
+ width: 12px !important;
+ }
+ }
+
.CodeMirror-linenumber {
color: @accent-comment;
min-width: 2.5em;
View
23 src/styles/brackets_patterns_override.less
@@ -517,6 +517,20 @@ a:focus {
background-color: @tc-gray-panel;
}
+.modal-body.no-padding {
+ padding: 0;
+}
+
+.modal-body.no-padding td:first-child,
+.modal-body.no-padding th:first-child {
+ padding-left: 15px;
+}
+
+.modal-body.no-padding td:last-child,
+.modal-body.no-padding th:last-child {
+ padding-right: 15px;
+}
+
.modal-body, .modal-header, .modal-footer {
/* See styles/bootstrap/patterns.less .modal class.
Pushing this value down to .modal-header and .modal-body
@@ -536,7 +550,7 @@ a:focus {
.modal-footer .btn {
float: none;
}
-.modal-footer .btn.left {
+.modal-footer .left {
float: left;
/* Transfer the margin to the right of each left-aligned button, so the left side lines up properly
@@ -1105,6 +1119,9 @@ input[type="color"],
tbody th {
border-top: 1px solid transparent);
}
+ .right {
+ text-align: right;
+ }
}
.table-striped tbody {
@@ -1141,6 +1158,10 @@ label {
font-size: @label-font-size;
}
+label.inline {
+ display: inline;
+}
+
/* Overriding Bootstrap's bold properties */
h1,
View
35 src/styles/brackets_scrollbars.less
@@ -32,6 +32,7 @@
::-webkit-scrollbar {
width: 9px;
height: 9px;
+ background-color: transparent;
}
::-webkit-scrollbar-corner {
@@ -85,7 +86,7 @@
::-webkit-scrollbar-thumb {
border-radius: 999px;
- box-shadow: 0 0 0 4px @custom-scrollbar-thumb inset;
+ box-shadow: 0 0 0 4px @linux-scrollbar-thumb inset;
border: 2px solid transparent;
}
@@ -94,6 +95,36 @@
}
::-webkit-scrollbar-thumb:window-inactive {
- box-shadow: 0 0 0 5px @custom-scrollbar-thumb-inactive inset;
+ box-shadow: 0 0 0 5px @linux-scrollbar-thumb-inactive inset;
+ }
+}
+
+.platform-win {
+ ::-webkit-scrollbar {
+ width: 12px;
+ height: 12px;
+ background-color: @win-scrollbar-track;
+ }
+
+ ::-webkit-scrollbar-thumb {
+ box-shadow: 0 0 0 12px @win-scrollbar-thumb inset;
+ }
+ ::-webkit-scrollbar-thumb:hover,
+ ::-webkit-scrollbar-thumb:focus {
+ box-shadow: 0 0 0 12px @win-scrollbar-thumb-hover inset;
+ }
+ ::-webkit-scrollbar-thumb:active {
+ box-shadow: 0 0 0 12px @win-scrollbar-thumb-active inset;
+ }
+
+ ::-webkit-scrollbar-thumb:vertical {
+ min-height: 20px;
+ }
+ ::-webkit-scrollbar-thumb:horizontal {
+ min-width: 20px;
+ }
+
+ ::-webkit-scrollbar-corner {
+ background: none;
}
}
View
9 src/styles/brackets_theme_default.less
@@ -121,8 +121,13 @@
@activeline-bgcolor: #ebefef;
/* custom scrollbar colors */
-@custom-scrollbar-thumb: rgba(0, 0, 0, 0.24);
-@custom-scrollbar-thumb-inactive: rgba(0, 0, 0, 0.12);
+@win-scrollbar-track: rgb(240, 240, 240);
+@win-scrollbar-thumb: rgb(206, 206, 206);
+@win-scrollbar-thumb-hover: rgb(166, 166, 166);
+@win-scrollbar-thumb-active: rgb(96, 96, 96);
+
+@linux-scrollbar-thumb: rgba(0, 0, 0, 0.24);
+@linux-scrollbar-thumb-inactive: rgba(0, 0, 0, 0.12);
/* live preview */
@live-preview-sync-error-background: #ff5d99;
View
13 src/utils/Async.js
@@ -280,8 +280,7 @@ define(function (require, exports, module) {
return masterDeferred.promise();
}
-
-
+
/** Value passed to fail() handlers that have been triggered due to withTimeout()'s timeout */
var ERROR_TIMEOUT = {};
@@ -385,6 +384,16 @@ define(function (require, exports, module) {
PromiseQueue.prototype._curPromise = null;
/**
+ * @type {number} The number of queued promises.
+ */
+ Object.defineProperties(PromiseQueue.prototype, {
+ "length": {
+ get: function () { return this._queue.length; },
+ set: function () { throw new Error("Cannot set length"); }
+ }
+ });
+
+ /**
* Adds an operation to the queue. If nothing is currently executing, it will execute immediately (and
* the next operation added to the queue will wait for it to complete). Otherwise, it will wait until
* the last operation in the queue (or the currently executing operation if nothing is in the queue) is
View
62 src/utils/NodeConnection.js
@@ -24,7 +24,7 @@
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4,
maxerr: 50, browser: true */
-/*global $, define, brackets, WebSocket */
+/*global $, define, brackets, WebSocket, ArrayBuffer, Uint32Array */
define(function (require, exports, module) {
"use strict";
@@ -46,6 +46,9 @@ define(function (require, exports, module) {
/** @define{number} Milliseconds to wait before retrying connecting */
var RETRY_DELAY = 500; // 1/2 second
+ /** @define {number} Maximum value of the command ID counter */
+ var MAX_COUNTER_VALUE = 4294967295; // 2^32 - 1
+
/**
* @private
* Helper function to auto-reject a deferred after a given amount of time.
@@ -74,6 +77,10 @@ define(function (require, exports, module) {
port = nodePort;
ws = new WebSocket("ws://localhost:" + port);
+ // Expect ArrayBuffer objects from Node when receiving binary
+ // data instead of DOM Blobs, which are the default.
+ ws.binaryType = "arraybuffer";
+
// If the server port isn't open, we get a close event
// at some point in the future (and will not get an onopen
// event)
@@ -176,6 +183,23 @@ define(function (require, exports, module) {
/**
* @private
+ * @return {number} The next command ID to use. Always representable as an
+ * unsigned 32-bit integer.
+ */
+ NodeConnection.prototype._getNextCommandID = function () {
+ var nextID;
+
+ if (this._commandCount > MAX_COUNTER_VALUE) {
+ nextID = this._commandCount = 0;
+ } else {
+ nextID = this._commandCount++;
+ }
+
+ return nextID;
+ };
+
+ /**
+ * @private
* Helper function to do cleanup work when a connection fails
*/
NodeConnection.prototype._cleanup = function () {
@@ -402,12 +426,36 @@ define(function (require, exports, module) {
*/
NodeConnection.prototype._receive = function (message) {
var responseDeferred = null;
+ var data = message.data;
var m;
- try {
- m = JSON.parse(message.data);
- } catch (e) {
- console.error("[NodeConnection] received malformed message", message, e.message);
- return;
+
+ if (message.data instanceof ArrayBuffer) {
+ // The first four bytes encode the command ID as an unsigned 32-bit integer
+ if (data.byteLength < 4) {
+ console.error("[NodeConnection] received malformed binary message");
+ return;
+ }
+
+ var header = data.slice(0, 4),
+ body = data.slice(4),
+ headerView = new Uint32Array(header),
+ id = headerView[0];
+
+ // Unpack the binary message into a commandResponse
+ m = {
+ type: "commandResponse",
+ message: {
+ id: id,
+ response: body
+ }
+ };
+ } else {
+ try {
+ m = JSON.parse(data);
+ } catch (e) {
+ console.error("[NodeConnection] received malformed message", message, e.message);
+ return;
+ }
}
switch (m.type) {
@@ -467,7 +515,7 @@ define(function (require, exports, module) {
return function () {
var deferred = $.Deferred();
var parameters = Array.prototype.slice.call(arguments, 0);
- var id = self._commandCount++;
+ var id = self._getNextCommandID();
self._pendingCommandDeferreds[id] = deferred;
self._send({id: id,
domain: domainName,
View
75 src/utils/ViewUtils.js
@@ -296,6 +296,48 @@ define(function (require, exports, module) {
}
/**
+ * Determine how much of an element rect is clipped in view.
+ *
+ * @param {!DOMElement} $view - A jQuery scrolling container
+ * @param {!{top: number, left: number, height: number, width: number}}
+ * elementRect - rectangle of element's default position/size
+ * @return {{top: number, right: number, bottom: number, left: number}}
+ * amount element rect is clipped in each direction
+ */
+ function getElementClipSize($view, elementRect) {
+ var delta,
+ clip = { top: 0, right: 0, bottom: 0, left: 0 },
+ viewOffset = $view.offset() || { top: 0, left: 0},
+ viewScroller = $view.get(0);
+
+ // Check if element extends below viewport
+ delta = (elementRect.top + elementRect.height) - (viewOffset.top + $view.height());
+ if (delta > 0) {
+ clip.bottom = delta;
+ }
+
+ // Check if element extends above viewport
+ delta = viewOffset.top - elementRect.top;
+ if (delta > 0) {
+ clip.top = delta;
+ }
+
+ // Check if element extends to the left of viewport
+ delta = viewOffset.left - elementRect.left;
+ if (delta > 0) {
+ clip.left = delta;
+ }
+
+ // Check if element extends to the right of viewport
+ delta = (elementRect.left + elementRect.width) - (viewOffset.left + $view.width());
+ if (delta > 0) {
+ clip.right = delta;
+ }
+
+ return clip;
+ }
+
+ /**
* Within a scrolling DOMElement, if necessary, scroll element into viewport.
*
* To Perform the minimum amount of scrolling necessary, cases should be handled as follows:
@@ -310,7 +352,7 @@ define(function (require, exports, module) {
*
* @param {!DOMElement} $view - A jQuery scrolling container
* @param {!DOMElement} $element - A jQuery element
- * @param {?boolean} scrollHorizontal - whether to also scroll horizonally
+ * @param {?boolean} scrollHorizontal - whether to also scroll horizontally
*/
function scrollElementIntoView($view, $element, scrollHorizontal) {
var viewOffset = $view.offset(),
@@ -319,25 +361,27 @@ define(function (require, exports, module) {
elementOffset = $element.offset();
// scroll minimum amount
- var delta = (elementOffset.top + $element.height()) - (viewOffset.top + $view.height());
+ var elementRect = {
+ top: elementOffset.top,
+ left: elementOffset.left,
+ height: $element.height(),
+ width: $element.width()
+ },
+ clip = getElementClipSize($view, elementRect);
- if (delta > 0) {
+ if (clip.bottom > 0) {
// below viewport
- $view.scrollTop($view.scrollTop() + delta);
- } else {
- delta = viewOffset.top - elementOffset.top;
-
- if (delta > 0) {
- // above viewport
- $view.scrollTop($view.scrollTop() - delta);
- }
+ $view.scrollTop($view.scrollTop() + clip.bottom);
+ } else if (clip.top > 0) {
+ // above viewport
+ $view.scrollTop($view.scrollTop() - clip.top);
}
if (scrollHorizontal) {
- if (elementOffset.left < 0) {
- $view.scrollLeft($view.scrollLeft() + elementOffset.left);
- } else if (elementOffset.left + $element.width() >= viewOffset.left + $view.width()) {
- $view.scrollLeft(elementOffset.left - viewOffset.left);
+ if (clip.left > 0) {
+ $view.scrollLeft($view.scrollLeft() - clip.left);
+ } else if (clip.right > 0) {
+ $view.scrollLeft($view.scrollLeft() + clip.right);
}
}
}
@@ -427,6 +471,7 @@ define(function (require, exports, module) {
exports.removeScrollerShadow = removeScrollerShadow;
exports.sidebarList = sidebarList;
exports.scrollElementIntoView = scrollElementIntoView;
+ exports.getElementClipSize = getElementClipSize;
exports.getFileEntryDisplay = getFileEntryDisplay;
exports.toggleClass = toggleClass;
exports.getDirNamesForDuplicateFiles = getDirNamesForDuplicateFiles;
View
1 test/UnitTestSuite.js
@@ -29,6 +29,7 @@ define(function (require, exports, module) {
require("spec/Async-test");
require("spec/CodeHint-test");
require("spec/CodeHintUtils-test");
+ require("spec/CodeInspection-test");
require("spec/CommandManager-test");
require("spec/CSSUtils-test");
require("spec/JSUtils-test");
View
3 test/spec/CodeInspection-test-files/errors.css
@@ -0,0 +1,3 @@
+h1 {
+ color: black;
+
View
2 test/spec/CodeInspection-test-files/errors.js
@@ -0,0 +1,2 @@
+// mispelled function keyword
+funtion foo() {};
View
3 test/spec/CodeInspection-test-files/no-errors.js
@@ -0,0 +1,3 @@
+function foo() {
+ "use strict";
+}
View
539 test/spec/CodeInspection-test.js
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 2013 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.
+ *
+ */
+
+/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
+/*global define, describe, it, expect, beforeEach, beforeFirst, afterEach, afterLast, waitsFor, runs, brackets, waitsForDone, spyOn, xit, jasmine */
+
+define(function (require, exports, module) {
+ "use strict";
+
+ var SpecRunnerUtils = require("spec/SpecRunnerUtils"),
+ FileSystem = require("filesystem/FileSystem");
+
+ describe("Code Inspection", function () {
+ this.category = "integration";
+
+ var testFolder = SpecRunnerUtils.getTestPath("/spec/CodeInspection-test-files/"),
+ testWindow,
+ $,
+ brackets,
+ CodeInspection,
+ EditorManager;
+
+ var toggleJSLintResults = function (visible) {
+ $("#status-inspection").triggerHandler("click");
+ expect($("#problems-panel").is(":visible")).toBe(visible);
+ };
+
+ function createCodeInspector(name, result) {
+ var provider = {
+ name: name,
+ // arguments to this function: text, fullPath
+ // omit the warning
+ scanFile: function () { return result; }
+ };
+
+ spyOn(provider, "scanFile").andCallThrough();
+
+ return provider;
+ }
+
+ function successfulLintResult() {
+ return {errors: []};
+ }
+
+ function failLintResult() {
+ return {
+ errors: [
+ {
+ pos: { line: 1, ch: 3 },
+ message: "Some errors here and there",
+ type: CodeInspection.Type.WARNING
+ }
+ ]
+ };
+ }
+
+ beforeFirst(function () {
+ runs(function () {
+ SpecRunnerUtils.createTestWindowAndRun(this, function (w) {
+ testWindow = w;
+ // Load module instances from brackets.test
+ $ = testWindow.$;
+ brackets = testWindow.brackets;
+ EditorManager = brackets.test.EditorManager;
+ CodeInspection = brackets.test.CodeInspection;
+ CodeInspection.toggleEnabled(true);
+ });
+ });
+
+ runs(function () {
+ SpecRunnerUtils.loadProjectInTestWindow(testFolder);
+ });
+ });
+
+ afterEach(function () {
+ testWindow.closeAllFiles();
+ });
+
+ afterLast(function () {