Compare commits
27 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 |
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,19 +0,0 @@
|
|||||||
{
|
|
||||||
// As soon as you open a markdown file, it opens the window preview
|
|
||||||
"markdown_live_preview_on_open": false,
|
|
||||||
|
|
||||||
// If an image starts with one of those strings, then it will be loaded from internet
|
|
||||||
"load_from_internet_when_starts": ["http://", "https://"],
|
|
||||||
|
|
||||||
// When the preview is opened, the markdown file is closed in the origin window and reopend in
|
|
||||||
// the preview window. If this option is set to 'true', then the markdown file will NOT be
|
|
||||||
// closed in the origin window
|
|
||||||
"keep_open_when_opening_preview": false,
|
|
||||||
|
|
||||||
// Choose what to do with YAML/TOML (---/+++ respectively) headers
|
|
||||||
// Valid values: "wrap_in_pre", "remove".
|
|
||||||
"header_action": "wrap_in_pre",
|
|
||||||
|
|
||||||
// Wait at least the specified *seconds* before updating the preview.
|
|
||||||
"update_preview_every": 0
|
|
||||||
}
|
|
||||||
@ -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>
|
|
||||||
107
MLPApi.py
@ -1,107 +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 .lib.pre_tables import pre_tables
|
|
||||||
|
|
||||||
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__)
|
|
||||||
|
|
||||||
|
|
||||||
# used to store the phantom's set
|
|
||||||
windows_phantom_set = {}
|
|
||||||
|
|
||||||
|
|
||||||
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 markdown2html(md, basepath, color_scheme):
|
|
||||||
|
|
||||||
# removes/format the YAML/TOML header.
|
|
||||||
md = manage_header(md, get_settings().get('header_action'))
|
|
||||||
|
|
||||||
html = '<style>\n{}\n</style>\n'.format(get_style(color_scheme))
|
|
||||||
|
|
||||||
|
|
||||||
# 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 += md2.markdown(md, extras=['fenced-code-blocks', 'tables', 'strike'])
|
|
||||||
|
|
||||||
# tables aren't supported by the Phantoms
|
|
||||||
# This function transforms them into aligned ASCII tables and displays them in a <pre> block
|
|
||||||
# (the ironic thing is that they aren't supported either :D)
|
|
||||||
html = pre_tables(html)
|
|
||||||
|
|
||||||
# pre block are not supported by the Phantoms.
|
|
||||||
# This functions replaces the \n in them with <br> so that it does (1/2)
|
|
||||||
html = pre_with_br(html)
|
|
||||||
|
|
||||||
# comments aren't supported by the Phantoms
|
|
||||||
# Simply removes them using bs4, so you can be sadic and type `<!-- hey hey! -->`, these one
|
|
||||||
# won't be stripped!
|
|
||||||
html = strip_html_comments(html)
|
|
||||||
|
|
||||||
# exception, again, because <pre> aren't supported by the phantoms
|
|
||||||
# so, because this is monosaped font, I just replace it with a '.' and make transparent ;)
|
|
||||||
html = html.replace(' ', '<i class="space">.</i>')
|
|
||||||
|
|
||||||
# Phantoms have problem with images size when they're loaded from an url/path
|
|
||||||
# So, the solution is to convert them to base64
|
|
||||||
html = replace_img_src_base64(html, basepath=basepath)
|
|
||||||
|
|
||||||
# BeautifulSoup uses the <br/> but the sublime phantoms do not support them...
|
|
||||||
html = html.replace('<br/>', '<br />').replace('<hr/>', '<hr />')
|
|
||||||
sublime.set_clipboard(html)
|
|
||||||
|
|
||||||
return html
|
|
||||||
|
|
||||||
def show_html(md_view, preview):
|
|
||||||
global windows_phantom_set
|
|
||||||
html = markdown2html(get_view_content(md_view), os.path.dirname(md_view.file_name()), md_view.settings().get('color_scheme'))
|
|
||||||
|
|
||||||
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,124 +1,237 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
import os.path
|
||||||
|
|
||||||
import sublime
|
import sublime
|
||||||
import sublime_plugin
|
import sublime_plugin
|
||||||
import time
|
|
||||||
|
|
||||||
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
|
||||||
if get_settings().get('keep_open_when_opening_preview') is False:
|
abs_path = os.path.join(sublime.packages_path(), "..", path)
|
||||||
current_view.close()
|
if os.path.isfile(abs_path):
|
||||||
if file_name is None:
|
with open(abs_path, "r") as fp:
|
||||||
return sublime.error_message('MarkdownLivePreview: Not supporting '
|
return fp.read()
|
||||||
'unsaved file for now')
|
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
|
||||||
now = time.time()
|
}
|
||||||
|
|
||||||
if now - vsettings.get(LAST_UPDATE, 0) < get_settings().get('update_preview_every'):
|
# we schedule an update for every key stroke, with a delay of DELAY
|
||||||
return
|
# then, we update only if now() - last_update > DELAY
|
||||||
vsettings.set(LAST_UPDATE, now)
|
last_update = 0
|
||||||
if not vsettings.get(PREVIEW_ENABLED):
|
|
||||||
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)
|
# FIXME: maybe we shouldn't restore the file in the original window...
|
||||||
return view, preview
|
|
||||||
|
|
||||||
def on_modified_async(self, view):
|
def on_pre_close(self, markdown_view):
|
||||||
if not is_markdown_view(view): # faster than getting the settings
|
""" 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
|
||||||
delay = get_settings().get('update_preview_every')
|
|
||||||
if not delay:
|
self.markdown_view = markdown_view
|
||||||
self.update(view)
|
self.preview_window = markdown_view.window()
|
||||||
|
self.file_name = markdown_view.file_name()
|
||||||
|
|
||||||
|
if self.file_name is None:
|
||||||
|
total_region = sublime.Region(0, markdown_view.size())
|
||||||
|
self.content = markdown_view.substr(total_region)
|
||||||
|
markdown_view.erase(edit, total_region)
|
||||||
else:
|
else:
|
||||||
sublime.set_timeout(lambda: self.update(view), delay * 1000)
|
self.content = None
|
||||||
|
|
||||||
def on_window_command(self, window, command, args):
|
def on_load_async(self, markdown_view):
|
||||||
if command == 'close' and window.settings().get(PREVIEW_WINDOW):
|
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
|
||||||
release_phantoms_set(window.id())
|
if not infos:
|
||||||
return 'close_window', {}
|
|
||||||
|
|
||||||
def on_activated_async(self, view):
|
|
||||||
vsettings = view.settings()
|
|
||||||
|
|
||||||
if (is_markdown_view(view) and get_settings().get(ON_OPEN)
|
|
||||||
and not vsettings.get(PREVIEW_ENABLED)
|
|
||||||
and vsettings.get('syntax') != 'Packages/MarkdownLivePreview/' + \
|
|
||||||
'.sublime/MarkdownLivePreviewSyntax' + \
|
|
||||||
'.hidden-tmLanguage'
|
|
||||||
and not any(filter(lambda window: window.settings().get(PREVIEW_WINDOW) is True,
|
|
||||||
sublime.windows()))):
|
|
||||||
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')
|
preview_view = markdown_view.window().active_view_in_group(1)
|
||||||
show_minimap = psettings.get('show_minimap')
|
|
||||||
show_status_bar = psettings.get('show_status_bar')
|
|
||||||
show_sidebar = psettings.get('show_sidebar')
|
|
||||||
show_menus = psettings.get('show_menus')
|
|
||||||
|
|
||||||
if show_tabs is not None:
|
self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
|
||||||
window.set_tabs_visible(show_tabs)
|
self._update_preview(markdown_view)
|
||||||
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):
|
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
|
||||||
|
|
||||||
def run(self):
|
assert (
|
||||||
clear_cache()
|
markdown_view.id() == self.markdown_view.id()
|
||||||
|
), "pre_close view.id() != close view.id()"
|
||||||
|
|
||||||
|
del self.phantom_sets[markdown_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 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
65
README.md
@ -1,57 +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
|
If you know what feature you want to implement, or what bug you wanna fix, then
|
||||||
[PackageControl](http://packagecontrol.io), which means you just have to
|
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
|
1. Fork this repo
|
||||||
[thank package control](https://packagecontrol.io/say_thanks) for this. :wink:
|
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*
|
|
||||||
|
|
||||||
For further infos, please [read the docs](https://math2001.github.io/MarkdownLivePreview/)!
|
|
||||||
|
|
||||||
### Demo
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 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][]
|
|
||||||
|
|
||||||
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
|
|
||||||
[st-css-rules]: http://www.sublimetext.com/docs/3/minihtml.html#css
|
|
||||||
[README]: http://github.com/math2001/MarkdownLivePreview/README.md
|
|
||||||
63
default.css
@ -1,63 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
padding-left: 0.2rem;
|
|
||||||
padding-right: 0.2rem;
|
|
||||||
background-color: var(--light-bg);
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.codehilite {
|
|
||||||
display: block;
|
|
||||||
margin-top: 20px;
|
|
||||||
background-color: var(--light-bg);
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-top: 5px;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
pre code {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre code .space {
|
|
||||||
color: var(--light-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.table {
|
|
||||||
background-color: var(--background);
|
|
||||||
}
|
|
||||||
pre.table code {
|
|
||||||
background-color: var(--background);
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
pre.table code .space {
|
|
||||||
color: var(--background);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
kbd {
|
|
||||||
padding: 0 5px;
|
|
||||||
background-color: var(--very-light-bg);
|
|
||||||
font-family: "Roboto Mono","Courier New",Courier,monospace;
|
|
||||||
}
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"*": {
|
"*": {
|
||||||
"*": [
|
"*": [
|
||||||
"bs4",
|
"bs4"
|
||||||
"pygments"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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):
|
|
||||||
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'],
|
|
||||||
'folders': ['lib'],
|
|
||||||
'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 |
|
Before Width: | Height: | Size: 70 KiB |
184
docs/index.md
@ -1,184 +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!
|
|
||||||
|
|
||||||
### Settings
|
|
||||||
|
|
||||||
To edit MarkdownLivePreview's settings, you just need to search in the command palette
|
|
||||||
`Preferences: MarkdownLivePreview Settings`, or from the menus:
|
|
||||||
*Preferences → Package Settings → MarkdownLivePreview → Settings*
|
|
||||||
|
|
||||||
Do not edit the left file (by default, you cannot), but the right one. This right file will
|
|
||||||
override the default one (on the left), and will be saved in your `User` folder, which makes it easy
|
|
||||||
to back up.
|
|
||||||
|
|
||||||
- `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][] for its [suggestion][@ooing suggestion]). 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://"]`
|
|
||||||
- `header_action`: If you're writing a blog with some markdown and a static website generator, you
|
|
||||||
probably have a YAML header. By default, this header will be displayed in a `pre` block. If you want
|
|
||||||
to hide it, then just change the value to `remove`. Thanks to [@tanhanjay][] for
|
|
||||||
[letting me know][front-matter-issue]!
|
|
||||||
- `keep_open_when_opening_preview`: Each time the preview window is opened, the original markdown
|
|
||||||
view is closed. If you want to keep it opened, just set this setting to `true`
|
|
||||||
|
|
||||||
### 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 for your MarkdownLivePreviewSyntax's settings (and what I use):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"show_menus": false,
|
|
||||||
"show_tabs": false,
|
|
||||||
"show_minimap": false,
|
|
||||||
"gutter": false,
|
|
||||||
"rulers": [],
|
|
||||||
"word_wrap": true
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And here's what you'll get (With the awesome [Boxy Theme][] and its [Monokai Color Scheme][]):
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
!!! tip
|
|
||||||
On Windows at least, you can press <kbd>alt</kbd> to focus (so show) the menus, 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" style="width: 120px; height: 30px; vertical-align: bottom"></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
|
|
||||||
[@ooing]: https://github.com/ooing
|
|
||||||
[@ooing suggestion]: https://github.com/math2001/MarkdownLivePreview/issues/7#issue-199464852
|
|
||||||
[@tanhanjay]: https://github.com/tanhanjay
|
|
||||||
[front-matter-issue]: https://github.com/math2001/MarkdownLivePreview/issues/17
|
|
||||||
[Boxy Theme]: https://packagecontrol.io/packages/Boxy%20Theme
|
|
||||||
[Monokai Color Scheme]: https://github.com/ihodev/sublime-boxy#boxy-monokai--predawn
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
This project is published under MIT license.
|
|
||||||
|
|
||||||
> The MIT License is a permissive license that is short and to the point. It lets people do anything
|
|
||||||
> they want with your code as long as they provide attribution back to you and don’t hold you
|
|
||||||
> liable.
|
|
||||||
>
|
|
||||||
> — *from [choosealicense.com](http://choosealicense.com), by [GitHub](https://github.com)*
|
|
||||||
|
|
||||||
Copyright 2017 Mathieu PATUREL
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
|
||||||
andassociated documentation files (the "Software"), to deal in the Software without restriction,
|
|
||||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
||||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
||||||
portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
||||||
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
||||||
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@ -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()
|
|
||||||
47
example.md
@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
title: Demo
|
|
||||||
description: Preview your markdown right in Sublime Text!
|
|
||||||
hope: You'll enjoy using it!
|
|
||||||
---
|
|
||||||
|
|
||||||
# Hello world
|
|
||||||
|
|
||||||
<!-- supports comments -->
|
|
||||||
|
|
||||||
And `<!-- vicious ones ;) -->`
|
|
||||||
|
|
||||||
Some `inline code` with *italic*, **bold** text, and ~~strike through~~.
|
|
||||||
|
|
||||||
```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
|
|
||||||
|
|
||||||
|
|
||||||
| ID | Name |
|
|
||||||
|-----------|-------|
|
|
||||||
| 56 | Matt |
|
|
||||||
| 42 | Colin |
|
|
||||||
| 23 | Lisa |
|
|
||||||
| 45 | John |
|
|
||||||
| `<table>` | `><` |
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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)
|
|
||||||
139
functions.py
@ -1,139 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
import base64
|
|
||||||
import os.path
|
|
||||||
import sublime
|
|
||||||
import re
|
|
||||||
from .image_manager import ImageManager
|
|
||||||
from .lib.pygments_from_theme import pygments_from_theme
|
|
||||||
from bs4 import BeautifulSoup, Comment as html_comment
|
|
||||||
|
|
||||||
def plugin_loaded():
|
|
||||||
global error404, loading, DEFAULT_STYLE, USER_STYLE_FILE
|
|
||||||
loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt')
|
|
||||||
error404 = sublime.load_resource('Packages/MarkdownLivePreview/404.txt')
|
|
||||||
|
|
||||||
DEFAULT_STYLE = sublime.load_resource('Packages/MarkdownLivePreview/default.css')
|
|
||||||
USER_STYLE_FILE = os.path.join(sublime.packages_path(), 'User', "MarkdownLivePreview.css")
|
|
||||||
|
|
||||||
MATCH_YAML_HEADER = re.compile(r'^([\-\+])\1{2}\n(?P<content>.+)\n\1{3}\n', re.DOTALL)
|
|
||||||
|
|
||||||
def strip_html_comments(html):
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
for element in soup.find_all(text=lambda text: isinstance(text, html_comment)):
|
|
||||||
element.extract()
|
|
||||||
return str(soup)
|
|
||||||
|
|
||||||
def manage_header(md, action):
|
|
||||||
matchobj = MATCH_YAML_HEADER.match(md)
|
|
||||||
if not matchobj:
|
|
||||||
return md
|
|
||||||
if action == 'remove':
|
|
||||||
return md[len(matchobj.group(0)):]
|
|
||||||
elif action == 'wrap_in_pre':
|
|
||||||
return '<pre><code>' + matchobj.group('content') + '</code></pre>' \
|
|
||||||
+ md[len(matchobj.group(0)):]
|
|
||||||
|
|
||||||
raise ValueError('Got an unknown action: "{}"'.format(action))
|
|
||||||
|
|
||||||
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 replace_img_src_base64(html, basepath):
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
load_from_internet_starters = get_settings().get('load_from_internet_when_starts')
|
|
||||||
for img in soup.find_all('img'):
|
|
||||||
if img['src'].startswith('data:image/'):
|
|
||||||
continue
|
|
||||||
elif img['src'].startswith(tuple(load_from_internet_starters)):
|
|
||||||
image = ImageManager.get(img['src']) or loading
|
|
||||||
else: # this is a local image
|
|
||||||
image = to_base64(os.path.join(basepath, img['src']))
|
|
||||||
|
|
||||||
img['src'] = image
|
|
||||||
|
|
||||||
return str(soup)
|
|
||||||
|
|
||||||
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_spaces(code):
|
|
||||||
for tag in code.find_all(text=True):
|
|
||||||
tag.replace_with(BeautifulSoup(str(tag).replace('\t', ' ' * 4).replace(' ', '<i class="space">.</i>').replace('\n', '<br />'), 'html.parser'))
|
|
||||||
return code
|
|
||||||
|
|
||||||
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>"""
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
for pre in soup.find_all('pre'):
|
|
||||||
code = pre.find('code')
|
|
||||||
code.replace_with(_pre_with_spaces(code))
|
|
||||||
return str(soup)
|
|
||||||
|
|
||||||
|
|
||||||
def get_style(color_scheme):
|
|
||||||
css = DEFAULT_STYLE
|
|
||||||
if os.path.exists(USER_STYLE_FILE):
|
|
||||||
with open(USER_STYLE_FILE) as fp:
|
|
||||||
css += '\n' + fp.read() + '\n'
|
|
||||||
if color_scheme:
|
|
||||||
css += pygments_from_theme(get_resource(color_scheme))
|
|
||||||
return ''.join([line.strip() + ' ' for line in css.splitlines()])
|
|
||||||
|
|
||||||
def get_resource(resource):
|
|
||||||
if os.path.exists(os.path.join(sublime.packages_path(), '..', resource)):
|
|
||||||
with open(os.path.join(sublime.packages_path(), '..', resource), encoding='utf-8') as fp:
|
|
||||||
return fp.read()
|
|
||||||
else:
|
|
||||||
return sublime.load_resource(resource)
|
|
||||||
@ -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)
|
|
||||||
1471
lib/markdown2.py
@ -1,52 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
'pre_tables' transform *html* tables into markdown tables, and put them in some <pre><code> tags
|
|
||||||
"""
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
def python_table(s_table):
|
|
||||||
"""Transform BeautifulSoup table into list of list"""
|
|
||||||
rows = []
|
|
||||||
for row in s_table.find_all('tr'):
|
|
||||||
# rows.append(list(map( lambda td: td.text, row.find_all(['th', 'td']) )))
|
|
||||||
rows.append(row.find_all(['th', 'td']))
|
|
||||||
return rows
|
|
||||||
|
|
||||||
def pre_table(s_table):
|
|
||||||
rows = python_table(s_table)
|
|
||||||
cols_width = [len(cell) for cell in rows[0]]
|
|
||||||
for j, row in enumerate(rows):
|
|
||||||
for i, cell in enumerate(row):
|
|
||||||
if cols_width[i] < len(cell.text):
|
|
||||||
cols_width[i] = len(cell.text)
|
|
||||||
text = '<pre class="table"><code>'
|
|
||||||
for i, row in enumerate(rows):
|
|
||||||
if i == 1:
|
|
||||||
for j, cell in enumerate(row):
|
|
||||||
text += '|' + '-' * (cols_width[j] + 2)
|
|
||||||
text += '|\n'
|
|
||||||
|
|
||||||
for j, cell in enumerate(row):
|
|
||||||
text += '| '
|
|
||||||
if cell.name == 'th':
|
|
||||||
title = ' ' * ((cols_width[j] - len(cell.text)) // 2) \
|
|
||||||
+ ''.join(str(node) for node in cell.contents) \
|
|
||||||
+ ' ' * int(round((cols_width[j] - len(cell.text)) / 2 ) + 1)
|
|
||||||
# + 1 because of the added space before the closing | of each cell
|
|
||||||
if cols_width[j] + 1 != len(title):
|
|
||||||
title += ' '
|
|
||||||
text += title
|
|
||||||
else:
|
|
||||||
text += ''.join(str(node) for node in cell.contents) \
|
|
||||||
+ ' ' * (cols_width[j] - len(cell.text) + 1)
|
|
||||||
text += '|\n'
|
|
||||||
text += '</pre></code>'
|
|
||||||
return text
|
|
||||||
|
|
||||||
def pre_tables(html):
|
|
||||||
soup = BeautifulSoup(html, 'html.parser')
|
|
||||||
for table in soup.find_all('table'):
|
|
||||||
table.replace_with(BeautifulSoup(pre_table(table), 'html.parser'))
|
|
||||||
return str(soup)
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from xml.dom.minidom import parseString
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
class Style:
|
|
||||||
# .highlight is the wrapper class for highlighting therefore
|
|
||||||
# all css rules are prefixed with .highlight
|
|
||||||
PREFIX = '.codehilite'
|
|
||||||
|
|
||||||
# -----------------------------------------
|
|
||||||
# Params
|
|
||||||
# name: the name of the class
|
|
||||||
# args: each argument is an array.
|
|
||||||
# Each array consists of css properties
|
|
||||||
# that is either a color or font style
|
|
||||||
# ----------------------------------------
|
|
||||||
|
|
||||||
def __init__(self, name, *args):
|
|
||||||
self.name = name # Name of the class
|
|
||||||
self.rules = {} # The css rules
|
|
||||||
for arr in args:
|
|
||||||
for value in arr:
|
|
||||||
# Only define properties if they are already not defined
|
|
||||||
# This allows "cascading" if rules to be applied
|
|
||||||
if value.startswith('#') and 'color' not in self.rules:
|
|
||||||
self.rules['color'] = value
|
|
||||||
else:
|
|
||||||
if 'italic' in value and 'font-style' not in self.rules:
|
|
||||||
self.rules['font-style'] = 'italic'
|
|
||||||
if 'underline' in value and 'text-decoration' not in self.rules:
|
|
||||||
self.rules['text-decoration'] = 'underline'
|
|
||||||
if 'bold' in value and 'font-weight' not in self.rules:
|
|
||||||
self.rules['font-weight'] = 'bold'
|
|
||||||
|
|
||||||
# Helper method for creating the css rule
|
|
||||||
def _join_attr(self):
|
|
||||||
temp = []
|
|
||||||
if(len(self.rules) == 0):
|
|
||||||
return ''
|
|
||||||
for key in self.rules:
|
|
||||||
temp.append(key + ': ' + self.rules[key])
|
|
||||||
return '; '.join(temp) + ';'
|
|
||||||
|
|
||||||
def toString(self):
|
|
||||||
joined = self._join_attr()
|
|
||||||
if joined:
|
|
||||||
return "%s .%s { %s }" % (Style.PREFIX, self.name, joined)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
# Crappy xml parsing function for getting the
|
|
||||||
# colors and font styles from colortheme file
|
|
||||||
|
|
||||||
|
|
||||||
def get_settings(color_scheme_content):
|
|
||||||
settings = defaultdict(lambda: [])
|
|
||||||
dom = parseString(color_scheme_content)
|
|
||||||
arr = dom.getElementsByTagName('array')[0]
|
|
||||||
editor_cfg = arr.getElementsByTagName('dict')[0].getElementsByTagName('dict')[0]
|
|
||||||
editor_vals = editor_cfg.getElementsByTagName('string')
|
|
||||||
background = editor_vals[0].firstChild.nodeValue
|
|
||||||
text_color = editor_vals[2].firstChild.nodeValue
|
|
||||||
settings['editor_bg'] = background
|
|
||||||
settings['text_color'] = text_color
|
|
||||||
for node in arr.childNodes:
|
|
||||||
if node.nodeName == "dict":
|
|
||||||
try:
|
|
||||||
setting = node.getElementsByTagName('string')[1].firstChild.nodeValue
|
|
||||||
attrs = []
|
|
||||||
values = node.getElementsByTagName('dict')[0].getElementsByTagName('string')
|
|
||||||
for v in values:
|
|
||||||
if v.firstChild:
|
|
||||||
a = str(v.firstChild.nodeValue).strip()
|
|
||||||
attrs.append(a)
|
|
||||||
for s in setting.split(', '):
|
|
||||||
settings[s] = attrs
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
return settings
|
|
||||||
|
|
||||||
|
|
||||||
def pygments_from_theme(color_scheme_content):
|
|
||||||
settings = get_settings(color_scheme_content)
|
|
||||||
styles = []
|
|
||||||
|
|
||||||
#Generic
|
|
||||||
styles.append(Style('ge', ['italic']))
|
|
||||||
styles.append(Style('gs', ['bold']))
|
|
||||||
|
|
||||||
# Comments
|
|
||||||
styles.append(Style('c', settings['comment']))
|
|
||||||
styles.append(Style('cp', settings['comment']))
|
|
||||||
styles.append(Style('c1', settings['comment']))
|
|
||||||
styles.append(Style('cs', settings['comment']))
|
|
||||||
styles.append(Style('cm', settings['comment.block'], settings['comment']))
|
|
||||||
|
|
||||||
# Constants
|
|
||||||
styles.append(Style('m', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant']))
|
|
||||||
styles.append(Style('mf', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant']))
|
|
||||||
styles.append(Style('mi', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant']))
|
|
||||||
styles.append(Style('mo', settings['constant.numeric'], settings['constant.other'], settings['constant'], settings['support.constant']))
|
|
||||||
styles.append(Style('se', settings['constant.language'], settings['constant.other'], settings['constant'], settings['support.constant']))
|
|
||||||
styles.append(Style('kc', settings['constant.language'], settings['constant.other'], settings['constant'], settings['support.constant']))
|
|
||||||
|
|
||||||
#Keywords
|
|
||||||
styles.append(Style('k', settings['entity.name.type'], settings['support.type'], settings['keyword']))
|
|
||||||
styles.append(Style('kd', settings['storage.type'], settings['storage']))
|
|
||||||
styles.append(Style('kn', settings['support.function.construct'], settings['keyword.control'], settings['keyword']))
|
|
||||||
styles.append(Style('kt', settings['entity.name.type'], settings['support.type'], settings['support.constant']))
|
|
||||||
|
|
||||||
#String
|
|
||||||
styles.append(Style('settings', settings['string.quoted.double'], settings['string.quoted'], settings['string']))
|
|
||||||
styles.append(Style('sb', settings['string.quoted.double'], settings['string.quoted'], settings['string']))
|
|
||||||
styles.append(Style('sc', settings['string.quoted.single'], settings['string.quoted'], settings['string']))
|
|
||||||
styles.append(Style('sd', settings['string.quoted.double'], settings['string.quoted'], settings['string']))
|
|
||||||
styles.append(Style('s2', settings['string.quoted.double'], settings['string.quoted'], settings['string']))
|
|
||||||
styles.append(Style('sh', settings['string']))
|
|
||||||
styles.append(Style('si', settings['string.interpolated'], settings['string']))
|
|
||||||
styles.append(Style('sx', settings['string.other'], settings['string']))
|
|
||||||
styles.append(Style('sr', settings['string.regexp'], settings['string']))
|
|
||||||
styles.append(Style('s1', settings['string.quoted.single'], settings['string']))
|
|
||||||
styles.append(Style('ss', settings['string']))
|
|
||||||
|
|
||||||
#Name
|
|
||||||
styles.append(Style('na', settings['entity.other.attribute-name'], settings['entity.other']))
|
|
||||||
styles.append(Style('bp', settings['variable.language'], settings['variable']))
|
|
||||||
styles.append(Style('nc', settings['entity.name.class'], settings['entity.other.inherited-class'], settings['support.class']))
|
|
||||||
styles.append(Style('no', settings['constant.language'], settings['constant']))
|
|
||||||
styles.append(Style('nd', settings['entity.name.class']))
|
|
||||||
styles.append(Style('ne', settings['entity.name.class']))
|
|
||||||
styles.append(Style('nf', settings['entity.name.function'], settings['support.function']))
|
|
||||||
styles.append(Style('nt', settings['entity.name.tag'], settings['keyword']))
|
|
||||||
styles.append(Style('nv', settings['variable'], [settings['text_color']]))
|
|
||||||
styles.append(Style('vc', settings['variable.language']))
|
|
||||||
styles.append(Style('vg', settings['variable.language']))
|
|
||||||
styles.append(Style('vi', settings['variable.language']))
|
|
||||||
|
|
||||||
#Operator
|
|
||||||
styles.append(Style('ow', settings['keyword.operator'], settings['keyword.operator'], settings['keyword']))
|
|
||||||
styles.append(Style('o', settings['keyword.operator'], settings['keyword.operator'], settings['keyword']))
|
|
||||||
|
|
||||||
# Text
|
|
||||||
styles.append(Style('n', [settings['text_color']]))
|
|
||||||
styles.append(Style('nl', [settings['text_color']]))
|
|
||||||
styles.append(Style('nn', [settings['text_color']]))
|
|
||||||
styles.append(Style('nx', [settings['text_color']]))
|
|
||||||
styles.append(Style('bp', settings['variable.language'], settings['variable'], [settings['text_color']]))
|
|
||||||
styles.append(Style('p', [settings['text_color']]))
|
|
||||||
|
|
||||||
css = '{} {{ background-color: {}; color: {}; }}\n'.format(Style.PREFIX, settings['editor_bg'], settings['text_color'])
|
|
||||||
for st in styles:
|
|
||||||
css_style = st.toString()
|
|
||||||
if css_style:
|
|
||||||
css += css_style + '\n'
|
|
||||||
|
|
||||||
return css
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
args = sys.argv[1:]
|
|
||||||
if len(args) < 1:
|
|
||||||
print("Please provide the .tmTheme file!", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(pygments_from_theme(args[0]))
|
|
||||||
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")
|
||||||
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);
|
||||||
|
}
|
||||||
@ -1,10 +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'
|
|
||||||
ON_OPEN = 'markdown_live_preview_on_open'
|
|
||||||
LAST_UPDATE = 'markdonw_live_preview_last_run'
|
|
||||||
16
test.md
@ -1,16 +0,0 @@
|
|||||||
```python
|
|
||||||
import this
|
|
||||||
|
|
||||||
if you.are('new'):
|
|
||||||
print('Welcome!')
|
|
||||||
if you.are('brand new'):
|
|
||||||
print("You'll see, python's just awesome")
|
|
||||||
else:
|
|
||||||
print('Hello!')
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>test</li>
|
|
||||||
</ol>
|
|
||||||
38
todo
@ -1,38 +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
|
|
||||||
☐ optimize usage of BeautifulSoup
|
|
||||||
|
|
||||||
Long:
|
|
||||||
☐ support anchor (TOC) @big
|
|
||||||
|
|
||||||
Unknown:
|
|
||||||
☐ properly convert tmtheme to css
|
|
||||||
|
|
||||||
___________________
|
|
||||||
Archive:
|
|
||||||
✔ add settings to keep md view open #13 @done Sat 11 Feb 2017 at 09:10 @project(Fast)
|
|
||||||
✔ 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
|
|
||||||
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
|
||||||