try to show applicable values in autocomplete for props (#1211)

+ restore proper toggling of autocompleteOnTyping
This commit is contained in:
tophf 2021-03-14 08:33:26 +03:00 committed by GitHub
parent 9531698dd7
commit 692d3c9826
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 131 additions and 70 deletions

View File

@ -2,6 +2,7 @@
/* global cmFactory */ /* global cmFactory */
/* global debounce */// toolbox.js /* global debounce */// toolbox.js
/* global editor */ /* global editor */
/* global linterMan */
/* global prefs */ /* global prefs */
'use strict'; 'use strict';
@ -11,30 +12,37 @@
const USO_VAR = 'uso-variable'; const USO_VAR = 'uso-variable';
const USO_VALID_VAR = 'variable-3 ' + USO_VAR; const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
const USO_INVALID_VAR = 'error ' + USO_VAR; const USO_INVALID_VAR = 'error ' + USO_VAR;
const rxPROP = /^(prop(erty)?|variable-2)\b/;
const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu; const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu;
const rxCONSUME = /([-\w]*\s*:\s?)?/yu; const rxCONSUME = /([-\w]*\s*:\s?)?/yu;
const cssMime = CodeMirror.mimeModes['text/css']; const cssMime = CodeMirror.mimeModes['text/css'];
const cssGlobalValues = [
'inherit',
'initial',
'revert',
'unset',
];
const docFuncs = addSuffix(cssMime.documentTypes, '('); const docFuncs = addSuffix(cssMime.documentTypes, '(');
const {tokenHooks} = cssMime; const {tokenHooks} = cssMime;
const originalCommentHook = tokenHooks['/']; const originalCommentHook = tokenHooks['/'];
const originalHelper = CodeMirror.hint.css || (() => {}); const originalHelper = CodeMirror.hint.css || (() => {});
let cssProps, cssMedia; let cssMedia, cssProps, cssPropsValues;
const aot = prefs.get('editor.autocompleteOnTyping'); const AOT_ID = 'autocompleteOnTyping';
CodeMirror.defineOption('autocompleteOnTyping', aot, aotToggled); const AOT_PREF_ID = 'editor.' + AOT_ID;
if (aot) cmFactory.globalSetOption('autocompleteOnTyping', true); const aot = prefs.get(AOT_PREF_ID);
CodeMirror.defineOption(AOT_ID, aot, (cm, value) => {
cm[value ? 'on' : 'off']('changes', autocompleteOnTyping);
cm[value ? 'on' : 'off']('pick', autocompletePicked);
});
prefs.subscribe(AOT_PREF_ID, (key, val) => cmFactory.globalSetOption(AOT_ID, val), {runNow: aot});
CodeMirror.registerHelper('hint', 'css', helper); CodeMirror.registerHelper('hint', 'css', helper);
CodeMirror.registerHelper('hint', 'stylus', helper); CodeMirror.registerHelper('hint', 'stylus', helper);
tokenHooks['/'] = tokenizeUsoVariables; tokenHooks['/'] = tokenizeUsoVariables;
function aotToggled(cm, value) { async function helper(cm) {
cm[value ? 'on' : 'off']('changes', autocompleteOnTyping);
cm[value ? 'on' : 'off']('pick', autocompletePicked);
}
function helper(cm) {
const pos = cm.getCursor(); const pos = cm.getCursor();
const {line, ch} = pos; const {line, ch} = pos;
const {styles, text} = cm.getLineHandle(line); const {styles, text} = cm.getLineHandle(line);
@ -64,7 +72,7 @@
const str = text.slice(prev, end); const str = text.slice(prev, end);
const left = text.slice(prev, ch).trim(); const left = text.slice(prev, ch).trim();
let leftLC = left.toLowerCase(); let leftLC = left.toLowerCase();
let list = []; let list;
switch (leftLC[0]) { switch (leftLC[0]) {
case '!': case '!':
@ -125,8 +133,29 @@
// fallthrough to `default` // fallthrough to `default`
default: default:
// property values
if (isStylusLang || getTokenState() === 'prop') {
while (i > 0 && !rxPROP.test(styles[i + 1])) i -= 2;
const propEnd = styles[i];
let prop;
if (propEnd > text.lastIndexOf(';', ch - 1)) {
while (i > 0 && rxPROP.test(styles[i + 1])) i -= 2;
prop = text.slice(styles[i] || 0, propEnd).match(/([-\w]+)?$/u)[1];
}
if (prop) {
if (/[^-\w]/.test(leftLC)) {
prev += execAt(/[\s:()]*/y, prev, text)[0].length;
leftLC = leftLC.replace(/^[^\w\s]\s*/, '');
}
if (prop.startsWith('--')) prop = 'color'; // assuming 90% of variables are colors
if (!cssPropsValues) cssPropsValues = await linterMan.worker.getCssPropsValues();
list = [...new Set([...cssPropsValues[prop] || [], ...cssGlobalValues])];
end = prev + execAt(/(\s*[-a-z(]+)?/y, prev, text)[0].length;
}
}
// properties and media features // properties and media features
if (/^(prop(erty|\?)|atom|error)/.test(type) && if (!list &&
/^(prop(erty|\?)|atom|error)/.test(type) &&
/^(block|atBlock_parens|maybeprop)/.test(getTokenState())) { /^(block|atBlock_parens|maybeprop)/.test(getTokenState())) {
if (!cssProps) initCssProps(); if (!cssProps) initCssProps();
if (type === 'prop?') { if (type === 'prop?') {
@ -136,7 +165,9 @@
list = state === 'atBlock_parens' ? cssMedia : cssProps; list = state === 'atBlock_parens' ? cssMedia : cssProps;
end -= /\W$/u.test(str); // e.g. don't consume ) when inside () end -= /\W$/u.test(str); // e.g. don't consume ) when inside ()
end += execAt(rxCONSUME, end, text)[0].length; end += execAt(rxCONSUME, end, text)[0].length;
} else {
}
if (!list) {
return isStylusLang return isStylusLang
? CodeMirror.hint.fromList(cm, {words: CodeMirror.hintWords.stylus}) ? CodeMirror.hint.fromList(cm, {words: CodeMirror.hintWords.stylus})
: originalHelper(cm); : originalHelper(cm);

View File

@ -16,6 +16,34 @@
.map(m => Object.assign(m, {rule: {id: m.rule.id}})); .map(m => Object.assign(m, {rule: {id: m.rule.id}}));
}, },
getCssPropsValues() {
require(['/js/csslint/parserlib']); /* global parserlib */
const {css: {Colors, Properties}, util: {describeProp}} = parserlib;
const namedColors = Object.keys(Colors);
const rxNonWord = /(?:<.+?>|[^-\w<(]+\d*)+/g;
const res = {};
// moving vendor-prefixed props to the end
const cmp = (a, b) => a[0] === '-' && b[0] !== '-' ? 1 : a < b ? -1 : a > b;
for (const [k, v] of Object.entries(Properties)) {
if (typeof v === 'string') {
let last = '';
const uniq = [];
// strip definitions of function arguments
const desc = describeProp(v).replace(/([-\w]+)\(.*?\)/g, 'z-$1');
const descNoColors = desc.replace(/<named-color>/g, '');
// add a prefix to functions to group them at the end
const words = descNoColors.split(rxNonWord).sort(cmp);
for (let w of words) {
if (w.startsWith('z-')) w = w.slice(2) + '(';
if (w !== last) uniq.push(last = w);
}
if (desc !== descNoColors) uniq.push(...namedColors);
if (uniq.length) res[k] = uniq;
}
}
return res;
},
getRules(linter) { getRules(linter) {
return ruleRetriever[linter](); // eslint-disable-line no-use-before-define return ruleRetriever[linter](); // eslint-disable-line no-use-before-define
}, },

View File

@ -34,7 +34,7 @@ self.parserlib = (() => {
'align-items': 'normal | stretch | <baseline-position> | [ <overflow-position>? <self-position> ]', 'align-items': 'normal | stretch | <baseline-position> | [ <overflow-position>? <self-position> ]',
'align-content': '<align-content>', 'align-content': '<align-content>',
'align-self': '<align-self>', 'align-self': '<align-self>',
'all': 'initial | inherit | unset', 'all': 'initial | inherit | revert | unset',
'alignment-adjust': 'auto | baseline | before-edge | text-before-edge | middle | central | ' + 'alignment-adjust': 'auto | baseline | before-edge | text-before-edge | middle | central | ' +
'after-edge | text-after-edge | ideographic | alphabetic | hanging | ' + 'after-edge | text-after-edge | ideographic | alphabetic | hanging | ' +
'mathematical | <length-pct>', 'mathematical | <length-pct>',
@ -745,9 +745,9 @@ self.parserlib = (() => {
'emoji | math | fangsong | ui-serif | ui-sans-serif | ui-monospace | ui-rounded', 'emoji | math | fangsong | ui-serif | ui-sans-serif | ui-monospace | ui-rounded',
'<geometry-box>': '<shape-box> | fill-box | stroke-box | view-box', '<geometry-box>': '<shape-box> | fill-box | stroke-box | view-box',
'<glyph-angle>': p => p.type === 'angle' && p.units === 'deg', '<glyph-angle>': p => p.type === 'angle' && p.units === 'deg',
'<gradient>': p => '<gradient>': 'radial-gradient() | linear-gradient() | conic-gradient() | gradient() | ' +
p.type === 'function' && 'repeating-radial-gradient() | repeating-linear-gradient() | repeating-conic-gradient() | ' +
/^(?:-(?:webkit|moz|ms|o)-)?(?:repeating-)?(?:radial-|linear-|conic-)?gradient/i.test(p), 'repeating-gradient()',
'<hex-color>': p => p.tokenType === Tokens.HASH, //eslint-disable-line no-use-before-define '<hex-color>': p => p.tokenType === Tokens.HASH, //eslint-disable-line no-use-before-define
'<icccolor>': 'cielab() | cielch() | cielchab() | icc-color() | icc-named-color()', '<icccolor>': 'cielab() | cielch() | cielchab() | icc-color() | icc-named-color()',
'<ident>': vtIsIdent, '<ident>': vtIsIdent,
@ -778,7 +778,7 @@ self.parserlib = (() => {
'<nonnegative-num-pct>': p => '<nonnegative-num-pct>': p =>
p.value >= 0 && (p.type === 'number' || p.type === 'percentage') || p.isCalc, p.value >= 0 && (p.type === 'number' || p.type === 'percentage') || p.isCalc,
//eslint-disable-next-line no-use-before-define //eslint-disable-next-line no-use-before-define
'<named-color>': p => p.text in Colors || lower(p.text) in Colors, '<named-color>': p => p.text in Colors || ColorsLC.has(lower(p.text)),
'<number>': p => p.type === 'number' || p.isCalc, '<number>': p => p.type === 'number' || p.isCalc,
'<number-pct>': p => p.type === 'number' || p.type === 'percentage' || p.isCalc, '<number-pct>': p => p.type === 'number' || p.type === 'percentage' || p.isCalc,
'<opacity-value>': p => p.type === 'number' && p.value >= 0 && p.value <= 1 || p.isCalc, '<opacity-value>': p => p.type === 'number' && p.value >= 0 && p.value <= 1 || p.isCalc,
@ -979,7 +979,12 @@ self.parserlib = (() => {
//#endregion //#endregion
//#region Colors //#region Colors
const Colors = { const Colors = Object.assign(Object.create(null), {
// 'currentColor' color keyword
// https://www.w3.org/TR/css3-color/#currentcolor
currentColor: '',
transparent: '#0000',
aliceblue: '#f0f8ff', aliceblue: '#f0f8ff',
antiquewhite: '#faebd7', antiquewhite: '#faebd7',
aqua: '#00ffff', aqua: '#00ffff',
@ -1128,56 +1133,49 @@ self.parserlib = (() => {
whitesmoke: '#f5f5f5', whitesmoke: '#f5f5f5',
yellow: '#ffff00', yellow: '#ffff00',
yellowgreen: '#9acd32', yellowgreen: '#9acd32',
// 'currentColor' color keyword
// https://www.w3.org/TR/css3-color/#currentcolor
currentcolor: '',
transparent: '#0000',
// CSS2 system colors // old = CSS2 system colors: https://www.w3.org/TR/css3-color/#css2-system
// https://www.w3.org/TR/css3-color/#css2-system // new = CSS4 system colors: https://drafts.csswg.org/css-color-4/#css-system-colors
activeborder: '', ActiveBorder: '',
activecaption: '', ActiveCaption: '',
appworkspace: '', ActiveText: '', // new
background: '', AppWorkspace: '',
buttonface: '', Background: '',
buttonhighlight: '', ButtonBorder: '', // new
buttonshadow: '', ButtonFace: '', // old+new
buttontext: '', ButtonHighlight: '',
captiontext: '', ButtonShadow: '',
graytext: '', ButtonText: '', // old+new
greytext: '', Canvas: '', // new
highlight: '', CanvasText: '', // new
highlighttext: '', CaptionText: '',
inactiveborder: '', Field: '', // new
inactivecaption: '', FieldText: '', // new
inactivecaptiontext: '', GrayText: '', // old+new
infobackground: '', Highlight: '', // old+new
infotext: '', HighlightText: '', // old+new
menu: '', InactiveBorder: '',
menutext: '', InactiveCaption: '',
scrollbar: '', InactiveCaptionText: '',
threeddarkshadow: '', InfoBackground: '',
threedface: '', InfoText: '',
threedhighlight: '', LinkText: '', // new
threedlightshadow: '', Mark: '', // new
threedshadow: '', MarkText: '', // new
window: '', Menu: '',
windowframe: '', MenuText: '',
windowtext: '', Scrollbar: '',
ThreeDDarkShadow: '',
// CSS4 system colors, only additions to the above ThreeDFace: '',
// https://drafts.csswg.org/css-color-4/#css-system-colors ThreeDHighlight: '',
activetext: '', ThreeDLightShadow: '',
buttonborder: '', ThreeDShadow: '',
canvas: '', VisitedText: '', // new
canvastext: '', Window: '',
field: '', WindowFrame: '',
fieldtext: '', WindowText: '',
linktext: '', });
mark: '', const ColorsLC = new Set(Object.keys(Colors).map(lower));
marktext: '',
visitedtext: '',
};
//#endregion //#endregion
//#region Tokens //#region Tokens
@ -4175,10 +4173,12 @@ self.parserlib = (() => {
if (asText) { if (asText) {
return text; return text;
} }
const m = rxVendorPrefix.exec(name) || [];
return SyntaxUnit.addFuncInfo( return SyntaxUnit.addFuncInfo(
new SyntaxUnit(text, start, 'function', { new SyntaxUnit(text, start, 'function', {
expr, expr,
name, name: m[2] || name,
prefix: m[1] || '',
tokenType: Tokens.FUNCTION, tokenType: Tokens.FUNCTION,
})); }));
} }
@ -4647,6 +4647,7 @@ self.parserlib = (() => {
Colors, Colors,
Combinator, Combinator,
Parser, Parser,
Properties,
PropertyName, PropertyName,
PropertyValue, PropertyValue,
PropertyValuePart, PropertyValuePart,
@ -4662,12 +4663,13 @@ self.parserlib = (() => {
ValidationError, ValidationError,
}, },
util: { util: {
EventTarget,
StringReader, StringReader,
SyntaxError, SyntaxError,
SyntaxUnit, SyntaxUnit,
EventTarget,
TokenStreamBase, TokenStreamBase,
rxVendorPrefix, rxVendorPrefix,
describeProp: vtExplode,
}, },
cache: parserCache, cache: parserCache,
}; };