Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d4c477749c | |||
| 79c785176f | |||
| 82ad98085f | |||
| dd184c5fdd | |||
| c334c49592 | |||
| 41c28e2b24 | |||
| e1eb17fe96 | |||
| 823d22afee | |||
| 91f4bc5052 | |||
| 7126c0e090 | |||
| 6a3dd6ac2f | |||
| 1542e5e898 | |||
| 05c471b5d9 | |||
| 76f580ba29 | |||
| 119acbb092 | |||
| 7c4354fb2e | |||
| b93aea6698 | |||
| b3fb5779d3 | |||
| 7bdda5f5c7 | |||
| 7257cb467e | |||
| 40a563fb1e | |||
| 3e0d6ad265 | |||
| f65a068b4e |
@ -12,5 +12,8 @@
|
||||
|
||||
// Choose what to do with YAML/TOML (---/+++ respectively) headers
|
||||
// Valid values: "wrap_in_pre", "remove".
|
||||
"header_action": "wrap_in_pre"
|
||||
"header_action": "wrap_in_pre",
|
||||
|
||||
// Wait at least the specified *seconds* before updating the preview.
|
||||
"update_preview_every": 0
|
||||
}
|
||||
|
||||
30
MLPApi.py
30
MLPApi.py
@ -8,6 +8,7 @@ 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 *
|
||||
@ -16,20 +17,11 @@ 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
|
||||
if os.path.exists(os.path.join(__folder__, 'default.css')):
|
||||
with open(os.path.join(__folder__, 'default.css')) as fp:
|
||||
DEFAULT_STYLE_FILE = fp.read()
|
||||
else:
|
||||
DEFAULT_STYLE_FILE = sublime.load_resource('Packages/MarkdownLivePreview/default.css')
|
||||
|
||||
def create_preview(window, file_name):
|
||||
preview = window.new_file()
|
||||
|
||||
@ -40,23 +32,17 @@ def create_preview(window, file_name):
|
||||
|
||||
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 markdown2html(md, basepath, color_scheme):
|
||||
|
||||
def markdown2html(md, basepath):
|
||||
# removes/format the header.
|
||||
# removes/format the YAML/TOML header.
|
||||
md = manage_header(md, get_settings().get('header_action'))
|
||||
|
||||
html = '<style>\n{}\n</style>\n'.format(get_style())
|
||||
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', 'no-code-highlighting', 'tables'])
|
||||
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
|
||||
@ -78,18 +64,16 @@ def markdown2html(md, basepath):
|
||||
|
||||
# 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=os.path.dirname(basepath))
|
||||
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) # print
|
||||
|
||||
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()))
|
||||
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'))
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import sublime
|
||||
import sublime_plugin
|
||||
import time
|
||||
|
||||
from .MLPApi import *
|
||||
from .setting_names import *
|
||||
@ -46,6 +47,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
|
||||
def update(self, view):
|
||||
vsettings = view.settings()
|
||||
now = time.time()
|
||||
|
||||
if now - vsettings.get(LAST_UPDATE, 0) < get_settings().get('update_preview_every'):
|
||||
return
|
||||
vsettings.set(LAST_UPDATE, now)
|
||||
if not vsettings.get(PREVIEW_ENABLED):
|
||||
return
|
||||
id = vsettings.get(PREVIEW_ID)
|
||||
@ -58,10 +64,14 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
show_html(view, preview)
|
||||
return view, preview
|
||||
|
||||
def on_modified(self, view):
|
||||
def on_modified_async(self, view):
|
||||
if not is_markdown_view(view): # faster than getting the settings
|
||||
return
|
||||
delay = get_settings().get('update_preview_every')
|
||||
if not delay:
|
||||
self.update(view)
|
||||
else:
|
||||
sublime.set_timeout(lambda: self.update(view), delay * 1000)
|
||||
|
||||
def on_window_command(self, window, command, args):
|
||||
if command == 'close' and window.settings().get(PREVIEW_WINDOW):
|
||||
@ -71,12 +81,13 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
def on_activated_async(self, view):
|
||||
vsettings = view.settings()
|
||||
|
||||
if (is_markdown_view(view)
|
||||
and get_settings().get('markdown_live_preview_on_open')
|
||||
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'):
|
||||
'.hidden-tmLanguage'
|
||||
and not any(filter(lambda window: window.settings().get(PREVIEW_WINDOW) is True,
|
||||
sublime.windows()))):
|
||||
sublime.run_command('new_markdown_live_preview')
|
||||
|
||||
|
||||
|
||||
@ -2,6 +2,12 @@
|
||||
|
||||
This is a sublime text **3** plugin that allows you to preview your markdown instantly *in* it!
|
||||
|
||||
## Unmaintained
|
||||
|
||||
I am now using vim. I don't have the energy or the time to maintain this plugin anymore.
|
||||
|
||||
If anyone is interested in maintaining it, fork it, and submit a PR to package control to make it point to your fork.
|
||||
|
||||
### Dependencies
|
||||
|
||||
**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.
|
||||
|
||||
@ -25,7 +25,7 @@ code {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
div.codehilite {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
background-color: var(--light-bg);
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{
|
||||
"*": {
|
||||
"*": [
|
||||
"bs4"
|
||||
"bs4",
|
||||
"pygments"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ hope: You'll enjoy using it!
|
||||
|
||||
And `<!-- vicious ones ;) -->`
|
||||
|
||||
Some `inline code` with *italic* and **bold** text.
|
||||
Some `inline code` with *italic*, **bold** text, and ~~strike through~~.
|
||||
|
||||
```python
|
||||
import this
|
||||
32
functions.py
32
functions.py
@ -4,13 +4,17 @@ 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
|
||||
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):
|
||||
@ -102,12 +106,34 @@ def get_view_from_id(window, id):
|
||||
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.replaceWith(BeautifulSoup(''.join(str(node) for node in pre.contents) \
|
||||
.replace('\n', '<br/>').replace(' ', '<i class="space">.</i>'), 'html.parser'))
|
||||
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 and color_scheme.endswith('.tmTheme'):
|
||||
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)
|
||||
|
||||
167
lib/pygments_from_theme.py
Normal file
167
lib/pygments_from_theme.py
Normal file
@ -0,0 +1,167 @@
|
||||
# -*- 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]))
|
||||
@ -6,3 +6,5 @@ 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
Normal file
16
test.md
Normal file
@ -0,0 +1,16 @@
|
||||
```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>
|
||||
@ -1,19 +1,20 @@
|
||||
Fast:
|
||||
☐ cache image in object when used, so that it's faster @needsTest
|
||||
☐ add settings to keep md view open #13
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user