lua-mode.el (92809B)
1 ;;; lua-mode.el --- a major-mode for editing Lua scripts -*- lexical-binding: t -*- 2 3 ;; Author: 2011-2013 immerrr <immerrr+lua@gmail.com> 4 ;; 2010-2011 Reuben Thomas <rrt@sc3d.org> 5 ;; 2006 Juergen Hoetzel <juergen@hoetzel.info> 6 ;; 2004 various (support for Lua 5 and byte compilation) 7 ;; 2001 Christian Vogler <cvogler@gradient.cis.upenn.edu> 8 ;; 1997 Bret Mogilefsky <mogul-lua@gelatinous.com> starting from 9 ;; tcl-mode by Gregor Schmid <schmid@fb3-s7.math.tu-berlin.de> 10 ;; with tons of assistance from 11 ;; Paul Du Bois <pld-lua@gelatinous.com> and 12 ;; Aaron Smith <aaron-lua@gelatinous.com>. 13 ;; 14 ;; URL: https://immerrr.github.io/lua-mode 15 ;; Version: 20221027 16 ;; Package-Requires: ((emacs "24.3")) 17 ;; 18 ;; This file is NOT part of Emacs. 19 ;; 20 ;; This program is free software; you can redistribute it and/or modify 21 ;; it under the terms of the GNU General Public License as published by 22 ;; the Free Software Foundation, either version 3 of the License, or 23 ;; (at your option) any later version. 24 ;; 25 ;; This program is distributed in the hope that it will be useful, 26 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 27 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 ;; GNU General Public License for more details. 29 ;; 30 ;; You should have received a copy of the GNU General Public License 31 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 32 33 ;; Keywords: languages, processes, tools 34 35 ;; This field is expanded to commit SHA and commit date during the 36 ;; archive creation. 37 ;; Revision: $Format:%h (%cD)$ 38 ;; 39 40 ;;; Commentary: 41 42 ;; lua-mode provides support for editing Lua, including automatic 43 ;; indentation, syntactical font-locking, running interactive shell, 44 ;; Flymake checks with luacheck, interacting with `hs-minor-mode' and 45 ;; online documentation lookup. 46 47 ;; The following variables are available for customization (see more via 48 ;; `M-x customize-group lua`): 49 50 ;; - Var `lua-indent-level': 51 ;; indentation offset in spaces 52 ;; - Var `lua-indent-string-contents': 53 ;; set to `t` if you like to have contents of multiline strings to be 54 ;; indented like comments 55 ;; - Var `lua-indent-nested-block-content-align': 56 ;; set to `nil' to stop aligning the content of nested blocks with the 57 ;; open parenthesis 58 ;; - Var `lua-indent-close-paren-align': 59 ;; set to `t' to align close parenthesis with the open parenthesis, 60 ;; rather than with the beginning of the line 61 ;; - Var `lua-mode-hook': 62 ;; list of functions to execute when lua-mode is initialized 63 ;; - Var `lua-documentation-url': 64 ;; base URL for documentation lookup 65 ;; - Var `lua-documentation-function': function used to 66 ;; show documentation (`eww` is a viable alternative for Emacs 25) 67 68 ;; These are variables/commands that operate on the Lua process: 69 70 ;; - Var `lua-default-application': 71 ;; command to start the Lua process (REPL) 72 ;; - Var `lua-default-command-switches': 73 ;; arguments to pass to the Lua process on startup (make sure `-i` is there 74 ;; if you expect working with Lua shell interactively) 75 ;; - Cmd `lua-start-process': start new REPL process, usually happens automatically 76 ;; - Cmd `lua-kill-process': kill current REPL process 77 78 ;; These are variables/commands for interaction with the Lua process: 79 80 ;; - Cmd `lua-show-process-buffer': switch to REPL buffer 81 ;; - Cmd `lua-hide-process-buffer': hide window showing REPL buffer 82 ;; - Var `lua-always-show': show REPL buffer after sending something 83 ;; - Cmd `lua-send-buffer': send whole buffer 84 ;; - Cmd `lua-send-current-line': send current line 85 ;; - Cmd `lua-send-defun': send current top-level function 86 ;; - Cmd `lua-send-region': send active region 87 ;; - Cmd `lua-restart-with-whole-file': restart REPL and send whole buffer 88 89 ;; To enable on-the-fly linting, make sure you have the luacheck 90 ;; program installed (available from luarocks) and activate 91 ;; `flymake-mode'. 92 93 ;; See "M-x apropos-command ^lua-" for a list of commands. 94 ;; See "M-x customize-group lua" for a list of customizable variables. 95 96 97 ;;; Code: 98 (eval-when-compile 99 (require 'cl-lib)) 100 101 (require 'comint) 102 (require 'newcomment) 103 (require 'rx) 104 105 106 ;; rx-wrappers for Lua 107 108 (eval-when-compile 109 ;; Silence compilation warning about `compilation-error-regexp-alist' defined 110 ;; in compile.el. 111 (require 'compile)) 112 113 (eval-and-compile 114 (if (fboundp #'rx-let) 115 (progn 116 ;; Emacs 27+ way of customizing rx 117 (defvar lua--rx-bindings) 118 119 (setq 120 lua--rx-bindings 121 '((symbol (&rest x) (seq symbol-start (or x) symbol-end)) 122 (ws (* (any " \t"))) 123 (ws+ (+ (any " \t"))) 124 125 (lua-name (symbol (seq (+ (any alpha "_")) (* (any alnum "_"))))) 126 (lua-funcname (seq lua-name (* ws "." ws lua-name) 127 (opt ws ":" ws lua-name))) 128 (lua-funcheader 129 ;; Outer (seq ...) is here to shy-group the definition 130 (seq (or (seq (symbol "function") ws (group-n 1 lua-funcname)) 131 (seq (group-n 1 lua-funcname) ws "=" ws 132 (symbol "function"))))) 133 (lua-number 134 (seq (or (seq (+ digit) (opt ".") (* digit)) 135 (seq (* digit) (opt ".") (+ digit))) 136 (opt (regexp "[eE][+-]?[0-9]+")))) 137 (lua-assignment-op (seq "=" (or buffer-end (not (any "="))))) 138 (lua-operator (or "+" "-" "*" "/" "%" "^" "#" "==" "~=" "<=" ">=" "<" 139 ">" "=" ";" ":" "," "." ".." "...")) 140 (lua-keyword-operator (symbol "and" "not" "or")) 141 (lua-keyword 142 (symbol "break" "do" "else" "elseif" "end" "for" "function" 143 "goto" "if" "in" "local" "repeat" "return" 144 "then" "until" "while")) 145 (lua-up-to-9-variables 146 (seq (group-n 1 lua-name) ws 147 (? "," ws (group-n 2 lua-name) ws 148 (? "," ws (group-n 3 lua-name) ws 149 (? "," ws (group-n 4 lua-name) ws 150 (? "," ws (group-n 5 lua-name) ws 151 (? "," ws (group-n 6 lua-name) ws 152 (? "," ws (group-n 7 lua-name) ws 153 (? "," ws (group-n 8 lua-name) ws 154 (? "," ws (group-n 9 lua-name) ws)))))))))))) 155 156 (defmacro lua-rx (&rest regexps) 157 (eval `(rx-let ,lua--rx-bindings 158 (rx ,@regexps)))) 159 160 (defun lua-rx-to-string (form &optional no-group) 161 (rx-let-eval lua--rx-bindings 162 (rx-to-string form no-group)))) 163 (progn 164 ;; Pre-Emacs 27 way of customizing rx 165 (defvar lua-rx-constituents) 166 (defvar rx-parent) 167 168 (defun lua-rx-to-string (form &optional no-group) 169 "Lua-specific replacement for `rx-to-string'. 170 171 See `rx-to-string' documentation for more information FORM and 172 NO-GROUP arguments." 173 (let ((rx-constituents lua-rx-constituents)) 174 (rx-to-string form no-group))) 175 176 (defmacro lua-rx (&rest regexps) 177 "Lua-specific replacement for `rx'. 178 179 See `rx' documentation for more information about REGEXPS param." 180 (cond ((null regexps) 181 (error "No regexp")) 182 ((cdr regexps) 183 (lua-rx-to-string `(and ,@regexps) t)) 184 (t 185 (lua-rx-to-string (car regexps) t)))) 186 187 (defun lua--new-rx-form (form) 188 "Add FORM definition to `lua-rx' macro. 189 190 FORM is a cons (NAME . DEFN), see more in `rx-constituents' doc. 191 This function enables specifying new definitions using old ones: 192 if DEFN is a list that starts with `:rx' symbol its second 193 element is itself expanded with `lua-rx-to-string'. " 194 (let ((form-definition (cdr form))) 195 (when (and (listp form-definition) (eq ':rx (car form-definition))) 196 (setcdr form (lua-rx-to-string (cadr form-definition) 'nogroup))) 197 (push form lua-rx-constituents))) 198 199 (defun lua--rx-symbol (form) 200 ;; form is a list (symbol XXX ...) 201 ;; Skip initial 'symbol 202 (setq form (cdr form)) 203 ;; If there's only one element, take it from the list, otherwise wrap the 204 ;; whole list into `(or XXX ...)' form. 205 (setq form (if (eq 1 (length form)) 206 (car form) 207 (append '(or) form))) 208 (and (fboundp 'rx-form) ; Silence Emacs 27's byte-compiler. 209 (rx-form `(seq symbol-start ,form symbol-end) rx-parent))) 210 211 (setq lua-rx-constituents (copy-sequence rx-constituents)) 212 213 (mapc 'lua--new-rx-form 214 `((symbol lua--rx-symbol 1 nil) 215 (ws . "[ \t]*") (ws+ . "[ \t]+") 216 (lua-name :rx (symbol (regexp "[[:alpha:]_]+[[:alnum:]_]*"))) 217 (lua-funcname 218 :rx (seq lua-name (* ws "." ws lua-name) 219 (opt ws ":" ws lua-name))) 220 (lua-funcheader 221 ;; Outer (seq ...) is here to shy-group the definition 222 :rx (seq (or (seq (symbol "function") ws (group-n 1 lua-funcname)) 223 (seq (group-n 1 lua-funcname) ws "=" ws 224 (symbol "function"))))) 225 (lua-number 226 :rx (seq (or (seq (+ digit) (opt ".") (* digit)) 227 (seq (* digit) (opt ".") (+ digit))) 228 (opt (regexp "[eE][+-]?[0-9]+")))) 229 (lua-assignment-op 230 :rx (seq "=" (or buffer-end (not (any "="))))) 231 (lua-operator 232 :rx (or "+" "-" "*" "/" "%" "^" "#" "==" "~=" "<=" ">=" "<" 233 ">" "=" ";" ":" "," "." ".." "...")) 234 (lua-keyword-operator 235 :rx (symbol "and" "not" "or")) 236 (lua-keyword 237 :rx (symbol "break" "do" "else" "elseif" "end" "for" "function" 238 "goto" "if" "in" "local" "repeat" "return" 239 "then" "until" "while")) 240 (lua-up-to-9-variables 241 :rx (seq (group-n 1 lua-name) ws 242 (? "," ws (group-n 2 lua-name) ws 243 (? "," ws (group-n 3 lua-name) ws 244 (? "," ws (group-n 4 lua-name) ws 245 (? "," ws (group-n 5 lua-name) ws 246 (? "," ws (group-n 6 lua-name) ws 247 (? "," ws (group-n 7 lua-name) ws 248 (? "," ws (group-n 8 lua-name) ws 249 (? "," ws (group-n 9 lua-name) ws))))))))))))))) 250 251 252 ;; Local variables 253 (defgroup lua nil 254 "Major mode for editing Lua code." 255 :prefix "lua-" 256 :group 'languages) 257 258 (defcustom lua-indent-level 3 259 "Amount by which Lua subexpressions are indented." 260 :type 'integer 261 :group 'lua 262 :safe #'integerp) 263 264 (defcustom lua-comment-start "-- " 265 "Default value of `comment-start'." 266 :type 'string 267 :group 'lua) 268 269 (defcustom lua-comment-start-skip "---*[ \t]*" 270 "Default value of `comment-start-skip'." 271 :type 'string 272 :group 'lua) 273 274 (defcustom lua-default-application "lua" 275 "Default application to run in Lua process. 276 277 Can be a string, where it denotes a command to be executed to 278 start Lua process, or a (HOST . PORT) cons, that can be used to 279 connect to Lua process running remotely." 280 :type '(choice (string) 281 (cons string integer)) 282 :group 'lua) 283 284 (defcustom lua-default-command-switches (list "-i") 285 "Command switches for `lua-default-application'. 286 Should be a list of strings." 287 :type '(repeat string) 288 :group 'lua) 289 (make-variable-buffer-local 'lua-default-command-switches) 290 291 (defcustom lua-always-show t 292 "*Non-nil means display lua-process-buffer after sending a command." 293 :type 'boolean 294 :group 'lua) 295 296 (defcustom lua-documentation-function 'browse-url 297 "Function used to fetch the Lua reference manual." 298 :type `(radio (function-item browse-url) 299 ,@(when (fboundp 'eww) '((function-item eww))) 300 ,@(when (fboundp 'w3m-browse-url) '((function-item w3m-browse-url))) 301 (function :tag "Other function")) 302 :group 'lua) 303 304 (defcustom lua-documentation-url 305 (or (and (file-readable-p "/usr/share/doc/lua/manual.html") 306 "file:///usr/share/doc/lua/manual.html") 307 "http://www.lua.org/manual/5.1/manual.html") 308 "URL pointing to the Lua reference manual." 309 :type 'string 310 :group 'lua) 311 312 313 (defvar lua-process nil 314 "The active Lua process") 315 316 (defvar lua-process-buffer nil 317 "Buffer used for communication with the Lua process.") 318 319 (defun lua--customize-set-prefix-key (prefix-key-sym prefix-key-val) 320 (cl-assert (eq prefix-key-sym 'lua-prefix-key)) 321 (set prefix-key-sym (if (and prefix-key-val (> (length prefix-key-val) 0)) 322 ;; read-kbd-macro returns a string or a vector 323 ;; in both cases (elt x 0) is ok 324 (elt (read-kbd-macro prefix-key-val) 0))) 325 (if (fboundp 'lua-prefix-key-update-bindings) 326 (lua-prefix-key-update-bindings))) 327 328 (defcustom lua-prefix-key "\C-c" 329 "Prefix for all lua-mode commands." 330 :type 'string 331 :group 'lua 332 :set 'lua--customize-set-prefix-key 333 :get '(lambda (sym) 334 (let ((val (eval sym))) (if val (single-key-description (eval sym)) "")))) 335 336 (defvar lua-mode-menu (make-sparse-keymap "Lua") 337 "Keymap for lua-mode's menu.") 338 339 (defvar lua-prefix-mode-map 340 (eval-when-compile 341 (let ((result-map (make-sparse-keymap))) 342 (mapc (lambda (key_defn) 343 (define-key result-map (read-kbd-macro (car key_defn)) (cdr key_defn))) 344 '(("C-l" . lua-send-buffer) 345 ("C-f" . lua-search-documentation))) 346 result-map)) 347 "Keymap that is used to define keys accessible by `lua-prefix-key'. 348 349 If the latter is nil, the keymap translates into `lua-mode-map' verbatim.") 350 351 (defvar lua--electric-indent-chars 352 (mapcar #'string-to-char '("}" "]" ")"))) 353 354 355 (defvar lua-mode-map 356 (let ((result-map (make-sparse-keymap))) 357 (unless (boundp 'electric-indent-chars) 358 (mapc (lambda (electric-char) 359 (define-key result-map 360 (read-kbd-macro 361 (char-to-string electric-char)) 362 #'lua-electric-match)) 363 lua--electric-indent-chars)) 364 (define-key result-map [menu-bar lua-mode] (cons "Lua" lua-mode-menu)) 365 (define-key result-map [remap backward-up-list] 'lua-backward-up-list) 366 367 ;; handle prefix-keyed bindings: 368 ;; * if no prefix, set prefix-map as parent, i.e. 369 ;; if key is not defined look it up in prefix-map 370 ;; * if prefix is set, bind the prefix-map to that key 371 (if lua-prefix-key 372 (define-key result-map (vector lua-prefix-key) lua-prefix-mode-map) 373 (set-keymap-parent result-map lua-prefix-mode-map)) 374 result-map) 375 "Keymap used in lua-mode buffers.") 376 377 (defvar lua-electric-flag t 378 "If t, electric actions (like automatic reindentation) will happen when an electric 379 key like `{' is pressed") 380 (make-variable-buffer-local 'lua-electric-flag) 381 382 (defcustom lua-prompt-regexp "[^\n]*\\(>[\t ]+\\)+$" 383 "Regexp which matches the Lua program's prompt." 384 :type 'regexp 385 :group 'lua) 386 387 (defcustom lua-traceback-line-re 388 ;; This regexp skips prompt and meaningless "stdin:N:" prefix when looking 389 ;; for actual file-line locations. 390 "^\\(?:[\t ]*\\|.*>[\t ]+\\)\\(?:[^\n\t ]+:[0-9]+:[\t ]*\\)*\\(?:\\([^\n\t ]+\\):\\([0-9]+\\):\\)" 391 "Regular expression that describes tracebacks and errors." 392 :type 'regexp 393 :group 'lua) 394 395 (defvar lua--repl-buffer-p nil 396 "Buffer-local flag saying if this is a Lua REPL buffer.") 397 (make-variable-buffer-local 'lua--repl-buffer-p) 398 399 400 (defadvice compilation-find-file (around lua--repl-find-file 401 (marker filename directory &rest formats) 402 activate) 403 "Return Lua REPL buffer when looking for \"stdin\" file in it." 404 (if (and 405 lua--repl-buffer-p 406 (string-equal filename "stdin") 407 ;; NOTE: this doesn't traverse `compilation-search-path' when 408 ;; looking for filename. 409 (not (file-exists-p (expand-file-name 410 filename 411 (when directory (expand-file-name directory)))))) 412 (setq ad-return-value (current-buffer)) 413 ad-do-it)) 414 415 416 (defadvice compilation-goto-locus (around lua--repl-goto-locus 417 (msg mk end-mk) 418 activate) 419 "When message points to Lua REPL buffer, go to the message itself. 420 Usually, stdin:XX line number points to nowhere." 421 (let ((errmsg-buf (marker-buffer msg)) 422 (error-buf (marker-buffer mk))) 423 (if (and (with-current-buffer errmsg-buf lua--repl-buffer-p) 424 (eq error-buf errmsg-buf)) 425 (progn 426 (compilation-set-window (display-buffer (marker-buffer msg)) msg) 427 (goto-char msg)) 428 ad-do-it))) 429 430 431 (defcustom lua-indent-string-contents nil 432 "If non-nil, contents of multiline string will be indented. 433 Otherwise leading amount of whitespace on each line is preserved." 434 :group 'lua 435 :type 'boolean 436 :safe #'booleanp) 437 438 (defcustom lua-indent-nested-block-content-align t 439 "If non-nil, the contents of nested blocks are indented to 440 align with the column of the opening parenthesis, rather than 441 just forward by `lua-indent-level'." 442 :group 'lua 443 :type 'boolean 444 :safe #'booleanp) 445 446 (defcustom lua-indent-close-paren-align t 447 "If non-nil, close parenthesis are aligned with their open 448 parenthesis. If nil, close parenthesis are aligned to the 449 beginning of the line." 450 :group 'lua 451 :type 'boolean 452 :safe #'booleanp) 453 454 (defcustom lua-jump-on-traceback t 455 "*Jump to innermost traceback location in *lua* buffer. When this 456 variable is non-nil and a traceback occurs when running Lua code in a 457 process, jump immediately to the source code of the innermost 458 traceback location." 459 :type 'boolean 460 :group 'lua) 461 462 (defcustom lua-mode-hook nil 463 "Hooks called when Lua mode fires up." 464 :type 'hook 465 :group 'lua) 466 467 (defvar lua-region-start (make-marker) 468 "Start of special region for Lua communication.") 469 470 (defvar lua-region-end (make-marker) 471 "End of special region for Lua communication.") 472 473 (defvar lua-emacs-menu 474 '(["Restart With Whole File" lua-restart-with-whole-file t] 475 ["Kill Process" lua-kill-process t] 476 ["Hide Process Buffer" lua-hide-process-buffer t] 477 ["Show Process Buffer" lua-show-process-buffer t] 478 ["Beginning Of Proc" lua-beginning-of-proc t] 479 ["End Of Proc" lua-end-of-proc t] 480 ["Set Lua-Region Start" lua-set-lua-region-start t] 481 ["Set Lua-Region End" lua-set-lua-region-end t] 482 ["Send Lua-Region" lua-send-lua-region t] 483 ["Send Current Line" lua-send-current-line t] 484 ["Send Region" lua-send-region t] 485 ["Send Proc" lua-send-proc t] 486 ["Send Buffer" lua-send-buffer t] 487 ["Search Documentation" lua-search-documentation t]) 488 "Emacs menu for Lua mode.") 489 490 ;; the whole defconst is inside eval-when-compile, because it's later referenced 491 ;; inside another eval-and-compile block 492 (eval-and-compile 493 (defconst 494 lua--builtins 495 (let* 496 ((modules 497 '("_G" "_VERSION" "assert" "collectgarbage" "dofile" "error" "getfenv" 498 "getmetatable" "ipairs" "load" "loadfile" "loadstring" "module" 499 "next" "pairs" "pcall" "print" "rawequal" "rawget" "rawlen" "rawset" 500 "require" "select" "setfenv" "setmetatable" "tonumber" "tostring" 501 "type" "unpack" "xpcall" "self" 502 ("bit32" . ("arshift" "band" "bnot" "bor" "btest" "bxor" "extract" 503 "lrotate" "lshift" "replace" "rrotate" "rshift")) 504 ("coroutine" . ("create" "isyieldable" "resume" "running" "status" 505 "wrap" "yield")) 506 ("debug" . ("debug" "getfenv" "gethook" "getinfo" "getlocal" 507 "getmetatable" "getregistry" "getupvalue" "getuservalue" 508 "setfenv" "sethook" "setlocal" "setmetatable" 509 "setupvalue" "setuservalue" "traceback" "upvalueid" 510 "upvaluejoin")) 511 ("io" . ("close" "flush" "input" "lines" "open" "output" "popen" 512 "read" "stderr" "stdin" "stdout" "tmpfile" "type" "write")) 513 ("math" . ("abs" "acos" "asin" "atan" "atan2" "ceil" "cos" "cosh" 514 "deg" "exp" "floor" "fmod" "frexp" "huge" "ldexp" "log" 515 "log10" "max" "maxinteger" "min" "mininteger" "modf" "pi" 516 "pow" "rad" "random" "randomseed" "sin" "sinh" "sqrt" 517 "tan" "tanh" "tointeger" "type" "ult")) 518 ("os" . ("clock" "date" "difftime" "execute" "exit" "getenv" 519 "remove" "rename" "setlocale" "time" "tmpname")) 520 ("package" . ("config" "cpath" "loaded" "loaders" "loadlib" "path" 521 "preload" "searchers" "searchpath" "seeall")) 522 ("string" . ("byte" "char" "dump" "find" "format" "gmatch" "gsub" 523 "len" "lower" "match" "pack" "packsize" "rep" "reverse" 524 "sub" "unpack" "upper")) 525 ("table" . ("concat" "insert" "maxn" "move" "pack" "remove" "sort" 526 "unpack")) 527 ("utf8" . ("char" "charpattern" "codepoint" "codes" "len" 528 "offset"))))) 529 530 (cl-labels 531 ((module-name-re (x) 532 (concat "\\(?1:\\_<" 533 (if (listp x) (car x) x) 534 "\\_>\\)")) 535 (module-members-re (x) (if (listp x) 536 (concat "\\(?:[ \t]*\\.[ \t]*" 537 "\\_<\\(?2:" 538 (regexp-opt (cdr x)) 539 "\\)\\_>\\)?") 540 ""))) 541 542 (concat 543 ;; common prefix: 544 ;; - beginning-of-line 545 ;; - or neither of [ '.', ':' ] to exclude "foo.string.rep" 546 ;; - or concatenation operator ".." 547 "\\(?:^\\|[^:. \t]\\|[.][.]\\)" 548 ;; optional whitespace 549 "[ \t]*" 550 "\\(?:" 551 ;; any of modules/functions 552 (mapconcat (lambda (x) (concat (module-name-re x) 553 (module-members-re x))) 554 modules 555 "\\|") 556 "\\)")))) 557 558 "A regexp that matches Lua builtin functions & variables. 559 560 This is a compilation of 5.1, 5.2 and 5.3 builtins taken from the 561 index of respective Lua reference manuals.") 562 563 564 (defvar lua-font-lock-keywords 565 `(;; highlight the hash-bang line "#!/foo/bar/lua" as comment 566 ("^#!.*$" . font-lock-comment-face) 567 568 ;; Builtin constants 569 (,(lua-rx (symbol "true" "false" "nil")) 570 . font-lock-constant-face) 571 572 ;; Keywords 573 (,(lua-rx (or lua-keyword lua-keyword-operator)) 574 . font-lock-keyword-face) 575 576 ;; Labels used by the "goto" statement 577 ;; Highlights the following syntax: ::label:: 578 (,(lua-rx "::" ws lua-name ws "::") 579 . font-lock-constant-face) 580 581 ;; Highlights the name of the label in the "goto" statement like 582 ;; "goto label" 583 (,(lua-rx (symbol (seq "goto" ws+ (group-n 1 lua-name)))) 584 (1 font-lock-constant-face)) 585 586 ;; Highlight Lua builtin functions and variables 587 (,lua--builtins 588 (1 font-lock-builtin-face) (2 font-lock-builtin-face nil noerror)) 589 590 (,(lua-rx (symbol "for") ws+ lua-up-to-9-variables) 591 (1 font-lock-variable-name-face) 592 (2 font-lock-variable-name-face nil noerror) 593 (3 font-lock-variable-name-face nil noerror) 594 (4 font-lock-variable-name-face nil noerror) 595 (5 font-lock-variable-name-face nil noerror) 596 (6 font-lock-variable-name-face nil noerror) 597 (7 font-lock-variable-name-face nil noerror) 598 (8 font-lock-variable-name-face nil noerror) 599 (9 font-lock-variable-name-face nil noerror)) 600 601 (,(lua-rx (symbol "function") (? ws+ lua-funcname) ws "(" ws lua-up-to-9-variables) 602 (1 font-lock-variable-name-face) 603 (2 font-lock-variable-name-face nil noerror) 604 (3 font-lock-variable-name-face nil noerror) 605 (4 font-lock-variable-name-face nil noerror) 606 (5 font-lock-variable-name-face nil noerror) 607 (6 font-lock-variable-name-face nil noerror) 608 (7 font-lock-variable-name-face nil noerror) 609 (8 font-lock-variable-name-face nil noerror) 610 (9 font-lock-variable-name-face nil noerror)) 611 612 (,(lua-rx lua-funcheader) 613 (1 font-lock-function-name-face)) 614 615 ;; local x, y, z 616 ;; local x = ..... 617 ;; 618 ;; NOTE: this is intentionally below funcheader matcher, so that in 619 ;; 620 ;; local foo = function() ... 621 ;; 622 ;; "foo" is fontified as function-name-face, and variable-name-face is not applied. 623 (,(lua-rx (symbol "local") ws+ lua-up-to-9-variables) 624 (1 font-lock-variable-name-face) 625 (2 font-lock-variable-name-face nil noerror) 626 (3 font-lock-variable-name-face nil noerror) 627 (4 font-lock-variable-name-face nil noerror) 628 (5 font-lock-variable-name-face nil noerror) 629 (6 font-lock-variable-name-face nil noerror) 630 (7 font-lock-variable-name-face nil noerror) 631 (8 font-lock-variable-name-face nil noerror) 632 (9 font-lock-variable-name-face nil noerror)) 633 634 (,(lua-rx (or (group-n 1 635 "@" (symbol "author" "copyright" "field" "release" 636 "return" "see" "usage" "description")) 637 (seq (group-n 1 "@" (symbol "param" "class" "name")) ws+ 638 (group-n 2 lua-name)))) 639 (1 font-lock-keyword-face t) 640 (2 font-lock-variable-name-face t noerror))) 641 642 "Default expressions to highlight in Lua mode.") 643 644 (defvar lua-imenu-generic-expression 645 `(("Requires" ,(lua-rx (or bol ";") ws (opt (seq (symbol "local") ws)) (group-n 1 lua-name) ws "=" ws (symbol "require")) 1) 646 (nil ,(lua-rx (or bol ";") ws (opt (seq (symbol "local") ws)) lua-funcheader) 1)) 647 "Imenu generic expression for lua-mode. See `imenu-generic-expression'.") 648 649 (defvar lua-sexp-alist '(("then" . "end") 650 ("function" . "end") 651 ("do" . "end") 652 ("repeat" . "until"))) 653 654 (defvar lua-mode-abbrev-table nil 655 "Abbreviation table used in lua-mode buffers.") 656 657 (define-abbrev-table 'lua-mode-abbrev-table 658 '(("end" "end" lua-indent-line :system t) 659 ("else" "else" lua-indent-line :system t) 660 ("elseif" "elseif" lua-indent-line :system t))) 661 662 (defvar lua-mode-syntax-table 663 (with-syntax-table (copy-syntax-table) 664 ;; main comment syntax: begins with "--", ends with "\n" 665 (modify-syntax-entry ?- ". 12") 666 (modify-syntax-entry ?\n ">") 667 668 ;; main string syntax: bounded by ' or " 669 (modify-syntax-entry ?\' "\"") 670 (modify-syntax-entry ?\" "\"") 671 672 ;; single-character binary operators: punctuation 673 (modify-syntax-entry ?+ ".") 674 (modify-syntax-entry ?* ".") 675 (modify-syntax-entry ?/ ".") 676 (modify-syntax-entry ?^ ".") 677 (modify-syntax-entry ?% ".") 678 (modify-syntax-entry ?> ".") 679 (modify-syntax-entry ?< ".") 680 (modify-syntax-entry ?= ".") 681 (modify-syntax-entry ?~ ".") 682 683 (syntax-table)) 684 "`lua-mode' syntax table.") 685 686 ;;;###autoload 687 (define-derived-mode lua-mode prog-mode "Lua" 688 "Major mode for editing Lua code." 689 :abbrev-table lua-mode-abbrev-table 690 :syntax-table lua-mode-syntax-table 691 :group 'lua 692 (setq-local font-lock-defaults '(lua-font-lock-keywords ;; keywords 693 nil ;; keywords-only 694 nil ;; case-fold 695 nil ;; syntax-alist 696 nil ;; syntax-begin 697 )) 698 699 (setq-local syntax-propertize-function 700 'lua--propertize-multiline-bounds) 701 702 (setq-local parse-sexp-lookup-properties t) 703 (setq-local indent-line-function 'lua-indent-line) 704 (setq-local beginning-of-defun-function 'lua-beginning-of-proc) 705 (setq-local end-of-defun-function 'lua-end-of-proc) 706 (setq-local comment-start lua-comment-start) 707 (setq-local comment-start-skip lua-comment-start-skip) 708 (setq-local comment-use-syntax t) 709 (setq-local fill-paragraph-function #'lua--fill-paragraph) 710 (with-no-warnings 711 (setq-local comment-use-global-state t)) 712 (setq-local imenu-generic-expression lua-imenu-generic-expression) 713 (when (boundp 'electric-indent-chars) 714 ;; If electric-indent-chars is not defined, electric indentation is done 715 ;; via `lua-mode-map'. 716 (setq-local electric-indent-chars 717 (append electric-indent-chars lua--electric-indent-chars))) 718 (add-hook 'flymake-diagnostic-functions #'lua-flymake nil t) 719 720 ;; setup menu bar entry (XEmacs style) 721 (if (and (featurep 'menubar) 722 (boundp 'current-menubar) 723 (fboundp 'set-buffer-menubar) 724 (fboundp 'add-menu) 725 (not (assoc "Lua" current-menubar))) 726 (progn 727 (set-buffer-menubar (copy-sequence current-menubar)) 728 (add-menu nil "Lua" lua-emacs-menu))) 729 ;; Append Lua menu to popup menu for Emacs. 730 (if (boundp 'mode-popup-menu) 731 (setq mode-popup-menu 732 (cons (concat mode-name " Mode Commands") lua-emacs-menu))) 733 734 ;; hideshow setup 735 (unless (assq 'lua-mode hs-special-modes-alist) 736 (add-to-list 'hs-special-modes-alist 737 `(lua-mode 738 ,(regexp-opt (mapcar 'car lua-sexp-alist) 'words) ;start 739 ,(regexp-opt (mapcar 'cdr lua-sexp-alist) 'words) ;end 740 nil lua-forward-sexp)))) 741 742 743 744 ;;;###autoload 745 (add-to-list 'auto-mode-alist '("\\.lua\\'" . lua-mode)) 746 747 ;;;###autoload 748 (add-to-list 'interpreter-mode-alist '("lua" . lua-mode)) 749 750 (defun lua-electric-match (arg) 751 "Insert character and adjust indentation." 752 (interactive "P") 753 (let (blink-paren-function) 754 (self-insert-command (prefix-numeric-value arg))) 755 (if lua-electric-flag 756 (lua-indent-line)) 757 (blink-matching-open)) 758 759 ;; private functions 760 761 (defun lua--fill-paragraph (&optional justify region) 762 ;; Implementation of forward-paragraph for filling. 763 ;; 764 ;; This function works around a corner case in the following situations: 765 ;; 766 ;; <> 767 ;; -- some very long comment .... 768 ;; some_code_right_after_the_comment 769 ;; 770 ;; If point is at the beginning of the comment line, fill paragraph code 771 ;; would have gone for comment-based filling and done the right thing, but it 772 ;; does not find a comment at the beginning of the empty line before the 773 ;; comment and falls back to text-based filling ignoring comment-start and 774 ;; spilling the comment into the code. 775 (save-excursion 776 (while (and (not (eobp)) 777 (progn (move-to-left-margin) 778 (looking-at paragraph-separate))) 779 (forward-line 1)) 780 (let ((fill-paragraph-handle-comment t)) 781 (fill-paragraph justify region)))) 782 783 784 (defun lua-prefix-key-update-bindings () 785 (let (old-cons) 786 (if (eq lua-prefix-mode-map (keymap-parent lua-mode-map)) 787 ;; if prefix-map is a parent, delete the parent 788 (set-keymap-parent lua-mode-map nil) 789 ;; otherwise, look for it among children 790 (if (setq old-cons (rassoc lua-prefix-mode-map lua-mode-map)) 791 (delq old-cons lua-mode-map))) 792 793 (if (null lua-prefix-key) 794 (set-keymap-parent lua-mode-map lua-prefix-mode-map) 795 (define-key lua-mode-map (vector lua-prefix-key) lua-prefix-mode-map)))) 796 797 (defun lua-set-prefix-key (new-key-str) 798 "Changes `lua-prefix-key' properly and updates keymaps 799 800 This function replaces previous prefix-key binding with a new one." 801 (interactive "sNew prefix key (empty string means no key): ") 802 (lua--customize-set-prefix-key 'lua-prefix-key new-key-str) 803 (message "Prefix key set to %S" (single-key-description lua-prefix-key)) 804 (lua-prefix-key-update-bindings)) 805 806 (defun lua-string-p (&optional pos) 807 "Returns true if the point is in a string." 808 (save-excursion (elt (syntax-ppss pos) 3))) 809 810 (defun lua--containing-double-hyphen-start-pos () 811 "Return position of the beginning comment delimiter (--). 812 813 Emacs syntax framework does not consider comment delimiters as 814 part of the comment itself, but for this package it is useful to 815 consider point as inside comment when it is between the two hyphens" 816 (and (eql (char-before) ?-) 817 (eql (char-after) ?-) 818 (1- (point)))) 819 820 (defun lua-comment-start-pos (&optional parsing-state) 821 "Return position of comment containing current point. 822 823 If point is not inside a comment, return nil." 824 (unless parsing-state (setq parsing-state (syntax-ppss))) 825 (and 826 ;; Not a string 827 (not (nth 3 parsing-state)) 828 ;; Syntax-based comment 829 (or (and (nth 4 parsing-state) (nth 8 parsing-state)) 830 (lua--containing-double-hyphen-start-pos)))) 831 832 (defun lua-comment-or-string-p (&optional pos) 833 "Returns true if the point is in a comment or string." 834 (save-excursion (let ((parse-result (syntax-ppss pos))) 835 (or (elt parse-result 3) (lua-comment-start-pos parse-result))))) 836 837 (defun lua-comment-or-string-start-pos (&optional pos) 838 "Returns start position of string or comment which contains point. 839 840 If point is not inside string or comment, return nil." 841 (save-excursion 842 (when pos (goto-char pos)) 843 (or (elt (syntax-ppss pos) 8) 844 (lua--containing-double-hyphen-start-pos)))) 845 846 ;; They're propertized as follows: 847 ;; 1. generic-comment 848 ;; 2. generic-string 849 ;; 3. equals signs 850 (defconst lua-ml-begin-regexp 851 "\\(?:\\(?1:-\\)-\\[\\|\\(?2:\\[\\)\\)\\(?3:=*\\)\\[") 852 853 854 (defun lua-try-match-multiline-end (end) 855 "Try to match close-bracket for multiline literal around point. 856 857 Basically, detect form of close bracket from syntactic 858 information provided at point and re-search-forward to it." 859 (let ((comment-or-string-start-pos (lua-comment-or-string-start-pos))) 860 ;; Is there a literal around point? 861 (and comment-or-string-start-pos 862 ;; It is, check if the literal is a multiline open-bracket 863 (save-excursion 864 (goto-char comment-or-string-start-pos) 865 (looking-at lua-ml-begin-regexp)) 866 867 ;; Yes it is, look for it matching close-bracket. Close-bracket's 868 ;; match group is determined by match-group of open-bracket. 869 (re-search-forward 870 (format "]%s\\(?%s:]\\)" 871 (match-string-no-properties 3) 872 (if (match-beginning 1) 1 2)) 873 end 'noerror)))) 874 875 876 (defun lua-try-match-multiline-begin (limit) 877 "Try to match multiline open-brackets. 878 879 Find next opening long bracket outside of any string/comment. 880 If none can be found before reaching LIMIT, return nil." 881 882 (let (last-search-matched) 883 (while 884 ;; This loop will iterate skipping all multiline-begin tokens that are 885 ;; inside strings or comments ending either at EOL or at valid token. 886 (and (setq last-search-matched 887 (re-search-forward lua-ml-begin-regexp limit 'noerror)) 888 ;; Ensure --[[ is not inside a comment or string. 889 ;; 890 ;; This includes "---[[" sequence, in which "--" at the beginning 891 ;; creates a single-line comment, and thus "-[[" is no longer a 892 ;; multi-line opener. 893 ;; 894 ;; XXX: need to ensure syntax-ppss beyond (match-beginning 0) is 895 ;; not calculated, or otherwise we'll need to flush the cache. 896 (lua-comment-or-string-start-pos (match-beginning 0)))) 897 898 last-search-matched)) 899 900 (defun lua-match-multiline-literal-bounds (limit) 901 ;; First, close any multiline literal spanning from previous block. This will 902 ;; move the point accordingly so as to avoid double traversal. 903 (or (lua-try-match-multiline-end limit) 904 (lua-try-match-multiline-begin limit))) 905 906 (defun lua--propertize-multiline-bounds (start end) 907 "Put text properties on beginnings and ends of multiline literals. 908 909 Intended to be used as a `syntax-propertize-function'." 910 (save-excursion 911 (goto-char start) 912 (while (lua-match-multiline-literal-bounds end) 913 (when (match-beginning 1) 914 (put-text-property (match-beginning 1) (match-end 1) 915 'syntax-table (string-to-syntax "!"))) 916 (when (match-beginning 2) 917 (put-text-property (match-beginning 2) (match-end 2) 918 'syntax-table (string-to-syntax "|")))))) 919 920 921 (defun lua-indent-line () 922 "Indent current line for Lua mode. 923 Return the amount the indentation changed by." 924 (let (indent 925 (case-fold-search nil) 926 ;; save point as a distance to eob - it's invariant w.r.t indentation 927 (pos (- (point-max) (point)))) 928 (back-to-indentation) 929 (if (lua-comment-or-string-p) 930 (setq indent (lua-calculate-string-or-comment-indentation)) ;; just restore point position 931 (setq indent (max 0 (lua-calculate-indentation)))) 932 933 (when (not (equal indent (current-column))) 934 (delete-region (line-beginning-position) (point)) 935 (indent-to indent)) 936 937 ;; If initial point was within line's indentation, 938 ;; position after the indentation. Else stay at same point in text. 939 (if (> (- (point-max) pos) (point)) 940 (goto-char (- (point-max) pos))) 941 942 indent)) 943 944 (defun lua-calculate-string-or-comment-indentation () 945 "This function should be run when point at (current-indentation) is inside string" 946 (if (and (lua-string-p) (not lua-indent-string-contents)) 947 ;; if inside string and strings aren't to be indented, return current indentation 948 (current-indentation) 949 950 ;; At this point, we know that we're inside comment, so make sure 951 ;; close-bracket is unindented like a block that starts after 952 ;; left-shifter. 953 (let ((left-shifter-p (looking-at "\\s *\\(?:--\\)?\\]\\(?1:=*\\)\\]"))) 954 (save-excursion 955 (goto-char (lua-comment-or-string-start-pos)) 956 (+ (current-indentation) 957 (if (and left-shifter-p 958 (looking-at (format "--\\[%s\\[" 959 (match-string-no-properties 1)))) 960 0 961 lua-indent-level)))))) 962 963 964 (defun lua--signum (x) 965 "Return 1 if X is positive, -1 if negative, 0 if zero." 966 ;; XXX: backport from cl-extras for Emacs24 967 (cond ((> x 0) 1) ((< x 0) -1) (t 0))) 968 969 (defun lua--ensure-point-within-limit (limit backward) 970 "Return non-nil if point is within LIMIT going forward. 971 972 With BACKWARD non-nil, return non-nil if point is within LIMIT 973 going backward. 974 975 If point is beyond limit, move it onto limit." 976 (if (= (lua--signum (- (point) limit)) 977 (if backward 1 -1)) 978 t 979 (goto-char limit) 980 nil)) 981 982 983 (defun lua--escape-from-string (&optional backward) 984 "Move point outside of string if it is inside one. 985 986 By default, point is placed after the string, with BACKWARD it is 987 placed before the string." 988 (interactive) 989 (let ((parse-state (syntax-ppss))) 990 (when (nth 3 parse-state) 991 (if backward 992 (goto-char (nth 8 parse-state)) 993 (parse-partial-sexp (point) (line-end-position) nil nil (syntax-ppss) 'syntax-table)) 994 t))) 995 996 997 (defun lua-find-regexp (direction regexp &optional limit) 998 "Searches for a regular expression in the direction specified. 999 1000 Direction is one of 'forward and 'backward. 1001 1002 Matches in comments and strings are ignored. If the regexp is 1003 found, returns point position, nil otherwise." 1004 (let ((search-func (if (eq direction 'forward) 1005 're-search-forward 're-search-backward)) 1006 (case-fold-search nil)) 1007 (cl-loop 1008 always (or (null limit) 1009 (lua--ensure-point-within-limit limit (not (eq direction 'forward)))) 1010 always (funcall search-func regexp limit 'noerror) 1011 for match-beg = (match-beginning 0) 1012 for match-end = (match-end 0) 1013 while (or (lua-comment-or-string-p match-beg) 1014 (lua-comment-or-string-p match-end)) 1015 do (let ((parse-state (syntax-ppss))) 1016 (cond 1017 ;; Inside a string 1018 ((nth 3 parse-state) 1019 (lua--escape-from-string (not (eq direction 'forward)))) 1020 ;; Inside a comment 1021 ((nth 4 parse-state) 1022 (goto-char (nth 8 parse-state)) 1023 (when (eq direction 'forward) 1024 (forward-comment 1))))) 1025 finally return (point)))) 1026 1027 1028 (defconst lua-block-regexp 1029 (eval-when-compile 1030 (concat 1031 "\\(\\_<" 1032 (regexp-opt '("do" "function" "repeat" "then" 1033 "else" "elseif" "end" "until") t) 1034 "\\_>\\)\\|" 1035 (regexp-opt '("{" "(" "[" "]" ")" "}") t)))) 1036 1037 (defconst lua-block-token-alist 1038 '(("do" "\\_<end\\_>" "\\_<for\\|while\\_>" middle-or-open) 1039 ("function" "\\_<end\\_>" nil open) 1040 ("repeat" "\\_<until\\_>" nil open) 1041 ("then" "\\_<\\(e\\(lse\\(if\\)?\\|nd\\)\\)\\_>" "\\_<\\(else\\)?if\\_>" middle) 1042 ("{" "}" nil open) 1043 ("[" "]" nil open) 1044 ("(" ")" nil open) 1045 ("if" "\\_<then\\_>" nil open) 1046 ("for" "\\_<do\\_>" nil open) 1047 ("while" "\\_<do\\_>" nil open) 1048 ("else" "\\_<end\\_>" "\\_<then\\_>" middle) 1049 ("elseif" "\\_<then\\_>" "\\_<then\\_>" middle) 1050 ("end" nil "\\_<\\(do\\|function\\|then\\|else\\)\\_>" close) 1051 ("until" nil "\\_<repeat\\_>" close) 1052 ("}" nil "{" close) 1053 ("]" nil "\\[" close) 1054 (")" nil "(" close)) 1055 "This is a list of block token information blocks. 1056 Each token information entry is of the form: 1057 KEYWORD FORWARD-MATCH-REGEXP BACKWARDS-MATCH-REGEXP TOKEN-TYPE 1058 KEYWORD is the token. 1059 FORWARD-MATCH-REGEXP is a regexp that matches all possible tokens when going forward. 1060 BACKWARDS-MATCH-REGEXP is a regexp that matches all possible tokens when going backwards. 1061 TOKEN-TYPE determines where the token occurs on a statement. open indicates that the token appears at start, close indicates that it appears at end, middle indicates that it is a middle type token, and middle-or-open indicates that it can appear both as a middle or an open type.") 1062 1063 (defconst lua-indentation-modifier-regexp 1064 ;; The absence of else is deliberate, since it does not modify the 1065 ;; indentation level per se. It only may cause the line, in which the 1066 ;; else is, to be shifted to the left. 1067 (concat 1068 "\\(\\_<" 1069 (regexp-opt '("do" "function" "repeat" "then" "if" "else" "elseif" "for" "while") t) 1070 "\\_>\\|" 1071 (regexp-opt '("{" "(" "[")) 1072 "\\)\\|\\(\\_<" 1073 (regexp-opt '("end" "until") t) 1074 "\\_>\\|" 1075 (regexp-opt '("]" ")" "}")) 1076 "\\)") 1077 ) 1078 1079 (defun lua-get-block-token-info (token) 1080 "Returns the block token info entry for TOKEN from lua-block-token-alist" 1081 (assoc token lua-block-token-alist)) 1082 1083 (defun lua-get-token-match-re (token-info direction) 1084 "Returns the relevant match regexp from token info" 1085 (cond 1086 ((eq direction 'forward) (cadr token-info)) 1087 ((eq direction 'backward) (nth 2 token-info)) 1088 (t nil))) 1089 1090 (defun lua-get-token-type (token-info) 1091 "Returns the relevant match regexp from token info" 1092 (nth 3 token-info)) 1093 1094 (defun lua-backwards-to-block-begin-or-end () 1095 "Move backwards to nearest block begin or end. Returns nil if not successful." 1096 (interactive) 1097 (lua-find-regexp 'backward lua-block-regexp)) 1098 1099 (defun lua-find-matching-token-word (token &optional direction) 1100 "Find matching open- or close-token for TOKEN in DIRECTION. 1101 Point has to be exactly at the beginning of TOKEN, e.g. with | being point 1102 1103 {{ }|} -- (lua-find-matching-token-word \"}\" 'backward) will return 1104 -- the first { 1105 {{ |}} -- (lua-find-matching-token-word \"}\" 'backward) will find 1106 -- the second {. 1107 1108 DIRECTION has to be either 'forward or 'backward." 1109 (let* ((token-info (lua-get-block-token-info token)) 1110 (match-type (lua-get-token-type token-info)) 1111 ;; If we are on a middle token, go backwards. If it is a middle or open, 1112 ;; go forwards 1113 (search-direction (or direction 1114 (if (or (eq match-type 'open) 1115 (eq match-type 'middle-or-open)) 1116 'forward 1117 'backward) 1118 'backward)) 1119 (match (lua-get-token-match-re token-info search-direction)) 1120 maybe-found-pos) 1121 ;; if we are searching forward from the token at the current point 1122 ;; (i.e. for a closing token), need to step one character forward 1123 ;; first, or the regexp will match the opening token. 1124 (if (eq search-direction 'forward) (forward-char 1)) 1125 (catch 'found 1126 ;; If we are attempting to find a matching token for a terminating token 1127 ;; (i.e. a token that starts a statement when searching back, or a token 1128 ;; that ends a statement when searching forward), then we don't need to look 1129 ;; any further. 1130 (if (or (and (eq search-direction 'forward) 1131 (eq match-type 'close)) 1132 (and (eq search-direction 'backward) 1133 (eq match-type 'open))) 1134 (throw 'found nil)) 1135 (while (lua-find-regexp search-direction lua-indentation-modifier-regexp) 1136 ;; have we found a valid matching token? 1137 (let ((found-token (match-string 0)) 1138 (found-pos (match-beginning 0))) 1139 (let ((found-type (lua-get-token-type 1140 (lua-get-block-token-info found-token)))) 1141 (if (not (and match (string-match match found-token))) 1142 ;; no - then there is a nested block. If we were looking for 1143 ;; a block begin token, found-token must be a block end 1144 ;; token; likewise, if we were looking for a block end token, 1145 ;; found-token must be a block begin token, otherwise there 1146 ;; is a grammatical error in the code. 1147 (if (not (and 1148 (or (eq match-type 'middle) 1149 (eq found-type 'middle) 1150 (eq match-type 'middle-or-open) 1151 (eq found-type 'middle-or-open) 1152 (eq match-type found-type)) 1153 (goto-char found-pos) 1154 (lua-find-matching-token-word found-token 1155 search-direction))) 1156 (when maybe-found-pos 1157 (goto-char maybe-found-pos) 1158 (throw 'found maybe-found-pos))) 1159 ;; yes. 1160 ;; if it is a not a middle kind, report the location 1161 (when (not (or (eq found-type 'middle) 1162 (eq found-type 'middle-or-open))) 1163 (throw 'found found-pos)) 1164 ;; if it is a middle-or-open type, record location, but keep searching. 1165 ;; If we fail to complete the search, we'll report the location 1166 (when (eq found-type 'middle-or-open) 1167 (setq maybe-found-pos found-pos)) 1168 ;; Cannot use tail recursion. too much nesting on long chains of 1169 ;; if/elseif. Will reset variables instead. 1170 (setq token found-token) 1171 (setq token-info (lua-get-block-token-info token)) 1172 (setq match (lua-get-token-match-re token-info search-direction)) 1173 (setq match-type (lua-get-token-type token-info)))))) 1174 maybe-found-pos))) 1175 1176 (defun lua-goto-matching-block-token (&optional parse-start direction) 1177 "Find block begion/end token matching the one at the point. 1178 This function moves the point to the token that matches the one 1179 at the current point. Returns the point position of the first character of 1180 the matching token if successful, nil otherwise. 1181 1182 Optional PARSE-START is a position to which the point should be moved first. 1183 DIRECTION has to be 'forward or 'backward ('forward by default)." 1184 (if parse-start (goto-char parse-start)) 1185 (let ((case-fold-search nil)) 1186 (if (looking-at lua-indentation-modifier-regexp) 1187 (let ((position (lua-find-matching-token-word (match-string 0) 1188 direction))) 1189 (and position 1190 (goto-char position)))))) 1191 1192 (defun lua-goto-matching-block (&optional noreport) 1193 "Go to the keyword balancing the one under the point. 1194 If the point is on a keyword/brace that starts a block, go to the 1195 matching keyword that ends the block, and vice versa. 1196 1197 If optional NOREPORT is non-nil, it won't flag an error if there 1198 is no block open/close open." 1199 (interactive) 1200 ;; search backward to the beginning of the keyword if necessary 1201 (when (and (eq (char-syntax (following-char)) ?w) 1202 (not (looking-at "\\_<"))) 1203 (re-search-backward "\\_<" nil t)) 1204 (let ((position (lua-goto-matching-block-token))) 1205 (if (and (not position) 1206 (not noreport)) 1207 (error "Not on a block control keyword or brace") 1208 position))) 1209 1210 (defun lua-skip-ws-and-comments-backward (&optional limit) 1211 "Move point back skipping all whitespace and comments. 1212 1213 If LIMIT is given, stop at it or before. 1214 1215 Return non-nil if moved point." 1216 (interactive) 1217 (unless (lua-string-p) 1218 (let ((start-pos (point)) 1219 (comment-start-pos (lua-comment-start-pos))) 1220 (setq limit (min (point) (or limit (point-min)))) 1221 (when comment-start-pos 1222 (goto-char (max limit comment-start-pos))) 1223 (when (< limit (point)) (forward-comment (- limit (point)))) 1224 (when (< (point) limit) (goto-char limit)) 1225 (when (/= start-pos (point)) 1226 (point))))) 1227 1228 (defun lua-skip-ws-and-comments-forward (&optional limit) 1229 "Move point forward skipping all whitespace and comments. 1230 1231 If LIMIT is given, stop at it or before. 1232 1233 Return non-nil if moved point." 1234 (interactive) 1235 (unless (lua-string-p) 1236 (let ((start-pos (point)) 1237 (comment-start-pos (lua-comment-start-pos))) 1238 (setq limit (max (point) (or limit (point-max)))) 1239 ;; Escape from current comment. It is necessary to use "while" because 1240 ;; luadoc parameters have non-comment face, and parse-partial-sexp with 1241 ;; 'syntax-table flag will stop on them. 1242 (when comment-start-pos 1243 (goto-char comment-start-pos) 1244 (forward-comment 1)) 1245 (when (< (point) limit) (forward-comment (- limit (point)))) 1246 (when (< limit (point)) (goto-char limit)) 1247 (when (/= start-pos (point)) 1248 (point))))) 1249 1250 1251 (defun lua-forward-line-skip-blanks (&optional back) 1252 "Move 1 line forward/backward and skip all insignificant ws/comment lines. 1253 1254 Moves point 1 line forward (or backward) skipping lines that contain 1255 no Lua code besides comments. The point is put to the beginning of 1256 the line. 1257 1258 Returns final value of point as integer or nil if operation failed." 1259 (let ((start-pos (point))) 1260 (if back 1261 (progn 1262 (beginning-of-line) 1263 (lua-skip-ws-and-comments-backward)) 1264 (forward-line) 1265 (lua-skip-ws-and-comments-forward)) 1266 (beginning-of-line) 1267 (when (> (count-lines start-pos (point)) 0) 1268 (point)))) 1269 1270 (eval-when-compile 1271 (defconst lua-operator-class 1272 "-+*/^.=<>~:&|")) 1273 1274 (defconst lua-cont-eol-regexp 1275 (eval-when-compile 1276 (concat 1277 "\\(?:\\(?1:\\_<" 1278 (regexp-opt '("and" "or" "not" "in" "for" "while" 1279 "local" "function" "if" "until" "elseif" "return") 1280 t) 1281 "\\_>\\)\\|" 1282 "\\(?:^\\|[^" lua-operator-class "]\\)\\(?2:" 1283 (regexp-opt '("+" "-" "*" "/" "%" "^" ".." "==" 1284 "=" "<" ">" "<=" ">=" "~=" "." ":" 1285 "&" "|" "~" ">>" "<<" "~" ",") 1286 t) 1287 "\\)\\)" 1288 "\\s *\\=")) 1289 "Regexp that matches the ending of a line that needs continuation. 1290 1291 This regexp starts from eol and looks for a binary operator or an unclosed 1292 block intro (i.e. 'for' without 'do' or 'if' without 'then') followed by 1293 an optional whitespace till the end of the line.") 1294 1295 (defconst lua-cont-bol-regexp 1296 (eval-when-compile 1297 (concat 1298 "\\=\\s *" 1299 "\\(?:\\(?1:\\_<" 1300 (regexp-opt '("and" "or" "not" "in") t) 1301 "\\_>\\)\\|\\(?2:" 1302 (regexp-opt '("," "+" "-" "*" "/" "%" "^" ".." "==" 1303 "=" "<" ">" "<=" ">=" "~=" "." ":" 1304 "&" "|" "~" ">>" "<<" "~") 1305 t) 1306 "\\)\\(?:$\\|[^" lua-operator-class "]\\)" 1307 "\\)")) 1308 "Regexp that matches a line that continues previous one. 1309 1310 This regexp means, starting from point there is an optional whitespace followed 1311 by Lua binary operator. Lua is very liberal when it comes to continuation line, 1312 so we're safe to assume that every line that starts with a binop continues 1313 previous one even though it looked like an end-of-statement.") 1314 1315 (defun lua-last-token-continues-p () 1316 "Return non-nil if the last token on this line is a continuation token." 1317 (let ((line-begin (line-beginning-position)) 1318 return-value) 1319 (save-excursion 1320 (end-of-line) 1321 (lua-skip-ws-and-comments-backward line-begin) 1322 (setq return-value (and (re-search-backward lua-cont-eol-regexp line-begin t) 1323 (or (match-beginning 1) 1324 (match-beginning 2)))) 1325 (if (and return-value 1326 (string-equal (match-string-no-properties 0) "return")) 1327 ;; "return" keyword is ambiguous and depends on next token 1328 (unless (save-excursion 1329 (goto-char (match-end 0)) 1330 (forward-comment (point-max)) 1331 (and 1332 ;; Not continuing: at end of file 1333 (not (eobp)) 1334 (or 1335 ;; "function" keyword: it is a continuation, e.g. 1336 ;; 1337 ;; return 1338 ;; function() return 123 end 1339 ;; 1340 (looking-at (lua-rx (symbol "function"))) 1341 ;; Looking at semicolon or any other keyword: not continuation 1342 (not (looking-at (lua-rx (or ";" lua-keyword))))))) 1343 (setq return-value nil))) 1344 return-value))) 1345 1346 1347 (defun lua-first-token-continues-p () 1348 "Return non-nil if the first token on this line is a continuation token." 1349 (let ((line-end (line-end-position))) 1350 (save-excursion 1351 (beginning-of-line) 1352 (lua-skip-ws-and-comments-forward line-end) 1353 ;; if first character of the line is inside string, it's a continuation 1354 ;; if strings aren't supposed to be indented, `lua-calculate-indentation' won't even let 1355 ;; the control inside this function 1356 (and 1357 (re-search-forward lua-cont-bol-regexp line-end t) 1358 (or (match-beginning 1) 1359 (match-beginning 2)))))) 1360 1361 1362 (defun lua--backward-up-list-noerror () 1363 "Safe version of lua-backward-up-list that does not signal an error." 1364 (condition-case nil 1365 (lua-backward-up-list) 1366 (scan-error nil))) 1367 1368 1369 (defun lua-backward-up-list () 1370 "Goto starter/opener of the block that contains point." 1371 (interactive) 1372 (let ((start-pos (point)) 1373 end-pos) 1374 (or 1375 ;; Return parent block opener token if it exists. 1376 (cl-loop 1377 ;; Search indentation modifier backward, return nil on failure. 1378 always (lua-find-regexp 'backward lua-indentation-modifier-regexp) 1379 ;; Fetch info about the found token 1380 for token = (match-string-no-properties 0) 1381 for token-info = (lua-get-block-token-info token) 1382 for token-type = (lua-get-token-type token-info) 1383 ;; If the token is a close token, continue to skip its opener. If not 1384 ;; close, stop and return found token. 1385 while (eq token-type 'close) 1386 ;; Find matching opener to skip it and continue from beginning. 1387 ;; 1388 ;; Return nil on failure. 1389 always (let ((position (lua-find-matching-token-word token 'backward))) 1390 (and position (goto-char position))) 1391 finally return token-info) 1392 (progn 1393 (setq end-pos (point)) 1394 (goto-char start-pos) 1395 (signal 'scan-error 1396 (list "Block open token not found" 1397 ;; If start-pos == end-pos, the "obstacle" is current 1398 (if (eql start-pos end-pos) start-pos (match-beginning 0)) 1399 (if (eql start-pos end-pos) start-pos (match-end 0)))))))) 1400 1401 (defun lua--continuation-breaking-line-p () 1402 "Return non-nil if looking at token(-s) that forbid continued line." 1403 (save-excursion 1404 (lua-skip-ws-and-comments-forward (line-end-position)) 1405 (looking-at (lua-rx (or (symbol "do" "while" "repeat" "until" 1406 "if" "then" "elseif" "else" 1407 "for" "local") 1408 lua-funcheader))))) 1409 1410 1411 (defun lua-is-continuing-statement-p-1 () 1412 "Return non-nil if current lined continues a statement. 1413 1414 More specifically, return the point in the line that is continued. 1415 The criteria for a continuing statement are: 1416 1417 * the last token of the previous line is a continuing op, 1418 OR the first token of the current line is a continuing op 1419 1420 * the expression is not enclosed by a parentheses/braces/brackets" 1421 (let (prev-line continuation-pos parent-block-opener) 1422 (save-excursion (setq prev-line (lua-forward-line-skip-blanks 'back))) 1423 (and prev-line 1424 (not (lua--continuation-breaking-line-p)) 1425 (save-excursion 1426 (or 1427 ;; Binary operator or keyword that implies continuation. 1428 (and (setq continuation-pos 1429 (or (lua-first-token-continues-p) 1430 (save-excursion (and (goto-char prev-line) 1431 ;; check last token of previous nonblank line 1432 (lua-last-token-continues-p))))) 1433 (not 1434 ;; Operators/keywords does not create continuation inside some blocks: 1435 (and 1436 (setq parent-block-opener (car-safe (lua--backward-up-list-noerror))) 1437 (or 1438 ;; - inside parens/brackets 1439 (member parent-block-opener '("(" "[")) 1440 ;; - inside braces if it is a comma 1441 (and (eq (char-after continuation-pos) ?,) 1442 (equal parent-block-opener "{"))))) 1443 continuation-pos)))))) 1444 1445 1446 (defun lua-is-continuing-statement-p (&optional parse-start) 1447 "Returns non-nil if the line at PARSE-START should be indented as continuation line. 1448 1449 This true is when the line : 1450 1451 * is continuing a statement itself 1452 1453 * starts with a 1+ block-closer tokens, an top-most block opener is on a continuation line 1454 " 1455 (save-excursion 1456 (if parse-start (goto-char parse-start)) 1457 1458 ;; If line starts with a series of closer tokens, whether or not the line 1459 ;; is a continuation line is decided by the opener line, e.g. 1460 ;; 1461 ;; x = foo + 1462 ;; long_function_name( 1463 ;; long_parameter_1, 1464 ;; long_parameter_2, 1465 ;; long_parameter_3, 1466 ;; ) + long_function_name2({ 1467 ;; long_parameter_1, 1468 ;; long_parameter_2, 1469 ;; long_parameter_3, 1470 ;; }) 1471 ;; 1472 ;; Final line, "})" is a continuation line, but it is decided by the 1473 ;; opener line, ") + long_function_name2({", which in its turn is decided 1474 ;; by the "long_function_name(" line, which is a continuation line 1475 ;; because the line before it ends with a binary operator. 1476 (cl-loop 1477 ;; Go to opener line 1478 while (and (lua--goto-line-beginning-rightmost-closer) 1479 (lua--backward-up-list-noerror)) 1480 ;; If opener line is continuing, repeat. If opener line is not 1481 ;; continuing, return nil. 1482 always (lua-is-continuing-statement-p-1) 1483 ;; We get here if there was no opener to go to: check current line. 1484 finally return (lua-is-continuing-statement-p-1)))) 1485 1486 (defun lua-make-indentation-info-pair (found-token found-pos) 1487 "Create a pair from FOUND-TOKEN and FOUND-POS for indentation calculation. 1488 1489 This is a helper function to lua-calculate-indentation-info. 1490 Don't use standalone." 1491 (cond 1492 ;; function is a bit tricky to indent right. They can appear in a lot ot 1493 ;; different contexts. Until I find a shortcut, I'll leave it with a simple 1494 ;; relative indentation. 1495 ;; The special cases are for indenting according to the location of the 1496 ;; function. i.e.: 1497 ;; (cons 'absolute (+ (current-column) lua-indent-level)) 1498 ;; TODO: Fix this. It causes really ugly indentations for in-line functions. 1499 ((string-equal found-token "function") 1500 (cons 'relative lua-indent-level)) 1501 1502 ;; block openers 1503 ((and lua-indent-nested-block-content-align 1504 (member found-token (list "{" "(" "["))) 1505 (save-excursion 1506 (let ((found-bol (line-beginning-position))) 1507 (forward-comment (point-max)) 1508 ;; If the next token is on this line and it's not a block opener, 1509 ;; the next line should align to that token. 1510 (if (and (zerop (count-lines found-bol (line-beginning-position))) 1511 (not (looking-at lua-indentation-modifier-regexp))) 1512 (cons 'absolute (current-column)) 1513 (cons 'relative lua-indent-level))))) 1514 1515 ;; These are not really block starters. They should not add to indentation. 1516 ;; The corresponding "then" and "do" handle the indentation. 1517 ((member found-token (list "if" "for" "while")) 1518 (cons 'relative 0)) 1519 ;; closing tokens follow: These are usually taken care of by 1520 ;; lua-calculate-indentation-override. 1521 ;; elseif is a bit of a hack. It is not handled separately, but it needs to 1522 ;; nullify a previous then if on the same line. 1523 ((member found-token (list "until" "elseif")) 1524 (save-excursion 1525 (let* ((line-beginning (line-beginning-position)) 1526 (same-line (and (lua-goto-matching-block-token found-pos 'backward) 1527 (<= line-beginning (point))))) 1528 (if same-line 1529 (cons 'remove-matching 0) 1530 (cons 'relative 0))))) 1531 1532 ;; else is a special case; if its matching block token is on the same line, 1533 ;; instead of removing the matching token, it has to replace it, so that 1534 ;; either the next line will be indented correctly, or the end on the same 1535 ;; line will remove the effect of the else. 1536 ((string-equal found-token "else") 1537 (save-excursion 1538 (let* ((line-beginning (line-beginning-position)) 1539 (same-line (and (lua-goto-matching-block-token found-pos 'backward) 1540 (<= line-beginning (point))))) 1541 (if same-line 1542 (cons 'replace-matching (cons 'relative lua-indent-level)) 1543 (cons 'relative lua-indent-level))))) 1544 1545 ;; Block closers. If they are on the same line as their openers, they simply 1546 ;; eat up the matching indentation modifier. Otherwise, they pull 1547 ;; indentation back to the matching block opener. 1548 ((member found-token (list ")" "}" "]" "end")) 1549 (save-excursion 1550 (let* ((line-beginning (line-beginning-position)) 1551 (same-line (and (lua-goto-matching-block-token found-pos 'backward) 1552 (<= line-beginning (point)))) 1553 (opener-pos (point)) 1554 opener-continuation-offset) 1555 (if same-line 1556 (cons 'remove-matching 0) 1557 (back-to-indentation) 1558 (setq opener-continuation-offset 1559 (if (lua-is-continuing-statement-p-1) lua-indent-level 0)) 1560 1561 ;; Accumulate indentation up to opener, including indentation. If 1562 ;; there were no other indentation modifiers until said opener, 1563 ;; ensure there is no continuation after the closer. 1564 `(multiple . ((absolute . ,(- (current-indentation) opener-continuation-offset)) 1565 ,@(when (/= opener-continuation-offset 0) 1566 (list (cons 'continued-line opener-continuation-offset))) 1567 ,@(delete nil (list (lua-calculate-indentation-info-1 nil opener-pos))) 1568 (cancel-continued-line . nil))))))) 1569 1570 ((member found-token '("do" "then")) 1571 `(multiple . ((cancel-continued-line . nil) (relative . ,lua-indent-level)))) 1572 1573 ;; Everything else. This is from the original code: If opening a block 1574 ;; (match-data 1 exists), then push indentation one level up, if it is 1575 ;; closing a block, pull it one level down. 1576 ('other-indentation-modifier 1577 (cons 'relative (if (nth 2 (match-data)) 1578 ;; beginning of a block matched 1579 lua-indent-level 1580 ;; end of a block matched 1581 (- lua-indent-level)))))) 1582 1583 (defun lua-add-indentation-info-pair (pair info-list) 1584 "Add the given indentation info PAIR to the list of indentation INFO-LIST. 1585 This function has special case handling for two tokens: remove-matching, 1586 and replace-matching. These two tokens are cleanup tokens that remove or 1587 alter the effect of a previously recorded indentation info. 1588 1589 When a remove-matching token is encountered, the last recorded info, i.e. 1590 the car of the list is removed. This is used to roll-back an indentation of a 1591 block opening statement when it is closed. 1592 1593 When a replace-matching token is seen, the last recorded info is removed, 1594 and the cdr of the replace-matching info is added in its place. This is used 1595 when a middle-of the block (the only case is 'else') is seen on the same line 1596 the block is opened." 1597 (cond 1598 ( (eq 'multiple (car pair)) 1599 (let ((info-pair-elts (cdr pair))) 1600 (while info-pair-elts 1601 (setq info-list (lua-add-indentation-info-pair (car info-pair-elts) info-list) 1602 info-pair-elts (cdr info-pair-elts))) 1603 info-list)) 1604 ( (eq 'cancel-continued-line (car pair)) 1605 (if (eq (caar info-list) 'continued-line) 1606 (cdr info-list) 1607 info-list)) 1608 ( (eq 'remove-matching (car pair)) 1609 ;; Remove head of list 1610 (cdr info-list)) 1611 ( (eq 'replace-matching (car pair)) 1612 ;; remove head of list, and add the cdr of pair instead 1613 (cons (cdr pair) (cdr info-list))) 1614 ( (listp (cdr-safe pair)) 1615 (nconc pair info-list)) 1616 ( t 1617 ;; Just add the pair 1618 (cons pair info-list)))) 1619 1620 (defun lua-calculate-indentation-info-1 (indentation-info bound) 1621 "Helper function for `lua-calculate-indentation-info'. 1622 1623 Return list of indentation modifiers from point to BOUND." 1624 (while (lua-find-regexp 'forward lua-indentation-modifier-regexp 1625 bound) 1626 (let ((found-token (match-string 0)) 1627 (found-pos (match-beginning 0))) 1628 (setq indentation-info 1629 (lua-add-indentation-info-pair 1630 (lua-make-indentation-info-pair found-token found-pos) 1631 indentation-info)))) 1632 indentation-info) 1633 1634 1635 (defun lua-calculate-indentation-info (&optional parse-end) 1636 "For each block token on the line, computes how it affects the indentation. 1637 The effect of each token can be either a shift relative to the current 1638 indentation level, or indentation to some absolute column. This information 1639 is collected in a list of indentation info pairs, which denote absolute 1640 and relative each, and the shift/column to indent to." 1641 (let (indentation-info cont-stmt-pos) 1642 (while (setq cont-stmt-pos (lua-is-continuing-statement-p)) 1643 (lua-forward-line-skip-blanks 'back) 1644 (when (< cont-stmt-pos (point)) 1645 (goto-char cont-stmt-pos))) 1646 1647 ;; calculate indentation modifiers for the line itself 1648 (setq indentation-info (list (cons 'absolute (current-indentation)))) 1649 1650 (back-to-indentation) 1651 (setq indentation-info 1652 (lua-calculate-indentation-info-1 1653 indentation-info (min parse-end (line-end-position)))) 1654 1655 ;; and do the following for each continuation line before PARSE-END 1656 (while (and (eql (forward-line 1) 0) 1657 (<= (point) parse-end)) 1658 1659 ;; handle continuation lines: 1660 (if (lua-is-continuing-statement-p) 1661 ;; if it's the first continuation line, add one level 1662 (unless (eq (car (car indentation-info)) 'continued-line) 1663 (push (cons 'continued-line lua-indent-level) indentation-info)) 1664 1665 ;; if it's the first non-continued line, subtract one level 1666 (when (eq (car (car indentation-info)) 'continued-line) 1667 (push (cons 'stop-continued-line (- lua-indent-level)) indentation-info))) 1668 1669 ;; add modifiers found in this continuation line 1670 (setq indentation-info 1671 (lua-calculate-indentation-info-1 1672 indentation-info (min parse-end (line-end-position))))) 1673 1674 indentation-info)) 1675 1676 1677 (defun lua-accumulate-indentation-info (reversed-indentation-info) 1678 "Accumulates the indentation information previously calculated by 1679 lua-calculate-indentation-info. Returns either the relative indentation 1680 shift, or the absolute column to indent to." 1681 (let (indentation-info 1682 (type 'relative) 1683 (accu 0)) 1684 ;; Aggregate all neighbouring relative offsets, reversing the INFO list. 1685 (cl-dolist (elt reversed-indentation-info) 1686 (if (and (eq (car elt) 'relative) 1687 (eq (caar indentation-info) 'relative)) 1688 (setcdr (car indentation-info) (+ (cdar indentation-info) (cdr elt))) 1689 (push elt indentation-info))) 1690 1691 ;; Aggregate indentation info, taking 'absolute modifiers into account. 1692 (mapc (lambda (x) 1693 (let ((new-val (cdr x))) 1694 (if (eq 'absolute (car x)) 1695 (progn (setq type 'absolute 1696 accu new-val)) 1697 (setq accu (+ accu new-val))))) 1698 indentation-info) 1699 1700 (cons type accu))) 1701 1702 (defun lua-calculate-indentation-block-modifier (&optional parse-end) 1703 "Return amount by which this line modifies the indentation. 1704 Beginnings of blocks add lua-indent-level once each, and endings 1705 of blocks subtract lua-indent-level once each. This function is used 1706 to determine how the indentation of the following line relates to this 1707 one." 1708 (let (indentation-info) 1709 (save-excursion 1710 ;; First go back to the line that starts it all 1711 ;; lua-calculate-indentation-info will scan through the whole thing 1712 (let ((case-fold-search nil)) 1713 (setq indentation-info 1714 (lua-accumulate-indentation-info 1715 (lua-calculate-indentation-info parse-end))))) 1716 1717 (if (eq (car indentation-info) 'absolute) 1718 (- (cdr indentation-info) (current-indentation)) 1719 (cdr indentation-info)))) 1720 1721 1722 (eval-when-compile 1723 (defconst lua--function-name-rx 1724 '(seq symbol-start 1725 (+ (any alnum "_")) 1726 (* "." (+ (any alnum "_"))) 1727 (? ":" (+ (any alnum "_"))) 1728 symbol-end) 1729 "Lua function name regexp in `rx'-SEXP format.")) 1730 1731 1732 (defconst lua--left-shifter-regexp 1733 (eval-when-compile 1734 (rx 1735 ;; This regexp should answer the following questions: 1736 ;; 1. is there a left shifter regexp on that line? 1737 ;; 2. where does block-open token of that left shifter reside? 1738 (or (seq (group-n 1 symbol-start "local" (+ blank)) "function" symbol-end) 1739 1740 (seq (group-n 1 (eval lua--function-name-rx) (* blank)) (any "{(")) 1741 (seq (group-n 1 (or 1742 ;; assignment statement prefix 1743 (seq (* nonl) (not (any "<=>~")) "=" (* blank)) 1744 ;; return statement prefix 1745 (seq word-start "return" word-end (* blank)))) 1746 ;; right hand side 1747 (or "{" 1748 "function" 1749 "(" 1750 (seq (group-n 1 (eval lua--function-name-rx) (* blank)) 1751 (any "({"))))))) 1752 1753 "Regular expression that matches left-shifter expression. 1754 1755 Left-shifter expression is defined as follows. If a block 1756 follows a left-shifter expression, its contents & block-close 1757 token should be indented relative to left-shifter expression 1758 indentation rather then to block-open token. 1759 1760 For example: 1761 -- 'local a = ' is a left-shifter expression 1762 -- 'function' is a block-open token 1763 local a = function() 1764 -- block contents is indented relative to left-shifter 1765 foobarbaz() 1766 -- block-end token is unindented to left-shifter indentation 1767 end 1768 1769 The following left-shifter expressions are currently handled: 1770 1. local function definition with function block, begin-end 1771 2. function call with arguments block, () or {} 1772 3. assignment/return statement with 1773 - table constructor block, {} 1774 - function call arguments block, () or {} block 1775 - function expression a.k.a. lambda, begin-end block.") 1776 1777 1778 (defun lua-point-is-after-left-shifter-p () 1779 "Check if point is right after a left-shifter expression. 1780 1781 See `lua--left-shifter-regexp' for description & example of 1782 left-shifter expression. " 1783 (save-excursion 1784 (let ((old-point (point))) 1785 (back-to-indentation) 1786 (and 1787 (/= (point) old-point) 1788 (looking-at lua--left-shifter-regexp) 1789 (= old-point (match-end 1)))))) 1790 1791 (defun lua--goto-line-beginning-rightmost-closer (&optional parse-start) 1792 (let (case-fold-search pos line-end-pos return-val) 1793 (save-excursion 1794 (if parse-start (goto-char parse-start)) 1795 (setq line-end-pos (line-end-position)) 1796 (back-to-indentation) 1797 (unless (lua-comment-or-string-p) 1798 (cl-loop while (and (<= (point) line-end-pos) 1799 (looking-at lua-indentation-modifier-regexp)) 1800 for token-info = (lua-get-block-token-info (match-string 0)) 1801 for token-type = (lua-get-token-type token-info) 1802 while (not (eq token-type 'open)) 1803 do (progn 1804 (setq pos (match-beginning 0) 1805 return-val token-info) 1806 (goto-char (match-end 0)) 1807 (forward-comment (line-end-position)))))) 1808 (when pos 1809 (progn 1810 (goto-char pos) 1811 return-val)))) 1812 1813 1814 (defun lua-calculate-indentation-override (&optional parse-start) 1815 "Return overriding indentation amount for special cases. 1816 1817 If there's a sequence of block-close tokens starting at the 1818 beginning of the line, calculate indentation according to the 1819 line containing block-open token for the last block-close token 1820 in the sequence. 1821 1822 If not, return nil." 1823 (let (case-fold-search rightmost-closer-info opener-info opener-pos) 1824 (save-excursion 1825 (when (and (setq rightmost-closer-info (lua--goto-line-beginning-rightmost-closer parse-start)) 1826 (setq opener-info (lua--backward-up-list-noerror)) 1827 ;; Ensure opener matches closer. 1828 (string-match (lua-get-token-match-re rightmost-closer-info 'backward) 1829 (car opener-info))) 1830 1831 ;; Special case: "middle" tokens like for/do, while/do, if/then, 1832 ;; elseif/then: corresponding "end" or corresponding "else" must be 1833 ;; unindented to the beginning of the statement, which is not 1834 ;; necessarily the same as beginning of string that contains "do", e.g. 1835 ;; 1836 ;; while ( 1837 ;; foo and 1838 ;; bar) do 1839 ;; hello_world() 1840 ;; end 1841 (setq opener-pos (point)) 1842 (when (/= (- opener-pos (line-beginning-position)) (current-indentation)) 1843 (unless (or 1844 (and (string-equal (car opener-info) "do") 1845 (member (car (lua--backward-up-list-noerror)) '("while" "for"))) 1846 (and (string-equal (car opener-info) "then") 1847 (member (car (lua--backward-up-list-noerror)) '("if" "elseif")))) 1848 (goto-char opener-pos))) 1849 1850 ;; (let (cont-stmt-pos) 1851 ;; (while (setq cont-stmt-pos (lua-is-continuing-statement-p)) 1852 ;; (goto-char cont-stmt-pos))) 1853 ;; Exception cases: when the start of the line is an assignment, 1854 ;; go to the start of the assignment instead of the matching item 1855 (if (and lua-indent-close-paren-align 1856 (member (car opener-info) '("{" "(" "[")) 1857 (not (lua-point-is-after-left-shifter-p))) 1858 (current-column) 1859 (current-indentation)))))) 1860 1861 1862 (defun lua-calculate-indentation () 1863 "Return appropriate indentation for current line as Lua code." 1864 (save-excursion 1865 (let ((cur-line-begin-pos (line-beginning-position))) 1866 (or 1867 ;; when calculating indentation, do the following: 1868 ;; 1. check, if the line starts with indentation-modifier (open/close brace) 1869 ;; and if it should be indented/unindented in special way 1870 (lua-calculate-indentation-override) 1871 1872 (when (lua-forward-line-skip-blanks 'back) 1873 ;; the order of function calls here is important. block modifier 1874 ;; call may change the point to another line 1875 (let* ((modifier 1876 (lua-calculate-indentation-block-modifier cur-line-begin-pos))) 1877 (+ (current-indentation) modifier))) 1878 1879 ;; 4. if there's no previous line, indentation is 0 1880 0)))) 1881 1882 (defvar lua--beginning-of-defun-re 1883 (lua-rx-to-string '(: bol (? (symbol "local") ws+) lua-funcheader)) 1884 "Lua top level (matches only at the beginning of line) function header regex.") 1885 1886 1887 (defun lua-beginning-of-proc (&optional arg) 1888 "Move backward to the beginning of a Lua proc (or similar). 1889 1890 With argument, do it that many times. Negative arg -N 1891 means move forward to Nth following beginning of proc. 1892 1893 Returns t unless search stops due to beginning or end of buffer." 1894 (interactive "P") 1895 (or arg (setq arg 1)) 1896 1897 (while (and (> arg 0) 1898 (re-search-backward lua--beginning-of-defun-re nil t)) 1899 (setq arg (1- arg))) 1900 1901 (while (and (< arg 0) 1902 (re-search-forward lua--beginning-of-defun-re nil t)) 1903 (beginning-of-line) 1904 (setq arg (1+ arg))) 1905 1906 (zerop arg)) 1907 1908 (defun lua-end-of-proc (&optional arg) 1909 "Move forward to next end of Lua proc (or similar). 1910 With argument, do it that many times. Negative argument -N means move 1911 back to Nth preceding end of proc. 1912 1913 This function just searches for a `end' at the beginning of a line." 1914 (interactive "P") 1915 (or arg 1916 (setq arg 1)) 1917 (let ((found nil) 1918 (ret t)) 1919 (if (and (< arg 0) 1920 (not (bolp)) 1921 (save-excursion 1922 (beginning-of-line) 1923 (eq (following-char) ?}))) 1924 (forward-char -1)) 1925 (while (> arg 0) 1926 (if (re-search-forward "^end" nil t) 1927 (setq arg (1- arg) 1928 found t) 1929 (setq ret nil 1930 arg 0))) 1931 (while (< arg 0) 1932 (if (re-search-backward "^end" nil t) 1933 (setq arg (1+ arg) 1934 found t) 1935 (setq ret nil 1936 arg 0))) 1937 (if found 1938 (progn 1939 (beginning-of-line) 1940 (forward-line))) 1941 ret)) 1942 1943 (defvar lua-process-init-code 1944 (mapconcat 1945 'identity 1946 '("local loadstring = loadstring or load" 1947 "function luamode_loadstring(str, displayname, lineoffset)" 1948 " if lineoffset > 1 then" 1949 " str = string.rep('\\n', lineoffset - 1) .. str" 1950 " end" 1951 "" 1952 " local x, e = loadstring(str, '@'..displayname)" 1953 " if e then" 1954 " error(e)" 1955 " end" 1956 " return x()" 1957 "end") 1958 " ")) 1959 1960 (defun lua-make-lua-string (str) 1961 "Convert string to Lua literal." 1962 (save-match-data 1963 (with-temp-buffer 1964 (insert str) 1965 (goto-char (point-min)) 1966 (while (re-search-forward "[\"'\\\t\\\n]" nil t) 1967 (cond 1968 ((string= (match-string 0) "\n") 1969 (replace-match "\\\\n")) 1970 ((string= (match-string 0) "\t") 1971 (replace-match "\\\\t")) 1972 (t 1973 (replace-match "\\\\\\&" t)))) 1974 (concat "'" (buffer-string) "'")))) 1975 1976 ;;;###autoload 1977 (defalias 'run-lua #'lua-start-process) 1978 1979 ;;;###autoload 1980 (defun lua-start-process (&optional name program startfile &rest switches) 1981 "Start a Lua process named NAME, running PROGRAM. 1982 PROGRAM defaults to NAME, which defaults to `lua-default-application'. 1983 When called interactively, switch to the process buffer." 1984 (interactive) 1985 (setq name (or name (if (consp lua-default-application) 1986 (car lua-default-application) 1987 lua-default-application))) 1988 (setq program (or program lua-default-application)) 1989 ;; don't re-initialize if there already is a lua process 1990 (unless (comint-check-proc (format "*%s*" name)) 1991 (setq lua-process-buffer (apply #'make-comint name program startfile 1992 (or switches lua-default-command-switches))) 1993 (setq lua-process (get-buffer-process lua-process-buffer)) 1994 (set-process-query-on-exit-flag lua-process nil) 1995 (with-current-buffer lua-process-buffer 1996 ;; enable error highlighting in stack traces 1997 (require 'compile) 1998 (setq lua--repl-buffer-p t) 1999 (make-local-variable 'compilation-error-regexp-alist) 2000 (setq compilation-error-regexp-alist 2001 (cons (list lua-traceback-line-re 1 2) 2002 compilation-error-regexp-alist)) 2003 (compilation-shell-minor-mode 1) 2004 (setq-local comint-prompt-regexp lua-prompt-regexp) 2005 2006 ;; Don't send initialization code until seeing the prompt to ensure that 2007 ;; the interpreter is ready. 2008 (while (not (lua-prompt-line)) 2009 (accept-process-output (get-buffer-process (current-buffer))) 2010 (goto-char (point-max))) 2011 (lua-send-string lua-process-init-code))) 2012 2013 ;; when called interactively, switch to process buffer 2014 (if (called-interactively-p 'any) 2015 (switch-to-buffer lua-process-buffer))) 2016 2017 (defun lua-get-create-process () 2018 "Return active Lua process creating one if necessary." 2019 (lua-start-process) 2020 lua-process) 2021 2022 (defun lua-kill-process () 2023 "Kill Lua process and its buffer." 2024 (interactive) 2025 (when (buffer-live-p lua-process-buffer) 2026 (kill-buffer lua-process-buffer) 2027 (setq lua-process-buffer nil))) 2028 2029 (defun lua-set-lua-region-start (&optional arg) 2030 "Set start of region for use with `lua-send-lua-region'." 2031 (interactive) 2032 (set-marker lua-region-start (or arg (point)))) 2033 2034 (defun lua-set-lua-region-end (&optional arg) 2035 "Set end of region for use with `lua-send-lua-region'." 2036 (interactive) 2037 (set-marker lua-region-end (or arg (point)))) 2038 2039 (defun lua-send-string (str) 2040 "Send STR plus a newline to the Lua process. 2041 2042 If `lua-process' is nil or dead, start a new process first." 2043 (unless (string-equal (substring str -1) "\n") 2044 (setq str (concat str "\n"))) 2045 (process-send-string (lua-get-create-process) str)) 2046 2047 (defun lua-send-current-line () 2048 "Send current line to the Lua process, found in `lua-process'. 2049 If `lua-process' is nil or dead, start a new process first." 2050 (interactive) 2051 (lua-send-region (line-beginning-position) (line-end-position))) 2052 2053 (defun lua-send-defun (pos) 2054 "Send the function definition around point to the Lua process." 2055 (interactive "d") 2056 (save-excursion 2057 (let ((start (if (save-match-data (looking-at "^function[ \t]")) 2058 ;; point already at the start of "function". 2059 ;; We need to handle this case explicitly since 2060 ;; lua-beginning-of-proc will move to the 2061 ;; beginning of the _previous_ function. 2062 (point) 2063 ;; point is not at the beginning of function, move 2064 ;; there and bind start to that position 2065 (lua-beginning-of-proc) 2066 (point))) 2067 (end (progn (lua-end-of-proc) (point)))) 2068 2069 ;; make sure point is in a function definition before sending to 2070 ;; the process 2071 (if (and (>= pos start) (< pos end)) 2072 (lua-send-region start end) 2073 (error "Not on a function definition"))))) 2074 2075 (defun lua-maybe-skip-shebang-line (start) 2076 "Skip shebang (#!/path/to/interpreter/) line at beginning of buffer. 2077 2078 Return a position that is after Lua-recognized shebang line (1st 2079 character in file must be ?#) if START is at its beginning. 2080 Otherwise, return START." 2081 (save-restriction 2082 (widen) 2083 (if (and (eq start (point-min)) 2084 (eq (char-after start) ?#)) 2085 (save-excursion 2086 (goto-char start) 2087 (forward-line) 2088 (point)) 2089 start))) 2090 2091 (defun lua-send-region (start end) 2092 (interactive "r") 2093 (setq start (lua-maybe-skip-shebang-line start)) 2094 (let* ((lineno (line-number-at-pos start)) 2095 (lua-file (or (buffer-file-name) (buffer-name))) 2096 (region-str (buffer-substring-no-properties start end)) 2097 (command 2098 ;; Print empty line before executing the code so that the first line 2099 ;; of output doesn't end up on the same line as current prompt. 2100 (format "print(''); luamode_loadstring(%s, %s, %s);\n" 2101 (lua-make-lua-string region-str) 2102 (lua-make-lua-string lua-file) 2103 lineno))) 2104 (lua-send-string command) 2105 (when lua-always-show (lua-show-process-buffer)))) 2106 2107 (defun lua-prompt-line () 2108 (save-excursion 2109 (save-match-data 2110 (forward-line 0) 2111 (if (looking-at comint-prompt-regexp) 2112 (match-end 0))))) 2113 2114 (defun lua-send-lua-region () 2115 "Send preset Lua region to Lua process." 2116 (interactive) 2117 (unless (and lua-region-start lua-region-end) 2118 (error "lua-region not set")) 2119 (lua-send-region lua-region-start lua-region-end)) 2120 2121 (defalias 'lua-send-proc 'lua-send-defun) 2122 2123 (defun lua-send-buffer () 2124 "Send whole buffer to Lua process." 2125 (interactive) 2126 (lua-send-region (point-min) (point-max))) 2127 2128 (defun lua-restart-with-whole-file () 2129 "Restart Lua process and send whole file as input." 2130 (interactive) 2131 (lua-kill-process) 2132 (lua-send-buffer)) 2133 2134 (defun lua-show-process-buffer () 2135 "Make sure `lua-process-buffer' is being displayed. 2136 Create a Lua process if one doesn't already exist." 2137 (interactive) 2138 (display-buffer (process-buffer (lua-get-create-process)))) 2139 2140 2141 (defun lua-hide-process-buffer () 2142 "Delete all windows that display `lua-process-buffer'." 2143 (interactive) 2144 (when (buffer-live-p lua-process-buffer) 2145 (delete-windows-on lua-process-buffer))) 2146 2147 (defun lua--funcname-char-p (c) 2148 "Check if character C is part of a function name. 2149 Return nil if C is nil. See `lua-funcname-at-point'." 2150 (and c (string-match-p "\\`[A-Za-z_.]\\'" (string c)))) 2151 2152 (defun lua-funcname-at-point () 2153 "Get current Name { '.' Name } sequence." 2154 (when (or (lua--funcname-char-p (char-before)) 2155 (lua--funcname-char-p (char-after))) 2156 (save-excursion 2157 (save-match-data 2158 (re-search-backward "\\`\\|[^A-Za-z_.]") 2159 ;; NOTE: `point' will be either at the start of the buffer or on a 2160 ;; non-symbol character. 2161 (re-search-forward "\\([A-Za-z_]+\\(?:\\.[A-Za-z_]+\\)*\\)") 2162 (match-string-no-properties 1))))) 2163 2164 (defun lua-search-documentation () 2165 "Search Lua documentation for the word at the point." 2166 (interactive) 2167 (let ((url (concat lua-documentation-url "#pdf-" (lua-funcname-at-point)))) 2168 (funcall lua-documentation-function url))) 2169 2170 (defun lua-toggle-electric-state (&optional arg) 2171 "Toggle the electric indentation feature. 2172 Optional numeric ARG, if supplied, turns on electric indentation when 2173 positive, turns it off when negative, and just toggles it when zero or 2174 left out." 2175 (interactive "P") 2176 (let ((num_arg (prefix-numeric-value arg))) 2177 (setq lua-electric-flag (cond ((or (null arg) 2178 (zerop num_arg)) (not lua-electric-flag)) 2179 ((< num_arg 0) nil) 2180 ((> num_arg 0) t)))) 2181 (message "%S" lua-electric-flag)) 2182 2183 (defun lua-forward-sexp (&optional count) 2184 "Forward to block end" 2185 (interactive "p") 2186 ;; negative offsets not supported 2187 (cl-assert (or (not count) (>= count 0))) 2188 (save-match-data 2189 (let ((count (or count 1)) 2190 (block-start (mapcar 'car lua-sexp-alist))) 2191 (while (> count 0) 2192 ;; skip whitespace 2193 (skip-chars-forward " \t\n") 2194 (if (looking-at (regexp-opt block-start 'words)) 2195 (let ((keyword (match-string 1))) 2196 (lua-find-matching-token-word keyword 'forward)) 2197 ;; If the current keyword is not a "begin" keyword, then just 2198 ;; perform the normal forward-sexp. 2199 (forward-sexp 1)) 2200 (setq count (1- count)))))) 2201 2202 ;; Flymake integration 2203 2204 (defcustom lua-luacheck-program "luacheck" 2205 "Name of the luacheck executable." 2206 :type 'string 2207 :group 'lua) 2208 2209 (defvar-local lua--flymake-process nil) 2210 2211 (defun lua-flymake (report-fn &rest _args) 2212 "Flymake backend using the luacheck program. 2213 Takes a Flymake callback REPORT-FN as argument, as expected of a 2214 member of `flymake-diagnostic-functions'." 2215 (when (process-live-p lua--flymake-process) 2216 (kill-process lua--flymake-process)) 2217 (let ((source (current-buffer))) 2218 (save-restriction 2219 (widen) 2220 (setq lua--flymake-process 2221 (make-process 2222 :name "luacheck" :noquery t :connection-type 'pipe 2223 :buffer (generate-new-buffer " *flymake-luacheck*") 2224 :command `(,lua-luacheck-program 2225 "--codes" "--ranges" "--formatter" "plain" "-") 2226 :sentinel 2227 (lambda (proc _event) 2228 (when (eq 'exit (process-status proc)) 2229 (unwind-protect 2230 (if (with-current-buffer source 2231 (eq proc lua--flymake-process)) 2232 (with-current-buffer (process-buffer proc) 2233 (goto-char (point-min)) 2234 (cl-loop 2235 while (search-forward-regexp 2236 "^\\([^:]*\\):\\([0-9]+\\):\\([0-9]+\\)-\\([0-9]+\\): \\(.*\\)$" 2237 nil t) 2238 for line = (string-to-number (match-string 2)) 2239 for col1 = (string-to-number (match-string 3)) 2240 for col2 = (1+ (string-to-number (match-string 4))) 2241 for msg = (match-string 5) 2242 for type = (if (string-match-p "\\`(E" msg) :error :warning) 2243 collect (flymake-make-diagnostic source 2244 (cons line col1) 2245 (cons line col2) 2246 type 2247 msg) 2248 into diags 2249 finally (funcall report-fn diags))) 2250 (flymake-log :warning "Canceling obsolete check %s" proc)) 2251 (kill-buffer (process-buffer proc))))))) 2252 (process-send-region lua--flymake-process (point-min) (point-max)) 2253 (process-send-eof lua--flymake-process)))) 2254 2255 ;; menu bar 2256 2257 (define-key lua-mode-menu [restart-with-whole-file] 2258 '("Restart With Whole File" . lua-restart-with-whole-file)) 2259 (define-key lua-mode-menu [kill-process] 2260 '("Kill Process" . lua-kill-process)) 2261 2262 (define-key lua-mode-menu [hide-process-buffer] 2263 '("Hide Process Buffer" . lua-hide-process-buffer)) 2264 (define-key lua-mode-menu [show-process-buffer] 2265 '("Show Process Buffer" . lua-show-process-buffer)) 2266 2267 (define-key lua-mode-menu [end-of-proc] 2268 '("End Of Proc" . lua-end-of-proc)) 2269 (define-key lua-mode-menu [beginning-of-proc] 2270 '("Beginning Of Proc" . lua-beginning-of-proc)) 2271 2272 (define-key lua-mode-menu [send-lua-region] 2273 '("Send Lua-Region" . lua-send-lua-region)) 2274 (define-key lua-mode-menu [set-lua-region-end] 2275 '("Set Lua-Region End" . lua-set-lua-region-end)) 2276 (define-key lua-mode-menu [set-lua-region-start] 2277 '("Set Lua-Region Start" . lua-set-lua-region-start)) 2278 2279 (define-key lua-mode-menu [send-current-line] 2280 '("Send Current Line" . lua-send-current-line)) 2281 (define-key lua-mode-menu [send-region] 2282 '("Send Region" . lua-send-region)) 2283 (define-key lua-mode-menu [send-proc] 2284 '("Send Proc" . lua-send-proc)) 2285 (define-key lua-mode-menu [send-buffer] 2286 '("Send Buffer" . lua-send-buffer)) 2287 (define-key lua-mode-menu [search-documentation] 2288 '("Search Documentation" . lua-search-documentation)) 2289 2290 2291 (provide 'lua-mode) 2292 2293 ;;; lua-mode.el ends here