🆕Multiple run modes, now including JAR and SERVER (#96)

It is now possible to support many execution modes, currently available are:

- `jar`: the traditional way of running PlantUML from `plantuml-mode`
- `server`: ask a [`plantuml-server`](https://github.com/plantuml/plantuml-server) instance to run the preview (defaults to https://www.plantuml.com/plantuml)
This commit is contained in:
Carlo Sciolla 2019-05-31 10:53:40 +02:00 committed by GitHub
parent 698a248549
commit 27d48942a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 268 additions and 110 deletions

View File

@ -35,6 +35,7 @@ You can also download the latest version of PlantUML straight into `plantuml-jar
- Syntax highlight
- Autocompletion
- Preview of buffer or region
- [EXPERIMENTAL] Use either local JAR or remote server for preview
# Enable the major mode
@ -88,6 +89,27 @@ Then you can edit a `plantuml` code block with `plantuml-mode` by hitting `C-'`
When in the `plantuml-mode` buffer you can then hit again `C-'` to return to the original `org-mode` document.
# Execution modes
**EXPERIMENTAL**
As of `v1.3.0` support is added for switching execution mode. The following two modes are available:
- `jar` (default) to run PlantUML as a local JAR file. This is the traditional system used by `plantuml-mode`
- `server` (experimental) to let an instance of [`plantuml-server`](https://github.com/plantuml/plantuml-server) render the preview
You can customize `plantuml-default-exec-mode` or run `plantuml-set-exec-mode` from a `plantuml-mode` buffer to switch modes.
## Configure server rendering
When selecting `server` execution modes, you can customize the following variable to set the server to use for rendering:
```
plantuml-server-url
```
It defaults to `"https://www.plantuml.com/plantuml"`.
# Migration from `puml-mode`
If you were previously using `puml-mode`, you should change any reference to a `puml-..` variable or function to its `plantuml-..` counterpart. Most notably, `puml-plantuml-jar-path` is now just `plantuml-jar-path`.

0
bin/download-plantuml.sh Normal file → Executable file
View File

View File

@ -37,6 +37,7 @@
;;; Change log:
;;
;; version 1.3.0, 2019-05-31 Added experimental support for multiple rendering modes and, specifically, preview using a PlantUML server
;; version 1.2.11, 2019-04-09 Added `plantuml-download-jar'
;; version 1.2.10, 2019-04-03 Avoid messing with window layouts and buffers -- courtesy of https://github.com/wailo
;; version 1.2.9, Revamped indentation support, now working with a greater number of keywords
@ -111,12 +112,24 @@
:type '(repeat string)
:group 'plantuml)
(defcustom plantuml-server-url "https://www.plantuml.com/plantuml"
"The base URL of the PlantUML server."
:type 'string
:group 'plantuml)
(defcustom plantuml-default-exec-mode 'server
"Default execution mode for PlantUML. Valid values are:
- `jar': run PlantUML as a JAR file (requires a local install of the PlantUML JAR file, see `plantuml-jar-path'"
:type 'symbol
:group 'plantuml
:options '(jar server))
(defcustom plantuml-suppress-deprecation-warning t
"To silence the deprecation warning when `puml-mode' is found upon loading."
:type 'boolean
:group 'plantuml)
(defun plantuml-render-command (&rest arguments)
(defun plantuml-jar-render-command (&rest arguments)
"Create a command line to execute PlantUML with arguments (as ARGUMENTS)."
(let* ((cmd-list (append plantuml-java-args (list (expand-file-name plantuml-jar-path)) plantuml-jar-args arguments))
(cmd (mapconcat 'identity cmd-list "|")))
@ -144,6 +157,25 @@
;; keyword completion
(defvar plantuml-kwdList nil "The plantuml keywords.")
;; PlantUML execution mode
(defvar-local plantuml-exec-mode plantuml-default-exec-mode
"The Plantuml execution mode. See `plantuml-default-exec-mode' for acceptable values.")
(defun plantuml-set-exec-mode (mode)
"Set the execution mode MODE for PlantUML."
(interactive (let* ((completion-ignore-case t)
(supported-modes '("jar" "server")))
(completing-read (format "Exec mode [%s]: " plantuml-exec-mode)
supported-modes
nil
t
nil
nil
plantuml-exec-mode)))
(if (member mode '("jar" "server"))
(setq plantuml-exec-mode (intern mode))
(error (concat "Unsupported mode:" mode))))
(defun plantuml-enable-debug ()
"Enables debug messages into the *PLANTUML Messages* buffer."
(interactive)
@ -185,20 +217,40 @@
(xml-node-children)
(first))))
(message (concat "Downloading PlantUML v" version " into " plantuml-jar-path))
(url-copy-file (format "https://search.maven.org/remotecontent?filepath=net/sourceforge/plantuml/plantuml/%s/plantuml-%s.jar" version version) plantuml-jar-path)
(kill-buffer))
(message "Aborted.")))
(url-copy-file (format "https://search.maven.org/remotecontent?filepath=net/sourceforge/plantuml/plantuml/%s/plantuml-%s.jar" version version) plantuml-jar-path t)
(kill-buffer)))
(message "Aborted."))
(message "Aborted.")))
(defun plantuml-init ()
"Initialize the keywords or builtins from the cmdline language output."
(defun plantuml-jar-get-language (buf)
"Retrieve the language specification from the PlantUML JAR file and paste it into BUF."
(unless (or (eq system-type 'cygwin) (file-exists-p plantuml-jar-path))
(error "Could not find plantuml.jar at %s" plantuml-jar-path))
(with-temp-buffer
(with-current-buffer buf
(let ((cmd-args (append (list plantuml-java-command nil t nil)
(plantuml-render-command "-language"))))
(plantuml-jar-render-command "-language"))))
(apply 'call-process cmd-args)
(goto-char (point-min)))
(goto-char (point-min)))))
(defun plantuml-server-get-language (buf)
"Retrieve the language specification from the PlantUML server and paste it into BUF."
(let ((lang-url (concat plantuml-server-url "/language")))
(with-current-buffer buf
(url-insert-file-contents lang-url))))
(defun plantuml-get-language (mode buf)
"Retrieve the language spec using the preferred PlantUML execution mode MODE. Paste the result into BUF."
(let ((get-fn (pcase mode
('jar #'plantuml-jar-get-language)
('server #'plantuml-server-get-language))))
(if get-fn
(funcall get-fn buf)
(error "Unsupported execution mode %s" mode))))
(defun plantuml-init (mode)
"Initialize the keywords or builtins from the cmdline language output. Use exec mode MODE to load the language details."
(with-temp-buffer
(plantuml-get-language mode (current-buffer))
(let ((found (search-forward ";" nil t))
(word "")
(count 0)
@ -236,10 +288,10 @@
(defvar plantuml-output-type
(if (not (display-images-p))
"utxt"
"txt"
(cond ((image-type-available-p 'svg) "svg")
((image-type-available-p 'png) "png")
(t "utxt")))
(t "txt")))
"Specify the desired output type to use for generated diagrams.")
(defun plantuml-read-output-type ()
@ -249,7 +301,7 @@
(append
(and (image-type-available-p 'svg) '("svg"))
(and (image-type-available-p 'png) '("png"))
'("utxt"))))
'("txt"))))
(completing-read (format "Output type [%s]: " plantuml-output-type)
available-types
nil
@ -267,57 +319,105 @@ default output type for new buffers."
(setq plantuml-output-type type))
(defun plantuml-is-image-output-p ()
"Return true if the diagram output format is an image, false if it's text based."
(not (equal "utxt" plantuml-output-type)))
"Return non-nil if the diagram output format is an image, false if it's text based."
(not (equal "txt" plantuml-output-type)))
(defun plantuml-output-type-opt ()
"Create the flag to pass to PlantUML to produce the selected output format."
(concat "-t" plantuml-output-type))
(defun plantuml-jar-output-type-opt (output-type)
"Create the flag to pass to PlantUML according to OUTPUT-TYPE.
Note that output type `txt' is promoted to `utxt' for better rendering."
(concat "-t" (pcase output-type
("txt" "utxt")
(_ output-type))))
(defun plantuml-start-process (buf)
(defun plantuml-jar-start-process (buf)
"Run PlantUML as an Emacs process and puts the output into the given buffer (as BUF)."
(apply #'start-process
"PLANTUML" buf plantuml-java-command
`(,@plantuml-java-args
,(expand-file-name plantuml-jar-path)
,(plantuml-output-type-opt)
,(plantuml-jar-output-type-opt plantuml-output-type)
,@plantuml-jar-args
"-p")))
(defun plantuml-preview-string (prefix string)
"Preview diagram from PlantUML sources (as STRING), using prefix (as PREFIX)
to choose where to display it:
(defun plantuml-update-preview-buffer (prefix buf)
"Show the preview in the preview buffer BUF.
Window is selected according to PREFIX:
- 4 (when prefixing the command with C-u) -> new window
- 16 (when prefixing the command with C-u C-u) -> new frame.
- else -> new buffer"
(let ((b (get-buffer plantuml-preview-buffer)))
(when b
(kill-buffer b)))
(let ((imagep (and (display-images-p)
(plantuml-is-image-output-p))))
(cond
((= prefix 16) (switch-to-buffer-other-frame buf))
((= prefix 4) (switch-to-buffer-other-window buf))
(t (display-buffer buf)))
(when imagep
(with-current-buffer buf
(image-mode)
(set-buffer-multibyte t)))))
(let* ((imagep (and (display-images-p)
(plantuml-is-image-output-p)))
(process-connection-type nil)
(buf (get-buffer-create plantuml-preview-buffer))
(coding-system-for-read (and imagep 'binary))
(coding-system-for-write (and imagep 'binary)))
(let ((ps (plantuml-start-process buf)))
(defun plantuml-jar-preview-string (prefix string buf)
"Preview the diagram from STRING by running the PlantUML JAR.
Put the result into buffer BUF. Window is selected according to PREFIX:
- 4 (when prefixing the command with C-u) -> new window
- 16 (when prefixing the command with C-u C-u) -> new frame.
- else -> new buffer"
(let* ((process-connection-type nil)
(ps (plantuml-jar-start-process buf)))
(process-send-string ps string)
(process-send-eof ps)
(set-process-sentinel ps
(lambda (_ps event)
(unless (equal event "finished\n")
(error "PLANTUML Preview failed: %s" event))
(cond
((= prefix 16)
(switch-to-buffer-other-frame plantuml-preview-buffer))
((= prefix 4)
(switch-to-buffer-other-window plantuml-preview-buffer))
(t (display-buffer plantuml-preview-buffer)))
(when imagep
(with-current-buffer plantuml-preview-buffer
(image-mode)
(set-buffer-multibyte t))))))))
(plantuml-update-preview-buffer prefix buf)))))
(defun plantuml-server-preview-string (prefix string buf)
"Preview the diagram from STRING as rendered by the PlantUML server.
Put the result into buffer BUF and place it according to PREFIX:
- 4 (when prefixing the command with C-u) -> new window
- 16 (when prefixing the command with C-u C-u) -> new frame.
- else -> new buffer"
(let* ((url-request-location (concat plantuml-server-url "/" plantuml-output-type "/-base64-" (base64-encode-string string))))
(save-current-buffer
(save-match-data
(url-retrieve url-request-location
(lambda (status)
;; TODO: error check
(goto-char (point-min))
;; skip the HTTP headers
(while (not (looking-at "\n"))
(forward-line))
(kill-region (point-min) (+ 1 (point)))
(copy-to-buffer buf (point-min) (point-max))
(plantuml-update-preview-buffer prefix buf)))))))
(defun plantuml-exec-mode-preview-string (prefix mode string buf)
"Preview the diagram from STRING using the execution mode MODE.
Put the result into buffer BUF, selecting the window according to PREFIX:
- 4 (when prefixing the command with C-u) -> new window
- 16 (when prefixing the command with C-u C-u) -> new frame.
- else -> new buffer"
(let ((preview-fn (pcase mode
('jar #'plantuml-jar-preview-string)
('server #'plantuml-server-preview-string))))
(if preview-fn
(funcall preview-fn prefix string buf)
(error "Unsupported execution mode %s" mode))))
(defun plantuml-preview-string (prefix string)
"Preview diagram from PlantUML sources (as STRING), using prefix (as PREFIX)
to choose where to display it."
(let ((b (get-buffer plantuml-preview-buffer)))
(when b
(kill-buffer b)))
(let* ((imagep (and (display-images-p)
(plantuml-is-image-output-p)))
(buf (get-buffer-create plantuml-preview-buffer))
(coding-system-for-read (and imagep 'binary))
(coding-system-for-write (and imagep 'binary)))
(plantuml-exec-mode-preview-string prefix plantuml-exec-mode string buf)))
(defun plantuml-preview-buffer (prefix)
"Preview diagram from the PlantUML sources in the current buffer.
@ -365,10 +465,11 @@ Uses prefix (as PREFIX) to choose where to display it:
(plantuml-preview-region prefix (region-beginning) (region-end))
(plantuml-preview-buffer prefix)))
(defun plantuml-init-once ()
"Ensure initialization only happens once."
(defun plantuml-init-once (&optional mode)
"Ensure initialization only happens once. Use exec mode MODE to load the language details, which defaults to `plantuml-exec-mode'."
(let ((mode (or mode plantuml-exec-mode)))
(unless plantuml-kwdList
(plantuml-init)
(plantuml-init mode)
(defvar plantuml-types-regexp (concat "^\\s *\\(" (regexp-opt plantuml-types 'words) "\\|\\<\\(note\\s +over\\|note\\s +\\(left\\|right\\|bottom\\|top\\)\\s +\\(of\\)?\\)\\>\\|\\<\\(\\(left\\|center\\|right\\)\\s +\\(header\\|footer\\)\\)\\>\\)"))
(defvar plantuml-keywords-regexp (concat "^\\s *" (regexp-opt plantuml-keywords 'words) "\\|\\(<\\|<|\\|\\*\\|o\\)\\(\\.+\\|-+\\)\\|\\(\\.+\\|-+\\)\\(>\\||>\\|\\*\\|o\\)\\|\\.\\{2,\\}\\|-\\{2,\\}"))
(defvar plantuml-builtins-regexp (regexp-opt plantuml-builtins 'words))
@ -431,7 +532,7 @@ or it is followed by line end.")
(setq plantuml-types-regexp nil)
(setq plantuml-keywords-regexp nil)
(setq plantuml-builtins-regexp nil)
(setq plantuml-preprocessors-regexp nil)))
(setq plantuml-preprocessors-regexp nil))))
(defun plantuml-complete-symbol ()
"Perform keyword completion on word before cursor."

View File

@ -0,0 +1,32 @@
;;; plantuml-config-test.el --- tests for plantuml-mode configuration knobs -*- lexical-binding: t; -*-
;; Author: Carlo Sciolla
;; Maintainer: Carlo Sciolla
;; URL: https://github.com/skuro/plantuml-mode
;;; Commentary:
;; Test user-accessible configuration knobs
;;; Code:
(require 'plantuml-mode)
(ert-deftest plantuml-config-test/set-exec-mode-happy-path ()
"Test switching execution modes"
(let ((orig-mode plantuml-exec-mode))
;; happy flows:
(plantuml-set-exec-mode "server")
(should (equal 'server plantuml-exec-mode))
(plantuml-set-exec-mode "jar")
(should (equal 'jar plantuml-exec-mode))
(setq plantuml-exec-mode orig-mode)))
(ert-deftest plantuml-config-test/set-exec-mode-wrong-mode ()
"Test setting the exec mode with the wrong text"
(should-error (plantuml-set-exec-mode "turing-machine")))
(provide 'plantuml-mode-config-test)
;;; plantuml-config-test.el ends here

View File

@ -13,11 +13,11 @@
(should (equal `("-Djava.awt.headless=true" "-jar"
,(expand-file-name "~/.plantuml/plantuml.jar")
"-charset" "UTF-8")
(plantuml-render-command)))
(plantuml-jar-render-command)))
(setq-local plantuml-jar-path "/path/with spaces/plantuml.jar")
(should (equal `("-Djava.awt.headless=true" "-jar" "/path/with spaces/plantuml.jar" "-charset" "UTF-8")
(plantuml-render-command))))
(plantuml-jar-render-command))))
(provide 'plantuml-mode-custom-jar-location-test)

View File

@ -8,16 +8,19 @@
;;; Code:
(defun assert-preview (puml output &optional format)
(defun assert-preview (puml output &optional format mode)
(if format
(setq plantuml-output-type format)
(setq plantuml-output-type "utxt"))
(setq plantuml-output-type "txt"))
(if mode
(setq plantuml-exec-mode mode)
(setq plantuml-exec-mode 'jar))
(plantuml-preview-string 42 (read-test-file puml))
(sleep-for 3)
(should (equal (format-preview-output (replace-regexp-in-string " " "~" (read-test-file output)))
(format-preview-output (replace-regexp-in-string " " "~" (read-preview-buffer))))))
(ert-deftest preview-utxt-test ()
(ert-deftest preview-txt-test ()
(setq-local plantuml-jar-path plantuml-test-jar-path)
(assert-preview "a-b.puml" "a-b.txt"))

View File

@ -53,15 +53,15 @@ Finally, the indented text in the buffer will be compared with AFTER."
(with-temp-buffer
;; fix the JAR location prior to mode initialization
;; for some reason, plantuml-mode disregards the setq-local
(setq-local plantuml-jar-path plantuml-test-jar-path)
(plantuml-init-once)
(setq plantuml-jar-path plantuml-test-jar-path)
(plantuml-init-once 'jar)
(insert before)
(goto-char (point-min))
(plantuml-mode)
;; use 2 spaces instead of one tab for indentation
(setq-local indent-tabs-mode nil)
(setq-local tab-width 2)
(setq indent-tabs-mode nil)
(setq tab-width 2)
(indent-region (point-min) (point-max))
(should (equal (buffer-string) after))))