From 3d213fdc97cd5d282cef73cdf76904cfcd1ebe5c Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Mon, 1 Oct 2012 12:21:52 -0700 Subject: [PATCH 1/8] initial commit for url hinting --- .../default/HTMLCodeHints/HtmlAttributes.json | 4 +- src/extensions/default/HTMLCodeHints/main.js | 126 +++++++++++++++++- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/extensions/default/HTMLCodeHints/HtmlAttributes.json b/src/extensions/default/HTMLCodeHints/HtmlAttributes.json index 97227f602e4..cf5b34a615b 100644 --- a/src/extensions/default/HTMLCodeHints/HtmlAttributes.json +++ b/src/extensions/default/HTMLCodeHints/HtmlAttributes.json @@ -138,7 +138,7 @@ "headers": { "attribOption": [] }, "height": { "attribOption": [] }, "high": { "attribOption": [] }, - "href": { "attribOption": [] }, + "href": { "attribOption": [], "type": "url" }, "hreflang": { "attribOption": [] }, "hspace": { "attribOption": [] }, "http-equiv": { "attribOption": ["content-type", "default-style", "refresh"] }, @@ -197,7 +197,7 @@ "size": { "attribOption": [] }, "sizes": { "attribOption": ["any"] }, "span": { "attribOption": [] }, - "src": { "attribOption": [] }, + "src": { "attribOption": [], "type": "url" }, "srcdoc": { "attribOption": [] }, "srclang": { "attribOption": [] }, "standby": { "attribOption": [] }, diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index e5516ff2f05..b9dcd9f0b43 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -29,12 +29,15 @@ define(function (require, exports, module) { "use strict"; // Load dependent modules - var HTMLUtils = brackets.getModule("language/HTMLUtils"), - HTMLTags = require("text!HtmlTags.json"), - HTMLAttributes = require("text!HtmlAttributes.json"), - CodeHintManager = brackets.getModule("editor/CodeHintManager"), - tags = JSON.parse(HTMLTags), - attributes = JSON.parse(HTMLAttributes); + var CodeHintManager = brackets.getModule("editor/CodeHintManager"), + DocumentManager = brackets.getModule("document/DocumentManager"), + EditorManager = brackets.getModule("editor/EditorManager"), + HTMLUtils = brackets.getModule("language/HTMLUtils"), + NativeFileSystem = brackets.getModule("file/NativeFileSystem").NativeFileSystem, + HTMLTags = require("text!HtmlTags.json"), + HTMLAttributes = require("text!HtmlAttributes.json"), + tags = JSON.parse(HTMLTags), + attributes = JSON.parse(HTMLAttributes); /** * @constructor @@ -126,6 +129,7 @@ define(function (require, exports, module) { */ function AttrHints() { this.globalAttributes = this.readGlobalAttrHints(); + this.cachedHints = null; } /** @@ -246,6 +250,113 @@ define(function (require, exports, module) { return query; }; + /** + * Helper function for search(). Create a list of urls to existing files base on the query. + * @param {Object.} + */ + AttrHints.prototype._getUrlList = function (query) { + var doc, + result = []; + + // get path to current document + doc = DocumentManager.getCurrentDocument(); + if (!doc || !doc.file) { + return result; + } + + var docUrl = window.PathUtils.parseUrl(doc.file.fullPath); + if (!docUrl) { + return result; + } + + var docDir = docUrl.domain + docUrl.directory; + + // get relative path from query string + // TODO: detect absolute path and exit + // TODO: need to handle "../" ? + // TODO: handle site-root relative + var queryDir = ""; + var queryUrl = window.PathUtils.parseUrl(query.queryStr); + if (queryUrl) { + queryDir = queryUrl.directory; + } + + // build target folder path + var targetDir = docDir + queryDir; + console.log("_getUrlList: dir=" + targetDir); + + // get list of files from target folder + var unfiltered = []; + var useCache = false; // TODO: is this flag necessary? + + // Getting the file/folder info is an asynch operation, so it works like this: + // + // The initial pass initiates the asynchronous retrieval of data and returns an + // empty list, so no code hints are displayed. In the async callback, the code + // hints and the original query are stored in a cache, and then the process to + // show code hints is re-initiated. + // + // During the next pass, there should now be code hints cached from the initial + // pass, but user may have typed while file/folder info was being retrieved from + // disk, so we need to make sure code hints still apply to current query. If so, + // display them, otherwise, clear cache and start over. + // + // As user types within a folder, the same unfiltered file/folder list is still + // valid and re-used from cache. Filtering based on user input is done outside + // of this method. When user moves to a new folder, then the cache is deleted, + // and file/folder info for new folder is then retrieved. + + if (this.cachedHints) { + // Determine if cached url hints are valid with current query + if (this.cachedHints.query.tag === query.tag && + this.cachedHints.query.attrName === query.attrName && + this.cachedHints.queryDir === queryDir) { + useCache = true; + } else { + this.cachedHints = null; + } + } + +// TODO: FIX RACE CONDITION: detect case where file/folder info is already in the process of being retrieved +// in this case, return empty list. + + if (useCache) { + unfiltered = this.cachedHints.unfiltered; + } else { + var self = this; + NativeFileSystem.requestNativeFileSystem(targetDir, function (dirEntry) { + dirEntry.createReader().readEntries(function (entries) { + entries.forEach(function (entry) { + var entryStr = entry.fullPath.replace(docDir, ""); + unfiltered.push(entryStr); + }); + + self.cachedHints = {}; + self.cachedHints.unfiltered = unfiltered; + self.cachedHints.query = query; + self.cachedHints.queryDir = queryDir; + + // re-initiate code hints + CodeHintManager.showHint(EditorManager.getFocusedEditor()); + }); + }); + return result; + } + + // filter list of files from query string + result = unfiltered; // for now... + + // TODO: filter by desired file type based on tag, type attr, etc. + + // TODO: add list item to top of list to popup modal File Finder dialog + // New string: "Browse..." or "Choose a File..." + // Command: Commands.FILE_OPEN + + return result; + }; + /** * Create a complete list of attributes for the tag in the query. Then filter * the list by attrName in the query and return the result. @@ -274,6 +385,8 @@ define(function (require, exports, module) { if (attrInfo) { if (attrInfo.type === "boolean") { unfiltered = ["false", "true"]; + } else if (attrInfo.type === "url") { + unfiltered = this._getUrlList(query); } else if (attrInfo.attribOption) { unfiltered = attrInfo.attribOption; } @@ -285,6 +398,7 @@ define(function (require, exports, module) { } if (unfiltered.length) { + console.assert(!result.length); result = $.map(unfiltered, function (item) { if (item.indexOf(filter) === 0) { return item; From d76d76f41ab2736a9bfef3e761e5a92e8e38902b Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Mon, 1 Oct 2012 19:06:03 -0700 Subject: [PATCH 2/8] fix race condition, handle ../ paths --- src/extensions/default/HTMLCodeHints/main.js | 49 +++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index b9dcd9f0b43..8c3ebe3eacc 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -274,8 +274,6 @@ define(function (require, exports, module) { var docDir = docUrl.domain + docUrl.directory; // get relative path from query string - // TODO: detect absolute path and exit - // TODO: need to handle "../" ? // TODO: handle site-root relative var queryDir = ""; var queryUrl = window.PathUtils.parseUrl(query.queryStr); @@ -285,11 +283,9 @@ define(function (require, exports, module) { // build target folder path var targetDir = docDir + queryDir; - console.log("_getUrlList: dir=" + targetDir); // get list of files from target folder var unfiltered = []; - var useCache = false; // TODO: is this flag necessary? // Getting the file/folder info is an asynch operation, so it works like this: // @@ -308,24 +304,30 @@ define(function (require, exports, module) { // of this method. When user moves to a new folder, then the cache is deleted, // and file/folder info for new folder is then retrieved. - if (this.cachedHints) { - // Determine if cached url hints are valid with current query - if (this.cachedHints.query.tag === query.tag && - this.cachedHints.query.attrName === query.attrName && - this.cachedHints.queryDir === queryDir) { - useCache = true; - } else { + if (this.cachedHints && !this.cachedHints.waiting) { + // url hints have been cached, so determine if they're are stale + if (!this.cachedHints.query || + this.cachedHints.query.tag !== query.tag || + this.cachedHints.query.attrName !== query.attrName || + this.cachedHints.queryDir !== queryDir) { + + // delete stale cache this.cachedHints = null; } } -// TODO: FIX RACE CONDITION: detect case where file/folder info is already in the process of being retrieved -// in this case, return empty list. - - if (useCache) { + if (this.cachedHints) { + // if (cachedHints.waiting === true), then we'll return an empty list + // and not start another readEntries() operation unfiltered = this.cachedHints.unfiltered; } else { var self = this; + + // create empty object so we can detect "waiting" state + self.cachedHints = {}; + self.cachedHints.unfiltered = []; + self.cachedHints.waiting = true; + NativeFileSystem.requestNativeFileSystem(targetDir, function (dirEntry) { dirEntry.createReader().readEntries(function (entries) { entries.forEach(function (entry) { @@ -333,10 +335,10 @@ define(function (require, exports, module) { unfiltered.push(entryStr); }); - self.cachedHints = {}; self.cachedHints.unfiltered = unfiltered; self.cachedHints.query = query; self.cachedHints.queryDir = queryDir; + self.cachedHints.waiting = false; // re-initiate code hints CodeHintManager.showHint(EditorManager.getFocusedEditor()); @@ -345,8 +347,19 @@ define(function (require, exports, module) { return result; } - // filter list of files from query string - result = unfiltered; // for now... + // build list + + // without these entries, typing "../" will not display entries for containing folder + if (queryUrl.filename === ".") { + result.push(queryDir + "."); + } else if (queryUrl.filename === "..") { + result.push(queryDir + ".."); + } + + // add file/folder entries + unfiltered.forEach(function (item) { + result.push(item); + }); // TODO: filter by desired file type based on tag, type attr, etc. From b21c2f1097b39bb7b6cc439cb450383b988bd3a3 Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Tue, 2 Oct 2012 09:42:57 -0700 Subject: [PATCH 3/8] use Tab key to 'select and continue hinting' --- src/editor/CodeHintManager.js | 76 +++++++++++++------- src/extensions/default/HTMLCodeHints/main.js | 26 ++++--- 2 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/editor/CodeHintManager.js b/src/editor/CodeHintManager.js index 7c4e442b262..546c640f8fa 100644 --- a/src/editor/CodeHintManager.js +++ b/src/editor/CodeHintManager.js @@ -73,14 +73,23 @@ define(function (require, exports, module) { /** * @private - * Enters the code completion text into the editor + * Enters the code completion text into the editor and closes list * @string {string} completion - text to insert into current code editor */ CodeHintList.prototype._handleItemClick = function (completion) { - this.currentProvider.handleSelect(completion, this.editor, this.editor.getCursorPos()); + this.currentProvider.handleSelect(completion, this.editor, this.editor.getCursorPos(), true); this.close(); }; + /** + * @private + * Enters the code completion text into the editor without closing list + * @string {string} completion - text to insert into current code editor + */ + CodeHintList.prototype._handleItemSelect = function (completion) { + this.currentProvider.handleSelect(completion, this.editor, this.editor.getCursorPos(), false); + }; + /** * Adds a single item to the hint list * @param {string} name @@ -98,6 +107,12 @@ define(function (require, exports, module) { // bootstrap-dropdown). e.stopPropagation(); self._handleItemClick(name); + }) + .on("select", function (e) { + // Don't let the "select" propagate upward (otherwise it will hit the close handler in + // bootstrap-dropdown). + e.stopPropagation(); + self._handleItemSelect(name); }); this.$hintMenu.find("ul.dropdown-menu") @@ -179,32 +194,39 @@ define(function (require, exports, module) { var keyCode = event.keyCode; // Up arrow, down arrow and enter key are always handled here - if (event.type !== "keypress" && - (keyCode === KeyEvent.DOM_VK_UP || keyCode === KeyEvent.DOM_VK_DOWN || keyCode === KeyEvent.DOM_VK_RETURN || - keyCode === KeyEvent.DOM_VK_PAGE_UP || keyCode === KeyEvent.DOM_VK_PAGE_DOWN)) { - - if (event.type === "keydown") { - if (keyCode === KeyEvent.DOM_VK_UP) { - // Up arrow - this.setSelectedIndex(this.selectedIndex - 1); - } else if (keyCode === KeyEvent.DOM_VK_DOWN) { - // Down arrow - this.setSelectedIndex(this.selectedIndex + 1); - } else if (keyCode === KeyEvent.DOM_VK_PAGE_UP) { - // Page Up - this.setSelectedIndex(this.selectedIndex - this.getItemsPerPage()); - } else if (keyCode === KeyEvent.DOM_VK_PAGE_DOWN) { - // Page Down - this.setSelectedIndex(this.selectedIndex + this.getItemsPerPage()); - } else { - // Enter/return key - // Trigger a click handler to commmit the selected item - $(this.$hintMenu.find("li")[this.selectedIndex]).triggerHandler("click"); + if (event.type !== "keypress") { + + if (keyCode === KeyEvent.DOM_VK_UP || keyCode === KeyEvent.DOM_VK_DOWN || keyCode === KeyEvent.DOM_VK_RETURN || + keyCode === KeyEvent.DOM_VK_PAGE_UP || keyCode === KeyEvent.DOM_VK_PAGE_DOWN) { + + if (event.type === "keydown") { + if (keyCode === KeyEvent.DOM_VK_UP) { + // Up arrow + this.setSelectedIndex(this.selectedIndex - 1); + } else if (keyCode === KeyEvent.DOM_VK_DOWN) { + // Down arrow + this.setSelectedIndex(this.selectedIndex + 1); + } else if (keyCode === KeyEvent.DOM_VK_PAGE_UP) { + // Page Up + this.setSelectedIndex(this.selectedIndex - this.getItemsPerPage()); + } else if (keyCode === KeyEvent.DOM_VK_PAGE_DOWN) { + // Page Down + this.setSelectedIndex(this.selectedIndex + this.getItemsPerPage()); + } else { + // Enter/return key + // Trigger a click handler to commmit the selected item + $(this.$hintMenu.find("li")[this.selectedIndex]).triggerHandler("click"); + } } + + event.preventDefault(); + return; + + } else if (keyCode === KeyEvent.DOM_VK_TAB) { + // Tab key is used for "select and continue hinting" + $(this.$hintMenu.find("li")[this.selectedIndex]).triggerHandler("select"); + event.preventDefault(); } - - event.preventDefault(); - return; } // All other key events trigger a rebuild of the list, but only @@ -420,7 +442,7 @@ define(function (require, exports, module) { * * @param {Object.< getQueryInfo: function(editor, cursor), * search: function(string), - * handleSelect: function(string, Editor, cursor), + * handleSelect: function(string, Editor, cursor, closeHints), * shouldShowHintsOnKey: function(string)>} * * Parameter Details: diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index 8c3ebe3eacc..4e24066e168 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -91,8 +91,9 @@ define(function (require, exports, module) { * @param {string} completion - text to insert into current code editor * @param {Editor} editor * @param {Cursor} current cursor location + * @param {boolean} closeHints - true to close hints, or false to continue hinting */ - TagHints.prototype.handleSelect = function (completion, editor, cursor) { + TagHints.prototype.handleSelect = function (completion, editor, cursor, closeHints) { var start = {line: -1, ch: -1}, end = {line: -1, ch: -1}, tagInfo = HTMLUtils.getTagInfo(editor, cursor), @@ -150,8 +151,9 @@ define(function (require, exports, module) { * @param {string} completion - text to insert into current code editor * @param {Editor} editor * @param {Cursor} current cursor location + * @param {boolean} closeHints - true to close hints, or false to continue hinting */ - AttrHints.prototype.handleSelect = function (completion, editor, cursor) { + AttrHints.prototype.handleSelect = function (completion, editor, cursor, closeHints) { var start = {line: -1, ch: -1}, end = {line: -1, ch: -1}, tagInfo = HTMLUtils.getTagInfo(editor, cursor), @@ -199,15 +201,17 @@ define(function (require, exports, module) { } } - if (insertedName) { - editor.setCursorPos(start.line, start.ch + completion.length - 1); - - // Since we're now inside the double-quotes we just inserted, - // mmediately pop up the attribute value hint. - CodeHintManager.showHint(editor); - } else if (tokenType === HTMLUtils.ATTR_VALUE && tagInfo.attr.hasEndQuote) { - // Move the cursor to the right of the existing end quote after value insertion. - editor.setCursorPos(start.line, start.ch + completion.length + 1); + if (closeHints) { + if (insertedName) { + editor.setCursorPos(start.line, start.ch + completion.length - 1); + + // Since we're now inside the double-quotes we just inserted, + // immediately pop up the attribute value hint. + CodeHintManager.showHint(editor); + } else if (tokenType === HTMLUtils.ATTR_VALUE && tagInfo.attr.hasEndQuote) { + // Move the cursor to the right of the existing end quote after value insertion. + editor.setCursorPos(start.line, start.ch + completion.length + 1); + } } }; From 3dba737a5373e9a11cd41478b788a9a900f2309c Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Thu, 4 Oct 2012 16:14:26 -0700 Subject: [PATCH 4/8] intermediate fix for encoding --- src/extensions/default/HTMLCodeHints/main.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index 4e24066e168..c355e70541d 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -176,6 +176,18 @@ define(function (require, exports, module) { shouldReplace = false; } } else if (tokenType === HTMLUtils.ATTR_VALUE) { +/*** + // Value encoding + if (closeHints) { + var tagPlusAttr = tagInfo.tagName + "/" + tagInfo.attr.name, + attrInfo = attributes[tagPlusAttr] || attributes[tagInfo.attr.name], + needsEncoding = attrInfo && (attrInfo.type === "url"); + if (needsEncoding) { + completion = encodeURI(completion); + } + } +***/ + charCount = tagInfo.attr.value.length; if (!tagInfo.attr.hasEndQuote) { endQuote = tagInfo.attr.quoteChar; @@ -308,7 +320,8 @@ define(function (require, exports, module) { // of this method. When user moves to a new folder, then the cache is deleted, // and file/folder info for new folder is then retrieved. - if (this.cachedHints && !this.cachedHints.waiting) { +// if (this.cachedHints && !this.cachedHints.waiting) { + if (this.cachedHints) { // url hints have been cached, so determine if they're are stale if (!this.cachedHints.query || this.cachedHints.query.tag !== query.tag || @@ -335,6 +348,7 @@ define(function (require, exports, module) { NativeFileSystem.requestNativeFileSystem(targetDir, function (dirEntry) { dirEntry.createReader().readEntries(function (entries) { entries.forEach(function (entry) { + // convert to doc relative path var entryStr = entry.fullPath.replace(docDir, ""); unfiltered.push(entryStr); }); From 199899aad8d2f4c8017b8d733d705ff8249282e5 Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Thu, 4 Oct 2012 17:31:52 -0700 Subject: [PATCH 5/8] encode url hints --- src/extensions/default/HTMLCodeHints/main.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index c355e70541d..b7963bd20e5 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -176,18 +176,6 @@ define(function (require, exports, module) { shouldReplace = false; } } else if (tokenType === HTMLUtils.ATTR_VALUE) { -/*** - // Value encoding - if (closeHints) { - var tagPlusAttr = tagInfo.tagName + "/" + tagInfo.attr.name, - attrInfo = attributes[tagPlusAttr] || attributes[tagInfo.attr.name], - needsEncoding = attrInfo && (attrInfo.type === "url"); - if (needsEncoding) { - completion = encodeURI(completion); - } - } -***/ - charCount = tagInfo.attr.value.length; if (!tagInfo.attr.hasEndQuote) { endQuote = tagInfo.attr.quoteChar; @@ -298,7 +286,7 @@ define(function (require, exports, module) { } // build target folder path - var targetDir = docDir + queryDir; + var targetDir = docDir + decodeURI(queryDir); // get list of files from target folder var unfiltered = []; @@ -320,7 +308,6 @@ define(function (require, exports, module) { // of this method. When user moves to a new folder, then the cache is deleted, // and file/folder info for new folder is then retrieved. -// if (this.cachedHints && !this.cachedHints.waiting) { if (this.cachedHints) { // url hints have been cached, so determine if they're are stale if (!this.cachedHints.query || @@ -350,7 +337,9 @@ define(function (require, exports, module) { entries.forEach(function (entry) { // convert to doc relative path var entryStr = entry.fullPath.replace(docDir, ""); - unfiltered.push(entryStr); + // code hints show the same strings that are inserted into text, + // so strings in list will be encoded. wysiwyg, baby! + unfiltered.push(encodeURI(entryStr)); }); self.cachedHints.unfiltered = unfiltered; From 7e6c3e410ac964333b43148806fd7eac126f4835 Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Fri, 5 Oct 2012 09:05:07 -0700 Subject: [PATCH 6/8] do not list hidden files --- src/extensions/default/HTMLCodeHints/main.js | 19 +++++++++++++------ src/project/ProjectManager.js | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index b7963bd20e5..5c038131baa 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -34,6 +34,7 @@ define(function (require, exports, module) { EditorManager = brackets.getModule("editor/EditorManager"), HTMLUtils = brackets.getModule("language/HTMLUtils"), NativeFileSystem = brackets.getModule("file/NativeFileSystem").NativeFileSystem, + ProjectManager = brackets.getModule("project/ProjectManager"), HTMLTags = require("text!HtmlTags.json"), HTMLAttributes = require("text!HtmlAttributes.json"), tags = JSON.parse(HTMLTags), @@ -309,7 +310,7 @@ define(function (require, exports, module) { // and file/folder info for new folder is then retrieved. if (this.cachedHints) { - // url hints have been cached, so determine if they're are stale + // url hints have been cached, so determine if they're stale if (!this.cachedHints.query || this.cachedHints.query.tag !== query.tag || this.cachedHints.query.attrName !== query.attrName || @@ -324,6 +325,7 @@ define(function (require, exports, module) { // if (cachedHints.waiting === true), then we'll return an empty list // and not start another readEntries() operation unfiltered = this.cachedHints.unfiltered; + } else { var self = this; @@ -334,12 +336,16 @@ define(function (require, exports, module) { NativeFileSystem.requestNativeFileSystem(targetDir, function (dirEntry) { dirEntry.createReader().readEntries(function (entries) { + entries.forEach(function (entry) { - // convert to doc relative path - var entryStr = entry.fullPath.replace(docDir, ""); - // code hints show the same strings that are inserted into text, - // so strings in list will be encoded. wysiwyg, baby! - unfiltered.push(encodeURI(entryStr)); + if (ProjectManager.shouldShow(entry)) { + // convert to doc relative path + var entryStr = entry.fullPath.replace(docDir, ""); + + // code hints show the same strings that are inserted into text, + // so strings in list will be encoded. wysiwyg, baby! + unfiltered.push(encodeURI(entryStr)); + } }); self.cachedHints.unfiltered = unfiltered; @@ -351,6 +357,7 @@ define(function (require, exports, module) { CodeHintManager.showHint(EditorManager.getFocusedEditor()); }); }); + return result; } diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index cd9849e518a..04cef75dedc 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -510,7 +510,7 @@ define(function (require, exports, module) { /** @param {Entry} entry File or directory to filter */ function shouldShow(entry) { - return [".git", ".svn", ".DS_Store", "Thumbs.db"].indexOf(entry.name) === -1; + return [".git", ".gitignore", ".gitmodules", ".svn", ".DS_Store", "Thumbs.db"].indexOf(entry.name) === -1; } /** From fefe7bd1d09fd789b1d96a7e916fc91fd0321963 Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Fri, 5 Oct 2012 11:11:50 -0700 Subject: [PATCH 7/8] other code review changes --- src/extensions/default/HTMLCodeHints/main.js | 24 ++++++++++++++----- src/utils/StringUtils.js | 25 +++++++++++++++++++- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index 5c038131baa..8aa4856b5a2 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -35,6 +35,7 @@ define(function (require, exports, module) { HTMLUtils = brackets.getModule("language/HTMLUtils"), NativeFileSystem = brackets.getModule("file/NativeFileSystem").NativeFileSystem, ProjectManager = brackets.getModule("project/ProjectManager"), + StringUtils = brackets.getModule("utils/StringUtils"), HTMLTags = require("text!HtmlTags.json"), HTMLAttributes = require("text!HtmlAttributes.json"), tags = JSON.parse(HTMLTags), @@ -256,7 +257,7 @@ define(function (require, exports, module) { }; /** - * Helper function for search(). Create a list of urls to existing files base on the query. + * Helper function for search(). Create a list of urls to existing files based on the query. * @param {Object.} @@ -265,6 +266,11 @@ define(function (require, exports, module) { var doc, result = []; + // site-root relative links are not yet supported, so filter them out + if (query.queryStr.length > 0 && query.queryStr[0] === "/") { + return result; + } + // get path to current document doc = DocumentManager.getCurrentDocument(); if (!doc || !doc.file) { @@ -327,7 +333,8 @@ define(function (require, exports, module) { unfiltered = this.cachedHints.unfiltered; } else { - var self = this; + var self = this, + origEditor = EditorManager.getFocusedEditor(); // create empty object so we can detect "waiting" state self.cachedHints = {}; @@ -353,8 +360,11 @@ define(function (require, exports, module) { self.cachedHints.queryDir = queryDir; self.cachedHints.waiting = false; - // re-initiate code hints - CodeHintManager.showHint(EditorManager.getFocusedEditor()); + // If the editor has not changed, then re-initiate code hints. Cached data + // is still valid for folder even if we're not going to show it now. + if (origEditor === EditorManager.getFocusedEditor()) { + CodeHintManager.showHint(origEditor); + } }); }); @@ -398,7 +408,8 @@ define(function (require, exports, module) { var tagName = query.tag, attrName = query.attrName, filter = query.queryStr, - unfiltered = []; + unfiltered = [], + sortFunc = null; if (attrName) { // We look up attribute values with tagName plus a slash and attrName first. @@ -414,6 +425,7 @@ define(function (require, exports, module) { unfiltered = ["false", "true"]; } else if (attrInfo.type === "url") { unfiltered = this._getUrlList(query); + sortFunc = StringUtils.urlSort; } else if (attrInfo.attribOption) { unfiltered = attrInfo.attribOption; } @@ -430,7 +442,7 @@ define(function (require, exports, module) { if (item.indexOf(filter) === 0) { return item; } - }).sort(); + }).sort(sortFunc); } } diff --git a/src/utils/StringUtils.js b/src/utils/StringUtils.js index 21ef65a48f2..a89b604f6a4 100644 --- a/src/utils/StringUtils.js +++ b/src/utils/StringUtils.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $ */ +/*global define, $, brackets */ /** * Utilities functions related to string manipulation @@ -119,6 +119,28 @@ define(function (require, exports, module) { } } + function urlSort(a, b) { + var a2, b2; + function isFile(s) { + return ((s.lastIndexOf("/") + 1) < s.length); + } + + if (brackets.platform === "win") { + // Windows: prepend folder names with a '0' and file names with a '1' so folders are listed first + a2 = ((isFile(a)) ? "1" : "0") + a.toLowerCase(); + b2 = ((isFile(b)) ? "1" : "0") + b.toLowerCase(); + } else { + a2 = a.toLowerCase(); + b2 = b.toLowerCase(); + } + + if (a2 === b2) { + return 0; + } else { + return (a2 > b2) ? 1 : -1; + } + } + // Define public API exports.format = format; exports.htmlEscape = htmlEscape; @@ -126,4 +148,5 @@ define(function (require, exports, module) { exports.jQueryIdEscape = jQueryIdEscape; exports.getLines = getLines; exports.offsetToLineNum = offsetToLineNum; + exports.urlSort = urlSort; }); From 8295aac6c39c33db01b5c446e66de256dbd19a86 Mon Sep 17 00:00:00 2001 From: Randy Edmunds Date: Fri, 5 Oct 2012 14:07:44 -0700 Subject: [PATCH 8/8] code cleanup --- src/extensions/default/HTMLCodeHints/main.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/extensions/default/HTMLCodeHints/main.js b/src/extensions/default/HTMLCodeHints/main.js index 8aa4856b5a2..ea05f0d5a9b 100644 --- a/src/extensions/default/HTMLCodeHints/main.js +++ b/src/extensions/default/HTMLCodeHints/main.js @@ -328,8 +328,7 @@ define(function (require, exports, module) { } if (this.cachedHints) { - // if (cachedHints.waiting === true), then we'll return an empty list - // and not start another readEntries() operation + // use cached hints unfiltered = this.cachedHints.unfiltered; } else { @@ -339,7 +338,6 @@ define(function (require, exports, module) { // create empty object so we can detect "waiting" state self.cachedHints = {}; self.cachedHints.unfiltered = []; - self.cachedHints.waiting = true; NativeFileSystem.requestNativeFileSystem(targetDir, function (dirEntry) { dirEntry.createReader().readEntries(function (entries) { @@ -358,7 +356,6 @@ define(function (require, exports, module) { self.cachedHints.unfiltered = unfiltered; self.cachedHints.query = query; self.cachedHints.queryDir = queryDir; - self.cachedHints.waiting = false; // If the editor has not changed, then re-initiate code hints. Cached data // is still valid for folder even if we're not going to show it now.