Skip to content

Commit 21a39c4

Browse files
committed
Manually merging upstream PR math2001#109
1 parent ec27d98 commit 21a39c4

File tree

4 files changed

+153
-329
lines changed

4 files changed

+153
-329
lines changed

Main.sublime-menu

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[
2+
{
3+
"id": "preferences",
4+
"children": [
5+
{
6+
"caption": "Package Settings",
7+
"id": "package-settings",
8+
"children": [
9+
{
10+
"caption": "MarkdownLivePreview settings",
11+
"command": "edit_settings",
12+
"args": {
13+
"base_file": "${packages}/MarkdownLivePreview/MarkdownLivePreview.sublime-settings",
14+
"default": "{\n\t$0\n}\n"
15+
}
16+
}
17+
]
18+
}
19+
]
20+
}
21+
]

MarkdownLivePreview.py

Lines changed: 84 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -6,256 +6,116 @@
66
original_window: the regular window
77
preview_window: the window with the markdown file and the preview
88
"""
9-
10-
import time
119
import os.path
12-
import struct
10+
import time
11+
from functools import partial
12+
1313
import sublime
1414
import sublime_plugin
1515

16-
from functools import partial
17-
1816
from .markdown2html import markdown2html
1917

20-
MARKDOWN_VIEW_INFOS = "markdown_view_infos"
21-
PREVIEW_VIEW_INFOS = "preview_view_infos"
22-
SETTING_DELAY_BETWEEN_UPDATES = "delay_between_updates"
23-
18+
PREVIEW_VIEW_INFO = "preview_view_info"
2419
resources = {}
2520

2621

27-
def plugin_loaded():
28-
global DELAY
29-
resources["base64_404_image"] = parse_image_resource(get_resource("404.base64"))
30-
resources["base64_loading_image"] = parse_image_resource(
31-
get_resource("loading.base64")
32-
)
33-
resources["stylesheet"] = get_resource("stylesheet.css")
34-
# FIXME: how could we make this setting update without restarting sublime text
35-
# and not loading it every update as well
36-
DELAY = get_settings().get(SETTING_DELAY_BETWEEN_UPDATES)
37-
38-
39-
class MdlpInsertCommand(sublime_plugin.TextCommand):
40-
def run(self, edit, point, string):
41-
self.view.insert(edit, point, string)
42-
43-
44-
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
45-
def run(self, edit):
46-
47-
""" If the file is saved exists on disk, we close it, and reopen it in a new
48-
window. Otherwise, we copy the content, erase it all (to close the file without
49-
a dialog) and re-insert it into a new view into a new window """
50-
51-
original_view = self.view
52-
original_window_id = original_view.window().id()
53-
file_name = original_view.file_name()
54-
55-
syntax_file = original_view.settings().get("syntax")
56-
57-
if file_name:
58-
original_view.close()
59-
else:
60-
# the file isn't saved, we need to restore the content manually
61-
total_region = sublime.Region(0, original_view.size())
62-
content = original_view.substr(total_region)
63-
original_view.erase(edit, total_region)
64-
original_view.close()
65-
# FIXME: save the document to a temporary file, so that if we crash,
66-
# the user doesn't lose what he wrote
67-
68-
sublime.run_command("new_window")
69-
preview_window = sublime.active_window()
70-
71-
preview_window.run_command(
72-
"set_layout",
73-
{
74-
"cols": [0.0, 0.5, 1.0],
75-
"rows": [0.0, 1.0],
76-
"cells": [[0, 0, 1, 1], [1, 0, 2, 1]],
77-
},
78-
)
79-
80-
preview_window.focus_group(1)
81-
preview_view = preview_window.new_file()
82-
preview_view.set_scratch(True)
83-
preview_view.settings().set(PREVIEW_VIEW_INFOS, {})
84-
preview_view.set_name("Preview")
85-
# FIXME: hide number lines on preview
86-
87-
preview_window.focus_group(0)
88-
if file_name:
89-
markdown_view = preview_window.open_file(file_name)
90-
else:
91-
markdown_view = preview_window.new_file()
92-
markdown_view.run_command("mdlp_insert", {"point": 0, "string": content})
93-
markdown_view.set_scratch(True)
22+
def find_preview(view):
23+
"""find previews for input view."""
24+
view_id = view.id()
25+
for x in view.window().views():
26+
d = x.settings().get(PREVIEW_VIEW_INFO)
27+
if d and d.get("id") == view_id:
28+
yield x
9429

95-
markdown_view.set_syntax_file(syntax_file)
96-
markdown_view.settings().set(
97-
MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id,},
98-
)
9930

100-
def is_enabled(self):
101-
# FIXME: is this the best way there is to check if the current syntax is markdown?
102-
# should we only support default markdown?
103-
# what about "md"?
104-
# FIXME: what about other languages, where markdown preview roughly works?
105-
return "markdown" in self.view.settings().get("syntax").lower()
31+
def get_resource(resource):
32+
path = "Packages/MarkdownLivePreview/resources/" + resource
33+
abs_path = os.path.join(sublime.packages_path(), "..", path)
34+
if os.path.isfile(abs_path):
35+
with open(abs_path, "r") as fp:
36+
return fp.read()
37+
return sublime.load_resource(path)
10638

10739

10840
class MarkdownLivePreviewListener(sublime_plugin.EventListener):
109-
110-
phantom_sets = {
111-
# markdown_view.id(): phantom set
112-
}
113-
114-
# we schedule an update for every key stroke, with a delay of DELAY
115-
# then, we update only if now() - last_update > DELAY
116-
last_update = 0
117-
118-
# FIXME: maybe we shouldn't restore the file in the original window...
119-
120-
def on_pre_close(self, markdown_view):
121-
""" Close the view in the preview window, and store information for the on_close
122-
listener (see doc there)
123-
"""
124-
if not markdown_view.settings().get(MARKDOWN_VIEW_INFOS):
125-
return
126-
127-
self.markdown_view = markdown_view
128-
self.preview_window = markdown_view.window()
129-
self.file_name = markdown_view.file_name()
130-
131-
if self.file_name is None:
132-
total_region = sublime.Region(0, markdown_view.size())
133-
self.content = markdown_view.substr(total_region)
134-
markdown_view.erase(edit, total_region)
41+
last_update = 0 # update only if now() - last_update > DELAY
42+
phantom_sets = {} # {preview.id(): PhantomSet}
43+
44+
def on_pre_close(self, view):
45+
"""Closing markdown files closes any associated previews."""
46+
if "markdown" in view.settings().get("syntax").lower():
47+
previews = list(find_preview(view))
48+
if previews:
49+
window = view.window()
50+
for preview in previews:
51+
window.focus_view(preview)
52+
window.run_command("close_file")
13553
else:
136-
self.content = None
137-
138-
def on_load_async(self, markdown_view):
139-
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
140-
if not infos:
141-
return
142-
143-
preview_view = markdown_view.window().active_view_in_group(1)
144-
145-
self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
146-
self._update_preview(markdown_view)
147-
148-
def on_close(self, markdown_view):
149-
""" Use the information saved to restore the markdown_view as an original_view
150-
"""
151-
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
152-
if not infos:
153-
return
154-
155-
assert (
156-
markdown_view.id() == self.markdown_view.id()
157-
), "pre_close view.id() != close view.id()"
158-
159-
del self.phantom_sets[markdown_view.id()]
160-
161-
self.preview_window.run_command("close_window")
162-
163-
# find the window with the right id
164-
original_window = next(
165-
window
166-
for window in sublime.windows()
167-
if window.id() == infos["original_window_id"]
168-
)
169-
if self.file_name:
170-
original_window.open_file(self.file_name)
171-
else:
172-
assert markdown_view.is_scratch(), (
173-
"markdown view of an unsaved file should " "be a scratch"
174-
)
175-
# note here that this is called original_view, because it's what semantically
176-
# makes sense, but this original_view.id() will be different than the one
177-
# that we closed first to reopen in the preview window
178-
# shouldn't cause any trouble though
179-
original_view = original_window.new_file()
180-
original_view.run_command(
181-
"mdlp_insert", {"point": 0, "string": self.content}
182-
)
183-
184-
original_view.set_syntax_file(markdown_view.settings().get("syntax"))
185-
186-
# here, views are NOT treated independently, which is theoretically wrong
187-
# but in practice, you can only edit one markdown file at a time, so it doesn't really
188-
# matter.
189-
# @min_time_between_call(.5)
190-
def on_modified_async(self, markdown_view):
191-
192-
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
193-
if not infos:
194-
return
195-
196-
# we schedule an update, which won't run if an
197-
sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY)
198-
199-
def _update_preview(self, markdown_view):
200-
# if the buffer id is 0, that means that the markdown_view has been closed
201-
# This check is needed since a this function is used as a callback for when images
202-
# are loaded from the internet (ie. it could finish loading *after* the user
203-
# closes the markdown_view)
54+
d = view.settings().get(PREVIEW_VIEW_INFO)
55+
if d:
56+
view_id = view.id()
57+
if view_id in self.phantom_sets:
58+
del self.phantom_sets[view_id]
59+
60+
def on_modified_async(self, view):
61+
"""Schedule an update when changing markdown files"""
62+
if "markdown" in view.settings().get("syntax").lower():
63+
sublime.set_timeout(partial(self.update_preview, view), DELAY)
64+
65+
def update_preview(self, view):
66+
# if the buffer id is 0, that means that the markdown_view has been
67+
# closed. This check is needed since a this function is used as a
68+
# callback for when images are loaded from the internet (ie. it could
69+
# finish loading *after* the user closes the markdown_view)
20470
if time.time() - self.last_update < DELAY / 1000:
20571
return
206-
207-
if markdown_view.buffer_id() == 0:
72+
if view.buffer_id() == 0:
73+
return
74+
previews = list(find_preview(view))
75+
if not previews:
20876
return
209-
21077
self.last_update = time.time()
78+
for preview in previews:
79+
html = markdown2html(
80+
view.substr(sublime.Region(0, view.size())),
81+
os.path.dirname(view.file_name()),
82+
partial(self.update_preview, view),
83+
resources,
84+
preview.viewport_extent()[0],
85+
)
86+
self.phantom_sets[preview.id()].update(
87+
[sublime.Phantom(sublime.Region(0), html, sublime.LAYOUT_BLOCK, lambda x: sublime.run_command("open_url", {"url": x}))]
88+
)
21189

212-
total_region = sublime.Region(0, markdown_view.size())
213-
markdown = markdown_view.substr(total_region)
214-
215-
preview_view = markdown_view.window().active_view_in_group(1)
216-
viewport_width = preview_view.viewport_extent()[0]
217-
218-
basepath = os.path.dirname(markdown_view.file_name())
219-
html = markdown2html(
220-
markdown,
221-
basepath,
222-
partial(self._update_preview, markdown_view),
223-
resources,
224-
viewport_width,
225-
)
226-
227-
self.phantom_sets[markdown_view.id()].update(
228-
[
229-
sublime.Phantom(
230-
sublime.Region(0),
231-
html,
232-
sublime.LAYOUT_BLOCK,
233-
lambda href: sublime.run_command("open_url", {"url": href}),
234-
)
235-
]
236-
)
237-
238-
239-
def get_settings():
240-
return sublime.load_settings("MarkdownLivePreview.sublime-settings")
24190

91+
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
92+
def run(self, edit):
93+
"""Set to multi-pane layout. Open markdown preview in another pane."""
94+
window = sublime.active_window()
95+
if window.num_groups() < 2:
96+
window.set_layout({"cols": [0.0, 0.5, 1.0], "rows": [0.0, 1.0], "cells": [[0, 0, 1, 1], [1, 0, 2, 1]]})
97+
window.focus_group(0 if window.active_group() else 1)
98+
view = window.new_file()
99+
view.set_scratch(True)
100+
view.set_name("Preview")
101+
view.settings().set(PREVIEW_VIEW_INFO, {"id": self.view.id()})
102+
ps = MarkdownLivePreviewListener.phantom_sets
103+
ps[view.id()] = sublime.PhantomSet(view)
104+
MarkdownLivePreviewListener().update_preview(self.view)
105+
window.focus_view(self.view)
242106

243-
def get_resource(resource):
244-
path = "Packages/MarkdownLivePreview/resources/" + resource
245-
abs_path = os.path.join(sublime.packages_path(), "..", path)
246-
if os.path.isfile(abs_path):
247-
with open(abs_path, "r") as fp:
248-
return fp.read()
249-
return sublime.load_resource(path)
107+
def is_enabled(self):
108+
return "markdown" in self.view.settings().get("syntax").lower()
250109

251110

252111
def parse_image_resource(text):
253112
width, height, base64_image = text.splitlines()
254113
return base64_image, (int(width), int(height))
255114

256115

257-
# try to reload the resources if we save this file
258-
try:
259-
plugin_loaded()
260-
except OSError:
261-
pass
116+
def plugin_loaded():
117+
global DELAY
118+
DELAY = sublime.load_settings("MarkdownLivePreview.sublime-settings").get("delay_between_updates")
119+
resources["base64_404_image"] = parse_image_resource(get_resource("404.base64"))
120+
resources["base64_loading_image"] = parse_image_resource(get_resource("loading.base64"))
121+
resources["stylesheet"] = get_resource("stylesheet.css")

MarkdownLivePreview.sublime-commands

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,5 @@
33
{
44
"caption": "MarkdownLivePreview: Open Preview",
55
"command": "open_markdown_preview"
6-
},
7-
{
8-
"caption": "MarkdownLivePreview: Open Settings",
9-
"command": "edit_settings", "args":
10-
{
11-
"base_file": "${packages}/MarkdownLivePreview/MarkdownLivePreview.sublime-settings",
12-
"default": "{\n\t$0\n}\n"
13-
},
146
}
157
]

0 commit comments

Comments
 (0)