diff --git a/src/LiveDevelopment/Inspector/Inspector.js b/src/LiveDevelopment/Inspector/Inspector.js index a465a35a6af..c06cf6904ac 100644 --- a/src/LiveDevelopment/Inspector/Inspector.js +++ b/src/LiveDevelopment/Inspector/Inspector.js @@ -109,6 +109,16 @@ define(function Inspector(require, exports, module) { * @param {object} the method signature */ function _send(method, signature, varargs) { + if (!_socket) { + // FUTURE: Our current implementation closes and re-opens an inspector connection whenever + // a new HTML file is selected. If done quickly enough, pending requests from the previous + // connection could come in before the new socket connection is established. For now we + // simply ignore this condition. + // This race condition will go away once we support multiple inspector connections and turn + // off auto re-opening when a new HTML file is selected. + return; + } + console.assert(_socket, "You must connect to the WebSocket before sending messages."); var id, callback, args, i, params = {}; diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index 132512caa43..e70261f8e08 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -252,7 +252,7 @@ define(function LiveDevelopment(require, exports, module) { // For Sprint 6, we only open live development connections for HTML files // FUTURE: Remove this test when we support opening connections for different // file types. - if (doc.extension.indexOf('htm') !== 0) { + if (!doc.extension || doc.extension.indexOf('htm') !== 0) { return; } @@ -333,16 +333,18 @@ define(function LiveDevelopment(require, exports, module) { /** Triggered by a document change from the DocumentManager */ function _onDocumentChange() { var doc = _getCurrentDocument(); + if (!doc) { + return; + } + if (Inspector.connected()) { - if (!doc) { - close(); - } else if (agents.network && agents.network.wasURLRequested(doc.url)) { + if (agents.network && agents.network.wasURLRequested(doc.url)) { _closeDocument(); var editor = EditorManager.getCurrentFullEditor(); _openDocument(doc, editor); } else { /* FUTURE: support live connections for docments other than html */ - if (doc.extension.indexOf('htm') === 0) { + if (doc.extension && doc.extension.indexOf('htm') === 0) { close(); setTimeout(open); } diff --git a/src/brackets.js b/src/brackets.js index 96048f41bce..31b7bba4061 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -176,7 +176,6 @@ define(function (require, exports, module) { function initWindowListeners() { // TODO: (issue 269) to support IE, need to listen to document instead (and even then it may not work when focus is in an input field?) $(window).focus(function () { - ProjectManager.reloadProject(); FileSyncManager.syncOpenDocuments(); FileIndexManager.markDirty(); }); diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index 25fed22ca8a..201f96ba0f8 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -7,7 +7,7 @@ define(function (require, exports, module) { 'use strict'; - + // Load dependent modules var DocumentManager = require("document/DocumentManager"), HTMLUtils = require("language/HTMLUtils"), @@ -55,6 +55,7 @@ define(function (require, exports, module) { // Bind event handlers this._updateRelatedContainer = this._updateRelatedContainer.bind(this); + this._onClick = this._onClick.bind(this); // Create DOM to hold editors and related list this.$editorsDiv = $(document.createElement('div')).addClass("inlineEditorHolder"); @@ -111,6 +112,9 @@ define(function (require, exports, module) { // Listen to the window resize event to reposition the relatedContainer // when the hostEditor's scrollbars visibility changes $(window).on("resize", this._updateRelatedContainer); + + // Listen for clicks directly on us, so we can set focus back to the editor + this.$htmlContent.on("click", this._onClick); return (new $.Deferred()).resolve(); }; @@ -206,6 +210,24 @@ define(function (require, exports, module) { this._relatedContainerInserted = true; }; + /** + * Handle a click outside our child editor by setting focus back to it. + */ + CSSInlineEditor.prototype._onClick = function (event) { + var childEditor = this.editors[0], + editorRoot = childEditor.getRootElement(), + editorPos = $(editorRoot).offset(); + if (!$.contains(editorRoot, event.target)) { + childEditor.focus(); + if (event.pageY < editorPos.top) { + childEditor.setCursorPos(0, 0); + } else if (event.pageY > editorPos.top + $(editorRoot).height()) { + var lastLine = childEditor.getLastVisibleLine(); + childEditor.setCursorPos(lastLine, childEditor.getLineText(lastLine).length); + } + } + }; + /** * * @@ -377,7 +399,7 @@ define(function (require, exports, module) { var result = new $.Deferred(); - CSSUtils.findMatchingRules(selectorName) + CSSUtils.findMatchingRules(selectorName, hostEditor.document) .done(function (rules) { if (rules && rules.length > 0) { var cssInlineEditor = new CSSInlineEditor(rules); diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 46d6d4e9de3..0e5e14e7986 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -357,14 +357,8 @@ define(function (require, exports, module) { * bugs in CodeMirror when lines are hidden. */ Editor.prototype._selectAllVisible = function () { - var startLine, endLine; - if (this._visibleRange) { - startLine = this._visibleRange.startLine; - endLine = this._visibleRange.endLine; - } else { - startLine = 0; - endLine = this.lineCount() - 1; - } + var startLine = this.getFirstVisibleLine(), + endLine = this.getLastVisibleLine(); this.setSelection({line: startLine, ch: 0}, {line: endLine, ch: this.getLineText(endLine).length}); }; @@ -664,6 +658,22 @@ define(function (require, exports, module) { Editor.prototype.lineCount = function () { return this._codeMirror.lineCount(); }; + + /** + * Gets the number of the first visible line in the editor. + * @returns {number} The 0-based index of the first visible line. + */ + Editor.prototype.getFirstVisibleLine = function () { + return (this._visibleRange ? this._visibleRange.startLine : 0); + }; + + /** + * Gets the number of the last visible line in the editor. + * @returns {number} The 0-based index of the last visible line. + */ + Editor.prototype.getLastVisibleLine = function () { + return (this._visibleRange ? this._visibleRange.endLine : this.lineCount() - 1); + }; /* Hides the specified line number in the editor * @param {!number} @@ -689,6 +699,13 @@ define(function (require, exports, module) { return this._codeMirror.getScrollerElement(); }; + /** + * Gets the root DOM node of the editor. + * @returns {Object} The editor's root DOM node. + */ + Editor.prototype.getRootElement = function () { + return this._codeMirror.getWrapperElement(); + }; /** * Adds an inline widget below the given line. If any inline widget was already open for that diff --git a/src/language/CSSUtils.js b/src/language/CSSUtils.js index 1b9c361e371..51447f200ea 100644 --- a/src/language/CSSUtils.js +++ b/src/language/CSSUtils.js @@ -13,6 +13,8 @@ define(function (require, exports, module) { var Async = require("utils/Async"), DocumentManager = require("document/DocumentManager"), + EditorManager = require("editor/EditorManager"), + HTMLUtils = require("language/HTMLUtils"), FileIndexManager = require("project/FileIndexManager"), NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem; @@ -38,7 +40,7 @@ define(function (require, exports, module) { * @param text {!String} CSS text to extract from * @return {Array.} Array with objects specifying selectors. */ - function extractAllSelectors(text) { + function _extractAllSelectors(text) { var selectors = []; var mode = CodeMirror.getMode({indentUnit: 2}, "css"); var state = CodeMirror.startState(mode); @@ -206,11 +208,12 @@ define(function (require, exports, module) { * * @param text {!String} CSS text to search * @param selector {!String} selector to search for - * @return {Array.<{line:number, declListEndLine:number}>} Array of objects containing the start - * and end line numbers (0-based, inclusive range) for each matched selector. + * @return {Array.<{selectorGroupStartLine:number, declListEndLine:number, selector:string}>} + * Array of objects containing the start and end line numbers (0-based, inclusive range) for each + * matched selector. */ function _findAllMatchingSelectorsInText(text, selector) { - var allSelectors = extractAllSelectors(text); + var allSelectors = _extractAllSelectors(text); var result = []; var i; @@ -244,6 +247,82 @@ define(function (require, exports, module) { return result; } + + /** + * Converts the results of _findAllMatchingSelectorsInText() into a simpler bag of data and + * appends those new objects to the given 'resultSelectors' Array. + * @param {Array.<{document:Document, lineStart:number, lineEnd:number}>} resultSelectors + * @param {Array.<{selectorGroupStartLine:number, declListEndLine:number, selector:string}>} selectorsToAdd + * @param {!Document} sourceDoc + * @param {!number} lineOffset Amount to offset all line number info by. Used if the first line + * of the parsed CSS text is not the first line of the sourceDoc. + */ + function _addSelectorsToResults(resultSelectors, selectorsToAdd, sourceDoc, lineOffset) { + selectorsToAdd.forEach(function (selectorInfo) { + resultSelectors.push({ + selector: selectorInfo.selector, + document: sourceDoc, + lineStart: selectorInfo.ruleStartLine + lineOffset, + lineEnd: selectorInfo.declListEndLine + lineOffset + }); + }); + } + + /** Finds matching selectors in CSS files; adds them to 'resultSelectors' */ + function _findMatchingRulesInCSSFiles(selector, resultSelectors) { + var result = new $.Deferred(), + cssFilesResult = FileIndexManager.getFileInfoList("css"); + + // Load one CSS file and search its contents + function _loadFileAndScan(fullPath, selector) { + var oneFileResult = new $.Deferred(); + + DocumentManager.getDocumentForPath(fullPath) + .done(function (doc) { + // Find all matching rules for the given CSS file's content, and add them to the + // overall search result + var oneCSSFileMatches = _findAllMatchingSelectorsInText(doc.getText(), selector); + _addSelectorsToResults(resultSelectors, oneCSSFileMatches, doc, 0); + + oneFileResult.resolve(); + }) + .fail(function (error) { + oneFileResult.reject(error); + }); + + return oneFileResult.promise(); + } + + // Load index of all CSS files; then process each CSS file in turn (see above) + cssFilesResult.done(function (fileInfos) { + Async.doInParallel(fileInfos, function (fileInfo, number) { + return _loadFileAndScan(fileInfo.fullPath, selector); + }) + .pipe(result.resolve, result.reject); + }); + + return result.promise(); + } + + /** Finds matching selectors in the