Skip to content
This repository has been archived by the owner on Sep 6, 2021. It is now read-only.

CSS Url Hinting support #3485

Merged
merged 34 commits into from
May 29, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
04d518e
move html url hinting to a new hint provider
redmunds Mar 6, 2013
2e51dd3
intermediate checkin
redmunds Mar 7, 2013
e7c34d7
temporary hack for url()
redmunds Mar 7, 2013
5fc0f12
Merge remote-tracking branch 'origin/master' into randy/css-url-hinting
redmunds Mar 10, 2013
e92470e
another intermediate checkin
redmunds Mar 11, 2013
dcfca87
basic url() support
redmunds Mar 12, 2013
a2805a3
remove properties file
redmunds Mar 13, 2013
a24acbf
only put cursor inside parens for url()
redmunds Mar 13, 2013
b4f0d8d
Merge remote-tracking branch 'origin/master' into randy/css-url-hinting
redmunds Mar 29, 2013
720c32b
do not show hints after '?' in url
redmunds Apr 16, 2013
b44b552
Merge remote-tracking branch 'origin/master' into randy/css-url-hinting
redmunds Apr 19, 2013
62ea61b
cleanup data
redmunds Apr 30, 2013
27e49b9
Merge remote-tracking branch 'origin/master' into randy/css-url-hinting
redmunds Apr 30, 2013
6fdaa81
@import url support
redmunds May 1, 2013
82da9ec
handle new import.url context; fix query string collection
redmunds May 1, 2013
2456a8b
hint insertion/replacement; support quotes inside of url()
redmunds May 1, 2013
77f2c6e
fix cursor placement; code cleanup
redmunds May 2, 2013
68280dd
fix 'merge' (that didn't seem to cause a conflict)
redmunds May 8, 2013
681e1a3
Merge remote-tracking branch 'origin/master' into randy/css-url-hinting
redmunds May 17, 2013
62e51eb
intermediate unit test checkin
redmunds May 18, 2013
0d8ae74
update tests for async hints
redmunds May 18, 2013
5fd6306
more tests
redmunds May 20, 2013
9de3221
add final set of tests
redmunds May 20, 2013
c61353b
changes for code review
redmunds May 21, 2013
2b59570
more code review changes
redmunds May 21, 2013
130c44b
merge with master
redmunds May 25, 2013
0d9fdb6
intermediate checkin
redmunds May 26, 2013
0904873
fixes for whitespace inside url()
redmunds May 26, 2013
46392d8
don't use 'char' as param name to make Travis/jshint happy
redmunds May 26, 2013
4440bb4
fix jsdoc
redmunds May 28, 2013
8d3ad50
fix merge conflict
redmunds May 29, 2013
c3e1733
more code review changes
redmunds May 29, 2013
769cc6b
yet another code review change
redmunds May 29, 2013
83a9176
more jsdoc fixes
redmunds May 29, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/extensions/default/CSSCodeHints/CSSProperties.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"background-attachment": {"values": ["fixed", "local", "scroll", "inherit"]},
"background-clip": {"values": ["border-box", "content-box", "padding-box", "inherit"]},
"background-color": {"values": ["currentColor", "transparent", "inherit"]},
"background-image": {"values": ["image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()"]},
"background-image": {"values": ["image()", "linear-gradient()", "radial-gradient()", "repeating-linear-gradient()", "repeating-radial-gradient()", "url()"]},
"background-origin": {"values": ["border-box", "content-box", "padding-box", "inherit"]},
"background-position": {"values": ["left", "center", "right", "bottom", "top"]},
"background-repeat": {"values": ["no-repeat", "repeat", "repeat-x", "repeat-y", "round", "space"]},
Expand All @@ -32,7 +32,7 @@
"border-bottom-right-radius": {"values": []},
"border-bottom-style": {"values": ["dashed", "dotted", "double", "groove", "hidden", "inset", "none", "outset", "ridge", "solid", "inherit"]},
"border-bottom-width": {"values": ["medium", "thin", "thick", "inherit"]},
"border-image": {"values": []},
"border-image": {"values": [ "url()" ]},
"border-image-outset": {"values": []},
"border-image-slice": {"values": []},
"border-image-source": {"values": []},
Expand Down Expand Up @@ -116,7 +116,7 @@
"letter-spacing": {"values": ["normal", "inherit"]},
"line-height": {"values": ["normal", "inherit"]},
"list-style": {"values": []},
"list-style-image": {"values": []},
"list-style-image": {"values": [ "url()" ]},
"list-style-position": {"values": ["inside", "outside", "inherit"]},
"list-style-type": {"values": ["armenian", "circle", "decimal", "decimal-leading-zero", "disc", "georgian", "lower-alpha", "lower-greek", "lower-latin", "lower-roman", "none", "square", "upper-alpha", "upper-latin", "upper-roman", "inherit"]},
"margin": {"values": ["inherit"]},
Expand Down Expand Up @@ -155,7 +155,7 @@
"quotes": {"values": ["none", "inherit"]},
"resize": {"values": ["both", "horizontal", "none", "vertical", "inherit"]},
"right": {"values": ["auto", "inherit"]},
"src": {"values": []},
"src": {"values": [ "url()"]},
"table-layout": {"values": ["auto", "fixed", "inherit"]},
"text-align": {"values": ["center", "left", "justify", "right", "inherit"]},
"text-align-last": {"values": ["center", "left", "justify", "right", "inherit"]},
Expand Down
24 changes: 18 additions & 6 deletions src/extensions/default/CSSCodeHints/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ define(function (require, exports, module) {
* that represents the last insertion and that indicates an implicit
* hinting request.
*
* @return {{hints: Array<string|jQueryObject>, match: string,
* @return {{hints: Array.<string|jQueryObject>, match: string,
* selectInitial: boolean}}
* Null if the provider wishes to end the hinting session. Otherwise, a
* response object that provides
Expand Down Expand Up @@ -270,12 +270,24 @@ define(function (require, exports, module) {
hint += ":";
}
}
} else if (!this.info.isNewItem && this.info.index !== -1) {
// Replacing an existing property value or partially typed value
end.ch = start.ch + this.info.values[this.info.index].length;
} else {
// Inserting a new property value
end.ch = start.ch;
if (!this.info.isNewItem && this.info.index !== -1) {
// Replacing an existing property value or partially typed value
end.ch = start.ch + this.info.values[this.info.index].length;
} else {
// Inserting a new property value
end.ch = start.ch;
}

var parenMatch = hint.match(/url\([\w\W]*?\)/i);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems odd to combine \w and \W inside a square bracket that has the meaning of everything. Can you use hint.match(/url\(.*?\)/i) instead of hint.match(/url\([\w\W]*?\)/i)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dot (.) matches "any single character except the newline character", so it's slightly different.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want new line characters, then you should be able to match it with m flag, right? It's up to you to keep it as is. I'm just curious of having [\w\W] in the regExp, and not really want you to change it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The m flag really only changes the meaning of the ^ and $ chars:

"The m flag is used to specify that a multiline input string should be treated as multiple lines. If the m flag is used, ^ and $ match at the start or end of any line within the input string instead of the start or end of the entire string".

if (parenMatch) {
// value has url(...), so place cursor inside opening paren
// and keep hints open
adjustCursor = true;
newCursor = { line: cursor.line,
ch: cursor.ch + 4 - this.info.offset };
keepHints = true;
}
}

// HACK (tracking adobe/brackets#1688): We talk to the private CodeMirror instance
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/default/HTMLCodeHints/HtmlAttributes.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
"headers": { "attribOption": [] },
"height": { "attribOption": [] },
"high": { "attribOption": [] },
"href": { "attribOption": [], "type": "url" },
"href": { "attribOption": [] },
"hreflang": { "attribOption": [] },
"hspace": { "attribOption": [] },
"http-equiv": { "attribOption": ["content-type", "default-style", "refresh"] },
Expand Down Expand Up @@ -197,7 +197,7 @@
"size": { "attribOption": [] },
"sizes": { "attribOption": ["any"] },
"span": { "attribOption": [] },
"src": { "attribOption": [], "type": "url" },
"src": { "attribOption": [] },
"srcdoc": { "attribOption": [] },
"srclang": { "attribOption": [] },
"standby": { "attribOption": [] },
Expand Down
194 changes: 4 additions & 190 deletions src/extensions/default/HTMLCodeHints/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,16 @@


/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $, window */
/*global define, brackets, $ */

define(function (require, exports, module) {
"use strict";

// Load dependent modules
var AppInit = brackets.getModule("utils/AppInit"),
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,
ProjectManager = brackets.getModule("project/ProjectManager"),
StringUtils = brackets.getModule("utils/StringUtils");

var HTMLTags = require("text!HtmlTags.json"),
HTMLTags = require("text!HtmlTags.json"),
HTMLAttributes = require("text!HtmlAttributes.json"),
tags,
attributes;
Expand Down Expand Up @@ -111,7 +105,7 @@ define(function (require, exports, module) {
* Returns a list of availble HTML tag hints if possible for the current
* editor context.
*
* @return {{hints: Array<string|jQueryObject>, match: string,
* @return {{hints: Array.<string|jQueryObject>, match: string,
* selectInitial: boolean}}
* Null if the provider wishes to end the hinting session. Otherwise, a
* response object that provides 1. a sorted array hints that consists
Expand Down Expand Up @@ -194,10 +188,6 @@ define(function (require, exports, module) {
this.globalAttributes = this.readGlobalAttrHints();
this.cachedHints = null;
this.exclusion = "";

// Used in URL hinting to keep the popup list open
// by setting this to false.
this.closeOnSelect = true;
}

/**
Expand All @@ -213,160 +203,6 @@ define(function (require, exports, module) {
});
};

/**
* Helper function for search(). Create a list of urls to existing files based on the query.
* @param {{queryStr: string}} query -- a query object, used to filter the code hints
* @return {Array.<string>}
*/
AttrHints.prototype._getUrlList = function (query) {
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) {
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: 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 + decodeURI(queryDir);

// get list of files from target folder
var unfiltered = [];

// 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) {
// 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 ||
this.cachedHints.queryDir !== queryDir ||
this.cachedHints.docDir !== docDir) {

// delete stale cache
this.cachedHints = null;
}
}

if (this.cachedHints) {
// use cached hints
unfiltered = this.cachedHints.unfiltered;

} else {
var self = this,
origEditor = EditorManager.getFocusedEditor();

if (self.cachedHints && self.cachedHints.deferred) {
self.cachedHints.deferred.reject();
}
// create empty object so we can detect "waiting" state
self.cachedHints = {};
self.cachedHints.deferred = $.Deferred();
self.cachedHints.unfiltered = [];

NativeFileSystem.requestNativeFileSystem(targetDir, function (fs) {
fs.root.createReader().readEntries(function (entries) {

entries.forEach(function (entry) {
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;
self.cachedHints.query = query;
self.cachedHints.queryDir = queryDir;
self.cachedHints.docDir = docDir;

if (self.cachedHints.deferred.state() !== "rejected") {
var currentDeferred = self.cachedHints.deferred;
// Since we've cached the results, the next call to _getUrlList should be synchronous.
// If it isn't, we've got a problem and should reject both the current deferred
// and any new deferred that got created on the call.
var syncResults = self._getUrlList(query);
if (syncResults instanceof Array) {
currentDeferred.resolveWith(self, [syncResults]);
} else {
if (currentDeferred && currentDeferred.state() === "pending") {
currentDeferred.reject();
}

if (self.cachedHints.deferred && self.cachedHints.deferred.state() === "pending") {
self.cachedHints.deferred.reject();
self.cachedHints.deferred = null;
}
}
}
});
});

return self.cachedHints.deferred;
}

// 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.

// 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;
};

/**
* Helper function that determines the possible value hints for a given html tag/attribute name pair
*
Expand Down Expand Up @@ -397,11 +233,6 @@ define(function (require, exports, module) {
if (attrInfo) {
if (attrInfo.type === "boolean") {
hints = ["false", "true"];
} else if (attrInfo.type === "url") {
// Default behavior for url hints is do not close on select.
this.closeOnSelect = false;
hints = this._getUrlList(query);
sortFunc = StringUtils.urlSort;
} else if (attrInfo.attribOption) {
hints = attrInfo.attribOption;
}
Expand Down Expand Up @@ -527,7 +358,7 @@ define(function (require, exports, module) {
* Returns a list of availble HTML attribute hints if possible for the
* current editor context.
*
* @return {{hints: Array<string|jQueryObject>, match: string,
* @return {{hints: Array.<string|jQueryObject>, match: string,
* selectInitial: boolean}}
* Null if the provider wishes to end the hinting session. Otherwise, a
* response object that provides 1. a sorted array hints that consists
Expand Down Expand Up @@ -577,8 +408,6 @@ define(function (require, exports, module) {
hints = [],
sortFunc = null;

this.closeOnSelect = true;

if (attrName) {
var hintsAndSortFunc = this._getValueHintsForAttr(query, tagName, attrName);
hints = hintsAndSortFunc.hints;
Expand Down Expand Up @@ -669,12 +498,6 @@ define(function (require, exports, module) {
charCount = this.tagInfo.attr.value.length;
}

// Special handling for URL hinting -- if the completion is a file name
// and not a folder, then close the code hint list.
if (!this.closeOnSelect && completion.match(/\/$/) === null) {
this.closeOnSelect = true;
}

if (!this.tagInfo.attr.hasEndQuote) {
endQuote = this.tagInfo.attr.quoteChar;
if (endQuote) {
Expand All @@ -699,15 +522,6 @@ define(function (require, exports, module) {
}
}

if (!this.closeOnSelect) {
// If we append the missing quote, then we need to adjust the cursor position
// to keep the code hint list open.
if (tokenType === HTMLUtils.ATTR_VALUE && !this.tagInfo.attr.hasEndQuote) {
this.editor.setCursorPos(start.line, start.ch + completion.length - 1);
}
return true;
}

if (insertedName) {
this.editor.setCursorPos(start.line, start.ch + completion.length - 1);

Expand Down
Loading