Files
MarkdownLivePreview/MarkdownLivePreview.py
Mathieu PATUREL bae26fc452 Viewing images works for local files
We have to make sure that everything is converted to base64 (and
replaced in the src attribute) because otherwise ST3 messes up the
height of the images
2019-11-14 17:28:47 +11:00

184 lines
6.8 KiB
Python

import os.path
import sublime
import sublime_plugin
from functools import partial
from .markdown2html import markdown2html
from .utils import *
def plugin_loaded():
pass
MARKDOWN_VIEW_INFOS = "markdown_view_infos"
PREVIEW_VIEW_INFOS = "preview_view_infos"
# FIXME: put this as a setting for the user to choose?
DELAY = 500 # ms
# original_view: the view in the regular editor, without it's own window
# markdown_view: the markdown view, in the special window
# preview_view: the preview view, in the special window
# original_window: the regular window
# preview_window: the window with the markdown file and the preview
class MdlpInsertCommand(sublime_plugin.TextCommand):
def run(self, edit, point, string):
self.view.insert(edit, point, string)
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
def run(self, edit):
""" If the file is saved exists on disk, we close it, and reopen it in a new
window. Otherwise, we copy the content, erase it all (to close the file without
a dialog) and re-insert it into a new view into a new window """
original_view = self.view
original_window_id = original_view.window().id()
file_name = original_view.file_name()
syntax_file = original_view.settings().get('syntax')
if file_name:
original_view.close()
else:
# the file isn't saved, we need to restore the content manually
total_region = sublime.Region(0, original_view.size())
content = original_view.substr(total_region)
original_view.erase(edit, total_region)
original_view.close()
# FIXME: save the document to a temporary file, so that if we crash,
# the user doesn't lose what he wrote
sublime.run_command('new_window')
preview_window = sublime.active_window()
preview_window.run_command('set_layout', {
'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_view = preview_window.new_file()
preview_view.set_scratch(True)
preview_view.settings().set(PREVIEW_VIEW_INFOS, {})
preview_window.focus_group(0)
if file_name:
markdown_view = preview_window.open_file(file_name)
else:
markdown_view = preview_window.new_file()
markdown_view.run_command('mdlp_insert', {'point': 0, 'string': content})
markdown_view.set_scratch(True)
markdown_view.set_syntax_file(syntax_file)
markdown_view.settings().set(MARKDOWN_VIEW_INFOS, {
"original_window_id": original_window_id
})
def is_enabled(self):
# FIXME: is this the best way there is to check if the current syntax is markdown?
# should we only support default markdown?
# what about "md"?
return 'markdown' in self.view.settings().get('syntax').lower()
class MarkdownLivePreviewListener(sublime_plugin.EventListener):
phantom_sets = {
# markdown_view.id(): phantom set
}
def on_pre_close(self, markdown_view):
""" Close the view in the preview window, and store information for the on_close
listener (see doc there)
"""
if not markdown_view.settings().get(MARKDOWN_VIEW_INFOS):
return
self.markdown_view = markdown_view
self.preview_window = markdown_view.window()
self.file_name = markdown_view.file_name()
if self.file_name is None:
# FIXME: this is duplicated code. How should it be generalized?
total_region = sublime.Region(0, markdown_view.size())
self.content = markdown_view.substr(total_region)
markdown_view.erase(edit, total_region)
else:
self.content = None
def on_load_async(self, markdown_view):
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
return
preview_view = markdown_view.window().active_view_in_group(1)
self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
self._update_preview(markdown_view)
def on_close(self, markdown_view):
""" Use the information saved to restore the markdown_view as an original_view
"""
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
return
assert markdown_view.id() == self.markdown_view.id(), \
"pre_close view.id() != close view.id()"
self.preview_window.run_command('close_window')
# find the window with the right id
original_window = next(window for window in sublime.windows() \
if window.id() == infos['original_window_id'])
if self.file_name:
original_window.open_file(self.file_name)
else:
assert markdown_view.is_scratch(), "markdown view of an unsaved file should " \
"be a scratch"
# 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
# that we closed first to reopen in the preview window
# shouldn't cause any trouble though
original_view = original_window.new_file()
original_view.run_command('mdlp_insert', {'point': 0, 'string': self.content})
original_view.set_syntax_file(markdown_view.settings().get('syntax'))
# here, views are NOT treated independently, which is theoratically wrong
# but in practice, you can only edit one markdown file at a time, so it doesn't really
# matter.
# @min_time_between_call(.5)
def on_modified_async(self, markdown_view):
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
return
self._update_preview(markdown_view)
def _update_preview(self, markdown_view):
# 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)
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}))
])