diff --git a/MarkdownLivePreview.py b/MarkdownLivePreview.py index 6b10aa2..3f5ae64 100644 --- a/MarkdownLivePreview.py +++ b/MarkdownLivePreview.py @@ -1,7 +1,10 @@ +import os.path import sublime import sublime_plugin -from .lib.markdown2 import Markdown +from functools import partial + +from .markdown2html import markdown2html from .utils import * def plugin_loaded(): @@ -85,8 +88,6 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand): class MarkdownLivePreviewListener(sublime_plugin.EventListener): - markdowner = Markdown() - phantom_sets = { # markdown_view.id(): phantom set } @@ -161,17 +162,23 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener): self._update_preview(markdown_view) def _update_preview(self, markdown_view): - print('update markdown view', markdown_view.is_loading()) + # if the buffer id is 0, that means that the markdown_view has been closed + # This check is needed since a this function is used as a callback for when images + # are loaded from the internet (ie. it could finish loading *after* the user + # closes the markdown_view) + if markdown_view.buffer_id() == 0: + return + total_region = sublime.Region(0, markdown_view.size()) markdown = markdown_view.substr(total_region) - html = self.markdowner.convert(markdown) - print(html) - - # FIXME: replace images + basepath = os.path.dirname(markdown_view.file_name()) + html = markdown2html(markdown, basepath, partial(self._update_preview, + markdown_view)) 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})) ]) + \ No newline at end of file diff --git a/dependencies.json b/dependencies.json new file mode 100644 index 0000000..d0b3031 --- /dev/null +++ b/dependencies.json @@ -0,0 +1,7 @@ +{ + "*": { + "*": [ + "bs4" + ] + } +} diff --git a/live-testing/sublime_text.png b/live-testing/sublime_text.png new file mode 100644 index 0000000..8be1c1f Binary files /dev/null and b/live-testing/sublime_text.png differ diff --git a/live-testing/test.md b/live-testing/test.md new file mode 100644 index 0000000..cfff733 --- /dev/null +++ b/live-testing/test.md @@ -0,0 +1,20 @@ +# hello world + +This is a *test*. + +I'm not sure that it **actually** going to work, but it seems nicer than the [previous version][prev] + +This is the first image from the local file system (absolute path, sorry): + +![The sublime text logo!](file:///home/math2001/.config/sublime-text-3/Packages/MarkdownLivePreview2/live-testing/sublime_text.png) + +This is the first image from the local file system, *relative* path! + +![The sublime text logo!](sublime_text.png) + +This is the first image from the internet! + + +![The sublime text logo!](https://www.sublimehq.com/images/sublime_text.png) + +[prev]: https://github.com/math2001/MarkdownLivePreview/tree/d4c477749ce7e77b8e9fc85464a2488f003c45bc \ No newline at end of file diff --git a/markdown2html.py b/markdown2html.py new file mode 100644 index 0000000..29de445 --- /dev/null +++ b/markdown2html.py @@ -0,0 +1,69 @@ +import base64 +import os.path +from functools import lru_cache +from .lib.markdown2 import Markdown +from bs4 import BeautifulSoup + +__all__ = ('markdown2html', ) + +markdowner = Markdown() + +# FIXME: put a nice picture please :^) +BASE64_LOADING_IMAGE = 'loading image!' +BASE64_404_IMAGE = '404 not found :-(' + +class LoadingError(Exception): + pass + +def markdown2html(markdown, basepath, re_render): + """ 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 + images from the internet. Hence, we take in re_render, which is just a function we + call when an image has finished loading to retrigger a render (see #90) + """ + html = markdowner.convert(markdown) + + soup = BeautifulSoup(html, "html.parser") + for img_element in soup.find_all('img'): + src = img_element['src'] + # already in base64, or something of the like + # FIXME: what other types are possible? Are they handled by ST? If not, could we + # convert it into base64? is it worth the effort? + if src.startswith('data:image/'): + continue + + if src.startswith('http://') or src.startswith('https://'): + path = src + elif src.startswith('file://'): + path = src[len('file://'):] + else: + # expanduser: ~ -> /home/math2001 + # realpath: simplify that paths so that we don't have duplicated caches + path = os.path.realpath(os.path.expanduser(os.path.join(basepath, src))) + + try: + base64 = get_base64_image(path) + except FileNotFoundError as e: + print("{!r} not found {!r}".format(path, e)) + base64 = BASE64_404_IMAGE + except LoadingError: + # the image is loading + base64 = BASE64_LOADING_IMAGE + + img_element['src'] = base64 + + # FIXME: how do tables look? should we use ascii tables? + + return str(soup) + +# FIXME: This is an in memory cache. 20 seems like a fair bit of images... Should it be +# bigger? Should the user be allowed to chose? There definitely should be a limit +# because we don't wanna use to much memory, we're a simple markdown preview plugin +@lru_cache(maxsize=20) +def get_base64_image(path): + if path.startswith('http://') or path.startswith('https://'): + return 'loading of the internet!' + + with open(path, 'rb') as fp: + return 'data:image/png;base64,' + base64.b64encode(fp.read()).decode('utf-8') + diff --git a/test.md b/test.md deleted file mode 100644 index 59ff640..0000000 --- a/test.md +++ /dev/null @@ -1,8 +0,0 @@ -# hello world - -This is a *test*. - -I'm not sure that it **actually** going to work, but it seems nicer than the [previous version][prev] - - -[prev]: https://github.com/math2001/MarkdownLivePreview/tree/d4c477749ce7e77b8e9fc85464a2488f003c45bc \ No newline at end of file