-
Notifications
You must be signed in to change notification settings - Fork 7.6k
CSS AtRules, Pseudo elements and Pseudo selector code hints #13295
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job, some initial comments! The feature itself seems to work just fine!
if (this.filter) { | ||
|
||
// Filter the property list based on the token string | ||
var result = $.map(Object.keys(AtRules), function (key, value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would prefer Array.prototype.map
here, maybe with something like:
Object.keys(AtRules).map(function (key) {
if (key.indexOf(token.string) === 0) {
return key
}
}).filter(function (key) {
return key;
}).sort();
this.editor = editor; | ||
|
||
// Check if we are at ':' pseudo rule or in 'variable-3' 'def' context | ||
if (token.state.state === "pseudo" || token.type === "variable-3") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return token.state.state === "pseudo" || token.type === "variable-3"
|
||
if (this.context !== -1) { | ||
// Filter the property list based on the token string | ||
var result = $.map(this.context === TOKEN_TYPE_PSEUDO_SELECTOR ? Object.keys(PseudoRules.selectors) : Object.keys(PseudoRules.elements), function (value, key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer Array.prototype.map
, maybe do the context check first.
var keys = this.context === TOKEN_TYPE_PSEUDO_SELECTOR ? Object.keys(PseudoRules.selectors) : Object.keys(PseudoRules.elements);
var result = keys.map(...)
handleWideResults: false | ||
}; | ||
} else { | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other similar function returns null
but I guess it doesn't matter
this.editor.setCursorPos({line: cursor.line, ch: cursor.ch - 1}); | ||
} | ||
|
||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to return anything?
return false; | ||
}; | ||
|
||
function _init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty method
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT unused, remove.
{ | ||
"selectors" : | ||
{ | ||
"active" : { "desc": "Selects the active link"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normalize whitespace in this file between the keys and values
@@ -468,7 +472,7 @@ define(function (require, exports, module) { | |||
|
|||
AppInit.appReady(function () { | |||
var cssPropHints = new CssPropHints(); | |||
CodeHintManager.registerHintProvider(cssPropHints, ["css", "scss", "less"], 0); | |||
CodeHintManager.registerHintProvider(cssPropHints, ["css", "scss", "less"], 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: What's the second parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why we need this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have mentioned it in the opening note of the PR 😄 .
In the implementation, priority of the hint providers have been kept at 0 while the priority of the default CSS code hint provider has been increased to 1. This has been done to ensure no additional overhead in case of CSS property:value code hints.
To explain, the second parameter is for priority. CodeHintManager keeps the providers for a particular mode in order of priority, If there are priority collisions, then the lists are sorted based on loading order(this is not in our control). CodeHintManager first checks the current doc mode and then iterates over this sorted providers by calling 'hasHints', once it gets affirmative answer from a particular provider, it breaks there and selects that provider as the current session hint provider. By increasing the priority of the existing provider I am trying to ensure that, it gets the opportunity first to provide hints ( as hasHints() of this provider will be called first). This will ensure that, there is zero overhead on the current property:value hint provider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@swmitra Sorry I already forgot about the opening note of PR 😸 thanks for the detailed explanation!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
This fixes #12486 |
|
||
|
||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ | ||
/*global define, brackets, $ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove both lines. Nowadays they are applyed globally.
And ideally there should be a jsdoc with a brief description of this file.
} | ||
|
||
// Check if we are at '@' rule 'def' context | ||
if ((token.type === "def" && cmState.context.type === "at") || (token.type === "variable-2" && (cmState.context.type === "top" || cmState.context.type === "block"))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split in two lines
this.filter = token.string; | ||
this.token = token; | ||
|
||
if (this.filter) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer an early return here:
if (!this.filter) {
return null;
}
var result...
* | ||
* @return {boolean} | ||
* Indicates whether the manager should follow hint insertion with an | ||
* additional explicit hint request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use an uniform style to doc comments: the description should be always inline or always at new line.
I think the current style is on the same line.
/** | ||
* Inserts a given @<rule> hint into the current editor context. | ||
* | ||
* @param {string} hint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrong param name
|
||
|
||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ | ||
/*global define, brackets, $ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As before, remove.
} | ||
|
||
// As we are only going to provide :<pseudo> name hints | ||
// we should claim that we don't have hints for anything else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use JSDoc and add the params
this.token = token; | ||
|
||
|
||
if (this.context !== -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Early return.
return false; | ||
}; | ||
|
||
function _init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAICT unused, remove.
@@ -468,7 +472,7 @@ define(function (require, exports, module) { | |||
|
|||
AppInit.appReady(function () { | |||
var cssPropHints = new CssPropHints(); | |||
CodeHintManager.registerHintProvider(cssPropHints, ["css", "scss", "less"], 0); | |||
CodeHintManager.registerHintProvider(cssPropHints, ["css", "scss", "less"], 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why we need this change?
I briefly tested, but I cannot use the pseudo selectors in a SCSS file, but I can in a CSS one. Do you have thought of using multiple extensions like suggested by @redmunds in #12486 (comment)? I don't like much the |
In general LGTM, but I still cannot see pseudo selector hints for SCSS code. I've use this snippet to test: @charset "utf8";
/*comment*/
.test1: {
}
.test2:: {
} |
@ficristo You probably have 'brackets-saas' extension installed. That extension registers 'SaasHints' provider with high priority and claims to provide hints ( 'hasHints' call returns true) even when it can't. |
I disabled all my extensions and I can still reproduce. |
@ficristo WIth the SCSS tokenizer in CM2, the token identifier is not able to detect the pseudo context in the second scenario you have mentioned. I have filed an issue with CM codemirror/codemirror5#4688 just now. Lets wait for a response from @marijnh. |
@ficristo Please refer to the CM issue and the discussion at codemirror/codemirror5#4688, we might have to live with this issue for sometime 😞 . But fortunately the moment you put the first char after |
Just figuring out what the SCSS mode is doing and fixing it is going to be a lot easier than trying to work around this from the outside. |
Thanks @marijnh for joining in 👍 |
According to http://sass-lang.com/documentation/file.SASS_REFERENCE.html#nested_properties you can "split" properties like this: .test {
font: {
family: serif;
}
} instead of writing .test {
font-family: serif;
} I don't know what we can do about it for our case. @swmitra is the "problem" also present with |
yes @ficristo. The regex used in the mode parser matches both the cases. This is what it does for punctuation ":": function(stream) {
if (stream.match(/\s*\{/))
return [null, "{"];
return false;
} Now if I remove this or try to provide a punctuation handler without wrapping the .test {
font: {
family: serif;
}
} I can think of one particular way though where we look backward for CSS property keywords and decide whether to identify this token as 'pseudo' or 'variable-3'. If the backward lookup tells us that the preceding token is actually a CSS property keyword then we don't handle the punctuation to identify as the start of 'pseudo'. @marijnh Badly need your input here... |
@marijnh I see that the following codemirror/codemirror5@2c46d1b fixes the issue 👍 |
@ficristo The issue in SCSS mode is fixed now using CM codemirror/codemirror5@2c46d1b and my last commit. Can you please check it once? |
To see the last changes in action we need to wait for a CodeMirror upgrade, is it correct? |
Yes @ficristo, I will upgrade CM once a release is available with the mentioned commit. I am writing tests for both the extensions... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of nits. Otherwise LGTM
} | ||
|
||
// Filter the property list based on the token string | ||
var result = Object.keys(AtRules).map(function (key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be wrong, but I think filter
would be more appropriate than map
for the first loop. This would also make the following filter
loop unnecessary.
"active": {"desc": "Selects the active link"}, | ||
"checked": {"desc": "Selects every checked <input> element"}, | ||
"disabled": {"desc": "Selects every disabled <input> element"}, | ||
"empty" : {"desc": "Selects every element that has no children/text (including text nodes)"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: formatting is a bit off
}, | ||
"elements" : | ||
{ | ||
"after": {"desc": "Insert something after the content of each element identified by this selctor"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: selector
"elements" : | ||
{ | ||
"after": {"desc": "Insert something after the content of each element identified by this selctor"}, | ||
"before": {"desc": "Insert something before the content of each element identified by this selctor"}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same selector
spelling
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically all instances of selctor
in this object ;-)
return key; | ||
} | ||
}).filter(function (key) { | ||
return key; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
replacing the previous map
call with filter
should make this obsolete
this.editor = editor; | ||
|
||
// Check if we are at ':' pseudo rule or in 'variable-3' 'def' context | ||
return token.state.state === "pseudo" || token.type === "variable-3" || token.string === PUNCTUATION_CHAR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this could be a function determining the state. The same statement (negated) is used in getHints
and could be reused there. Plus, it makes testing this function in isolation easier.
@petetnt @ingorichter @ficristo Can you guys please have a look at this PR now? |
There is still the nit of @ingorichter to fix. No time to look again. |
This PR adds CSS restricted block and pseudo selector/element code hints. In the implementation, priority of the hint providers have been kept at 0 while the priority of the default CSS code hint provider has been increased to 1. This has been done to ensure no additional overhead in case of CSS property:value code hints. Attached some screen shots of the corresponding code hints for illustration.
@rules
Pseudo selectors
Pseudo elements
Copying @ingorichter @petetnt @ficristo @zaggino @peterflynn for code review.
Note: Additional description have been captured in the json def files to enable description tooltip at a later point of time.