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