Compare commits

..

37 Commits

Author SHA1 Message Date
cda4532833 update messages 2017-01-09 12:13:36 +11:00
ff8c94bda5 add clear cache command 2017-01-09 12:04:12 +11:00
ded9c28096 fix loading image with relative paths 2017-01-08 19:22:58 +11:00
0e6660a331 add settings for the preview.
Need to update the README
2017-01-08 17:44:17 +11:00
0143428114 update README installation part 2017-01-08 16:17:46 +11:00
f48ef63956 update tasks 2017-01-08 15:54:32 +11:00
a4e670de43 clean code/files 2017-01-08 15:40:25 +11:00
d466a29cd4 the stylesheet's back 2017-01-08 15:30:49 +11:00
0f0e53ff34 minor updates 2017-01-08 14:26:25 +11:00
30ac30082f update README 2017-01-08 14:19:09 +11:00
e4e7c44c3c clean unused variables/import 2017-01-08 13:51:50 +11:00
75a8cf53f9 working, needs few improvement 2017-01-08 12:30:20 +11:00
3dcaed0ede small optimisation, working 2017-01-07 16:09:36 +11:00
c605ffb3db added package control messages 2017-01-07 12:36:17 +11:00
aa5fd4faf8 set main callback async (should help #6) 2017-01-07 09:11:30 +11:00
613e3fb1b2 support loading files from resource #5 2017-01-06 19:25:51 +11:00
057f770859 fix loading 404 from resource 2017-01-06 18:49:13 +11:00
76d56deff6 quick fix for #5 2017-01-06 17:21:32 +11:00
d2863e4a43 use timeout (1s) only when needed
The timeout are needed, at least on my computer, because ST crashes
otherwise
2017-01-05 16:05:27 +11:00
ed57d2813a update timeout (not crashing anymore) 2017-01-05 14:26:40 +11:00
b4038c3575 clean MarkdownLivePreview.py 2017-01-05 09:22:51 +11:00
fd633e0bc0 focus the md_view back 2017-01-05 09:18:09 +11:00
4a3caf30bb auto create preview when modified (and enabled) 2017-01-04 19:49:49 +11:00
28a7274b05 really basic but working base 2017-01-04 19:46:36 +11:00
6ad9e79926 preview markdown working 2017-01-04 12:24:05 +11:00
ac09c523e7 code formatting 2017-01-04 10:29:23 +11:00
3ad29ede37 hide/show preview on focus working 2017-01-04 10:26:58 +11:00
576956a8d1 restart from scratch 2017-01-03 16:22:25 +11:00
93a04733da better handling of 404. Still not perfect 2017-01-02 18:03:24 +11:00
6a0267fb3b minor fixes (see .tasks) 2017-01-02 16:45:35 +11:00
f52bf98470 remove unused function, code formating 2017-01-02 16:29:33 +11:00
5a3b1a7f81 make CSS monoline 2017-01-02 16:13:11 +11:00
182862ecce loading images from internet working 2017-01-02 16:07:15 +11:00
518f6f1ed4 image loader working but messy 2017-01-02 12:15:40 +11:00
b44151ed69 add 404 error 2017-01-01 20:58:05 +11:00
e4a5ea886a local image working with base64 #3
Need to be tested for a while, but looks good
2017-01-01 19:45:37 +11:00
e6ea13f0a2 fix escaping of the & (helping #2) 2016-12-10 08:08:05 +11:00
30 changed files with 661 additions and 276 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
Thumbs.db Thumbs.db
__pycache__/
cache.txt

View File

@ -1,10 +1,7 @@
[ [
{ {
"keys": ["alt+m"], "keys": ["alt+m"],
"command": "toggle_setting", "command": "new_markdown_live_preview",
"args": {
"setting": "markdown_preview_enabled"
},
"context": [ "context": [
{ {
"key": "selector", "key": "selector",

View File

@ -0,0 +1,14 @@
<?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>MarkdownLivePreview</string>
<key>patterns</key>
<array>
</array>
<key>scopeName</key>
<string>text.markdown-live-preview</string>
</dict>
</plist>

View File

@ -0,0 +1,10 @@
[
{
"caption": "MarkdownLivePreview: Edit Current File",
"command": "new_markdown_live_preview"
},
{
"caption": "MarkdownLivePreview: Clear Cache",
"command": "markdown_live_preview_clear_cache"
}
]

View File

@ -0,0 +1,3 @@
{
"load_from_internet_when_starts": ["http://", "https://"]
}

1
404.txt Normal file

File diff suppressed because one or more lines are too long

89
MLPApi.py Normal file
View File

@ -0,0 +1,89 @@
# -*- encoding: utf-8 -*-
import sublime
import sublime_plugin
import os.path
from html.parser import HTMLParser
from .lib import markdown2 as md2
from .escape_amp import *
from .functions import *
from .setting_names import *
from .image_manager import CACHE_FILE
__folder__ = os.path.dirname(__file__)
STYLE_FILE = os.path.join(os.path.dirname(__folder__), 'User',
'MarkdownLivePreview.css')
def plugin_loaded():
global DEFAULT_STYLE_FILE
DEFAULT_STYLE_FILE = sublime.load_resource('Packages/MarkdownLivePreview/'
'default.css')
def get_preview_name(md_view):
file_name = md_view.file_name()
name = md_view.name() \
or os.path.basename(file_name) if file_name else None \
or 'Untitled'
return name + ' - Preview'
def create_preview(window, file_name):
preview = window.new_file()
preview.set_name(get_preview_name(file_name))
preview.set_scratch(True)
preview.set_syntax_file('Packages/MarkdownLivePreview/.sublime/'
'MarkdownLivePreview.hidden-tmLanguage')
return preview
def get_style():
content = ''.join([line.strip() + ' ' for line in DEFAULT_STYLE_FILE.splitlines()])
return content + "pre code .space {color: var(--light-bg)}"
def show_html(md_view, preview):
html = []
html.append('<style>\n{}\n</style>'.format(get_style()))
html.append(pre_with_br(md2.markdown(get_view_content(md_view),
extras=['fenced-code-blocks',
'no-code-highlighting'])))
# the option no-code-highlighting does not exists
# in the official version of markdown2 for now
# I personaly edited the file (markdown2.py:1743)
html = '\n'.join(html)
html = html.replace('&nbsp;', '&nbspespace;') # save where are the spaces
html = HTMLParser().unescape(html)
html = escape_amp(html)
# exception, again, because <pre> aren't supported by the phantoms
html = html.replace('&nbspespace;', '<i class="space">.</i>')
html = replace_img_src_base64(html, basepath=os.path.dirname(
md_view.file_name()))
preview.erase_phantoms('markdown_preview')
preview.add_phantom('markdown_preview',
sublime.Region(-1),
html,
sublime.LAYOUT_BLOCK,
lambda href: sublime.run_command('open_url',
{'url': href}))
return
# set viewport position
# 0 < y < 1
y = md_view.text_to_layout(md_view.sel()[0].begin())[1] / md_view.layout_extent()[1]
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
vector[1] = mini(vector[1], 0)
vector[1] += preview.line_height()
preview.set_viewport_position(vector, animate=False)
def clear_cache():
"""Removes the cache file"""
os.remove(CACHE_FILE)

97
MarkdownLivePreview.py Normal file
View File

@ -0,0 +1,97 @@
# -*- encoding: utf-8 -*-
import sublime
import sublime_plugin
from .MLPApi import *
from .setting_names import *
from .functions import *
class NewMarkdownLivePreviewCommand(sublime_plugin.ApplicationCommand):
def run(self):
"""Inspired by the edit_settings command"""
current_view = sublime.active_window().active_view()
file_name = current_view.file_name()
current_view.close()
if file_name is None:
return sublime.error_message('Not supporting unsaved file for now')
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)
md_view = self.window.open_file(file_name)
mdsettings = md_view.settings()
mdsettings.set(PREVIEW_ENABLED, True)
mdsettings.set(PREVIEW_ID, preview.id())
def is_enabled(self):
return is_markdown_view(sublime.active_window().active_view())
class MarkdownLivePreviewListener(sublime_plugin.EventListener):
def update(self, view):
if not is_markdown_view(view): # faster than getting the settings
return
vsettings = view.settings()
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)
return view, preview
def on_modified(self, view):
self.update(view)
def on_window_command(self, window, command, args):
if command == 'close' and window.settings().get(PREVIEW_WINDOW):
return 'close_window', {}
def on_load_async(self, view):
try:
md_view, preview = self.update(view)
except TypeError:
return
window = preview.window()
psettings = preview.settings()
show_tabs = psettings.get('show_tabs')
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:
window.set_tabs_visible(show_tabs)
if show_minimap is not None:
window.set_minimap_visible(show_minimap)
if show_status_bar is not None:
window.set_status_bar_visible(show_status_bar)
if show_sidebar is not None:
window.set_sidebar_visible(show_sidebar)
if show_menus is not None:
window.set_menu_visible(show_menus)
class MarkdownLivePreviewClearCacheCommand(sublime_plugin.ApplicationCommand):
def run(self):
clear_cache()

View File

@ -1,9 +0,0 @@
[
{
"caption": "MarkdownLivePreview: Toggle",
"command": "toggle_setting",
"args": {
"setting": "markdown_preview_enabled"
}
}
]

35
MarkdownLivePreview.tasks Normal file
View File

@ -0,0 +1,35 @@
Fast:
☐ sync scroll @needsUpdate(because of images)
☐ cache image in object when used, so that it's faster @needsTest
☐ add clear cache command
☐ update README for settings in view
Medium:
☐ auto refresh preview if loading images
☐ use alt attribute for 404 error
☐ use MarkdownLivePreview syntax, so we can use syntax's settings
☐ listen for settings to change
Long:
☐ fix #4 @high
☐ support hanchor (TOC) @big
Unknown:
☐ check how many times is the show_html function called
___________________
Archive:
✘ 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

View File

@ -8,27 +8,22 @@ This is a sublime text **3** plugin that allows you to preview your markdown ins
## Installation ## Installation
Although MarkdownLivePreview is not available on the default channel of [PackageControl](http://packagecontrol.io), you can still use it to download this little package. MarkdownLivePreview is available on the default channel of [PackageControl](http://packagecontrol.io), which means you just have to
1. Open the command palette (`ctrl+shift+p`)
2. Search for: `Package Control: Add Repository`
3. Enter in the input at the bottom of ST the path to this repo: <https://github.com/math2001/MarkdownLivePreview> (tip: just drag the link in)
4. Hit <kbd>enter</kbd>
What this does is simply adding this repo to the list of packages you get when you install a package using PC.
So, as you probably understood, now you just need to install MarkdownLivePreview as if it was available on the default channel:
1. Open the command palette (`ctrl+shift+p`) 1. Open the command palette (`ctrl+shift+p`)
2. Search for: `Package Control: Install Package` 2. Search for: `Package Control: Install Package`
3. Search for: `MarkdownLivePreview` 3. Search for: `MarkdownLivePreview`
4. hit <kbd>enter</kbd> 4. hit <kbd>enter</kbd>
Done! to have MarkdownLivePreview working on your computer. Cool right? You can [thank package control](https://packagecontrol.io/say_thanks) for this.
### Usage ### Usage
Sometimes, you just want to open a markdown file to edit it quickly, you don't care about the preview, and even worse, **you don't want it**. So, if you want to have the preview, press `alt+m`, edit your file, and you'll get a nice preview. 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.
It will open a new window, with only your markdown file, with the preview. Once your 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*
### In dev ### In dev
@ -45,3 +40,7 @@ It is possible to set your own css. But, be carefull, you have to respect [those
### How to open the [README](http://github.com/math2001/MarkdownLivePreview/README.md) ### How to open the [README](http://github.com/math2001/MarkdownLivePreview/README.md)
Some of the package add a command in the menus, others in the command palette, or other nowhere. None of those options are really good, especially the last one on ST3 because the packages are compressed. But, fortunately, there is plugin that exists and **will solve this problem** for us (and he has a really cute name, don't you think?): [ReadmePlease](https://packagecontrol.io/packages/ReadmePlease). 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

39
default.css Normal file
View File

@ -0,0 +1,39 @@
html {
--light-bg: color(var(--background) blend(#999 85%));
}
body {
padding:10px;
padding-top: 0px;
font-family: "Open Sans", sans-serif;
background-color: var(--background);
font-size: 15px;
}
blockquote {
font-style: italic;
display: block;
margin-left: 30px;
border: 1px solid red;
}
code {
padding-left: 0.2rem;
padding-right: 0.2rem;
background-color: var(--light-bg);
margin: 0;
border-radius: 3px;
margin: 5px;
}
pre {
display: block;
margin-top: 20px;
line-height: 1.7;
background-color: var(--light-bg);
padding-left: 10px;
width: 100%;
border-radius: 3px;
}
pre code {
padding-left: 0;
}

20
devListener.py Normal file
View File

@ -0,0 +1,20 @@
# -*- encoding: utf-8 -*-
import sublime
import sublime_plugin
import os.path
class MLPDevListener(sublime_plugin.EventListener):
def on_post_save(self, view):
return
if not (os.path.dirname(__file__) in view.file_name() and
view.file_name().endswith('.py')):
return
sublime.run_command('reload_plugin', {
'main': os.path.join(sublime.packages_path(),
'MarkdownLivePreview', 'md_in_popup.py'),
'scripts': ['image_manager', 'functions', 'MLPApi',
'setting_names'],
'quiet': True
})

39
escape_amp.py Normal file
View File

@ -0,0 +1,39 @@
# -*- 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('&', '&amp;')
def escape_amp(text):
return RE_REPLACE_AMPERSAND.sub(replace, text)
def run_tests():
tests = [
['&amp;', '&amp;'],
['&amp', '&amp;amp'],
['&', '&amp;'],
['& &hello &bonjour;', '&amp; &amp;hello &bonjour;']
]
fails = 0
for i, (subject, result) in enumerate(tests):
if RE_REPLACE_AMPERSAND.sub(replace, subject) != result:
# CSW: ignore
print('TEST FAIL ({i}): {subject!r} escaped did not match {result!r}'.format(**locals()))
fails += 1
if fails == 0:
# CSW: ignore
print("SUCCESS: every tests ({}) passed successfully!".format(len(tests)))
else:
# CSW: ignore
print("{} test{} failed".format(fails, 's' if fails > 1 else ''))
if __name__ == '__main__':
run_tests()

102
functions.py Normal file
View File

@ -0,0 +1,102 @@
# -*- encoding: utf-8 -*-
import base64
import os.path
import sublime
import re
from .image_manager import ImageManager
def plugin_loaded():
global error404, loading
loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt')
error404 = sublime.load_resource('Packages/MarkdownLivePreview/404.txt')
def replace_img_src_base64(html, basepath):
"""Really messy, but it works (should be updated)"""
index = -1
tag_start = '<img src="'
shtml, html = html, list(html)
while True:
index = shtml.find(tag_start, index + 1)
if index == -1:
break
path, end = get_content_till(html, '"', start=index + len(tag_start))
if ''.join(path).startswith('data:image/'):
continue
if ''.join(path).startswith(tuple(get_settings().get('load_from_internet'
'_when_starts'))):
image = ImageManager.get(''.join(path))
image = image or loading
else:
# local image
path = ''.join(path)
path = os.path.join(basepath, path)
image = to_base64(path)
html[index+len(tag_start):end] = image
shtml = ''.join(html)
return ''.join(html)
def is_markdown_view(view):
return 'markdown' in view.scope_name(0)
def to_base64(path=None, content=None):
if path is None and content is None:
return error404
elif content is None and path is not None:
try:
with open(path, 'rb') as fp:
content = fp.read()
except (FileNotFoundError, OSError):
return error404
return 'data:image/png;base64,' + ''.join([chr(el) for el in list(base64.standard_b64encode(content))])
def md(*t, **kwargs):
sublime.message_dialog(kwargs.get('sep', '\n').join([str(el) for el in t]))
def sm(*t, **kwargs):
sublime.status_message(kwargs.get('sep', ' ').join([str(el) for el in t]))
def em(*t, **kwargs):
sublime.error_message(kwargs.get('sep', ' ').join([str(el) for el in t]))
def mini(val, min):
if val < min:
return min
return val
def get_content_till(string, char_to_look_for, start=0):
i = start
while i < len(string):
if string[i] == char_to_look_for:
return string[start:i], i
i += 1
def get_view_content(view):
return view.substr(sublime.Region(0, view.size()))
def get_view_from_id(window, id):
if not isinstance(id, int):
return
for view in window.views():
if view.id() == id:
return view
def get_settings():
return sublime.load_settings('MarkdownLivePreview.sublime-settings')
def pre_with_br(html):
"""Because the phantoms of sublime text does not support <pre> blocks
this function replaces every \n with a <br> in a <pre>"""
while True:
obj = re.search(r'<pre>(.*?)</pre>', html, re.DOTALL)
if not obj:
break
html = list(html)
html[obj.start(0):obj.end(0)] = '<pre >' + ''.join(html[obj.start(1):obj.end(1)]) \
.replace('\n', '<br>') \
.replace(' ', '&nbsp;') + '</pre>'
html = ''.join(html)
return html

98
image_manager.py Normal file
View File

@ -0,0 +1,98 @@
# -*- 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)

BIN
imgs/404-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

1
imgs/README.md Normal file
View File

@ -0,0 +1 @@
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)

BIN
imgs/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

View File

@ -1,3 +1,4 @@
# CSW: ignore
# -*- encoding: utf-8 -*- # -*- encoding: utf-8 -*-
#!/usr/bin/env python #!/usr/bin/env python

1
loading.txt Normal file
View File

@ -0,0 +1 @@
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=

View File

@ -1,200 +0,0 @@
import sublime
import sublime_plugin
from . import markdown2
import os.path
import re
from html.parser import HTMLParser
# Main sublime tools function
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
STYLE_FILE = os.path.join(sublime.packages_path(), 'User', 'MarkdownLivePreview.css')
def get_style():
content = None
if os.path.exists(STYLE_FILE):
with open(STYLE_FILE) as fp:
content = fp.read()
return content
if not content:
content = """
html {
--light-bg: color(var(--background) blend(#999 85%))
}
body {
padding:10px;
padding-top: 0px;
font-family: "Open Sans", sans-serif;
background-color: var(--background);
font-size: 15px;
}
blockquote {
font-style: italic;
display: block;
margin-left: 30px;
border: 1px solid red;
}
code {
padding-left: 0.2rem;
padding-right: 0.2rem;
background-color: var(--light-bg);
margin: 0;
border-radius: 3px;
margin: 5px;
}
pre {
display: block;
margin-top: 20px;
line-height: 1.7;
background-color: var(--light-bg);
padding-left: 10px;
width: 100%;
border-radius: 3px;
}
pre code {
padding-left: 0;
}
"""
return content + "pre code .space {color: var(--light-bg)}"
def pre_with_br(html):
"""Because the phantoms of sublime text does not support <pre> blocks
this function replaces every \n with a <br> in a <pre>"""
while True:
obj = re.search(r'<pre>(.*?)</pre>', html, re.DOTALL)
if not obj:
break
html = list(html)
html[obj.start(0):obj.end(0)] = '<pre >' + ''.join(html[obj.start(1):obj.end(1)]) \
.replace('\n', '<br>') \
.replace(' ', '&nbsp;') + '</pre>'
html = ''.join(html)
return html
def close_preview(md_view_settings, preview):
preview.close()
md_view_settings.erase('markdown_preview_id')
md_view_settings.erase('markdown_preview_enabled')
def create_preview(window, md_view):
focus_group, focus_view = window.get_view_index(md_view)
preview = window.new_file()
window.run_command('new_pane') # move the preview to a new group
preview.set_name(os.path.basename(md_view.file_name()) + ' - Preview')
preview_settings = preview.settings()
preview_settings.set('gutter', False)
preview_settings.set('is_markdown_preview', True)
preview_settings.set('markdown_view_id', md_view.id())
md_view.settings().set('markdown_preview_id', preview.id())
window.focus_group(focus_group)
window.focus_view(md_view)
return preview
def show_html(md_view, preview):
html = ('<style>{}</style>'.format(get_style()) +
pre_with_br(markdown2.markdown(get_view_content(md_view),
extras=['fenced-code-blocks', 'no-code-highlighting'])))
# the option no-code-highlighting does not exists
# in the official version of markdown2 for now
# I personaly edited the file (markdown2.py:1743)
html = html.replace('&nbsp;', '&nbspespace;') # save where are the spaces
html = HTMLParser().unescape(html)
# exception, again, because <pre> aren't supported by the phantoms
html = html.replace('&nbspespace;', '<i class="space">.</i>')
preview.erase_phantoms('markdown_preview')
preview.add_phantom('markdown_preview',
sublime.Region(-1),
html,
sublime.LAYOUT_BLOCK,
lambda href: sublime.run_command('open_url', {'url': href}))
# 0 < y < 1
y = md_view.text_to_layout(md_view.sel()[0].begin())[1] / md_view.layout_extent()[1]
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
vector[1] = mini(vector[1], 0)
vector[1] += preview.line_height()
preview.set_viewport_position(vector, animate=False)
def get_view_content(view):
return view.substr(sublime.Region(0, view.size()))
def get_view_from_id(window, id):
for view in window.views():
if view.id() == id:
return view
class MarkdownInPopupCommand(sublime_plugin.EventListener):
def on_load(self, view):
settings = view.settings()
if not 'markdown' in settings.get('syntax').lower():
return
settings.add_on_change('markdown_preview_enabled', lambda: self.on_modified(view))
def on_modified(self, md_view):
window = md_view.window()
md_view_settings = md_view.settings()
if not 'markdown' in md_view_settings.get('syntax').lower():
return
markdown_preview_enabled = md_view_settings.get('markdown_preview_enabled') is True
preview_id = md_view_settings.get('markdown_preview_id', None)
if not markdown_preview_enabled:
if preview_id is not None:
preview = get_view_from_id(window, preview_id)
if preview:
close_preview(md_view_settings, preview)
return
if preview_id is None:
preview = create_preview(window, md_view)
else:
preview = get_view_from_id(window, preview_id)
if not preview:
md_view_settings.erase('markdown_preview_id')
md_view_settings.erase('markdown_preview_enabled')
return
show_html(md_view, preview)
def on_pre_close(self, view):
settings = view.settings()
if settings.get('markdown_preview_enabled') is True:
preview = get_view_from_id(view.window(), settings.get('markdown_preview_id'))
if preview:
sublime.set_timeout_async(lambda: preview.close(), 250)
elif settings.get('is_markdown_preview') is True:
md_view = get_view_from_id(view.window(), settings.get('markdown_view_id'))
if md_view:
def callback():
md_view_settings = md_view.settings()
md_view_settings.erase('markdown_preview_enabled')
md_view_settings.erase('markdown_preview_id')
sublime.set_timeout_async(callback, 250)

5
messages.json Normal file
View File

@ -0,0 +1,5 @@
{
"install": "messages/install.txt",
"1.1.2": "messages/1.1.2.txt",
"2.0.1": "messages/2.0.1.txt"
}

12
messages/1.1.2.txt Normal file
View File

@ -0,0 +1,12 @@
Sorry to interrupt you... :(
Small changes occured on MarkdownLivePreview.
Changes:
~~~~~~~~
Main callback is now async (prevent ST from crashing)
Tip of the day: Delete all the content till the end of the line: Ctrl+k, Ctrl+k
You'll use it much more than you think ;)

22
messages/2.0.1.txt Normal file
View File

@ -0,0 +1,22 @@
Sorry to interrupt you... :(
Some quite important changes have occured on MarkdownLivePreview.
The first version was quite buggy: it made Sublime Text 3 crash. So I released
a whole new version, which is working differently.
When you active MarkdownLivePreview, it'll open a new window with the markdown
view on the left, and the preview on the right.
When you'll close any file in this window, it'll close the *entire* window.
I hope you'll still enjoy using MarkdownLivePreview. If you have any request,
just let me know by raising an issue here:
github.com/math2001/MarkdownLivePreview/issues
Tip of the day: you can duplicate a line by pressing 'ctrl+d'
remove a line by pressing 'ctrl+shift+k'
join lines by pressing 'ctrl+j'
get use to use these few shortcuts, and you'll speed up
significantly.

42
messages/install.txt Normal file
View File

@ -0,0 +1,42 @@
__ __ _ _ _ _ _____ _
| \/ | | | | | | | (_) | __ \ (_)
| \ / | __ _ _ __| | ____| | _____ ___ __ | | ___ _____| |__) | __ _____ ___ _____ __
| |\/| |/ _` | '__| |/ / _` |/ _ \ \ /\ / / '_ \| | | \ \ / / _ \ ___/ '__/ _ \ \ / / |/ _ \ \ /\ / /
| | | | (_| | | | < (_| | (_) \ V V /| | | | |____| |\ V / __/ | | | | __/\ V /| | __/\ V V /
|_| |_|\__,_|_| |_|\_\__,_|\___/ \_/\_/ |_| |_|______|_| \_/ \___|_| |_| \___| \_/ |_|\___| \_/\_/
Thanks for installing MarkdownLivePreview! I hope you'll enjoy using it!
Quick Start:
~~~~~~~~~~~~
To enable MarkdownLivePreview, you need to me on a markdown view (works with
Markdown Extended). Then just press `alt+m`, or search up in the command
palette: 'MarkdownLivePreview: Toggle'. Hit enter and you're done. As soon as
you'll type anything in, it'll show up the preview in a new group.
Say thanks:
~~~~~~~~~~~
Just letting me know you're enjoying this plugin is a great way to say thanks!
You can do so by staring the GitHub repo, or sending a tweet (@_math2001) for
example!
Troubles?
~~~~~~~~~
If you have any kind of trouble with it, just let me now by raising an issue on
the GitHub issue tracker here:
https://github.com/math2001/MarkdownLivePreview/issues
Tip of the day: Right click on a URL and choose 'Open <the url>' to open it in
your default browser.
Credits:
~~~~~~~~
The ASCII MLP has been generated using the ASCII Decorator.
https://github.com/viisual/ASCII-Decorator

View File

@ -1,46 +1,15 @@
# Hello world! # DuckDuckGo - The Search engine you'll fall in love
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Hope you'll enjoy using MarkdownLivePreview!
### The Zen of Python, by Tim Peters ![Sublime Text Forum](https://forum.sublimetext.com/uploads/st-forum-wide.png)
> Beautiful is better than ugly.
> Explicit is better than implicit.
> Simple is better than complex.
> Complex is better than complicated.
> Flat is better than nested.
> Sparse is better than dense.
> Readability counts.
> Special cases aren't special enough to break the rules.
> Although practicality beats purity.
> Errors should never pass silently.
> Unless explicitly silenced.
> In the face of ambiguity, refuse the temptation to guess.
> There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch.
> Now is better than never.
> Although never is often better than *right* now.
> If the implementation is hard to explain, it's a bad idea.
> If the implementation is easy to explain, it may be a good idea.
> Namespaces are one honking great idea -- let's do more of those!
> Code tells you how, comments tells you why
print('hello world')
print('hi')
```python ```python
print('This is some pretty') print('Hello world')
print('cool stuff') if DEBUG:
if test: print('DEBUG_MODE on')
print('hello world')
``` ```
This is some `code` > Only a fool knows everything. A wise man knows how little he knows
- a The only think I know right now is that Boxy Theme's just awesome.
- list
1. and
2. other
3. list

8
setting_names.py Normal file
View File

@ -0,0 +1,8 @@
# -*- 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'

12
todo.md
View File

@ -1,12 +0,0 @@
# todo
- add message in status bar @notGoodIdea
- add **custom css** feature @done
- sync scroll @done
- regive focus to the right markdown view @done
- set the title of the preview @done
- disable previewing when the preview is closed @done
- check when setting is activated and create panel and stuff @done