Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

738 lines
35 KiB

  1. ;;; plantuml-mode.el --- Major mode for PlantUML -*- lexical-binding: t; -*-
  2. ;; Filename: plantuml-mode.el
  3. ;; Description: Major mode for PlantUML diagrams sources
  4. ;; Compatibility: Tested with Emacs 25 through 27 (current master)
  5. ;; Author: Zhang Weize (zwz)
  6. ;; Maintainer: Carlo Sciolla (skuro)
  7. ;; Keywords: uml plantuml ascii
  8. ;; Package-Commit: 5889166b6cfe94a37532ea27fc8de13be2ebfd02
  9. ;; Version: 1.2.9
  10. ;; Package-Version: 1.4.1
  11. ;; Package-X-Original-Version: 1.2.9
  12. ;; Package-Requires: ((dash "2.0.0") (emacs "25.0"))
  13. ;; This file is free software; you can redistribute it and/or modify
  14. ;; it under the terms of the GNU General Public License as published by
  15. ;; the Free Software Foundation; either version 3, or (at your option)
  16. ;; any later version.
  17. ;; This file is distributed in the hope that it will be useful,
  18. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. ;; GNU General Public License for more details.
  21. ;; You should have received a copy of the GNU General Public License
  22. ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
  23. ;;; Commentary:
  24. ;;
  25. ;; A major mode for plantuml, see: http://plantuml.sourceforge.net/
  26. ;; Plantuml is an open-source tool in java that allows to quickly write :
  27. ;; - sequence diagram,
  28. ;; - use case diagram,
  29. ;; - class diagram,
  30. ;; - activity diagram,
  31. ;; - component diagram,
  32. ;; - state diagram
  33. ;; - object diagram
  34. ;;; Change log:
  35. ;;
  36. ;; version 1.4.1, 2019-09-03 Better indentation; more bugfixing; actually adding `executable' mode
  37. ;; version 1.4.0, 2019-08-21 Added `executable' exec mode to use locally installed `plantuml' binaries, various bugfixes
  38. ;; version 1.3.1, 2019-08-02 Fixed interactive behavior of `plantuml-set-exec-mode'
  39. ;; version 1.3.0, 2019-05-31 Added experimental support for multiple rendering modes and, specifically, preview using a PlantUML server
  40. ;; version 1.2.11, 2019-04-09 Added `plantuml-download-jar'
  41. ;; version 1.2.10, 2019-04-03 Avoid messing with window layouts and buffers -- courtesy of https://github.com/wailo
  42. ;; version 1.2.9, Revamped indentation support, now working with a greater number of keywords
  43. ;; version 1.2.8, 2019-01-07 Support indentation for activate / deactivate blocks; allow customization of `plantuml-java-args'
  44. ;; version 1.2.7, 2018-08-15 Added support for indentation; Fixed the comiling error when installing with melpa
  45. ;; 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
  46. ;; version 1.2.5, 2017-08-19 #53 Fixed installation warnings
  47. ;; version 1.2.4, 2017-08-18 #60 Licensed with GPLv3+ to be compatible with Emacs
  48. ;; version 1.2.3, 2016-12-25 #50 unicode support in generated output
  49. ;; version 1.2.2, 2016-11-11 Fixed java commands handling under windows; support spaces in `plantuml-jar-path'
  50. ;; version 1.2.1, 2016-11-11 Support for paths like `~/.plantuml/plantuml.jar' for `plantuml-jar-path' (the tilde was previously unsupported)
  51. ;; version 1.2.0, 2016-11-09 Added `plantuml-preview-current-buffer', courtesy of @7mamu4
  52. ;; version 1.1.1, 2016-11-08 Fix process handling with Windows native emacs; better file extention match for autoloading the mode
  53. ;; 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.
  54. ;; version 1.0.1, 2016-10-17 Bugfix release: proper auto-mode-alist regex; init delayed at mode load; avoid calling hooks twice.
  55. ;; 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.
  56. ;; version 0.6.7, 2016-10-11 [from puml-mode] Added deprecation warning in favor of plantuml-mode
  57. ;; version 0.6.6, 2016-07-19 [from puml-mode] Added autoload, minor bug fixes
  58. ;; version 0.6.5, 2016-03-24 [from puml-mode] Added UTF8 support and open in new window / frame shortcuts
  59. ;; version 0.6.4, 2015-12-12 [from puml-mode] Added support for comments (single and multiline) -- thanks to https://github.com/nivekuil
  60. ;; version 0.6.3, 2015-11-07 [from puml-mode] Added per-buffer configurability of output type (thanks to https://github.com/davazp)
  61. ;; version 0.6.2, 2015-11-07 [from puml-mode] Added debugging capabilities to improve issue analysis
  62. ;; version 0.6.1, 2015-09-26 [from puml-mode] Bugfix: use eq to compare symbols instead of cl-equalp
  63. ;; version 0.6, 2015-09-26 [from puml-mode] Fixed PNG preview
  64. ;; version 0.5, 2015-09-21 [from puml-mode] Added preview capabilities
  65. ;; version 0.4, 2015-06-14 [from puml-mode] Use a puml- prefix to distinguish from the other plantuml-mode
  66. ;; version 0.3, 2015-06-13 [from puml-mode] Compatibility with Emacs 24.x
  67. ;; 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.
  68. ;; version 0.1, 2010-08-25 [from puml-mode] First version
  69. ;;; Code:
  70. (require 'thingatpt)
  71. (require 'dash)
  72. (require 'xml)
  73. (defgroup plantuml-mode nil
  74. "Major mode for editing plantuml file."
  75. :group 'languages)
  76. (defcustom plantuml-jar-path
  77. (expand-file-name "~/plantuml.jar")
  78. "The location of the PlantUML executable JAR."
  79. :type 'string
  80. :group 'plantuml)
  81. (defcustom plantuml-executable-path
  82. "plantuml"
  83. "The location of the PlantUML executable."
  84. :type 'string
  85. :group 'plantuml)
  86. (defvar plantuml-mode-hook nil "Standard hook for plantuml-mode.")
  87. (defconst plantuml-mode-version "1.4.1" "The plantuml-mode version string.")
  88. (defvar plantuml-mode-debug-enabled nil)
  89. (defvar plantuml-font-lock-keywords nil)
  90. (defvar plantuml-mode-map
  91. (let ((keymap (make-sparse-keymap)))
  92. (define-key keymap (kbd "C-c C-c") 'plantuml-preview)
  93. keymap)
  94. "Keymap for plantuml-mode.")
  95. (defcustom plantuml-java-command "java"
  96. "The java command used to execute PlantUML."
  97. :type 'string
  98. :group 'plantuml)
  99. (defcustom plantuml-java-args (list "-Djava.awt.headless=true" "-jar" "--illegal-access=deny")
  100. "The parameters passed to `plantuml-java-command' when executing PlantUML."
  101. :type '(repeat string)
  102. :group 'plantuml)
  103. (defcustom plantuml-jar-args (list "-charset" "UTF-8" )
  104. "The parameters passed to `plantuml.jar', when executing PlantUML."
  105. :type '(repeat string)
  106. :group 'plantuml)
  107. (defcustom plantuml-server-url "https://www.plantuml.com/plantuml"
  108. "The base URL of the PlantUML server."
  109. :type 'string
  110. :group 'plantuml)
  111. (defcustom plantuml-executable-args (list "-headless")
  112. "The parameters passed to plantuml executable when executing PlantUML."
  113. :type '(repeat string)
  114. :group 'plantuml)
  115. (defcustom plantuml-default-exec-mode 'server
  116. "Default execution mode for PlantUML. Valid values are:
  117. - `jar': run PlantUML as a JAR file (requires a local install of the PlantUML JAR file, see `plantuml-jar-path'"
  118. :type 'symbol
  119. :group 'plantuml
  120. :options '(jar server executable))
  121. (defcustom plantuml-suppress-deprecation-warning t
  122. "To silence the deprecation warning when `puml-mode' is found upon loading."
  123. :type 'boolean
  124. :group 'plantuml)
  125. (defun plantuml-jar-render-command (&rest arguments)
  126. "Create a command line to execute PlantUML with arguments (as ARGUMENTS)."
  127. (let* ((cmd-list (append plantuml-java-args (list (expand-file-name plantuml-jar-path)) plantuml-jar-args arguments))
  128. (cmd (mapconcat 'identity cmd-list "|")))
  129. (plantuml-debug (format "Command is [%s]" cmd))
  130. cmd-list))
  131. ;;; syntax table
  132. (defvar plantuml-mode-syntax-table
  133. (let ((synTable (make-syntax-table)))
  134. (modify-syntax-entry ?\/ ". 14c" synTable)
  135. (modify-syntax-entry ?' "< 23" synTable)
  136. (modify-syntax-entry ?\n ">" synTable)
  137. (modify-syntax-entry ?\r ">" synTable)
  138. (modify-syntax-entry ?! "w" synTable)
  139. (modify-syntax-entry ?@ "w" synTable)
  140. (modify-syntax-entry ?# "'" synTable)
  141. synTable)
  142. "Syntax table for `plantuml-mode'.")
  143. (defvar plantuml-types nil)
  144. (defvar plantuml-keywords nil)
  145. (defvar plantuml-preprocessors nil)
  146. (defvar plantuml-builtins nil)
  147. ;; keyword completion
  148. (defvar plantuml-kwdList nil "The plantuml keywords.")
  149. ;; PlantUML execution mode
  150. (defvar-local plantuml-exec-mode nil
  151. "The Plantuml execution mode override. See `plantuml-default-exec-mode' for acceptable values.")
  152. (defun plantuml-set-exec-mode (mode)
  153. "Set the execution mode MODE for PlantUML."
  154. (interactive (let* ((completion-ignore-case t)
  155. (supported-modes '("jar" "server" "executable")))
  156. (list (completing-read (format "Exec mode [%s]: " plantuml-exec-mode)
  157. supported-modes
  158. nil
  159. t
  160. nil
  161. nil
  162. plantuml-exec-mode))))
  163. (if (member mode '("jar" "server" "executable"))
  164. (setq plantuml-exec-mode (intern mode))
  165. (error (concat "Unsupported mode:" mode))))
  166. (defun plantuml-get-exec-mode ()
  167. "Retrieves the currently active PlantUML exec mode."
  168. (or plantuml-exec-mode
  169. plantuml-default-exec-mode))
  170. (defun plantuml-enable-debug ()
  171. "Enables debug messages into the *PLANTUML Messages* buffer."
  172. (interactive)
  173. (setq plantuml-mode-debug-enabled t))
  174. (defun plantuml-disable-debug ()
  175. "Stops any debug messages to be added into the *PLANTUML Messages* buffer."
  176. (interactive)
  177. (setq plantuml-mode-debug-enabled nil))
  178. (defun plantuml-debug (msg)
  179. "Writes msg (as MSG) into the *PLANTUML Messages* buffer without annoying the user."
  180. (if plantuml-mode-debug-enabled
  181. (let* ((log-buffer-name "*PLANTUML Messages*")
  182. (log-buffer (get-buffer-create log-buffer-name)))
  183. (save-excursion
  184. (with-current-buffer log-buffer
  185. (goto-char (point-max))
  186. (insert msg)
  187. (insert "\n"))))))
  188. (defun plantuml-download-jar ()
  189. "Download the latest PlantUML JAR file and install it into `plantuml-jar-path'."
  190. (interactive)
  191. (if (y-or-n-p (format "Download the latest PlantUML JAR file into %s? " plantuml-jar-path))
  192. (if (or (not (file-exists-p plantuml-jar-path))
  193. (y-or-n-p (format "The PlantUML jar file already exists at %s, overwrite? " plantuml-jar-path)))
  194. (with-current-buffer (url-retrieve-synchronously "https://search.maven.org/solrsearch/select?q=g:net.sourceforge.plantuml+AND+a:plantuml&core=gav&start=0&rows=1&wt=xml")
  195. (mkdir (file-name-directory plantuml-jar-path) t)
  196. (let* ((parse-tree (xml-parse-region))
  197. (doc (->> parse-tree
  198. (assq 'response)
  199. (assq 'result)
  200. (assq 'doc)))
  201. (strs (xml-get-children doc 'str))
  202. (version (->> strs
  203. (--filter (string-equal "v" (xml-get-attribute it 'name)))
  204. (car)
  205. (xml-node-children)
  206. (car))))
  207. (message (concat "Downloading PlantUML v" version " into " plantuml-jar-path))
  208. (url-copy-file (format "https://search.maven.org/remotecontent?filepath=net/sourceforge/plantuml/plantuml/%s/plantuml-%s.jar" version version) plantuml-jar-path t)
  209. (kill-buffer)))
  210. (message "Aborted."))
  211. (message "Aborted.")))
  212. (defun plantuml-jar-get-language (buf)
  213. "Retrieve the language specification from the PlantUML JAR file and paste it into BUF."
  214. (unless (or (eq system-type 'cygwin) (file-exists-p plantuml-jar-path))
  215. (error "Could not find plantuml.jar at %s" plantuml-jar-path))
  216. (with-current-buffer buf
  217. (let ((cmd-args (append (list plantuml-java-command nil t nil)
  218. (plantuml-jar-render-command "-language"))))
  219. (apply 'call-process cmd-args)
  220. (goto-char (point-min)))))
  221. (defun plantuml-server-get-language (buf)
  222. "Retrieve the language specification from the PlantUML server and paste it into BUF."
  223. (let ((lang-url (concat plantuml-server-url "/language")))
  224. (with-current-buffer buf
  225. (url-insert-file-contents lang-url))))
  226. (defun plantuml-executable-get-language (buf)
  227. "Retrieve the language specification from the PlantUML executable and paste it into BUF."
  228. (with-current-buffer buf
  229. (let ((cmd-args (append (list plantuml-executable-path nil t nil) (list "-language"))))
  230. (apply 'call-process cmd-args)
  231. (goto-char (point-min)))))
  232. (defun plantuml-get-language (mode buf)
  233. "Retrieve the language spec using the preferred PlantUML execution mode MODE. Paste the result into BUF."
  234. (let ((get-fn (pcase mode
  235. ('jar #'plantuml-jar-get-language)
  236. ('server #'plantuml-server-get-language)
  237. ('executable #'plantuml-executable-get-language))))
  238. (if get-fn
  239. (funcall get-fn buf)
  240. (error "Unsupported execution mode %s" mode))))
  241. (defun plantuml-init (mode)
  242. "Initialize the keywords or builtins from the cmdline language output. Use exec mode MODE to load the language details."
  243. (with-temp-buffer
  244. (plantuml-get-language mode (current-buffer))
  245. (let ((found (search-forward ";" nil t))
  246. (word "")
  247. (count 0)
  248. (pos 0))
  249. (while found
  250. (forward-char)
  251. (setq word (current-word))
  252. (if (string= word "EOF") (setq found nil)
  253. ;; else
  254. (forward-line)
  255. (setq count (string-to-number (current-word)))
  256. (beginning-of-line 2)
  257. (setq pos (point))
  258. (forward-line count)
  259. (cond ((string= word "type")
  260. (setq plantuml-types
  261. (split-string
  262. (buffer-substring-no-properties pos (point)))))
  263. ((string= word "keyword")
  264. (setq plantuml-keywords
  265. (split-string
  266. (buffer-substring-no-properties pos (point)))))
  267. ((string= word "preprocessor")
  268. (setq plantuml-preprocessors
  269. (split-string
  270. (buffer-substring-no-properties pos (point)))))
  271. (t (setq plantuml-builtins
  272. (append
  273. plantuml-builtins
  274. (split-string
  275. (buffer-substring-no-properties pos (point)))))))
  276. (setq found (search-forward ";" nil nil)))))))
  277. (defconst plantuml-preview-buffer "*PLANTUML Preview*")
  278. (defvar plantuml-output-type
  279. (if (not (display-images-p))
  280. "txt"
  281. (cond ((image-type-available-p 'svg) "svg")
  282. ((image-type-available-p 'png) "png")
  283. (t "txt")))
  284. "Specify the desired output type to use for generated diagrams.")
  285. (defun plantuml-read-output-type ()
  286. "Read from the minibuffer a output type."
  287. (let* ((completion-ignore-case t)
  288. (available-types
  289. (append
  290. (and (image-type-available-p 'svg) '("svg"))
  291. (and (image-type-available-p 'png) '("png"))
  292. '("txt"))))
  293. (completing-read (format "Output type [%s]: " plantuml-output-type)
  294. available-types
  295. nil
  296. t
  297. nil
  298. nil
  299. plantuml-output-type)))
  300. (defun plantuml-set-output-type (type)
  301. "Set the desired output type (as TYPE) for the current buffer.
  302. If the
  303. major mode of the current buffer mode is not plantuml-mode, set the
  304. default output type for new buffers."
  305. (interactive (list (plantuml-read-output-type)))
  306. (setq plantuml-output-type type))
  307. (defun plantuml-is-image-output-p ()
  308. "Return non-nil if the diagram output format is an image, false if it's text based."
  309. (not (equal "txt" plantuml-output-type)))
  310. (defun plantuml-jar-output-type-opt (output-type)
  311. "Create the flag to pass to PlantUML according to OUTPUT-TYPE.
  312. Note that output type `txt' is promoted to `utxt' for better rendering."
  313. (concat "-t" (pcase output-type
  314. ("txt" "utxt")
  315. (_ output-type))))
  316. (defun plantuml-jar-start-process (buf)
  317. "Run PlantUML as an Emacs process and puts the output into the given buffer (as BUF)."
  318. (apply #'start-process
  319. "PLANTUML" buf plantuml-java-command
  320. `(,@plantuml-java-args
  321. ,(expand-file-name plantuml-jar-path)
  322. ,(plantuml-jar-output-type-opt plantuml-output-type)
  323. ,@plantuml-jar-args
  324. "-p")))
  325. (defun plantuml-executable-start-process (buf)
  326. "Run PlantUML as an Emacs process and puts the output into the given buffer (as BUF)."
  327. (apply #'start-process
  328. "PLANTUML" buf plantuml-executable-path
  329. `(,@plantuml-executable-args
  330. ,(plantuml-jar-output-type-opt plantuml-output-type)
  331. "-p")))
  332. (defun plantuml-update-preview-buffer (prefix buf)
  333. "Show the preview in the preview buffer BUF.
  334. Window is selected according to PREFIX:
  335. - 4 (when prefixing the command with C-u) -> new window
  336. - 16 (when prefixing the command with C-u C-u) -> new frame.
  337. - else -> new buffer"
  338. (let ((imagep (and (display-images-p)
  339. (plantuml-is-image-output-p))))
  340. (cond
  341. ((= prefix 16) (switch-to-buffer-other-frame buf))
  342. ((= prefix 4) (switch-to-buffer-other-window buf))
  343. (t (display-buffer buf)))
  344. (when imagep
  345. (with-current-buffer buf
  346. (image-mode)
  347. (set-buffer-multibyte t)))))
  348. (defun plantuml-jar-preview-string (prefix string buf)
  349. "Preview the diagram from STRING by running the PlantUML JAR.
  350. Put the result into buffer BUF. Window is selected according to PREFIX:
  351. - 4 (when prefixing the command with C-u) -> new window
  352. - 16 (when prefixing the command with C-u C-u) -> new frame.
  353. - else -> new buffer"
  354. (let* ((process-connection-type nil)
  355. (ps (plantuml-jar-start-process buf)))
  356. (process-send-string ps string)
  357. (process-send-eof ps)
  358. (set-process-sentinel ps
  359. (lambda (_ps event)
  360. (unless (equal event "finished\n")
  361. (error "PLANTUML Preview failed: %s" event))
  362. (plantuml-update-preview-buffer prefix buf)))))
  363. (defun plantuml-server-encode-url (string)
  364. "Encode the string STRING into a URL suitable for PlantUML server interactions."
  365. (let* ((coding-system (or buffer-file-coding-system
  366. "utf8"))
  367. (encoded-string (base64-encode-string (encode-coding-string string coding-system) t)))
  368. (concat plantuml-server-url "/" plantuml-output-type "/-base64-" encoded-string)))
  369. (defun plantuml-server-preview-string (prefix string buf)
  370. "Preview the diagram from STRING as rendered by the PlantUML server.
  371. Put the result into buffer BUF and place it according to PREFIX:
  372. - 4 (when prefixing the command with C-u) -> new window
  373. - 16 (when prefixing the command with C-u C-u) -> new frame.
  374. - else -> new buffer"
  375. (let* ((url-request-location (plantuml-server-encode-url string)))
  376. (save-current-buffer
  377. (save-match-data
  378. (url-retrieve url-request-location
  379. (lambda (status)
  380. ;; TODO: error check
  381. (goto-char (point-min))
  382. ;; skip the HTTP headers
  383. (while (not (looking-at "\n"))
  384. (forward-line))
  385. (kill-region (point-min) (+ 1 (point)))
  386. (copy-to-buffer buf (point-min) (point-max))
  387. (plantuml-update-preview-buffer prefix buf)))))))
  388. (defun plantuml-executable-preview-string (prefix string buf)
  389. "Preview the diagram from STRING by running the PlantUML JAR.
  390. Put the result into buffer BUF. Window is selected according to PREFIX:
  391. - 4 (when prefixing the command with C-u) -> new window
  392. - 16 (when prefixing the command with C-u C-u) -> new frame.
  393. - else -> new buffer"
  394. (let* ((process-connection-type nil)
  395. (ps (plantuml-executable-start-process buf)))
  396. (process-send-string ps string)
  397. (process-send-eof ps)
  398. (set-process-sentinel ps
  399. (lambda (_ps event)
  400. (unless (equal event "finished\n")
  401. (error "PLANTUML Preview failed: %s" event))
  402. (plantuml-update-preview-buffer prefix buf)))))
  403. (defun plantuml-exec-mode-preview-string (prefix mode string buf)
  404. "Preview the diagram from STRING using the execution mode MODE.
  405. Put the result into buffer BUF, selecting the window according to PREFIX:
  406. - 4 (when prefixing the command with C-u) -> new window
  407. - 16 (when prefixing the command with C-u C-u) -> new frame.
  408. - else -> new buffer"
  409. (let ((preview-fn (pcase mode
  410. ('jar #'plantuml-jar-preview-string)
  411. ('server #'plantuml-server-preview-string)
  412. ('executable #'plantuml-executable-preview-string))))
  413. (if preview-fn
  414. (funcall preview-fn prefix string buf)
  415. (error "Unsupported execution mode %s" mode))))
  416. (defun plantuml-preview-string (prefix string)
  417. "Preview diagram from PlantUML sources (as STRING), using prefix (as PREFIX)
  418. to choose where to display it."
  419. (let ((b (get-buffer plantuml-preview-buffer)))
  420. (when b
  421. (kill-buffer b)))
  422. (let* ((imagep (and (display-images-p)
  423. (plantuml-is-image-output-p)))
  424. (buf (get-buffer-create plantuml-preview-buffer))
  425. (coding-system-for-read (and imagep 'binary))
  426. (coding-system-for-write (and imagep 'binary)))
  427. (plantuml-exec-mode-preview-string prefix (plantuml-get-exec-mode) string buf)))
  428. (defun plantuml-preview-buffer (prefix)
  429. "Preview diagram from the PlantUML sources in the current buffer.
  430. Uses prefix (as PREFIX) to choose where to display it:
  431. - 4 (when prefixing the command with C-u) -> new window
  432. - 16 (when prefixing the command with C-u C-u) -> new frame.
  433. - else -> new buffer"
  434. (interactive "p")
  435. (plantuml-preview-string prefix (buffer-string)))
  436. (defun plantuml-preview-region (prefix begin end)
  437. "Preview diagram from the PlantUML sources in from BEGIN to END.
  438. Uses the current region when called interactively.
  439. Uses prefix (as PREFIX) to choose where to display it:
  440. - 4 (when prefixing the command with C-u) -> new window
  441. - 16 (when prefixing the command with C-u C-u) -> new frame.
  442. - else -> new buffer"
  443. (interactive "p\nr")
  444. (plantuml-preview-string prefix (concat "@startuml\n"
  445. (buffer-substring-no-properties
  446. begin end)
  447. "\n@enduml")))
  448. (defun plantuml-preview-current-block (prefix)
  449. "Preview diagram from the PlantUML sources from the previous @startuml to the next @enduml.
  450. Uses prefix (as PREFIX) to choose where to display it:
  451. - 4 (when prefixing the command with C-u) -> new window
  452. - 16 (when prefixing the command with C-u C-u) -> new frame.
  453. - else -> new buffer"
  454. (interactive "p")
  455. (save-restriction
  456. (narrow-to-region
  457. (search-backward "@startuml") (search-forward "@enduml"))
  458. (plantuml-preview-buffer prefix)))
  459. (defun plantuml-preview (prefix)
  460. "Preview diagram from the PlantUML sources.
  461. Uses the current region if one is active, or the entire buffer otherwise.
  462. Uses prefix (as PREFIX) to choose where to display it:
  463. - 4 (when prefixing the command with C-u) -> new window
  464. - 16 (when prefixing the command with C-u C-u) -> new frame.
  465. - else -> new buffer"
  466. (interactive "p")
  467. (if mark-active
  468. (plantuml-preview-region prefix (region-beginning) (region-end))
  469. (plantuml-preview-buffer prefix)))
  470. (defun plantuml-init-once (&optional mode)
  471. "Ensure initialization only happens once. Use exec mode MODE to load the language details or by first querying `plantuml-get-exec-mode'."
  472. (let ((mode (or mode (plantuml-get-exec-mode))))
  473. (unless plantuml-kwdList
  474. (plantuml-init mode)
  475. (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\\)\\)\\>\\)"))
  476. (defvar plantuml-keywords-regexp (concat "^\\s *" (regexp-opt plantuml-keywords 'words) "\\|\\(<\\|<|\\|\\*\\|o\\)\\(\\.+\\|-+\\)\\|\\(\\.+\\|-+\\)\\(>\\||>\\|\\*\\|o\\)\\|\\.\\{2,\\}\\|-\\{2,\\}"))
  477. (defvar plantuml-builtins-regexp (regexp-opt plantuml-builtins 'words))
  478. (defvar plantuml-preprocessors-regexp (concat "^\\s *" (regexp-opt plantuml-preprocessors 'words)))
  479. ;; Below are the regexp's for indentation.
  480. ;; Notes:
  481. ;; - there is some control on what it is indented by overriding some of below
  482. ;; X-start and X-end regexp before plantuml-mode is loaded. E.g., to disable
  483. ;; indentation on activate, you might define in your .emacs something like
  484. ;; (setq plantuml-indent-regexp-activate-start
  485. ;; "NEVER MATCH THIS EXPRESSION"); define _before_ load plantuml-mode!
  486. ;; (setq plantuml-indent-regexp-activate-end
  487. ;; "NEVER MATCH THIS EXPRESSION"); define _before_ load plantuml-mode!
  488. ;; - due to the nature of using (context-insensitive) regexp, indentation have
  489. ;; following limitations
  490. ;; - commands commented out by /' ... '/ will _not_ be ignored
  491. ;; and potentially lead to miss-indentation
  492. ;; - you can though somewhat correct mis-indentation by adding in '-comment lines
  493. ;; PLANTUML_MODE_INDENT_INCREASE and/or PLANTUML_MODE_INDENT_DECREASE
  494. ;; to increase and/or decrease the level of indentation
  495. ;; (Note: the line with the comment should not contain any text matching other indent
  496. ;; regexp or this user-control instruction will be ignored; also at most will count
  497. ;; per line ...)
  498. (defvar plantuml-indent-regexp-block-start "^.*{\s*$"
  499. "Indentation regex for all plantuml elements that might define a {} block.
  500. Plantuml elements like skinparam, rectangle, sprite, package, etc.
  501. The opening { has to be the last visible character in the line (whitespace
  502. might follow).")
  503. (defvar plantuml-indent-regexp-note-start "^\s*\\(floating\s+\\)?[hr]?note\s+\\(right\\|left\\|top\\|bottom\\|over\\)[^:]*?$" "simplyfied regex; note syntax is especially inconsistent across diagrams")
  504. (defvar plantuml-indent-regexp-group-start "^\s*\\(alt\\|else\\|opt\\|loop\\|par\\|break\\|critical\\|group\\)\\(?:\s+.+\\|$\\)"
  505. "Indentation regex for plantuml group elements that are defined for sequence diagrams.
  506. Two variants for groups: keyword is either followed by whitespace and some text
  507. or it is followed by line end.")
  508. (defvar plantuml-indent-regexp-activate-start "^\s*activate\s+.+$")
  509. (defvar plantuml-indent-regexp-box-start "^\s*box\s+.+$")
  510. (defvar plantuml-indent-regexp-ref-start "^\s*ref\s+over\s+[^:]+?$")
  511. (defvar plantuml-indent-regexp-title-start "^\s*title\s*\\('.*\\)?$")
  512. (defvar plantuml-indent-regexp-header-start "^\s*\\(?:\\(?:center\\|left\\|right\\)\s+header\\|header\\)\s*\\('.*\\)?$")
  513. (defvar plantuml-indent-regexp-footer-start "^\s*\\(?:\\(?:center\\|left\\|right\\)\s+footer\\|footer\\)\s*\\('.*\\)?$")
  514. (defvar plantuml-indent-regexp-legend-start "^\s*\\(?:legend\\|legend\s+\\(?:bottom\\|top\\)\\|legend\s+\\(?:center\\|left\\|right\\)\\|legend\s+\\(?:bottom\\|top\\)\s+\\(?:center\\|left\\|right\\)\\)\s*\\('.*\\)?$")
  515. (defvar plantuml-indent-regexp-oldif-start "^.*if\s+\".*\"\s+then\s*\\('.*\\)?$" "used in current activity diagram, sometimes already mentioned as deprecated")
  516. (defvar plantuml-indent-regexp-macro-start "^\s*!definelong.*$")
  517. (defvar plantuml-indent-regexp-user-control-start "^.*'.*\s*PLANTUML_MODE_INDENT_INCREASE\s*.*$")
  518. (defvar plantuml-indent-regexp-start (list plantuml-indent-regexp-block-start
  519. plantuml-indent-regexp-group-start
  520. plantuml-indent-regexp-activate-start
  521. plantuml-indent-regexp-box-start
  522. plantuml-indent-regexp-ref-start
  523. plantuml-indent-regexp-legend-start
  524. plantuml-indent-regexp-note-start
  525. plantuml-indent-regexp-oldif-start
  526. plantuml-indent-regexp-title-start
  527. plantuml-indent-regexp-header-start
  528. plantuml-indent-regexp-footer-start
  529. plantuml-indent-regexp-macro-start
  530. plantuml-indent-regexp-oldif-start
  531. plantuml-indent-regexp-user-control-start))
  532. (defvar plantuml-indent-regexp-block-end "^\s*\\(?:}\\|endif\\|else\s*.*\\|end\\)\s*\\('.*\\)?$")
  533. (defvar plantuml-indent-regexp-note-end "^\s*\\(end\s+note\\|end[rh]note\\)\s*\\('.*\\)?$")
  534. (defvar plantuml-indent-regexp-group-end "^\s*end\s*\\('.*\\)?$")
  535. (defvar plantuml-indent-regexp-activate-end "^\s*deactivate\s+.+$")
  536. (defvar plantuml-indent-regexp-box-end "^\s*end\s+box\s*\\('.*\\)?$")
  537. (defvar plantuml-indent-regexp-ref-end "^\s*end\s+ref\s*\\('.*\\)?$")
  538. (defvar plantuml-indent-regexp-title-end "^\s*end\s+title\s*\\('.*\\)?$")
  539. (defvar plantuml-indent-regexp-header-end "^\s*endheader\s*\\('.*\\)?$")
  540. (defvar plantuml-indent-regexp-footer-end "^\s*endfooter\s*\\('.*\\)?$")
  541. (defvar plantuml-indent-regexp-legend-end "^\s*endlegend\s*\\('.*\\)?$")
  542. (defvar plantuml-indent-regexp-oldif-end "^\s*\\(endif\\|else\\)\s*\\('.*\\)?$")
  543. (defvar plantuml-indent-regexp-macro-end "^\s*!enddefinelong\s*\\('.*\\)?$")
  544. (defvar plantuml-indent-regexp-user-control-end "^.*'.*\s*PLANTUML_MODE_INDENT_DECREASE\s*.*$")
  545. (defvar plantuml-indent-regexp-end (list plantuml-indent-regexp-block-end
  546. plantuml-indent-regexp-group-end
  547. plantuml-indent-regexp-activate-end
  548. plantuml-indent-regexp-box-end
  549. plantuml-indent-regexp-ref-end
  550. plantuml-indent-regexp-legend-end
  551. plantuml-indent-regexp-note-end
  552. plantuml-indent-regexp-oldif-end
  553. plantuml-indent-regexp-title-end
  554. plantuml-indent-regexp-header-end
  555. plantuml-indent-regexp-footer-end
  556. plantuml-indent-regexp-macro-end
  557. plantuml-indent-regexp-oldif-end
  558. plantuml-indent-regexp-user-control-end))
  559. (setq plantuml-font-lock-keywords
  560. `(
  561. (,plantuml-types-regexp . font-lock-type-face)
  562. (,plantuml-keywords-regexp . font-lock-keyword-face)
  563. (,plantuml-builtins-regexp . font-lock-builtin-face)
  564. (,plantuml-preprocessors-regexp . font-lock-preprocessor-face)
  565. ;; note: order matters
  566. ))
  567. (setq plantuml-kwdList (make-hash-table :test 'equal))
  568. (mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-types)
  569. (mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-keywords)
  570. (mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-builtins)
  571. (mapc (lambda (x) (puthash x t plantuml-kwdList)) plantuml-preprocessors)
  572. (put 'plantuml-kwdList 'risky-local-variable t)
  573. ;; clear memory
  574. (setq plantuml-types nil)
  575. (setq plantuml-keywords nil)
  576. (setq plantuml-builtins nil)
  577. (setq plantuml-preprocessors nil)
  578. (setq plantuml-types-regexp nil)
  579. (setq plantuml-keywords-regexp nil)
  580. (setq plantuml-builtins-regexp nil)
  581. (setq plantuml-preprocessors-regexp nil))))
  582. (defun plantuml-complete-symbol ()
  583. "Perform keyword completion on word before cursor."
  584. (interactive)
  585. (let ((posEnd (point))
  586. (meat (thing-at-point 'symbol))
  587. maxMatchResult)
  588. (when (not meat) (setq meat ""))
  589. (setq maxMatchResult (try-completion meat plantuml-kwdList))
  590. (cond ((eq maxMatchResult t))
  591. ((null maxMatchResult)
  592. (message "Can't find completion for \"%s\"" meat)
  593. (ding))
  594. ((not (string= meat maxMatchResult))
  595. (delete-region (- posEnd (length meat)) posEnd)
  596. (insert maxMatchResult))
  597. (t (message "Making completion list...")
  598. (with-output-to-temp-buffer "*Completions*"
  599. (display-completion-list
  600. (all-completions meat plantuml-kwdList)))
  601. (message "Making completion list...%s" "done")))))
  602. ;; indentation
  603. (defun plantuml-current-block-depth ()
  604. "Trace the current block indentation level by recursively looking back line by line."
  605. (save-excursion
  606. (let ((relative-depth 0))
  607. ;; current line
  608. (beginning-of-line)
  609. (if (-any? 'looking-at plantuml-indent-regexp-end)
  610. (setq relative-depth (1- relative-depth)))
  611. ;; from current line backwards to beginning of buffer
  612. (while (not (bobp))
  613. (forward-line -1)
  614. (if (-any? 'looking-at plantuml-indent-regexp-end)
  615. (setq relative-depth (1- relative-depth)))
  616. (if (-any? 'looking-at plantuml-indent-regexp-start)
  617. (setq relative-depth (1+ relative-depth))))
  618. (if (<= relative-depth 0)
  619. 0
  620. relative-depth))))
  621. (defun plantuml-indent-line ()
  622. "Indent the current line to its desired indentation level.
  623. Restore point to same position in text of the line as before indentation."
  624. (interactive)
  625. ;; store position of point in line measured from end of line
  626. (let ((original-position-eol (- (line-end-position) (point))))
  627. (save-excursion
  628. (beginning-of-line)
  629. (indent-line-to (* tab-width (plantuml-current-block-depth))))
  630. ;; restore position in text of line
  631. (goto-char (- (line-end-position) original-position-eol))))
  632. ;;;###autoload
  633. (add-to-list 'auto-mode-alist '("\\.\\(plantuml\\|pum\\|plu\\)\\'" . plantuml-mode))
  634. ;;;###autoload
  635. (define-derived-mode plantuml-mode prog-mode "plantuml"
  636. "Major mode for plantuml.
  637. Shortcuts Command Name
  638. \\[plantuml-complete-symbol] `plantuml-complete-symbol'"
  639. (plantuml-init-once)
  640. (make-local-variable 'plantuml-output-type)
  641. (set (make-local-variable 'comment-start-skip) "\\('+\\|/'+\\)\\s *")
  642. (set (make-local-variable 'comment-start) "/'")
  643. (set (make-local-variable 'comment-end) "'/")
  644. (set (make-local-variable 'comment-multi-line) t)
  645. (set (make-local-variable 'comment-style) 'extra-line)
  646. (set (make-local-variable 'indent-line-function) 'plantuml-indent-line)
  647. (setq font-lock-defaults '((plantuml-font-lock-keywords) nil t)))
  648. (defun plantuml-deprecation-warning ()
  649. "Warns the user about the deprecation of the `puml-mode' project."
  650. (if (and plantuml-suppress-deprecation-warning
  651. (featurep 'puml-mode))
  652. (display-warning :warning
  653. "`puml-mode' is now deprecated and no longer updated, but it's still present in your system. \
  654. You should move your configuration to use `plantuml-mode'. \
  655. See more at https://github.com/skuro/puml-mode/issues/26")))
  656. (add-hook 'plantuml-mode-hook 'plantuml-deprecation-warning)
  657. (provide 'plantuml-mode)
  658. ;;; plantuml-mode.el ends here