Compare commits

..

58 Commits

Author SHA1 Message Date
f4e6cd4ab0 Update README.md 2025-05-06 06:51:29 +00:00
c90d3071ea Update README.md 2025-05-06 06:51:06 +00:00
9de735aace Update package 2025-04-25 10:05:20 +02:00
786a72126c fix attribute error 2025-04-25 10:05:17 +02:00
70022a9b6c Update package 2025-04-25 10:03:02 +02:00
1f7b68e432 potential fix for same window preview 2025-04-25 10:02:58 +02:00
507a7e5d92 Update package 2025-04-25 10:00:08 +02:00
cc5d737c16 add logging for debugging 2025-04-25 10:00:03 +02:00
a7116d7206 Update package 2025-04-25 09:55:16 +02:00
4563a0f653 potentially fixes view in same window 2025-04-25 09:55:12 +02:00
e7a15ea070 Update package 2025-04-25 09:51:21 +02:00
34f8b3d733 open view in current window not in new one 2025-04-25 09:51:10 +02:00
e6a880d2a4 Merge branch 'master' of git.0x42.cloud:christian.morpurgo/MarkdownLivePreview 2025-04-24 20:33:02 +02:00
9cb5e2087f Update package 2025-04-24 20:32:52 +02:00
20dc7da4e8 Update package 2025-04-24 20:32:22 +02:00
0413ecca4b fix resourcxe path if repository name is different 2025-04-24 20:32:19 +02:00
37660abe64 Update repository.json 2025-04-24 18:29:07 +00:00
c2093e19aa Update package 2025-04-24 20:25:57 +02:00
b9ee2819e3 adjust scxale added more tags 2025-04-24 20:25:49 +02:00
99a4f21be3 Update package 2025-04-24 20:20:12 +02:00
edb424de99 font scale fix 2025-04-24 20:20:08 +02:00
d89e34f3e2 Update package 2025-04-24 20:18:52 +02:00
ecfd19dd85 potential fix for font scaling 2025-04-24 20:18:48 +02:00
69c722a933 Update package 2025-04-24 20:14:13 +02:00
7c37a9e413 potential fix for fontscaling 2025-04-24 20:14:10 +02:00
89d712fcce Update package 2025-04-24 20:11:22 +02:00
9f243f09c9 fix 2025-04-24 20:11:19 +02:00
ce768dce10 Update package 2025-04-24 20:09:01 +02:00
731f2def96 loggin 2025-04-24 20:08:56 +02:00
b3ffe8bf55 Update package 2025-04-24 20:05:55 +02:00
8576d4a631 Update package 2025-04-24 20:02:45 +02:00
49329d1f64 potential fix for font_scale 2025-04-24 20:02:39 +02:00
21b67f5b86 Update package 2025-04-24 20:00:49 +02:00
837979232e fix because we are using python 3.3 sic 2025-04-24 20:00:38 +02:00
547225ac4d Update package 2025-04-24 19:59:00 +02:00
e05516ab22 log font_scale 2025-04-24 19:58:47 +02:00
bbb90a8a97 Update package 2025-04-24 19:55:01 +02:00
598e22002b potential fix for font scale not working 2025-04-24 19:54:50 +02:00
e81b359294 Update package 2025-04-24 19:48:09 +02:00
8709e88fbd Update package 2025-04-24 19:46:51 +02:00
b94ad5856d fix import 2025-04-24 19:46:39 +02:00
3004ab4b41 Update package 2025-04-24 19:43:26 +02:00
14f6474cd5 fixing import error 2025-04-24 19:43:14 +02:00
d0323405c0 Update package 2025-04-24 19:40:38 +02:00
661f5b8911 fix impoert for soupsieve 2025-04-24 19:40:29 +02:00
3947b4cc4d Update package 2025-04-24 19:36:33 +02:00
2b35cf5000 add test_import to be ignored on export 2025-04-24 19:36:25 +02:00
72c684e89c Update package 2025-04-24 19:32:11 +02:00
ae1ea101d2 update package 2025-04-24 19:32:01 +02:00
3dd7b5a18d made bs4 and soupsieve standalone in this project 2025-04-24 19:26:20 +02:00
aefb27614f fully make bs4 and soupsieve standalone in the project 2025-04-24 17:39:41 +02:00
ed336866ee new build 2025-04-24 17:07:15 +02:00
afa554b52b Merge branch 'master' of git.0x42.cloud:christian.morpurgo/MarkdownLivePreview 2025-04-24 17:06:49 +02:00
e0daa147f1 fix import 2025-04-24 17:06:45 +02:00
f3cd6d3fcf Update repository.json 2025-04-24 15:04:58 +00:00
0034602fff new build 2025-04-24 17:04:04 +02:00
b29d054de2 new build 2025-04-24 16:49:41 +02:00
a9f2f951c0 update repo 2025-04-24 16:42:21 +02:00
23 changed files with 369 additions and 116 deletions

2
.gitattributes vendored
View File

@ -1,3 +1,5 @@
docs/ export-ignore docs/ export-ignore
resources/ resources/
!resources/*.base64 !resources/*.base64
test_imports.py export-ignore
MarkdownLivePreview.sublime-package export-ignore

View File

@ -1,8 +1,21 @@
import os, sys, sublime import os
pkg = os.path.basename(os.path.dirname(__file__)) import sys
lib = os.path.join(sublime.packages_path(), pkg, "lib") import sublime
if lib not in sys.path:
sys.path.insert(0, lib) # Add the package archive path itself to sys.path
package_path = os.path.dirname(__file__)
if package_path not in sys.path:
sys.path.insert(0, package_path)
# --- Add lib to sys.path ---
# Get the directory containing this file (MarkdownLivePreview.py)
plugin_dir = os.path.dirname(__file__)
# Construct the absolute path to the 'lib' directory
lib_path = os.path.join(plugin_dir, 'lib')
# Add it to the beginning of sys.path if it's not already there
if lib_path not in sys.path:
sys.path.insert(0, lib_path)
# --- End sys.path modification ---
""" """
Terminology Terminology
@ -38,7 +51,7 @@ def plugin_loaded():
resources["stylesheet"] = get_resource("stylesheet.css") resources["stylesheet"] = get_resource("stylesheet.css")
# FIXME: how could we make this setting update without restarting sublime text # FIXME: how could we make this setting update without restarting sublime text
# and not loading it every update as well # and not loading it every update as well
DELAY = get_settings().get(SETTING_DELAY_BETWEEN_UPDATES) DELAY = get_settings().get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default
class MdlpInsertCommand(sublime_plugin.TextCommand): class MdlpInsertCommand(sublime_plugin.TextCommand):
@ -49,6 +62,7 @@ class MdlpInsertCommand(sublime_plugin.TextCommand):
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand): class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
def run(self, edit): def run(self, edit):
print("--- MarkdownLivePreview: OpenMarkdownPreviewCommand running ---")
""" If the file is saved exists on disk, we close it, and reopen it in a new """ 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 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 """ a dialog) and re-insert it into a new view into a new window """
@ -56,22 +70,30 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
original_view = self.view original_view = self.view
original_window_id = original_view.window().id() original_window_id = original_view.window().id()
file_name = original_view.file_name() file_name = original_view.file_name()
print("--- MarkdownLivePreview: Original view ID: {}, File: {}, Window ID: {} ---".format(original_view.id(), file_name, original_window_id))
syntax_file = original_view.settings().get("syntax") syntax_file = original_view.settings().get("syntax")
if file_name: # don't close the original view; keep it in your main window:
original_view.close() # if file_name:
else: # original_view.close()
# the file isn't saved, we need to restore the content manually # 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
if not file_name:
# If the file isn't saved, we still need the content for the new view
total_region = sublime.Region(0, original_view.size()) total_region = sublime.Region(0, original_view.size())
content = original_view.substr(total_region) content = original_view.substr(total_region)
original_view.erase(edit, total_region) print("--- MarkdownLivePreview: Unsaved file, content length: {} ---".format(len(content)))
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") # instead of making a new window, grab your existing one:
preview_window = sublime.active_window() preview_window = original_view.window()
print("--- MarkdownLivePreview: Using existing window ID: {} ---".format(preview_window.id()))
preview_window.run_command( preview_window.run_command(
"set_layout", "set_layout",
@ -87,20 +109,44 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
preview_view.set_scratch(True) preview_view.set_scratch(True)
preview_view.settings().set(PREVIEW_VIEW_INFOS, {}) preview_view.settings().set(PREVIEW_VIEW_INFOS, {})
preview_view.set_name("Preview") preview_view.set_name("Preview")
print("--- MarkdownLivePreview: Created preview_view ID: {} ---".format(preview_view.id()))
# FIXME: hide number lines on preview # FIXME: hide number lines on preview
preview_window.focus_group(0) preview_window.focus_group(0)
if file_name: if file_name:
markdown_view = preview_window.open_file(file_name) markdown_view = preview_window.open_file(file_name)
print("--- MarkdownLivePreview: Opened markdown_view ID: {} for file: {} ---".format(markdown_view.id(), file_name))
else: else:
markdown_view = preview_window.new_file() markdown_view = preview_window.new_file()
markdown_view.run_command("mdlp_insert", {"point": 0, "string": content}) markdown_view.run_command("mdlp_insert", {"point": 0, "string": content})
markdown_view.set_scratch(True) markdown_view.set_scratch(True)
print("--- MarkdownLivePreview: Created new markdown_view ID: {} for unsaved content ---".format(markdown_view.id()))
markdown_view.set_syntax_file(syntax_file) markdown_view.set_syntax_file(syntax_file)
markdown_view.settings().set( markdown_view.settings().set(
MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id,}, MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id, "preview_view_id": preview_view.id(),},
) )
infos_to_log = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
print("--- MarkdownLivePreview: Stored infos on markdown_view {}: {} ---".format(markdown_view.id(), infos_to_log))
# Manually trigger the initial setup and render via the listener
# Use set_timeout_async to run it after the command finishes
print("--- MarkdownLivePreview: Scheduling setup_and_update_preview for md_view: {}, pv_view: {} ---".format(markdown_view.id(), preview_view.id()))
sublime.set_timeout_async(lambda: self.trigger_listener_setup(markdown_view.id(), preview_view.id()), 0)
# Helper method to find and call the listener instance method
# This is needed because the listener isn't easily accessible directly from the command instance
def trigger_listener_setup(self, md_view_id, pv_view_id):
print("--- MarkdownLivePreview: trigger_listener_setup running for md_view: {}, pv_view: {} ---".format(md_view_id, pv_view_id))
# Access the listener instance directly via its class variable
listener_instance = MarkdownLivePreviewListener.instance
if listener_instance:
print("--- MarkdownLivePreview: Found listener instance via class variable, calling setup_and_update_preview ---")
listener_instance.setup_and_update_preview(md_view_id, pv_view_id)
else:
# This should ideally not happen if the listener loaded correctly before the command ran
print("--- MarkdownLivePreview: ERROR: MarkdownLivePreviewListener.instance is None! Cannot trigger setup. ---")
def is_enabled(self): def is_enabled(self):
# FIXME: is this the best way there is to check if the current syntax is markdown? # FIXME: is this the best way there is to check if the current syntax is markdown?
@ -111,6 +157,7 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
class MarkdownLivePreviewListener(sublime_plugin.EventListener): class MarkdownLivePreviewListener(sublime_plugin.EventListener):
instance = None # Class variable to hold the single instance
phantom_sets = { phantom_sets = {
# markdown_view.id(): phantom set # markdown_view.id(): phantom set
@ -120,6 +167,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
# then, we update only if now() - last_update > DELAY # then, we update only if now() - last_update > DELAY
last_update = 0 last_update = 0
def __init__(self):
super().__init__() # Good practice to call super
MarkdownLivePreviewListener.instance = self
print("--- MarkdownLivePreview: Listener instance created and registered. ---")
# FIXME: maybe we shouldn't restore the file in the original window... # FIXME: maybe we shouldn't restore the file in the original window...
def on_pre_close(self, markdown_view): def on_pre_close(self, markdown_view):
@ -136,18 +188,30 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
if self.file_name is None: if self.file_name is None:
total_region = sublime.Region(0, markdown_view.size()) total_region = sublime.Region(0, markdown_view.size())
self.content = markdown_view.substr(total_region) self.content = markdown_view.substr(total_region)
markdown_view.erase(edit, total_region)
else: else:
self.content = None self.content = None
def on_load_async(self, markdown_view): def on_load_async(self, markdown_view):
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos: if not infos:
# print("--- MarkdownLivePreview: on_load_async ignored for view {} - no infos ---".format(markdown_view.id())) # Optional: very verbose
return return
preview_view = markdown_view.window().active_view_in_group(1) print("--- MarkdownLivePreview: on_load_async triggered for markdown_view {} ---".format(markdown_view.id()))
preview_view_id = infos.get("preview_view_id")
if not preview_view_id:
print("--- MarkdownLivePreview: ERROR in on_load_async: No preview_view_id found in infos: {} ---".format(infos))
return # Should not happen if setup was correct
self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view) preview_view = sublime.View(preview_view_id)
if not preview_view.is_valid():
print("--- MarkdownLivePreview: ERROR in on_load_async: Preview view {} is no longer valid ---".format(preview_view_id))
return # Preview view was closed before loading finished
print("--- MarkdownLivePreview: on_load_async found valid preview_view {} ---".format(preview_view.id()))
# PhantomSet creation is now handled by setup_and_update_preview triggered from the command
# self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
# print("--- MarkdownLivePreview: PhantomSet created in on_load_async for preview_view {} ---".format(preview_view.id())) # Keep log commented
self._update_preview(markdown_view) self._update_preview(markdown_view)
def on_close(self, markdown_view): def on_close(self, markdown_view):
@ -157,13 +221,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
if not infos: if not infos:
return return
assert ( if markdown_view.id() in self.phantom_sets:
markdown_view.id() == self.markdown_view.id()
), "pre_close view.id() != close view.id()"
del self.phantom_sets[markdown_view.id()] del self.phantom_sets[markdown_view.id()]
self.preview_window.run_command("close_window") # don't close the entire window—just let the user close the preview tab:
# self.preview_window.run_command("close_window")
# find the window with the right id # find the window with the right id
original_window = next( original_window = next(
@ -194,14 +256,17 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
# @min_time_between_call(.5) # @min_time_between_call(.5)
def on_modified_async(self, markdown_view): def on_modified_async(self, markdown_view):
# print("--- MarkdownLivePreview: on_modified_async triggered for view {} ---".format(markdown_view.id())) # Optional: very verbose
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos: if not infos:
return return
print("--- MarkdownLivePreview: Scheduling update for markdown_view {} ---".format(markdown_view.id()))
# we schedule an update, which won't run if an # we schedule an update, which won't run if an
sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY) sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY)
def _update_preview(self, markdown_view): def _update_preview(self, markdown_view):
print("--- MarkdownLivePreview: _update_preview called for markdown_view {} ---".format(markdown_view.id()))
# if the buffer id is 0, that means that the markdown_view has been closed # 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 # 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 # are loaded from the internet (ie. it could finish loading *after* the user
@ -210,25 +275,53 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
settings = get_settings() settings = get_settings()
delay = settings.get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default delay = settings.get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default
font_scale = settings.get(SETTING_FONT_SCALE, 1.0) # Provide default font_scale = settings.get(SETTING_FONT_SCALE, 1.0) # Provide default
print("--- MarkdownLivePreview: Using font_scale: {} ---".format(font_scale))
if time.time() - self.last_update < delay / 1000: if time.time() - self.last_update < delay / 1000:
print("--- MarkdownLivePreview: Update skipped for view {} due to time delay ---".format(markdown_view.id()))
return return
if markdown_view.buffer_id() == 0: if markdown_view.buffer_id() == 0:
print("--- MarkdownLivePreview: Update skipped for view {}: buffer_id is 0 (view closed) ---".format(markdown_view.id()))
return return
# Check if the phantom set still exists for this view ID
if markdown_view.id() not in self.phantom_sets:
print("--- MarkdownLivePreview: Update skipped for view {}: No phantom set found ---".format(markdown_view.id()))
# View might have been closed between modification and update
return
print("--- MarkdownLivePreview: Update proceeding for view {} ---".format(markdown_view.id()))
self.last_update = time.time() self.last_update = time.time()
total_region = sublime.Region(0, markdown_view.size()) total_region = sublime.Region(0, markdown_view.size())
markdown = markdown_view.substr(total_region) markdown = markdown_view.substr(total_region)
print("--- MarkdownLivePreview: Read markdown content (length: {}) ---".format(len(markdown)))
preview_view = markdown_view.window().active_view_in_group(1) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
print("--- MarkdownLivePreview: ERROR in _update_preview: No infos found for view {} ---".format(markdown_view.id()))
return # Should not happen
preview_view_id = infos.get("preview_view_id")
if not preview_view_id:
print("--- MarkdownLivePreview: ERROR in _update_preview: No preview_view_id found in infos: {} ---".format(infos))
return # Should not happen
preview_view = sublime.View(preview_view_id)
if not preview_view.is_valid():
print("--- MarkdownLivePreview: ERROR in _update_preview: Preview view {} is no longer valid ---".format(preview_view_id))
return # Preview view was closed
print("--- MarkdownLivePreview: Found valid preview_view {} for update ---".format(preview_view.id()))
# Get viewport_width, default to a large value if view isn't ready # Get viewport_width, default to a large value if view isn't ready
viewport_extent = preview_view.viewport_extent() viewport_extent = preview_view.viewport_extent()
viewport_width = viewport_extent[0] if viewport_extent else 1024 viewport_width = viewport_extent[0] if viewport_extent else 1024
print("--- MarkdownLivePreview: Viewport width: {} ---".format(viewport_width))
basepath = os.path.dirname(markdown_view.file_name()) if markdown_view.file_name() else '.' # Handle unsaved files basepath = os.path.dirname(markdown_view.file_name()) if markdown_view.file_name() else '.' # Handle unsaved files
print("--- MarkdownLivePreview: Calling markdown2html with basepath: {} ---".format(basepath))
html = markdown2html( html = markdown2html(
markdown, markdown,
basepath, basepath,
@ -238,6 +331,10 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
font_scale, font_scale,
) )
# Truncate HTML safely for logging
html_preview = html[:100].replace('\n', ' ') # Avoid breaking log lines
print("--- MarkdownLivePreview: Generated HTML (starts with): {}... ---".format(html_preview))
self.phantom_sets[markdown_view.id()].update( self.phantom_sets[markdown_view.id()].update(
[ [
sublime.Phantom( sublime.Phantom(
@ -248,6 +345,32 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
) )
] ]
) )
print("--- MarkdownLivePreview: Updated phantoms in preview_view {} ---".format(preview_view.id()))
def setup_and_update_preview(self, markdown_view_id, preview_view_id):
print("--- MarkdownLivePreview: setup_and_update_preview called for md_view: {}, pv_view: {} ---".format(markdown_view_id, preview_view_id))
markdown_view = sublime.View(markdown_view_id)
preview_view = sublime.View(preview_view_id)
if not markdown_view.is_valid():
print("--- MarkdownLivePreview: ERROR in setup_and_update: markdown_view {} is not valid ---".format(markdown_view_id))
return
if not preview_view.is_valid():
print("--- MarkdownLivePreview: ERROR in setup_and_update: preview_view {} is not valid ---".format(preview_view_id))
return
# Create PhantomSet if it doesn't exist (shouldn't at this point)
if markdown_view_id not in self.phantom_sets:
print("--- MarkdownLivePreview: Creating PhantomSet for preview_view {} in setup ---".format(preview_view.id()))
self.phantom_sets[markdown_view_id] = sublime.PhantomSet(preview_view)
else:
# This case might occur if the command is run multiple times rapidly? Ensure it's associated correctly.
print("--- MarkdownLivePreview: Warning: PhantomSet already existed for markdown_view {} in setup. Re-associating with preview_view {}. ---".format(markdown_view_id, preview_view.id()))
self.phantom_sets[markdown_view_id].view = preview_view # Ensure it points to the correct view
# Trigger the first update
print("--- MarkdownLivePreview: Triggering initial _update_preview from setup ---")
self._update_preview(markdown_view)
def get_settings(): def get_settings():
@ -255,11 +378,15 @@ def get_settings():
def get_resource(resource): def get_resource(resource):
path = "Packages/MarkdownLivePreview/resources/" + resource package_name = __package__ # Get the current package name dynamically
abs_path = os.path.join(sublime.packages_path(), "..", path) path = "Packages/{}/resources/{}".format(package_name, resource)
# Original logic: check absolute path first (useful for unpacked development)
# Adjusted abs_path to use dynamic package_name
abs_path = os.path.join(sublime.packages_path(), package_name, "resources", resource)
if os.path.isfile(abs_path): if os.path.isfile(abs_path):
with open(abs_path, "r") as fp: with open(abs_path, "r") as fp:
return fp.read() return fp.read()
# Fallback to sublime.load_resource (works for packed and unpacked)
return sublime.load_resource(path) return sublime.load_resource(path)

Binary file not shown.

View File

@ -1,5 +1,25 @@
# MarkdownLivePreview # MarkdownLivePreview
## Acknowledgments
This project is a fork of [MarkdownLivePreview](https://github.com/math2001/MarkdownLivePreview) by **math2001**. I'm grateful for the original implementation, which provided a solid foundation for live Markdown preview in Sublime Text.
Many thanks to math2001 for the original code—this fork wouldn't have been possible without their work.
## Changes contained in this Fork
In this fork ([christian.morpurgo/MarkdownLivePreview](https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview)), I've made the following enhancements:
- **Bundled `bs4` and `soupsieve`**
Repackaged both libraries as standalone modules by rewriting their imports, resolving errors when Sublime Text 4 attempted to install these old versions as external dependencies.
- **`font_scale` option**
Added a new `font_scale: number` setting to allow users to increase or decrease the preview text size directly from their Sublime Text settings.
- **In-window preview**
Changed the preview behavior so that starting a preview reuses the current window instead of opening a new one.
A simple plugin to preview your markdown as you type right in Sublime Text. A simple plugin to preview your markdown as you type right in Sublime Text.
No dependencies! No dependencies!
@ -31,7 +51,7 @@ could be working on, then there are a bunch of `FIXME`s all over this package.
Just pick one and fix it :-) Just pick one and fix it :-)
``` ```
$ git clone https://github.com/math2001/MarkdownLivePreview $ git clone https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview
$ cd MarkdownLivePreview $ cd MarkdownLivePreview
$ grep -R FIXME $ grep -R FIXME
``` ```

6
create_package.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
git archive --format=zip --prefix="" --output=MarkdownLivePreview.sublime-package master
git add MarkdownLivePreview.sublime-package
git commit -m "Update package"
git push origin master

View File

@ -4,7 +4,7 @@ __license__ = "MIT"
from collections import defaultdict from collections import defaultdict
import itertools import itertools
import sys import sys
from bs4.element import ( from ..element import (
CharsetMetaAttributeValue, CharsetMetaAttributeValue,
ContentMetaAttributeValue, ContentMetaAttributeValue,
Stylesheet, Stylesheet,

View File

@ -7,13 +7,13 @@ __all__ = [
import warnings import warnings
import re import re
from bs4.builder import ( from . import (
PERMISSIVE, PERMISSIVE,
HTML, HTML,
HTML_5, HTML_5,
HTMLTreeBuilder, HTMLTreeBuilder,
) )
from bs4.element import ( from ..element import (
NamespacedAttribute, NamespacedAttribute,
nonwhitespace_re, nonwhitespace_re,
) )
@ -22,7 +22,7 @@ from html5lib.constants import (
namespaces, namespaces,
prefixes, prefixes,
) )
from bs4.element import ( from ..element import (
Comment, Comment,
Doctype, Doctype,
NavigableString, NavigableString,
@ -120,7 +120,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
if soup: if soup:
self.soup = soup self.soup = soup
else: else:
from bs4 import BeautifulSoup from .. import BeautifulSoup
# TODO: Why is the parser 'html.parser' here? To avoid an # TODO: Why is the parser 'html.parser' here? To avoid an
# infinite loop? # infinite loop?
self.soup = BeautifulSoup( self.soup = BeautifulSoup(
@ -166,7 +166,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
return TextNode(Comment(data), self.soup) return TextNode(Comment(data), self.soup)
def fragmentClass(self): def fragmentClass(self):
from bs4 import BeautifulSoup from .. import BeautifulSoup
# TODO: Why is the parser 'html.parser' here? To avoid an # TODO: Why is the parser 'html.parser' here? To avoid an
# infinite loop? # infinite loop?
self.soup = BeautifulSoup("", "html.parser") self.soup = BeautifulSoup("", "html.parser")
@ -184,7 +184,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
return treebuilder_base.TreeBuilder.getFragment(self).element return treebuilder_base.TreeBuilder.getFragment(self).element
def testSerializer(self, element): def testSerializer(self, element):
from bs4 import BeautifulSoup from .. import BeautifulSoup
rv = [] rv = []
doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$') doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$')

View File

@ -34,16 +34,16 @@ CONSTRUCTOR_STRICT_IS_DEPRECATED = major == 3 and minor == 3
CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4 CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4
from bs4.element import ( from ..element import (
CData, CData,
Comment, Comment,
Declaration, Declaration,
Doctype, Doctype,
ProcessingInstruction, ProcessingInstruction,
) )
from bs4.dammit import EntitySubstitution, UnicodeDammit from ..dammit import EntitySubstitution, UnicodeDammit
from bs4.builder import ( from . import (
HTML, HTML,
HTMLTreeBuilder, HTMLTreeBuilder,
STRICT, STRICT,

View File

@ -14,14 +14,14 @@ except ImportError as e:
from io import BytesIO from io import BytesIO
from io import StringIO from io import StringIO
from lxml import etree from lxml import etree
from bs4.element import ( from ..element import (
Comment, Comment,
Doctype, Doctype,
NamespacedAttribute, NamespacedAttribute,
ProcessingInstruction, ProcessingInstruction,
XMLProcessingInstruction, XMLProcessingInstruction,
) )
from bs4.builder import ( from . import (
FAST, FAST,
HTML, HTML,
HTMLTreeBuilder, HTMLTreeBuilder,
@ -29,7 +29,7 @@ from bs4.builder import (
ParserRejectedMarkup, ParserRejectedMarkup,
TreeBuilder, TreeBuilder,
XML) XML)
from bs4.dammit import EncodingDetector from ..dammit import EncodingDetector
LXML = 'lxml' LXML = 'lxml'

View File

@ -6,9 +6,9 @@ __license__ = "MIT"
import cProfile import cProfile
from io import StringIO from io import StringIO
from html.parser import HTMLParser from html.parser import HTMLParser
import bs4 from . import BeautifulSoup as bs4
from bs4 import BeautifulSoup, __version__ from . import BeautifulSoup, __version__
from bs4.builder import builder_registry from .builder import builder_registry
import os import os
import pstats import pstats

View File

@ -9,14 +9,16 @@ import re
import sys import sys
import warnings import warnings
try: try:
import soupsieve # We are installed under the bs4 package
from soupsieve import *
except ImportError as e: except ImportError as e:
# We are installed standalone, or soupsieve is not installed.
soupsieve = None soupsieve = None
warnings.warn( warnings.warn(
'The soupsieve package is not installed. CSS selectors cannot be used.' 'The soupsieve package is not installed. CSS selectors cannot be used.'
) )
from bs4.formatter import ( from .formatter import (
Formatter, Formatter,
HTMLFormatter, HTMLFormatter,
XMLFormatter, XMLFormatter,
@ -380,7 +382,7 @@ class PageElement(object):
and not isinstance(new_child, NavigableString)): and not isinstance(new_child, NavigableString)):
new_child = NavigableString(new_child) new_child = NavigableString(new_child)
from bs4 import BeautifulSoup from . import BeautifulSoup
if isinstance(new_child, BeautifulSoup): if isinstance(new_child, BeautifulSoup):
# We don't want to end up with a situation where one BeautifulSoup # We don't want to end up with a situation where one BeautifulSoup
# object contains another. Insert the children one at a time. # object contains another. Insert the children one at a time.

View File

@ -1,4 +1,4 @@
from bs4.dammit import EntitySubstitution from .dammit import EntitySubstitution
class Formatter(EntitySubstitution): class Formatter(EntitySubstitution):
"""Describes a strategy to use when outputting a parse tree to a string. """Describes a strategy to use when outputting a parse tree to a string.

View File

@ -9,8 +9,8 @@ import copy
import functools import functools
import unittest import unittest
from unittest import TestCase from unittest import TestCase
from bs4 import BeautifulSoup from . import BeautifulSoup
from bs4.element import ( from .element import (
CharsetMetaAttributeValue, CharsetMetaAttributeValue,
Comment, Comment,
ContentMetaAttributeValue, ContentMetaAttributeValue,
@ -22,7 +22,7 @@ from bs4.element import (
Tag Tag
) )
from bs4.builder import HTMLParserTreeBuilder from .builder import HTMLParserTreeBuilder
default_builder = HTMLParserTreeBuilder default_builder = HTMLParserTreeBuilder
BAD_DOCUMENT = """A bare string BAD_DOCUMENT = """A bare string

View File

@ -3,21 +3,21 @@
import unittest import unittest
import warnings import warnings
from bs4 import BeautifulSoup from .. import BeautifulSoup
from bs4.builder import ( from ..builder import (
builder_registry as registry, builder_registry as registry,
HTMLParserTreeBuilder, HTMLParserTreeBuilder,
TreeBuilderRegistry, TreeBuilderRegistry,
) )
try: try:
from bs4.builder import HTML5TreeBuilder from ..builder import HTML5TreeBuilder
HTML5LIB_PRESENT = True HTML5LIB_PRESENT = True
except ImportError: except ImportError:
HTML5LIB_PRESENT = False HTML5LIB_PRESENT = False
try: try:
from bs4.builder import ( from ..builder import (
LXMLTreeBuilderForXML, LXMLTreeBuilderForXML,
LXMLTreeBuilder, LXMLTreeBuilder,
) )

View File

@ -3,12 +3,12 @@
import warnings import warnings
try: try:
from bs4.builder import HTML5TreeBuilder from ..builder import HTML5TreeBuilder
HTML5LIB_PRESENT = True HTML5LIB_PRESENT = True
except ImportError as e: except ImportError as e:
HTML5LIB_PRESENT = False HTML5LIB_PRESENT = False
from bs4.element import SoupStrainer from ..element import SoupStrainer
from bs4.testing import ( from ..testing import (
HTML5TreeBuilderSmokeTest, HTML5TreeBuilderSmokeTest,
SoupTest, SoupTest,
skipIf, skipIf,

View File

@ -3,9 +3,9 @@ trees."""
from pdb import set_trace from pdb import set_trace
import pickle import pickle
from bs4.testing import SoupTest, HTMLTreeBuilderSmokeTest from ..testing import SoupTest, HTMLTreeBuilderSmokeTest
from bs4.builder import HTMLParserTreeBuilder from ..builder import HTMLParserTreeBuilder
from bs4.builder._htmlparser import BeautifulSoupHTMLParser from ..builder._htmlparser import BeautifulSoupHTMLParser
class HTMLParserTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest): class HTMLParserTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest):

View File

@ -12,16 +12,16 @@ except ImportError as e:
LXML_VERSION = (0,) LXML_VERSION = (0,)
if LXML_PRESENT: if LXML_PRESENT:
from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML from ..builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
from bs4 import ( from .. import (
BeautifulSoup, BeautifulSoup,
BeautifulStoneSoup, BeautifulStoneSoup,
) )
from bs4.element import Comment, Doctype, SoupStrainer from ..element import Comment, Doctype, SoupStrainer
from bs4.testing import skipIf from ..testing import skipIf
from bs4.tests import test_htmlparser from . import test_htmlparser
from bs4.testing import ( from ..testing import (
HTMLTreeBuilderSmokeTest, HTMLTreeBuilderSmokeTest,
XMLTreeBuilderSmokeTest, XMLTreeBuilderSmokeTest,
SoupTest, SoupTest,

View File

@ -7,17 +7,17 @@ import unittest
import sys import sys
import tempfile import tempfile
from bs4 import ( from .. import (
BeautifulSoup, BeautifulSoup,
BeautifulStoneSoup, BeautifulStoneSoup,
GuessedAtParserWarning, GuessedAtParserWarning,
MarkupResemblesLocatorWarning, MarkupResemblesLocatorWarning,
) )
from bs4.builder import ( from ..builder import (
TreeBuilder, TreeBuilder,
ParserRejectedMarkup, ParserRejectedMarkup,
) )
from bs4.element import ( from ..element import (
CharsetMetaAttributeValue, CharsetMetaAttributeValue,
Comment, Comment,
ContentMetaAttributeValue, ContentMetaAttributeValue,
@ -27,13 +27,13 @@ from bs4.element import (
NavigableString, NavigableString,
) )
import bs4.dammit from ..dammit import *
from bs4.dammit import ( from ..dammit import (
EntitySubstitution, EntitySubstitution,
UnicodeDammit, UnicodeDammit,
EncodingDetector, EncodingDetector,
) )
from bs4.testing import ( from ..testing import (
default_builder, default_builder,
SoupTest, SoupTest,
skipIf, skipIf,
@ -41,7 +41,7 @@ from bs4.testing import (
import warnings import warnings
try: try:
from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML from ..builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
LXML_PRESENT = True LXML_PRESENT = True
except ImportError as e: except ImportError as e:
LXML_PRESENT = False LXML_PRESENT = False
@ -120,11 +120,10 @@ class TestConstructor(SoupTest):
def feed(self, *args, **kwargs): def feed(self, *args, **kwargs):
raise ParserRejectedMarkup("Nope.") raise ParserRejectedMarkup("Nope.")
def prepare_markup(self, *args, **kwargs): def prepare_markup(self, markup, *args, **kwargs):
# We're going to try two different ways of preparing this markup, # We're going to try two different ways of preparing this markup,
# but feed() will reject both of them. # but feed() will reject both of them.
yield markup, None, None, False yield markup, None, None, False
yield markup, None, None, False
import re import re
self.assertRaisesRegex( self.assertRaisesRegex(
@ -418,13 +417,13 @@ class TestEncodingConversion(SoupTest):
def test_ascii_in_unicode_out(self): def test_ascii_in_unicode_out(self):
# ASCII input is converted to Unicode. The original_encoding # ASCII input is converted to Unicode. The original_encoding
# attribute is set to 'utf-8', a superset of ASCII. # attribute is set to 'utf-8', a superset of ASCII.
chardet = bs4.dammit.chardet_dammit chardet = chardet_dammit
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
try: try:
def noop(str): def noop(str):
return None return None
# Disable chardet, which will realize that the ASCII is ASCII. # Disable chardet, which will realize that the ASCII is ASCII.
bs4.dammit.chardet_dammit = noop chardet_dammit = noop
ascii = b"<foo>a</foo>" ascii = b"<foo>a</foo>"
soup_from_ascii = self.soup(ascii) soup_from_ascii = self.soup(ascii)
unicode_output = soup_from_ascii.decode() unicode_output = soup_from_ascii.decode()
@ -433,7 +432,7 @@ class TestEncodingConversion(SoupTest):
self.assertEqual(soup_from_ascii.original_encoding.lower(), "utf-8") self.assertEqual(soup_from_ascii.original_encoding.lower(), "utf-8")
finally: finally:
logging.disable(logging.NOTSET) logging.disable(logging.NOTSET)
bs4.dammit.chardet_dammit = chardet chardet_dammit = chardet
def test_unicode_in_unicode_out(self): def test_unicode_in_unicode_out(self):
# Unicode input is left alone. The original_encoding attribute # Unicode input is left alone. The original_encoding attribute
@ -574,12 +573,12 @@ class TestUnicodeDammit(unittest.TestCase):
doc = b"""\357\273\277<?xml version="1.0" encoding="UTF-8"?> doc = b"""\357\273\277<?xml version="1.0" encoding="UTF-8"?>
<html><b>\330\250\330\252\330\261</b> <html><b>\330\250\330\252\330\261</b>
<i>\310\322\321\220\312\321\355\344</i></html>""" <i>\310\322\321\220\312\321\355\344</i></html>"""
chardet = bs4.dammit.chardet_dammit chardet = chardet_dammit
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
try: try:
def noop(str): def noop(str):
return None return None
bs4.dammit.chardet_dammit = noop chardet_dammit = noop
dammit = UnicodeDammit(doc) dammit = UnicodeDammit(doc)
self.assertEqual(True, dammit.contains_replacement_characters) self.assertEqual(True, dammit.contains_replacement_characters)
self.assertTrue("\ufffd" in dammit.unicode_markup) self.assertTrue("\ufffd" in dammit.unicode_markup)
@ -588,7 +587,7 @@ class TestUnicodeDammit(unittest.TestCase):
self.assertTrue(soup.contains_replacement_characters) self.assertTrue(soup.contains_replacement_characters)
finally: finally:
logging.disable(logging.NOTSET) logging.disable(logging.NOTSET)
bs4.dammit.chardet_dammit = chardet chardet_dammit = chardet
def test_byte_order_mark_removed(self): def test_byte_order_mark_removed(self):
# A document written in UTF-16LE will have its byte order marker stripped. # A document written in UTF-16LE will have its byte order marker stripped.
@ -613,7 +612,7 @@ class TestUnicodeDammit(unittest.TestCase):
self.assertRaises(UnicodeDecodeError, doc.decode, "utf8") self.assertRaises(UnicodeDecodeError, doc.decode, "utf8")
# Unicode, Dammit thinks the whole document is Windows-1252, # Unicode, Dammit thinks the whole document is Windows-1252,
# and decodes it into "☃☃☃Hi, I like Windows!☃☃☃" # and decodes it into "☃☃☃"Hi, I like Windows!"☃☃☃"
# But if we run it through fix_embedded_windows_1252, it's fixed: # But if we run it through fix_embedded_windows_1252, it's fixed:

View File

@ -14,12 +14,12 @@ import copy
import pickle import pickle
import re import re
import warnings import warnings
from bs4 import BeautifulSoup from .. import BeautifulSoup
from bs4.builder import ( from ..builder import (
builder_registry, builder_registry,
HTMLParserTreeBuilder, HTMLParserTreeBuilder,
) )
from bs4.element import ( from ..element import (
PY3K, PY3K,
CData, CData,
Comment, Comment,
@ -33,11 +33,11 @@ from bs4.element import (
Tag, Tag,
TemplateString, TemplateString,
) )
from bs4.testing import ( from ..testing import (
SoupTest, SoupTest,
skipIf, skipIf,
) )
from soupsieve import SelectorSyntaxError from ...soupsieve import SelectorSyntaxError
XML_BUILDER_PRESENT = (builder_registry.lookup("xml") is not None) XML_BUILDER_PRESENT = (builder_registry.lookup("xml") is not None)
LXML_PRESENT = (builder_registry.lookup("lxml") is not None) LXML_PRESENT = (builder_registry.lookup("lxml") is not None)

View File

@ -6,10 +6,13 @@ from .import css_types as ct
import unicodedata import unicodedata
from collections.abc import Sequence from collections.abc import Sequence
import bs4 from ..bs4 import BeautifulSoup
from ..bs4.element import (
Tag, NavigableString, Comment, Declaration, CData, ProcessingInstruction, Doctype
)
# Empty tag pattern (whitespace okay) # Empty tag pattern (whitespace okay)
RE_NOT_EMPTY = re.compile('[^ \t\r\n\f]') RE_NOT_EMPTY = re.compile(r'[^ \t\r\n\f]')
RE_NOT_WS = re.compile('[^ \t\r\n\f]+') RE_NOT_WS = re.compile('[^ \t\r\n\f]+')
@ -90,37 +93,37 @@ class _DocumentNav(object):
@staticmethod @staticmethod
def is_doc(obj): def is_doc(obj):
"""Is `BeautifulSoup` object.""" """Is `BeautifulSoup` object."""
return isinstance(obj, bs4.BeautifulSoup) return isinstance(obj, BeautifulSoup)
@staticmethod @staticmethod
def is_tag(obj): def is_tag(obj):
"""Is tag.""" """Is tag."""
return isinstance(obj, bs4.Tag) return isinstance(obj, Tag)
@staticmethod @staticmethod
def is_declaration(obj): # pragma: no cover def is_declaration(obj): # pragma: no cover
"""Is declaration.""" """Is declaration."""
return isinstance(obj, bs4.Declaration) return isinstance(obj, Declaration)
@staticmethod @staticmethod
def is_cdata(obj): def is_cdata(obj):
"""Is CDATA.""" """Is CDATA."""
return isinstance(obj, bs4.CData) return isinstance(obj, CData)
@staticmethod @staticmethod
def is_processing_instruction(obj): # pragma: no cover def is_processing_instruction(obj): # pragma: no cover
"""Is processing instruction.""" """Is processing instruction."""
return isinstance(obj, bs4.ProcessingInstruction) return isinstance(obj, ProcessingInstruction)
@staticmethod @staticmethod
def is_navigable_string(obj): def is_navigable_string(obj):
"""Is navigable string.""" """Is navigable string."""
return isinstance(obj, bs4.NavigableString) return isinstance(obj, NavigableString)
@staticmethod @staticmethod
def is_special_string(obj): def is_special_string(obj):
"""Is special string.""" """Is special string."""
return isinstance(obj, (bs4.Comment, bs4.Declaration, bs4.CData, bs4.ProcessingInstruction, bs4.Doctype)) return isinstance(obj, (Comment, Declaration, CData, ProcessingInstruction, Doctype))
@classmethod @classmethod
def is_content_string(cls, obj): def is_content_string(cls, obj):

View File

@ -11,11 +11,12 @@ import os.path
import concurrent.futures import concurrent.futures
import urllib.request import urllib.request
import base64 import base64
import bs4 from .lib.bs4 import BeautifulSoup as bs4
from .lib.bs4.element import Comment
from functools import partial from functools import partial
from markdown2 import Markdown from .lib.markdown2 import Markdown
__all__ = ("markdown2html",) __all__ = ("markdown2html",)
@ -33,7 +34,7 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
""" """
html = markdowner.convert(markdown) html = markdowner.convert(markdown)
soup = bs4.BeautifulSoup(html, "html.parser") soup = bs4(html, "html.parser")
for img_element in soup.find_all("img"): for img_element in soup.find_all("img"):
src = img_element["src"] src = img_element["src"]
@ -61,7 +62,7 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
# remove comments, because they pollute the console with error messages # remove comments, because they pollute the console with error messages
for comment_element in soup.find_all( for comment_element in soup.find_all(
text=lambda text: isinstance(text, bs4.Comment) text=lambda text: isinstance(text, Comment)
): ):
comment_element.extract() comment_element.extract()
@ -79,14 +80,61 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
.replace("\n", "<br />") .replace("\n", "<br />")
) )
code_element.replace_with(bs4.BeautifulSoup(fixed_pre, "html.parser")) code_element.replace_with(bs4(fixed_pre, "html.parser"))
# FIXME: highlight the code using Sublime's syntax # FIXME: highlight the code using Sublime's syntax
# Apply font scaling via inline styles
if font_scale != 1.0:
BASE_PX_SIZE = 15 # Base font size in pixels
TAG_MULTIPLIERS = {
'p': 1.0,
'li': 1.0,
'h1': 2.0,
'h2': 1.8,
'h3': 1.6,
'h4': 1.4,
'h5': 1.2,
'h6': 1.1,
'blockquote': 1.0,
'td': 1.0,
'th': 1.0,
'dt': 1.0,
'dd': 1.0,
'table': 1.0,
'tr': 1.0,
'ul': 1.0,
'ol': 1.0,
'code': 1.0,
'pre': 1.0,
'a': 1.0,
'strong': 1.0,
'em': 1.0,
's': 1.0,
'sup': 1.0,
'sub': 1.0,
'mark': 1.0,
'small': 1.0,
'big': 1.0,
'kbd': 1.0,
'samp': 1.0,
'var': 1.0,
'cite': 1.0,
'dfn': 1.0,
'abbr': 1.0,
'acronym': 1.0,
}
# Find all tags that we want to scale
for element in soup.find_all(list(TAG_MULTIPLIERS.keys())):
multiplier = TAG_MULTIPLIERS.get(element.name, 1.0)
target_size = round(BASE_PX_SIZE * multiplier * font_scale)
# Simple style setting (overwrites existing inline style if any)
# A more robust solution would parse and merge existing styles
element['style'] = "font-size: {}px;".format(target_size)
# FIXME: report that ST doesn't support <br/> but does work with <br />... WTF? # FIXME: report that ST doesn't support <br/> but does work with <br />... WTF?
# Add font scaling CSS rule stylesheet = resources["stylesheet"] # Use only the base stylesheet
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( return "<style>\n{}\n</style>\n\n{}".format(stylesheet, soup).replace(
"<br/>", "<br />" "<br/>", "<br />"
@ -194,13 +242,19 @@ def get_image_size(fhandle, pathlike):
fhandle.seek(size, 1) fhandle.seek(size, 1)
byte = fhandle.read(1) byte = fhandle.read(1)
if byte == b"": if byte == b"":
fhandle = end # Reached end of file unexpectedly, break the loop
byte = fhandle.read(1) break
while ord(byte) == 0xFF: while ord(byte) == 0xFF:
byte = fhandle.read(1) byte = fhandle.read(1)
if byte == b"": # Check EOF in inner loop too
break
if byte == b"": # Break outer loop if inner loop hit EOF
break
ftype = ord(byte) ftype = ord(byte)
size = struct.unpack(">H", fhandle.read(2))[0] - 2 size = struct.unpack(">H", fhandle.read(2))[0] - 2
# Check if the loop exited because of a break (EOF) before finding the marker
if not (0xC0 <= ftype <= 0xCF):
return "unknown format {!r}".format(format_)
# We are at a SOFn block # We are at a SOFn block
fhandle.seek(1, 1) # Skip `precision' byte. fhandle.seek(1, 1) # Skip `precision' byte.
height, width = struct.unpack(">HH", fhandle.read(4)) height, width = struct.unpack(">HH", fhandle.read(4))

View File

@ -2,14 +2,14 @@
"schema_version": "3.0.0", "schema_version": "3.0.0",
"packages": [ "packages": [
{ {
"name": "MarkdownLivePreview", "name": "MarkdownLivePreview-FORK",
"description": "My enhanced live-preview fork of MarkdownLivePreview", "description": "My enhanced live-preview fork of MarkdownLivePreview",
"author": "Christian Morpurgo", "author": "Christian Morpurgo",
"homepage": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview", "homepage": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview",
"releases": [ "releases": [
{ {
"version": "6.0.0", "version": "6.0.2",
"url": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview/releases/download/v6.0.0/MarkdownLivePreview.sublime-package", "url": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview/releases/download/v6.0.2/MarkdownLivePreview.sublime-package",
"date": "2025-04-24 00:00:00", "date": "2025-04-24 00:00:00",
"sublime_text": "*" "sublime_text": "*"
} }

40
test_imports.py Normal file
View File

@ -0,0 +1,40 @@
# test_imports.py
import sys
import os
# Optional: Explicitly add project root to path if needed,
# although running from the root often suffices.
# project_root = os.path.dirname(__file__)
# if project_root not in sys.path:
# sys.path.insert(0, project_root)
print("Attempting imports...")
try:
# Try importing the main entry point for bs4 from the lib structure
from lib.bs4 import BeautifulSoup
print("- Successfully imported BeautifulSoup from lib.bs4")
# Try creating a simple soup object (tests basic bs4 internal imports)
soup = BeautifulSoup("<a></a>", "html.parser")
print(f"- Created soup object: {soup.a}")
# Try importing the main entry point for soupsieve
from lib.soupsieve import compile as soupsieve_compile
print("- Successfully imported compile from lib.soupsieve")
# Try compiling a simple selector (tests basic soupsieve internal imports)
compiled = soupsieve_compile("a")
print(f"- Compiled selector: {compiled.pattern}")
# Try using the selector (tests soupsieve -> bs4 interaction)
match = compiled.select_one(soup)
print(f"- Selector match: {match}")
print("\nBasic import and usage tests passed!")
except ImportError as e:
print(f"\nImport Error: {e}")
print("Failed to import. Check paths and internal library structure.")
except Exception as e:
print(f"\nRuntime Error: {e}")
print("Imports might have worked, but usage failed.")