From 7ee91d7feb18823403a8ff37e791bfd34179ef1b Mon Sep 17 00:00:00 2001 From: TheSecEng Date: Wed, 15 Apr 2020 13:28:44 -0400 Subject: [PATCH] update: phantoms rather than regions --- PrettyJson.py | 187 +++++++++--------- PrettyJsonListeners.py | 4 +- {simplejson => libs/simplejson}/__init__.py | 0 {simplejson => libs/simplejson}/_speedups.c | 0 {simplejson => libs/simplejson}/compat.py | 0 {simplejson => libs/simplejson}/decoder.py | 0 {simplejson => libs/simplejson}/encoder.py | 0 {simplejson => libs/simplejson}/errors.py | 0 .../simplejson}/ordered_dict.py | 0 {simplejson => libs/simplejson}/raw_json.py | 0 {simplejson => libs/simplejson}/scanner.py | 0 {simplejson => libs/simplejson}/tool.py | 0 phantom.css | 28 +++ 13 files changed, 124 insertions(+), 95 deletions(-) rename {simplejson => libs/simplejson}/__init__.py (100%) rename {simplejson => libs/simplejson}/_speedups.c (100%) rename {simplejson => libs/simplejson}/compat.py (100%) rename {simplejson => libs/simplejson}/decoder.py (100%) rename {simplejson => libs/simplejson}/encoder.py (100%) rename {simplejson => libs/simplejson}/errors.py (100%) rename {simplejson => libs/simplejson}/ordered_dict.py (100%) rename {simplejson => libs/simplejson}/raw_json.py (100%) rename {simplejson => libs/simplejson}/scanner.py (100%) rename {simplejson => libs/simplejson}/tool.py (100%) create mode 100644 phantom.css diff --git a/PrettyJson.py b/PrettyJson.py index 637e1e8..f84eb85 100644 --- a/PrettyJson.py +++ b/PrettyJson.py @@ -3,21 +3,21 @@ import os import re import subprocess import sys -from xml.dom import minidom from xml.etree import ElementTree import sublime import sublime_plugin -from . import simplejson as json -from .simplejson import OrderedDict +from .libs import simplejson as json +from .libs.simplejson import OrderedDict SUBLIME_MAJOR_VERSION = int(sublime.version()) / 1000 +s = sublime.load_settings("Pretty JSON.sublime-settings") + xml_syntax = "Packages/XML/XML.tmLanguage" json_syntax = "Packages/JSON/JSON.tmLanguage" - jq_exists = False jq_init = False @@ -51,10 +51,9 @@ def check_jq(): jq_exists = False -s = sublime.load_settings("Pretty JSON.sublime-settings") - class PrettyJsonBaseCommand: json_error_matcher = re.compile(r"line (\d+)") + json_char_matcher = re.compile(r"char (\d+)") force_sorting = False @staticmethod @@ -104,7 +103,7 @@ class PrettyJsonBaseCommand: def json_dumps_minified(obj): line_separator = s.get("line_separator", ",") """:type : str""" - value_separator = s.get("value_separator", ": ") + value_separator = s.get("value_separator", ":") """:type : str""" sort_keys = s.get("sort_keys", False) @@ -121,12 +120,11 @@ class PrettyJsonBaseCommand: def reindent(self, text, selection): current_line = self.view.line(selection.begin()) - text_before_sel = sublime.Region( - current_line.begin(), selection.begin()) + text_before_sel = sublime.Region(current_line.begin(), selection.begin()) - reindent_mode = s.get('reindent_block', 'minimal') + reindent_mode = s.get("reindent_block", "minimal") - if reindent_mode == 'start': + if reindent_mode == "start": # Reindent to the column where the selection starts space_number = text_before_sel.size() indent_space = " " * space_number @@ -136,28 +134,39 @@ class PrettyJsonBaseCommand: # Extracts the spaces at the start of the line to use them # as padding - indent_space = re.search( - '^\s*', self.view.substr(text_before_sel)).group(0) + indent_space = re.search("^\s*", self.view.substr(text_before_sel)).group(0) - lines = text.split('\n') + lines = text.split("\n") # Pad every line except the first one i = 1 - while (i < len(lines)): + while i < len(lines): lines[i] = indent_space + lines[i] i += 1 return "\n".join(lines) + def show_exception(self, region, msg): + sublime.status_message("[Error]: {}".format(msg)) + if region is None: + return + self.highlight_error(region=region, message="{}".format(msg)) - def highlight_error(self, message): + def highlight_error(self, region, message): + self.phantom_set = sublime.PhantomSet(self.view, "json_errors") + self.phantoms = list() - self.view.erase_regions("json_errors") - self.view.erase_status("json_errors") - - m = self.json_error_matcher.search(message) - if m: - line = int(m.group(1)) - 1 + matches = self.json_error_matcher.search(message) + char_match = self.json_char_matcher.search(message) + if char_match: + if region.a > region.b: + region.b += int(char_match.group(1)) + 1 + region.a = region.b + 1 + else: + region.a += int(char_match.group(1)) + 1 + region.b = region.a + 1 + if matches: + line = int(matches.group(1)) - 1 # sometime we need to highlight one line above if "','" in message and "delimiter" in message: @@ -165,7 +174,8 @@ class PrettyJsonBaseCommand: self.view.full_line(self.view.text_point(line - 1, 0)) ) if ( - line_content.strip()[-1] != "," + line_content.strip() + and line_content.strip()[-1] != "," and line_content.strip() != "{" and line_content.strip() != "}" ): @@ -179,19 +189,38 @@ class PrettyJsonBaseCommand: if len(quotes) % 2 != 0 and len(quotes) != 0: line -= 1 - regions = [ - self.view.full_line(self.view.text_point(line, 0)), - ] - - self.view.add_regions( - "json_errors", regions, "invalid", "dot", sublime.DRAW_OUTLINED + self.phantoms.append( + sublime.Phantom( + region, + self.create_phantom_html(message, "error"), + sublime.LAYOUT_BELOW, + self.navigation, + ) ) - self.view.show(regions[0]) + self.phantom_set.update(self.phantoms) self.view.set_status("json_errors", message) - def show_exception(self, msg): - sublime.status_message("[Error]: {}".format(msg)) - self.highlight_error("{}".format(msg)) + # Description: Taken from https://github.com/sublimelsp/LSP/blob/master/plugin/diagnostics.py + # - Thanks to the LSP Team + def create_phantom_html(self, content: str, severity: str) -> str: + stylesheet = sublime.load_resource("Packages/SublimePrettyJson/phantom.css") + return """ + +
+
+
+ × +
+
{}
+
+ """.format( + stylesheet, severity, severity, content + ) + + def navigation(self, href): + if href == "hide": + self.phantoms = list() + self.phantom_set.update(self.phantoms) def syntax_to_json(self): """ Changes syntax to JSON if its in plain text """ @@ -208,6 +237,7 @@ class PrettyJsonValidate(PrettyJsonBaseCommand, sublime_plugin.TextCommand): continue elif region.empty() and s.get("use_entire_file_if_no_selection", True): selection = sublime.Region(0, self.view.size()) + region = sublime.Region(0, self.view.size()) else: selection = region @@ -215,12 +245,13 @@ class PrettyJsonValidate(PrettyJsonBaseCommand, sublime_plugin.TextCommand): self.json_loads(self.view.substr(selection)) sublime.status_message("JSON is Valid") except Exception as ex: - self.show_exception(msg=ex) - sublime.message_dialog("Invalid JSON") + self.show_exception(region=region, msg=ex) class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): - """ Pretty Print JSON """ + """ + Description: Pretty Print JSON + """ def run(self, edit): self.view.erase_regions("json_errors") @@ -231,6 +262,7 @@ class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): continue elif region.empty() and s.get("use_entire_file_if_no_selection", True): selection = sublime.Region(0, self.view.size()) + region = sublime.Region(0, self.view.size()) selected_entire_file = True else: selection = region @@ -251,33 +283,36 @@ class PrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): except Exception as ex: try: - amount_of_single_quotes = re.findall(r"(\'[^\']+\'?)", selection_text) - amount_of_double_quotes = re.findall(r"(\"[^\"]+\"?)", selection_text) + amount_of_single_quotes = re.findall( + r"(\'[^\']+\'?)", selection_text + ) + amount_of_double_quotes = re.findall( + r"(\"[^\"]+\"?)", selection_text + ) if len(amount_of_single_quotes) >= len(amount_of_double_quotes): - selection_text_modified = re.sub(r"(?:\'([^\']+)\'?)", r'"\1"', selection_text) + selection_text_modified = re.sub( + r"(?:\'([^\']+)\'?)", r'"\1"', selection_text + ) obj = self.json_loads(selection_text_modified) - json_text = self.json_dumps(obj) if not selected_entire_file and s.get("reindent_block", False): json_text = self.reindent(json_text, selection) self.view.replace(edit, selection, json_text) - - if selected_entire_file: self.syntax_to_json() else: - self.show_exception(msg=ex) + self.show_exception(region=region, msg=ex) except Exception as ex: - self.show_exception(msg=ex) - + self.show_exception(region=region, msg=ex) class PrettyJsonAndSortCommand(PrettyJsonCommand, sublime_plugin.TextCommand): - - """ Pretty print json with forced sorting """ + """ + Description: Pretty print json with forced sorting + """ def run(self, edit): PrettyJsonBaseCommand.force_sorting = True @@ -286,18 +321,19 @@ class PrettyJsonAndSortCommand(PrettyJsonCommand, sublime_plugin.TextCommand): class UnPrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): - - """ Compress/minify JSON - it makes json as one-liner """ + """ + Description: Compress/minify JSON - it makes json as one-liner + """ def run(self, edit): self.view.erase_regions("json_errors") for region in self.view.sel(): - selected_entire_file = False # If no selection, use the entire file as the selection if region.empty() and s.get("use_entire_file_if_no_selection", True): selection = sublime.Region(0, self.view.size()) + region = sublime.Region(0, self.view.size()) selected_entire_file = True else: selection = region @@ -307,15 +343,15 @@ class UnPrettyJsonCommand(PrettyJsonBaseCommand, sublime_plugin.TextCommand): self.view.replace(edit, selection, self.json_dumps_minified(obj)) if selected_entire_file: - self.syntax_to_xml() + self.syntax_to_json() except Exception as ex: - self.show_exception(msg=ex) + self.show_exception(region=region, msg=ex) class JqPrettyJson(sublime_plugin.WindowCommand): """ - Allows work with ./jq + Description: Allows work with ./jq """ def run(self): @@ -351,14 +387,7 @@ class JqPrettyJson(sublime_plugin.WindowCommand): ) raw_json = self.get_content() - - if SUBLIME_MAJOR_VERSION < 3: - if sys.platform != "win32": - out, err = p.communicate(bytes(raw_json)) - else: - out, err = p.communicate(unicode(raw_json).encode("utf-8")) - else: - out, err = p.communicate(bytes(raw_json, "utf-8")) + out, err = p.communicate(bytes(raw_json, "utf-8")) output = out.decode("UTF-8").replace(os.linesep, "\n").strip() if output: view = self.window.new_file() @@ -372,7 +401,7 @@ class JqPrettyJson(sublime_plugin.WindowCommand): class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand): """ - converts Json to XML + Description: converts Json to XML """ def run(self, edit): @@ -396,9 +425,6 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand): xml_string = "\n" - if SUBLIME_MAJOR_VERSION < 3: - self.indent_for_26(root) - rtn = ElementTree.tostring(root, "utf-8") if type(rtn) is bytes: @@ -406,17 +432,9 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand): xml_string += rtn - # for some reason python 2.6 shipped with ST2 - # does not have pyexpat - if SUBLIME_MAJOR_VERSION >= 3: - xml_string = minidom.parseString(xml_string).toprettyxml( - encoding="UTF-8" - ) - if type(xml_string) is bytes: xml_string = xml_string.decode("utf-8") - if not selected_entire_file and s.get("reindent_block", False): xml_string = self.reindent(xml_string, selection) @@ -426,23 +444,7 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand): self.syntax_to_xml() except Exception as ex: - self.show_exception(msg=ex) - - def indent_for_26(self, elem, level=0): - """ intent of ElementTree in case it's py26 without minidom/pyexpat """ - i = "\n" + level * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - self.indent_for_26(elem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i + self.show_exception(region=region, msg=ex) def traverse(self, element, json_dict): """ recursive traverse through dict and build xml tree """ @@ -466,7 +468,7 @@ class JsonToXml(PrettyJsonBaseCommand, sublime_plugin.TextCommand): class JqPrettyJsonOut(sublime_plugin.TextCommand): - def run(self, edit, jq_output=""): + def run(self, edit, jq_output=str()): self.view.insert(edit, 0, jq_output) @@ -476,13 +478,12 @@ class PrettyJsonGotoSymbolCommand(PrettyJsonBaseCommand, sublime_plugin.TextComm self.goto_items = [] content = self.view.substr(sublime.Region(0, self.view.size())) - try: json_data = self.json_loads(content) self.generate_items(json_data, "") sublime.active_window().show_quick_panel(self.items, self.goto) except Exception as ex: - self.show_exception(msg=ex) + self.show_exception(region=None, msg=ex) def generate_items(self, json_data, root_key): if isinstance(json_data, OrderedDict): diff --git a/PrettyJsonListeners.py b/PrettyJsonListeners.py index b18bfa2..8246281 100644 --- a/PrettyJsonListeners.py +++ b/PrettyJsonListeners.py @@ -27,8 +27,8 @@ class PrettyJsonLintListener(sublime_plugin.EventListener, PrettyJsonBaseCommand try: self.json_loads(json_content) - except Exception: - self.show_exception() + except Exception as ex: + self.show_exception(msg=ex) class PrettyJsonAutoPrettyOnSaveListener(sublime_plugin.EventListener): diff --git a/simplejson/__init__.py b/libs/simplejson/__init__.py similarity index 100% rename from simplejson/__init__.py rename to libs/simplejson/__init__.py diff --git a/simplejson/_speedups.c b/libs/simplejson/_speedups.c similarity index 100% rename from simplejson/_speedups.c rename to libs/simplejson/_speedups.c diff --git a/simplejson/compat.py b/libs/simplejson/compat.py similarity index 100% rename from simplejson/compat.py rename to libs/simplejson/compat.py diff --git a/simplejson/decoder.py b/libs/simplejson/decoder.py similarity index 100% rename from simplejson/decoder.py rename to libs/simplejson/decoder.py diff --git a/simplejson/encoder.py b/libs/simplejson/encoder.py similarity index 100% rename from simplejson/encoder.py rename to libs/simplejson/encoder.py diff --git a/simplejson/errors.py b/libs/simplejson/errors.py similarity index 100% rename from simplejson/errors.py rename to libs/simplejson/errors.py diff --git a/simplejson/ordered_dict.py b/libs/simplejson/ordered_dict.py similarity index 100% rename from simplejson/ordered_dict.py rename to libs/simplejson/ordered_dict.py diff --git a/simplejson/raw_json.py b/libs/simplejson/raw_json.py similarity index 100% rename from simplejson/raw_json.py rename to libs/simplejson/raw_json.py diff --git a/simplejson/scanner.py b/libs/simplejson/scanner.py similarity index 100% rename from simplejson/scanner.py rename to libs/simplejson/scanner.py diff --git a/simplejson/tool.py b/libs/simplejson/tool.py similarity index 100% rename from simplejson/tool.py rename to libs/simplejson/tool.py diff --git a/phantom.css b/phantom.css new file mode 100644 index 0000000..e20e804 --- /dev/null +++ b/phantom.css @@ -0,0 +1,28 @@ +div.error-arrow { + border-top: 0.4rem solid transparent; + border-left: 0.5rem solid color(var(--redish) blend(var(--background) 30%)); + width: 0; + height: 0; +} +div.container { + margin: 0; + border-radius: 0 0.2rem 0.2rem 0.2rem; +} +div.content { + padding: 0.4rem 0.4rem 0.4rem 0.7rem; +} +div.content p { + margin: 0.2rem; +} +div.toolbar { + padding: 0.2rem 0.7rem 0.2rem 0.7rem; +} +div.toolbar a { + text-decoration: none +} +html.dark div.toolbar { + background-color: #00000018; +} +html.light div.toolbar { + background-color: #ffffff18; +} \ No newline at end of file