Compare commits

..

107 Commits

Author SHA1 Message Date
192f61bf0c improve caching of images
First, we used two caches. Turns out that lru_cache wasn't needed, the
dict works perfectly fine on it's own.

Second, we now also cache local images, so that we don't have to read
them off the filesystem and convert them to base64 on every keystroke

Maybe there should be a maximum size on that cache dict, but I doubt
anyone would actually run into any trouble this cache taking too much
ram.
2019-11-16 10:16:12 +11:00
2785df74ce Format everything with black
Follow the readme's instruction haha
2019-11-16 09:57:45 +11:00
e13842ede4 Mention the FIXME's in the README.md for peole to contribute 2019-11-16 09:41:00 +11:00
c6ac821c4a Merge remote-tracking branch 'update/master'
This is a weird merge. I had re-written everything in a separate
repository, and basically wanted that new repo (update) to be the new
master of this repository, whilst preserving the history of the update
repo.

Here's what I did:

$ git remote add update <path to update>
$ git fetch update
$ git merge -X theirs --allow-unrelated-histories update/master
$ # remove extra files
$ git commit --amend
2019-11-16 09:31:39 +11:00
9ad3f25d14 add license 2019-11-16 09:04:14 +11:00
eaa357a65f don't export png's and compilers to the final users
Though they are still available to the devs, as they are just
`export-ignore`d in the .gitattributes. I've done a little bit of
research, and this attribute prevents the files from being archived by
git. So, I'm assuming that package control fetches archives of the
packages, and not the actual ones. But I haven't found any documentation
on packagecontrol.io stating that. I quickly browsed its source code
on GitHub, and couldn't really find anything. So, I'm going to contact
@jfcherng, who showed me this trick on FileManager.
2019-11-16 08:48:51 +11:00
0f5630c3dc Set the preview's name (in the tab)
just a one liner
2019-11-16 08:28:04 +11:00
c14c28b56b fix the flickering by delaying and throwing
Explanation coming on my blog, math2001.github.io, something like
"lessons learned from re writing MarkdownLivePreview"
2019-11-16 07:50:55 +11:00
0dea8afba4 fix clipping of files after pre
The aim is to replace every \n in a <pre> with a <br /> because st
doesn't support pre.

However, ST doesn't support <br/> for some reason, only <br> or <br />.
We use to add some <br>s, but BeautifulSoup automatically adds a <br/>
when it sees a <br> (close the tag), which causes the clipping of the
rest of the file by ST. But if we replace every \n with a <br />,
BeautifulSoup automatically replaces it with <br/> (= ST bug)

So, we do exactly that, except that at the very end, when markdown2html
returns, we replace every <br/> with a <br />
2019-11-15 15:19:15 +11:00
e3896a6b3d fix indentation in pre in preview
Again, this is a dodgy hack: replace spaces with dot with the color
exactly like the background, because otherwise ST ignores it, even in
pre
2019-11-15 15:02:31 +11:00
6016f07cd1 add line breaks for pre 2019-11-15 14:52:20 +11:00
c0c9867cc8 move resources.py to MarkdownLivePreview.py
Having an extra file is just a pain because you have to save it manually
for ST to reload, and it was small and created way more trouble than it
needed to... It fits really nicely in there anyway

And add some nice CSS
2019-11-15 13:33:58 +11:00
5f2cac54e8 use a resource system to load images and stylesheet 2019-11-15 07:21:41 +11:00
8c1012eb8c Remove entry in phantom_sets when markdown_view is closed
Otherwise the object would keep on growing forever. It probably would
never ever be a problem for anyone, but it just makes me feel better
2019-11-14 21:33:37 +11:00
cc28bfef96 clean up code; support code block; add few FIXMEs 2019-11-14 21:14:20 +11:00
ef9b2daf6d Load images from the internet using an in memory cache
As soon as the plugin is reloaded, or the editor restarted, the images
must be reloaded. Maybe we could use a file cache...
2019-11-14 19:30:26 +11:00
bae26fc452 Viewing images works for local files
We have to make sure that everything is converted to base64 (and
replaced in the src attribute) because otherwise ST3 messes up the
height of the images
2019-11-14 17:28:47 +11:00
5738f6b5ff render the preview as soon as the markdown file loads 2019-11-14 15:56:55 +11:00
6bb8e6ebaa update preview when the user types
We don't have any delay in between updates (because i'm scared of
threading), which has a few problem:

1. probably really sluggish on slow systems
2. probably slow for readmes with images (need to test)
3. flickers (the phantoms are updated too quickly, so sometimes it
doesn't replace the old one smoothly)

BUG: the preview doesn't load when we preview the markdown file
2019-11-14 15:56:22 +11:00
8eb6882d60 Update readme.md with contributing info
Jokes, I haven't even set black on my own editor yet...
2019-11-14 12:16:18 +11:00
61cf2984eb Fix restoration of markdown_view as an original_view for unsaved files
It use to ask for confirmation (to save) because on_pre_close is run
after this dialog. Hence, we set the markdown_view as scratch for
unsaved files. :^)
2019-11-14 11:24:31 +11:00
7f7dcd6ba8 Restore the markdown_view as an original_view
See the top of MarkdownLivePreview.py for terminology

Doesn't work well for unsaved files, as pre_close is triggered after the
confirmation dialog
2019-11-14 11:20:20 +11:00
d3d88ddb49 Add basic readme.md 2019-11-14 11:16:47 +11:00
9a8ac3886e Open current markdown file in a new window
It works for saved and unsaved files. Maybe unsaved file's content
should be written to a temporary file in case we crash, so that the user
doesn't lose all it's content.
2019-11-13 13:57:24 +11:00
d4c477749c Add unmaintained notice 2018-10-12 09:14:23 +11:00
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
f65a068b4e don't auto reopen preview window #13 2017-02-07 16:32:27 +11:00
bc328642e7 [docs] remove doc from readme 2017-02-05 09:28:07 +11:00
d2053be41e add messages for 2.4.x 2017-02-05 08:07:25 +11:00
eb48b1c79f add keep_open_when_opening_preview doc to README 2017-02-05 07:56:59 +11:00
8317fa738c add 'header_action' "doc" to the readme 2017-02-03 18:09:23 +11:00
3be12b0539 add settings for YAML/TOML header #17 2017-02-03 17:44:19 +11:00
=
c92d78fb20 fix <hr>s ref #17
Phantoms do not support <hr/>, but <hr />
2017-02-02 18:30:42 +11:00
30d75f159d add option: keep the md view when previewing #13
Keep the markdown view opened in the original window when when opening
the preview
2017-01-27 09:55:52 +11:00
52e4b917e5 move get_preview_name to functions.py 2017-01-26 19:06:53 +11:00
48a68b2a79 detailed comment in markdown2html function 2017-01-26 18:43:51 +11:00
8eb0172eb4 improve replace_img_src_base64: use bs4 2017-01-26 18:32:08 +11:00
52e35fb610 Remove comments. Fix #14
Phantom's do not support them.
2017-01-26 18:12:34 +11:00
84f809e57f [docs] add license and fix iframe in index.md 2017-01-26 14:35:20 +11:00
351e8bd355 fix spaces (dot) color in pre.table 2017-01-26 14:19:21 +11:00
5babc862b4 format README 2017-01-26 14:12:12 +11:00
dc7139fbe7 fix background color in tables 2017-01-26 12:59:23 +11:00
bbbeae6fe9 center tables <th>s #12 2017-01-26 12:24:09 +11:00
271c7c619a fix bug: remove hard-coded color in CSS 2017-01-26 11:47:18 +11:00
8cc6b2b263 fix typo, add a tiny bit of doc to the code 2017-01-26 11:30:56 +11:00
c7961ce94c update stylesheet for tables 2017-01-26 11:03:52 +11:00
eae91fa428 add separator for tables' header 2017-01-26 10:57:12 +11:00
6f18e8e4a2 remove .prettify() @ pre_with_pr (bs4);
fix spaces in tables too
2017-01-26 10:43:04 +11:00
48c1800065 use bs4 for pre2br; fix style sheet for tables
Also: load default.css from the actual file if the package is not
zipped.
2017-01-26 10:36:52 +11:00
bad1cb74c6 MDtables base OK. Use bs4. #12 2017-01-26 09:21:49 +11:00
4198504fd1 [docs] add custom css part 2017-01-22 19:41:00 +11:00
1bef00de14 fix bug: support custom css from user 2017-01-22 18:41:36 +11:00
d707cf7a47 enable devListener 2017-01-22 17:51:48 +11:00
c27cd5f210 [docs] fix link 2017-01-22 15:15:42 +11:00
41bbc3d03d add logo 2017-01-22 15:15:23 +11:00
1e651bebc6 [default.css] support kbd 2017-01-22 14:55:33 +11:00
ea309f2323 [docs] add docs 2017-01-22 14:42:50 +11:00
3306d6ad5e add license 2017-01-22 14:38:56 +11:00
0505ca30bc Use phantomSet #11 2017-01-22 11:23:14 +11:00
c49ae26720 tiny update 2017-01-20 18:52:01 +11:00
c2618ead1d Merge pull request #9 from jimperio/master
Fix a typo and some minor copy errors
2017-01-14 09:23:17 +11:00
6e113fef6e fix trailing commas in Main.sublime-menu 2017-01-14 09:21:59 +11:00
e5378e2300 Fix a typo and some minor copy errors 2017-01-13 23:15:48 +08:00
caf932b536 update messages 2017-01-12 11:25:36 +11:00
fa106c8206 fix bug when settings preview's position 2017-01-12 11:24:51 +11:00
0ac9fd9aaa add messages for sync scroll 2017-01-12 09:37:44 +11:00
5bbfb4606d comment code 2017-01-12 09:22:40 +11:00
37703e9bab enable sync scroll #8 2017-01-12 08:48:33 +11:00
3b920f4336 update docs (a big name really) 2017-01-09 18:51:12 +11:00
abe151fdb7 add message for 2.1.0 2017-01-09 17:35:37 +11:00
acc8beb3be update README #7 2017-01-09 15:14:38 +11:00
9206b6de62 auto open up preview window #7 2017-01-09 15:08:13 +11:00
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
43 changed files with 1562 additions and 1168 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
docs/ export-ignore
resources/*.png export-ignore
resources/*.py export-ignore

4
.gitignore vendored
View File

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

View File

@ -1,16 +0,0 @@
[
{
"keys": ["alt+m"],
"command": "toggle_setting",
"args": {
"setting": "markdown_live_preview_enabled"
},
"context": [
{
"key": "selector",
"operator": "equal",
"operand": "text.html.markdown"
}
]
}
]

View File

@ -1,20 +0,0 @@
[
{
"id": "preferences",
"children": [
{
"id": "package-settings",
"children": [
{
"caption": "MarkdownLivePreview",
"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"
}
}
]
}
]
}
]

View File

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

View File

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

File diff suppressed because one or more lines are too long

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
Copyright 2017 Mathieu PATUREL
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 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.

144
MLPApi.py
View File

@ -1,144 +0,0 @@
# -*- encoding: utf-8 -*-
import sublime
import sublime_plugin
import os.path
from html.parser import HTMLParser
from .lib import markdown2
from .escape_amp import *
from .functions import *
from .setting_names import *
__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):
name = md_view.name() \
or os.path.basename(md_view.file_name()) \
or 'Untitled'
return name + ' - Preview'
def find_preview(window):
for view in window.views():
vsettings = view.settings()
if vsettings.get(IS_PREVIEW):
yield view, vsettings
def create_preview(md_view):
window = md_view.window()
md_view_settings = md_view.settings()
md_view_settings.set(JUST_CREATED, True)
preview = window.new_file()
psettings = preview.settings()
psettings.set(IS_PREVIEW, True)
psettings.set(MD_VIEW_ID, md_view.id())
preview.set_name(get_preview_name(md_view))
preview.set_scratch(True)
md_view_settings.set(PREVIEW_ID, preview.id())
def move_and_focus_md_view():
window.run_command('new_pane')
sublime.set_timeout_async(lambda: window.focus_view(md_view), 250)
sublime.set_timeout_async(move_and_focus_md_view, 250)
return preview
def hide_preview(view):
window = view.window()
vsettings = view.settings()
if vsettings.get(IS_PREVIEW):
preview = view
psettings = vsettings
md_view_id = vsettings.get(MD_VIEW_ID)
md_view = get_view_from_id(window, md_view_id)
if md_view is None:
raise ValueError('Tried to get md_view from id {} but got None'.format(md_view_id))
mdvsettings = md_view.settings()
elif vsettings.get(PREVIEW_ENABLED):
md_view = view
preview_id = vsettings.get(PREVIEW_ID)
preview = get_view_from_id(window, preview_id)
mdvsettings = vsettings
if preview is None:
raise ValueError('Tried to get preview from id {} but got None'.format(preview_id))
psettings = preview.settings()
else:
raise ValueError('Call hide_preview with a view which is not the preview or the md_view')
psettings.set(IS_HIDDEN, True)
mdvsettings.erase(PREVIEW_ID)
sublime.set_timeout(lambda: preview.close(), 250)
return
window = md_view.window()
if window is None:
return
mdvsettings = md_view.settings()
preview_id = mdvsettings.get(PREVIEW_ID)
if not preview_id:
return
mdvsettings.erase(PREVIEW_ID)
preview = get_view_from_id(window, preview_id)
if preview is None:
raise ValueError('Tried to get view from id {} but got None'.format(preview_id))
psettings = preview.settings()
psettings.set(IS_HIDDEN, True)
sublime.set_timeout(preview.close(), 250)
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 = '<style>{}</style>\n{}'.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)
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)
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}))
# set viewport position
# sublime.set_clipboard(html)
return
# 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)

View File

@ -1,51 +1,237 @@
# -*- encoding: utf-8 -*-
import sys
import os.path import os.path
import sublime import sublime
import sublime_plugin import sublime_plugin
from .MLPApi import * from functools import partial
from .setting_names import *
from .functions import * from .markdown2html import markdown2html
from .utils import *
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
def get_resource(resource):
path = "Packages/MarkdownLivePreview/resources/" + resource
abs_path = os.path.join(sublime.packages_path(), "..", path)
if os.path.isfile(abs_path):
with open(abs_path, "r") as fp:
return fp.read()
return sublime.load_resource(path)
resources = {}
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):
# 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 on_modified(self, view): phantom_sets = {
window = view.window() # markdown_view.id(): phantom set
vsettings = view.settings() }
if vsettings.get(PREVIEW_ENABLED):
id = vsettings.get(PREVIEW_ID)
preview = get_view_from_id(window, id)
if id is None or preview is None:
preview = create_preview(view)
sublime.set_timeout(lambda: show_html(view, preview), 1000)
else:
show_html(view, preview)
# we schedule an update for every key stroke, with a delay of DELAY
# then, we update only if now() - last_update > DELAY
last_update = 0
# FIXME: maybe we shouldn't restore the file in the original window...
def on_pre_close(self, markdown_view):
""" Close the view in the preview window, and store information for the on_close
listener (see doc there)
"""
if not markdown_view.settings().get(MARKDOWN_VIEW_INFOS):
return return
def on_activated(self, view): self.markdown_view = markdown_view
# if view is md_view and has no preview self.preview_window = markdown_view.window()
# -> create preview self.file_name = markdown_view.file_name()
window = view.window()
vsettings = view.settings() if self.file_name is None:
if vsettings.get(PREVIEW_ENABLED): total_region = sublime.Region(0, markdown_view.size())
id = vsettings.get(PREVIEW_ID) self.content = markdown_view.substr(total_region)
preview = get_view_from_id(window, id) markdown_view.erase(edit, total_region)
if id is None or preview is None: else:
preview = create_preview(view) self.content = None
sublime.set_timeout(lambda: show_html(view, preview), 1000)
else: def on_load_async(self, markdown_view):
show_html(view, preview) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
return return
# if view is preview preview_view = markdown_view.window().active_view_in_group(1)
# -> do nothing
if vsettings.get(IS_PREVIEW): self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
self._update_preview(markdown_view)
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 return
# if view is not the md_view or the preview
# remove preview if any assert (
for view, settings in find_preview(window): markdown_view.id() == self.markdown_view.id()
settings.set(IS_HIDDEN, True) ), "pre_close view.id() != close view.id()"
view.close()
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}),
)
]
)

View File

@ -0,0 +1,7 @@
[
{
"caption": "MarkdownLivePreview: Open Preview",
"command": "open_markdown_preview"
}
]

View File

@ -1,32 +0,0 @@
Fast:
☐ sync scroll @needsUpdate(because of images)
☐ cache image in object when used, so that it's faster @needsTest
☐ call settings listener on_new too - might be too heavy
☐ add clear cache command
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:
✔ 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

@ -1,47 +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
Although MarkdownLivePreview is not available on the default channel of [PackageControl](http://packagecontrol.io), you can still use it to download this little package. If you know what feature you want to implement, or what bug you wanna fix, then
go ahead and hack! But if you wanna contribute just to say thanks, and don't
really know what you could be working on, then there are a bunch of `FIXME`s
in `MarkdownLivePreview.py` and `markdown2html.py` (GitHub only shows the top
2 results if you try to search using their interface :slightly_frowning_face:).
1. Open the command palette (`ctrl+shift+p`) ### Hack it!
2. Search for: `Package Control: 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. 1. Fork this repo
2. Make your own branch (the name of the branch should be the feature you are
implementing eg. `improve-tables`, `fix-crash-on-multiple-preview`
3. All your code should be formated by black.
4. Send a PR!
So, as you probably understood, now you just need to install MarkdownLivePreview as if it was available on the default channel: FIXME: add a git hook to format using black (can the git hook be added on github?)
1. Open the command palette (`ctrl+shift+p`)
2. Search for: `Package Control: Install Package`
3. Search for: `MarkdownLivePreview`
4. hit <kbd>enter</kbd>
Done!
### 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.
### In dev
This plugin is not finished, there's still some things to fix (custom css, focus, etc). So, don't run away if you have any trouble, just submit an issue [here](http://github.com/math2001/MarkdownLivePreview/issues).
### Demo
![demo](demo.gif)
### Custom css
It is possible to set your own css. But, be carefull, you have to respect [those rules](http://www.sublimetext.com/docs/3/minihtml.html#css). Just go to `Preferences -> Package Settings -> MarkdownLivePreview`. It will open a css file, here: `$packages/User/MarkdownLivePreview.css`. Just save it and it will automatically use it instead of the default one.
### 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).

View File

@ -1,43 +0,0 @@
:root, html, body {
height: 100%;
}
html {
--light-bg: color(var(--background) blend(#999 85%))
height: 100%;
}
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;
}

BIN
demo.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

7
dependencies.json Normal file
View File

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

View File

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

View File

@ -1,38 +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('&', '&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:
print('TEST FAIL ({i}): {subject!r} escaped did not match {result!r}'.format(**locals()))
fails += 1
if fails == 0:
print("SUCCESS: every tests ({}) passed successfully!".format(len(tests)))
else:
print("{} test{} failed".format(fails, 's' if fails > 1 else ''))
if __name__ == '__main__':
run_tests()

View File

@ -1,98 +0,0 @@
# -*- encoding: utf-8 -*-
import base64
import os.path
import sublime
import re
from .image_manager import ImageManager
def plugin_loaded():
global error404, loading
loading = sublime.load_resource('Packages/MarkdownLivePreview/loading.txt')
error404 = sublime.load_resource('Packages/MarkdownLivePreview/404.txt')
def replace_img_src_base64(html):
"""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
image = to_base64(''.join(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):
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

View File

@ -1,98 +0,0 @@
# -*- encoding: utf-8 -*-
import os.path
from threading import Thread
import urllib.request, urllib.error
import sublime
from .functions import *
import tempfile
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)

File diff suppressed because it is too large Load Diff

18
live-testing/images.md Normal file
View 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):
![The sublime text logo!](file:///home/math2001/.config/sublime-text-3/Packages/MarkdownLivePreview/live-testing/sublime_text.png)
This is the first image from the local file system, *relative* path!
![The sublime text logo!](sublime_merge.png)
This is the first image from the internet!
![math2001's logo](https://avatars1.githubusercontent.com/u/15224242?s=400&u=53324cf4e303d15032ba53aa41673a2046b3284b&v=4)
[prev]: https://github.com/math2001/MarkdownLivePreview/tree/d4c477749ce7e77b8e9fc85464a2488f003c45bc

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

18
live-testing/test.md Normal file
View 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!

View File

@ -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
View 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")

7
messages.json Normal file
View File

@ -0,0 +1,7 @@
{
"install": "messages/install.txt",
"1.1.2": "messages/1.1.2.txt",
"2.0.1": "messages/2.0.1.txt",
"2.2.1": "messages/2.2.0.txt",
"2.4.1": "messages/2.4.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.

8
messages/2.1.0.txt Normal file
View File

@ -0,0 +1,8 @@
Sorry to interrupt you... :(
A settings is now available: `markdown_live_preview_on_open`. If set to true,
it opens the window preview as soon as you open a markdown file. See the
README for more infos.
Tip of the day: `ctrl+w`: closes the current file
`ctrl+shift+w`: closes the current window

8
messages/2.2.0.txt Normal file
View File

@ -0,0 +1,8 @@
Sorry to interrupt you... :(
Something changed on MarkdownLivePreview: the preview is now scrolled to where
you are editing your markdown file! It doesn't scroll back up all the time any
more!
Tip of the day: You can center the screen on your cursor by pressing:
`ctrl+k, ctrl+v` (on OSX `cmd+k, cmd+v`)

10
messages/2.4.txt Normal file
View File

@ -0,0 +1,10 @@
Sorry to interrupt you... :(
Some stuff changed on MarkdownLivePreview. It now supports YAML/TOML front matters. You can hide it,
or show it in a pre block (edit your settings for this).
Hope you'll enjoy it!
Tip of the day: If you want a VIM-like search feature, then just press 'ctrl/cmd+i'
(Find → Incremental find). You can still go the next match by pressing 'f3', and to
the previous one by pressing 'shift+f3'

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 be 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 type anything in, it'll show up with 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

1
resources/404.base64 Normal file

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

View 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
View 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=

View File

Before

Width:  |  Height:  |  Size: 953 B

After

Width:  |  Height:  |  Size: 953 B

44
resources/stylesheet.css Normal file
View 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);
}

View File

@ -1,18 +0,0 @@
# DuckDuckGo - The Search engine you'll fall in love with this is a test.
This is a test, and this is pretty cool!
![image](http://afterishtar.pl/images/100x100.gif)
Hope you'll enjoy using MarkdownLivePreview!
![image](https://forum.sublimetext.com/uploads/st-forum-wide.png)
![image](http://local.dev/tests/php/img/image-php.php)
hello world
This is a test dfsdf hello world, this is pretty cool.
This is a test

View File

@ -1,8 +0,0 @@
# -*- encoding: utf-8 -*-
PREVIEW_ENABLED = 'markdown_live_preview_enabled'
PREVIEW_ID = 'markdown_live_preview_id'
IS_PREVIEW = 'is_markdown_live_preview'
IS_HIDDEN = 'is_hidden_markdown_live_preview'
MD_VIEW_ID = 'markdown_live_preview_md_id'
JUST_CREATED = 'markdown_live_preview_just_created'

27
utils.py Normal file
View 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