Format everything with black

Follow the readme's instruction haha
This commit is contained in:
Mathieu PATUREL
2019-11-16 09:54:07 +11:00
parent e13842ede4
commit 2785df74ce
5 changed files with 875 additions and 551 deletions

View File

@ -12,20 +12,24 @@ PREVIEW_VIEW_INFOS = "preview_view_infos"
# FIXME: put this as a setting for the user to choose? # FIXME: put this as a setting for the user to choose?
DELAY = 100 # ms DELAY = 100 # ms
def get_resource(resource): def get_resource(resource):
path = 'Packages/MarkdownLivePreview/resources/' + resource path = "Packages/MarkdownLivePreview/resources/" + resource
abs_path = os.path.join(sublime.packages_path(), '..', path) abs_path = os.path.join(sublime.packages_path(), "..", path)
if os.path.isfile(abs_path): if os.path.isfile(abs_path):
with open(abs_path, 'r') as fp: with open(abs_path, "r") as fp:
return fp.read() return fp.read()
return sublime.load_resource(path) return sublime.load_resource(path)
resources = {} resources = {}
def plugin_loaded(): def plugin_loaded():
resources["base64_loading_image"] = get_resource('loading.base64') resources["base64_loading_image"] = get_resource("loading.base64")
resources["base64_404_image"] = get_resource('404.base64') resources["base64_404_image"] = get_resource("404.base64")
resources["stylesheet"] = get_resource('stylesheet.css') resources["stylesheet"] = get_resource("stylesheet.css")
# try to reload the resources if we save this file # try to reload the resources if we save this file
try: try:
@ -40,13 +44,13 @@ except OSError:
# original_window: the regular window # original_window: the regular window
# preview_window: the window with the markdown file and the preview # preview_window: the window with the markdown file and the preview
class MdlpInsertCommand(sublime_plugin.TextCommand):
class MdlpInsertCommand(sublime_plugin.TextCommand):
def run(self, edit, point, string): def run(self, edit, point, string):
self.view.insert(edit, point, string) self.view.insert(edit, point, string)
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
def run(self, edit): def run(self, edit):
""" If the file is saved exists on disk, we close it, and reopen it in a new """ If the file is saved exists on disk, we close it, and reopen it in a new
@ -57,7 +61,7 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
original_window_id = original_view.window().id() original_window_id = original_view.window().id()
file_name = original_view.file_name() file_name = original_view.file_name()
syntax_file = original_view.settings().get('syntax') syntax_file = original_view.settings().get("syntax")
if file_name: if file_name:
original_view.close() original_view.close()
@ -70,41 +74,44 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
# FIXME: save the document to a temporary file, so that if we crash, # FIXME: save the document to a temporary file, so that if we crash,
# the user doesn't lose what he wrote # the user doesn't lose what he wrote
sublime.run_command('new_window') sublime.run_command("new_window")
preview_window = sublime.active_window() preview_window = sublime.active_window()
preview_window.run_command('set_layout', { preview_window.run_command(
'cols': [0.0, 0.5, 1.0], "set_layout",
'rows': [0.0, 1.0], {
'cells': [[0, 0, 1, 1], [1, 0, 2, 1]] "cols": [0.0, 0.5, 1.0],
}) "rows": [0.0, 1.0],
"cells": [[0, 0, 1, 1], [1, 0, 2, 1]],
},
)
preview_window.focus_group(1) preview_window.focus_group(1)
preview_view = preview_window.new_file() preview_view = preview_window.new_file()
preview_view.set_scratch(True) preview_view.set_scratch(True)
preview_view.settings().set(PREVIEW_VIEW_INFOS, {}) preview_view.settings().set(PREVIEW_VIEW_INFOS, {})
preview_view.set_name('Preview') preview_view.set_name("Preview")
preview_window.focus_group(0) preview_window.focus_group(0)
if file_name: if file_name:
markdown_view = preview_window.open_file(file_name) markdown_view = preview_window.open_file(file_name)
else: else:
markdown_view = preview_window.new_file() markdown_view = preview_window.new_file()
markdown_view.run_command('mdlp_insert', {'point': 0, 'string': content}) markdown_view.run_command("mdlp_insert", {"point": 0, "string": content})
markdown_view.set_scratch(True) markdown_view.set_scratch(True)
markdown_view.set_syntax_file(syntax_file) markdown_view.set_syntax_file(syntax_file)
markdown_view.settings().set(MARKDOWN_VIEW_INFOS, { markdown_view.settings().set(
"original_window_id": original_window_id MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id}
}) )
def is_enabled(self): def is_enabled(self):
# FIXME: is this the best way there is to check if the current syntax is markdown? # FIXME: is this the best way there is to check if the current syntax is markdown?
# should we only support default markdown? # should we only support default markdown?
# what about "md"? # what about "md"?
# FIXME: what about other languages, where markdown preview roughly works? # FIXME: what about other languages, where markdown preview roughly works?
return 'markdown' in self.view.settings().get('syntax').lower() return "markdown" in self.view.settings().get("syntax").lower()
class MarkdownLivePreviewListener(sublime_plugin.EventListener): class MarkdownLivePreviewListener(sublime_plugin.EventListener):
@ -153,30 +160,36 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
if not infos: if not infos:
return return
assert markdown_view.id() == self.markdown_view.id(), \ assert (
"pre_close view.id() != close view.id()" markdown_view.id() == self.markdown_view.id()
), "pre_close view.id() != close view.id()"
del self.phantom_sets[markdown_view.id()] del self.phantom_sets[markdown_view.id()]
self.preview_window.run_command('close_window') self.preview_window.run_command("close_window")
# find the window with the right id # find the window with the right id
original_window = next(window for window in sublime.windows() \ original_window = next(
if window.id() == infos['original_window_id']) window
for window in sublime.windows()
if window.id() == infos["original_window_id"]
)
if self.file_name: if self.file_name:
original_window.open_file(self.file_name) original_window.open_file(self.file_name)
else: else:
assert markdown_view.is_scratch(), "markdown view of an unsaved file should " \ assert markdown_view.is_scratch(), (
"be a scratch" "markdown view of an unsaved file should " "be a scratch"
)
# note here that this is called original_view, because it's what semantically # note here that this is called original_view, because it's what semantically
# makes sense, but this original_view.id() will be different than the one # makes sense, but this original_view.id() will be different than the one
# that we closed first to reopen in the preview window # that we closed first to reopen in the preview window
# shouldn't cause any trouble though # shouldn't cause any trouble though
original_view = original_window.new_file() original_view = original_window.new_file()
original_view.run_command('mdlp_insert', {'point': 0, 'string': self.content}) original_view.run_command(
"mdlp_insert", {"point": 0, "string": self.content}
original_view.set_syntax_file(markdown_view.settings().get('syntax')) )
original_view.set_syntax_file(markdown_view.settings().get("syntax"))
# here, views are NOT treated independently, which is theoretically wrong # here, views are NOT treated independently, which is theoretically wrong
# but in practice, you can only edit one markdown file at a time, so it doesn't really # but in practice, you can only edit one markdown file at a time, so it doesn't really
@ -209,15 +222,16 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
basepath = os.path.dirname(markdown_view.file_name()) basepath = os.path.dirname(markdown_view.file_name())
html = markdown2html( html = markdown2html(
markdown, markdown, basepath, partial(self._update_preview, markdown_view), resources
basepath,
partial(self._update_preview, markdown_view),
resources
) )
self.phantom_sets[markdown_view.id()].update([ self.phantom_sets[markdown_view.id()].update(
sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK, [
lambda href: sublime.run_command('open_url', {'url': href})) sublime.Phantom(
]) sublime.Region(0),
html,
sublime.LAYOUT_BLOCK,
lambda href: sublime.run_command("open_url", {"url": href}),
)
]
)

File diff suppressed because it is too large Load Diff

View File

@ -9,9 +9,9 @@ from functools import lru_cache, partial
from .lib.markdown2 import Markdown from .lib.markdown2 import Markdown
__all__ = ('markdown2html', ) __all__ = ("markdown2html",)
markdowner = Markdown(extras=['fenced-code-blocks']) markdowner = Markdown(extras=["fenced-code-blocks"])
# FIXME: how do I choose how many workers I want? Does thread pool reuse threads or # FIXME: how do I choose how many workers I want? Does thread pool reuse threads or
# does it stupidly throw them out? (we could implement something of our own) # does it stupidly throw them out? (we could implement something of our own)
@ -19,9 +19,11 @@ executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
images_cache = {} images_cache = {}
class LoadingError(Exception): class LoadingError(Exception):
pass pass
def markdown2html(markdown, basepath, re_render, resources): def markdown2html(markdown, basepath, re_render, resources):
""" converts the markdown to html, loads the images and puts in base64 for sublime """ converts the markdown to html, loads the images and puts in base64 for sublime
to understand them correctly. That means that we are responsible for loading the to understand them correctly. That means that we are responsible for loading the
@ -31,19 +33,19 @@ def markdown2html(markdown, basepath, re_render, resources):
html = markdowner.convert(markdown) html = markdowner.convert(markdown)
soup = bs4.BeautifulSoup(html, "html.parser") soup = bs4.BeautifulSoup(html, "html.parser")
for img_element in soup.find_all('img'): for img_element in soup.find_all("img"):
src = img_element['src'] src = img_element["src"]
# already in base64, or something of the like # already in base64, or something of the like
# FIXME: what other types are possible? Are they handled by ST? If not, could we # FIXME: what other types are possible? Are they handled by ST? If not, could we
# convert it into base64? is it worth the effort? # convert it into base64? is it worth the effort?
if src.startswith('data:image/'): if src.startswith("data:image/"):
continue continue
if src.startswith('http://') or src.startswith('https://'): if src.startswith("http://") or src.startswith("https://"):
path = src path = src
elif src.startswith('file://'): elif src.startswith("file://"):
path = src[len('file://'):] path = src[len("file://") :]
else: else:
# expanduser: ~ -> /home/math2001 # expanduser: ~ -> /home/math2001
# realpath: simplify that paths so that we don't have duplicated caches # realpath: simplify that paths so that we don't have duplicated caches
@ -52,37 +54,43 @@ def markdown2html(markdown, basepath, re_render, resources):
try: try:
base64 = get_base64_image(path, re_render) base64 = get_base64_image(path, re_render)
except FileNotFoundError as e: except FileNotFoundError as e:
base64 = resources['base64_404_image'] base64 = resources["base64_404_image"]
except LoadingError: except LoadingError:
base64 = resources['base64_loading_image'] base64 = resources["base64_loading_image"]
img_element['src'] = base64 img_element["src"] = base64
# remove comments, because they pollute the console with error messages # remove comments, because they pollute the console with error messages
for comment_element in soup.find_all(text=lambda text: isinstance(text, bs4.Comment)): for comment_element in soup.find_all(
text=lambda text: isinstance(text, bs4.Comment)
):
comment_element.extract() comment_element.extract()
# FIXME: how do tables look? should we use ascii tables? # FIXME: how do tables look? should we use ascii tables?
# pre aren't handled by ST3. The require manual adjustment # pre aren't handled by ST3. The require manual adjustment
for pre_element in soup.find_all('pre'): for pre_element in soup.find_all("pre"):
# select the first child, <code> # select the first child, <code>
code_element = next(pre_element.children) code_element = next(pre_element.children)
# FIXME: this method sucks, but can we do better? # FIXME: this method sucks, but can we do better?
fixed_pre = str(code_element) \ fixed_pre = (
.replace(' ', '<i class="space">.</i>') \ str(code_element)
.replace('\n', '<br />') .replace(" ", '<i class="space">.</i>')
.replace("\n", "<br />")
)
code_element.replace_with(bs4.BeautifulSoup(fixed_pre, "html.parser")) code_element.replace_with(bs4.BeautifulSoup(fixed_pre, "html.parser"))
# FIXME: highlight the code using Sublime's syntax # FIXME: highlight the code using Sublime's syntax
# FIXME: report that ST doesn't support <br/> but does work with <br />... WTF? # FIXME: report that ST doesn't support <br/> but does work with <br />... WTF?
return "<style>\n{}\n</style>\n\n{}".format(resources['stylesheet'], soup).replace('<br/>', '<br />') return "<style>\n{}\n</style>\n\n{}".format(resources["stylesheet"], soup).replace(
"<br/>", "<br />"
)
def get_base64_image(path, re_render): def get_base64_image(path, re_render):
def callback(url, future): def callback(url, future):
# this is "safe" to do because callback is called in the same thread as # this is "safe" to do because callback is called in the same thread as
# add_done_callback: # add_done_callback:
@ -94,7 +102,7 @@ def get_base64_image(path, re_render):
# will read from the cache # will read from the cache
re_render() re_render()
if path.startswith('http://') or path.startswith('https://'): if path.startswith("http://") or path.startswith("https://"):
if path in images_cache: if path in images_cache:
return images_cache[path] return images_cache[path]
executor.submit(load_image, path).add_done_callback(partial(callback, path)) executor.submit(load_image, path).add_done_callback(partial(callback, path))
@ -102,8 +110,9 @@ def get_base64_image(path, re_render):
# FIXME: use some kind of cache for this as well, because it decodes on every # FIXME: use some kind of cache for this as well, because it decodes on every
# keystroke here... # keystroke here...
with open(path, 'rb') as fp: with open(path, "rb") as fp:
return 'data:image/png;base64,' + base64.b64encode(fp.read()).decode('utf-8') return "data:image/png;base64," + base64.b64encode(fp.read()).decode("utf-8")
# FIXME: wait what the hell? Why do I have two caches? (lru and images_cache) # FIXME: wait what the hell? Why do I have two caches? (lru and images_cache)
# FIXME: This is an in memory cache. 20 seems like a fair bit of images... Should it be # FIXME: This is an in memory cache. 20 seems like a fair bit of images... Should it be
@ -114,6 +123,10 @@ def get_base64_image(path, re_render):
def load_image(url): def load_image(url):
with urllib.request.urlopen(url, timeout=60) as conn: with urllib.request.urlopen(url, timeout=60) as conn:
content_type = conn.info().get_content_type() content_type = conn.info().get_content_type()
if 'image' not in content_type: if "image" not in content_type:
raise ValueError("{!r} doesn't point to an image, but to a {!r}".format(url, content_type)) raise ValueError(
return 'data:image/png;base64,' + base64.b64encode(conn.read()).decode('utf-8') "{!r} doesn't point to an image, but to a {!r}".format(
url, content_type
)
)
return "data:image/png;base64," + base64.b64encode(conn.read()).decode("utf-8")

View File

@ -2,8 +2,8 @@
from base64 import b64encode from base64 import b64encode
with open('404.png', 'rb') as png, open('404.base64', 'wb') as base64: with open("404.png", "rb") as png, open("404.base64", "wb") as base64:
base64.write(b64encode(png.read())) base64.write(b64encode(png.read()))
with open('loading.png', 'rb') as png, open('loading.base64', 'wb') as base64: with open("loading.png", "rb") as png, open("loading.base64", "wb") as base64:
base64.write(b64encode(png.read())) base64.write(b64encode(png.read()))

View File

@ -1,9 +1,11 @@
# import sublime # import sublime
import time import time
def get_settings(): def get_settings():
return sublime.get_settings("MarkdownLivePreview.sublime-settings") return sublime.get_settings("MarkdownLivePreview.sublime-settings")
def min_time_between_call(timeout, on_block=lambda *args, **kwargs: None): def min_time_between_call(timeout, on_block=lambda *args, **kwargs: None):
""" Enforces a timeout between each call to the function """ Enforces a timeout between each call to the function
timeout is in seconds timeout is in seconds
@ -19,5 +21,7 @@ def min_time_between_call(timeout, on_block=lambda *args, **kwargs: None):
last_call = time.time() last_call = time.time()
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper return wrapper
return outer return outer