Compare commits

..

80 Commits

Author SHA1 Message Date
2973f7f138 Update repository.json 2025-04-24 12:41:55 +00:00
2a58c22160 Update repository.json 2025-04-24 12:41:44 +00:00
beb6cfe709 Update repository.json 2025-04-24 12:33:51 +00:00
44eb19d923 Update repository.json 2025-04-24 12:32:30 +00:00
0354ddf41d Add channel.json 2025-04-24 12:30:54 +00:00
fede6c2873 Update repository.json 2025-04-24 12:29:18 +00:00
141a7d062c Update repository.json 2025-04-24 12:25:46 +00:00
2365d6fec2 Merge branch 'master' of git.0x42.cloud:christian.morpurgo/MarkdownLivePreview 2025-04-24 14:19:26 +02:00
35c8a954d0 add feature:
setting to increase font scale
2025-04-24 14:18:29 +02:00
e914a2d4e9 Update repository.json 2025-04-24 12:13:14 +00:00
7fbb23b480 Add repository.json 2025-04-24 12:12:04 +00:00
ec27d980a3 meta: add known limitation about ordered lists #97
Sublime Text renders ordered lists as unordered lists. I've only added it to the README.md for now, because I'm not sure about how the GitHub website and README should sync...
2019-12-05 07:35:27 +11:00
9fe7369029 meta: add keybinding info @ README
Just something people can copy paste quickly
2019-11-27 09:35:29 +11:00
e462e8b3bc ImageLoader: spawn one thread per image. fix #93
In the process of fixing #93, I realized that we were spawning new
threads to fetch images for each update (we only need one).
2019-11-16 15:54:22 +11:00
cf68b2c202 Set the maxwidth for images (fix #48)
It didn't look pretty when images where larger than the viewport, and it
"broke" word wrap (because it stretched the phantom, and hence lines
wrapped further, see #34)

Sublime Text's minihtml only supports width and height attributes on
img tags, therefore, we have to determine the aspect ratio of the images
ourselves if we want to set a maxsize (so that we can set the height).

We use a hacky function copy pasted from stackoverflow to determine the
size of common format of images using std lib python.
2019-11-16 14:56:03 +11:00
c10bc95e54 remove utils.py file; use a setting file for delay between updates
utils.py contained two functions. One which wasn't used, and another,
get_settings() which I moved to MarkdownLivePreview.py

Add an entry to the command palette to edit the settings
2019-11-16 14:19:46 +11:00
84fb15aec3 update stylesheet to spread pre blocks (fix #56) 2019-11-16 11:16:58 +11:00
04989f8660 Use cuddled-lists options on markdown2 (fix #82) 2019-11-16 10:41:23 +11:00
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
47 changed files with 1660 additions and 1716 deletions

3
.gitattributes vendored Normal file
View File

@ -0,0 +1,3 @@
docs/ export-ignore
resources/
!resources/*.base64

6
.gitignore vendored
View File

@ -1,5 +1 @@
Thumbs.db __pycache__
__pycache__/
cache.txt
venv/
site/

View File

@ -1,13 +0,0 @@
[
{
"keys": ["alt+m"],
"command": "new_markdown_live_preview",
"context": [
{
"key": "selector",
"operator": "equal",
"operand": "text.html.markdown"
}
]
}
]

View File

@ -1,33 +0,0 @@
[
{
"id": "preferences",
"children": [
{
"id": "package-settings",
"children": [
{
"caption": "MarkdownLivePreview",
"children": [
{
"caption": "Settings",
"command": "edit_settings",
"args": {
"base_file": "$packages/MarkdownLivePreview/.sublime/MarkdownLivePreview.sublime-settings",
"default": "// Your settings for MarkdownLivePreview. See the default file to see the different options. \n{\n\t\n}\n"
}
},
{
"caption": "Style - CSS",
"command": "open_file",
"args": {
"file": "$packages/User/MarkdownLivePreview.css",
"contents": "/* See http://www.sublimetext.com/docs/3/minihtml.html#css to know which property you're able to use */\n\n$0"
}
}
]
}
]
}
]
}
]

View File

@ -1,25 +0,0 @@
[
{
"caption": "MarkdownLivePreview: Edit Current File",
"command": "new_markdown_live_preview"
},
{
"caption": "MarkdownLivePreview: Clear the cache",
"command": "markdown_live_preview_clear_cache"
},
{
"caption": "MarkdownLivePreview: Edit Custom CSS File",
"command": "open_file",
"args": {
"file": "$packages/User/MarkdownLivePreview.css"
}
},
{
"caption": "Preferences: MarkdownLivePreview Settings",
"command": "edit_settings",
"args": {
"base_file": "${packages}/MarkdownLivePreview/.sublime/MarkdownLivePreview.sublime-settings",
"default": "// Your settings for MarkdownLivePreview. See the default file to see the different options. \n{\n\t$0\n}\n"
}
}
]

View File

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

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>name</key>
<string>MarkdownLivePreviewSyntax</string>
<key>patterns</key>
<array>
</array>
<key>scopeName</key>
<string>text.markdown-live-preview</string>
</dict>
</plist>

106
MLPApi.py
View File

@ -1,106 +0,0 @@
# -*- encoding: utf-8 -*-
import sublime
import sublime_plugin
import os.path
from html.parser import HTMLParser
from .lib import markdown2 as md2
from .lib.pre_tables import pre_tables
from .escape_amp import *
from .functions import *
from .setting_names import *
from .image_manager import CACHE_FILE
from random import randint as rnd
__folder__ = os.path.dirname(__file__)
USER_STYLE_FILE = os.path.join(os.path.dirname(__folder__), 'User', 'MarkdownLivePreview.css')
# used to store the phantom's set
windows_phantom_set = {}
def plugin_loaded():
global DEFAULT_STYLE_FILE
if os.path.exists(os.path.join(__folder__, 'default.css')):
with open(os.path.join(__folder__, 'default.css')) as fp:
DEFAULT_STYLE_FILE = fp.read()
else:
DEFAULT_STYLE_FILE = sublime.load_resource('Packages/MarkdownLivePreview/default.css')
def 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/' + \
'MarkdownLivePreviewSyntax.hidden-tmLanguage')
return preview
def get_style():
content = ''.join([line.strip() + ' ' for line in DEFAULT_STYLE_FILE.splitlines()])
if os.path.exists(USER_STYLE_FILE):
with open(USER_STYLE_FILE) as fp:
content += '\n' + fp.read() + '\n'
return content
def markdown2html(md, basepath):
html = ''
html += '<style>\n{}\n</style>\n'.format(get_style())
# pre_with_br
html += pre_with_br(pre_tables(md2.markdown(md, extras=['fenced-code-blocks',
'no-code-highlighting', 'tables'])))
# 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
# 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(basepath))
return html
def show_html(md_view, preview):
global windows_phantom_set
html = markdown2html(get_view_content(md_view), os.path.dirname(md_view.file_name()))
phantom_set = windows_phantom_set.setdefault(preview.window().id(),
sublime.PhantomSet(preview, 'markdown_live_preview'))
phantom_set.update([sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK,
lambda href: sublime.run_command('open_url', {'url': href}))])
# lambda href: sublime.run_command('open_url', {'url': href})
# get the "ratio" of the markdown view's position.
# 0 < y < 1
y = md_view.text_to_layout(md_view.sel()[0].begin())[1] / md_view.layout_extent()[1]
# set the vector (position) for the preview
vector = [0, y * preview.layout_extent()[1]]
# remove half of the viewport_extent.y to center it on the screen (verticaly)
vector[1] -= preview.viewport_extent()[1] / 2
# make sure the minimum is 0
vector[1] = 0 if vector[1] < 0 else vector[1]
# the hide the first line
vector[1] += preview.line_height()
preview.set_viewport_position(vector, animate=False)
def clear_cache():
"""Removes the cache file"""
os.remove(CACHE_FILE)
def release_phantoms_set(view_id=None):
global windows_phantom_set
if view_id is None:
windows_phantom_set = {}
else:
del windows_phantom_set[view_id]

View File

@ -1,112 +1,271 @@
# -*- encoding: utf-8 -*- """
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
"""
import time
import os.path
import struct
import sublime import sublime
import sublime_plugin import sublime_plugin
from .MLPApi import * from functools import partial
from .setting_names import *
from .functions import *
class NewMarkdownLivePreviewCommand(sublime_plugin.ApplicationCommand): from .markdown2html import markdown2html
def run(self): MARKDOWN_VIEW_INFOS = "markdown_view_infos"
PREVIEW_VIEW_INFOS = "preview_view_infos"
SETTING_DELAY_BETWEEN_UPDATES = "delay_between_updates"
SETTING_FONT_SCALE = "font_scale"
"""Inspired by the edit_settings command""" resources = {}
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('MarkdownLivePreview: Not supporting '
'unsaved file for now')
sublime.run_command('new_window') def plugin_loaded():
self.window = sublime.active_window() global DELAY
self.window.settings().set(PREVIEW_WINDOW, True) resources["base64_404_image"] = parse_image_resource(get_resource("404.base64"))
self.window.run_command('set_layout', { resources["base64_loading_image"] = parse_image_resource(
'cols': [0.0, 0.5, 1.0], get_resource("loading.base64")
'rows': [0.0, 1.0], )
'cells': [[0, 0, 1, 1], [1, 0, 2, 1]] resources["stylesheet"] = get_resource("stylesheet.css")
}) # FIXME: how could we make this setting update without restarting sublime text
self.window.focus_group(1) # and not loading it every update as well
preview = create_preview(self.window, current_view) DELAY = get_settings().get(SETTING_DELAY_BETWEEN_UPDATES)
self.window.focus_group(0)
md_view = self.window.open_file(file_name)
mdsettings = md_view.settings()
mdsettings.set(PREVIEW_ENABLED, True) class MdlpInsertCommand(sublime_plugin.TextCommand):
mdsettings.set(PREVIEW_ID, preview.id()) 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")
# FIXME: hide number lines on preview
preview_window.focus_group(0)
if file_name:
markdown_view = preview_window.open_file(file_name)
else:
markdown_view = preview_window.new_file()
markdown_view.run_command("mdlp_insert", {"point": 0, "string": content})
markdown_view.set_scratch(True)
markdown_view.set_syntax_file(syntax_file)
markdown_view.settings().set(
MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id,},
)
def is_enabled(self): def is_enabled(self):
return is_markdown_view(sublime.active_window().active_view()) # FIXME: is this the best way there is to check if the current syntax is markdown?
# should we only support default markdown?
# what about "md"?
# FIXME: what about other languages, where markdown preview roughly works?
return "markdown" in self.view.settings().get("syntax").lower()
class MarkdownLivePreviewListener(sublime_plugin.EventListener): class MarkdownLivePreviewListener(sublime_plugin.EventListener):
def update(self, view): phantom_sets = {
vsettings = view.settings() # markdown_view.id(): phantom set
if not vsettings.get(PREVIEW_ENABLED): }
# 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
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) self.markdown_view = markdown_view
return view, preview self.preview_window = markdown_view.window()
self.file_name = markdown_view.file_name()
def on_modified(self, view): if self.file_name is None:
if not is_markdown_view(view): # faster than getting the settings total_region = sublime.Region(0, markdown_view.size())
self.content = markdown_view.substr(total_region)
markdown_view.erase(edit, total_region)
else:
self.content = None
def on_load_async(self, markdown_view):
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
return return
self.update(view)
def on_window_command(self, window, command, args): preview_view = markdown_view.window().active_view_in_group(1)
if command == 'close' and window.settings().get(PREVIEW_WINDOW):
release_phantoms_set(window.id())
return 'close_window', {}
def on_activated_async(self, view): self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
vsettings = view.settings() self._update_preview(markdown_view)
if (is_markdown_view(view) def on_close(self, markdown_view):
and get_settings().get('markdown_live_preview_on_open') """ Use the information saved to restore the markdown_view as an original_view
and not vsettings.get(PREVIEW_ENABLED) """
and vsettings.get('syntax') != 'Packages/MarkdownLivePreview/' + \ infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
'.sublime/MarkdownLivePreviewSyntax' + \ if not infos:
'.hidden-tmLanguage'):
sublime.run_command('new_markdown_live_preview')
def on_load_async(self, view):
"""Check the settings to hide menu, minimap, etc"""
try:
md_view, preview = self.update(view)
except TypeError:
# the function update has returned None
return return
window = preview.window()
psettings = preview.settings()
show_tabs = psettings.get('show_tabs') assert (
show_minimap = psettings.get('show_minimap') markdown_view.id() == self.markdown_view.id()
show_status_bar = psettings.get('show_status_bar') ), "pre_close view.id() != close view.id()"
show_sidebar = psettings.get('show_sidebar')
show_menus = psettings.get('show_menus')
if show_tabs is not None: del self.phantom_sets[markdown_view.id()]
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): self.preview_window.run_command("close_window")
def run(self): # find the window with the right id
clear_cache() 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)
# Reload settings each time to catch changes
settings = get_settings()
delay = settings.get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default
font_scale = settings.get(SETTING_FONT_SCALE, 1.0) # Provide default
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)
preview_view = markdown_view.window().active_view_in_group(1)
# Get viewport_width, default to a large value if view isn't ready
viewport_extent = preview_view.viewport_extent()
viewport_width = viewport_extent[0] if viewport_extent else 1024
basepath = os.path.dirname(markdown_view.file_name()) if markdown_view.file_name() else '.' # Handle unsaved files
html = markdown2html(
markdown,
basepath,
partial(self._update_preview, markdown_view),
resources,
viewport_width,
font_scale,
)
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}),
)
]
)
def get_settings():
return sublime.load_settings("MarkdownLivePreview.sublime-settings")
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)
def parse_image_resource(text):
width, height, base64_image = text.splitlines()
return base64_image, (int(width), int(height))
# try to reload the resources if we save this file
try:
plugin_loaded()
except OSError:
pass

View File

@ -0,0 +1,15 @@
[
{
"caption": "MarkdownLivePreview: Open Preview",
"command": "open_markdown_preview"
},
{
"caption": "MarkdownLivePreview: Open Settings",
"command": "edit_settings", "args":
{
"base_file": "${packages}/MarkdownLivePreview/MarkdownLivePreview.sublime-settings",
"default": "{\n\t$0\n}\n"
},
}
]

View File

@ -0,0 +1,7 @@
{
// minimum number of milliseconds to wait before updating the preview again
"delay_between_updates": 100,
// scale factor for the font size relative to markdown settings
"font_scale": 1.0
}

View File

@ -1,36 +0,0 @@
Fast:
☐ cache image in object when used, so that it's faster @needsTest
Medium:
☐ auto refresh preview if loading images
☐ use alt attribute for 404 error
Long:
☐ support anchor (TOC) @big
Unknown:
___________________
Archive:
✔ fix custom css @bug @done Sun 22 Jan 2017 at 18:40 @project(Medium)
✘ check how many times is the show_html function called @cancelled Sun 22 Jan 2017 at 18:40 @project(Unknown)
✔ sync scroll @needsUpdate(because of images) @done Sun 22 Jan 2017 at 18:39 @project(Fast)
✔ fix #4 @high @done Mon 09 Jan 2017 at 18:42 @project(Long)
✔ use MarkdownLivePreview syntax, so we can use syntax's settings @done Mon 09 Jan 2017 at 18:41 @project(Medium)
✔ add clear cache command @done Mon 09 Jan 2017 at 18:41 @project(Fast)
✔ update README for settings in view @done Mon 09 Jan 2017 at 18:41 @project(Fast)
✔ add edit settings @done Mon 09 Jan 2017 at 18:41 @project(Fast)
✘ listen for settings to change @cancelled Mon 09 Jan 2017 at 18:41 @project(Medium)
✘ call settings listener on_new too - might be too heavy @cancelled Sun 08 Jan 2017 at 19:33 @project(Fast)
✔ fix relative source @done Sun 08 Jan 2017 at 19:22 @project(Medium)
✔ add settings for the preview @done Sun 08 Jan 2017 at 17:36 @project(Fast)
✔ regive focus to the right markdown view @done Mon 02 Jan 2017 at 18:34 @project(Fast)
✔ try/except for 404 @done Mon 02 Jan 2017 at 18:03 @project(Fast)
✔ fix bug when empty `src` @done Mon 02 Jan 2017 at 17:15 @project(Fast)
✔ preview.set_scratch(True) @done Mon 02 Jan 2017 at 16:58
✔ set the title of the preview @done Mon 02 Jan 2017 at 16:58
✔ preview.wordWrap => True @done Mon 02 Jan 2017 at 16:58
✔ clean the code (syntax) @done Mon 02 Jan 2017 at 16:58
✔ add 404 image @done Mon 02 Jan 2017 at 16:57
✔ load images from internet (`https:`) @done Mon 02 Jan 2017 at 16:57

115
README.md
View File

@ -1,96 +1,65 @@
# 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 ## Setting a keybinding
MarkdownLivePreview is available on the default channel of [PackageControl](http://packagecontrol.io), which means you just have to The open the preview, you can search up in the command palette
(<kbd>ctrl+shift+p</kbd>) `MarkdownLivePreview: Open Preview`. But if you
1. Open the command palette (`ctrl+shift+p`) prefer to have a shortcut, add this to your keybindings file:
2. Search for: `Package Control: Install Package`
3. Search for: `MarkdownLivePreview`
4. hit <kbd>enter</kbd>
to have MarkdownLivePreview working on your computer. Cool right? You can [thank package control](https://packagecontrol.io/say_thanks) for this.
### Usage
You can choose to enable MarkdownLivePreview by pressing <kbd>alt+m</kbd> or selecting in the command palette `MarkdownLivePreview: Edit Current File`. Note that you need to be editing (simply having the focus on) a markdown file. Because [Markdown Extended][markdown-extended] did a good job, it's compatible with this plugin.
So, once you've run it, it will open a new window, with only your markdown file, with the preview. Once you're done, close whichever file and it'll close the entire window.
*Notice that it will close the entire window if you close __whichever__ file. It means that if you open a random file in this window, and then close it, it'll close the entire window still*
### Settings
- `markdown_live_preview_on_open`: if set to `true`, as soon as you open a markdown file, the preview window will popup (thanks to [@ooing](https://github.com/ooing) for its [suggestion](https://github.com/math2001/MarkdownLivePreview/issues/7#issue-199464852)). Default to `false`
- `load_from_internet_when_starts`: every images that starts with any of the string specified in this list will be loaded from internet. Default to `["http://", "https://"]`
Note: To edit your settings, search up in the command palette `Preferences: MarkdownLivePreview Settings`, or by using the menu: `Preferences → Packages Settings → MarkdownLivePreview → Settings` ;. It's not your global settings, but only the `MarkdownLivePreview`'s one
### Syntax Specific Settings
This in an other "type" of setting. :laughing: If you have a look at the syntax of the preview file (not the markdown one, really the preview), you'll see that the syntax is `MarkdownLivePreviewSyntax`. This mean that you can specify specific settings for this specific syntax (such as `word_wrap: true`, `rulers: []`, etc).
To do so, you can
1. focus the *preview* (<kbd>ctrl+2</kbd> to focus the second group, so, by default, the preview's group)
2. search up in the command palette `Preferences: Settings Syntax Specific`. It's in the *right* file that you can add the settings you want (not the left one).
Note: MarkdownLivePreview will actualy look in this file for settings that aren't supported by default. Here they are:
- `show_tabs`
- `show_minimap`
- `show_status_bar`
- `show_sidebar`
- `show_menus`
They talk for themself, don't they? All of them takes a boolean (`true` or `false`). Note that those settings are *window* specific, not just view specific (that's why they aren't supported). It means that they'll affect the entire window, and every view in it.
Here is an example of syntax specific settings for MarkdownLivePreviewSyntax:
```json ```json
{ {
"show_menus": false, "keys": ["alt+m"],
"show_tabs": false, "command": "open_markdown_preview"
"show_minimap": false,
"gutter": false,
"rulers": [],
"word_wrap": true
} }
``` ```
And here's what you'll get: ## How to contribute
![MarkdownLivePreview Screenshoot](screenshoots/syntax-specific-settings.png) If you know what feature you want to implement, or what bug you wanna fix, then
go ahead and hack! Maybe raise an issue before hand so that we can talk about
it if it's a big feature.
*Note: to close a file, you can do <kbd>ctrl+w</kbd> (on Mac OS, it's <kbd>cmd+w</kbd>)* 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 all over this package.
Just pick one and fix it :-)
### Clear the cache ```
$ git clone https://github.com/math2001/MarkdownLivePreview
$ cd MarkdownLivePreview
$ grep -R FIXME
```
MarkdownLivePreview caches every images it loads from internet (otherwise, you'd never see your images, or you'd need to have a *really* fast internet connection :smile:). So, if for some reason you want to clear the cache (a simple file), you can do so from the command palette by running ` ### Hack it!
### Demo 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!
![demo](demo.gif) ### Known limitations
### Custom css #### Numbered lists are rendered as unordered lists
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 → Style - CSS`. 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. ```md
1. first
2. second
3. third
```
### Somethings wrong!! will be previewed the exact same way as
If you find that something's wrong with this package, you can let me know by raising an issue on the [GitHub issue tracker][github-issue-tracker] ```md
- first
- second
- third
```
### How to open the [README](http://github.com/math2001/MarkdownLivePreview/README.md) The issue comes from [Sublime Text's minihtml](https://www.sublimetext.com/docs/3/minihtml.html) which [doesn't support ordered lists](https://github.com/sublimehq/sublime_text/issues/1767). If you think feel like implementing a workaround, feel free to contribute, but it's not something I'm planning on doing. It isn't a critical feature, and support should come with time...
Some of the package add a command in the menus, others in the command palette, or other nowhere. None of those options are really good, especially the last one on ST3 because the packages are compressed. But, fortunately, there is plugin that exists and **will solve this problem** for us (and he has a really cute name, don't you think?): [ReadmePlease](https://packagecontrol.io/packages/ReadmePlease).
[markdown-extended]: https://packagecontrol.io/packages/Markdown%20Extended
[github-issue-tracker]: https://github.com/math2001/MarkdownLivePreview/issues

7
channel.json Normal file
View File

@ -0,0 +1,7 @@
{
"$schema": "sublime://packagecontrol.io/schemas/channel",
"schema_version": "4.0.0",
"repositories": [
"https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview/raw/branch/master/repository.json"
]
}

View File

@ -1,58 +0,0 @@
html {
--light-bg: color(var(--background) blend(#999 85%));
--very-light-bg: color(var(--background) blend(#999 92%));
}
body {
padding: 10px;
padding-top: 0px;
font-family: "Open Sans", sans-serif;
background-color: var(--background);
font-size: 15px;
}
blockquote {
font-style: italic;
display: block;
margin-left: 30px;
}
code {
padding-left: 0.2rem;
padding-right: 0.2rem;
background-color: var(--light-bg);
margin: 0;
border-radius: 3px;
}
pre {
display: block;
margin-top: 20px;
background-color: var(--light-bg);
padding-left: 10px;
padding-top: 5px;
padding-bottom: 5px;
width: 100%;
border-radius: 3px;
}
pre code {
padding: 0;
}
pre code .space {
color: var(--light-bg);
}
pre.table {
background-color: var(--backgroud);
}
pre.table code {
background-color: var(--backgroud);
padding: 0;
margin: 0;
}
kbd {
padding: 0 5px;
background-color: var(--very-light-bg);
font-family: "Roboto Mono","Courier New",Courier,monospace;
}

BIN
demo.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

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):
if not (os.path.dirname(__file__) in view.file_name() and
view.file_name().endswith('.py')):
return
sublime.run_command('reload_plugin', {
'main': os.path.join(sublime.packages_path(), 'MarkdownLivePreview',
'MarkdownLivePreview.py'),
'scripts': ['image_manager', 'functions', 'MLPApi',
'setting_names'],
'folders': ['lib'],
'quiet': True
})

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
viewBox="0 0 99.999996 100"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="MarkdownLivePreview.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#2196f3"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="36.574985"
inkscape:cy="6.9424003"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
showborder="true"
inkscape:window-width="1920"
inkscape:window-height="1018"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-349.87364,-434.14672)">
<g
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:1;fill:#e1ecff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="text7027"
transform="translate(-0.13506381,-12.064395)">
<path
d="m 352.04084,461.61747 12.16825,0 10.2962,16.08783 10.29621,-16.08783 12.46076,0 0,41.36034 -13.39678,0 0,-22.46445 -9.65269,16.35108 -2.83731,0 -9.97445,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
style="fill:#e1ecff;fill-opacity:1;stroke:none"
id="path7039"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(-1,0,0,1,-0.13506381,-12.064395)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:1;fill:#b9d5ff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="text7031">
<path
d="m -447.97656,461.61747 12.16825,0 10.29621,16.08783 10.29621,-16.08783 12.46075,0 0,41.36034 -13.39677,0 0,-22.46445 -9.6527,16.35108 -2.8373,0 -9.97446,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
style="fill:#b9d5ff;fill-opacity:1;stroke:none"
id="path7036"
inkscape:connector-curvature="0" />
</g>
<path
style="opacity:0.75;fill:#cde0ff;fill-opacity:1;stroke:none;stroke-width:1.00199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 375.63144,496.36803 24.28571,14.02137 24.28572,-14.02137 14.46428,0 -38.75,22.37233 -38.75,-22.37233 z"
id="path7081"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
viewBox="0 0 99.999996 100"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="MarkdownLivePreview.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.8"
inkscape:cx="36.574985"
inkscape:cy="6.9424003"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
showborder="true"
inkscape:window-width="1920"
inkscape:window-height="1018"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:showpageshadow="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-global="false" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-349.87364,-434.14672)">
<g
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:1;fill:#5599ff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="text7027"
transform="translate(-0.13506381,-12.064395)">
<path
d="m 352.04084,461.61747 12.16825,0 10.2962,16.08783 10.29621,-16.08783 12.46076,0 0,41.36034 -13.39678,0 0,-22.46445 -9.65269,16.35108 -2.83731,0 -9.97445,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
style="fill:#5599ff;fill-opacity:1;stroke:none"
id="path7039"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(-1,0,0,1,-0.13506381,-12.064395)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:59.90521622px;font-family:Candal;-inkscape-font-specification:Candal;text-align:start;text-anchor:start;opacity:0.75;fill:#5599ff;fill-opacity:1;stroke:#ef524f;stroke-width:3.75156426;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="text7031">
<path
d="m -447.97656,461.61747 12.16825,0 10.29621,16.08783 10.29621,-16.08783 12.46075,0 0,41.36034 -13.39677,0 0,-22.46445 -9.6527,16.35108 -2.8373,0 -9.97446,-16.35108 0,22.46445 -9.36019,0 0,-41.36034 z"
style="fill:#5599ff;fill-opacity:1;stroke:none"
id="path7036"
inkscape:connector-curvature="0" />
</g>
<path
style="opacity:0.75;fill:#aaccff;fill-opacity:1;stroke:none;stroke-width:1.00199997;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 375.63144,496.36803 24.28571,14.02137 24.28572,-14.02137 14.46428,0 -38.75,22.37233 -38.75,-22.37233 z"
id="path7081"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,152 +0,0 @@
# Welcome to MarkdownLivePreview's documentation!
<img src="imgs/MarkdownLivePreview.svg" alt="MarkdownLivePreview's logo"
style="width: 400px; margin: auto; display: block;">
MarkdownLivePreview is a [Sublime Text 3][st] plugin to preview your markdown as you type,
*right in Sublime Text itself*, without *any* dependency!
It's very easy to use, but there's a few things that you might want to be aware of... So, let's
get started
## Installation
### Using Package Control
You can really easily install MarkdownLivePreview by using [Package Control][pck-con].
If it's not already, you need to [install it][install-pck-con] first.
!!! note
If you're using the latest build of Sublime Text 3, you can just do
*Tools → Install Package Control…*
- Open up the command palette (<kbd>ctrl+shift+p</kbd>)
- Search up `Package Control: Install Package` (might take a few seconds)
- In the panel that just showed up, search for `MarkdownLivePreview`
Done! You have now access to every single features of MarkdownLivePreview! :wink:
### Using `git`
```sh
$ cd "%APPDATA%\Sublime Text 3\Packages" # on Windows
$ cd ~/Library/Application\ Support/Sublime\ Text\ 3 # on Mac
$ cd ~/.config/sublime-text-3 # on Linux
$ git clone "https://github.com/math2001/MarkdownLivePreview"
```
> So, which one do I pick?!
I depends of what you want to do. If you want to just use MarkdownLivePreview, pick the first
solution, you'll get every update automatically. But if you want to contribute, then choose the
second solution.
## Usage
### Previewing
As told in the introduction, MarkdownLivePreview is very easy to use:
- open a markdown file
- press <kbd>alt+m</kbd>
- or select in the command palette `MarkdownLivePreview: Edit Current File`
!!! note
The preview of unsaved markdown files is currently not supported. It should be fixed soon.
!!! tip
[Markdown Extended][] is supported too!
That's it. That's all you need to do to preview your markdown!
### Custom CSS
If you want to, you can add custom `CSS` to the MarkdownLivePreview's default stylesheet.
Just search for `MarkdownLivePreview: Edit Custom CSS File` in the command palette
(<kbd>ctrl+shift+p</kbd>). It will open a file in which you can add some CSS that will be *added* to
the normal CSS.
!!! bug
Comments in the CSS is interpreted weirdly by Sublime Text's phantoms. After a few tests, I
think that everything that is bellow a comment is ignored.
If you want to be sure that your CSS works, don't put any comments in it
#### Share your tweaks!
If you think that other users would enjoy your added CSS, then raise an issue, or PR the
[GitHub repo][] to share your tweaks!
### Clearing the cache
MarkdownLivePreview has a cache system to store images you load from internet. You can clear this
cache by searching up in the command palette `MarkdownLivePreview: Clear the cache`.
!!! tip
The cache is one simple file called `MarkdownLivePreviewCache`, which is located in your temp
folder. To know where it is, you can open the Sublime Text console (<kbd>ctrl+`</kbd> or
*View → Show Console*), and paste this in:
```python
import tempfile; print(tempfile.gettempdir())
```
### Custom settings for the preview
Sublime Text makes it easy to set custom settings for a specific *type* of view. For example,
`markdown`, `python`, etc. MarkdownLivePreview takes advantage of that: the preview view (the view
on the right) is a specific syntax (called — sorry for the originality —
`MarkdownLivePreviewSyntax`). So, to change this, you can focus the right view, open up the command
palette (<kbd>ctrl+shift+p</kbd>), and search up `Preferences: Settings — Syntax Specific`. In here,
you can specify any settings that is going to be applied only to this view.
### The hacky part
In fact, MarkdownLivePreview parses those settings, and looks for specific ones:
- `show_tabs`
- `show_minimap`
- `show_status_bar`
- `show_sidebar`
- `show_menus`
Those settings aren't supported by default because they affect the entire *window* instead of just
the view. But MarkdownLivePreview will look for them in your *preview*'s settings, and hide/show the
tabs, the minimap, etc...
As you probably guessed those settings takes a bool for value (`true` or `false`).
### Recommendation
Here's what I'd recommend (and use):
```json
{
"show_menus": false,
"show_tabs": false,
"show_minimap": false,
"gutter": false,
"rulers": [],
"word_wrap": true
}
```
!!! tip
On Windows at least, you can press <kbd>alt</kbd> to focus (so show) the menu, even if they're
originally hidden
That's it! I hope you'll enjoy using this package! If it's the case, please let your friends know
about it, and even myself by sending me a [tweet][] or staring the repo
<iframe
src="https://ghbtns.com/github-btn.html?user=math2001&repo=MarkdownLivePreview&type=star&count=true&size=large"
frameborder="0" scrolling="0" width="160px" height="30px"></iframe>!
[st]: https://sublimetext.com
[Markdown Extended]: https://packagecontrol.io/packages/Markdown%20Extended
[pck-con]: https://packagecontrol.io
[install-pck-con]: https://packagecontrol.io/installation
[tweet]: https://twitter.com/_math2001
[GitHub repo]: https://github.com/math2001/MarkdownLivePreview/issues

View File

@ -1,39 +0,0 @@
# -*- encoding: utf-8 -*-
import re
__all__ = ['escape_amp']
RE_REPLACE_AMPERSAND = re.compile(r'&(\w*)(;)?')
def replace(matchobj):
if matchobj.group(2):
return matchobj.group(0)
else:
return matchobj.group(0).replace('&', '&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()

View File

@ -1,98 +0,0 @@
# -*- encoding: utf-8 -*-
import base64
import os.path
import sublime
import re
from .image_manager import ImageManager
from bs4 import BeautifulSoup
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>"""
soup = BeautifulSoup(html)
for pre in soup.find_all('pre'):
code = pre.find('code')
code.replaceWith(BeautifulSoup(''.join(str(node) for node in pre.contents) \
.replace('\n', '<br/>').replace(' ', '<i class="space">.</i>'), 'html.parser'))
return str(soup).replace('<br/>', '<br />')

View File

@ -1,98 +0,0 @@
# -*- encoding: utf-8 -*-
import os.path
import tempfile
import sublime
from threading import Thread
import urllib.request, urllib.error
from .functions import *
CACHE_FILE = os.path.join(tempfile.gettempdir(),
'MarkdownLivePreviewCache.txt')
TIMEOUT = 20 # seconds
SEPARATOR = '---%cache%--'
def get_base64_saver(loading, url):
def callback(content):
if isinstance(content, urllib.error.HTTPError):
if content.getcode() == 404:
loading[url] = 404
return
elif isinstance(content, urllib.error.URLError):
if (content.reason.errno == 11001 and
content.reason.strerror == 'getaddrinfo failed'):
loading[url] = 404
return
return sublime.error_message('An unexpected error has occured: ' +
str(content))
loading[url] = to_base64(content=content)
return callback
def get_cache_for(imageurl):
if not os.path.exists(CACHE_FILE):
return
with open(CACHE_FILE) as fp:
for line in fp.read().splitlines():
url, base64 = line.split(SEPARATOR, 1)
if url == imageurl:
return base64
def cache(imageurl, base64):
with open(CACHE_FILE, 'a') as fp:
fp.write(imageurl + SEPARATOR + base64 + '\n')
class ImageLoader(Thread):
def __init__(self, url, callback):
Thread.__init__(self)
self.url = url
self.callback = callback
def run(self):
try:
page = urllib.request.urlopen(self.url, None, TIMEOUT)
except Exception as e:
self.callback(e)
else:
self.callback(page.read())
class ImageManager(object):
"""
Usage:
>>> image = ImageManager.get('http://domain.com/image.png')
>>> image = ImageManager.get('http://domain.com/image.png')
# still loading (this is a comment, no an outputed text), it doesn't
# run an other request
>>> image = ImageManager.get('http://domain.com/image.png')
'data:image/png;base64,....'
"""
loading = {}
@staticmethod
def get(imageurl, user_callback=None):
cached = get_cache_for(imageurl)
if cached:
return cached
elif imageurl in ImageManager.loading.keys():
# return None (the file is still loading, already made a request)
# return string the base64 of the url (which is going to be cached)
temp_cached = ImageManager.loading[imageurl]
if temp_cached == 404:
return to_base64('404.png')
if temp_cached:
cache(imageurl, temp_cached)
del ImageManager.loading[imageurl]
return temp_cached
else:
# load from internet
ImageManager.loading[imageurl] = None
callback = get_base64_saver(ImageManager.loading, imageurl)
loader = ImageLoader(imageurl, callback)
loader.start()
sublime.set_timeout_async(lambda: loader.join(), TIMEOUT * 1000)

View File

@ -1 +0,0 @@
There is images here, allthough they aren't of any use for the plugin. They're just the image that I used to generate the base64 (404.txt and loading.txt)

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
# -*- encoding: utf-8 -*-
"""
'pre_tables' transform *html* tables into markdown tables, and put them in some <pre><code> tags
"""
from bs4 import BeautifulSoup
def python_table(s_table):
"""Transform BeautifulSoup table into list of list"""
rows = []
for row in s_table.find_all('tr'):
# rows.append(list(map( lambda td: td.text, row.find_all(['th', 'td']) )))
rows.append(row.find_all(['th', 'td']))
return rows
def pre_table(s_table):
rows = python_table(s_table)
cols_width = [len(cell) for cell in rows[0]]
for j, row in enumerate(rows):
for i, cell in enumerate(row):
if cols_width[i] < len(cell.text):
cols_width[i] = len(cell.text)
text = '<pre class="table"><code>'
for i, row in enumerate(rows):
if i == 1:
for j, cell in enumerate(row):
text += '|' + '-' * (cols_width[j] + 2)
text += '|\n'
for j, cell in enumerate(row):
text += '| '
if cell.name == 'th':
title = ' ' * ((cols_width[j] - len(cell.text)) // 2) \
+ ''.join(str(node) for node in cell.contents) \
+ ' ' * int(round((cols_width[j] - len(cell.text)) / 2 ) + 1)
# + 1 because of the added space before the closing | of each cell
if cols_width[j] + 1 != len(title):
title += ' '
text += title
else:
text += ''.join(str(node) for node in cell.contents) \
+ ' ' * (cols_width[j] - len(cell.text) + 1)
text += '|\n'
text += '</pre></code>'
return text
def pre_tables(html):
soup = BeautifulSoup(html, 'html.parser')
for table in soup.find_all('table'):
table.replace_with(BeautifulSoup(pre_table(table), 'html.parser'))
return str(soup)

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

52
live-testing/test.md Normal file
View File

@ -0,0 +1,52 @@
# Why?
Why do you want to use fancy symbols in your standard monospace font? Obviously to have a fancy prompt like mine :-)
<!-- ![prompt](https://github.com/gabrielelana/awesome-terminal-fonts/raw/master/why.png) -->
And because when you live in a terminal a symbol can convey more informations in less space creating a dense and beautiful (for those who have a certain aesthetic taste) informative workspace
Heavily inspired by <https://github.com/Lokaltog/vim-powerline> and the relative patch script from **Kim Silkebækken** (kim.silkebaekken+vim@gmail.com)
## Patching vs Fallback
There are two strategies that could be used to have symbols in a terminal
* you can take a bunch of symbol fonts, your favourite monospace font and merge them together (patching strategy)
* you can use a feature of `freetype2` font engine, basically you can say that whenever the current font doesn't have a glyph for a certain codepoint then fallback and go look into other fonts (fallback strategy)
Initially I used the first strategy, later I switched to the second. The patching strategy it's more reliable and portable, the problem is that you need to patch every monospace font you want to use and patching a single font it's a lot of manual fine tuning. If you want you can find all previous patched fonts in [patching-strategy branch](https://github.com/gabrielelana/awesome-terminal-fonts/tree/patching-strategy)
## Font Maps
Referring to glyphs by codepints (eg. `\uf00c`) in your scripts or shell configuration it's not recommended because icon fonts like [Font Awesome](http://fontawesome.io/) use [code points ranges](https://en.wikipedia.org/wiki/Private_Use_Areas) those ranges are not disciplined by the unicode consortium, every font can associate every glyphs to those codepoints. This means that [Font Awesome](http://fontawesome.io/) can choose to move glyphs around freely, today `\uf00c` is associated to the `check` symbol, tomorrow it can be associated to something else. Moreover, more than one icon font can use the same codepoint for different glyphs and if we want to use them both we need to move one of them. So, if you use a codepoint to refer to a glyph after an update that codepoint can point to another glyph. To avoid this situation you can use the font maps in the `./build` directory, font maps are scripts which define shell variables that give names to glyphs, by sourcing those files in your shell you can refer to glyphs by name (eg. `$CODEPOINT_OF_AWESOME_CHECK`).
TLDR: don't refer to glyphs by codepoints (eg. `\uf00c`) but by name (eg. `$CODEPOINT_OF_AWESOME_CHECK`) to make your scripts and shell configurations resilient to future updates. To do that don't forget to copy font maps (`*.sh` files) in the `./build` directory in your home directory and to source them in your shell startup
## Included Fonts
In this repository you can find a bunch of fonts that I use as symbol fonts with the relative font maps
* **Font Awesome 4.7.0**: `./fonts/fontawesome-regular.ttf`, for further informations and license see http://fortawesome.github.io/Font-Awesome
* **Devicons 1.8.0**: `./fonts/devicons-regular.ttf`, for further informations and license see https://github.com/vorillaz/devicons
* **Octicons 1.0.0**: `./fonts/octicons-regular.ttf`, for further informations and license see https://github.com/blog/1135-the-making-of-octicons
* **Pomicons 1.0.0**: `./fonts/pomicons-regular.ttf`, for further informations and license see https://github.com/gabrielelana/pomicons
## How to install (Linux)
* copy all the fonts from `./build` directory to `~/.fonts` directory
* copy all the font maps (all `*.sh` files) from `./build` directory to `~/.fonts` directory
* run `fc-cache -fv ~/.fonts` to let freetype2 know of those fonts
* customize the configuration file `./config/10-symbols.conf` replacing `PragmataPro` with the name of the font you want to use in the terminal (I will add more fonts in the future so that this step could be skippable)
* copy the above configuration file to `~/.config/fontconfig/conf.d` directory
* source the font maps (`source ~/.fonts/*.sh`) in your shell startup script (eg. `~/.bashrc` or `~/.zshrc`)
### Arch Linux
We have been included in the [official repositories](https://www.archlinux.org/packages/community/any/awesome-terminal-fonts/), so if you are running an Arch Linux
* run `pacman -Syu awesome-terminal-fonts`
## How to install (OSX)
* follow [this detailed instructions](https://github.com/gabrielelana/awesome-terminal-fonts/wiki/OS-X) contributed by [@inkrement](https://github.com/inkrement)
* copy all the fonts maps (all `*.sh` files) from `./build` directory to `~/.fonts` directory
* source the font maps (`source ~/.fonts/*.sh`) in your shell startup script (eg. `~/.bashrc` or `~/.zshrc`)
* If it still doesn't work, consider to use the [patching strategy](#patching-vs-fallback)
## How to install (Windows)
* make sure you have permissions to execute Powershell scripts in your machine. To do so, open Windows Powershell as Administrator and paste & run the following command `Set-ExecutionPolicy RemoteSigned`
* then run the install script `./install.ps1`
## License
[MIT](https://github.com/gabrielelana/awesome-terminal-fonts/blob/master/LICENSE)

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=

226
markdown2html.py Normal file
View File

@ -0,0 +1,226 @@
""" 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 io
import struct
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", "cuddled-lists"])
# 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)
def markdown2html(markdown, basepath, re_render, resources, viewport_width, font_scale=1.0):
""" 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)))
base64, (width, height) = get_base64_image(path, re_render, resources)
img_element["src"] = base64
if width > viewport_width:
img_element["width"] = viewport_width
img_element["height"] = viewport_width * (height / width)
# 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?
# Add font scaling CSS rule
font_scale_css = "body {{ font-size: {}em; }}\n".format(font_scale)
stylesheet = font_scale_css + resources["stylesheet"]
return "<style>\n{}\n</style>\n\n{}".format(stylesheet, soup).replace(
"<br/>", "<br />"
)
images_cache = {}
images_loading = []
def get_base64_image(path, re_render, resources):
""" 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)
return base64_data, (width, height)
"""
def callback(path, resources, future):
# altering images_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
try:
images_cache[path] = future.result()
except urllib.error.HTTPError as e:
images_cache[path] = resources['base64_404_image']
print("Error loading {!r}: {!r}".format(path, e))
images_loading.remove(path)
# 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://"):
# FIXME: submiting a load of loaders, we should only have one
if path not in images_loading:
executor.submit(load_image, path).add_done_callback(partial(callback, path, resources))
images_loading.append(path)
return resources['base64_loading_image']
with open(path, "rb") as fhandle:
image_content = fhandle.read()
width, height = get_image_size(io.BytesIO(image_content), path)
image = "data:image/png;base64," + base64.b64encode(image_content).decode(
"utf-8"
)
images_cache[path] = image, (width, height)
return images_cache[path]
def load_image(url):
with urllib.request.urlopen(url, timeout=60) as conn:
image_content = conn.read()
width, height = get_image_size(io.BytesIO(image_content), url)
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(image_content).decode("utf-8"),
(width, height),
)
def get_image_size(fhandle, pathlike):
""" Thanks to https://stackoverflow.com/a/20380514/6164984 for providing the basis
of a working solution.
fhandle should be a seekable stream. It's not the best for non-seekable streams,
but in our case, we have to load the whole stream into memory anyway because base64
library only accepts bytes-like objects, and not streams.
pathlike is the filename/path/url of the image so that we can guess the file format
"""
format_ = os.path.splitext(os.path.basename(pathlike))[1][1:]
head = fhandle.read(24)
if len(head) != 24:
return "invalid head"
if format_ == "png":
check = struct.unpack(">i", head[4:8])[0]
if check != 0x0D0A1A0A:
return
width, height = struct.unpack(">ii", head[16:24])
elif format_ == "gif":
width, height = struct.unpack("<HH", head[6:10])
elif format_ == "jpeg":
try:
fhandle.seek(0) # Read 0xff next
size = 2
ftype = 0
while not 0xC0 <= ftype <= 0xCF:
fhandle.seek(size, 1)
byte = fhandle.read(1)
if byte == b"":
fhandle = end
byte = fhandle.read(1)
while ord(byte) == 0xFF:
byte = fhandle.read(1)
ftype = ord(byte)
size = struct.unpack(">H", fhandle.read(2))[0] - 2
# We are at a SOFn block
fhandle.seek(1, 1) # Skip `precision' byte.
height, width = struct.unpack(">HH", fhandle.read(4))
except Exception as e: # IGNORE:W0703
raise e
else:
return "unknown format {!r}".format(format_)
return width, height
def independent_markdown2html(markdown):
return markdown2html(
markdown,
".",
lambda: None,
{
"base64_404_image": ("", (0, 0)),
"base64_loading_image": ("", (0, 0)),
"stylesheet": "",
},
960,
1.0, # Add default font_scale for independent call
)

View File

@ -2,5 +2,6 @@
"install": "messages/install.txt", "install": "messages/install.txt",
"1.1.2": "messages/1.1.2.txt", "1.1.2": "messages/1.1.2.txt",
"2.0.1": "messages/2.0.1.txt", "2.0.1": "messages/2.0.1.txt",
"2.2.1": "messages/2.2.0.txt" "2.2.1": "messages/2.2.0.txt",
"2.4.1": "messages/2.4.txt"
} }

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'

View File

@ -1,35 +0,0 @@
site_name: MarkdownLivePreview
theme: material
repo_name: math2001/MarkdownLivePreview
repo_url: https://github.com/math2001/MarkdownLivePreview
site_description: Sublime Text 3 Plugin MarkdownLivePreview's documentation
site_author: math2001
markdown_extensions:
- toc(permalink=true)
- pymdownx.arithmatex
- pymdownx.betterem(smart_enable=all)
- pymdownx.caret
- pymdownx.critic
- pymdownx.emoji:
emoji_generator: !!python/name:pymdownx.emoji.to_svg
- pymdownx.inlinehilite
- pymdownx.magiclink
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.superfences
- pymdownx.tasklist(custom_checkbox=true)
- pymdownx.tilde
- admonition
- codehilite
extra:
logo: imgs/MarkdownLivePreview-opposite.svg
palette:
primary: Blue
accent: Indigo
social:
- type: github
link: https://github.com/math2001
- type: twitter
link: https://twitter.com/_math2001

19
repository.json Normal file
View File

@ -0,0 +1,19 @@
{
"schema_version": "3.0.0",
"packages": [
{
"name": "MarkdownLivePreview-Fork",
"description": "My enhanced live-preview fork of MarkdownLivePreview",
"author": "Christian Morpurgo",
"homepage": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview",
"releases": [
{
"version": "4.0.0",
"url": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview/archive/v4.0.0.zip",
"date": "2025-04-24 00:00:00",
"sublime_text": "*"
}
]
}
]
}

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,34 @@
""" A small script to convert the images into base64 data """
import struct
from base64 import b64encode
def get_image_size(fhandle):
"""https://stackoverflow.com/a/20380514/6164984"""
head = fhandle.read(24)
if len(head) != 24:
return
# always going to be png
check = struct.unpack(">i", head[4:8])[0]
if check != 0x0D0A1A0A:
raise ValueError("invalid check (?)")
width, height = struct.unpack(">ii", head[16:24])
return width, height
def make_cache(image_name):
with open("{}.png".format(image_name), "rb") as png, open(
"{}.base64".format(image_name), "wb"
) as base64:
width, height = get_image_size(png)
png.seek(0)
base64.write(bytes("{}\n{}\n".format(width, height), encoding="utf-8"))
base64.write(b'data:image/png;base64,')
base64.write(b64encode(png.read()))
make_cache("404")
make_cache("loading")

3
resources/loading.base64 Normal file

File diff suppressed because one or more lines are too long

BIN
resources/loading.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
resources/loading.xcf Normal file

Binary file not shown.

45
resources/stylesheet.css Normal file
View File

@ -0,0 +1,45 @@
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);
margin: 10px 0;
}
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

Before

Width:  |  Height:  |  Size: 953 B

After

Width:  |  Height:  |  Size: 953 B

View File

@ -1,36 +0,0 @@
# Hello world
Some `inline code` with *italic* and **bold** text.
```python
import this
if you is moods.curious:
print('then do it!')
```
<kbd>ctrl+\`</kbd> or *View → Show Console* and paste `import this`!
> Perfect programmers do NOT need comments.
- to be efficient
- you need
- todos
| ID | Name |
|-----------|-------|
| 56 | Matt |
| 42 | Colin |
| 23 | Lisa |
| 45 | John |
| `<table>` | `><` |
[Sublime Text Logo](https://upload.wikimedia.org/wikipedia/en/4/4c/Sublime_Text_Logo.png)
Some plugin I just *need*:
- [PackageResourceReviewer](https://packagecontrol.io/packages/PackageResourceViewer)
- [Boxy Theme](https://packagecontrol.io/packages/Boxy%20Theme)
- [Markdown Preview](https://packagecontrol.io/packages/Markdown%20Preview)
- [FileManager](https://packagecontrol.io/packages/FileManager)
- [PlainTasks](https://packagecontrol.io/packages/PlainTasks)
- [JSONComma](https://packagecontrol.io/packages/JSONComma)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

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'
PREVIEW_WINDOW = 'markdown_live_preview_window'