def variable_color_swatch(view: sublime.View, region: sublime.Region, definition_scope_selector: str, show_errors: bool, on_pre_show_popup, on_hide_popup) -> None: """ Display preview for color variable in Sass, SCSS & Less """ variable_name = view.substr(region) definition_regions = [region for region in view.find_by_selector(definition_scope_selector) if view.substr(region) == variable_name] if len(definition_regions) == 0: status_message(view, show_errors, 'No definition found for variable {}'.format(variable_name)) return elif len(definition_regions) > 1: status_message(view, show_errors, 'More than one definition found for variable {}'.format(variable_name)) return a = view.find(r'\S', definition_regions[0].b).a msg = 'No valid color could be identified for variable {}'.format(variable_name) if view.substr(a) != ':': status_message(view, show_errors, msg) return a += 1 b = view.find_by_class(definition_regions[0].b, forward=True, classes=sublime.CLASS_LINE_END) if a >= b: status_message(view, show_errors, msg) return value_region = sublime.Region(a, b) text = re.split('[;}]', view.substr(value_region))[0].strip() mcolor = Color.match(text) if mcolor is not None: mcolor.color.convert('srgb', in_place=True) # type: ignore r = int(255 * mcolor.color.red) g = int(255 * mcolor.color.green) b = int(255 * mcolor.color.blue) a = mcolor.color.alpha rgba_color_swatch(view, region, r, g, b, a, on_pre_show_popup, on_hide_popup) return status_message(view, show_errors, msg)
def on_hover(self, view: sublime.View, point: int, hover_zone: int) -> None: if hover_zone != sublime.HOVER_TEXT: return if self.active_region and self.active_region.contains(point): # prevent flickering on small mouse movements return settings = sublime.load_settings(SETTINGS_FILE) if view.match_selector(point, settings.get('image_scope_selector')): if not settings.get('image_preview'): return region = view.extract_scope(point) image_preview(view, region, settings, settings.get('extensionless_image_preview'), False, self.set_active_region, self.reset_active_region) elif settings.get('color_preview'): if view.match_selector(point, SCOPE_SELECTOR_CSS_COLORNAME): region = view.word(point) rgb_color_swatch(view, region, self.set_active_region, self.reset_active_region) elif view.match_selector(point, SCOPE_SELECTOR_CSS_RGB_LITERAL): region = view.extract_scope(point) if region.a > 0 and region.size() in [3, 6] and view.substr(region.a - 1) == '#': # fix for unconventional scopes from SCSS package region.a -= 1 rgb_color_swatch(view, region, self.set_active_region, self.reset_active_region) elif view.match_selector(point, SCOPE_SELECTOR_CSS_RGBA_LITERAL): region = view.extract_scope(point) r, g, b, a = hex2rgba(view.substr(region)) rgba_color_swatch(view, region, r, g, b, a, self.set_active_region, self.reset_active_region) elif view.match_selector(point, SCOPE_SELECTOR_CSS_CUSTOM_PROPERTY_REFERENCE): region = view.extract_scope(point) css_custom_property_color_swatch(view, region, False, self.set_active_region, self.reset_active_region) elif view.match_selector(point, SCOPE_SELECTOR_SASS_VARIABLE_REFERENCE): region = view.extract_scope(point) variable_color_swatch(view, region, SCOPE_SELECTOR_SASS_VARIABLE_DEFINITION, False, self.set_active_region, self.reset_active_region) elif view.match_selector(point, SCOPE_SELECTOR_LESS_VARIABLE_REFERENCE): region = view.extract_scope(point) variable_color_swatch(view, region, SCOPE_SELECTOR_LESS_VARIABLE_DEFINITION, False, self.set_active_region, self.reset_active_region) elif view.match_selector(point, SCOPE_SELECTOR_SUBLIME_COLOR_SCHEME_VARIABLE_REFERENCE): region = view.extract_scope(point) sublime_variable_color_swatch(view, region, False, self.set_active_region, self.reset_active_region) # for now color variables from themes are not supported, because they can use legacy color syntax and it would be required to resolve 'extends' for themes elif view.match_selector(point, SCOPE_SELECTOR_CSS_FUNCTION): regions = view.find_by_selector(SCOPE_SELECTOR_CSS_FUNCTION) for region in regions: if region.contains(point): logging.debug(view.substr(region)) if view.match_selector(region.a, 'support.function.color'): mcolor = Color.match(view.substr(region), fullmatch=True) # https://facelessuser.github.io/coloraide/color/#color-matching if mcolor is not None: mcolor.color.convert('srgb', in_place=True) # type: ignore r = int(255 * mcolor.color.red) g = int(255 * mcolor.color.green) b = int(255 * mcolor.color.blue) a = mcolor.color.alpha rgba_color_swatch(view, region, r, g, b, a, self.set_active_region, self.reset_active_region) return elif view.match_selector(region.a, 'support.function.gradient'): # @todo Find a python library to parse CSS gradients and convert to png image if possible logging.debug('CSS gradients are not supported yet') return # if view.substr(region).startswith('linear-gradient'): # pass break
def find_colors(text): """Find colors in text buffer.""" colors = [] for m in RE_COLOR_START.finditer(text): start = m.start() mcolor = Color.match(text, start=start) if mcolor is not None: colors.append(ColorTuple(text[mcolor.start:mcolor.end], mcolor.color)) return colors
def parse_color(string, start=0, second=False): """ Parse colors. The return of `more`: - `None`: there is no more colors to process - `True`: there are more colors to process - `False`: there are more colors to process, but we failed to find them. """ length = len(string) more = None percent = None space = None # First color color = Color.match(string, start=start, fullmatch=False) if color: start = color.end if color.end != length: more = True # Percentage if provided m = RE_PERCENT.match(string, start) if m: start = m.end(0) text = m.group(1) percent = float(text.rstrip('%')) / 100.0 # Is the first color in the input or the second? if not second: # Plus sign indicating we have an additional color to mix m = RE_PLUS.match(string, start) if m: start = m.end(0) more = start != length else: more = False else: # Color space indicator m = RE_SPACE.match(string, start) if m: text = m.group(1).lower() if text in color.color.CS_MAP: space = text start = m.end(0) more = None if start == length else False if color: color.end = start return color, percent, more, space
def parse_color_contrast(string, start=0, second=False): """ Parse colors. The return of `more`: - `None`: there is no more colors to process - `True`: there are more colors to process - `False`: there are more colors to process, but we failed to find them. """ length = len(string) more = None ratio = None # First color color = Color.match(string, start=start, fullmatch=False, filters=util.SRGB_SPACES) if color: start = color.end if color.end != length: more = True m = RE_RATIO.match(string, start) if m: ratio = float(m.group(1)) start = m.end(0) # Is the first color in the input or the second? if not second and not ratio: # Plus sign indicating we have an additional color to mix m = RE_SLASH.match(string, start) if m and not ratio: start = m.end(0) more = start != length else: more = False else: more = None if start == length else False if color: color.end = start return color, ratio, more
def css_custom_property_color_swatch(view: sublime.View, region: sublime.Region, show_errors: bool, on_pre_show_popup, on_hide_popup) -> None: """ Display preview for custom properties (variables) in CSS """ custom_property_name = view.substr(region) definition_regions = [region for region in view.find_by_selector(SCOPE_SELECTOR_CSS_CUSTOM_PROPERTY_DEFINITION) if view.substr(region) == custom_property_name] # only proceed if there is exactly 1 definition for the custom property, because this implementation is # not aware of CSS rule scopes or possible inheritance resulting from the HTML structure if len(definition_regions) == 0: status_message(view, show_errors, 'No definition found for custom property {}'.format(custom_property_name)) return elif len(definition_regions) > 1: status_message(view, show_errors, 'More than one definition found for custom property {}'.format(custom_property_name)) return # extract next token a = view.find(r'\S', definition_regions[0].b).a msg = 'No valid color could be identified for custom property {}'.format(custom_property_name) if view.substr(a) != ':': status_message(view, show_errors, msg) return a += 1 b = view.find_by_class(definition_regions[0].b, forward=True, classes=sublime.CLASS_LINE_END) if a >= b: status_message(view, show_errors, msg) return value_region = sublime.Region(a, b) text = re.split('[;}]', view.substr(value_region))[0].strip() mcolor = Color.match(text, fullmatch=True) # fullmatch=True ensures that the custom property is only a color if mcolor is not None: mcolor.color.convert('srgb', in_place=True) # type: ignore r = int(255 * mcolor.color.red) g = int(255 * mcolor.color.green) b = int(255 * mcolor.color.blue) a = mcolor.color.alpha rgba_color_swatch(view, region, r, g, b, a, on_pre_show_popup, on_hide_popup) return status_message(view, show_errors, msg)
def sublime_variable_color_swatch(view: sublime.View, region: sublime.Region, show_errors: bool, on_pre_show_popup, on_hide_popup) -> None: """ Display preview for color variables in Sublime resource files (JSON) """ filename = view.file_name() variable_name = view.substr(region) value = None if filename: # search for variable also in overridden files data_path = os.path.dirname(sublime.packages_path()) for resource in sublime.find_resources(os.path.basename(filename)): try: is_current_view = os.path.samefile(filename, os.path.join(data_path, resource)) # use buffer content for current view, because there can be unsaved changes content = sublime.decode_value(view.substr(sublime.Region(0, view.size()))) if is_current_view else sublime.decode_value(sublime.load_resource(resource)) if isinstance(content, dict) and isinstance(content.get('variables'), dict) and isinstance(content['variables'].get(variable_name), str): value = content['variables'][variable_name] except: pass else: # search for variable only in current view try: content = sublime.decode_value(view.substr(sublime.Region(0, view.size()))) if isinstance(content, dict) and isinstance(content.get('variables'), dict) and isinstance(content['variables'].get(variable_name), str): value = content['variables'][variable_name] except: # @todo Try to resolve variable via scopes like in CSS pass if value: mcolor = Color.match(value, fullmatch=True) # @todo Also support minihtml color() mod function if mcolor is not None: mcolor.color.convert('srgb', in_place=True) # type: ignore r = int(255 * mcolor.color.red) g = int(255 * mcolor.color.green) b = int(255 * mcolor.color.blue) a = mcolor.color.alpha rgba_color_swatch(view, region, r, g, b, a, on_pre_show_popup, on_hide_popup) return status_message(view, show_errors, 'No valid color could be identified for variable {}'.format(variable_name))
def run(self, edit: sublime.Edit) -> None: if self.popup_active: self.view.hide_popup() return try: region = self.view.sel()[0] # in case of multiple cursors only the first one is used, because there can only a single popup be visible at a time except IndexError: logging.error('no selections in the active view') return is_empty_selection = region.empty() if is_empty_selection: point = region.b region = self.view.line(point) elif len(self.view.lines(region)) > 1: window = self.view.window() if window: window.status_message('QuickView not possible for selections that span multiple lines') return else: point = region.begin() settings = sublime.load_settings(SETTINGS_FILE) if is_empty_selection and self.view.match_selector(point, settings.get('image_scope_selector')): region = self.view.extract_scope(point) image_preview(self.view, region, settings, True, True, self.set_popup_active, self.set_popup_inactive) return elif is_empty_selection and self.view.match_selector(point, SCOPE_SELECTOR_CSS_CUSTOM_PROPERTY_REFERENCE): region = self.view.extract_scope(point) css_custom_property_color_swatch(self.view, region, True, self.set_popup_active, self.set_popup_inactive) return elif is_empty_selection and self.view.match_selector(point, SCOPE_SELECTOR_SASS_VARIABLE_REFERENCE): region = self.view.extract_scope(point) variable_color_swatch(self.view, region, SCOPE_SELECTOR_SASS_VARIABLE_DEFINITION, True, self.set_popup_active, self.set_popup_inactive) return elif is_empty_selection and self.view.match_selector(point, SCOPE_SELECTOR_LESS_VARIABLE_REFERENCE): region = self.view.extract_scope(point) variable_color_swatch(self.view, region, SCOPE_SELECTOR_LESS_VARIABLE_DEFINITION, True, self.set_popup_active, self.set_popup_inactive) return elif is_empty_selection and self.view.match_selector(point, SCOPE_SELECTOR_SUBLIME_COLOR_SCHEME_VARIABLE_REFERENCE): region = self.view.extract_scope(point) sublime_variable_color_swatch(self.view, region, True, self.set_popup_active, self.set_popup_inactive) return else: text = self.view.substr(region) offset = region.begin() for m in IMAGE_URI_PATTERN.finditer(text): if not is_empty_selection or m.start() <= point - offset <= m.end(): link_region = sublime.Region(offset + m.start(), offset + m.end()) logging.debug('potential image URI found: %s', self.view.substr(link_region)) image_preview(self.view, link_region, settings, True, True, self.set_popup_active, self.set_popup_inactive) return else: break for m in COLOR_START_PATTERN.finditer(text): if not is_empty_selection or m.start() <= point - offset: mcolor = Color.match(text, start=m.start()) if mcolor is not None and (not is_empty_selection or point - offset <= mcolor.end): # type: ignore color_region = sublime.Region(offset + mcolor.start, offset + mcolor.end) # type: ignore mcolor.color.convert('srgb', in_place=True) # type: ignore r = int(255 * mcolor.color.red) g = int(255 * mcolor.color.green) b = int(255 * mcolor.color.blue) a = mcolor.color.alpha rgba_color_swatch(self.view, color_region, r, g, b, a, self.set_popup_active, self.set_popup_inactive) return else: break msg = 'QuickView not possible at current cursor position' if is_empty_selection else 'QuickView not available for selection "{}"'.format(text) window = self.view.window() if window: window.status_message(msg)