--- /dev/null
+;;; groovy-electric.el --- Electric mode for Groovy
+
+;; Copyright (C) 2009 Jim Morris
+
+;; Author: Jim Morris <morrisNOSPAMwolfman.com>
+;; Created: 2009-12-11
+;; Maintainer: Russel Winder <russelNOSPAMwinder.org.uk>
+;; Version: 201605040906
+;; Keywords: languages
+
+;;;; NB Version number is date and time yyyymmddhhMM UTC.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;; Based on ruby-electric.el Copyright (C) 2005 by Dee Zsombor <dee dot zsombor at gmail dot com>.
+;; Due credit: original work was inspired by a code snippet posted by
+;; Frederick Ros at http://rubygarden.org/ruby?EmacsExtensions.
+
+;;; Usage:
+;; If you install using the packaging system no further set up should be needed. If you install this mode
+;; manually then you will likely need to add some extra lines to your init file:
+;;
+;; 0) copy groovy-electric.el into directory where emacs can find it.
+;;
+;; 1) modify your startup file (.emacs or whatever) by adding
+;; following lines to load and enable the mode when groovy-mode loads
+;;
+;; (add-hook 'groovy-mode-hook
+;; '(lambda ()
+;; (require 'groovy-electric)
+;; (groovy-electric-mode)))
+;;
+;; or add this to your init file
+;;
+;; (require 'groovy-electric)
+;;
+;; note that you need to have font lock enabled beforehand.
+;;
+;; 2) toggle Groovy Electric Mode on/off with groovy-electric-mode.
+
+;;; Commentary:
+;; By default automatically inserts closing delimiter for {[('"
+;; Additionally when in a GString typing a $ will insert { } and place
+;; cursor between the braces. All these can be turned on or off
+;; individually in the customization window for groovy-electric
+
+;;; Bugs:
+;; Bug tracking is currently handled using the GitHub issue tracker at
+;; https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/issues
+
+;;; Versions:
+;; This mode is available on MELPA which tracks the mainline Git repository on GitHub, so there is a rolling release
+;; system based on commits to the mainline.
+
+;;; Notes:
+
+;;; TODO:
+;; Issues with this code are managed via the project issue management
+;; on GitHub: https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/issues?state=open
+
+;;; History:
+;; History is tracked in the Git repository rather than in this file.
+;; See https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/commits/master
+
+;;----------------------------------------------------------------------------
+;;; Code:
+
+(require 'groovy-mode)
+(defgroup groovy-electric nil
+ "Minor mode providing electric editing commands for groovy files"
+ :group 'groovy)
+
+(defvar groovy-electric-matching-delimeter-alist
+ '((?\[ . ?\])
+ (?\( . ?\))
+ (?\' . ?\')
+ (?\" . ?\")))
+
+(defcustom groovy-electric-expand-delimiters-list '(all)
+ "*List of contexts where matching delimiter should be inserted.
+The word 'all' will do all insertions."
+ :type '(set :extra-offset 8
+ (const :tag "Everything" all )
+ (const :tag "Curly brace" ?\{ )
+ (const :tag "Square brace" ?\[ )
+ (const :tag "Round brace" ?\( )
+ (const :tag "Quote" ?\' )
+ (const :tag "Double quote" ?\" )
+ (const :tag "Dollar in GStrings" ?\$ ))
+ :group 'groovy-electric)
+
+(defcustom groovy-electric-newline-before-closing-bracket nil
+ "*Controls whether a newline should be inserted before the
+closing bracket or not."
+ :type 'boolean :group 'groovy-electric)
+
+;;;###autoload
+(define-minor-mode groovy-electric-mode
+ "Toggle Groovy Electric minor mode.
+With no argument, this command toggles the mode. Non-null prefix
+argument turns on the mode. Null prefix argument turns off the
+mode.
+
+When Groovy Electric mode is enabled, simple, double and back
+quotes as well as braces are paired auto-magically. Expansion
+does not occur inside comments and strings. Note that you must
+have Font Lock enabled. ${ } is expanded when in a GString"
+ ;; initial value.
+ nil
+ ;;indicator for the mode line.
+ " Ge"
+ ;;keymap
+ groovy-mode-map
+ (groovy-electric-setup-keymap))
+
+(defun groovy-electric-setup-keymap()
+ (define-key groovy-mode-map "{" 'groovy-electric-curlies)
+ (define-key groovy-mode-map "(" 'groovy-electric-matching-char)
+ (define-key groovy-mode-map "[" 'groovy-electric-matching-char)
+ (define-key groovy-mode-map "\"" 'groovy-electric-matching-char)
+ (define-key groovy-mode-map "\'" 'groovy-electric-matching-char)
+ (define-key groovy-mode-map "\$" 'groovy-electric-pound)
+ )
+
+(defun groovy-electric-code-at-point-p()
+ (and groovy-electric-mode
+ (let* ((properties (text-properties-at (point))))
+ (and (null (memq 'font-lock-string-face properties))
+ (null (memq 'font-lock-comment-face properties))))))
+
+(defun groovy-electric-string-at-point-p()
+ (and groovy-electric-mode
+ (consp (memq 'font-lock-string-face (text-properties-at (point))))))
+
+;; This checks it is a GString ("...") not normal string '...'
+(defun groovy-electric-gstring-at-point-p()
+ (and groovy-electric-mode
+ (consp (memq 'font-lock-string-face (text-properties-at (point))))
+ (save-excursion
+ (char-equal ?\" (char-after (car (c-literal-limits)))))))
+
+(defun groovy-electric-is-last-command-event-expandable-punct-p()
+ (or (memq 'all groovy-electric-expand-delimiters-list)
+ (memq last-command-event groovy-electric-expand-delimiters-list)))
+
+(defun groovy-electric-curlies(arg)
+ (interactive "P")
+ (self-insert-command (prefix-numeric-value arg))
+ (when (and (groovy-electric-is-last-command-event-expandable-punct-p)
+ (groovy-electric-code-at-point-p))
+ (insert " ")
+ (save-excursion
+ (if groovy-electric-newline-before-closing-bracket
+ (newline))
+ (insert "}"))))
+
+(defun groovy-electric-matching-char(arg)
+ (interactive "P")
+ (self-insert-command (prefix-numeric-value arg))
+ (and (groovy-electric-is-last-command-event-expandable-punct-p)
+ (groovy-electric-code-at-point-p)
+ (save-excursion
+ (insert (cdr (assoc last-command-event
+ groovy-electric-matching-delimeter-alist))))))
+
+(defun groovy-electric-pound(arg)
+ (interactive "P")
+ (self-insert-command (prefix-numeric-value arg))
+ (when (and (groovy-electric-is-last-command-event-expandable-punct-p)
+ (groovy-electric-gstring-at-point-p)
+ (not (save-excursion ; make sure it is not escaped
+ (backward-char 1)
+ (char-equal ?\\ (preceding-char)))))
+ (insert "{}")
+ (backward-char 1)))
+
+
+;;----------------------------------------------------------------------------
+
+(provide 'groovy-electric)
+
+;;; groovy-electric.el ends here
--- /dev/null
+;;; groovy-mode-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "groovy-electric" "groovy-electric.el" (0 0
+;;;;;; 0 0))
+;;; Generated autoloads from groovy-electric.el
+
+(autoload 'groovy-electric-mode "groovy-electric" "\
+Toggle Groovy Electric minor mode.
+With no argument, this command toggles the mode. Non-null prefix
+argument turns on the mode. Null prefix argument turns off the
+mode.
+
+This is a minor mode. If called interactively, toggle the
+`Groovy-Electric mode' mode. If the prefix argument is positive,
+enable the mode, and if it is zero or negative, disable the mode.
+
+If called from Lisp, toggle the mode if ARG is `toggle'. Enable
+the mode if ARG is nil, omitted, or is a positive number.
+Disable the mode if ARG is a negative number.
+
+To check whether the minor mode is enabled in the current buffer,
+evaluate `groovy-electric-mode'.
+
+The mode's hook is called both when the mode is enabled and when
+it is disabled.
+
+When Groovy Electric mode is enabled, simple, double and back
+quotes as well as braces are paired auto-magically. Expansion
+does not occur inside comments and strings. Note that you must
+have Font Lock enabled. ${ } is expanded when in a GString
+
+\(fn &optional ARG)" t nil)
+
+(register-definition-prefixes "groovy-electric" '("groovy-electric-"))
+
+;;;***
+\f
+;;;### (autoloads nil "groovy-mode" "groovy-mode.el" (0 0 0 0))
+;;; Generated autoloads from groovy-mode.el
+
+(add-to-list 'auto-mode-alist '("\\.g\\(?:ant\\|roovy\\|radle\\)\\'" . groovy-mode))
+
+(add-to-list 'auto-mode-alist '("/Jenkinsfile\\'" . groovy-mode))
+
+(add-to-list 'interpreter-mode-alist '("groovy" . groovy-mode))
+
+(autoload 'groovy-mode "groovy-mode" "\
+Major mode for editing Groovy code.
+
+The hook `groovy-mode-hook' is run with no args at mode
+initialization.
+
+Key bindings:
+\\{groovy-mode-map}
+
+\(fn)" t nil)
+
+(register-definition-prefixes "groovy-mode" '("groovy-"))
+
+;;;***
+\f
+;;;### (autoloads nil "inf-groovy" "inf-groovy.el" (0 0 0 0))
+;;; Generated autoloads from inf-groovy.el
+
+(autoload 'inf-groovy-keys "inf-groovy" "\
+Set local key defs for inf-groovy in `groovy-mode'." nil nil)
+
+(autoload 'inferior-groovy-mode "inf-groovy" "\
+Major mode for interacting with an inferior groovy (groovysh) process.
+
+The following commands are available:
+\\{inferior-groovy-mode-map}
+
+A groovy process can be fired up with \\[run-groovy].
+
+Customisation: Entry to this mode runs the hooks on
+`comint-mode-hook' and `inferior-groovy-mode-hook' (in that order).
+
+You can send text to the inferior groovy process from other buffers containing
+Groovy source.
+ `switch-to-groovy' switches the current buffer to the groovy process buffer.
+ `groovy-send-definition' sends the current definition to the groovy process.
+ `groovy-send-region' sends the current region to the groovy process.
+
+ `groovy-send-definition-and-go', `groovy-send-region-and-go',
+ switch to the groovy process buffer after sending their text.
+For information on running multiple processes in multiple buffers, see
+documentation for variable `groovy-buffer'.
+
+Commands:
+Return after the end of the process' output sends the text from the
+ end of process to point.
+Return before the end of the process' output copies the sexp ending at point
+ to the end of the process' output, and sends it.
+Delete converts tabs to spaces as it moves back.
+Tab indents for groovy; with argument, shifts rest
+ of expression rigidly with the current line.
+\\<groovy-mode-map>
+\\[prog-indent-sexp] does Tab on each line starting within following expression.
+Paragraphs are separated only by blank lines. # start comments.
+If you accidentally suspend your process, use \\[comint-continue-subjob]
+to continue it." t nil)
+
+(autoload 'run-groovy "inf-groovy" "\
+Run an inferior Groovy process, input and output via buffer *groovy*.
+If there is a process already running in *groovy*, switch to that buffer.
+With a prefix argument, prompt for the groovysh path and arguments
+\(see variables `groovysh' and `groovysh-args' for the defaults).
+
+Runs the hook `inferior-groovy-mode-hook' (after the
+`comint-mode-hook' is run). Type \\[describe-mode] in the
+process buffer for a list of commands.
+
+\(fn CMD)" t nil)
+
+(eval-after-load 'groovy-mode (lambda nil (add-hook 'groovy-mode-hook 'inf-groovy-keys)))
+
+(register-definition-prefixes "inf-groovy" '("groovy" "inf" "remove-in-string" "switch-to-groovy"))
+
+;;;***
+\f
+;;;### (autoloads nil nil ("groovy-mode-pkg.el") (0 0 0 0))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; groovy-mode-autoloads.el ends here
--- /dev/null
+(define-package "groovy-mode" "20230318.533" "Major mode for Groovy source files"
+ '((s "1.12.0")
+ (emacs "24.3")
+ (dash "2.13.0"))
+ :commit "7b8520b2e2d3ab1d62b35c426e17ac25ed0120bb" :authors
+ '(("Russel Winder" . "russelNOSPAMwinder.org.uk")
+ ("Jim Morris" . "morrisNOSPAMwolfman.com")
+ ("Wilfred Hughes" . "meNOSPAMwilfred.me.uk"))
+ :maintainers
+ '(("Russel Winder" . "russelNOSPAMwinder.org.uk"))
+ :maintainer
+ '("Russel Winder" . "russelNOSPAMwinder.org.uk")
+ :keywords
+ '("languages")
+ :url "https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; groovy-mode.el --- Major mode for Groovy source files
+
+;; Copyright © 2006, 2009–2010, 2012–2016 Russel Winder
+
+;; Author: Russel Winder <russelNOSPAMwinder.org.uk>, 2006–
+;; Jim Morris <morrisNOSPAMwolfman.com>, 2009–
+;; Wilfred Hughes <meNOSPAMwilfred.me.uk>, 2017–
+;; Maintainer: Russel Winder <russelNOSPAMwinder.org.uk>
+;; Created: 2006-08-01
+;; Keywords: languages
+;; URL: https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes
+;; Version: 2.2
+;; Package-Requires: ((s "1.12.0") (emacs "24.3") (dash "2.13.0"))
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Usage:
+;; If you install using the packaging system no further set up should be needed. If you install this mode
+;; manually then you will likely need to put these lines in your init file:
+;;
+;; (autoload 'groovy-mode "groovy-mode" "Major mode for editing Groovy code." t)
+;; (add-to-list 'auto-mode-alist '("\\.groovy\\'" . groovy-mode))
+
+;;; Bugs:
+;; Bug tracking is currently handled using the GitHub issue tracker at
+;; https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/issues
+
+;;; Notes:
+;; Should we support GString / template markup ( e.g. `<%' and `%>') specially?
+
+;;; Commentary:
+
+;;; Code:
+
+(require 's)
+(require 'dash)
+
+(defvar groovy-mode-syntax-table
+ (let ((table (make-syntax-table)))
+ ;; Operators
+ ;; http://docs.groovy-lang.org/latest/html/documentation/#groovy-operators
+ (dolist (i '(?+ ?- ?* ?/ ?% ?& ?| ?^ ?! ?< ?> ?~ ?@ ?=))
+ (modify-syntax-entry i "." table))
+ ;; Strings
+ (modify-syntax-entry ?\" "\"" table)
+ (modify-syntax-entry ?\\ "\\" table)
+ (modify-syntax-entry ?' "\"" table)
+ ;; Comments
+ (modify-syntax-entry ?/ ". 124b" table)
+ (modify-syntax-entry ?* ". 23" table)
+ (modify-syntax-entry ?\n "> b" table)
+ (modify-syntax-entry ?\^m "> b" table)
+ table)
+ "Syntax table used in Groovy mode buffers.")
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.g\\(?:ant\\|roovy\\|radle\\)\\'" . groovy-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("/Jenkinsfile\\'" . groovy-mode))
+;;;###autoload
+(add-to-list 'interpreter-mode-alist '("groovy" . groovy-mode))
+
+;; Regexp Constants
+(defconst groovy-type-regexp
+ (rx symbol-start
+ (group
+ (or
+ ;; Treat Foo, FooBar and FFoo as type names, but not FOO.
+ (seq (+ upper) lower (0+ (or (syntax symbol) (syntax word))))
+ "byte"
+ "short"
+ "int"
+ "long"
+ "float"
+ "double"
+ "boolean"
+ "char"
+ "void"))
+ symbol-end
+ (? "[]"))
+ "Matches types, where the name is first group.")
+
+(defconst groovy-declaration-keyword-regex
+ (rx
+ (* space)
+ symbol-start
+ (group (*
+ (seq
+ (+ (or "def" "public" "private" "protected" "final" "static"
+ "abstract" "synchronized" "native")
+ (+ space))))))
+ "Matches declaration keywords.")
+
+(defconst groovy-declaration-regexp
+ (rx-to-string
+ `(seq
+ (or bol "(" ";" "," "{")
+ (regexp ,groovy-declaration-keyword-regex)
+ (seq
+ (* space)
+ symbol-start
+ (group
+ (or
+ ;; Treat Foo, FooBar and FFoo as type names, but not FOO.
+ ;; also Foo<Bar>
+ (seq (+ upper) lower (* (syntax word)) (or " " "<"))
+ (seq (or
+ "def"
+ "byte"
+ "short"
+ "int"
+ "long"
+ "float"
+ "double"
+ "boolean"
+ "char"
+ "void")
+ symbol-end
+ (? "[]")))))))
+ "Matches declarations of the type `def FooBar<?>'.")
+
+(defconst groovy-symbol-regexp
+ (rx
+ symbol-start
+ (group (+ (or (syntax word) (syntax symbol))))
+ symbol-end)
+ "A variable name or a type name.")
+
+(defconst groovy-variable-assignment-regexp
+ (rx-to-string
+ `(seq
+ (regexp ,groovy-symbol-regexp)
+ (* space)
+ "="
+ (not (any "~" "="))))
+ "Matches variable assignments of the type `a = 1'.")
+
+(defconst groovy-class-regexp
+ "^[ \t\n\r]*\\(final\\|abstract\\|public\\|[ \t\n\r]\\)*class[ \t\n\r]+\\([a-zA-Z0-9_$]+\\)[^;{]*{"
+ "Matches class names in groovy code, select match 2.")
+
+(defconst groovy-interface-regexp
+ (rx-to-string
+ `(seq
+ line-start (0+ space)
+ (0+ (or "abstract" "public") (+ space))
+ "interface" (+ space)
+ (group (regexp ,groovy-symbol-regexp))))
+ "Matches interface names in groovy code.")
+
+(defconst groovy-annotation-regexp
+ (rx "@" symbol-start (+ (or (syntax word) (syntax symbol))) symbol-end)
+ "Match annotation names.")
+
+;; vars
+(defvar groovy-imenu-regexp
+ (list ;; FIXME: removed `groovy-function-regexp', now is using a function.
+ ;;(list "Functions" groovy-function-regexp 2)
+ (list "Classes" groovy-class-regexp 2)
+ (list "Interfaces" groovy-interface-regexp 1)
+ (list "Closures" "def[ \t]+\\([a-zA-Z_][a-zA-Z0-9_]*\\)[ \t]*=[ \t]*{" 1))
+ "Imenu expression for Groovy.")
+
+
+;; For compatibility with Emacs < 24
+(defalias 'groovy-parent-mode
+ (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode))
+
+(defsubst groovy--in-string-p ()
+ "Return t if (point) is in a string."
+ (nth 3 (syntax-ppss)))
+(defsubst groovy--in-string-at-p (pos)
+ "Return t if POS is in a string."
+ (save-excursion
+ (nth 3 (syntax-ppss pos))))
+
+(defun groovy--preceded-by-odd-number-of-backslashes ()
+ "Return non-nil if point is preceded by an odd number of backslashes."
+ (let ((point (- (point) 2))
+ (slashes 0))
+ (while (eq (char-before point) ?\\)
+ (cl-incf slashes)
+ (cl-decf point))
+ (cl-oddp slashes)))
+
+(defvar groovy-font-lock-keywords
+ ;; Annotations are defined with the @interface, which is a keyword:
+ ;; http://groovy-lang.org/objectorientation.html#_annotation
+ ;; but it's nicer to use annotation highlighting.
+ `(("@interface" . groovy-annotation-face)
+ (,(regexp-opt
+ ;; http://docs.groovy-lang.org/latest/html/documentation/#_keywords
+ '("as"
+ "assert"
+ "break"
+ "case"
+ "catch"
+ "class"
+ "const"
+ "continue"
+ "def"
+ "default"
+ "do"
+ "else"
+ "enum"
+ "extends"
+ "finally"
+ "for"
+ "goto"
+ "if"
+ "implements"
+ "import"
+ "in"
+ "instanceof"
+ "interface"
+ "new"
+ "package"
+ "return"
+ "super"
+ "switch"
+ "this"
+ "throw"
+ "throws"
+ "trait"
+ "try"
+ "while"
+ ;; Other strings that we want to highlight as keywords.
+ "abstract"
+ "final"
+ "native"
+ "private"
+ "protected"
+ "public"
+ "static"
+ "synchronized"
+ )
+ 'symbols)
+ . font-lock-keyword-face)
+ ;; Highlight println as a keyword, but don't highlight foo.println.
+ (,(rx (or line-start space)
+ symbol-start (or "print" "printf" "println") symbol-end)
+ . font-lock-keyword-face)
+ ;; Constants
+ (,(regexp-opt '("false" "null" "true") 'symbols)
+ . font-lock-constant-face)
+ (,(rx symbol-start "it" symbol-end)
+ . font-lock-variable-name-face)
+ ;; Annotations
+ (,groovy-annotation-regexp . groovy-annotation-face)
+ ;; highlight types 'Foo', 'char'
+ (,groovy-type-regexp 1 font-lock-type-face)
+ ;; Highlight declarations of the form 'def foo' and 'public void fooBar()'.
+ (groovy-declaration-search 1 font-lock-variable-name-face)
+ ;; Highlight $foo and $foo.bar string interpolation, but not \$foo.
+ (,(lambda (limit)
+ (let ((pattern
+ (rx (not (any "\\"))
+ (group
+ "$" (+ (or (syntax word) (syntax symbol))) symbol-end
+ (? "." (+ (or (syntax word) (syntax symbol))) symbol-end))))
+ res match-data)
+ (save-match-data
+ ;; Search forward for $foo and terminate on the first
+ ;; instance we find that's inside a sring.
+ (while (and
+ (not res)
+ (re-search-forward pattern limit t))
+ (let* ((string-delimiter-pos (nth 8 (syntax-ppss)))
+ (string-delimiter (char-after string-delimiter-pos)))
+ (when (and (groovy--in-string-p)
+ ;; Interpolation does not apply in single-quoted strings.
+ (not (eq string-delimiter ?')))
+ (setq res (point))
+ ;; Set match data to the group we matched.
+ (setq match-data (list (match-beginning 1) (match-end 1)))))))
+ ;; Set match data and return point so we highlight this
+ ;; instance.
+ (when res
+ (set-match-data match-data)
+ res)))
+ (0 font-lock-variable-name-face t))
+ ;; Highlight ${foo} string interpolation.
+ (,(lambda (limit)
+ (let (res start)
+ (while (and
+ (not res)
+ (search-forward "${" limit t))
+ (let* ((string-delimiter-pos (nth 8 (syntax-ppss)))
+ (string-delimiter (char-after string-delimiter-pos))
+ (escaped-p (and (not (eq string-delimiter ?/))
+ (groovy--preceded-by-odd-number-of-backslashes))))
+ (when (and (groovy--in-string-p)
+ ;; Interpolation does not apply in single-quoted strings.
+ (not (eq string-delimiter ?'))
+ (not escaped-p))
+ (setq start (match-beginning 0))
+ (let ((restart-pos (match-end 0)))
+ (let (finish)
+ ;; Search forward for the } that matches the opening {.
+ (while (and (not res) (search-forward "}" limit t))
+ (let ((end-pos (point)))
+ (save-excursion
+ (when (and (ignore-errors (backward-list 1))
+ (= start (1- (point))))
+ (setq res end-pos)))))
+ (unless res
+ (goto-char restart-pos)))))))
+ ;; Set match data and return point so we highlight this
+ ;; instance.
+ (when res
+ (set-match-data (list start res))
+ res)))
+ (0 font-lock-variable-name-face t))
+ (groovy-special-variable-search 1 font-lock-variable-name-face)
+ (groovy-function-name-search 1 font-lock-function-name-face)))
+
+(eval-when-compile
+ ;; http://groovy-lang.org/syntax.html#_shebang_line
+ (defconst groovy-shebang-regex
+ (rx buffer-start "#"))
+ (defconst groovy-triple-double-quoted-string-regex
+ (rx "\"\"\""))
+ (defconst groovy-triple-single-quoted-string-regex
+ (rx "'''"))
+ (defconst groovy-slashy-open-regex
+ ;; /foo/ is a slashy-string, but // or /* are not.
+ (rx "/" (not (any "/" "*"))))
+
+ (defconst groovy-dollar-slashy-open-regex
+ (rx "$/"))
+ (defconst groovy-dollar-slashy-close-regex
+ (rx "/$")))
+
+(defconst groovy-postfix-operator-regex
+ (rx (or "++" "--")))
+
+(defun groovy-special-variable-search (limit)
+ "Search for text marked with `groovy-special-variable' up to LIMIT."
+ (groovy-special-prop-search limit 'groovy-special-variable))
+
+(defun groovy-function-name-search (limit)
+ "Search for text marked with `groovy-special-variable' up to LIMIT."
+ (groovy-special-prop-search limit 'groovy-function-name))
+
+(defun groovy-special-prop-search (limit prop-name)
+ "Search up to LIMIT for text property PROP-NAME."
+ (let* ((pos (point))
+ (chg (next-single-property-change pos prop-name nil limit)))
+ (when (and chg (> chg pos))
+ (goto-char chg)
+ (let ((v (get-text-property chg prop-name)))
+ (set-match-data v)
+ (or v (groovy-special-prop-search limit prop-name))))))
+
+(defun groovy--travel-parameterized-types ()
+ "Pass over <Foo<Bar>> when searching declarations."
+ (let ((count 1)
+ (found t))
+ (while (and (> count 0) found)
+ (setq found (re-search-forward (rx (or ">" "<")) (line-end-position) t))
+ (when found
+ (setq count (if (equal (match-string 0) ">")
+ (1- count)
+ (1+ count)))))))
+
+(defun groovy-variable-assignment-search (limit)
+ "Highlight variable assignments up to LIMIT."
+ (let ((case-fold-search nil)
+ (pos (point))
+ (match (re-search-forward groovy-variable-assignment-regexp limit t)))
+ (when (and match (> match pos))
+ (or (save-excursion
+ (save-match-data
+ (not (string-match
+ (rx-to-string
+ `(seq
+ (regexp ,groovy-annotation-regexp)
+ (* space)
+ (zero-or-one (seq "(" (* (not (any ")")))))
+ eol))
+ (buffer-substring-no-properties (line-beginning-position) (match-beginning 0))))))
+ (groovy-variable-assignment-search limit)))))
+
+(defun groovy-declaration-search (limit)
+ "Find variable declarations up to LIMIT."
+ (remove-text-properties (point)
+ (or limit (point-max))
+ '(groovy-special-variable nil
+ groovy-function-name nil))
+ (let ((pos (point))
+ (case-fold-search nil)
+ (match (re-search-forward groovy-declaration-regexp limit t)))
+ (when (and match (> match pos))
+ (or (and
+ (not (groovy--in-string-p))
+ (not (groovy--comment-p (point)))
+ (let ((match-s (s-trim (match-string 0))))
+ (when (s-ends-with-p "<" match-s)
+ (groovy--travel-parameterized-types))
+ (let ((var-match
+ (re-search-forward
+ (rx-to-string `(seq point (* space)
+ (regexp ,groovy-symbol-regexp)))
+ (line-end-position) t)))
+ ;; matches initial regexp, now look at special cases
+ (if var-match
+ ;; if the var ends in a '(' it's a method name
+ (let ((md (match-data)))
+ (if (re-search-forward (rx point (* space) "(") nil t)
+ (progn
+ (backward-char 1)
+ (set-match-data md)
+ (with-silent-modifications
+ (put-text-property (match-beginning 1) (match-end 1)
+ 'groovy-function-name (match-data)))
+ nil)
+ ;; else if declaration followed by ',' then it's of form `String a, b,c'
+ (unless (or (s-starts-with-p "(" match-s)
+ (s-starts-with-p "{" match-s)
+ (s-starts-with-p "," match-s))
+
+ (with-silent-modifications
+ (while (re-search-forward (rx-to-string
+ `(seq point (* space) "," (* space)
+ (regexp ,groovy-symbol-regexp)))
+ limit t)
+ (put-text-property (match-beginning 1) (match-end 1)
+ 'groovy-special-variable (match-data))))
+ (set-match-data md))
+ t))
+ ;; didn't match regexp, check if it's of form `def (a, b, c) = [1, 2, 3]'
+ (when (re-search-forward
+ (rx-to-string
+ `(seq point (* space) "(" (* space)
+ (group (+ (seq
+ (regexp ,groovy-symbol-regexp)
+ (* space)
+ (opt ",")
+ (* space))))
+ (* space) ")" (* space) "="))
+ limit t)
+ (let ((s (match-string 1))
+ (beg (match-beginning 0))
+ (end (match-end 0)))
+ (when (save-excursion
+ (string-match
+ (rx-to-string
+ `(seq
+ bol
+ (+ (seq
+ (* space)
+ (regexp ,groovy-symbol-regexp)
+ (* space)
+ (or "," eol)))
+ eol))
+ s))
+ (with-silent-modifications
+ (save-excursion
+ (goto-char beg)
+ (while (re-search-forward groovy-symbol-regexp end t)
+ ;; mark list for later search
+ (put-text-property (match-beginning 1) (match-end 1)
+ 'groovy-special-variable (match-data)))))
+ nil)))))))
+ (groovy-declaration-search limit)))))
+
+(defun groovy-stringify-triple-quote ()
+ "Put `syntax-table' property on triple-quoted strings."
+ ;; This applies to both ''' and """
+ (let* ((delim-end-pos (point))
+ (delim-start-pos (- delim-end-pos 3))
+ (ppss (prog2
+ (backward-char 3)
+ (syntax-ppss)
+ (forward-char 3)))
+ (in-comment (nth 4 ppss))
+ (string-start-pos (nth 8 ppss)))
+ (unless in-comment
+ (if string-start-pos
+ (let ((open-delimiter (char-after string-start-pos))
+ (current-delimiter (char-after delim-start-pos)))
+ ;; Ensure that we're closing with the same triple-quote
+ ;; type as we opened with, because '''""""''' is a legal
+ ;; string literal.
+ (when (equal open-delimiter current-delimiter)
+ ;; We're in a string, so this must be the closing triple-quote.
+ ;; Put | on the last ' or " character.
+ (put-text-property (1- delim-end-pos) delim-end-pos
+ 'syntax-table (string-to-syntax "|"))))
+ ;; We're not in a string, so this is the opening triple-quote.
+ ;; Put | on the first ' or " character.
+ (put-text-property delim-start-pos (1+ delim-start-pos)
+ 'syntax-table (string-to-syntax "|"))))))
+
+(defun groovy--comment-p (pos)
+ "Return t if POS is in a comment."
+ (save-excursion
+ (nth 4 (syntax-ppss pos))))
+
+(defun groovy-stringify-slashy-string ()
+ "Put `syntax-table' property on slashy-quoted strings (strings
+of the form /foo/)."
+ (let* ((final-pos (point))
+ ;; We matched two characters starting with "/", e.g. "/x". Point is
+ ;; currently after the "x". Calculate the position just after the "/".
+ (slash-pos (1- (point)))
+ (prev-char (char-before (- slash-pos 1)))
+ (prev-prev-char (char-before (- slash-pos 2)))
+ ;; Look at the previous char: // is a comment, not an empty
+ ;; slashy-string. /foo\// does not contain a comment though.
+ (singleline-comment (and (eq prev-char ?/)
+ (not (eq prev-prev-char ?\\))))
+ ;; Look at this syntax on the previous char: if we're on a /*
+ ;; or a */ this isn't a slashy-string.
+ (multiline-comment (save-excursion
+ (goto-char (1- slash-pos))
+ (groovy--comment-p (point))))
+ (string-open-pos (nth 8 (syntax-ppss slash-pos))))
+
+ (unless (or singleline-comment multiline-comment)
+ (if string-open-pos
+ ;; If we're in a string, that was opened with /, then this
+ ;; is the closing /. This prevents confusion with """ /* """
+ (if (eq (char-after string-open-pos) ?/)
+ (put-text-property (1- slash-pos) slash-pos
+ 'syntax-table (string-to-syntax "|"))
+ ;; We're in a string, but not a slashy string. Ensure we
+ ;; don't put point beyond the /, to avoid confusion with
+ ;; """foo/""".
+ (setq final-pos (1- final-pos)))
+ ;; We're not in a string, so this is the opening / or division.
+ (let ((str (buffer-substring-no-properties (line-beginning-position) slash-pos)))
+ ;; Test if an operator precedes this slash. if so, slashy-string,
+ ;; otherwise it's division so ignore.
+ (when (string-match
+ (rx
+ (or bol
+ (or "+" "-" "=" "+=" "-=" "==" "!="
+ "<" "<=" ">" ">=" "&&" "||" "?" "?:" ":"
+ "=~" "==~" "<=>" "(" "~" ","))
+ (0+ whitespace)
+ "/"
+ eol)
+ str)
+ (put-text-property (1- slash-pos) slash-pos
+ 'syntax-table (string-to-syntax "|"))))))
+ (goto-char final-pos)))
+
+(defun groovy-stringify-dollar-slashy-open ()
+ "Put `syntax-table' property on the opening $/ of
+dollar-slashy-quoted strings."
+ (let ((delimiter-end-pos (point)))
+ (if (or (groovy--comment-p delimiter-end-pos) (groovy--in-string-p))
+ ;; if not $/ open comment then go back a char so we can test
+ ;; if it's a closing slashy-string /
+ (backward-char 1)
+ ;; Mark the $ in $/ as a generic string delimiter.
+ (put-text-property (- delimiter-end-pos 2) (- delimiter-end-pos 1)
+ 'syntax-table (string-to-syntax "|")))))
+
+(defun groovy-stringify-dollar-slashy-close ()
+ "Put `syntax-table' property on the closing /$ of
+dollar-slashy-quoted strings."
+ (let* ((delimiter-end-pos (point))
+ ;; We can't use `syntax-ppss' here as the state may not be
+ ;; set yet. Using `parse-partial-sexp' ensures that the
+ ;; highlighting is correct even when the mode is started
+ ;; initially.
+ (ppss (parse-partial-sexp (point-min) delimiter-end-pos))
+ (in-string (nth 3 ppss))
+ (string-start-pos (nth 8 ppss)))
+ (cond
+ ((groovy--comment-p delimiter-end-pos)
+ ;; Do nothing inside comments.
+ nil)
+ ((not in-string)
+ ;; If we're not in a string, then /$ is the start of a normal
+ ;; slashy-string, e.g. /$ foo/.
+ ;;
+ ;; Note that both `groovy-stringify-dollar-slashy-close' and
+ ;; `groovy-stringify-slashy-string' expect to be two characters
+ ;; after the /, so we don't need to move point before calling.
+ (groovy-stringify-slashy-string))
+ ((looking-back (rx "$/$") 3)
+ ;; Ignore $/$ as it's escaped and not a /$ close delimiter.
+ nil)
+ (t
+ ;; Otherwise, we're in a string.
+ (when (eq (char-after string-start-pos) ?$)
+ ;; If this string opened with $, this is a string of the form
+ ;; $/foo/$. Mark the final $ as a generic string delimiter.
+ (put-text-property (- delimiter-end-pos 1) delimiter-end-pos
+ 'syntax-table (string-to-syntax "|")))))))
+
+
+(defconst groovy-syntax-propertize-function
+ (syntax-propertize-rules
+ ;; Treat the shebang as a comment. We reuse comment sequence b
+ ;; (which is the // comment) so Emacs treats \n as the end of the
+ ;; comment.
+ (groovy-shebang-regex
+ (0 "< b"))
+
+ ;; WARNING: These are a pain to refactor. Emacs tries each one of
+ ;; these regexps in order. It resumes parsing from wherever point
+ ;; is left at the end of the function call.
+ ;;
+ ;; As a result, it's important that these functions move point
+ ;; backwards if they may have moved over another delimiter (e.g. /$
+ ;; and /). However, they must all move point by a non-zero amount,
+ ;; or you get an infinite loop during fontification.
+ ;;
+ ;; The unit tests are pretty thorough, so they should catch any
+ ;; issues.
+ (groovy-triple-double-quoted-string-regex
+ (0 (ignore (groovy-stringify-triple-quote))))
+ (groovy-triple-single-quoted-string-regex
+ (0 (ignore (groovy-stringify-triple-quote))))
+
+ ;; http://groovy-lang.org/syntax.html#_dollar_slashy_string
+ (groovy-dollar-slashy-open-regex
+ (0 (ignore (groovy-stringify-dollar-slashy-open))))
+ (groovy-dollar-slashy-close-regex
+ (0 (ignore (groovy-stringify-dollar-slashy-close))))
+
+ ;; http://groovy-lang.org/syntax.html#_slashy_string
+ (groovy-slashy-open-regex
+ (0 (ignore (groovy-stringify-slashy-string))))))
+
+(defgroup groovy nil
+ "A Groovy major mode."
+ :group 'languages)
+
+(defcustom groovy-indent-offset 4
+ "Indentation amount for Groovy."
+ :type 'natnum
+ :safe #'natnump
+ :group 'groovy)
+
+(defcustom groovy-highlight-assignments nil
+ "Highlight variable assignments after declaration."
+ :type 'boolean
+ :group 'groovy)
+
+(defvar groovy-annotation-face 'groovy-annotation-face)
+(defface groovy-annotation-face
+ '((default :inherit font-lock-constant-face))
+ "Face for highlighting annotations in Groovy mode."
+ :group 'groovy)
+
+(defconst groovy-expression-end-regexp
+ (rx-to-string
+ `(or symbol-end
+ space
+ (syntax string-quote)
+ (syntax string-delimiter)
+ (syntax close-parenthesis)
+ (regexp ,groovy-postfix-operator-regex))))
+
+(defun groovy--ends-with-token-p (token-list str)
+ "Return t if STR ends with one of the tokens in TOKEN-LIST."
+ (string-match-p
+ (rx-to-string
+ `(seq
+ (regexp ,groovy-expression-end-regexp)
+ (or ,@token-list)
+ (0+ space)
+ line-end))
+ str))
+
+(defun groovy--ends-with-infix-p (str)
+ "Does STR end with an infix operator?"
+ (groovy--ends-with-token-p
+ ;; http://docs.groovy-lang.org/next/html/documentation/core-operators.html
+ '("+" "-" "*" "/" "%" "**"
+ "=" "+=" "-=" "*=" "/=" "%=" "**="
+ "==" "!=" "<" "<=" ">" ">=" "<<=" ">>=" ">>>=" "&=" "^=" "|="
+ "&&" "||"
+ "&" "|" "^" "<<" "<<<" ">>" ">>>"
+ "?" "?:"
+ ;; Kludge: require a space before :. This enables us to
+ ;; distinguish labels from ternary calls. Strictly speaking,
+ ;; Groovy does not require a space before :, instead it seems to
+ ;; look for a preceding ?.
+ " :"
+ "=~" "==~"
+ "<=>" "<>"
+ "in" "as")
+ str))
+
+(defun groovy--ends-with-comma-p (str)
+ "Does STR end with a comma?"
+ (groovy--ends-with-token-p
+ '(",")
+ str))
+
+(defun groovy--current-line ()
+ "The current line enclosing point."
+ (buffer-substring-no-properties
+ (line-beginning-position) (line-end-position)))
+
+(defun groovy--enclosing-blocks ()
+ "Return a list of the block keywords that enclose point.
+
+Given the code, where | is point:
+
+def foo() {
+ if (bar) {
+ switch (baz) {
+ |
+ }
+ }
+}
+
+Then this function returns (\"def\" \"if\" \"switch\")."
+ (let (enclosing-paren-pos blocks)
+ (save-excursion
+ (setq enclosing-paren-pos (nth 1 (syntax-ppss)))
+ (while enclosing-paren-pos
+ (goto-char enclosing-paren-pos)
+ (let* ((line (s-trim (groovy--current-line)))
+ (symbols (s-split (rx symbol-end) line t))
+ (symbol (car-safe symbols)))
+ (push symbol blocks))
+ (setq enclosing-paren-pos (nth 1 (syntax-ppss)))))
+ blocks))
+
+(defconst groovy--case-regexp
+ (rx (or
+ (seq "case" symbol-end (+ any))
+ (seq "default" symbol-end))
+ ":"))
+
+(defun groovy--remove-comments (src)
+ "Remove all comments from a string SRC of groovy source code."
+ (->> src
+ (replace-regexp-in-string (rx "/*" (*? anything) "*/") "")
+ (replace-regexp-in-string (rx "//" (* not-newline)) "")))
+
+(defun groovy--effective-paren-depth (pos)
+ "Return the indentation paren depth of position POS.
+Only one opening paren per source code line is counted."
+ (let ((paren-depth 0)
+ (syntax (syntax-ppss pos))
+ (current-line (line-number-at-pos pos)))
+ (save-excursion
+ ;; Keep going whilst we're inside parens.
+ (while (> (nth 0 syntax) 0)
+ ;; Go to the most recent enclosing open paren.
+ (goto-char (nth 1 syntax))
+
+ ;; Count this paren, but only if it was on another line.
+ (let ((new-line (line-number-at-pos (point))))
+ (unless (= new-line current-line)
+ (setq paren-depth (1+ paren-depth))
+ (setq current-line new-line)))
+
+ (setq syntax (syntax-ppss (point)))))
+ paren-depth))
+
+(defun groovy--extract-line-without-comments ()
+ "Extracts the part of the current line that is not a comment."
+ (let (code-text
+ (start-pos (line-beginning-position))
+ (end-pos (line-end-position)))
+ ;; Unless this line is already inside a multiline comment,
+ ;; use parse-partial-sexp to get the part of the line not
+ ;; a part of a comment.
+ (unless (groovy--comment-p start-pos)
+ (save-excursion
+ (parse-partial-sexp start-pos end-pos nil nil nil t)
+ (setq code-text (buffer-substring start-pos (point)))
+
+ ;; Unless we went all the way to the end of the line, we
+ ;; encountered a comment delimiter //, /* or #. Remove this delimiter.
+ (unless (= (point) end-pos)
+ (let ((delims '("//" "/*" "#")))
+ (setq code-text (replace-regexp-in-string
+ (rx-to-string `(seq (or ,@delims) line-end)) "" code-text))))))
+
+ ;; Return the part of the line that isn't a comment (may be nil).
+ code-text))
+
+(defun groovy--backwards-to-prev-code-line ()
+ "Move point to the previous non-comment line, and return its contents."
+ (catch 'done
+ (let (code-text)
+ (while t
+ ;; Move backwards one line, or throw 'done if we're at the
+ ;; beginning of the buffer.
+ (unless (zerop (forward-line -1))
+ (throw 'done nil))
+
+ ;; Get the part of the line that isn't in a comment.
+ ;; If this isn't just white space, return it as a code line.
+ (setq code-text (groovy--extract-line-without-comments))
+ (unless (s-blank-str-p code-text)
+ (throw 'done code-text))))))
+
+(defun groovy--line-ends-with-incomplete-block-statement-p ()
+ "Return t if the current line ends with an incomplete block
+statement, without an open curly brace."
+ (unless (groovy--in-string-p)
+ (save-excursion
+ (end-of-line)
+ ;; Search backwards until we are no longer in a comment.
+ (while (and (not (bolp)) (groovy--comment-p (point)))
+ (re-search-backward "/[/*]" (line-beginning-position) t))
+ (unless (groovy--comment-p (point))
+ (skip-chars-backward (rx blank))
+ ;; We should be at the end of the actual code line. We are looking
+ ;; for a line that ends with either if|while|for followed by
+ ;; (condition), or a lone else.
+ (if (eq (char-before) ?\))
+ (when (condition-case nil (progn (backward-sexp) t) (scan-error nil))
+ (skip-chars-backward (rx blank))
+ (string-match (rx symbol-start (group (or "if" "for" "while")) line-end)
+ (buffer-substring-no-properties (line-beginning-position) (point))))
+ (string-match (rx symbol-start "else" line-end)
+ (buffer-substring-no-properties (line-beginning-position) (point))))))))
+
+(defun groovy--trim-current-line ()
+ "Return the current code line trimmed and without comments."
+ (s-trim (groovy--remove-comments (groovy--current-line))))
+
+(defun groovy-indent-line ()
+ "Indent the current line according to the number of parentheses."
+ (interactive)
+ (let* ((point-offset (- (current-column) (current-indentation)))
+ (syntax-bol (syntax-ppss (line-beginning-position)))
+ (multiline-string-p (nth 3 syntax-bol))
+ (multiline-comment-p (nth 4 syntax-bol))
+ (current-paren-depth (groovy--effective-paren-depth (line-beginning-position)))
+ (current-paren-pos (nth 1 syntax-bol))
+ (current-paren-character
+ (when (nth 1 syntax-bol) (char-after (nth 1 syntax-bol))))
+ (text-after-paren
+ (when current-paren-pos
+ (save-excursion
+ (goto-char current-paren-pos)
+ (s-trim
+ (groovy--remove-comments
+ (buffer-substring
+ (1+ current-paren-pos)
+ (line-end-position)))))))
+ (current-line (s-trim (groovy--current-line)))
+ has-closing-paren)
+ ;; If this line starts with a closing paren, unindent by one level.
+ ;; if {
+ ;; } <- this should not be indented.
+ (when (or (s-starts-with-p "}" current-line)
+ (s-starts-with-p ")" current-line)
+ (s-starts-with-p "]" current-line))
+ (setq has-closing-paren t)
+ (setq current-paren-depth (1- current-paren-depth)))
+
+ ;; `current-paren-depth' should never be negative, unless the code
+ ;; contains unbalanced parens. Ensure we handle that robustly.
+ (when (< current-paren-depth 0)
+ (setq current-paren-depth 0))
+
+ (cond
+ ;; Don't try to indent the line if we're in a multiline string.
+ (multiline-string-p 'noindent)
+ ;; Ensure we indent
+ ;; /*
+ ;; * foo
+ ;; */
+ ;; correctly.
+ (multiline-comment-p
+ (indent-line-to (1+ (* groovy-indent-offset current-paren-depth))))
+
+ ;; Ensure we indent
+ ;; def x = [1,
+ ;; 2,
+ ;; ]
+ ;; correctly.
+ ((and (not (s-blank-str? text-after-paren))
+ (not has-closing-paren)
+ ;; ensure we don't indent closures
+ (not (string-match (rx "->" eol) text-after-paren)))
+ (let (open-paren-column)
+ (save-excursion
+ (goto-char current-paren-pos)
+ (setq open-paren-column (current-column)))
+ (indent-line-to (1+ open-paren-column))))
+
+ ;; Indent according to the number of parens.
+ (t
+ (let ((indent-level current-paren-depth))
+ ;; If the previous line ended with an arithmetic operator like
+ ;; `foo +`, then this line should be indented one more level.
+ (save-excursion
+ (let* ((prev-line (groovy--backwards-to-prev-code-line))
+ (line-end (line-end-position))
+ ;; Check if the last thing is a slashy-string end, so we
+ ;; distinguish a string `/foo bar/` from arithmetic `x /`.
+ (end-slashy-string (and
+ prev-line
+ (eq (char-before line-end) ?/)
+ (groovy--in-string-at-p (- line-end 1)))))
+ (when (and
+ prev-line
+ (not end-slashy-string)
+ (not (s-matches-p groovy--case-regexp prev-line))
+ (or (groovy--ends-with-infix-p prev-line)
+ (and (groovy--ends-with-comma-p prev-line)
+ (not (memq current-paren-character (list ?\[ ?\()))
+ (not has-closing-paren))))
+ (setq indent-level (1+ indent-level)))))
+
+ ;; If the previous lines are block statements (if, for, while, else)
+ ;; without the optional curly brace and without a body, then indent
+ ;; for each block.
+ (save-excursion
+ (let (next-line line-after)
+ (while
+ (and
+ (setq line-after (groovy--trim-current-line))
+ (groovy--backwards-to-prev-code-line)
+
+ ;; Handle the special case of a chain of if-else statements, e.g.
+ ;; if (x) foo()
+ ;; else if (y) bar()
+ ;; else baz()
+ (progn
+ (while
+ (let ((cur-line (groovy--trim-current-line)))
+ (and (s-starts-with-p "else" line-after)
+ (string-match (rx line-start (optional "else" (+ blank)) "if" symbol-end) cur-line)
+ (groovy--backwards-to-prev-code-line)
+ (setq line-after cur-line))))
+ t)
+
+ (groovy--line-ends-with-incomplete-block-statement-p))
+ (setq indent-level (1+ indent-level)))))
+
+ ;; If this line is .methodCall() then we should indent one
+ ;; more level.
+ (when (s-starts-with-p "." current-line)
+ (setq indent-level (1+ indent-level)))
+
+ ;; If we're inside a switch statement, we should indent
+ ;; another level after case labels, e.g.
+ ;; case foo:
+ ;; bar // <- extra indent
+ (let ((blocks (groovy--enclosing-blocks))
+ (switch-count 0))
+ (dolist (block-symbol blocks)
+ (when (equal block-symbol "switch")
+ (setq switch-count (1+ switch-count)))
+ ;; Ensure we indent closures by one extra level.
+ ;;
+ ;; [1, 2, 3]
+ ;; .findAll { // <- indented
+ (when (s-starts-with-p "." block-symbol)
+ (setq indent-level (1+ indent-level))))
+
+ (when (> switch-count 0)
+ (setq indent-level (+ indent-level switch-count))
+ ;; The `case foo:' line should be indented less than the body.
+ (when (s-matches-p groovy--case-regexp current-line)
+ (setq indent-level (1- indent-level)))
+ ;; The extra indent does not apply to the } closing the
+ ;; switch block.
+ (when (and
+ (equal (car (last blocks)) "switch")
+ (equal current-line "}"))
+ (setq indent-level (1- indent-level)))))
+
+ (indent-line-to (* groovy-indent-offset indent-level)))))
+ ;; Point is now at the beginning of indentation, restore it
+ ;; to its original position (relative to indentation).
+ (when (>= point-offset 0)
+ (move-to-column (+ (current-indentation) point-offset)))))
+
+;;;###autoload
+(define-derived-mode groovy-mode groovy-parent-mode "Groovy"
+ "Major mode for editing Groovy code.
+
+The hook `groovy-mode-hook' is run with no args at mode
+initialization.
+
+Key bindings:
+\\{groovy-mode-map}"
+ ;; if `groovy-highlight-assignments' add to keyword search.
+ (when groovy-highlight-assignments
+ (add-to-list 'groovy-font-lock-keywords
+ '(groovy-variable-assignment-search 1 font-lock-variable-name-face) t))
+ (set (make-local-variable 'font-lock-defaults)
+ '(groovy-font-lock-keywords))
+
+ ;; set electric characters
+ (setq-local electric-indent-chars
+ (append "{}():;,[]" electric-indent-chars))
+
+ (set (make-local-variable 'syntax-propertize-function)
+ groovy-syntax-propertize-function)
+ (setq imenu-generic-expression groovy-imenu-regexp)
+ (set (make-local-variable 'indent-line-function) #'groovy-indent-line)
+ (set (make-local-variable 'comment-start) "//")
+ (set (make-local-variable 'comment-start-skip)
+ (rx (or "//" "/*" "/**" "/**@") (zero-or-more space))))
+
+(provide 'groovy-mode)
+
+;;; groovy-mode.el ends here
--- /dev/null
+;;; inf-groovy.el --- inferior Groovy mode – groovy process in a buffer
+
+;;; Inferior Groovy Mode – groovy process in a buffer.
+;;; adapted from cmuscheme.el and inf-haskell.el
+
+;; Copyright (c) 2006, 2010 Stuart Clayman
+;; Copyright © 2015–2017 Russel WInder
+
+;; Author: Stuart Clayman <sclaymanNOSPAMee.ucl.ac.uk>
+;;; 2006-08-01 v1
+;;; 2010-04-07 v2 fixes for new groovy-mode
+;; Maintainer: Russel Winder <russelNOSPAMwinder.org.uk>
+;; Version: 201705161954
+;; Keywords: languages
+;; Package-Requires: ((groovy-mode "0"))
+
+;;;; NB Version number is date and time yyyymmddhhMM UTC.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Usage:
+;; If you install using the packaging system no further set up should be needed. If you install this mode
+;; manually then you will likely need to add some extra lines in your init file:
+;;
+;; (1) modify .emacs to use groovy-mode
+;; for example :
+;;
+;; (autoload 'groovy-mode "groovy-mode" "Mode for editing groovy source files" t)
+;; (setq auto-mode-alist
+;; (append '(("\\.groovy\\'" . groovy-mode)) auto-mode-alist))
+;; (setq interpreter-mode-alist (append '(("groovy" . groovy-mode))
+;; interpreter-mode-alist))
+;;
+;; (2) set to load inf-groovy and set inf-groovy key definition in groovy-mode.
+;;
+;; (autoload 'groovy-mode "groovy-mode" "Groovy mode." t)
+;; (autoload 'run-groovy "inf-groovy" "Run an inferior Groovy process")
+;; (autoload 'inf-groovy-keys "inf-groovy" "Set local key defs for inf-groovy in groovy-mode")
+;;
+;; (add-hook 'groovy-mode-hook
+;; '(lambda ()
+;; (inf-groovy-keys)
+;; ))
+;;
+;; (3) If groovysh isn't on $PATH and you haven't set $GROOVY_HOME,
+;; you need to tell Emacs where groovysh is.
+;;
+;; (setq groovysh "/Users/sclayman/Downloads/groovy-1.7.1/bin/groovysh")
+;;
+;; (4) execute
+;; M-x run-groovy
+
+;;; Commentary:
+
+;;; Bugs:
+;; Bug tracking is currently handled using the GitHub issue tracker at
+;; https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/issues
+
+;;; Versions:
+;; This mode is available on MELPA which tracks the mainline Git repository on GitHub, so there is a rolling release
+;; system based on commits to the mainline.
+
+;;; Notes:
+
+;;; TODO:
+;; Issues with this code are managed via the project issue management
+;; on GitHub: https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/issues?state=open
+
+;;; History
+;; History is tracked in the Git repository rather than in this file.
+;; See https://github.com/Groovy-Emacs-Modes/groovy-emacs-modes/commits/master
+
+;;----------------------------------------------------------------------------
+;;; Code:
+
+(require 'comint)
+(require 'compile)
+(require 'groovy-mode)
+(require 'cc-cmds)
+
+(defcustom groovysh
+ (or
+ ;; If groovysh in on $PATH, use that by default.
+ (executable-find "groovysh")
+ ;; Fall back to $GROOVY_HOME if the user has set that.
+ (executable-find
+ (format "%s/bin/groovysh" (or (getenv "GROOVY_HOME") ""))))
+ "The path to the groovysh binary, used by `run-groovy'."
+ :type 'string
+ :group 'groovy)
+
+(defcustom groovysh-args
+ "--color=false"
+ "Arguments passed to groovysh when starting an inferior groovy buffer."
+ :type 'string
+ :group 'groovy)
+
+;;;; for groovy
+(defvar inferior-groovy-first-prompt-pattern "^groovy:.*> *"
+ "First prompt regex pattern of groovy interpreter.")
+
+(defvar inferior-groovy-prompt-pattern "^groovy:.*> *"
+ "Prompt regex pattern of groovy interpreter.")
+
+;;
+;; mode variables
+;;
+(defvar inferior-groovy-mode-hook nil
+ "*Hook for customising inferior-groovy mode.")
+
+(defvar inferior-groovy-mode-map nil
+ "*Mode map for inferior-groovy-mode.")
+
+(defconst inferior-groovy-error-regexp-alist
+ '(("SyntaxError: compile error\n^\\([^\(].*\\):\\([1-9][0-9]*\\):" 1 2)
+ ("^\tfrom \\([^\(].*\\):\\([1-9][0-9]*\\)\\(:in `.*'\\)?$" 1 2)))
+
+(cond ((not inferior-groovy-mode-map)
+ (setq inferior-groovy-mode-map
+ (copy-keymap comint-mode-map))
+ (define-key inferior-groovy-mode-map "\C-c\C-l" 'groovy-load-file)
+ (define-key inferior-groovy-mode-map "\C-c\C-m" 'inferior-groovy-newline)))
+
+;;;###autoload
+(defun inf-groovy-keys ()
+ "Set local key defs for inf-groovy in `groovy-mode'."
+ (define-key groovy-mode-map "\M-\C-x" 'groovy-send-definition)
+ (define-key groovy-mode-map "\C-x\C-e" 'groovy-send-last-sexp)
+ (define-key groovy-mode-map "\C-c\M-d" 'groovy-send-definition)
+ (define-key groovy-mode-map "\C-c\C-x" 'groovy-send-definition-and-go)
+ (define-key groovy-mode-map "\C-c\M-r" 'groovy-send-region)
+ (define-key groovy-mode-map "\C-c\C-r" 'groovy-send-region-and-go)
+ (define-key groovy-mode-map "\C-c\C-z" 'switch-to-groovy)
+ (define-key groovy-mode-map "\C-c\C-l" 'groovy-load-file)
+ (define-key groovy-mode-map "\C-c\C-s" 'run-groovy))
+
+(defvar groovy-buffer nil "Current groovy (actually groovysh) process buffer.")
+
+;;;###autoload
+(defun inferior-groovy-mode ()
+ "Major mode for interacting with an inferior groovy (groovysh) process.
+
+The following commands are available:
+\\{inferior-groovy-mode-map}
+
+A groovy process can be fired up with \\[run-groovy].
+
+Customisation: Entry to this mode runs the hooks on
+`comint-mode-hook' and `inferior-groovy-mode-hook' (in that order).
+
+You can send text to the inferior groovy process from other buffers containing
+Groovy source.
+ `switch-to-groovy' switches the current buffer to the groovy process buffer.
+ `groovy-send-definition' sends the current definition to the groovy process.
+ `groovy-send-region' sends the current region to the groovy process.
+
+ `groovy-send-definition-and-go', `groovy-send-region-and-go',
+ switch to the groovy process buffer after sending their text.
+For information on running multiple processes in multiple buffers, see
+documentation for variable `groovy-buffer'.
+
+Commands:
+Return after the end of the process' output sends the text from the
+ end of process to point.
+Return before the end of the process' output copies the sexp ending at point
+ to the end of the process' output, and sends it.
+Delete converts tabs to spaces as it moves back.
+Tab indents for groovy; with argument, shifts rest
+ of expression rigidly with the current line.
+\\<groovy-mode-map>
+\\[prog-indent-sexp] does Tab on each line starting within following expression.
+Paragraphs are separated only by blank lines. # start comments.
+If you accidentally suspend your process, use \\[comint-continue-subjob]
+to continue it."
+ (interactive)
+ (comint-mode)
+ ;; Customise in inferior-groovy-mode-hook
+ (setq comint-prompt-regexp inferior-groovy-prompt-pattern)
+ (setq major-mode 'inferior-groovy-mode)
+ (setq mode-name "Inferior Groovy")
+ (setq mode-line-process '(":%s"))
+ (use-local-map inferior-groovy-mode-map)
+ (define-key inferior-groovy-mode-map "\C-c\C-m" 'inferior-groovy-newline)
+ (setq comint-input-filter (function groovy-input-filter))
+ (setq comint-get-old-input (function groovy-get-old-input))
+ (setq comint-use-prompt-regexp t) ;; added v2
+ (setq comint-process-echoes t) ;; added v2
+ (setq comint-eol-on-send t) ;; added v2
+ (compilation-shell-minor-mode t)
+ (make-local-variable 'compilation-error-regexp-alist)
+ (setq compilation-error-regexp-alist inferior-groovy-error-regexp-alist)
+ (run-hooks 'inferior-groovy-mode-hook))
+
+(defvar inferior-groovy-filter-regexp "\\`\\s *\\S ?\\S ?\\s *\\'"
+ "*Input matching this regexp are not saved on the history list.
+Defaults to a regexp ignoring all inputs of 0, 1, or 2 letters.")
+
+(defun inferior-groovy-newline ()
+ (interactive)
+ (comint-send-input)
+ (let ((proc (groovy-proc)))
+ (comint-send-string proc "\n")))
+
+(defun groovy-input-filter (str)
+ "Don't save anything matching `inferior-groovy-filter-regexp'."
+ (not (string-match inferior-groovy-filter-regexp str)))
+
+;; adapted from replace-in-string in XEmacs (subr.el)
+(defun remove-in-string (str regexp)
+ "Remove all matches in STR for REGEXP and return the new string."
+ (let ((rtn-str "") (start 0) match prev-start)
+ (while (setq match (string-match regexp str start))
+ (setq prev-start start
+ start (match-end 0)
+ rtn-str (concat rtn-str (substring str prev-start match))))
+ (concat rtn-str (substring str start))))
+
+(defun groovy-get-old-input ()
+ "Snarf the sexp ending at point."
+ (save-excursion
+ (let ((end (point)))
+ (re-search-backward inferior-groovy-first-prompt-pattern)
+ (remove-in-string (buffer-substring (point) end)
+ inferior-groovy-prompt-pattern))))
+
+(defun groovy-args-to-list (string)
+ (let ((where (string-match "[ \t]" string)))
+ (cond ((null where) (list string))
+ ((not (= where 0))
+ (cons (substring string 0 where)
+ (groovy-args-to-list (substring string (+ 1 where)
+ (length string)))))
+ (t (let ((pos (string-match "[^ \t]" string)))
+ (if (null pos)
+ nil
+ (groovy-args-to-list (substring string pos
+ (length string)))))))))
+
+;;;###autoload
+(defun run-groovy (cmd)
+ "Run an inferior Groovy process, input and output via buffer *groovy*.
+If there is a process already running in *groovy*, switch to that buffer.
+With a prefix argument, prompt for the groovysh path and arguments
+\(see variables `groovysh' and `groovysh-args' for the defaults).
+
+Runs the hook `inferior-groovy-mode-hook' (after the
+`comint-mode-hook' is run). Type \\[describe-mode] in the
+process buffer for a list of commands."
+ (interactive (list
+ (format "%s %s"
+ (if current-prefix-arg
+ (read-file-name "groovysh binary: " groovysh)
+ groovysh)
+ (if current-prefix-arg
+ (read-string "groovysh arguments: " groovysh-args)
+ groovysh-args))))
+ (if (not (comint-check-proc "*groovy*"))
+ (let ((cmdlist (groovy-args-to-list cmd)))
+ (set-buffer (apply 'make-comint "groovy" (car cmdlist)
+ nil (cdr cmdlist)))
+ (inferior-groovy-mode)))
+ (setq groovy-buffer "*groovy*")
+ (pop-to-buffer "*groovy*")
+ (get-buffer-process groovy-buffer))
+
+
+(defun groovy-proc ()
+ "Return the current groovy process. See variable `groovy-buffer'."
+ (let ((proc (get-buffer-process (if (eq major-mode 'inferior-groovy-mode)
+ (current-buffer)
+ groovy-buffer))))
+ (or proc
+ (call-interactively 'run-groovy))))
+
+(defun groovy-send-region (start end)
+ "Send the current region to the inferior Groovy process."
+ (interactive "r")
+
+ (save-excursion
+ (save-restriction
+ (let ((str (concat (buffer-substring start end) "\n"))
+ (proc (groovy-proc)))
+
+ (with-current-buffer (process-buffer proc)
+ (while (and
+ (goto-char comint-last-input-end)
+ (not (re-search-forward comint-prompt-regexp nil t))
+ (accept-process-output proc)))
+ (goto-char (process-mark proc))
+ (insert-before-markers str)
+ (move-marker comint-last-input-end (point))
+ (comint-send-string proc str)
+ (comint-send-string proc "\n"))))))
+
+(defun groovy-send-string (str)
+ "Send a string STR to the inferior Groovy process."
+ (interactive "r")
+
+ (save-excursion
+ (save-restriction
+ (let ((proc (groovy-proc)))
+
+ (with-current-buffer (process-buffer proc)
+ (while (and
+ (goto-char comint-last-input-end)
+ (not (re-search-forward comint-prompt-regexp nil t))
+ (accept-process-output proc)))
+ (goto-char (process-mark proc))
+ (insert-before-markers str)
+ (move-marker comint-last-input-end (point))
+ (comint-send-string proc str)
+ (comint-send-string proc "\n"))))))
+
+(defun groovy-send-definition ()
+ "Send the current definition to the inferior Groovy process."
+ (interactive)
+ (save-excursion
+ (c-end-of-defun)
+ (let ((end (point)))
+ (c-beginning-of-defun)
+ (groovy-send-region (point) end))))
+
+(defun groovy-send-last-sexp ()
+ "Send the previous sexp to the inferior Groovy process."
+ (interactive)
+ (groovy-send-region (save-excursion (backward-sexp) (point)) (point)))
+
+(defun switch-to-groovy (eob-p)
+ "Switch to the groovy process buffer.
+With argument, positions cursor at end of buffer."
+ (interactive "P")
+ (if (get-buffer groovy-buffer)
+ (pop-to-buffer groovy-buffer)
+ (error "No current process buffer, see variable `groovy-buffer'"))
+ (cond (eob-p
+ (push-mark)
+ (goto-char (point-max)))))
+
+(defun groovy-send-string-and-go (str)
+ "Send the string STR to the inferior Groovy process.
+Then switch to the process buffer."
+ (interactive "r")
+ (groovy-send-string str)
+
+ (switch-to-groovy t))
+
+(defun groovy-send-region-and-go (start end)
+ "Send the current region to the inferior Groovy process.
+Then switch to the process buffer."
+ (interactive "r")
+ (groovy-send-region start end)
+
+ (switch-to-groovy t))
+
+(defun groovy-send-definition-and-go ()
+ "Send the current definition to the inferior Groovy.
+Then switch to the process buffer."
+ (interactive)
+ (groovy-send-definition)
+ (switch-to-groovy t))
+
+(defvar groovy-source-modes '(groovy-mode)
+ "*Used to determine if a buffer contains Groovy source code.
+If it's loaded into a buffer that is in one of these major modes, it's
+considered a groovy source file by groovy-load-file.
+Used by these commands to determine defaults.")
+
+(defvar groovy-prev-l/c-dir/file nil
+ "Caches the last (directory . file) pair.
+Caches the last pair used in the last groovy-load-file command.
+Used for determining the default in the
+next one.")
+
+(defun groovy-load-file (file-name)
+ "Load a Groovy file into the inferior Groovy process."
+ (interactive (comint-get-source "Load Groovy file: " groovy-prev-l/c-dir/file
+ groovy-source-modes t)) ; T because LOAD
+ ; needs an exact name
+ (comint-check-source file-name) ; Check to see if buffer needs saved.
+ (setq groovy-prev-l/c-dir/file (cons (file-name-directory file-name)
+ (file-name-nondirectory file-name)))
+ (comint-send-string (groovy-proc) (concat ":l "
+ file-name
+ "\n")))
+
+;;; Do the user's customisation...
+
+(defvar inf-groovy-load-hook nil
+ "This hook is run when inf-groovy is loaded in.
+This is a good place to put keybindings.")
+
+(run-hooks 'inf-groovy-load-hook)
+
+;;;###autoload
+(eval-after-load 'groovy-mode
+ (lambda ()
+ (add-hook 'groovy-mode-hook 'inf-groovy-keys)))
+
+;;----------------------------------------------------------------------------
+
+(provide 'inf-groovy)
+
+;;; inf-groovy.el ends here
--- /dev/null
+;;; jenkinsfile-mode-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*-
+;;
+;;; Code:
+
+(add-to-list 'load-path (directory-file-name
+ (or (file-name-directory #$) (car load-path))))
+
+\f
+;;;### (autoloads nil "jenkinsfile-mode" "jenkinsfile-mode.el" (0
+;;;;;; 0 0 0))
+;;; Generated autoloads from jenkinsfile-mode.el
+
+(autoload 'jenkinsfile-mode "jenkinsfile-mode" "\
+Major mode for editing Jenkins declarative pipeline files.
+
+\(fn)" t nil)
+
+(add-to-list 'auto-mode-alist '("Jenkinsfile\\'" . jenkinsfile-mode))
+
+(register-definition-prefixes "jenkinsfile-mode" '("jenkinsfile-mode-"))
+
+;;;***
+\f
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; coding: utf-8
+;; End:
+;;; jenkinsfile-mode-autoloads.el ends here
--- /dev/null
+;;; Generated package description from jenkinsfile-mode.el -*- no-byte-compile: t -*-
+(define-package "jenkinsfile-mode" "20221124.30" "Major mode for editing Jenkins declarative pipeline syntax" '((emacs "24") (groovy-mode "2.0")) :commit "1d90c1ff8edc7ea88844af92a206e7c5f083b568" :url "https://github.com/john2x/jenkinsfile-mode")
--- /dev/null
+;;; jenkinsfile-mode.el --- Major mode for editing Jenkins declarative pipeline syntax -*- lexical-binding: t -*-
+
+;; Copyright (c) 2019 John Louis Del Rosario
+;; Package-Requires: ((emacs "24") (groovy-mode "2.0"))
+;; Package-Commit: 1d90c1ff8edc7ea88844af92a206e7c5f083b568
+;; Package-Version: 20221124.30
+;; Package-X-Original-Version: 0.0.1
+;; Homepage: https://github.com/john2x/jenkinsfile-mode
+;;
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Provides a major mode `jenkinsfile-mode' (derived from groovy-mode)
+;; for editing Jenkins declarative pipeline files.
+
+;;; Code:
+
+(require 'groovy-mode)
+
+(defcustom jenkinsfile-mode-vim-source-url
+ "https://raw.githubusercontent.com/martinda/Jenkinsfile-vim-syntax/master/syntax/Jenkinsfile.vim"
+ "URL to Jenkinsfile.vim source file."
+ :group 'jenkinsfile-mode
+ :type 'string)
+
+(defcustom jenkinsfile-mode-indent-offset 4
+ "Indentation width to use."
+ :group 'jenkinsfile-mode
+ :type 'integer)
+
+(defvar jenkinsfile-mode--file-section-keywords
+ '("pipeline" "agent" "stages" "steps" "post"))
+
+(defvar jenkinsfile-mode--directive-keywords
+ '("environment" "options" "parameters" "triggers" "stage" "tools" "input" "when" "libraries"))
+
+(defvar jenkinsfile-mode--option-keywords
+ '("contained" "buildDiscarder"
+ "disableConcurrentBuilds" "overrideIndexTriggers"
+ "skipDefaultCheckout" "nextgroup=jenkinsfileOptionParams" "contained"
+ "skipStagesAfterUnstable" "checkoutToSubdirectory" "timeout" "retry"
+ "timestamps" "nextgroup=jenkinsfileOptionParams"))
+
+(defvar jenkinsfile-mode--core-step-keywords
+ '("checkout" "docker"
+ "dockerfile" "skipwhite" "nextgroup=jenkinsFileDockerConfigBlock"
+ "node" "scm" "sh" "stage" "parallel" "steps" "step" "tool" "always"
+ "changed" "failure" "success" "unstable" "aborted" "unsuccessful"
+ "regression" "fixed" "cleanup"))
+
+(defvar jenkinsfile-mode--pipeline-step-keywords
+ '("Applitools"
+ "ArtifactoryGradleBuild" "Consul" "MavenDescriptorStep" "OneSky"
+ "VersionNumber" "ViolationsToBitbucketServer" "ViolationsToGitHub"
+ "ViolationsToGitLab" "_OcAction" "_OcContextInit" "_OcWatch"
+ "acceptGitLabMR" "acsDeploy" "activateDTConfiguration" "addBadge"
+ "addErrorBadge" "addGitLabMRComment" "addInfoBadge"
+ "addInteractivePromotion" "addShortText" "addWarningBadge" "allure"
+ "anchore" "androidApkMove" "androidApkUpload" "androidLint"
+ "ansiColor" "ansiblePlaybook" "ansibleTower" "ansibleVault"
+ "appMonBuildEnvironment" "appMonPublishTestResults"
+ "appMonRegisterTestRun" "applatix" "approveReceivedEvent"
+ "approveRequestedEvent" "aqua" "archive" "archiveArtifacts"
+ "arestocats" "artifactResolver" "artifactoryDistributeBuild"
+ "artifactoryDownload" "artifactoryMavenBuild"
+ "artifactoryPromoteBuild" "artifactoryUpload" "awaitDeployment"
+ "awaitDeploymentCompletion" "awsCodeBuild" "awsIdentity" "azureCLI"
+ "azureDownload" "azureFunctionAppPublish" "azureUpload"
+ "azureVMSSUpdate" "azureVMSSUpdateInstances" "azureWebAppPublish"
+ "backlogPullRequest" "bat" "bearychatSend" "benchmark"
+ "bitbucketStatusNotify" "blazeMeterTest" "build" "buildBamboo"
+ "buildImage" "bzt" "cache" "catchError" "cbt" "cbtScreenshotsTest"
+ "cbtSeleniumTest" "cfInvalidate" "cfnCreateChangeSet" "cfnDelete"
+ "cfnDeleteStackSet" "cfnDescribe" "cfnExecuteChangeSet" "cfnExports"
+ "cfnUpdate" "cfnUpdateStackSet" "cfnValidate" "changeAsmVer"
+ "checkstyle" "chefSinatraStep" "cifsPublisher" "cleanWs" "cleanup"
+ "cloudshareDockerMachine" "cm" "cmake" "cmakeBuild" "cobertura"
+ "codefreshLaunch" "codefreshRun" "codescene" "codesonar" "collectEnv"
+ "conanAddRemote" "conanAddUser" "configFileProvider" "container"
+ "containerLog" "contrastAgent" "contrastVerification" "copy"
+ "copyArtifacts" "coverityResults" "cpack" "createDeploymentEvent"
+ "createEnvironment" "createEvent" "createMemoryDump" "createSummary"
+ "createThreadDump" "crxBuild" "crxDeploy" "crxDownload" "crxReplicate"
+ "crxValidate" "ctest" "ctmInitiatePipeline" "ctmPostPiData"
+ "ctmSetPiData" "cucumber" "cucumberSlackSend" "currentNamespace"
+ "debianPbuilder" "deleteDir" "dependencyCheckAnalyzer"
+ "dependencyCheckPublisher" "dependencyCheckUpdateOnly"
+ "dependencyTrackPublisher" "deployAPI" "deployArtifacts"
+ "deployLambda" "dingding" "dir" "disk" "dockerFingerprintFrom"
+ "dockerFingerprintRun" "dockerNode" "dockerPullStep" "dockerPushStep"
+ "dockerPushWithProxyStep" "doktor" "downloadProgetPackage"
+ "downstreamPublisher" "dropbox" "dry" "ec2" "ec2ShareAmi" "echo"
+ "ecrLogin" "emailext" "emailextrecipients" "envVarsForTool" "error"
+ "evaluateGate" "eventSourceLambda" "executeCerberusCampaign"
+ "exportPackages" "exportProjects" "exws" "exwsAllocate" "figlet"
+ "fileExists" "fileOperations" "findFiles" "findbugs" "fingerprint"
+ "flywayrunner" "ftp" "ftpPublisher" "gatlingArchive"
+ "getArtifactoryServer" "getContext" "getLastChangesPublisher" "git"
+ "gitbisect" "githubNotify" "gitlabBuilds" "gitlabCommitStatus"
+ "googleCloudBuild" "googleStorageDownload" "googleStorageUpload"
+ "gprbuild" "greet" "hipchatSend" "http" "httpRequest" "hub_detect"
+ "hub_scan" "hub_scan_failure" "hubotApprove" "hubotSend"
+ "importPackages" "importProjects" "inNamespace" "inSession"
+ "initConanClient" "input" "invokeLambda" "isUnix" "ispwOperation"
+ "ispwRegisterWebhook" "ispwWaitForWebhook" "jacoco" "jdbc"
+ "jiraAddComment" "jiraAddWatcher" "jiraAssignIssue"
+ "jiraAssignableUserSearch" "jiraComment" "jiraDeleteAttachment"
+ "jiraDeleteIssueLink" "jiraDeleteIssueRemoteLink"
+ "jiraDeleteIssueRemoteLinks" "jiraDownloadAttachment"
+ "jiraEditComment" "jiraEditComponent" "jiraEditIssue"
+ "jiraEditVersion" "jiraGetAttachmentInfo" "jiraGetComment"
+ "jiraGetComments" "jiraGetComponent" "jiraGetComponentIssueCount"
+ "jiraGetFields" "jiraGetIssue" "jiraGetIssueLink"
+ "jiraGetIssueLinkTypes" "jiraGetIssueRemoteLink"
+ "jiraGetIssueRemoteLinks" "jiraGetIssueTransitions"
+ "jiraGetIssueWatches" "jiraGetProject" "jiraGetProjectComponents"
+ "jiraGetProjectStatuses" "jiraGetProjectVersions" "jiraGetProjects"
+ "jiraGetVersion" "jiraIssueSelector" "jiraJqlSearch" "jiraLinkIssues"
+ "jiraNewComponent" "jiraNewIssue" "jiraNewIssueRemoteLink"
+ "jiraNewIssues" "jiraNewVersion" "jiraNotifyIssue" "jiraSearch"
+ "jiraTransitionIssue" "jiraUploadAttachment" "jiraUserSearch"
+ "jmhReport" "jobDsl" "junit" "klocworkBuildSpecGeneration"
+ "klocworkIncremental" "klocworkIntegrationStep1"
+ "klocworkIntegrationStep2" "klocworkIssueSync"
+ "klocworkQualityGateway" "klocworkWrapper" "kubernetesApply"
+ "kubernetesDeploy" "lastChanges" "library" "libraryResource"
+ "liquibaseDbDoc" "liquibaseRollback" "liquibaseUpdate"
+ "listAWSAccounts" "livingDocs" "loadRunnerTest" "lock" "logstashSend"
+ "mail" "marathon" "mattermostSend" "memoryMap" "milestone" "mockLoad"
+ "newArtifactoryServer" "newBuildInfo" "newGradleBuild" "newMavenBuild"
+ "nexusArtifactUploader" "nexusPolicyEvaluation" "nexusPublisher"
+ "node" "nodejs" "nodesByLabel" "notifyBitbucket" "notifyDeploymon"
+ "notifyOTC" "nunit" "nvm" "octoPerfTest" "office365ConnectorSend"
+ "openTasks" "openshiftBuild" "openshiftCreateResource"
+ "openshiftDeleteResourceByJsonYaml" "openshiftDeleteResourceByKey"
+ "openshiftDeleteResourceByLabels" "openshiftDeploy" "openshiftExec"
+ "openshiftImageStream" "openshiftScale" "openshiftTag"
+ "openshiftVerifyBuild" "openshiftVerifyDeployment"
+ "openshiftVerifyService" "openstackMachine"
+ "osfBuilderSuiteForSFCCDeploy" "p4" "p4approve" "p4publish" "p4sync"
+ "p4tag" "p4unshelve" "pagerduty" "parasoftFindings" "pcBuild" "pdrone"
+ "perfReport" "perfSigReports" "perfpublisher" "plot" "pmd"
+ "podTemplate" "powershell" "pragprog" "pretestedIntegrationPublisher"
+ "properties" "protecodesc" "publishATX" "publishBrakeman"
+ "publishBuildInfo" "publishBuildRecord" "publishConfluence"
+ "publishDeployRecord" "publishETLogs" "publishEventQ"
+ "publishGenerators" "publishHTML" "publishLambda" "publishLastChanges"
+ "publishSQResults" "publishStoplight" "publishTMS" "publishTRF"
+ "publishTestResult" "publishTraceAnalysis" "publishUNIT"
+ "publishValgrind" "pullPerfSigReports" "puppetCode" "puppetHiera"
+ "puppetJob" "puppetQuery" "pushImage" "pushToCloudFoundry" "pwd"
+ "pybat" "pysh" "qc" "queryModuleBuildRequest" "questavrm" "r"
+ "radargunreporting" "rancher" "readFile" "readJSON" "readManifest"
+ "readMavenPom" "readProperties" "readTrusted" "readXml" "readYaml"
+ "realtimeJUnit" "registerWebhook" "release" "resolveScm" "retry"
+ "rocketSend" "rtp" "runConanCommand" "runFromAlmBuilder"
+ "runLoadRunnerScript" "runValgrind" "s3CopyArtifact" "s3Delete"
+ "s3Download" "s3FindFiles" "s3Upload" "salt" "sauce" "saucePublisher"
+ "sauceconnect" "script" "selectRun" "sendCIMessage"
+ "sendDeployableMessage" "serviceNow_attachFile" "serviceNow_attachZip"
+ "serviceNow_createChange" "serviceNow_getCTask"
+ "serviceNow_getChangeState" "serviceNow_updateChangeItem"
+ "setAccountAlias" "setGerritReview" "setGitHubPullRequestStatus" "sh"
+ "sha1" "signAndroidApks" "silkcentral" "silkcentralCollectResults"
+ "slackSend" "sleep" "sloccountPublish" "snsPublish" "snykSecurity"
+ "sonarToGerrit" "sparkSend" "splitTests" "springBoot" "sscm"
+ "sseBuild" "sseBuildAndPublish" "sshPublisher" "sshagent" "stage"
+ "startET" "startSandbox" "startSession" "startTS" "stash" "step"
+ "stepcounter" "stopET" "stopSandbox" "stopSession" "stopTS"
+ "submitJUnitTestResultsToqTest" "submitModuleBuildRequest"
+ "svChangeModeStep" "svDeployStep" "svExportStep" "svUndeployStep"
+ "svn" "tagImage" "task" "teamconcert" "tee" "testFolder" "testPackage"
+ "testProject" "testiniumExecution" "themisRefresh" "themisReport"
+ "throttle" "time" "timeout" "timestamps" "tm" "tool" "touch"
+ "triggerInputStep" "triggerJob" "typetalkSend" "uftScenarioLoad"
+ "unarchive" "unstash" "unzip" "updateBotPush"
+ "updateGitlabCommitStatus" "updateIdP" "updateTrustPolicy"
+ "upload-pgyer" "uploadProgetPackage" "uploadToIncappticConnect"
+ "vSphere" "validateDeclarativePipeline" "vmanagerLaunch"
+ "waitForCIMessage" "waitForJob" "waitForQualityGate" "waitForWebhook"
+ "waitUntil" "walk" "waptProReport" "warnings" "whitesource"
+ "winRMClient" "withAWS" "withAnt" "withContext" "withCoverityEnv"
+ "withCredentials" "withDockerContainer" "withDockerRegistry"
+ "withDockerServer" "withEnv" "withKafkaLog" "withKubeConfig"
+ "withMaven" "withNPM" "withPod" "withPythonEnv" "withSCM"
+ "withSandbox" "withSonarQubeEnv" "withTypetalk" "wrap" "writeFile"
+ "writeJSON" "writeMavenPom" "writeProperties" "writeXml" "writeYaml"
+ "ws" "xUnitImporter" "xUnitUploader" "xunit" "xldCreatePackage"
+ "xldDeploy" "xldPublishPackage" "xlrCreateRelease" "xrayScanBuild"
+ "zip"))
+
+
+(defun jenkinsfile-mode--fetch-keywords-from-jenkinsfile-vim ()
+ "Fetch and extract keywords from `jenkinsfile-mode-vim-source-url'.
+Run this manually when editing this file to get an updated the list of keywords."
+ (let* (file-sections directives options core-steps pipeline-steps
+ (syn-keyword-p (lambda (name line) (string-prefix-p (concat "syn keyword " name " ") line)))
+ (split-keywords (lambda (line) (split-string (replace-regexp-in-string "syn keyword [^ .]* " "" line)))))
+ (with-current-buffer (url-retrieve-synchronously jenkinsfile-mode-vim-source-url t)
+ (goto-char (point-min))
+ (while (not (eobp))
+ (move-beginning-of-line 1)
+ (let ((line (buffer-substring (point) (line-end-position))))
+ (cond
+ ((funcall syn-keyword-p "jenkinsfileSection" line) (setq file-sections (append file-sections (funcall split-keywords line))))
+ ((funcall syn-keyword-p "jenkinsfileDirective" line) (setq directives (append directives (funcall split-keywords line))))
+ ((funcall syn-keyword-p "jenkinsfileOption" line) (setq options (append options (funcall split-keywords line))))
+ ((funcall syn-keyword-p "jenkinsfileCoreStep" line) (setq core-steps (append core-steps (funcall split-keywords line))))
+ ((funcall syn-keyword-p "jenkinsfilePipelineStep" line) (setq pipeline-steps (append pipeline-steps (funcall split-keywords line))))))
+ (forward-line 1)))
+ (let ((buf (get-buffer-create "*jenkinsfile-mode--fetch-keywords-from-jenkinsfile-vim*")))
+ (with-current-buffer buf
+ (erase-buffer)
+ (emacs-lisp-mode)
+ (insert ";; copy the contents of this buffer into jenkinsfile-mode.el\n\n")
+ (insert (format "(setq jenkinsfile-mode--file-section-keywords '%S)\n\n" file-sections))
+ (insert (format "(setq jenkinsfile-mode--directive-keywords '%S)\n\n" directives))
+ (insert (format "(setq jenkinsfile-mode--option-keywords '%S)\n\n" options))
+ (insert (format "(setq jenkinsfile-mode--core-step-keywords '%S)\n\n" core-steps))
+ (insert (format "(setq jenkinsfile-mode--pipeline-step-keywords '%S)\n\n" pipeline-steps))
+ (fill-region 0 (point)))
+ (switch-to-buffer buf))))
+
+(defvar jenkinsfile-mode-font-lock-keywords
+ `((,(regexp-opt jenkinsfile-mode--file-section-keywords 'symbols) . font-lock-constant-face)
+ (,(regexp-opt jenkinsfile-mode--directive-keywords 'symbols) . font-lock-constant-face)
+ (,(regexp-opt jenkinsfile-mode--option-keywords 'symbols) . font-lock-function-name-face)
+ (,(regexp-opt jenkinsfile-mode--core-step-keywords 'symbols) . font-lock-function-name-face)
+ (,(regexp-opt jenkinsfile-mode--pipeline-step-keywords 'symbols) . font-lock-keyword-face)))
+
+(defvar jenkinsfile-mode-font-lock-defaults
+ (append groovy-font-lock-keywords jenkinsfile-mode-font-lock-keywords))
+
+(defun jenkinsfile-mode--pipeline-step-compeletion-at-point ()
+ "completion for pipeline step"
+ (interactive)
+ (let* (
+ (bds (bounds-of-thing-at-point 'symbol))
+ (start (car bds))
+ (end (cdr bds)))
+ (list start end jenkinsfile-mode--pipeline-step-keywords . nil ))
+ )
+
+(defun jenkinsfile-mode--core-step-compeletion-at-point ()
+ "completion for core step"
+ (interactive)
+ (let* (
+ (bds (bounds-of-thing-at-point 'symbol))
+ (start (car bds))
+ (end (cdr bds)))
+ (list start end jenkinsfile-mode--core-step-keywords . nil ))
+ )
+
+(defun jenkinsfile-mode--option-compeletion-at-point ()
+ "completion for option step"
+ (interactive)
+ (let* (
+ (bds (bounds-of-thing-at-point 'symbol))
+ (start (car bds))
+ (end (cdr bds)))
+ (list start end jenkinsfile-mode--option-keywords . nil ))
+ )
+
+(defun jenkinsfile-mode--directive-compeletion-at-point ()
+ "completion for directive step"
+ (interactive)
+ (let* (
+ (bds (bounds-of-thing-at-point 'symbol))
+ (start (car bds))
+ (end (cdr bds)))
+ (list start end jenkinsfile-mode--directive-keywords . nil ))
+ )
+
+(defun jenkinsfile-mode--file-compeletion-at-point ()
+ "completion for file step"
+ (interactive)
+ (let* (
+ (bds (bounds-of-thing-at-point 'symbol))
+ (start (car bds))
+ (end (cdr bds)))
+ (list start end jenkinsfile-mode--file-section-keywords . nil ))
+ )
+
+;;;###autoload
+(define-derived-mode jenkinsfile-mode groovy-mode "Jenkinsfile"
+ "Major mode for editing Jenkins declarative pipeline files."
+ (setq font-lock-defaults '(jenkinsfile-mode-font-lock-defaults))
+ (add-hook 'completion-at-point-functions 'jenkinsfile-mode--file-compeletion-at-point nil 'local)
+ (add-hook 'completion-at-point-functions 'jenkinsfile-mode--directive-compeletion-at-point nil 'local)
+ (add-hook 'completion-at-point-functions 'jenkinsfile-mode--option-compeletion-at-point nil 'local)
+ (add-hook 'completion-at-point-functions 'jenkinsfile-mode--pipeline-step-compeletion-at-point nil 'local)
+ (add-hook 'completion-at-point-functions 'jenkinsfile-mode--core-step-compeletion-at-point nil 'local)
+ (setq-local groovy-indent-offset jenkinsfile-mode-indent-offset)
+ (with-eval-after-load 'company-keywords
+ (add-to-list 'company-keywords-alist
+ `(jenkinsfile-mode . ,(append jenkinsfile-mode--file-section-keywords
+ jenkinsfile-mode--directive-keywords
+ jenkinsfile-mode--option-keywords
+ jenkinsfile-mode--pipeline-step-keywords
+ jenkinsfile-mode--core-step-keywords
+ ))))
+ )
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("Jenkinsfile\\'" . jenkinsfile-mode))
+
+(provide 'jenkinsfile-mode)
+;;; jenkinsfile-mode.el ends here