plantuml-mode/plantuml-mode.el

459 lines
19 KiB
EmacsLisp
Raw Normal View History

;;; plantuml-mode.el --- Major mode for PlantUML -*- lexical-binding: t; -*-
2012-06-11 13:31:49 +02:00
;; Filename: plantuml-mode.el
2015-07-14 16:50:41 +02:00
;; Description: Major mode for PlantUML diagrams sources
2018-08-14 21:59:34 +02:00
;; Compatibility: Tested with Emacs 25 through 27 (current master)
2012-06-11 13:31:49 +02:00
;; Author: Zhang Weize (zwz)
;; Maintainer: Carlo Sciolla (skuro)
2015-09-21 17:28:46 +02:00
;; Keywords: uml plantuml ascii
;; Version: 1.2.6
2018-08-14 21:59:34 +02:00
;; Package-Requires: ((emacs "25.0"))
2012-06-11 13:31:49 +02:00
2017-08-18 12:16:12 +02:00
;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
2012-06-11 13:31:49 +02:00
2015-07-14 16:50:41 +02:00
;;; Commentary:
;;
2012-06-11 13:31:49 +02:00
;; A major mode for plantuml, see: http://plantuml.sourceforge.net/
;; Plantuml is an open-source tool in java that allows to quickly write :
;; - sequence diagram,
;; - use case diagram,
;; - class diagram,
;; - activity diagram,
;; - component diagram,
;; - state diagram
;; - object diagram
2015-07-14 16:50:41 +02:00
;;; Change log:
;;
;; version 1.2.6, 2018-07-17 Introduced custom variable `plantuml-jar-args' to control which arguments are passed to PlantUML jar. Fix the warning of failing to specify types of 'defcustom' variables
2017-08-19 19:31:30 +02:00
;; version 1.2.5, 2017-08-19 #53 Fixed installation warnings
;; version 1.2.4, 2017-08-18 #60 Licensed with GPLv3+ to be compatible with Emacs
2016-12-25 20:42:24 +01:00
;; version 1.2.3, 2016-12-25 #50 unicode support in generated output
2016-11-11 11:05:24 +01:00
;; version 1.2.2, 2016-11-11 Fixed java commands handling under windows; support spaces in `plantuml-jar-path'
2016-11-11 09:19:47 +01:00
;; version 1.2.1, 2016-11-11 Support for paths like `~/.plantuml/plantuml.jar' for `plantuml-jar-path' (the tilde was previously unsupported)
2016-11-09 12:21:12 +01:00
;; version 1.2.0, 2016-11-09 Added `plantuml-preview-current-buffer', courtesy of @7mamu4
2016-11-08 12:33:24 +01:00
;; version 1.1.1, 2016-11-08 Fix process handling with Windows native emacs; better file extention match for autoloading the mode
2016-10-18 13:18:58 +02:00
;; version 1.1.0, 2016-10-18 Make PlantUML run headless by default; introduced custom variable `plantuml-java-args' to control which arguments are passed to Plantuml.
2016-10-17 16:27:10 +02:00
;; version 1.0.1, 2016-10-17 Bugfix release: proper auto-mode-alist regex; init delayed at mode load; avoid calling hooks twice.
;; version 1.0.0, 2016-10-16 Moved the mode to plantuml-mode, superseding zwz/plantuml-mode and skuro/puml-mode. Added preview for the currently selected region.
;; version 0.6.7, 2016-10-11 [from puml-mode] Added deprecation warning in favor of plantuml-mode
;; version 0.6.6, 2016-07-19 [from puml-mode] Added autoload, minor bug fixes
;; version 0.6.5, 2016-03-24 [from puml-mode] Added UTF8 support and open in new window / frame shortcuts
;; version 0.6.4, 2015-12-12 [from puml-mode] Added support for comments (single and multiline) -- thanks to https://github.com/nivekuil
;; version 0.6.3, 2015-11-07 [from puml-mode] Added per-buffer configurability of output type (thanks to https://github.com/davazp)
;; version 0.6.2, 2015-11-07 [from puml-mode] Added debugging capabilities to improve issue analysis
;; version 0.6.1, 2015-09-26 [from puml-mode] Bugfix: use eq to compare symbols instead of cl-equalp
;; version 0.6, 2015-09-26 [from puml-mode] Fixed PNG preview
;; version 0.5, 2015-09-21 [from puml-mode] Added preview capabilities
;; version 0.4, 2015-06-14 [from puml-mode] Use a puml- prefix to distinguish from the other plantuml-mode
;; version 0.3, 2015-06-13 [from puml-mode] Compatibility with Emacs 24.x
;; version 0.2, 2010-09-20 [from puml-mode] Initialize the keywords from the -language output of plantuml.jar instead of the hard-coded way.
;; version 0.1, 2010-08-25 [from puml-mode] First version
2012-06-11 13:31:49 +02:00
;;; Code:
2012-06-11 13:31:49 +02:00
(require 'thingatpt)
(defgroup plantuml-mode nil
2012-06-11 13:31:49 +02:00
"Major mode for editing plantuml file."
:group 'languages)
(defcustom plantuml-jar-path
2015-07-14 17:02:09 +02:00
(expand-file-name "~/plantuml.jar")
"The location of the PlantUML executable JAR."
:type 'string
:group 'plantuml)
2012-06-11 13:31:49 +02:00
(defvar plantuml-mode-hook nil "Standard hook for plantuml-mode.")
2012-06-11 13:31:49 +02:00
2016-12-25 20:42:24 +01:00
(defconst plantuml-mode-version "1.2.3" "The plantuml-mode version string.")
2012-06-11 13:31:49 +02:00
(defvar plantuml-mode-debug-enabled nil)
2015-12-07 16:45:40 +01:00
(defvar plantuml-font-lock-keywords nil)
2015-12-07 17:06:19 +01:00
(defvar plantuml-mode-map
2016-10-10 19:42:40 +02:00
(let ((keymap (make-sparse-keymap)))
(define-key keymap (kbd "C-c C-c") 'plantuml-preview)
keymap)
"Keymap for plantuml-mode.")
2012-06-11 13:31:49 +02:00
(defcustom plantuml-java-command "java"
"The java command used to execute PlantUML."
:type 'string
:group 'plantuml)
(defcustom plantuml-java-args (list "-Djava.awt.headless=true" "-jar")
"The parameters passed to `plantuml-java-command' when executing PlantUML."
:type '(repeat string)
:group 'plantuml)
(defcustom plantuml-jar-args (list "-charset" "UTF-8" )
"The parameters passed to `plantuml.jar', when executing PlantUML."
:type '(repeat string)
:group 'plantuml)
(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)
2016-10-10 20:05:27 +02:00
"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))
2016-11-08 12:33:24 +01:00
(cmd (mapconcat 'identity cmd-list "|")))
(plantuml-debug (format "Command is [%s]" cmd))
cmd-list))
2016-10-10 19:42:40 +02:00
2012-06-11 13:31:49 +02:00
;;; syntax table
(defvar plantuml-mode-syntax-table
2012-06-11 13:31:49 +02:00
(let ((synTable (make-syntax-table)))
(modify-syntax-entry ?\/ ". 14c" synTable)
(modify-syntax-entry ?' "< 23" synTable)
(modify-syntax-entry ?\n ">" synTable)
(modify-syntax-entry ?\r ">" synTable)
(modify-syntax-entry ?! "w" synTable)
(modify-syntax-entry ?@ "w" synTable)
(modify-syntax-entry ?# "'" synTable)
2012-06-11 13:31:49 +02:00
synTable)
"Syntax table for `plantuml-mode'.")
2012-06-11 13:31:49 +02:00
(defvar plantuml-types nil)
(defvar plantuml-keywords nil)
(defvar plantuml-preprocessors nil)
(defvar plantuml-builtins nil)
2012-06-11 13:31:49 +02:00
;; keyword completion
(defvar plantuml-kwdList nil "The plantuml keywords.")
2012-06-11 13:31:49 +02:00
(defun plantuml-enable-debug ()
"Enables debug messages into the *PLANTUML Messages* buffer."
2015-12-07 16:49:21 +01:00
(interactive)
(setq plantuml-mode-debug-enabled t))
2015-12-07 16:45:40 +01:00
(defun plantuml-disable-debug ()
"Stops any debug messages to be added into the *PLANTUML Messages* buffer."
2015-12-07 16:49:21 +01:00
(interactive)
(setq plantuml-mode-debug-enabled nil))
2015-12-07 16:45:40 +01:00
(defun plantuml-debug (msg)
"Writes msg (as MSG) into the *PLANTUML Messages* buffer without annoying the user."
(if plantuml-mode-debug-enabled
(let* ((log-buffer-name "*PLANTUML Messages*")
2015-12-07 16:45:40 +01:00
(log-buffer (get-buffer-create log-buffer-name)))
(save-excursion
(with-current-buffer log-buffer
(goto-char (point-max))
(insert msg)
(insert "\n"))))))
2012-06-11 13:31:49 +02:00
(defun plantuml-init ()
2015-07-14 16:50:41 +02:00
"Initialize the keywords or builtins from the cmdline language output."
(unless (or (eq system-type 'cygwin) (file-exists-p plantuml-jar-path))
(error "Could not find plantuml.jar at %s" plantuml-jar-path))
2012-06-11 13:31:49 +02:00
(with-temp-buffer
(let ((cmd-args (append (list plantuml-java-command nil t nil)
(plantuml-render-command "-language"))))
(apply 'call-process cmd-args)
(goto-char (point-min)))
2015-07-02 19:09:55 +02:00
(let ((found (search-forward ";" nil t))
2012-06-11 13:31:49 +02:00
(word "")
(count 0)
(pos 0))
(while found
(forward-char)
(setq word (current-word))
(if (string= word "EOF") (setq found nil)
2016-10-10 20:05:27 +02:00
;; else
(forward-line)
(setq count (string-to-number (current-word)))
(beginning-of-line 2)
(setq pos (point))
(forward-line count)
(cond ((string= word "type")
(setq plantuml-types
2016-10-10 20:05:27 +02:00
(split-string
(buffer-substring-no-properties pos (point)))))
((string= word "keyword")
(setq plantuml-keywords
2016-10-10 20:05:27 +02:00
(split-string
(buffer-substring-no-properties pos (point)))))
((string= word "preprocessor")
(setq plantuml-preprocessors
2016-10-10 20:05:27 +02:00
(split-string
(buffer-substring-no-properties pos (point)))))
(t (setq plantuml-builtins
2016-10-10 20:05:27 +02:00
(append
plantuml-builtins
2016-10-10 20:05:27 +02:00
(split-string
(buffer-substring-no-properties pos (point)))))))
(setq found (search-forward ";" nil nil)))))))
2012-06-11 13:31:49 +02:00
(defconst plantuml-preview-buffer "*PLANTUML Preview*")
2015-09-14 12:04:32 +02:00
(defvar plantuml-output-type
(if (not (display-images-p))
"utxt"
(cond ((image-type-available-p 'svg) "svg")
2016-01-24 21:34:23 +01:00
((image-type-available-p 'png) "png")
(t "utxt")))
"Specify the desired output type to use for generated diagrams.")
(defun plantuml-read-output-type ()
"Read from the minibuffer a output type."
(let* ((completion-ignore-case t)
(available-types
(append
(and (image-type-available-p 'svg) '("svg"))
(and (image-type-available-p 'png) '("png"))
'("utxt"))))
(completing-read (format "Output type [%s]: " plantuml-output-type)
available-types
nil
t
nil
nil
plantuml-output-type)))
(defun plantuml-set-output-type (type)
2015-12-07 17:06:19 +01:00
"Set the desired output type (as TYPE) for the current buffer.
2015-12-07 17:43:36 +01:00
If the
major mode of the current buffer mode is not plantuml-mode, set the
default output type for new buffers."
(interactive (list (plantuml-read-output-type)))
(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)))
(defun plantuml-output-type-opt ()
"Create the flag to pass to PlantUML to produce the selected output format."
(concat "-t" plantuml-output-type))
2015-09-14 12:04:32 +02:00
(defmacro plantuml-start-process (buf)
"Run PlantUML as an Emacs process and puts the output into the given buffer (as BUF)."
`(start-process "PLANTUML" ,buf
plantuml-java-command
,@plantuml-java-args
2016-11-11 11:03:04 +01:00
(expand-file-name plantuml-jar-path)
(plantuml-output-type-opt)
,@plantuml-jar-args
"-p"))
(defun plantuml-preview-string (prefix string)
2016-10-10 20:05:27 +02:00
"Preview diagram from PlantUML sources (as STRING), using prefix (as PREFIX)
to choose where to display it:
- 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)))
2015-09-14 12:04:32 +02:00
(when b
(kill-buffer b)))
2015-12-07 17:43:36 +01:00
(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))
2015-12-07 17:43:36 +01:00
(coding-system-for-write (and imagep 'binary)))
(let ((ps (plantuml-start-process buf)))
2016-10-10 20:05:27 +02:00
(process-send-string ps string)
2015-09-14 12:04:32 +02:00
(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 (switch-to-buffer plantuml-preview-buffer)))
(when imagep
(image-mode)
(set-buffer-multibyte t)))))))
2015-09-14 12:04:32 +02:00
(defun plantuml-preview-buffer (prefix)
2016-10-10 20:05:27 +02:00
"Preview diagram from the PlantUML sources in the current buffer.
Uses prefix (as PREFIX) to choose where to display it:
- 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"
(interactive "p")
(plantuml-preview-string prefix (buffer-string)))
2016-10-10 20:05:27 +02:00
(defun plantuml-preview-region (prefix begin end)
"Preview diagram from the PlantUML sources in from BEGIN to END.
Uses the current region when called interactively.
2016-10-10 20:05:27 +02:00
Uses prefix (as PREFIX) to choose where to display it:
- 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"
(interactive "p\nr")
(plantuml-preview-string prefix (concat "@startuml\n"
2016-10-10 20:05:27 +02:00
(buffer-substring-no-properties
begin end)
2016-10-10 20:05:27 +02:00
"\n@enduml")))
(defun plantuml-preview-current-block (prefix)
"Preview diagram from the PlantUML sources from the previous @startuml to the next @enduml.
Uses prefix (as PREFIX) to choose where to display it:
- 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"
(interactive "p")
(save-restriction
(narrow-to-region
(search-backward "@startuml") (search-forward "@enduml"))
(plantuml-preview-buffer prefix)))
(defun plantuml-preview (prefix)
2016-10-10 20:05:27 +02:00
"Preview diagram from the PlantUML sources.
Uses the current region if one is active, or the entire buffer otherwise.
Uses prefix (as PREFIX) to choose where to display it:
- 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"
(interactive "p")
(if mark-active
(plantuml-preview-region prefix (region-beginning) (region-end))
(plantuml-preview-buffer prefix)))
2016-10-10 20:05:27 +02:00
(defun plantuml-init-once ()
"Ensure initialization only happens once."
(unless plantuml-kwdList
(plantuml-init)
(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))
(defvar plantuml-preprocessors-regexp (concat "^\\s *" (regexp-opt plantuml-preprocessors 'words)))
(defvar plantuml-indent-regexp-start "^[ \t]*\\(\\(?:.*\\)?\s*\\(?:[<>.*a-z-|]+\\)?\s*\\(?:\\[[a-zA-Z]+\\]\\)?\s+if\\|alt\\|else\\|note\s+over\\|note\sas\s.*\\|note\s+\\(\\(?:\\(?:buttom\\|left\\|right\\|top\\)\\)\\)\\(?:\s+of\\)?\\|\\(?:class\\|enum\\|package\\)\s+.*{\\)")
(defvar plantuml-indent-regexp-end "^[ \t]*\\(endif\\|else\\|end\\|end\s+note\\|.*}\\)")
(setq plantuml-font-lock-keywords
`(
(,plantuml-types-regexp . font-lock-type-face)
(,plantuml-keywords-regexp . font-lock-keyword-face)
(,plantuml-builtins-regexp . font-lock-builtin-face)
(,plantuml-preprocessors-regexp . font-lock-preprocessor-face)
;; note: order matters
))
(setq plantuml-kwdList (make-hash-table :test 'equal))
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-types)
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-keywords)
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-builtins)
(mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-preprocessors)
(put 'plantuml-kwdList 'risky-local-variable t)
;; clear memory
(setq plantuml-types nil)
(setq plantuml-keywords nil)
(setq plantuml-builtins nil)
(setq plantuml-preprocessors nil)
(setq plantuml-types-regexp nil)
(setq plantuml-keywords-regexp nil)
(setq plantuml-builtins-regexp nil)
(setq plantuml-preprocessors-regexp nil)))
(defun plantuml-complete-symbol ()
2012-06-11 13:31:49 +02:00
"Perform keyword completion on word before cursor."
(interactive)
(let ((posEnd (point))
(meat (thing-at-point 'symbol))
maxMatchResult)
(when (not meat) (setq meat ""))
(setq maxMatchResult (try-completion meat plantuml-kwdList))
2012-06-11 13:31:49 +02:00
(cond ((eq maxMatchResult t))
((null maxMatchResult)
(message "Can't find completion for \"%s\"" meat)
(ding))
((not (string= meat maxMatchResult))
(delete-region (- posEnd (length meat)) posEnd)
(insert maxMatchResult))
(t (message "Making completion list...")
(with-output-to-temp-buffer "*Completions*"
(display-completion-list
(all-completions meat plantuml-kwdList)))
2012-06-11 13:31:49 +02:00
(message "Making completion list...%s" "done")))))
;; indentation
(defun plantuml-current-block-indentation ()
(save-excursion
(let ((relative-depth 0))
(while (>= relative-depth 0)
(forward-line -1)
(if (bobp)
(setq relative-depth -2)) ;end fast
(if (looking-at plantuml-indent-regexp-end)
(setq relative-depth (1+ relative-depth)))
(if (looking-at plantuml-indent-regexp-start)
(setq relative-depth (1- relative-depth))))
(if (= -2 relative-depth)
0
(+ tab-width (current-indentation))))))
(defun plantuml-indent-line ()
(interactive)
(save-excursion
(beginning-of-line)
(if (bobp)
(indent-line-to 0)
(let ((offset (plantuml-current-block-indentation)))
(when (looking-at plantuml-indent-regexp-end)
(setq offset (max (- offset tab-width) 0)))
(indent-line-to offset)))))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.\\(plantuml\\|pum\\|plu\\)\\'" . plantuml-mode))
2012-06-11 13:31:49 +02:00
;;;###autoload
(define-derived-mode plantuml-mode prog-mode "plantuml"
2012-06-11 13:31:49 +02:00
"Major mode for plantuml.
Shortcuts Command Name
\\[plantuml-complete-symbol] `plantuml-complete-symbol'"
(plantuml-init-once)
(make-local-variable 'plantuml-output-type)
(set (make-local-variable 'comment-start-skip) "\\('+\\|/'+\\)\\s *")
(set (make-local-variable 'comment-start) "/'")
(set (make-local-variable 'comment-end) "'/")
(set (make-local-variable 'comment-multi-line) t)
(set (make-local-variable 'comment-style) 'extra-line)
(set (make-local-variable 'indent-line-function) 'plantuml-indent-line)
(setq font-lock-defaults '((plantuml-font-lock-keywords) nil t)))
2016-10-10 20:25:07 +02:00
2016-10-14 21:27:20 +02:00
(defun plantuml-deprecation-warning ()
"Warns the user about the deprecation of the `puml-mode' project."
(if (and plantuml-suppress-deprecation-warning
(featurep 'puml-mode))
2016-10-14 21:27:20 +02:00
(display-warning :warning
"`puml-mode' is now deprecated and no longer updated, but it's still present in your system.\
You should move your configuration to use `plantuml-mode'. See https://github.com/sytac/plantuml-mode. \
See more at https://github.com/skuro/puml-mode/issues/26")))
(add-hook 'plantuml-mode-hook 'plantuml-deprecation-warning)
(provide 'plantuml-mode)
;;; plantuml-mode.el ends here