Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 192f61bf0c | |||
| 2785df74ce | |||
| e13842ede4 | |||
| c6ac821c4a | |||
| 9ad3f25d14 | |||
| eaa357a65f | |||
| 0f5630c3dc | |||
| c14c28b56b | |||
| 0dea8afba4 | |||
| e3896a6b3d | |||
| 6016f07cd1 | |||
| c0c9867cc8 | |||
| 5f2cac54e8 | |||
| 8c1012eb8c | |||
| cc28bfef96 | |||
| ef9b2daf6d | |||
| bae26fc452 | |||
| 5738f6b5ff | |||
| 6bb8e6ebaa | |||
| 8eb6882d60 | |||
| 61cf2984eb | |||
| 7f7dcd6ba8 | |||
| d3d88ddb49 | |||
| 9a8ac3886e | |||
| d4c477749c | |||
| 79c785176f | |||
| 82ad98085f | |||
| dd184c5fdd | |||
| c334c49592 | |||
| 41c28e2b24 | |||
| e1eb17fe96 | |||
| 823d22afee | |||
| 91f4bc5052 | |||
| 7126c0e090 | |||
| 6a3dd6ac2f | |||
| 1542e5e898 | |||
| 05c471b5d9 | |||
| 76f580ba29 | |||
| 119acbb092 | |||
| 7c4354fb2e | |||
| b93aea6698 | |||
| b3fb5779d3 | |||
| 7bdda5f5c7 | |||
| 7257cb467e | |||
| 40a563fb1e | |||
| 3e0d6ad265 | |||
| f65a068b4e | |||
| bc328642e7 | |||
| d2053be41e | |||
| eb48b1c79f | |||
| 8317fa738c | |||
| 3be12b0539 | |||
| c92d78fb20 | |||
| 30d75f159d | |||
| 52e4b917e5 | |||
| 48a68b2a79 | |||
| 8eb0172eb4 | |||
| 52e35fb610 | |||
| 84f809e57f | |||
| 351e8bd355 | |||
| 5babc862b4 | |||
| dc7139fbe7 | |||
| bbbeae6fe9 | |||
| 271c7c619a | |||
| 8cc6b2b263 | |||
| c7961ce94c | |||
| eae91fa428 | |||
| 6f18e8e4a2 | |||
| 48c1800065 | |||
| bad1cb74c6 |
3
.gitattributes
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
docs/ export-ignore
|
||||||
|
resources/*.png export-ignore
|
||||||
|
resources/*.py export-ignore
|
||||||
6
.gitignore
vendored
@ -1,5 +1 @@
|
|||||||
Thumbs.db
|
__pycache__
|
||||||
__pycache__/
|
|
||||||
cache.txt
|
|
||||||
venv/
|
|
||||||
site/
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"keys": ["alt+m"],
|
|
||||||
"command": "new_markdown_live_preview",
|
|
||||||
"context": [
|
|
||||||
{
|
|
||||||
"key": "selector",
|
|
||||||
"operator": "equal",
|
|
||||||
"operand": "text.html.markdown"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "preferences",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"id": "package-settings",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"caption": "MarkdownLivePreview",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"caption": "Settings",
|
|
||||||
"command": "edit_settings",
|
|
||||||
"args": {
|
|
||||||
"base_file": "$packages/MarkdownLivePreview/.sublime/MarkdownLivePreview.sublime-settings",
|
|
||||||
"default": "// Your settings for MarkdownLivePreview. See the default file to see the different options. \n{\n\t\n}\n"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"caption": "Style - CSS",
|
|
||||||
"command": "open_file",
|
|
||||||
"args": {
|
|
||||||
"file": "$packages/User/MarkdownLivePreview.css",
|
|
||||||
"contents": "/* See http://www.sublimetext.com/docs/3/minihtml.html#css to know which property you're able to use */\n\n$0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"caption": "MarkdownLivePreview: Edit Current File",
|
|
||||||
"command": "new_markdown_live_preview"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"caption": "MarkdownLivePreview: Clear the cache",
|
|
||||||
"command": "markdown_live_preview_clear_cache"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"caption": "MarkdownLivePreview: Edit Custom CSS File",
|
|
||||||
"command": "open_file",
|
|
||||||
"args": {
|
|
||||||
"file": "$packages/User/MarkdownLivePreview.css"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"caption": "Preferences: MarkdownLivePreview Settings",
|
|
||||||
"command": "edit_settings",
|
|
||||||
"args": {
|
|
||||||
"base_file": "${packages}/MarkdownLivePreview/.sublime/MarkdownLivePreview.sublime-settings",
|
|
||||||
"default": "// Your settings for MarkdownLivePreview. See the default file to see the different options. \n{\n\t$0\n}\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"markdown_live_preview_on_open": false,
|
|
||||||
"load_from_internet_when_starts": ["http://", "https://"]
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>name</key>
|
|
||||||
<string>MarkdownLivePreviewSyntax</string>
|
|
||||||
|
|
||||||
<key>patterns</key>
|
|
||||||
<array>
|
|
||||||
</array>
|
|
||||||
<key>scopeName</key>
|
|
||||||
<string>text.markdown-live-preview</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
104
MLPApi.py
@ -1,104 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
import sublime
|
|
||||||
import sublime_plugin
|
|
||||||
|
|
||||||
import os.path
|
|
||||||
from html.parser import HTMLParser
|
|
||||||
|
|
||||||
from .lib import markdown2 as md2
|
|
||||||
from .escape_amp import *
|
|
||||||
from .functions import *
|
|
||||||
from .setting_names import *
|
|
||||||
from .image_manager import CACHE_FILE
|
|
||||||
from random import randint as rnd
|
|
||||||
|
|
||||||
__folder__ = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
USER_STYLE_FILE = os.path.join(os.path.dirname(__folder__), 'User', 'MarkdownLivePreview.css')
|
|
||||||
|
|
||||||
# used to store the phantom's set
|
|
||||||
windows_phantom_set = {}
|
|
||||||
|
|
||||||
def plugin_loaded():
|
|
||||||
global DEFAULT_STYLE_FILE
|
|
||||||
DEFAULT_STYLE_FILE = sublime.load_resource('Packages/MarkdownLivePreview/default.css')
|
|
||||||
|
|
||||||
def get_preview_name(md_view):
|
|
||||||
file_name = md_view.file_name()
|
|
||||||
name = md_view.name() \
|
|
||||||
or os.path.basename(file_name) if file_name else None \
|
|
||||||
or 'Untitled'
|
|
||||||
return name + ' - Preview'
|
|
||||||
|
|
||||||
def create_preview(window, file_name):
|
|
||||||
preview = window.new_file()
|
|
||||||
|
|
||||||
preview.set_name(get_preview_name(file_name))
|
|
||||||
preview.set_scratch(True)
|
|
||||||
preview.set_syntax_file('Packages/MarkdownLivePreview/.sublime/' + \
|
|
||||||
'MarkdownLivePreviewSyntax.hidden-tmLanguage')
|
|
||||||
|
|
||||||
return preview
|
|
||||||
|
|
||||||
def get_style():
|
|
||||||
content = ''.join([line.strip() + ' ' for line in DEFAULT_STYLE_FILE.splitlines()])
|
|
||||||
if os.path.exists(USER_STYLE_FILE):
|
|
||||||
with open(USER_STYLE_FILE) as fp:
|
|
||||||
content += '\n' + fp.read() + '\n'
|
|
||||||
return content
|
|
||||||
|
|
||||||
def show_html(md_view, preview):
|
|
||||||
global windows_phantom_set
|
|
||||||
html = []
|
|
||||||
html.append('<style>\n{}\n</style>'.format(get_style()))
|
|
||||||
html.append(pre_with_br(md2.markdown(get_view_content(md_view),
|
|
||||||
extras=['fenced-code-blocks',
|
|
||||||
'no-code-highlighting'])))
|
|
||||||
# the option no-code-highlighting does not exists
|
|
||||||
# in the official version of markdown2 for now
|
|
||||||
# I personaly edited the file (markdown2.py:1743)
|
|
||||||
html = '\n'.join(html)
|
|
||||||
|
|
||||||
html = html.replace(' ', ' espace;') # save where are the spaces
|
|
||||||
|
|
||||||
html = HTMLParser().unescape(html)
|
|
||||||
|
|
||||||
html = escape_amp(html)
|
|
||||||
|
|
||||||
# exception, again, because <pre> aren't supported by the phantoms
|
|
||||||
html = html.replace(' espace;', '<i class="space">.</i>')
|
|
||||||
html = replace_img_src_base64(html, basepath=os.path.dirname(
|
|
||||||
md_view.file_name()))
|
|
||||||
|
|
||||||
phantom_set = windows_phantom_set.setdefault(preview.window().id(),
|
|
||||||
sublime.PhantomSet(preview,
|
|
||||||
'markdown_live_preview'))
|
|
||||||
phantom_set.update([sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK,
|
|
||||||
lambda href: sublime.run_command('open_url',
|
|
||||||
{'url': href}))])
|
|
||||||
|
|
||||||
# lambda href: sublime.run_command('open_url', {'url': href})
|
|
||||||
# get the "ratio" of the markdown view's position.
|
|
||||||
# 0 < y < 1
|
|
||||||
y = md_view.text_to_layout(md_view.sel()[0].begin())[1] / md_view.layout_extent()[1]
|
|
||||||
# set the vector (position) for the preview
|
|
||||||
vector = [0, y * preview.layout_extent()[1]]
|
|
||||||
# remove half of the viewport_extent.y to center it on the screen (verticaly)
|
|
||||||
vector[1] -= preview.viewport_extent()[1] / 2
|
|
||||||
# make sure the minimum is 0
|
|
||||||
vector[1] = 0 if vector[1] < 0 else vector[1]
|
|
||||||
# the hide the first line
|
|
||||||
vector[1] += preview.line_height()
|
|
||||||
preview.set_viewport_position(vector, animate=False)
|
|
||||||
|
|
||||||
def clear_cache():
|
|
||||||
"""Removes the cache file"""
|
|
||||||
os.remove(CACHE_FILE)
|
|
||||||
|
|
||||||
def release_phantoms_set(view_id=None):
|
|
||||||
global windows_phantom_set
|
|
||||||
if view_id is None:
|
|
||||||
windows_phantom_set = {}
|
|
||||||
else:
|
|
||||||
del windows_phantom_set[view_id]
|
|
||||||
@ -1,112 +1,237 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
import os.path
|
||||||
|
|
||||||
import sublime
|
import sublime
|
||||||
import sublime_plugin
|
import sublime_plugin
|
||||||
|
|
||||||
from .MLPApi import *
|
from functools import partial
|
||||||
from .setting_names import *
|
|
||||||
from .functions import *
|
|
||||||
|
|
||||||
class NewMarkdownLivePreviewCommand(sublime_plugin.ApplicationCommand):
|
from .markdown2html import markdown2html
|
||||||
|
from .utils import *
|
||||||
|
|
||||||
def run(self):
|
MARKDOWN_VIEW_INFOS = "markdown_view_infos"
|
||||||
|
PREVIEW_VIEW_INFOS = "preview_view_infos"
|
||||||
|
# FIXME: put this as a setting for the user to choose?
|
||||||
|
DELAY = 100 # ms
|
||||||
|
|
||||||
"""Inspired by the edit_settings command"""
|
|
||||||
|
|
||||||
current_view = sublime.active_window().active_view()
|
def get_resource(resource):
|
||||||
file_name = current_view.file_name()
|
path = "Packages/MarkdownLivePreview/resources/" + resource
|
||||||
current_view.close()
|
abs_path = os.path.join(sublime.packages_path(), "..", path)
|
||||||
if file_name is None:
|
if os.path.isfile(abs_path):
|
||||||
return sublime.error_message('MarkdownLivePreview: Not supporting '
|
with open(abs_path, "r") as fp:
|
||||||
'unsaved file for now')
|
return fp.read()
|
||||||
|
return sublime.load_resource(path)
|
||||||
|
|
||||||
sublime.run_command('new_window')
|
|
||||||
self.window = sublime.active_window()
|
|
||||||
self.window.settings().set(PREVIEW_WINDOW, True)
|
|
||||||
self.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]]
|
|
||||||
})
|
|
||||||
self.window.focus_group(1)
|
|
||||||
preview = create_preview(self.window, current_view)
|
|
||||||
|
|
||||||
self.window.focus_group(0)
|
resources = {}
|
||||||
md_view = self.window.open_file(file_name)
|
|
||||||
mdsettings = md_view.settings()
|
|
||||||
|
|
||||||
mdsettings.set(PREVIEW_ENABLED, True)
|
|
||||||
mdsettings.set(PREVIEW_ID, preview.id())
|
def plugin_loaded():
|
||||||
|
resources["base64_404_image"] = get_resource("404.base64")
|
||||||
|
resources["base64_loading_image"] = get_resource("loading.base64")
|
||||||
|
resources["stylesheet"] = get_resource("stylesheet.css")
|
||||||
|
|
||||||
|
|
||||||
|
# try to reload the resources if we save this file
|
||||||
|
try:
|
||||||
|
plugin_loaded()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Terminology
|
||||||
|
# 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_view.set_name("Preview")
|
||||||
|
|
||||||
|
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):
|
def is_enabled(self):
|
||||||
return is_markdown_view(sublime.active_window().active_view())
|
# 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"?
|
||||||
|
# FIXME: what about other languages, where markdown preview roughly works?
|
||||||
|
return "markdown" in self.view.settings().get("syntax").lower()
|
||||||
|
|
||||||
|
|
||||||
class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||||
|
|
||||||
def update(self, view):
|
phantom_sets = {
|
||||||
vsettings = view.settings()
|
# markdown_view.id(): phantom set
|
||||||
if not vsettings.get(PREVIEW_ENABLED):
|
}
|
||||||
|
|
||||||
|
# we schedule an update for every key stroke, with a delay of DELAY
|
||||||
|
# then, we update only if now() - last_update > DELAY
|
||||||
|
last_update = 0
|
||||||
|
|
||||||
|
# FIXME: maybe we shouldn't restore the file in the original window...
|
||||||
|
|
||||||
|
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
|
return
|
||||||
id = vsettings.get(PREVIEW_ID)
|
|
||||||
if id is None:
|
|
||||||
raise ValueError('The preview id is None')
|
|
||||||
preview = get_view_from_id(view.window(), id)
|
|
||||||
if preview is None:
|
|
||||||
raise ValueError('The preview is None (id: {})'.format(id))
|
|
||||||
|
|
||||||
show_html(view, preview)
|
self.markdown_view = markdown_view
|
||||||
return view, preview
|
self.preview_window = markdown_view.window()
|
||||||
|
self.file_name = markdown_view.file_name()
|
||||||
|
|
||||||
def on_modified(self, view):
|
if self.file_name is None:
|
||||||
if not is_markdown_view(view): # faster than getting the settings
|
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
|
return
|
||||||
self.update(view)
|
|
||||||
|
|
||||||
def on_window_command(self, window, command, args):
|
preview_view = markdown_view.window().active_view_in_group(1)
|
||||||
if command == 'close' and window.settings().get(PREVIEW_WINDOW):
|
|
||||||
release_phantoms_set(window.id())
|
|
||||||
return 'close_window', {}
|
|
||||||
|
|
||||||
def on_activated_async(self, view):
|
self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
|
||||||
vsettings = view.settings()
|
self._update_preview(markdown_view)
|
||||||
|
|
||||||
if (is_markdown_view(view)
|
def on_close(self, markdown_view):
|
||||||
and get_settings().get('markdown_live_preview_on_open')
|
""" Use the information saved to restore the markdown_view as an original_view
|
||||||
and not vsettings.get(PREVIEW_ENABLED)
|
"""
|
||||||
and vsettings.get('syntax') != 'Packages/MarkdownLivePreview/' + \
|
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
|
||||||
'.sublime/MarkdownLivePreviewSyntax' + \
|
if not infos:
|
||||||
'.hidden-tmLanguage'):
|
|
||||||
sublime.run_command('new_markdown_live_preview')
|
|
||||||
|
|
||||||
|
|
||||||
def on_load_async(self, view):
|
|
||||||
"""Check the settings to hide menu, minimap, etc"""
|
|
||||||
try:
|
|
||||||
md_view, preview = self.update(view)
|
|
||||||
except TypeError:
|
|
||||||
# the function update has returned None
|
|
||||||
return
|
return
|
||||||
window = preview.window()
|
|
||||||
psettings = preview.settings()
|
|
||||||
|
|
||||||
show_tabs = psettings.get('show_tabs')
|
assert (
|
||||||
show_minimap = psettings.get('show_minimap')
|
markdown_view.id() == self.markdown_view.id()
|
||||||
show_status_bar = psettings.get('show_status_bar')
|
), "pre_close view.id() != close view.id()"
|
||||||
show_sidebar = psettings.get('show_sidebar')
|
|
||||||
show_menus = psettings.get('show_menus')
|
|
||||||
|
|
||||||
if show_tabs is not None:
|
del self.phantom_sets[markdown_view.id()]
|
||||||
window.set_tabs_visible(show_tabs)
|
|
||||||
if show_minimap is not None:
|
|
||||||
window.set_minimap_visible(show_minimap)
|
|
||||||
if show_status_bar is not None:
|
|
||||||
window.set_status_bar_visible(show_status_bar)
|
|
||||||
if show_sidebar is not None:
|
|
||||||
window.set_sidebar_visible(show_sidebar)
|
|
||||||
if show_menus is not None:
|
|
||||||
window.set_menu_visible(show_menus)
|
|
||||||
|
|
||||||
class MarkdownLivePreviewClearCacheCommand(sublime_plugin.ApplicationCommand):
|
self.preview_window.run_command("close_window")
|
||||||
|
|
||||||
def run(self):
|
# find the window with the right id
|
||||||
clear_cache()
|
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 theoretically 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
|
||||||
|
|
||||||
|
# we schedule an update, which won't run if an
|
||||||
|
sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY)
|
||||||
|
|
||||||
|
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 time.time() - self.last_update < DELAY / 1000:
|
||||||
|
return
|
||||||
|
|
||||||
|
if markdown_view.buffer_id() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.last_update = time.time()
|
||||||
|
|
||||||
|
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), resources,
|
||||||
|
)
|
||||||
|
|
||||||
|
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}),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
7
MarkdownLivePreview.sublime-commands
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
|
||||||
|
{
|
||||||
|
"caption": "MarkdownLivePreview: Open Preview",
|
||||||
|
"command": "open_markdown_preview"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,36 +0,0 @@
|
|||||||
Fast:
|
|
||||||
☐ cache image in object when used, so that it's faster @needsTest
|
|
||||||
|
|
||||||
Medium:
|
|
||||||
☐ auto refresh preview if loading images
|
|
||||||
☐ use alt attribute for 404 error
|
|
||||||
|
|
||||||
Long:
|
|
||||||
☐ support anchor (TOC) @big
|
|
||||||
|
|
||||||
Unknown:
|
|
||||||
|
|
||||||
|
|
||||||
___________________
|
|
||||||
Archive:
|
|
||||||
✔ fix custom css @bug @done Sun 22 Jan 2017 at 18:40 @project(Medium)
|
|
||||||
✘ check how many times is the show_html function called @cancelled Sun 22 Jan 2017 at 18:40 @project(Unknown)
|
|
||||||
✔ sync scroll @needsUpdate(because of images) @done Sun 22 Jan 2017 at 18:39 @project(Fast)
|
|
||||||
✔ fix #4 @high @done Mon 09 Jan 2017 at 18:42 @project(Long)
|
|
||||||
✔ use MarkdownLivePreview syntax, so we can use syntax's settings @done Mon 09 Jan 2017 at 18:41 @project(Medium)
|
|
||||||
✔ add clear cache command @done Mon 09 Jan 2017 at 18:41 @project(Fast)
|
|
||||||
✔ update README for settings in view @done Mon 09 Jan 2017 at 18:41 @project(Fast)
|
|
||||||
✔ add edit settings @done Mon 09 Jan 2017 at 18:41 @project(Fast)
|
|
||||||
✘ listen for settings to change @cancelled Mon 09 Jan 2017 at 18:41 @project(Medium)
|
|
||||||
✘ call settings listener on_new too - might be too heavy @cancelled Sun 08 Jan 2017 at 19:33 @project(Fast)
|
|
||||||
✔ fix relative source @done Sun 08 Jan 2017 at 19:22 @project(Medium)
|
|
||||||
✔ add settings for the preview @done Sun 08 Jan 2017 at 17:36 @project(Fast)
|
|
||||||
✔ regive focus to the right markdown view @done Mon 02 Jan 2017 at 18:34 @project(Fast)
|
|
||||||
✔ try/except for 404 @done Mon 02 Jan 2017 at 18:03 @project(Fast)
|
|
||||||
✔ fix bug when empty `src` @done Mon 02 Jan 2017 at 17:15 @project(Fast)
|
|
||||||
✔ preview.set_scratch(True) @done Mon 02 Jan 2017 at 16:58
|
|
||||||
✔ set the title of the preview @done Mon 02 Jan 2017 at 16:58
|
|
||||||
✔ preview.wordWrap => True @done Mon 02 Jan 2017 at 16:58
|
|
||||||
✔ clean the code (syntax) @done Mon 02 Jan 2017 at 16:58
|
|
||||||
✔ add 404 image @done Mon 02 Jan 2017 at 16:57
|
|
||||||
✔ load images from internet (`https:`) @done Mon 02 Jan 2017 at 16:57
|
|
||||||
104
README.md
@ -1,96 +1,26 @@
|
|||||||
# MarkdownLivePreview
|
# MarkdownLivePreview
|
||||||
|
|
||||||
This is a sublime text **3** plugin that allows you to preview your markdown instantly *in* it!
|
A simple plugin to preview your markdown as you type right in Sublime Text.
|
||||||
|
No dependencies!
|
||||||
|
|
||||||
### Dependencies
|
## How to install
|
||||||
|
|
||||||
**None**! There is no dependency! It uses [markdown2](https://github.com/trentm/python-markdown2) but it's a one file plugin, so it's included in the package.
|
It's available on package control!
|
||||||
|
|
||||||
## Installation
|
## How to contribute
|
||||||
|
|
||||||
MarkdownLivePreview is available on the default channel of [PackageControl](http://packagecontrol.io), which means you just have to
|
If you know what feature you want to implement, or what bug you wanna fix, then
|
||||||
|
go ahead and hack! But if you wanna contribute just to say thanks, and don't
|
||||||
|
really know what you could be working on, then there are a bunch of `FIXME`s
|
||||||
|
in `MarkdownLivePreview.py` and `markdown2html.py` (GitHub only shows the top
|
||||||
|
2 results if you try to search using their interface :slightly_frowning_face:).
|
||||||
|
|
||||||
1. Open the command palette (`ctrl+shift+p`)
|
### Hack it!
|
||||||
2. Search for: `Package Control: Install Package`
|
|
||||||
3. Search for: `MarkdownLivePreview`
|
|
||||||
4. hit <kbd>enter</kbd>
|
|
||||||
|
|
||||||
to have MarkdownLivePreview working on your computer. Cool right? You can [thank package control](https://packagecontrol.io/say_thanks) for this.
|
1. Fork this repo
|
||||||
|
2. Make your own branch (the name of the branch should be the feature you are
|
||||||
|
implementing eg. `improve-tables`, `fix-crash-on-multiple-preview`
|
||||||
|
3. All your code should be formated by black.
|
||||||
|
4. Send a PR!
|
||||||
|
|
||||||
### Usage
|
FIXME: add a git hook to format using black (can the git hook be added on github?)
|
||||||
|
|
||||||
You can choose to enable MarkdownLivePreview by pressing <kbd>alt+m</kbd> or selecting in the command palette `MarkdownLivePreview: Edit Current File`. Note that you need to be editing (simply having the focus on) a markdown file. Because [Markdown Extended][markdown-extended] did a good job, it's compatible with this plugin.
|
|
||||||
|
|
||||||
So, once you've run it, it will open a new window, with only your markdown file, with the preview. Once you're done, close whichever file and it'll close the entire window.
|
|
||||||
|
|
||||||
*Notice that it will close the entire window if you close __whichever__ file. It means that if you open a random file in this window, and then close it, it'll close the entire window still*
|
|
||||||
|
|
||||||
### Settings
|
|
||||||
|
|
||||||
- `markdown_live_preview_on_open`: if set to `true`, as soon as you open a markdown file, the preview window will popup (thanks to [@ooing](https://github.com/ooing) for its [suggestion](https://github.com/math2001/MarkdownLivePreview/issues/7#issue-199464852)). Default to `false`
|
|
||||||
- `load_from_internet_when_starts`: every images that starts with any of the string specified in this list will be loaded from internet. Default to `["http://", "https://"]`
|
|
||||||
|
|
||||||
Note: To edit your settings, search up in the command palette `Preferences: MarkdownLivePreview Settings`, or by using the menu: `Preferences → Packages Settings → MarkdownLivePreview → Settings` ;. It's not your global settings, but only the `MarkdownLivePreview`'s one
|
|
||||||
|
|
||||||
### Syntax Specific Settings
|
|
||||||
|
|
||||||
This in an other "type" of setting. :laughing: If you have a look at the syntax of the preview file (not the markdown one, really the preview), you'll see that the syntax is `MarkdownLivePreviewSyntax`. This mean that you can specify specific settings for this specific syntax (such as `word_wrap: true`, `rulers: []`, etc).
|
|
||||||
|
|
||||||
To do so, you can
|
|
||||||
|
|
||||||
1. focus the *preview* (<kbd>ctrl+2</kbd> to focus the second group, so, by default, the preview's group)
|
|
||||||
2. search up in the command palette `Preferences: Settings Syntax Specific`. It's in the *right* file that you can add the settings you want (not the left one).
|
|
||||||
|
|
||||||
Note: MarkdownLivePreview will actualy look in this file for settings that aren't supported by default. Here they are:
|
|
||||||
|
|
||||||
- `show_tabs`
|
|
||||||
- `show_minimap`
|
|
||||||
- `show_status_bar`
|
|
||||||
- `show_sidebar`
|
|
||||||
- `show_menus`
|
|
||||||
|
|
||||||
They talk for themself, don't they? All of them takes a boolean (`true` or `false`). Note that those settings are *window* specific, not just view specific (that's why they aren't supported). It means that they'll affect the entire window, and every view in it.
|
|
||||||
|
|
||||||
Here is an example of syntax specific settings for MarkdownLivePreviewSyntax:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"show_menus": false,
|
|
||||||
"show_tabs": false,
|
|
||||||
"show_minimap": false,
|
|
||||||
"gutter": false,
|
|
||||||
"rulers": [],
|
|
||||||
"word_wrap": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And here's what you'll get:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
*Note: to close a file, you can do <kbd>ctrl+w</kbd> (on Mac OS, it's <kbd>cmd+w</kbd>)*
|
|
||||||
|
|
||||||
### Clear the cache
|
|
||||||
|
|
||||||
MarkdownLivePreview caches every images it loads from internet (otherwise, you'd never see your images, or you'd need to have a *really* fast internet connection :smile:). So, if for some reason you want to clear the cache (a simple file), you can do so from the command palette by running `
|
|
||||||
|
|
||||||
### Demo
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Custom css
|
|
||||||
|
|
||||||
It is possible to set your own css. But, be carefull, you have to respect [those rules](http://www.sublimetext.com/docs/3/minihtml.html#css). Just go to `Preferences → Package Settings → MarkdownLivePreview → Style - CSS`. It will open a css file, here: `$packages/User/MarkdownLivePreview.css`. Just save it and it will automatically use it instead of the default one.
|
|
||||||
|
|
||||||
### Somethings wrong!!
|
|
||||||
|
|
||||||
If you find that something's wrong with this package, you can let me know by raising an issue on the [GitHub issue tracker][github-issue-tracker]
|
|
||||||
|
|
||||||
### How to open the [README](http://github.com/math2001/MarkdownLivePreview/README.md)
|
|
||||||
|
|
||||||
Some of the package add a command in the menus, others in the command palette, or other nowhere. None of those options are really good, especially the last one on ST3 because the packages are compressed. But, fortunately, there is plugin that exists and **will solve this problem** for us (and he has a really cute name, don't you think?): [ReadmePlease](https://packagecontrol.io/packages/ReadmePlease).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[markdown-extended]: https://packagecontrol.io/packages/Markdown%20Extended
|
|
||||||
[github-issue-tracker]: https://github.com/math2001/MarkdownLivePreview/issues
|
|
||||||
51
default.css
@ -1,51 +0,0 @@
|
|||||||
html {
|
|
||||||
--light-bg: color(var(--background) blend(#999 85%));
|
|
||||||
--very-light-bg: color(var(--background) blend(#999 92%));
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
padding:10px;
|
|
||||||
padding-top: 0px;
|
|
||||||
font-family: "Open Sans", sans-serif;
|
|
||||||
background-color: var(--background);
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
font-style: italic;
|
|
||||||
display: block;
|
|
||||||
margin-left: 30px;
|
|
||||||
border: 1px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding-left: 0.2rem;
|
|
||||||
padding-right: 0.2rem;
|
|
||||||
background-color: var(--light-bg);
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
margin-top: 20px;
|
|
||||||
line-height: 1.7;
|
|
||||||
background-color: var(--light-bg);
|
|
||||||
padding-left: 10px;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
pre code {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code .space {
|
|
||||||
color: var(--light-bg)
|
|
||||||
}
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
padding: 0 5px;
|
|
||||||
background-color: var(--very-light-bg);
|
|
||||||
font-family: "Roboto Mono","Courier New",Courier,monospace;
|
|
||||||
}
|
|
||||||
7
dependencies.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"bs4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
import sublime
|
|
||||||
import sublime_plugin
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
class MLPDevListener(sublime_plugin.EventListener):
|
|
||||||
|
|
||||||
def on_post_save(self, view):
|
|
||||||
# return
|
|
||||||
if not (os.path.dirname(__file__) in view.file_name() and
|
|
||||||
view.file_name().endswith('.py')):
|
|
||||||
return
|
|
||||||
sublime.run_command('reload_plugin', {
|
|
||||||
'main': os.path.join(sublime.packages_path(), 'MarkdownLivePreview',
|
|
||||||
'MarkdownLivePreview.py'),
|
|
||||||
'scripts': ['image_manager', 'functions', 'MLPApi',
|
|
||||||
'setting_names'],
|
|
||||||
'quiet': True
|
|
||||||
})
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
viewBox="0 0 99.999996 100"
|
|
||||||
id="svg2"
|
|
||||||
version="1.1"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="MarkdownLivePreview.svg">
|
|
||||||
<defs
|
|
||||||
id="defs4" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#2196f3"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="2.8"
|
|
||||||
inkscape:cx="36.574985"
|
|
||||||
inkscape:cy="6.9424003"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
showborder="true"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1018"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:showpageshadow="false"
|
|
||||||
units="px"
|
|
||||||
showguides="true"
|
|
||||||
inkscape:guide-bbox="true"
|
|
||||||
inkscape:snap-global="false" />
|
|
||||||
<metadata
|
|
||||||
id="metadata7">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-349.87364,-434.14672)">
|
|
||||||
<g
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:1;fill:#e1ecff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="text7027"
|
|
||||||
transform="translate(-0.13506381,-12.064395)">
|
|
||||||
<path
|
|
||||||
d="m 352.04084,461.61747 12.16825,0 10.2962,16.08783 10.29621,-16.08783 12.46076,0 0,41.36034 -13.39678,0 0,-22.46445 -9.65269,16.35108 -2.83731,0 -9.97445,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
|
|
||||||
style="fill:#e1ecff;fill-opacity:1;stroke:none"
|
|
||||||
id="path7039"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="matrix(-1,0,0,1,-0.13506381,-12.064395)"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:1;fill:#b9d5ff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="text7031">
|
|
||||||
<path
|
|
||||||
d="m -447.97656,461.61747 12.16825,0 10.29621,16.08783 10.29621,-16.08783 12.46075,0 0,41.36034 -13.39677,0 0,-22.46445 -9.6527,16.35108 -2.8373,0 -9.97446,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
|
|
||||||
style="fill:#b9d5ff;fill-opacity:1;stroke:none"
|
|
||||||
id="path7036"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
style="opacity:0.75;fill:#cde0ff;fill-opacity:1;stroke:none;stroke-width:1.00199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 375.63144,496.36803 24.28571,14.02137 24.28572,-14.02137 14.46428,0 -38.75,22.37233 -38.75,-22.37233 z"
|
|
||||||
id="path7081"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB |
@ -1,92 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
width="100"
|
|
||||||
height="100"
|
|
||||||
viewBox="0 0 99.999996 100"
|
|
||||||
id="svg2"
|
|
||||||
version="1.1"
|
|
||||||
inkscape:version="0.91 r13725"
|
|
||||||
sodipodi:docname="MarkdownLivePreview.svg">
|
|
||||||
<defs
|
|
||||||
id="defs4" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="2.8"
|
|
||||||
inkscape:cx="36.574985"
|
|
||||||
inkscape:cy="6.9424003"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
showborder="true"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="1018"
|
|
||||||
inkscape:window-x="-8"
|
|
||||||
inkscape:window-y="-8"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
fit-margin-top="0"
|
|
||||||
fit-margin-left="0"
|
|
||||||
fit-margin-right="0"
|
|
||||||
fit-margin-bottom="0"
|
|
||||||
inkscape:showpageshadow="false"
|
|
||||||
units="px"
|
|
||||||
showguides="true"
|
|
||||||
inkscape:guide-bbox="true"
|
|
||||||
inkscape:snap-global="false" />
|
|
||||||
<metadata
|
|
||||||
id="metadata7">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
<dc:title></dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-349.87364,-434.14672)">
|
|
||||||
<g
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:1;fill:#5599ff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="text7027"
|
|
||||||
transform="translate(-0.13506381,-12.064395)">
|
|
||||||
<path
|
|
||||||
d="m 352.04084,461.61747 12.16825,0 10.2962,16.08783 10.29621,-16.08783 12.46076,0 0,41.36034 -13.39678,0 0,-22.46445 -9.65269,16.35108 -2.83731,0 -9.97445,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
|
|
||||||
style="fill:#5599ff;fill-opacity:1;stroke:none"
|
|
||||||
id="path7039"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
transform="matrix(-1,0,0,1,-0.13506381,-12.064395)"
|
|
||||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:0.75;fill:#5599ff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
id="text7031">
|
|
||||||
<path
|
|
||||||
d="m -447.97656,461.61747 12.16825,0 10.29621,16.08783 10.29621,-16.08783 12.46075,0 0,41.36034 -13.39677,0 0,-22.46445 -9.6527,16.35108 -2.8373,0 -9.97446,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
|
|
||||||
style="fill:#5599ff;fill-opacity:1;stroke:none"
|
|
||||||
id="path7036"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
style="opacity:0.75;fill:#aaccff;fill-opacity:1;stroke:none;stroke-width:1.00199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
||||||
d="m 375.63144,496.36803 24.28571,14.02137 24.28572,-14.02137 14.46428,0 -38.75,22.37233 -38.75,-22.37233 z"
|
|
||||||
id="path7081"
|
|
||||||
inkscape:connector-curvature="0" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
152
docs/index.md
@ -1,152 +0,0 @@
|
|||||||
# Welcome to MarkdownLivePreview's documentation!
|
|
||||||
|
|
||||||
<img src="imgs/MarkdownLivePreview.svg" alt="MarkdownLivePreview's logo"
|
|
||||||
style="width: 400px; margin: auto; display: block;">
|
|
||||||
|
|
||||||
MarkdownLivePreview is a [Sublime Text 3][st] plugin to preview your markdown as you type,
|
|
||||||
*right in Sublime Text itself*, without *any* dependency!
|
|
||||||
|
|
||||||
It's very easy to use, but there's a few things that you might want to be aware of... So, let's
|
|
||||||
get started
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### Using Package Control
|
|
||||||
|
|
||||||
You can really easily install MarkdownLivePreview by using [Package Control][pck-con].
|
|
||||||
|
|
||||||
If it's not already, you need to [install it][install-pck-con] first.
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
If you're using the latest build of Sublime Text 3, you can just do
|
|
||||||
*Tools → Install Package Control…*
|
|
||||||
|
|
||||||
- Open up the command palette (<kbd>ctrl+shift+p</kbd>)
|
|
||||||
- Search up `Package Control: Install Package` (might take a few seconds)
|
|
||||||
- In the panel that just showed up, search for `MarkdownLivePreview`
|
|
||||||
|
|
||||||
Done! You have now access to every single features of MarkdownLivePreview! :wink:
|
|
||||||
|
|
||||||
### Using `git`
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ cd "%APPDATA%\Sublime Text 3\Packages" # on Windows
|
|
||||||
$ cd ~/Library/Application\ Support/Sublime\ Text\ 3 # on Mac
|
|
||||||
$ cd ~/.config/sublime-text-3 # on Linux
|
|
||||||
|
|
||||||
$ git clone "https://github.com/math2001/MarkdownLivePreview"
|
|
||||||
```
|
|
||||||
|
|
||||||
> So, which one do I pick?!
|
|
||||||
|
|
||||||
I depends of what you want to do. If you want to just use MarkdownLivePreview, pick the first
|
|
||||||
solution, you'll get every update automatically. But if you want to contribute, then choose the
|
|
||||||
second solution.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Previewing
|
|
||||||
|
|
||||||
As told in the introduction, MarkdownLivePreview is very easy to use:
|
|
||||||
|
|
||||||
- open a markdown file
|
|
||||||
- press <kbd>alt+m</kbd>
|
|
||||||
- or select in the command palette `MarkdownLivePreview: Edit Current File`
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
The preview of unsaved markdown files is currently not supported. It should be fixed soon.
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
[Markdown Extended][] is supported too!
|
|
||||||
|
|
||||||
That's it. That's all you need to do to preview your markdown!
|
|
||||||
|
|
||||||
### Custom CSS
|
|
||||||
|
|
||||||
If you want to, you can add custom `CSS` to the MarkdownLivePreview's default stylesheet.
|
|
||||||
|
|
||||||
Just search for `MarkdownLivePreview: Edit Custom CSS File` in the command palette
|
|
||||||
(<kbd>ctrl+shift+p</kbd>). It will open a file in which you can add some CSS that will be *added* to
|
|
||||||
the normal CSS.
|
|
||||||
|
|
||||||
!!! bug
|
|
||||||
Comments in the CSS is interpreted weirdly by Sublime Text's phantoms. After a few tests, I
|
|
||||||
think that everything that is bellow a comment is ignored.
|
|
||||||
|
|
||||||
If you want to be sure that your CSS works, don't put any comments in it
|
|
||||||
|
|
||||||
#### Share your tweaks!
|
|
||||||
|
|
||||||
If you think that other users would enjoy your added CSS, then raise an issue, or PR the
|
|
||||||
[GitHub repo][] to share your tweaks!
|
|
||||||
|
|
||||||
### Clearing the cache
|
|
||||||
|
|
||||||
MarkdownLivePreview has a cache system to store images you load from internet. You can clear this
|
|
||||||
cache by searching up in the command palette `MarkdownLivePreview: Clear the cache`.
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
The cache is one simple file called `MarkdownLivePreviewCache`, which is located in your temp
|
|
||||||
folder. To know where it is, you can open the Sublime Text console (<kbd>ctrl+`</kbd> or
|
|
||||||
*View → Show Console*), and paste this in:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import tempfile; print(tempfile.gettempdir())
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom settings for the preview
|
|
||||||
|
|
||||||
Sublime Text makes it easy to set custom settings for a specific *type* of view. For example,
|
|
||||||
`markdown`, `python`, etc. MarkdownLivePreview takes advantage of that: the preview view (the view
|
|
||||||
on the right) is a specific syntax (called — sorry for the originality —
|
|
||||||
`MarkdownLivePreviewSyntax`). So, to change this, you can focus the right view, open up the command
|
|
||||||
palette (<kbd>ctrl+shift+p</kbd>), and search up `Preferences: Settings — Syntax Specific`. In here,
|
|
||||||
you can specify any settings that is going to be applied only to this view.
|
|
||||||
|
|
||||||
### The hacky part
|
|
||||||
|
|
||||||
In fact, MarkdownLivePreview parses those settings, and looks for specific ones:
|
|
||||||
|
|
||||||
- `show_tabs`
|
|
||||||
- `show_minimap`
|
|
||||||
- `show_status_bar`
|
|
||||||
- `show_sidebar`
|
|
||||||
- `show_menus`
|
|
||||||
|
|
||||||
Those settings aren't supported by default because they affect the entire *window* instead of just
|
|
||||||
the view. But MarkdownLivePreview will look for them in your *preview*'s settings, and hide/show the
|
|
||||||
tabs, the minimap, etc...
|
|
||||||
|
|
||||||
As you probably guessed those settings takes a bool for value (`true` or `false`).
|
|
||||||
|
|
||||||
### Recommendation
|
|
||||||
|
|
||||||
Here's what I'd recommend (and use):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"show_menus": false,
|
|
||||||
"show_tabs": false,
|
|
||||||
"show_minimap": false,
|
|
||||||
"gutter": false,
|
|
||||||
"rulers": [],
|
|
||||||
"word_wrap": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
On Windows at least, you can press <kbd>alt</kbd> to focus (so show) the menu, even if they're
|
|
||||||
originally hidden
|
|
||||||
|
|
||||||
That's it! I hope you'll enjoy using this package! If it's the case, please let your friends know
|
|
||||||
about it, and even myself by sending me a [tweet][] or staring the repo
|
|
||||||
<iframe
|
|
||||||
src="https://ghbtns.com/github-btn.html?user=math2001&repo=MarkdownLivePreview&type=star&count=true&size=large"
|
|
||||||
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>!
|
|
||||||
|
|
||||||
[st]: https://sublimetext.com
|
|
||||||
[Markdown Extended]: https://packagecontrol.io/packages/Markdown%20Extended
|
|
||||||
[pck-con]: https://packagecontrol.io
|
|
||||||
[install-pck-con]: https://packagecontrol.io/installation
|
|
||||||
[tweet]: https://twitter.com/_math2001
|
|
||||||
[GitHub repo]: https://github.com/math2001/MarkdownLivePreview/issues
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
__all__ = ['escape_amp']
|
|
||||||
|
|
||||||
RE_REPLACE_AMPERSAND = re.compile(r'&(\w*)(;)?')
|
|
||||||
|
|
||||||
def replace(matchobj):
|
|
||||||
if matchobj.group(2):
|
|
||||||
return matchobj.group(0)
|
|
||||||
else:
|
|
||||||
return matchobj.group(0).replace('&', '&')
|
|
||||||
|
|
||||||
def escape_amp(text):
|
|
||||||
return RE_REPLACE_AMPERSAND.sub(replace, text)
|
|
||||||
|
|
||||||
def run_tests():
|
|
||||||
tests = [
|
|
||||||
['&', '&'],
|
|
||||||
['&', '&amp'],
|
|
||||||
['&', '&'],
|
|
||||||
['& &hello &bonjour;', '& &hello &bonjour;']
|
|
||||||
]
|
|
||||||
fails = 0
|
|
||||||
for i, (subject, result) in enumerate(tests):
|
|
||||||
if RE_REPLACE_AMPERSAND.sub(replace, subject) != result:
|
|
||||||
# CSW: ignore
|
|
||||||
print('TEST FAIL ({i}): {subject!r} escaped did not match {result!r}'.format(**locals()))
|
|
||||||
fails += 1
|
|
||||||
if fails == 0:
|
|
||||||
# CSW: ignore
|
|
||||||
print("SUCCESS: every tests ({}) passed successfully!".format(len(tests)))
|
|
||||||
else:
|
|
||||||
# CSW: ignore
|
|
||||||
print("{} test{} failed".format(fails, 's' if fails > 1 else ''))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
run_tests()
|
|
||||||
102
functions.py
@ -1,102 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
import base64
|
|
||||||
import os.path
|
|
||||||
import sublime
|
|
||||||
import re
|
|
||||||
from .image_manager import ImageManager
|
|
||||||
|
|
||||||
def plugin_loaded():
|
|
||||||
global error404, loading
|
|
||||||
loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt')
|
|
||||||
error404 = sublime.load_resource('Packages/MarkdownLivePreview/404.txt')
|
|
||||||
|
|
||||||
|
|
||||||
def replace_img_src_base64(html, basepath):
|
|
||||||
"""Really messy, but it works (should be updated)"""
|
|
||||||
index = -1
|
|
||||||
tag_start = '<img src="'
|
|
||||||
shtml, html = html, list(html)
|
|
||||||
while True:
|
|
||||||
index = shtml.find(tag_start, index + 1)
|
|
||||||
if index == -1:
|
|
||||||
break
|
|
||||||
path, end = get_content_till(html, '"', start=index + len(tag_start))
|
|
||||||
if ''.join(path).startswith('data:image/'):
|
|
||||||
continue
|
|
||||||
if ''.join(path).startswith(tuple(get_settings().get('load_from_internet' + \
|
|
||||||
'_when_starts', []))):
|
|
||||||
image = ImageManager.get(''.join(path))
|
|
||||||
image = image or loading
|
|
||||||
|
|
||||||
else:
|
|
||||||
# local image
|
|
||||||
path = ''.join(path)
|
|
||||||
path = os.path.join(basepath, path)
|
|
||||||
image = to_base64(path)
|
|
||||||
html[index+len(tag_start):end] = image
|
|
||||||
shtml = ''.join(html)
|
|
||||||
return ''.join(html)
|
|
||||||
|
|
||||||
def is_markdown_view(view):
|
|
||||||
return 'markdown' in view.scope_name(0)
|
|
||||||
|
|
||||||
def to_base64(path=None, content=None):
|
|
||||||
if path is None and content is None:
|
|
||||||
return error404
|
|
||||||
elif content is None and path is not None:
|
|
||||||
try:
|
|
||||||
with open(path, 'rb') as fp:
|
|
||||||
content = fp.read()
|
|
||||||
except (FileNotFoundError, OSError):
|
|
||||||
return error404
|
|
||||||
|
|
||||||
return 'data:image/png;base64,' + ''.join([chr(el) for el in list(base64.standard_b64encode(content))])
|
|
||||||
|
|
||||||
def md(*t, **kwargs):
|
|
||||||
sublime.message_dialog(kwargs.get('sep', '\n').join([str(el) for el in t]))
|
|
||||||
|
|
||||||
def sm(*t, **kwargs):
|
|
||||||
sublime.status_message(kwargs.get('sep', ' ').join([str(el) for el in t]))
|
|
||||||
|
|
||||||
def em(*t, **kwargs):
|
|
||||||
sublime.error_message(kwargs.get('sep', ' ').join([str(el) for el in t]))
|
|
||||||
|
|
||||||
def mini(val, min):
|
|
||||||
if val < min:
|
|
||||||
return min
|
|
||||||
return val
|
|
||||||
|
|
||||||
def get_content_till(string, char_to_look_for, start=0):
|
|
||||||
i = start
|
|
||||||
while i < len(string):
|
|
||||||
if string[i] == char_to_look_for:
|
|
||||||
return string[start:i], i
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
def get_view_content(view):
|
|
||||||
return view.substr(sublime.Region(0, view.size()))
|
|
||||||
|
|
||||||
def get_view_from_id(window, id):
|
|
||||||
if not isinstance(id, int):
|
|
||||||
return
|
|
||||||
for view in window.views():
|
|
||||||
if view.id() == id:
|
|
||||||
return view
|
|
||||||
|
|
||||||
def get_settings():
|
|
||||||
return sublime.load_settings('MarkdownLivePreview.sublime-settings')
|
|
||||||
|
|
||||||
def pre_with_br(html):
|
|
||||||
"""Because the phantoms of sublime text does not support <pre> blocks
|
|
||||||
this function replaces every \n with a <br> in a <pre>"""
|
|
||||||
|
|
||||||
while True:
|
|
||||||
obj = re.search(r'<pre>(.*?)</pre>', html, re.DOTALL)
|
|
||||||
if not obj:
|
|
||||||
break
|
|
||||||
html = list(html)
|
|
||||||
html[obj.start(0):obj.end(0)] = '<pre >' + ''.join(html[obj.start(1):obj.end(1)]) \
|
|
||||||
.replace('\n', '<br>') \
|
|
||||||
.replace(' ', ' ') + '</pre>'
|
|
||||||
html = ''.join(html)
|
|
||||||
return html
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
import os.path
|
|
||||||
import tempfile
|
|
||||||
import sublime
|
|
||||||
from threading import Thread
|
|
||||||
import urllib.request, urllib.error
|
|
||||||
from .functions import *
|
|
||||||
|
|
||||||
CACHE_FILE = os.path.join(tempfile.gettempdir(),
|
|
||||||
'MarkdownLivePreviewCache.txt')
|
|
||||||
TIMEOUT = 20 # seconds
|
|
||||||
|
|
||||||
SEPARATOR = '---%cache%--'
|
|
||||||
|
|
||||||
def get_base64_saver(loading, url):
|
|
||||||
def callback(content):
|
|
||||||
if isinstance(content, urllib.error.HTTPError):
|
|
||||||
if content.getcode() == 404:
|
|
||||||
loading[url] = 404
|
|
||||||
return
|
|
||||||
elif isinstance(content, urllib.error.URLError):
|
|
||||||
if (content.reason.errno == 11001 and
|
|
||||||
content.reason.strerror == 'getaddrinfo failed'):
|
|
||||||
loading[url] = 404
|
|
||||||
return
|
|
||||||
return sublime.error_message('An unexpected error has occured: ' +
|
|
||||||
str(content))
|
|
||||||
loading[url] = to_base64(content=content)
|
|
||||||
|
|
||||||
return callback
|
|
||||||
|
|
||||||
def get_cache_for(imageurl):
|
|
||||||
if not os.path.exists(CACHE_FILE):
|
|
||||||
return
|
|
||||||
with open(CACHE_FILE) as fp:
|
|
||||||
for line in fp.read().splitlines():
|
|
||||||
url, base64 = line.split(SEPARATOR, 1)
|
|
||||||
if url == imageurl:
|
|
||||||
return base64
|
|
||||||
|
|
||||||
def cache(imageurl, base64):
|
|
||||||
with open(CACHE_FILE, 'a') as fp:
|
|
||||||
fp.write(imageurl + SEPARATOR + base64 + '\n')
|
|
||||||
|
|
||||||
class ImageLoader(Thread):
|
|
||||||
|
|
||||||
def __init__(self, url, callback):
|
|
||||||
Thread.__init__(self)
|
|
||||||
self.url = url
|
|
||||||
self.callback = callback
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
page = urllib.request.urlopen(self.url, None, TIMEOUT)
|
|
||||||
except Exception as e:
|
|
||||||
self.callback(e)
|
|
||||||
else:
|
|
||||||
self.callback(page.read())
|
|
||||||
|
|
||||||
|
|
||||||
class ImageManager(object):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
>>> image = ImageManager.get('http://domain.com/image.png')
|
|
||||||
>>> image = ImageManager.get('http://domain.com/image.png')
|
|
||||||
# still loading (this is a comment, no an outputed text), it doesn't
|
|
||||||
# run an other request
|
|
||||||
>>> image = ImageManager.get('http://domain.com/image.png')
|
|
||||||
'data:image/png;base64,....'
|
|
||||||
"""
|
|
||||||
loading = {}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get(imageurl, user_callback=None):
|
|
||||||
|
|
||||||
cached = get_cache_for(imageurl)
|
|
||||||
if cached:
|
|
||||||
return cached
|
|
||||||
elif imageurl in ImageManager.loading.keys():
|
|
||||||
# return None (the file is still loading, already made a request)
|
|
||||||
# return string the base64 of the url (which is going to be cached)
|
|
||||||
temp_cached = ImageManager.loading[imageurl]
|
|
||||||
if temp_cached == 404:
|
|
||||||
return to_base64('404.png')
|
|
||||||
if temp_cached:
|
|
||||||
cache(imageurl, temp_cached)
|
|
||||||
del ImageManager.loading[imageurl]
|
|
||||||
return temp_cached
|
|
||||||
else:
|
|
||||||
# load from internet
|
|
||||||
ImageManager.loading[imageurl] = None
|
|
||||||
callback = get_base64_saver(ImageManager.loading, imageurl)
|
|
||||||
loader = ImageLoader(imageurl, callback)
|
|
||||||
loader.start()
|
|
||||||
sublime.set_timeout_async(lambda: loader.join(), TIMEOUT * 1000)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
There is images here, allthough they aren't of any use for the plugin. They're just the image that I used to generate the base64 (404.txt and loading.txt)
|
|
||||||
1341
lib/markdown2.py
18
live-testing/images.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
I'm not sure that it **actually** going to work, but it seems nicer than the [previous version][prev]
|
||||||
|
|
||||||
|
this is a test, hello world
|
||||||
|
|
||||||
|
This is the first image from the local file system (absolute path, sorry, it's not going
|
||||||
|
to work on your system unless your username is math2001):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This is the first image from the local file system, *relative* path!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This is the first image from the internet!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
[prev]: https://github.com/math2001/MarkdownLivePreview/tree/d4c477749ce7e77b8e9fc85464a2488f003c45bc
|
||||||
BIN
live-testing/sublime_merge.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
live-testing/sublime_text.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
18
live-testing/test.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# hello world
|
||||||
|
|
||||||
|
This is a *test*. Some inline `[2]code()`.
|
||||||
|
|
||||||
|
what the hell...
|
||||||
|
|
||||||
|
```python
|
||||||
|
import this
|
||||||
|
|
||||||
|
if input("answer yes") != 'yes':
|
||||||
|
print("Really?")
|
||||||
|
```
|
||||||
|
|
||||||
|
this flickering is really annoying...
|
||||||
|
|
||||||
|
It looks like it's gone... Oh wait nah, it's still here...
|
||||||
|
|
||||||
|
This should still be working, and it is!
|
||||||
@ -1 +0,0 @@
|
|||||||
data:image/png;base64,R0lGODlhZABkAPU+AAAAAAwNDRweHyYpKzg8Pzo+QUBFSERJTEdMT05UV1NYXFVbX1hfY1lfZGFobWJpbmhvdGxzeHF5fnJ6gHV9g3Z+hHqDiXuEin+IjoCIjoKLkYKMkoSNk4eQl4iSmIqTmouUm42XnY+ZoJKco5OdpJOepZSeppahqJeiqZmjqpumrZ6psJ+qsqOutqSvt6SwuKezu6i0vKm1vay4wK66wq+7w6+8xLK+xrK/x7TAybXCy7bDy7jEzbjFzrzJ0gAAACH5BAUAAD4AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAG/kCfcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiW8IAAAUilUpjQABkEsmMUchkwBIOTOQBQICGUabk0ctFhYdiSajAgOZRKeNRjkYqxaghyuwAgxFtZ1FJBe6NokHvya0nEUzuhYgijG/B86oRCDSOZAPv6VCw0SquiiWNwOwAzfjz0I8uasYPIMvDQ0kRhm/Ee/afKiQ1sIIDBAgkuUxQKDhA29ERMHK9GJSJR85pLUiwkOELgx6Goo0sG/IK1gVhCig9MjHimOreAmBMU+XngciRTrAMSQB/qxmR6KtEjGko7Shey7kbGgA6A0GBz4oOUjCno8YNXWp6NOCwVICD6UYPQqiBiANDHNOkILiqIYVg2Y0yPlAikddICASQtuwJJQY9OAimqFCZpRPei0pPnKjg4fHkB936JBYyg4VmDNrVlH5zYMFoEOLZgDBSoejqDfQEc1atBXUsOl8bi26bpUNsKWpnlPjg+PIj32brZJjs/HOi5PjiMFzCo4ZyAWpqCBhwgspMFa9NZRjg4TvEjZCEQFzWvQ9KiiA/+73SVtpGAT7mcFh/XcPVqH0uCsNhDs+J9gnAQXX+cADCSDMggRVVtGE2lZ6fCAgfkPcdYFhRAhlAVHxxfCnC4d42EdghtII1hYGLgjxki6GOSiNHtR990F+QpymizcZ0SNEjquI1+FHetDHQYFEuCANhBpaMMRAuqRYxEEJDSLPR1YlWVRN9Vjy3ioFCWHlEC6Uh44iOcB0gQck2kSEB90o4sEFx1yY5irQ9JdIDdIANcSXRBiDzGAfVcbnELiwmEgHx3Q5p5JGmOPjIdAF9eIRnyRnhA1AWvqEn4pq6umnoIYq6qiklmrqqaimquqqrLbq6quwxirrrLTWauuttwYBADs=
|
|
||||||
138
markdown2html.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
""" Notice how this file is completely independent of sublime text
|
||||||
|
|
||||||
|
I think it should be kept this way, just because it gives a bit more organisation,
|
||||||
|
and makes it a lot easier to think about, and for anyone who would want to, test since
|
||||||
|
markdown2html is just a pure function
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
import concurrent.futures
|
||||||
|
import urllib.request
|
||||||
|
import base64
|
||||||
|
import bs4
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from .lib.markdown2 import Markdown
|
||||||
|
|
||||||
|
__all__ = ("markdown2html",)
|
||||||
|
|
||||||
|
markdowner = Markdown(extras=["fenced-code-blocks"])
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
|
||||||
|
|
||||||
|
images_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
class LoadingError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def markdown2html(markdown, basepath, re_render, resources):
|
||||||
|
""" 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 = bs4.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, re_render)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
base64 = resources["base64_404_image"]
|
||||||
|
except LoadingError:
|
||||||
|
base64 = resources["base64_loading_image"]
|
||||||
|
|
||||||
|
img_element["src"] = base64
|
||||||
|
|
||||||
|
# remove comments, because they pollute the console with error messages
|
||||||
|
for comment_element in soup.find_all(
|
||||||
|
text=lambda text: isinstance(text, bs4.Comment)
|
||||||
|
):
|
||||||
|
comment_element.extract()
|
||||||
|
|
||||||
|
# FIXME: how do tables look? should we use ascii tables?
|
||||||
|
|
||||||
|
# pre aren't handled by ST3. The require manual adjustment
|
||||||
|
for pre_element in soup.find_all("pre"):
|
||||||
|
# select the first child, <code>
|
||||||
|
code_element = next(pre_element.children)
|
||||||
|
|
||||||
|
# FIXME: this method sucks, but can we do better?
|
||||||
|
fixed_pre = (
|
||||||
|
str(code_element)
|
||||||
|
.replace(" ", '<i class="space">.</i>')
|
||||||
|
.replace("\n", "<br />")
|
||||||
|
)
|
||||||
|
|
||||||
|
code_element.replace_with(bs4.BeautifulSoup(fixed_pre, "html.parser"))
|
||||||
|
|
||||||
|
# FIXME: highlight the code using Sublime's syntax
|
||||||
|
|
||||||
|
# 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 />"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_base64_image(path, re_render):
|
||||||
|
""" Gets the base64 for the image (local and remote images). re_render is a
|
||||||
|
callback which is called when we finish loading an image from the internet
|
||||||
|
to trigger an update of the preview (the image will then be loaded from the cache)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def callback(path, future):
|
||||||
|
# altering image_cache is "safe" to do because callback is called in the same
|
||||||
|
# thread as add_done_callback:
|
||||||
|
# > Added callables are called in the order that they were added and are always
|
||||||
|
# > called in a thread belonging to the process that added them
|
||||||
|
# > --- Python docs
|
||||||
|
images_cache[path] = future.result()
|
||||||
|
# we render, which means this function will be called again, but this time, we
|
||||||
|
# will read from the cache
|
||||||
|
re_render()
|
||||||
|
|
||||||
|
if path in images_cache:
|
||||||
|
return images_cache[path]
|
||||||
|
|
||||||
|
if path.startswith("http://") or path.startswith("https://"):
|
||||||
|
executor.submit(load_image, path).add_done_callback(partial(callback, path))
|
||||||
|
raise LoadingError()
|
||||||
|
|
||||||
|
with open(path, "rb") as fp:
|
||||||
|
image = "data:image/png;base64," + base64.b64encode(fp.read()).decode("utf-8")
|
||||||
|
images_cache[path] = image
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
def load_image(url):
|
||||||
|
with urllib.request.urlopen(url, timeout=60) as conn:
|
||||||
|
content_type = conn.info().get_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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return "data:image/png;base64," + base64.b64encode(conn.read()).decode("utf-8")
|
||||||
@ -2,5 +2,6 @@
|
|||||||
"install": "messages/install.txt",
|
"install": "messages/install.txt",
|
||||||
"1.1.2": "messages/1.1.2.txt",
|
"1.1.2": "messages/1.1.2.txt",
|
||||||
"2.0.1": "messages/2.0.1.txt",
|
"2.0.1": "messages/2.0.1.txt",
|
||||||
"2.2.1": "messages/2.2.0.txt"
|
"2.2.1": "messages/2.2.0.txt",
|
||||||
|
"2.4.1": "messages/2.4.txt"
|
||||||
}
|
}
|
||||||
|
|||||||
10
messages/2.4.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
Sorry to interrupt you... :(
|
||||||
|
|
||||||
|
Some stuff changed on MarkdownLivePreview. It now supports YAML/TOML front matters. You can hide it,
|
||||||
|
or show it in a pre block (edit your settings for this).
|
||||||
|
|
||||||
|
Hope you'll enjoy it!
|
||||||
|
|
||||||
|
Tip of the day: If you want a VIM-like search feature, then just press 'ctrl/cmd+i'
|
||||||
|
(Find → Incremental find). You can still go the next match by pressing 'f3', and to
|
||||||
|
the previous one by pressing 'shift+f3'
|
||||||
35
mkdocs.yml
@ -1,35 +0,0 @@
|
|||||||
site_name: MarkdownLivePreview
|
|
||||||
theme: material
|
|
||||||
repo_name: math2001/MarkdownLivePreview
|
|
||||||
repo_url: https://github.com/math2001/MarkdownLivePreview
|
|
||||||
site_description: Sublime Text 3 Plugin MarkdownLivePreview's documentation
|
|
||||||
site_author: math2001
|
|
||||||
|
|
||||||
markdown_extensions:
|
|
||||||
- toc(permalink=true)
|
|
||||||
- pymdownx.arithmatex
|
|
||||||
- pymdownx.betterem(smart_enable=all)
|
|
||||||
- pymdownx.caret
|
|
||||||
- pymdownx.critic
|
|
||||||
- pymdownx.emoji:
|
|
||||||
emoji_generator: !!python/name:pymdownx.emoji.to_svg
|
|
||||||
- pymdownx.inlinehilite
|
|
||||||
- pymdownx.magiclink
|
|
||||||
- pymdownx.mark
|
|
||||||
- pymdownx.smartsymbols
|
|
||||||
- pymdownx.superfences
|
|
||||||
- pymdownx.tasklist(custom_checkbox=true)
|
|
||||||
- pymdownx.tilde
|
|
||||||
- admonition
|
|
||||||
- codehilite
|
|
||||||
|
|
||||||
extra:
|
|
||||||
logo: imgs/MarkdownLivePreview-opposite.svg
|
|
||||||
palette:
|
|
||||||
primary: Blue
|
|
||||||
accent: Indigo
|
|
||||||
social:
|
|
||||||
- type: github
|
|
||||||
link: https://github.com/math2001
|
|
||||||
- type: twitter
|
|
||||||
link: https://twitter.com/_math2001
|
|
||||||
1
resources/404.base64
Normal file
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
9
resources/convertresources.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
""" A small script to convert the images into base64 data """
|
||||||
|
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
with open("404.png", "rb") as png, open("404.base64", "wb") as base64:
|
||||||
|
base64.write(b64encode(png.read()))
|
||||||
|
|
||||||
|
with open("loading.png", "rb") as png, open("loading.base64", "wb") as base64:
|
||||||
|
base64.write(b64encode(png.read()))
|
||||||
1
resources/loading.base64
Normal file
@ -0,0 +1 @@
|
|||||||
|
R0lGODlhZABkAPU+AAAAAAwNDRweHyYpKzg8Pzo+QUBFSERJTEdMT05UV1NYXFVbX1hfY1lfZGFobWJpbmhvdGxzeHF5fnJ6gHV9g3Z+hHqDiXuEin+IjoCIjoKLkYKMkoSNk4eQl4iSmIqTmouUm42XnY+ZoJKco5OdpJOepZSeppahqJeiqZmjqpumrZ6psJ+qsqOutqSvt6SwuKezu6i0vKm1vay4wK66wq+7w6+8xLK+xrK/x7TAybXCy7bDy7jEzbjFzrzJ0gAAACH5BAUAAD4AIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAZABkAAAG/kCfcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiW8IAAAUilUpjQABkEsmMUchkwBIOTOQBQICGUabk0ctFhYdiSajAgOZRKeNRjkYqxaghyuwAgxFtZ1FJBe6NokHvya0nEUzuhYgijG/B86oRCDSOZAPv6VCw0SquiiWNwOwAzfjz0I8uasYPIMvDQ0kRhm/Ee/afKiQ1sIIDBAgkuUxQKDhA29ERMHK9GJSJR85pLUiwkOELgx6Goo0sG/IK1gVhCig9MjHimOreAmBMU+XngciRTrAMSQB/qxmR6KtEjGko7Shey7kbGgA6A0GBz4oOUjCno8YNXWp6NOCwVICD6UYPQqiBiANDHNOkILiqIYVg2Y0yPlAikddICASQtuwJJQY9OAimqFCZpRPei0pPnKjg4fHkB936JBYyg4VmDNrVlH5zYMFoEOLZgDBSoejqDfQEc1atBXUsOl8bi26bpUNsKWpnlPjg+PIj32brZJjs/HOi5PjiMFzCo4ZyAWpqCBhwgspMFa9NZRjg4TvEjZCEQFzWvQ9KiiA/+73SVtpGAT7mcFh/XcPVqH0uCsNhDs+J9gnAQXX+cADCSDMggRVVtGE2lZ6fCAgfkPcdYFhRAhlAVHxxfCnC4d42EdghtII1hYGLgjxki6GOSiNHtR990F+QpymizcZ0SNEjquI1+FHetDHQYFEuCANhBpaMMRAuqRYxEEJDSLPR1YlWVRN9Vjy3ioFCWHlEC6Uh44iOcB0gQck2kSEB90o4sEFx1yY5irQ9JdIDdIANcSXRBiDzGAfVcbnELiwmEgHx3Q5p5JGmOPjIdAF9eIRnyRnhA1AWvqEn4pq6umnoIYq6qiklmrqqaimquqqrLbq6quwxirrrLTWauuttwYBADs=
|
||||||
|
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 953 B |
44
resources/stylesheet.css
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
html {
|
||||||
|
--light-bg: color(var(--background) blend(#fff 90%));
|
||||||
|
--very-light-bg: color(var(--background) blend(#fff 85%));
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Ubuntu", "DejaVu Sans", "Open Sans", sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
font-style: italic;
|
||||||
|
display: block;
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
display: block;
|
||||||
|
background-color: var(--very-light-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding-left: 0.2rem;
|
||||||
|
padding-right: 0.2rem;
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding-left: 0.2rem;
|
||||||
|
padding-right: 0.2rem;
|
||||||
|
background-color: var(--very-light-bg);
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i.space {
|
||||||
|
color: var(--very-light-bg);
|
||||||
|
}
|
||||||
28
sample.md
@ -1,28 +0,0 @@
|
|||||||
# Hello world
|
|
||||||
|
|
||||||
Some `inline code` with *italic* and **bold** text.
|
|
||||||
|
|
||||||
```python
|
|
||||||
import this
|
|
||||||
if you is moods.curious:
|
|
||||||
print('then do it!')
|
|
||||||
```
|
|
||||||
|
|
||||||
<kbd>ctrl+\`</kbd> or *View → Show Console* and paste `import this`!
|
|
||||||
|
|
||||||
> Perfect programmers do NOT need comments.
|
|
||||||
|
|
||||||
- to be efficient
|
|
||||||
- you need
|
|
||||||
- todos
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Some plugin I just *need*:
|
|
||||||
|
|
||||||
- [PackageResourceReviewer](https://packagecontrol.io/packages/PackageResourceViewer)
|
|
||||||
- [Boxy Theme](https://packagecontrol.io/packages/Boxy%20Theme)
|
|
||||||
- [Markdown Preview](https://packagecontrol.io/packages/Markdown%20Preview)
|
|
||||||
- [FileManager](https://packagecontrol.io/packages/FileManager)
|
|
||||||
- [PlainTasks](https://packagecontrol.io/packages/PlainTasks)
|
|
||||||
- [JSONComma](https://packagecontrol.io/packages/JSONComma)
|
|
||||||
|
Before Width: | Height: | Size: 70 KiB |
@ -1,8 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
PREVIEW_ENABLED = 'markdown_live_preview_enabled'
|
|
||||||
PREVIEW_ID = 'markdown_live_preview_id'
|
|
||||||
IS_PREVIEW = 'is_markdown_live_preview'
|
|
||||||
IS_HIDDEN = 'is_hidden_markdown_live_preview'
|
|
||||||
MD_VIEW_ID = 'markdown_live_preview_md_id'
|
|
||||||
PREVIEW_WINDOW = 'markdown_live_preview_window'
|
|
||||||
27
utils.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# import sublime
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings():
|
||||||
|
return sublime.get_settings("MarkdownLivePreview.sublime-settings")
|
||||||
|
|
||||||
|
|
||||||
|
def min_time_between_call(timeout, on_block=lambda *args, **kwargs: None):
|
||||||
|
""" Enforces a timeout between each call to the function
|
||||||
|
timeout is in seconds
|
||||||
|
"""
|
||||||
|
last_call = 0
|
||||||
|
|
||||||
|
def outer(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nonlocal last_call
|
||||||
|
|
||||||
|
if time.time() - last_call < timeout:
|
||||||
|
time.sleep(timeout - (time.time() - last_call))
|
||||||
|
|
||||||
|
last_call = time.time()
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return outer
|
||||||