Format everything with black
Follow the readme's instruction haha
This commit is contained in:
@ -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}),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
1153
lib/markdown2.py
1153
lib/markdown2.py
File diff suppressed because it is too large
Load Diff
@ -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")
|
||||||
|
|||||||
@ -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()))
|
||||||
|
|||||||
4
utils.py
4
utils.py
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user