Compare commits

...

21 Commits

Author SHA1 Message Date
79c785176f 🐛 check if color scheme is valid before loading 2018-08-04 10:59:39 +10:00
82ad98085f 🐛 don't copy to html to clipboard. Fix #36 2017-09-29 07:12:55 +10:00
dd184c5fdd Merge pull request #35 from sepich/master
Fix for #33
Specify encoding when opening resource (`utf-8`)
2017-09-18 08:36:22 +10:00
c334c49592 Fix for #33 2017-09-17 21:32:57 +03:00
41c28e2b24 use option 'strike' although no-sublime-support 2017-07-29 14:34:57 +10:00
e1eb17fe96 🔀 Merge branch 'master' of github.com:math2001/MarkdownLivePreview 2017-03-22 18:35:59 +11:00
823d22afee 🎨 on_modified → async; use timeout only if needed 2017-03-22 18:32:42 +11:00
91f4bc5052 🐛 timeout to update preview 2017-03-22 17:53:07 +11:00
7126c0e090 use setting for delay before updating preview 2017-03-22 17:40:20 +11:00
6a3dd6ac2f update preview at the most once every 0.8s 2017-03-22 17:30:32 +11:00
1542e5e898 🐛 in functions.py@get_resource 2017-03-18 14:39:17 +11:00
05c471b5d9 add get_resource function
Allow to load the last version of a resource when extracted
2017-03-07 08:28:56 +11:00
76f580ba29 open color scheme as resource #25
allows the color scheme to be in a packaged package
2017-03-07 08:24:19 +11:00
119acbb092 update todo 2017-02-18 08:50:40 +11:00
7c4354fb2e Local image loading now working. #19 2017-02-18 08:13:45 +11:00
b93aea6698 Revert "add pygments_from_theme"
This reverts commit 40a563fb1ee3c97f869741324534ab5dfe62a725.
2017-02-11 09:19:28 +11:00
b3fb5779d3 update todo 2017-02-11 09:16:01 +11:00
7bdda5f5c7 minify css in functions.py@get_style 2017-02-11 09:10:03 +11:00
7257cb467e Highlight code blocks with pygments #18 2017-02-11 08:52:42 +11:00
40a563fb1e add pygments_from_theme 2017-02-10 17:32:15 +11:00
3e0d6ad265 *add* user css rules
instead of replacing the default ones
2017-02-09 19:13:46 +11:00
11 changed files with 244 additions and 36 deletions

View File

@ -12,5 +12,8 @@
// Choose what to do with YAML/TOML (---/+++ respectively) headers // Choose what to do with YAML/TOML (---/+++ respectively) headers
// Valid values: "wrap_in_pre", "remove". // 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
} }

View File

@ -8,6 +8,7 @@ from html.parser import HTMLParser
from .lib import markdown2 as md2 from .lib import markdown2 as md2
from .lib.pre_tables import pre_tables from .lib.pre_tables import pre_tables
from .escape_amp import * from .escape_amp import *
from .functions import * from .functions import *
from .setting_names import * from .setting_names import *
@ -16,20 +17,11 @@ from random import randint as rnd
__folder__ = os.path.dirname(__file__) __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 # used to store the phantom's set
windows_phantom_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): def create_preview(window, file_name):
preview = window.new_file() preview = window.new_file()
@ -40,23 +32,17 @@ def create_preview(window, file_name):
return preview return preview
def get_style(): def markdown2html(md, basepath, color_scheme):
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): # removes/format the YAML/TOML header.
# removes/format the header.
md = manage_header(md, get_settings().get('header_action')) 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 # the option no-code-highlighting does not exists in the official version of markdown2 for now
# I personaly edited the file (markdown2.py:1743) # 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 # tables aren't supported by the Phantoms
# This function transforms them into aligned ASCII tables and displays them in a <pre> block # 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 # Phantoms have problem with images size when they're loaded from an url/path
# So, the solution is to convert them to base64 # 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... # BeautifulSoup uses the <br/> but the sublime phantoms do not support them...
html = html.replace('<br/>', '<br />').replace('<hr/>', '<hr />') html = html.replace('<br/>', '<br />').replace('<hr/>', '<hr />')
sublime.set_clipboard(html) # print
return html return html
def show_html(md_view, preview): def show_html(md_view, preview):
global windows_phantom_set 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(), phantom_set = windows_phantom_set.setdefault(preview.window().id(),
sublime.PhantomSet(preview, 'markdown_live_preview')) sublime.PhantomSet(preview, 'markdown_live_preview'))

View File

@ -2,6 +2,7 @@
import sublime import sublime
import sublime_plugin import sublime_plugin
import time
from .MLPApi import * from .MLPApi import *
from .setting_names import * from .setting_names import *
@ -46,6 +47,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
def update(self, view): def update(self, view):
vsettings = view.settings() 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): if not vsettings.get(PREVIEW_ENABLED):
return return
id = vsettings.get(PREVIEW_ID) id = vsettings.get(PREVIEW_ID)
@ -58,10 +64,14 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
show_html(view, preview) show_html(view, preview)
return 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 if not is_markdown_view(view): # faster than getting the settings
return return
self.update(view) 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): def on_window_command(self, window, command, args):
if command == 'close' and window.settings().get(PREVIEW_WINDOW): if command == 'close' and window.settings().get(PREVIEW_WINDOW):
@ -78,7 +88,6 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
'.hidden-tmLanguage' '.hidden-tmLanguage'
and not any(filter(lambda window: window.settings().get(PREVIEW_WINDOW) is True, and not any(filter(lambda window: window.settings().get(PREVIEW_WINDOW) is True,
sublime.windows()))): sublime.windows()))):
# print("MarkdownLivePreview.py:81", 'open window')
sublime.run_command('new_markdown_live_preview') sublime.run_command('new_markdown_live_preview')

View File

@ -25,7 +25,7 @@ code {
border-radius: 3px; border-radius: 3px;
} }
pre { div.codehilite {
display: block; display: block;
margin-top: 20px; margin-top: 20px;
background-color: var(--light-bg); background-color: var(--light-bg);

View File

@ -1,7 +1,8 @@
{ {
"*": { "*": {
"*": [ "*": [
"bs4" "bs4",
"pygments"
] ]
} }
} }

View File

@ -1,6 +1,6 @@
--- ---
title: Demo title: Demo
description: Preview your markdown right in Sublime Text! description: Preview your markdown right in Sublime Text!
hope: You'll enjoy using it! hope: You'll enjoy using it!
--- ---
@ -10,7 +10,7 @@ hope: You'll enjoy using it!
And `<!-- vicious ones ;) -->` And `<!-- vicious ones ;) -->`
Some `inline code` with *italic* and **bold** text. Some `inline code` with *italic*, **bold** text, and ~~strike through~~.
```python ```python
import this import this

View File

@ -4,13 +4,17 @@ import os.path
import sublime import sublime
import re import re
from .image_manager import ImageManager from .image_manager import ImageManager
from .lib.pygments_from_theme import pygments_from_theme
from bs4 import BeautifulSoup, Comment as html_comment from bs4 import BeautifulSoup, Comment as html_comment
def plugin_loaded(): def plugin_loaded():
global error404, loading global error404, loading, DEFAULT_STYLE, USER_STYLE_FILE
loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt') loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt')
error404 = sublime.load_resource('Packages/MarkdownLivePreview/404.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) MATCH_YAML_HEADER = re.compile(r'^([\-\+])\1{2}\n(?P<content>.+)\n\1{3}\n', re.DOTALL)
def strip_html_comments(html): def strip_html_comments(html):
@ -102,12 +106,34 @@ def get_view_from_id(window, id):
def get_settings(): def get_settings():
return sublime.load_settings('MarkdownLivePreview.sublime-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): def pre_with_br(html):
"""Because the phantoms of sublime text does not support <pre> blocks """Because the phantoms of sublime text does not support <pre> blocks
this function replaces every \n with a <br> in a <pre>""" this function replaces every \n with a <br> in a <pre>"""
soup = BeautifulSoup(html, 'html.parser') soup = BeautifulSoup(html, 'html.parser')
for pre in soup.find_all('pre'): for pre in soup.find_all('pre'):
code = pre.find('code') code = pre.find('code')
code.replaceWith(BeautifulSoup(''.join(str(node) for node in pre.contents) \ code.replace_with(_pre_with_spaces(code))
.replace('\n', '<br/>').replace(' ', '<i class="space">.</i>'), 'html.parser'))
return str(soup) 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
View 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]))

View File

@ -7,3 +7,4 @@ IS_HIDDEN = 'is_hidden_markdown_live_preview'
MD_VIEW_ID = 'markdown_live_preview_md_id' MD_VIEW_ID = 'markdown_live_preview_md_id'
PREVIEW_WINDOW = 'markdown_live_preview_window' PREVIEW_WINDOW = 'markdown_live_preview_window'
ON_OPEN = 'markdown_live_preview_on_open' ON_OPEN = 'markdown_live_preview_on_open'
LAST_UPDATE = 'markdonw_live_preview_last_run'

16
test.md Normal file
View 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!')
```
![img](docs/imgs/syntax-specific-settings.png)
<ol>
<li>test</li>
</ol>

View File

@ -1,19 +1,20 @@
Fast: Fast:
☐ cache image in object when used, so that it's faster @needsTest ☐ cache image in object when used, so that it's faster @needsTest
☐ add settings to keep md view open #13
Medium: Medium:
☐ auto refresh preview if loading images ☐ auto refresh preview if loading images
☐ use alt attribute for 404 error ☐ use alt attribute for 404 error
☐ optimize usage of BeautifulSoup
Long: Long:
☐ support anchor (TOC) @big ☐ support anchor (TOC) @big
Unknown: Unknown:
☐ properly convert tmtheme to css
___________________ ___________________
Archive: 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) ✔ 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) ✘ 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) ✔ sync scroll @needsUpdate(because of images) @done Sun 22 Jan 2017 at 18:39 @project(Fast)