diff --git a/plugins/completions/__init__.py b/plugins/completions/__init__.py index b74c97eb..756e545f 100644 --- a/plugins/completions/__init__.py +++ b/plugins/completions/__init__.py @@ -1 +1 @@ -from .provider import ScssCompletions +from .provider import * diff --git a/plugins/completions/provider.py b/plugins/completions/provider.py index 011a639e..355d7f9b 100755 --- a/plugins/completions/provider.py +++ b/plugins/completions/provider.py @@ -1,34 +1,18 @@ import re import sublime import sublime_plugin -import timeit -from functools import cached_property, wraps +from functools import cached_property from .function_args import get_func_args from .properties import get_properties -__all__ = ['ScssCompletions'] +__all__ = ['SassCompletions', 'ScssCompletions'] KIND_CSS_PROPERTY = (sublime.KIND_ID_KEYWORD, "p", "property") KIND_CSS_FUNCTION = (sublime.KIND_ID_FUNCTION, "f", "function") KIND_CSS_CONSTANT = (sublime.KIND_ID_VARIABLE, "c", "constant") -ENABLE_TIMING = False - - -def timing(func): - @wraps(func) - def wrap(*args, **kw): - if ENABLE_TIMING: - ts = timeit.default_timer() - result = func(*args, **kw) - if ENABLE_TIMING: - te = timeit.default_timer() - print(f"{func.__name__}({args}, {kw}) took: {1000.0 * (te - ts):2.3f} ms") - return result - return wrap - def match_selector(view, pt, scope): # This will catch scenarios like: @@ -44,7 +28,7 @@ def next_none_whitespace(view, pt): return ch -class ScssCompletions(sublime_plugin.EventListener): +class BaseCompletionsProvider: @cached_property def func_args(self): @@ -62,39 +46,7 @@ def re_name(self): def re_value(self): return re.compile(r"^(?:\s*(:)|([ \t]*))([^:]*)([;}])") - @timing - def on_query_completions(self, view, prefix, locations): - - settings = sublime.load_settings('SCSS.sublime-settings') - if settings.get('disable_default_completions'): - return None - - selector = settings.get('default_completions_selector') - if not selector: - return None - - if isinstance(selector, list): - selector = ''.join(selector) - - pt = locations[0] - if not match_selector(view, pt, selector): - return None - - if match_selector(view, pt, "meta.property-value.css meta.function-call.arguments"): - items = self.complete_function_argument(view, prefix, pt) - elif view.match_selector(pt - 1, "meta.property-value.css, punctuation.separator.key-value"): - items = self.complete_property_value(view, prefix, pt) - elif view.match_selector(pt - 1, "meta.property-name.css, meta.property-list.css - meta.selector"): - items = self.complete_property_name(view, prefix, pt) - else: - # TODO: provide selectors, at-rules - items = None - - if items: - return sublime.CompletionList(items) - return None - - def complete_property_name(self, view, prefix, pt): + def complete_property_name(self, view, prefix, pt, semicolon): text = view.substr(sublime.Region(pt, view.line(pt).end())) matches = self.re_value.search(text) if matches: @@ -115,7 +67,7 @@ def complete_property_name(self, view, prefix, pt): suffix = ":$0" # terminate empty value if not within parentheses - if not value and not term and not match_selector(view, pt, "meta.group"): + if semicolon and not value and not term and not match_selector(view, pt, "meta.group"): suffix += ";" return ( @@ -127,7 +79,7 @@ def complete_property_name(self, view, prefix, pt): ) for prop in self.props ) - def complete_property_value(self, view, prefix, pt): + def complete_property_value(self, view, prefix, pt, semicolon): completions = [ sublime.CompletionItem( trigger="!important", @@ -144,10 +96,10 @@ def complete_property_value(self, view, prefix, pt): if values: details = f"{prop} property-value" - if match_selector(view, pt, "meta.group") or next_none_whitespace(view, pt) == ";": - suffix = "" - else: + if semicolon and not match_selector(view, pt, "meta.group") and next_none_whitespace(view, pt) != ";": suffix = "$0;" + else: + suffix = "" for value in values: if isinstance(value, list): @@ -228,3 +180,65 @@ def complete_function_argument(self, view: sublime.View, prefix, pt): )) return completions + + +class SassCompletions(BaseCompletionsProvider, sublime_plugin.EventListener): + + def on_query_completions(self, view, prefix, locations): + settings = sublime.load_settings('Sass.sublime-settings') + if settings.get('disable_default_completions'): + return None + + selector = settings.get('default_completions_selector') + if not selector: + return None + + if isinstance(selector, list): + selector = ''.join(selector) + + pt = locations[0] + if not match_selector(view, pt, selector): + return None + + if match_selector(view, pt, "meta.function-call.arguments"): + items = self.complete_function_argument(view, prefix, pt) + elif match_selector(view, pt - 1, "meta.property-value, punctuation.separator.key-value"): + items = self.complete_property_value(view, prefix, pt, False) + else: + items = self.complete_property_name(view, prefix, pt, False) + + if items: + return sublime.CompletionList(items, sublime.INHIBIT_WORD_COMPLETIONS) + return None + + +class ScssCompletions(BaseCompletionsProvider, sublime_plugin.EventListener): + + def on_query_completions(self, view, prefix, locations): + settings = sublime.load_settings('SCSS.sublime-settings') + if settings.get('disable_default_completions'): + return None + + selector = settings.get('default_completions_selector') + if not selector: + return None + + if isinstance(selector, list): + selector = ''.join(selector) + + pt = locations[0] + if not match_selector(view, pt, selector): + return None + + if match_selector(view, pt, "meta.function-call.arguments"): + items = self.complete_function_argument(view, prefix, pt) + elif view.match_selector(pt - 1, "meta.property-value, punctuation.separator.key-value"): + items = self.complete_property_value(view, prefix, pt, True) + elif view.match_selector(pt - 1, "meta.property-name, meta.property-list - meta.selector"): + items = self.complete_property_name(view, prefix, pt, True) + else: + items = None + + if items: + return sublime.CompletionList(items) + return None diff --git a/sass_completions.py b/sass_completions.py deleted file mode 100755 index 2001920a..00000000 --- a/sass_completions.py +++ /dev/null @@ -1,966 +0,0 @@ -import re -import sublime -import sublime_plugin -import timeit - -from functools import cached_property, wraps - -__all__ = ['SassCompletions'] - -KIND_CSS_PROPERTY = (sublime.KIND_ID_KEYWORD, "p", "property") -KIND_CSS_FUNCTION = (sublime.KIND_ID_FUNCTION, "f", "function") -KIND_CSS_CONSTANT = (sublime.KIND_ID_VARIABLE, "c", "constant") - -def get_common_values(): - common_values = { - 'animation-direction': [ - 'alternate', 'alternate-reverse', 'normal', 'reverse' - ], - 'absolute-size': [ - 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large' - ], - 'absolute-weight': [ - '100', '200', '300', '400', '500', '600', '700', '800', '900', - 'normal', 'bold' - ], - 'basic-shape': [ - ['circle()', 'circle($1)'], - ['ellipse()', 'ellipse($1)'], - ['inset()', 'inset($1)'], - ['polygon()', 'polygon($1)'] - ], - 'blend-mode': [ - 'normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten', - 'color-dodge', 'color-burn', 'hard-light', 'soft-light', 'difference', - 'exclusion', 'hue', 'saturation', 'color', 'luminosity' - ], - 'border-style': [ - 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', - 'groove', 'ridge', 'inset', 'outset' - ], - 'border-width': ['thin', 'medium', 'thick'], - 'break-before-after': [ - 'always', 'left', 'right', 'recto', 'verso', 'page', 'column', 'region' - ], - 'break-inside': [ - 'auto', 'avoid', 'avoid-page', 'avoid-column', 'avoid-region' - ], - 'calc': [ - ['calc()', 'calc($1)'], - ['clamp()', 'clamp(${1:0}, ${2:0}, ${3:0})'], - ['max()', 'max(${1:0}, ${2:0})'], - ['min()', 'min(${1:0}, ${2:0})'] - ], - 'color': [ - 'currentColor', - 'transparent', - ['rgb()', 'rgb(${1:0}, ${2:0}, ${3:0})'], - ['rgba()', 'rgba(${1:0}, ${2:0}, ${3:0}, ${4:1.0})'], - ['hsl()', 'hsl(${1:0}, ${2:100%}, ${3:50%})'], - ['hsla()', 'hsla(${1:0}, ${2:100%}, ${3:50%}, ${4:1.0})'], - # Named colors - 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', - 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', - 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', - 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', - 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', 'darkgreen', - 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', - 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', - 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', - 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', - 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', - 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', - 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', - 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', - 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', - 'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen', - 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', - 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', - 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', - 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', - 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', - 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', - 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', - 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', - 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', - 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', - 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', - 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', - 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', - 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', - 'whitesmoke', 'yellow', 'yellowgreen' - ], - 'counter-style': [ - ['symbols()', 'symbols($1)'] - ], - 'counter-symbols': [ - 'cyclic', 'numeric', 'alphabetic', 'symbolic', 'additive', 'fixed' - ], - 'ending-shape': ['circle', 'ellipse'], - 'fill-rule': ['nonzero', 'evenodd'], - 'filter-function': [ - ['blur()', 'blur($1)'], - ['brightness()', 'brightness($1)'], - ['contrast()', 'contrast($1)'], - ['drop-shadow()', 'drop-shadow($1)'], - ['grayscale()', 'grayscale($1)'], - ['hue-rotate()', 'hue-rotate($1)'], - ['invert()', 'invert($1)'], - ['opacity()', 'opacity($1)'], - ['saturate()', 'saturate($1)'], - ['sepia()', 'sepia($1)'] - ], - 'font-variant-alternates': [ - 'normal', 'historical-forms', - ['stylistic()', 'stylistic($1)'], - ['styleset()', 'styleset($1)'], - ['character-variant()', 'character-variant($1)'], - ['swash()', 'swash($1)'], - ['ornaments()', 'ornaments($1)'], - ['annotation()', 'annotation($1)'] - ], - 'generic-font-name': [ - 'serif', 'sans-serif', 'cursive', 'fantasy', 'monospace' - ], - 'gradient': [ - ['conic-gradient()', 'conic-gradient($1)'], - ['linear-gradient()', 'linear-gradient($1)'], - ['radial-gradient()', 'radial-gradient($1)'], - ['repeating-conic-gradient()', 'repeating-conic-gradient($1)'], - ['repeating-linear-gradient()', 'repeating-linear-gradient($1)'], - ['repeating-radial-gradient()', 'repeating-radial-gradient($1)'] - ], - 'grid': [ - ['repeat()', 'repeat(${1:2}, ${2:1fr})'], - ['minmax()', 'minmax(${1:100px}, ${2:1fr})'], - ], - 'image': [ - '', - ['image()', 'image($1)'], - ['image-set()', 'image-set($1)'], - ['element()', 'element($1)'], - ['paint()', 'paint($1)'], - ['cross-fade()', 'cross-fade($1)'], - ['linear-gradient()', 'linear-gradient($1)'], - ['repeating-linear-gradient()', 'repeating-linear-gradient($1)'], - ['radial-gradient()', 'radial-gradient($1)'], - ['repeating-radial-gradient()', 'repeating-radial-gradient($1)'], - ['conic-gradient()', 'conic-gradient($1)'], - ], - 'image-tags': ['ltr', 'rtl'], - 'line-style': [ - 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', - 'ridge', 'inset', 'outset' - ], - 'leader-type': [ - 'dotted', 'solid', 'space' - ], - 'list-style-type': [ - 'none', 'inline', 'disc', 'circle', 'square', 'decimal', - 'decimal-leading-zero', 'arabic-indic', 'binary', 'bengali', - 'cambodian', 'khmer', 'devanagari', 'gujarati', 'gurmukhi', - 'kannada', 'lower-hexadecimal', 'lao', 'malayalam', 'mongolian', - 'myanmar', 'octal', 'oriya', 'persian', 'urdu', 'telugu', - 'tibetan', 'thai', 'upper-hexadecimal', 'lower-roman', - 'upper-roman', 'lower-greek', 'lower-alpha', 'lower-latin', - 'upper-alpha', 'upper-latin', 'afar', 'ethiopic-halehame-aa-et', - 'ethiopic-halehame-aa-er', 'amharic', 'ethiopic-halehame-am-et', - 'amharic-abegede', 'ethiopic-abegede-am-et', 'cjk-earthly-branch', - 'cjk-heavenly-stem', 'ethiopic', 'ethiopic-halehame-gez', - 'ethiopic-abegede', 'ethiopic-abegede-gez', 'hangul-consonant', - 'hangul', 'lower-norwegian', 'oromo', 'ethiopic-halehame-om-et', - 'sidama', 'ethiopic-halehame-sid-et', 'somali', - 'ethiopic-halehame-so-et', 'tigre', 'ethiopic-halehame-tig', - 'tigrinya-er', 'ethiopic-halehame-ti-er', 'tigrinya-er-abegede', - 'ethiopic-abegede-ti-er', 'tigrinya-et', 'ethiopic-halehame-ti-et', - 'tigrinya-et-abegede', 'ethiopic-abegede-ti-et', 'upper-greek', - 'upper-norwegian', 'asterisks', 'footnotes', 'hebrew', 'armenian', - 'lower-armenian', 'upper-armenian', 'georgian', 'cjk-ideographic', - 'hiragana', 'katakana', 'hiragana-iroha', 'katakana-iroha' - ], - 'position': ['', 'center'], - 'relative-size': ['larger', 'smaller'], - 'relative-weight': ['bolder', 'lighter'], - 'repeat-style': [ - 'repeat', 'repeat-x', 'repeat-y', 'space', 'round', 'no-repeat' - ], - 'self-position': [ - 'center', 'start', 'end', 'self-start', 'self-end', 'flex-start', - 'flex-end' - ], - 'shape-radius': [ - 'closest-side', 'farthest-side' - ], - 'side-or-corner': [ - 'left', 'right', 'top', 'bottom' - ], - 'timing-function': [ - 'linear', - 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'step-start', 'step-end', - ['cubic-bezier()', 'cubic-bezier(${1:0.0}, ${2:0.0}, ${3:1.0}, ${4:1.0})'], - ['steps()', 'steps(${1:2}, ${2:start})'], - ], - 'type-or-unit': [ - 'string', 'color', 'url', 'integer', 'number', 'length', 'angle', - 'time', 'frequency', 'cap', 'ch', 'em', 'ex', 'ic', 'lh', 'rlh', - 'rem', 'vb', 'vi', 'vw', 'vh', 'vmin', 'vmax', 'mm', 'Q', 'cm', - 'in', 'pt', 'pc', 'px', 'deg', 'grad', 'rad', 'turn', 'ms', 's', - 'Hz', 'kHz', '%' - ], - 'url': [['url()', 'url($1)']], - } - - resolved_values = {} - - def resolve(vals): - vals_resolved = [] - for val in vals: - if val[0] == '<' and val[-1] == '>': - key = val[1:-1] - resolved = resolved_values.get(key) - if resolved: - vals_resolved += resolved - continue - resolved = common_values.get(key) - if resolved: - vals_resolved += resolve(resolved) - continue - - vals_resolved.append(val) - return vals_resolved - - for val_key, val_vals in common_values.items(): - resolved_values[val_key] = resolve(val_vals) - - return resolved_values - - -def get_func_args(): - - common_values = get_common_values() - - func_args = { - 'attr': ['', ''], - 'blur': [''], - 'brightness': [''], - 'calc': [ - ['attr()', 'attr($1)'], - '' - ], - 'circle': ['', '', 'at', ''], - 'clamp': [ - ['attr()', 'attr($1)'], - '' - ], - 'conic-gradient': ['from', 'at', '', ''], - 'contrast': [], - 'counter': [''], - 'counters': [''], - 'cross-fade': ['', ''], - 'cubic-bezier': [''], - 'drop-shadow': ['', ''], - 'element': [], - 'ellipse': ['', '', 'at', ''], - 'env': [], - 'filter': ['', ''], - 'fit-content': [], - 'grayscale': [''], - 'hsl': [''], - 'hsla': [''], - 'hue-rotate': [''], - 'image': ['', '', ''], - 'image-set': [ - ['type()', 'type($)'], - '', '' - ], - 'inset': ['', 'round'], - 'invert': [''], - 'leader': [''], - 'linear-gradient': ['', '', 'to'], - 'matrix': [''], - 'matrix3d': [''], - 'max': [ - ['attr()', 'attr($1)'], - '' - ], - 'min': [ - ['attr()', 'attr($1)'], - '' - ], - 'minmax': ['min-content', 'max-content', 'auto'], - 'opacity': [''], - 'path': [''], - 'paint': [], - 'perspective': [''], - 'polygon': ['', ''], - 'radial-gradient': [ - '', '', 'at', '', '' - ], - 'rect': ['', 'auto'], - 'repeat': ['', 'auto-fill', 'auto-fit'], - 'repeating-conic-gradient': ['from', 'at', '', ''], - 'repeating-linear-gradient': ['', '', 'to'], - 'repeating-radial-gradient': [ - '', '', 'at', '', '' - ], - 'rgb': [''], - 'rgba': [''], - 'rotate': [''], - 'rotate3d': [''], - 'rotateX': [''], - 'rotateY': [''], - 'rotateZ': [''], - 'saturate': [''], - 'scale': [''], - 'scale3d': [''], - 'scaleX': [''], - 'scaleY': [''], - 'scaleZ': [''], - 'skew': [''], - 'skewX': [''], - 'skewY': [''], - 'sepia': [''], - 'steps': ['', 'end', 'middle', 'start'], - 'target-counter': ['', ''], - 'target-counters': ['', ''], - 'target-text': ['', 'content', 'before', 'after', 'first-letter'], - 'toggle': ['', ''], - 'translate': [''], - 'translate3d': [''], - 'translateX': [''], - 'translateY': [''], - 'translateZ': [''], - 'var': [], - } - - completions = {} - - for func, args in func_args.items(): - # args that are allowed for all properties - expanded_args = [['var()', 'var($1)']] - - # Determine which args are available for the current property name - for arg in args: - if arg[0] == '<' and arg[-1] == '>': - key = arg[1:-1] - if key in common_values: - expanded_args += common_values[key] - else: - expanded_args.append(arg) - - completions[func] = expanded_args - - return completions - - -def get_properties(): - ''' - Gets the properties. - - Prepare some common property values for when there is more than one way to - specify a certain value type. The color value for example can be specified - by `rgb()` or `hsl()` and so on. Example where `|` denotes the caret: - - color: rg| --> color: rgb(|); - - This is also helpful when multiple properties share the same value types. - ''' - common_values = get_common_values() - - properties_dict = { - 'align-content': [ - 'center', 'flex-end', 'flex-start', 'space-around', 'space-between', - 'stretch' - ], - 'align-items': [ - 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' - ], - 'align-self': [ - 'auto', 'baseline', 'center', 'flex-end', 'flex-start', 'stretch' - ], - 'alignment-baseline': [ - 'baseline', 'middle', 'auto', 'before-edge', 'after-edge', 'central', - 'text-before-edge', 'text-after-edge', 'ideographic', 'alphabetic', - 'hanging', 'mathematical' - ], - 'animation': [ - 'none', '', 'infinite', '', - 'forwards', 'backwards', 'both', 'running', 'paused' - ], - 'animation-name': ['none', ''], - 'animation-duration': ['