From 0ba1dfccd0401c0b32bfa212f0e66b2066e15e4c Mon Sep 17 00:00:00 2001 From: Locke Birdsey Date: Wed, 3 Dec 2025 20:13:06 +0200 Subject: [PATCH 1/7] get 90% of the fixes in --- Mermaid-build.py | 92 ++++++++++++++++++++++++++++++++++++++++ Mermaid.sublime-build | 21 ++++----- mermaid.sublime-settings | 26 +++++++++++- 3 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 Mermaid-build.py diff --git a/Mermaid-build.py b/Mermaid-build.py new file mode 100644 index 0000000..f5b08dd --- /dev/null +++ b/Mermaid-build.py @@ -0,0 +1,92 @@ +import sublime +import sublime_plugin +import os +import sys +import subprocess +import time + +class MermaidBuildCommand(sublime_plugin.WindowCommand): + """Build command for Mermaid""" + + valid_output_types = ['md', 'svg', 'png', 'pdf'] + + def init_panel(self): + """Initialize the output panel.""" + if not hasattr(self, 'output_view'): + self.output_view = self.window.create_output_panel("mermaid") + + def puts(self, message): + """Output to panel.""" + message = message + '\n' + self.output_view.run_command('append', {'characters': message, 'force': True, 'scroll_to_end': True}) + + def run(self): + """Run the build and convert the Mermaid diagram.""" + view = self.window.active_view() + if not view: + return + start_time = time.time() + + self.init_panel() + settings = sublime.load_settings('mermaid.sublime-settings') + + # TODO how to get this to work + # show_panel_on_build = settings.get("show_panel_on_build", True) + # if show_panel_on_build: + # self.window.run_command("show_panel", {"panel": "output.mermaid"}) + + build_settings = settings.get('build') + # remove unused settings + pruned_build_settings = {k: v for k, v in build_settings.items() if v} + # set input file + pruned_build_settings["input"] = view.file_name() + # make output + output = os.path.splitext(view.file_name()) + outputFile = output[0] + print(pruned_build_settings) + if "outputFormat" not in pruned_build_settings: + outputFormat = "svg" + else: + outputFormat = pruned_build_settings["outputFormat"] + + outputFile = outputFile + '.' + outputFormat + pruned_build_settings["output"] = outputFile + + # handle the weird singleton flag + pdfFit = None + pdfFit = pruned_build_settings.pop("pdfFit") + + # joined flags + flags = [['--'+k, str(v)] for k, v in pruned_build_settings.items()] + print(flags) + + flattened_flags = ["mmdc"] + [x for i in flags for x in i] + if outputFormat.lower() == 'pdf' and pdfFit: + flattened_flags += ["--pdfFit"] + + print("running mmdc with flags: " + str(flattened_flags)) + # run mmdc + # TODO handle windows + p = subprocess.Popen(flattened_flags, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # stream stdout and stderr + while True: + output = p.stdout.readline() + if not output: + err = p.stderr.readline() + if not err: + break + else: + print(err.rstrip(), flush=True) + else: + print(output.rstrip(), flush=True) + + print("mmdc has finished generating. Rendered Mermaid is at "+outputFile) + + will_open = settings.get('open_in_default_app_after_build', False) + # TODO get list of opener handlers + if will_open: + print("opening in "+str(['xdg-open', outputFile])) + subprocess.Popen(['xdg-open', outputFile]) + + diff --git a/Mermaid.sublime-build b/Mermaid.sublime-build index 856e551..f2af722 100644 --- a/Mermaid.sublime-build +++ b/Mermaid.sublime-build @@ -1,13 +1,14 @@ { "selector" : "source.mermaid", - "cmd": [ - "mmdc", - "-i", "$file_name", - "-o", "$file_base_name.png", - //"-t", "dark", - "-b", "transparent", - "--width", "2048", - "--height", "2048", - "--scale", "2", - ], + "target": "mermaid_build", + // "cmd": [ + // "mmdc", + // "-i", "$file_name", + // "-o", "$file_base_name.png", + // //"-t", "dark", + // "-b", "transparent", + // "--width", "2048", + // "--height", "2048", + // "--scale", "2", + // ], } diff --git a/mermaid.sublime-settings b/mermaid.sublime-settings index dde3c0e..d5755b3 100644 --- a/mermaid.sublime-settings +++ b/mermaid.sublime-settings @@ -7,5 +7,29 @@ "quiet_graph_links": false, // The theme to use for view-in-browser. // Available: dark, default, forest, neutral. - "theme": "neutral" + "theme": "neutral", + + "build":{ + // The location where the rendered Mermaid diagram will be generated + // If left blank, it will be the same directory as the Mermaid file + "output": "", + // The format of the rendered Mermaid diagram + // Overrides the file extension used in `output`. Options are: 'svg', 'png', 'pdf' + "outputFormat": "svg", + // The width of the page (mmdc defaults to 800) + "width": 2048, + // The height of the page (mmdc defaults to 600) + "height": 2048, + // The scale factor used when rendering the Mermaid diagram (mmdc defaults to 1) + "scale": 2, + // The background color for PNGs and SVGs. Accepts CSS color names or hex values + "backgroundColor": "transparent", + // Theme of the chart. Options are 'default', 'forest', 'dark', 'neutral' + "theme": "default", + // The CSS file to use when rendering the Mermaid diagram + "cssFile": "", + // Scale the PDF to fit the chart (if using PDF output) + "pdfFit": true + }, + "open_in_default_app_after_build": true } From fad0f3cf331566aa4a15c0494ffe253a7ab8b3fc Mon Sep 17 00:00:00 2001 From: Locke Birdsey <5200346+LockeBirdsey@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:39:36 +0200 Subject: [PATCH 2/7] multi-platform openers --- Mermaid-build.py | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/Mermaid-build.py b/Mermaid-build.py index f5b08dd..7385fb9 100644 --- a/Mermaid-build.py +++ b/Mermaid-build.py @@ -30,7 +30,7 @@ def run(self): self.init_panel() settings = sublime.load_settings('mermaid.sublime-settings') - # TODO how to get this to work + # TODO how to get this to work and utilise quiet flag # show_panel_on_build = settings.get("show_panel_on_build", True) # if show_panel_on_build: # self.window.run_command("show_panel", {"panel": "output.mermaid"}) @@ -66,14 +66,32 @@ def run(self): print("running mmdc with flags: " + str(flattened_flags)) # run mmdc - # TODO handle windows + # TODO handle Windows p = subprocess.Popen(flattened_flags, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # stream stdout and stderr + handle_popen(p) + + print("mmdc has finished generating. Rendered Mermaid is at "+outputFile) + + will_open = settings.get('open_in_default_app_after_build', False) + if will_open: + if sys.platform == 'darwin': + opener = "open" + elif sys.platform == 'linux': + opener += "xdg-open" + elif sys.platform == 'win32': + opener = "Invoke-Item" + # debug + print("opening in "+str([opener, outputFile])) + p = subprocess.Popen([opener, outputFile]) + handle_popen(p) + + def handle_popen(handler): while True: - output = p.stdout.readline() + output = handler.stdout.readline() if not output: - err = p.stderr.readline() + err = handler.stderr.readline() if not err: break else: @@ -81,12 +99,3 @@ def run(self): else: print(output.rstrip(), flush=True) - print("mmdc has finished generating. Rendered Mermaid is at "+outputFile) - - will_open = settings.get('open_in_default_app_after_build', False) - # TODO get list of opener handlers - if will_open: - print("opening in "+str(['xdg-open', outputFile])) - subprocess.Popen(['xdg-open', outputFile]) - - From 2b5abc6a0d9148d8f8adc185a4c341357af0718a Mon Sep 17 00:00:00 2001 From: Locke Birdsey <5200346+LockeBirdsey@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:40:58 +0200 Subject: [PATCH 3/7] mmdc-location --- mermaid.sublime-settings | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mermaid.sublime-settings b/mermaid.sublime-settings index d5755b3..0d92df3 100644 --- a/mermaid.sublime-settings +++ b/mermaid.sublime-settings @@ -8,7 +8,8 @@ // The theme to use for view-in-browser. // Available: dark, default, forest, neutral. "theme": "neutral", - + // Location of the mermaid-cli executable. If let blank it will check the PATH + "mmdc-location": "", "build":{ // The location where the rendered Mermaid diagram will be generated // If left blank, it will be the same directory as the Mermaid file From 9ebc3817c0b1897fea40e27efe0ab7e6d7b256d6 Mon Sep 17 00:00:00 2001 From: Locke Birdsey <5200346+LockeBirdsey@users.noreply.github.com> Date: Wed, 3 Dec 2025 20:45:58 +0200 Subject: [PATCH 4/7] get mmdc from settings or default --- Mermaid-build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Mermaid-build.py b/Mermaid-build.py index 7385fb9..6066504 100644 --- a/Mermaid-build.py +++ b/Mermaid-build.py @@ -34,7 +34,8 @@ def run(self): # show_panel_on_build = settings.get("show_panel_on_build", True) # if show_panel_on_build: # self.window.run_command("show_panel", {"panel": "output.mermaid"}) - + + mmdc_cmd = settings.get('mmdc_location',"mmdc") build_settings = settings.get('build') # remove unused settings pruned_build_settings = {k: v for k, v in build_settings.items() if v} @@ -60,7 +61,7 @@ def run(self): flags = [['--'+k, str(v)] for k, v in pruned_build_settings.items()] print(flags) - flattened_flags = ["mmdc"] + [x for i in flags for x in i] + flattened_flags = [mmdc_cmd] + [x for i in flags for x in i] if outputFormat.lower() == 'pdf' and pdfFit: flattened_flags += ["--pdfFit"] From 1ef6c388078920914bc4a829cd2ede534ae87b7b Mon Sep 17 00:00:00 2001 From: Locke Birdsey Date: Thu, 4 Dec 2025 06:45:51 +0200 Subject: [PATCH 5/7] error output, tidy --- Mermaid-build.py | 174 ++++++++++++++++++--------------------- mermaid.sublime-settings | 11 +-- 2 files changed, 85 insertions(+), 100 deletions(-) diff --git a/Mermaid-build.py b/Mermaid-build.py index 6066504..83e94cd 100644 --- a/Mermaid-build.py +++ b/Mermaid-build.py @@ -5,98 +5,86 @@ import subprocess import time + class MermaidBuildCommand(sublime_plugin.WindowCommand): - """Build command for Mermaid""" - - valid_output_types = ['md', 'svg', 'png', 'pdf'] - - def init_panel(self): - """Initialize the output panel.""" - if not hasattr(self, 'output_view'): - self.output_view = self.window.create_output_panel("mermaid") - - def puts(self, message): - """Output to panel.""" - message = message + '\n' - self.output_view.run_command('append', {'characters': message, 'force': True, 'scroll_to_end': True}) - - def run(self): - """Run the build and convert the Mermaid diagram.""" - view = self.window.active_view() - if not view: - return - start_time = time.time() - - self.init_panel() - settings = sublime.load_settings('mermaid.sublime-settings') - - # TODO how to get this to work and utilise quiet flag - # show_panel_on_build = settings.get("show_panel_on_build", True) - # if show_panel_on_build: - # self.window.run_command("show_panel", {"panel": "output.mermaid"}) - - mmdc_cmd = settings.get('mmdc_location',"mmdc") - build_settings = settings.get('build') - # remove unused settings - pruned_build_settings = {k: v for k, v in build_settings.items() if v} - # set input file - pruned_build_settings["input"] = view.file_name() - # make output - output = os.path.splitext(view.file_name()) - outputFile = output[0] - print(pruned_build_settings) - if "outputFormat" not in pruned_build_settings: - outputFormat = "svg" - else: - outputFormat = pruned_build_settings["outputFormat"] - - outputFile = outputFile + '.' + outputFormat - pruned_build_settings["output"] = outputFile - - # handle the weird singleton flag - pdfFit = None - pdfFit = pruned_build_settings.pop("pdfFit") - - # joined flags - flags = [['--'+k, str(v)] for k, v in pruned_build_settings.items()] - print(flags) - - flattened_flags = [mmdc_cmd] + [x for i in flags for x in i] - if outputFormat.lower() == 'pdf' and pdfFit: - flattened_flags += ["--pdfFit"] - - print("running mmdc with flags: " + str(flattened_flags)) - # run mmdc - # TODO handle Windows - p = subprocess.Popen(flattened_flags, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - # stream stdout and stderr - handle_popen(p) - - print("mmdc has finished generating. Rendered Mermaid is at "+outputFile) - - will_open = settings.get('open_in_default_app_after_build', False) - if will_open: - if sys.platform == 'darwin': - opener = "open" - elif sys.platform == 'linux': - opener += "xdg-open" - elif sys.platform == 'win32': - opener = "Invoke-Item" - # debug - print("opening in "+str([opener, outputFile])) - p = subprocess.Popen([opener, outputFile]) - handle_popen(p) - - def handle_popen(handler): - while True: - output = handler.stdout.readline() - if not output: - err = handler.stderr.readline() - if not err: - break + """Build command for Mermaid""" + + def handle_popen(self, handler): + """Stream output from mermaid-cli""" + err_collector = [] + while True: + stdout = handler.stdout + if not stdout: + break + output = stdout.readline() + if not output: + err = handler.stderr.readline() + if not err: + break + else: + err_collector.append(err.decode().rstrip()) + else: + print(output.rstrip(), flush=True) + if len(err_collector) > 0: + sublime.error_message("Issue occurred when rendering Mermaid diagram:\n" + "\n".join(err_collector)) + return False + return True + + def run(self): + """Run the build and convert the Mermaid diagram.""" + view = self.window.active_view() + if not view: + return + + settings = sublime.load_settings("mermaid.sublime-settings") + + mmdc_cmd = settings.get("mmdc_location", "mmdc") + build_settings = settings.get("build") + # remove unused settings + pruned_build_settings = {k: v for k, v in build_settings.items() if v} + + # set input file + pruned_build_settings["input"] = view.file_name() + + # make output file + output = os.path.splitext(view.file_name()) + outputFile = output[0] + if "outputFormat" not in pruned_build_settings: + outputFormat = "svg" else: - print(err.rstrip(), flush=True) - else: - print(output.rstrip(), flush=True) - + outputFormat = pruned_build_settings["outputFormat"] + + outputFile = outputFile + "." + outputFormat + pruned_build_settings["output"] = outputFile + + # handle the weird singleton flag + pdfFit = None + pdfFit = pruned_build_settings.pop("pdfFit") + + # create flags then flatten for Popen arg + flags = [["--" + k, str(v)] for k, v in pruned_build_settings.items()] + flattened_flags = [mmdc_cmd] + [x for i in flags for x in i] + if outputFormat.lower() == "pdf" and pdfFit: + flattened_flags += ["--pdfFit"] + + # run mmdc + p = subprocess.Popen(flattened_flags, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + # stream stdout and stderr + success = self.handle_popen(p) + + if success: + print("mmdc has finished generating. Rendered Mermaid is at " + outputFile) + sublime.status_message("Build finished") + + will_open = settings.get("open_in_default_app_after_build", False) + if will_open: + if sys.platform == "darwin": + opener = "open" + elif sys.platform == "linux": + opener = "xdg-open" + elif sys.platform == "win32": + opener = "Invoke-Item" + + p = subprocess.Popen([opener, outputFile]) + self.handle_popen(p) diff --git a/mermaid.sublime-settings b/mermaid.sublime-settings index 0d92df3..94a774e 100644 --- a/mermaid.sublime-settings +++ b/mermaid.sublime-settings @@ -5,18 +5,15 @@ // Whether to dim links to reduce noise, // for certain graphs with more links. "quiet_graph_links": false, - // The theme to use for view-in-browser. - // Available: dark, default, forest, neutral. - "theme": "neutral", - // Location of the mermaid-cli executable. If let blank it will check the PATH - "mmdc-location": "", + // Absolute location of the mermaid-cli executable. If let blank it will check the PATH + "mmdc-location": "/usr/bin/mmdc", "build":{ // The location where the rendered Mermaid diagram will be generated // If left blank, it will be the same directory as the Mermaid file "output": "", // The format of the rendered Mermaid diagram // Overrides the file extension used in `output`. Options are: 'svg', 'png', 'pdf' - "outputFormat": "svg", + "outputFormat": "png", // The width of the page (mmdc defaults to 800) "width": 2048, // The height of the page (mmdc defaults to 600) @@ -26,7 +23,7 @@ // The background color for PNGs and SVGs. Accepts CSS color names or hex values "backgroundColor": "transparent", // Theme of the chart. Options are 'default', 'forest', 'dark', 'neutral' - "theme": "default", + "theme": "forest", // The CSS file to use when rendering the Mermaid diagram "cssFile": "", // Scale the PDF to fit the chart (if using PDF output) From 6f10112075f66f17e6e2b69080411ab8fec88173 Mon Sep 17 00:00:00 2001 From: Locke Birdsey Date: Thu, 4 Dec 2025 06:46:12 +0200 Subject: [PATCH 6/7] default options --- mermaid.sublime-settings | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mermaid.sublime-settings b/mermaid.sublime-settings index 94a774e..97825d9 100644 --- a/mermaid.sublime-settings +++ b/mermaid.sublime-settings @@ -2,11 +2,8 @@ // Default values for mermaid.sublime-settings. // { - // Whether to dim links to reduce noise, - // for certain graphs with more links. - "quiet_graph_links": false, // Absolute location of the mermaid-cli executable. If let blank it will check the PATH - "mmdc-location": "/usr/bin/mmdc", + "mmdc-location": "", "build":{ // The location where the rendered Mermaid diagram will be generated // If left blank, it will be the same directory as the Mermaid file @@ -23,11 +20,11 @@ // The background color for PNGs and SVGs. Accepts CSS color names or hex values "backgroundColor": "transparent", // Theme of the chart. Options are 'default', 'forest', 'dark', 'neutral' - "theme": "forest", + "theme": "default", // The CSS file to use when rendering the Mermaid diagram "cssFile": "", // Scale the PDF to fit the chart (if using PDF output) "pdfFit": true }, - "open_in_default_app_after_build": true + "open_in_default_app_after_build": false } From 35e703554ee7f32575089855cf3c836c07a6c344 Mon Sep 17 00:00:00 2001 From: Locke Birdsey Date: Thu, 4 Dec 2025 06:47:28 +0200 Subject: [PATCH 7/7] tidy --- Mermaid.sublime-build | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/Mermaid.sublime-build b/Mermaid.sublime-build index f2af722..2f4a03a 100644 --- a/Mermaid.sublime-build +++ b/Mermaid.sublime-build @@ -1,14 +1,4 @@ { "selector" : "source.mermaid", - "target": "mermaid_build", - // "cmd": [ - // "mmdc", - // "-i", "$file_name", - // "-o", "$file_base_name.png", - // //"-t", "dark", - // "-b", "transparent", - // "--width", "2048", - // "--height", "2048", - // "--scale", "2", - // ], + "target": "mermaid_build" }