+ ((save-excursion
+ (and
+ (not (re-search-forward "^ *<" (line-end-position) t))
+ (re-search-forward "}> *$" (line-end-position) t)))
+ (hack-xhp-indent-debug "terminating php block")
+ nil)
+ ;; CASE 2: user is indenting a closing block, so out-dent
+ ;; e.g.
+ ;;
+ ;;
+ ((save-excursion
+ (beginning-of-line)
+ (re-search-forward "^ *" (line-end-position) t))
+ (+ base-indent (- hack-indent-offset)))
+ ;; CASE 3: if this happens to be /> on its own
+ ;; line, reduce indent (coding standard)
+ ((save-excursion
+ (goto-char start-pos)
+ (re-search-forward "^ */> *" (line-end-position) t))
+ (+ base-indent (- hack-indent-offset)))
+ ;; CASE 4: close of xhp passed to a function, e.g.
+ ;; foo(
+ ;;
+ ;; );
+ ((save-excursion
+ (re-search-forward "^ *);" (line-end-position) t))
+ (+ base-indent (- hack-indent-offset)))
+ ;; DEFAULT: no modification.
+ (t base-indent))
+ ;; STEP 2.2: FIRST STATEMENT AFTER XHP. if we're after
+ ;; the close of an xhp statement it still messes up the php
+ ;; indentation, so check that here and override
+ (cond
+ ;; CASE 1: multiline self-enclosing tag or closing tag
+ ;; e.g.
+ ;; ;
+ ;; - or -
+ ;;
+ ;; ...
+ ;;
;
+ ((save-excursion
+ (hack-xhp-backward-whitespace)
+ (and
+ (looking-back "\\(/>\\|\\);" nil)
+ ;; don't match single-line xhp $foo = ;
+ (not (re-search-backward "^ *\\$" (line-beginning-position) t))))
+ ;; previous statement IS xhp. check what user has typed so
+ ;; far
+ (+
+ (save-excursion (hack-xhp-backward-whitespace) (current-indentation))
+ (cond
+ ;; CASE 0: user typed a brace. outdent even more
+ ((looking-at ".*}") (* -2 hack-indent-offset))
+ ;; CASE 1: close of case in a switch stmt, e.g. case FOO:
+ ((looking-at ".*: *$") (* -2 hack-indent-offset))
+ ;; DEFAULT
+ (t (- hack-indent-offset)))))
+ ;; DEFAULT: not first stmt after xhp, indent normally
+ (t nil)))))
+
+(defun hack--indent-preserve-point (offset)
+ "Indent the current line by OFFSET spaces.
+Ensure point is still on the same part of the line afterwards."
+ (let ((point-offset (- (current-column) (current-indentation))))
+ (indent-line-to offset)
+
+ ;; 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)))))
+
+(defun hack--paren-depth-for-indent (pos)
+ "Return the number of parentheses around POS.
+Repeated parens on the same line are consider a single paren."
+ (let ((depth 0)
+ ;; Keep tracking of which line we saw each paren on. Since
+ ;; calculating line number is O(n), just track the start
+ ;; position of each line.
+ (prev-paren-line-start nil))
+ (save-excursion
+ (goto-char pos)
+ (catch 'done
+ (while t
+ (let* ((ppss (syntax-ppss))
+ (paren-start (nth 1 ppss)))
+ (if paren-start
+ (progn
+ (goto-char paren-start)
+ (unless (eq (line-beginning-position) prev-paren-line-start)
+ (setq depth (1+ depth)))
+ (setq prev-paren-line-start (line-beginning-position)))
+ (throw 'done t))))))
+ depth))
+
+(defun hack--ends-with-infix-p (str)
+ "Does STR end with an infix operator?"
+ (string-match-p
+ (rx
+ space
+ ;; https://docs.hhvm.com/hack/expressions-and-operators/operator-precedence
+ (or "*" "/" "%" "+" "-" "."
+ "<<" ">>" "<" "<=" ">" ">="
+ "==" "!=" "===" "!==" "<=>"
+ "&" "^" "|" "&&" "||" "?:" "??" "|>"
+ "=" "+=" "-=" ".=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|="
+ "instanceof" "is" "as" "?as")
+ (0+ space)
+ line-end)
+ str))
+
+(defun hack--current-line ()
+ "The current line enclosing point."
+ (buffer-substring
+ (line-beginning-position) (line-end-position)))
+
+(defun hack--switch-count ()
+ "Return a count of the switch blocks that enclose point.
+
+Given the code, where | is point:
+
+function foo() {
+ if ($bar) {
+ switch ($baz) {
+ |
+ }
+ }
+}
+
+Then this function returns 1."
+ (let ((switches 0)
+ enclosing-paren-pos)
+ (save-excursion
+ (setq enclosing-paren-pos (nth 1 (syntax-ppss)))
+ (while enclosing-paren-pos
+ (goto-char enclosing-paren-pos)
+ (let* ((line (s-trim (hack--current-line)))
+ (symbols (s-split (rx symbol-end) line t))
+ (symbol (car-safe symbols)))
+ (when (string= symbol "switch")
+ (setq switches (1+ switches))))
+ (setq enclosing-paren-pos (nth 1 (syntax-ppss)))))
+ switches))
+
+(defun hack-indent-line ()
+ "Indent the current line of Hack code.
+Preserves point position in the line where possible."
+ (interactive)
+ (if (hack--in-xhp-p (point))
+ (let ((indent (hack-xhp-indent-offset)))
+ (when indent
+ (hack--indent-preserve-point indent)))
+ (hack--indent-line)))
+
+(defun hack--indent-line ()
+ "Indent the current line of non-XHP Hack code."
+ (let* ((syntax-bol (syntax-ppss (line-beginning-position)))
+ (in-multiline-string-p (nth 3 syntax-bol))
+ (point-offset (- (current-column) (current-indentation)))
+ (paren-depth (hack--paren-depth-for-indent (line-beginning-position)))
+
+ (ppss (syntax-ppss (line-beginning-position)))
+ (current-paren-pos (nth 1 ppss))
+ (text-after-paren
+ (when current-paren-pos
+ (save-excursion
+ (goto-char current-paren-pos)
+ (buffer-substring
+ (1+ current-paren-pos)
+ (line-end-position)))))
+ (in-multiline-comment-p (nth 4 ppss))
+ (current-line (hack--current-line)))
+ ;; If the current line is just a closing paren, unindent by one level.
+ (when (and
+ (not in-multiline-comment-p)
+ (string-match-p (rx bol (0+ space) (or ")" "}")) current-line))
+ (setq paren-depth (1- paren-depth)))
+ (cond
+ ;; Don't indent inside heredoc/nowdoc strings.
+ (in-multiline-string-p
+ nil)
+ ;; In multiline comments, ensure the leading * is indented by one
+ ;; more space. For example:
+ ;; /*
+ ;; * <- this line
+ ;; */
+ (in-multiline-comment-p
+ ;; Don't modify lines that don't start with *, to avoid changing the indentation of commented-out code.
+ (when (or (string-match-p (rx bol (0+ space) "*") current-line)
+ (string= "" current-line))
+ (hack--indent-preserve-point (1+ (* hack-indent-offset paren-depth)))))
+ ;; Indent according to the last paren position, if there is text
+ ;; after the paren. For example:
+ ;; foo(bar,
+ ;; baz, <- this line
+ ((and
+ text-after-paren
+ (not (string-match-p (rx bol (0+ space) eol) text-after-paren)))
+ (let (open-paren-column)
+ (save-excursion
+ (goto-char current-paren-pos)
+ (setq open-paren-column (current-column)))
+ (hack--indent-preserve-point (1+ open-paren-column))))
+ ;; Indent according to the amount of nesting.
+ (t
+ (let ((current-line (s-trim current-line))
+ prev-line)
+ (save-excursion
+ ;; Try to go back one line.
+ (when (zerop (forward-line -1))
+ (setq prev-line
+ (buffer-substring (line-beginning-position) (line-end-position)))))
+
+ ;; Increase indent if the past line ended with an infix
+ ;; operator, so we get
+ ;; $x =
+ ;; foo();
+ (when (and prev-line
+ (hack--ends-with-infix-p prev-line))
+ (setq paren-depth (1+ paren-depth)))
+
+ ;; Increase indent for lines that are method calls or pipe expressions.
+ ;;
+ ;; $foo
+ ;; ->bar(); <- this line
+ (when (or (s-starts-with-p "->" current-line)
+ (s-starts-with-p "?->" current-line)
+ (s-starts-with-p "|>" current-line))
+ (setq paren-depth (1+ paren-depth))))
+
+ ;; Inside switch statements.
+ (let ((switch-count (hack--switch-count)))
+ (when (> switch-count 0)
+ ;; Expressions inside switch statements should be further
+ ;; indented, so they are underneath the case or default.
+ (setq paren-depth (+ paren-depth switch-count))
+
+ ;; The case or default line should be outdented one step.
+ (when (string-match-p (rx bol (* whitespace) (or "case" "default:" "}"))
+ current-line)
+ (setq paren-depth (1- paren-depth)))))
+
+ (hack--indent-preserve-point (* hack-indent-offset paren-depth))))
+ ;; 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)))))
+
+(defalias 'hack-xhp-indent-line 'hack-indent-line)
+
+;; hh_server can choke if you symlink your www root
+(setq find-file-visit-truename t)
+
+;; Ensure that we use `hack-mode' for .php files, but put the
+;; association at the end of the list so `php-mode' wins (if installed).
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.php$" . hack-mode) t)
+
+;; These extensions are hack-specific.
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.hhi$" . hack-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.hack$" . hack-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.hck$" . hack-mode))
+
+(defun hack-format ()
+ "Format the current buffer or region with hackfmt."
+ (interactive)
+ (if (use-region-p)
+ (hack--format-region)
+ (hack-format-buffer)))
+
+(defun hack--format-region ()
+ "Format the active region."
+ (interactive)
+ (let* ((start (save-restriction
+ (widen)
+ (region-beginning)))
+ (end (save-restriction
+ (widen)
+ (region-end)))
+ (command
+ (format "%s --range %d %d %s"
+ hack-hackfmt-name
+ start end (buffer-file-name))))
+ (shell-command-on-region
+ start
+ end
+ command nil t)))
+
+(defun hack-format-buffer ()
+ "Format the current buffer with hackfmt."
+ (interactive)
+ (let ((src-buf (current-buffer))
+ (src (buffer-string))
+ (start-line (line-number-at-pos (point)))
+ (start-column (current-column))
+ (output-buf (get-buffer-create "*hackfmt*")))
+ (with-current-buffer output-buf
+ (erase-buffer)
+ (insert src)
+ (if (zerop
+ (call-process-region (point-min) (point-max)
+ hack-hackfmt-name t t nil))
+ (progn
+ (unless (string= (buffer-string) src)
+ ;; We've changed something, so update the source buffer.
+ (copy-to-buffer src-buf (point-min) (point-max)))
+ (kill-buffer))
+ (error "Hackfmt failed, see *hackfmt* buffer for details")))
+ ;; Do our best to restore point position.
+ (goto-char (point-min))
+ (forward-line (1- start-line))
+ (forward-char start-column)))
+
+(defun hack--maybe-format ()
+ (when hack-format-on-save
+ (hack-format-buffer)))
+
+(defun hack-enable-format-on-save ()
+ "Enable automatic formatting on the current hack-mode buffer.."
+ (interactive)
+ (setq-local hack-format-on-save t))
+
+(defun hack-disable-format-on-save ()
+ "Disable automatic formatting on the current hack-mode buffer.."
+ (interactive)
+ (setq-local hack-format-on-save nil))
+
+;;;###autoload
+(define-derived-mode hack-mode prog-mode "Hack"
+ "Major mode for editing Hack code.
+
+\\{hack-mode-map\\}"
+ ;; Remove any old text properties, so we don't get stuck with
+ ;; incorrect regions of hack-xhp-expression.
+ (set-text-properties (point-min) (point-max) nil)
+
+ (setq-local font-lock-defaults '(hack-font-lock-keywords))
+ (set (make-local-variable 'syntax-propertize-function)
+ hack--syntax-propertize-function)
+
+ (setq-local compile-command (concat hack-client-program-name " --from emacs"))
+ (setq-local indent-line-function #'hack-indent-line)
+ (setq-local comment-start "// ")
+ (setq-local fill-paragraph-function #'hack-fill-paragraph)
+ (setq imenu-generic-expression
+ ;; TODO: distinguish functions from methods.
+ `(("Function"
+ ,(rx
+ line-start
+ (? (* space) (seq symbol-start (or "private" "protected" "public") symbol-end))
+ (? (* space) (seq symbol-start "static" symbol-end))
+ (? (* space) (seq symbol-start "async" symbol-end))
+ (* space)
+ symbol-start "function" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)
+ ("Class"
+ ,(rx
+ line-start
+ (? (* space) (seq symbol-start "abstract" symbol-end))
+ (? (* space) (seq symbol-start "final" symbol-end))
+ (* space)
+ symbol-start "class" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)
+ ("Interface"
+ ,(rx symbol-start "interface" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)
+ ("Trait"
+ ,(rx symbol-start "trait" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)))
+
+ (add-hook 'before-save-hook #'hack--maybe-format nil t))
+
+
+
+(provide 'hack-mode)
+;;; hack-mode.el ends here
diff --git a/sample_files/strings_before.el b/sample_files/strings_before.el
new file mode 100644
index 000000000..28aafd5d0
--- /dev/null
+++ b/sample_files/strings_before.el
@@ -0,0 +1,1917 @@
+;;; hack-mode.el --- Major mode for the Hack programming language -*- lexical-binding: t -*-
+
+;; Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
+
+;; 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 .
+
+;; Author: John Allen , Wilfred Hughes
+;; Version: 1.1.0
+;; Package-Requires: ((emacs "25.1") (s "1.11.0"))
+;; URL: https://github.com/hhvm/hack-mode
+
+;;; Commentary:
+;;
+;; Implements `hack-mode' for the Hack programming language. This
+;; includes basic support for highlighting and indentation.
+;;
+
+;;; Code:
+(require 's)
+
+(defgroup hack nil
+ "Major mode `hack-mode' for editing Hack code."
+ :prefix "hack-"
+ :group 'languages)
+
+(defcustom hack-client-program-name "hh_client"
+ "The command to run to communicate with hh_server."
+ :type 'string
+ :group 'hack-mode)
+
+(defcustom hack-format-on-save nil
+ "Format the current buffer on save."
+ :type 'boolean
+ :safe #'booleanp
+ :group 'hack-mode)
+
+(defcustom hack-hackfmt-name "hackfmt"
+ "The command to run to format code."
+ :type 'string
+ :group 'hack-mode)
+
+(defcustom hack-indent-offset 2
+ "Indentation amount (in spaces) for Hack code."
+ :safe #'integerp
+ :type 'integer
+ :group 'hack-mode)
+
+(defface hack-xhp-tag
+ '((t . (:inherit font-lock-function-name-face)))
+ "Face used to highlight XHP tags in `hack-mode' buffers."
+ :group 'hack-mode)
+
+(defun hack--propertize-xhp ()
+ "Put syntax properties on XHP blocks."
+ (let* ((start-pos (match-beginning 1))
+ (ppss (syntax-ppss start-pos))
+ (in-comment (nth 4 ppss)))
+ (unless in-comment
+ (hack--forward-parse-xhp start-pos nil)
+ ;; Point is now at the end of the XHP section.
+ (let ((end-pos (point)))
+ (goto-char start-pos)
+ ;; Ensure that ' is not treated as a string delimiter, unless
+ ;; we're in an XHP interpolated region.
+ (while (search-forward "'" end-pos t)
+ (let* ((ppss (syntax-ppss))
+ (paren-pos (nth 1 ppss)))
+ (when (or (null paren-pos)
+ (> start-pos paren-pos))
+ (put-text-property (1- (point)) (point)
+ 'syntax-table
+ (string-to-syntax ".")))))
+ ;; We need to leave point after where we started, or we get an
+ ;; infinite loop.
+ (goto-char end-pos)))))
+
+(defun hack--propertize-heredoc ()
+ "Put `syntax-table' text properties on heredoc and nowdoc string literals.
+
+See ."
+ ;; Point starts just after the <<<, so the start position is three
+ ;; chars back.
+ (let ((start-pos (match-beginning 0))
+ (identifier (match-string 1)))
+ ;; Nowdoc literals have the form
+ ;; $x <<<'FOO'
+ ;; bar
+ ;; FOO; // unquoted at end.
+ (setq identifier
+ (s-chop-suffix "'" (s-chop-prefix "'" identifier)))
+ ;; The closing identifier must be at the beginning of a line.
+ (search-forward (format "\n%s" identifier) nil t)
+
+ ;; Mark the beginning < as a string beginning, so we don't think
+ ;; any ' or " inside the heredoc are string delimiters.
+ (put-text-property start-pos (1+ start-pos)
+ 'syntax-table
+ (string-to-syntax "|"))
+ (put-text-property (1- (point)) (point)
+ 'syntax-table
+ (string-to-syntax "|"))))
+
+(defun hack--propertize-xhp-class-name ()
+ "Ensure that : and - in XHP class names are marked as symbol constituents.
+
+Note that the first : is excluded, so :foo:bar is considered to
+be punctuation : followed by symbol foo:bar. This ensures we
+match XHP usage, which looks like ."
+ (let ((start (match-beginning 1))
+ (end (match-end 1)))
+ (save-excursion
+ (goto-char (1+ start))
+ (while (re-search-forward (rx (or ":" "-")) end t)
+ (put-text-property (1- (point)) (point)
+ 'syntax-table (string-to-syntax "_"))))))
+
+(eval-when-compile
+ ;; https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
+ (defconst hack--heredoc-regex
+ (rx
+ "<<<"
+ (group
+ (or
+ (+ (or (syntax word) (syntax symbol)))
+ ;; https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.nowdoc
+ (seq "'" (+ (or (syntax word) (syntax symbol))) "'")))
+ "\n"))
+
+ (defconst hack--header-regex
+ ;; "
+ "?"
+ "="
+ "(")
+ (* space)
+ (group "<" (not (any ?< ?\\ ??))))
+ "The regex used to match the start of an XHP expression."))
+
+(defun hack-font-lock-xhp (limit)
+ "Search up to LIMIT for an XHP expression.
+If we find one, move point to its end, and set match data."
+ (let ((start-pos nil)
+ (end-pos nil))
+ (save-excursion
+ (while (and (not end-pos)
+ (re-search-forward hack-xhp-start-regex limit t))
+ ;; Ignore XHP in comments.
+ (unless (nth 4 (syntax-ppss (match-beginning 1)))
+ (setq start-pos (match-beginning 1))
+ (hack--forward-parse-xhp start-pos limit t)
+ (setq end-pos (point)))))
+ (when end-pos
+ (set-match-data (list start-pos end-pos))
+ (goto-char end-pos))))
+
+(defun hack--propertize-lt ()
+ "Ensure < is not treated a < delimiter in other syntactic contexts."
+ (let ((start (1- (point))))
+ (when (or (looking-at "?hh")
+ ;; Ignore left shift operators 1 << 2.
+ (looking-at "< ")
+ ;; If there's a following space, assume it's 1 < 2.
+ (looking-at " "))
+ (put-text-property start (1+ start)
+ 'syntax-table (string-to-syntax ".")))))
+
+(defun hack-->-paired-p (pos)
+ "Return t if the > at POS is a paired delimiter.
+
+E.g. Foo has a paired delimiter, 1 > 2 does not."
+ (when (> pos (1+ (point-min)))
+ (let ((prev-char (char-before pos))
+ (prev-prev-char (char-before (1- pos)))
+ (next-char (char-after (1+ pos))))
+ (not
+ (or
+ ;; If there's a preceding space, we assume it's 1 > 2 rather
+ ;; than vec < int > with excess space.
+ (eq prev-char ?\ )
+ ;; 1 >> 2, looking at the second >.
+ (and (eq prev-char ?>) (eq prev-prev-char ?\ ))
+ ;; $foo->bar and 1<=>2
+ (memq prev-char (list ?= ?-))
+ ;; 1>=2
+ (eq next-char ?=))))))
+
+(defun hack--propertize-gt ()
+ "Ensure > in -> or => isn't treated as a > delimiter."
+ (unless (hack-->-paired-p (1- (point)))
+ (put-text-property (1- (point)) (point)
+ 'syntax-table (string-to-syntax "."))))
+
+(defconst hack--syntax-propertize-function
+ (syntax-propertize-rules
+ ;; Heredocs, e.g.
+ ;; $x = <<"
+ (0 (ignore (hack--propertize-gt))))))
+
+(defun hack-font-lock-fallthrough (limit)
+ "Search for FALLTHROUGH comments."
+ (let ((case-fold-search nil)
+ (found-pos nil)
+ (match-data nil))
+ ;; FALLTHROUGH must start with //, and can have any text afterwards. See
+ ;; full_fidelity_lexer.ml.
+ (save-excursion
+ (while (and (not found-pos)
+ (search-forward "FALLTHROUGH" limit t))
+ (let* ((ppss (syntax-ppss))
+ (in-comment (nth 4 ppss))
+ (comment-start (nth 8 ppss)))
+ (when in-comment
+ (save-excursion
+ (goto-char comment-start)
+ (when (re-search-forward
+ (rx point "//" (0+ whitespace) (group "FALLTHROUGH"))
+ limit t)
+ (setq found-pos (point))
+ (setq match-data (match-data))))))))
+ (when found-pos
+ (set-match-data match-data)
+ (goto-char found-pos))))
+
+(defun hack-font-lock-unsafe (limit)
+ "Search for UNSAFE comments."
+ (let ((case-fold-search nil)
+ (found-pos nil)
+ (match-data nil))
+ ;; UNSAFE must start with //, and can have any text afterwards. See
+ ;; full_fidelity_lexer.ml.
+ (save-excursion
+ (while (and (not found-pos)
+ (search-forward "UNSAFE" limit t))
+ (let* ((ppss (syntax-ppss))
+ (in-comment (nth 4 ppss))
+ (comment-start (nth 8 ppss)))
+ (when in-comment
+ (save-excursion
+ (goto-char comment-start)
+ (when (re-search-forward
+ (rx point "//" (0+ whitespace) (group "UNSAFE"))
+ limit t)
+ (setq found-pos (point))
+ (setq match-data (match-data))))))))
+ (when found-pos
+ (set-match-data match-data)
+ (goto-char found-pos))))
+
+(defun hack-font-lock-unsafe-expr (limit)
+ "Search for UNSAFE_EXPR comments."
+ (let ((case-fold-search nil)
+ (found-pos nil)
+ (match-data nil))
+ ;; UNSAFE_EXPR must start with /*, and can have any text afterwards. See
+ ;; full_fidelity_lexer.ml.
+ (save-excursion
+ (while (and (not found-pos)
+ (search-forward "UNSAFE_EXPR" limit t))
+ (let* ((ppss (syntax-ppss))
+ (in-comment (nth 4 ppss))
+ (comment-start (nth 8 ppss)))
+ (when in-comment
+ (save-excursion
+ (goto-char comment-start)
+ (when (re-search-forward
+ (rx point "/*" (0+ whitespace) (group "UNSAFE_EXPR"))
+ limit t)
+ (setq found-pos (point))
+ (setq match-data (match-data))))))))
+ (when found-pos
+ (set-match-data match-data)
+ (goto-char found-pos))))
+
+(defun hack-font-lock-fixme (limit)
+ "Search for HH_FIXME comments."
+ (let ((case-fold-search nil)
+ (found-pos nil)
+ (match-data nil))
+ ;; HH_FIXME must start with /*, see full_fidelity_lexer.ml. The
+ ;; format is matched by scour_comments in full_fidelity_ast.ml,
+ ;; using, the ignore_error regex defined in that file.
+ (save-excursion
+ (while (and (not found-pos)
+ (search-forward "HH_FIXME" limit t))
+ (let* ((ppss (syntax-ppss))
+ (in-comment (nth 4 ppss))
+ (comment-start (nth 8 ppss)))
+ (when in-comment
+ (save-excursion
+ (goto-char comment-start)
+ (when (re-search-forward
+ (rx point "/*" (0+ whitespace)
+ (group "HH_FIXME" (0+ whitespace) "[" (+ digit) "]"))
+ limit t)
+ (setq found-pos (point))
+ (setq match-data (match-data))))))))
+ (when found-pos
+ (set-match-data match-data)
+ (goto-char found-pos))))
+
+(defun hack-font-lock-ignore-error (limit)
+ "Search for HH_IGNORE_ERROR comments."
+ (let ((case-fold-search nil)
+ (found-pos nil)
+ (match-data nil))
+ ;; HH_IGNORE_ERROR must start with /*, see full_fidelity_lexer.ml. The
+ ;; format is matched by scour_comments in full_fidelity_ast.ml,
+ ;; using, the ignore_error regex defined in that file.
+ (save-excursion
+ (while (and (not found-pos)
+ (search-forward "HH_IGNORE_ERROR" limit t))
+ (let* ((ppss (syntax-ppss))
+ (in-comment (nth 4 ppss))
+ (comment-start (nth 8 ppss)))
+ (when in-comment
+ (save-excursion
+ (goto-char comment-start)
+ (when (re-search-forward
+ (rx point "/*" (0+ whitespace)
+ (group "HH_IGNORE_ERROR" (0+ whitespace) "[" (+ digit) "]"))
+ limit t)
+ (setq found-pos (point))
+ (setq match-data (match-data))))))))
+ (when found-pos
+ (set-match-data match-data)
+ (goto-char found-pos))))
+
+(defun hack-font-lock-interpolate (limit)
+ "Search for $foo string interpolation."
+ (let ((pattern
+ (rx (not (any "\\"))
+ (group
+ (or
+ (seq
+ ;; $foo
+ "$" (+ (or (syntax word) (syntax symbol))) symbol-end
+ (0+ (or
+ ;; $foo->bar
+ (seq "->" (+ (or (syntax word) (syntax symbol))) symbol-end)
+ ;; $foo[123]
+ (seq "[" (+ (or (syntax word) (syntax symbol))) symbol-end "]"))))
+ ;; ${foo}
+ (seq "${" (+ (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* ((ppss (syntax-ppss))
+ (in-string-p (nth 3 ppss))
+ (string-delimiter-pos (nth 8 ppss))
+ (string-delimiter
+ (when in-string-p (char-after string-delimiter-pos)))
+ (interpolation-p in-string-p))
+ (cond
+ ;; Interpolation does not apply in single-quoted strings.
+ ((eq string-delimiter ?')
+ (setq interpolation-p nil))
+ ;; We can interpolate in <<")) tag-start t)
+ (cond
+ ;; Ensure that $foo->bar is not treated as a paired < > inside interpolated XHP.
+ ((and (eq (char-before) ?>)
+ ;; We deliberately check for interpolation
+ ;; *before* the >. This ensures that we don't
+ ;; get confused if we still think > is a paired
+ ;; delimiter.
+ (hack--in-xhp-interpolation-p (1- (point)) start-pos))
+ (unless (hack-->-paired-p (1- (point)))
+ (put-text-property (1- (point)) (point)
+ 'syntax-table (string-to-syntax "."))))
+ ;; Ensure that " and ' are just punctuation inside XHP expressions.
+ ((memq (char-before) (list ?\" ?'))
+ (unless (hack--in-xhp-interpolation-p (point) start-pos)
+ (put-text-property (1- (point)) (point)
+ 'syntax-table (string-to-syntax ".")))))))))
+
+ (let ((close-p (looking-at-p "/"))
+ tag-name tag-name-start tag-name-end
+ self-closing-p)
+ (when close-p
+ (forward-char 1))
+ ;; Get the name of the current tag.
+ (re-search-forward
+ (rx (+ (or (syntax word) (syntax symbol) ":" "-"))))
+ (setq tag-name (match-string 0))
+ (setq tag-name-start (match-beginning 0))
+ (setq tag-name-end (match-end 0))
+
+ (if propertize-tags
+ (add-face-text-property tag-name-start tag-name-end 'hack-xhp-tag)
+
+ ;; Ensure : and - are symbol consituents on tag names.
+ (goto-char tag-name-start)
+ (while (re-search-forward (rx (or ":" "-")) tag-name-end t)
+ (put-text-property (1- (point)) (point)
+ 'syntax-table (string-to-syntax "_"))))
+
+ (unless (search-forward ">" limit t)
+ ;; Can't find the matching close angle bracket, so the XHP
+ ;; expression is incomplete.
+ (throw 'done t))
+
+ (save-excursion
+ (backward-char 2)
+ (when (looking-at "/>")
+ (setq self-closing-p t)))
+ (cond
+ (self-closing-p
+ ;; A self-closing tag doesn't need pushing to the stack.
+ nil)
+ ((and close-p (string= tag-name (car tags)))
+ ;; A balanced closing tag.
+ (pop tags))
+ (close-p
+ ;; An unbalanced close tag, we were expecting something
+ ;; else. Assume this is the end of the XHP section.
+ (throw 'done t))
+ (t
+ ;; An open tag.
+ (push tag-name tags)))
+
+ (unless tags
+ ;; Reach the close of the initial open XHP tag.
+ (throw 'done t))
+
+ (setq prev-tag-end (point)))))
+
+ (put-text-property start-pos (point)
+ 'hack-xhp-expression start-pos)))
+
+(defun hack-font-lock-interpolate-complex (limit)
+ "Search for {$foo} string interpolation."
+ (let (res start)
+ (while (and
+ (not res)
+ (search-forward "{$" limit t))
+ (let* ((ppss (syntax-ppss))
+ (in-string-p (nth 3 ppss))
+ (string-delimiter-pos (nth 8 ppss))
+ (string-delimiter
+ (when in-string-p (char-after string-delimiter-pos)))
+ (interpolation-p in-string-p))
+ (cond
+ ;; Interpolation does not apply in single-quoted strings.
+ ((eq string-delimiter ?')
+ (setq interpolation-p nil))
+ ;; We can interpolate in <<"
+ "list"
+ "array"
+ "OR"
+ "AND"
+ "XOR"
+ "dict"
+ "keyset"
+ "shape"
+ "type"
+ "newtype"
+ "where"
+ "await"
+ "vec"
+ "varray"
+ "darray"
+ "inout"
+ "async"
+ "tuple"
+ "__CLASS__"
+ "__TRAIT__"
+ "__FUNCTION__"
+ "__METHOD__"
+ "__LINE__"
+ "__FILE__"
+ "__DIR__"
+ "__NAMESPACE__"
+ "__COMPILER_FRONTEND__"
+ ;; Treat self:: and static:: as keywords.
+ "self"
+ "parent")
+ 'symbols))
+
+(defconst hack--type-regex
+ (rx
+ (or symbol-start whitespace "<")
+ (? "?")
+ (group
+ (or
+ ;; Built-in classes, based on Classes in
+ ;; naming_special_names.ml, excluding self/parent (which we've
+ ;; treated as keywords above).
+ "stdClass"
+ "classname"
+ "typename"
+
+ ;; Built-in types, based on Typehints in naming_special_names.ml.
+ "void"
+ "resource"
+ "num"
+ "arraykey"
+ "noreturn"
+ "mixed"
+ "nonnull"
+ "this"
+ "dynamic"
+ "int"
+ "bool"
+ "float"
+ "string"
+ "array"
+ "darray"
+ "varray"
+ "varray_or_darray"
+ "integer"
+ "boolean"
+ "double"
+ "real"
+ "callable"
+ "object"
+ "unset"
+
+ ;; Placeholder types, e.g. in vec<_>.
+ "_"
+
+ ;; PHP built-in classes that don't start with an upper case character
+ ;; https://docs.hhvm.com/php/reference/
+ "finfo"
+ "libXMLError"
+ "mysqli"
+ "mysqli_driver"
+ "mysqli_result"
+ "mysqli_stmt"
+ "mysqli_warning"
+ "php_user_filter"
+
+ ;; User-defined type.
+ (seq
+ (0+ "_")
+ (any upper)
+ (* (or (syntax word) (syntax symbol))))
+
+ ;; XHP type, based on is_next_xhp_class_name in full_fidelity_lexer.ml.
+ (seq
+ ":"
+ (+ (or (syntax word) (syntax symbol) ":" "-")))))
+ symbol-end))
+
+(defconst hack--built-in-fun-regex
+ (rx
+ (or line-start whitespace)
+ symbol-start
+ (group
+ (or
+ ;; https://docs.hhvm.com/hack/reference/function/ HH\foo functions
+ "array_key_cast" "asio_get_current_context_idx" "asio_get_running" "asio_get_running_in_context" "autoload_set_paths"
+ "class_meth" "clear_instance_memoization" "clear_lsb_memoization" "clear_static_memoization" "could_include"
+ "curl_create_pool" "curl_destroy_pool" "curl_init_pooled" "curl_list_pools" "darray"
+ "deferred_errors" "disable_code_coverage_with_frequency" "enable_legacy_behavior" "execution_context" "ext_factparse_version"
+ "ext_watchman_version" "facts_parse" "ffp_parse_string" "ffp_parse_string_native" "fun"
+ "get_headers_secure" "heapgraph_create" "heapgraph_dfs_edges" "heapgraph_dfs_nodes" "heapgraph_edge"
+ "heapgraph_foreach_edge" "heapgraph_foreach_node" "heapgraph_foreach_root" "heapgraph_foreach_root_node" "heapgraph_node"
+ "heapgraph_node_in_edges" "heapgraph_node_out_edges" "heapgraph_stats" "heapgraph_version" "idx"
+ "inst_meth" "invariant" "invariant_callback_register" "invariant_violation" "is_any_array"
+ "is_darray" "is_dict" "is_keyset" "is_list_like" "is_varray"
+ "is_vec" "meth_caller" "non_crypto_md5_lower" "non_crypto_md5_upper" "object_prop_array"
+ "objprof_get_data" "objprof_get_paths" "objprof_get_strings" "serialize_memoize_param" "serialize_with_options"
+ "server_health_level" "server_is_prepared_to_stop" "server_is_stopping" "server_process_start_time" "server_uptime"
+ "server_warmup_status" "server_warmup_status_monotonic" "set_frame_metadata" "set_mem_threshold_callback" "set_soft_late_init_default"
+ "thread_mark_stack" "thread_memory_stats" "varray" "watchman_check_sub" "watchman_run"
+ "watchman_subscribe" "watchman_sync_sub" "watchman_unsubscribe" "xenon_get_and_clear_missed_sample_count" "xenon_get_and_clear_samples"
+ "xenon_get_data" "xenon_get_is_profiled_request"
+
+ ;; https://docs.hhvm.com/hack/reference/function/ global functions
+ "call_user_func_array" "curl_multi_await" "fb_autoload_map" "fb_compact_serialize" "fb_compact_unserialize"
+ "fb_curl_getopt" "fb_curl_multi_fdset" "fb_disable_code_coverage" "fb_enable_code_coverage" "fb_get_code_coverage"
+ "fb_get_last_flush_size" "fb_htmlspecialchars" "fb_intercept" "fb_lazy_lstat" "fb_lazy_realpath"
+ "fb_output_compression" "fb_rename_function" "fb_serialize" "fb_set_exit_callback" "fb_setprofile"
+ "fb_unserialize" "fb_utf8_strlen" "fb_utf8_strlen_deprecated" "fb_utf8_substr" "fb_utf8ize"
+ "forward_static_call_array" "furchash_hphp_ext" "furchash_hphp_ext_supported" "get_class_constants" "get_http_request_size"
+ "hphp_array_idx" "hphp_clear_hardware_events" "hphp_clear_unflushed" "hphp_crash_log" "hphp_create_continuation"
+ "hphp_create_object" "hphp_create_object_without_constructor" "hphp_current_ref" "hphp_debug_backtrace_hash" "hphp_debug_break"
+ "hphp_debug_caller_info" "hphp_debug_session_auth" "hphp_debugger_attached" "hphp_directoryiterator___construct" "hphp_directoryiterator___tostring"
+ "hphp_directoryiterator_current" "hphp_directoryiterator_isdot" "hphp_directoryiterator_key" "hphp_directoryiterator_next" "hphp_directoryiterator_rewind"
+ "hphp_directoryiterator_seek" "hphp_directoryiterator_valid" "hphp_get_class_constant" "hphp_get_extension_info" "hphp_get_hardware_counters"
+ "hphp_get_iostatus" "hphp_get_iterator" "hphp_get_mutable_iterator" "hphp_get_property" "hphp_get_static_property"
+ "hphp_get_stats" "hphp_get_status" "hphp_get_this" "hphp_get_thread_id" "hphp_get_timers"
+ "hphp_gettid" "hphp_instanceof" "hphp_instruction_counter" "hphp_invoke" "hphp_invoke_method"
+ "hphp_memory_get_interval_peak_usage" "hphp_memory_heap_capacity" "hphp_memory_heap_usage" "hphp_memory_start_interval" "hphp_memory_stop_interval"
+ "hphp_murmurhash" "hphp_object_pointer" "hphp_output_global_state" "hphp_process_abort" "hphp_recursivedirectoryiterator___construct"
+ "hphp_recursivedirectoryiterator___tostring" "hphp_recursivedirectoryiterator_current" "hphp_recursivedirectoryiterator_getchildren" "hphp_recursivedirectoryiterator_getsubpath" "hphp_recursivedirectoryiterator_getsubpathname"
+ "hphp_recursivedirectoryiterator_haschildren" "hphp_recursivedirectoryiterator_key" "hphp_recursivedirectoryiterator_next" "hphp_recursivedirectoryiterator_rewind" "hphp_recursivedirectoryiterator_seek"
+ "hphp_recursivedirectoryiterator_valid" "hphp_recursiveiteratoriterator___construct" "hphp_recursiveiteratoriterator_current" "hphp_recursiveiteratoriterator_getinneriterator" "hphp_recursiveiteratoriterator_key"
+ "hphp_recursiveiteratoriterator_next" "hphp_recursiveiteratoriterator_rewind" "hphp_recursiveiteratoriterator_valid" "hphp_scalar_typehints_enabled" "hphp_set_error_page"
+ "hphp_set_hardware_events" "hphp_set_iostatus_address" "hphp_set_property" "hphp_set_static_property" "hphp_stats"
+ "hphp_thread_type" "hphp_throw_fatal_error" "hphp_to_string" "hphpd_auth_token" "hphpd_break"
+ "lz4hccompress" "memory_get_allocation" "mysql_async_connect_completed" "mysql_async_connect_start" "mysql_async_fetch_array"
+ "mysql_async_query_completed" "mysql_async_query_result" "mysql_async_query_start" "mysql_async_status" "mysql_async_wait_actionable"
+ "mysql_connect_with_db" "mysql_more_results" "mysql_multi_query" "mysql_next_result" "mysql_pconnect_with_db"
+ "mysql_set_timeout" "mysql_warning_count" "pagelet_server_flush" "pagelet_server_is_done" "pagelet_server_is_enabled"
+ "pagelet_server_task_result" "pagelet_server_task_start" "pagelet_server_task_status" "pagelet_server_tasks_started" "register_postsend_function"
+ "sncompress" "stream_await" "xbox_get_thread_time" "xbox_get_thread_timeout" "xbox_post_message"
+ "xbox_process_call_message" "xbox_schedule_thread_reset" "xbox_send_message" "xbox_set_thread_timeout" "xbox_task_result"
+ "xbox_task_start" "xbox_task_status" "xhprof_disable" "xhprof_enable" "xhprof_frame_begin"
+ "xhprof_frame_end" "xhprof_network_disable" "xhprof_network_enable" "xhprof_sample_disable" "xhprof_sample_enable"
+
+ ;; https://docs.hhvm.com/php/reference/
+ ;; Contains some duplicates with the above.
+ "abs" "acos" "acosh" "addcslashes" "addslashes"
+ "apc_add" "apc_cache_info" "apc_cas" "apc_clear_cache" "apc_dec"
+ "apc_delete" "apc_exists" "apc_fetch" "apc_inc" "apc_sma_info"
+ "apc_store" "array_change_key_case" "array_chunk" "array_column" "array_combine"
+ "array_count_values" "array_diff" "array_diff_assoc" "array_diff_key" "array_diff_uassoc"
+ "array_diff_ukey" "array_fill" "array_fill_keys" "array_filter" "array_flip"
+ "array_intersect" "array_intersect_assoc" "array_intersect_key" "array_intersect_uassoc" "array_intersect_ukey"
+ "array_key_exists" "array_keys" "array_map" "array_merge" "array_merge_recursive"
+ "array_multisort" "array_pad" "array_pop" "array_product" "array_push"
+ "array_rand" "array_reduce" "array_replace" "array_replace_recursive" "array_reverse"
+ "array_search" "array_shift" "array_slice" "array_splice" "array_sum"
+ "array_udiff" "array_udiff_assoc" "array_udiff_uassoc" "array_uintersect" "array_uintersect_assoc"
+ "array_uintersect_uassoc" "array_unique" "array_unshift" "array_values" "array_walk"
+ "array_walk_recursive" "arsort" "asin" "asinh" "asort"
+ "assert" "assert_options" "atan" "atan2" "atanh"
+ "base64_decode" "base64_encode" "base_convert" "basename" "bcadd"
+ "bccomp" "bcdiv" "bcmod" "bcmul" "bcpow"
+ "bcpowmod" "bcscale" "bcsqrt" "bcsub" "bin2hex"
+ "bind_textdomain_codeset" "bindec" "bindtextdomain" "boolval" "bzclose"
+ "bzcompress" "bzdecompress" "bzerrno" "bzerror" "bzerrstr"
+ "bzflush" "bzopen" "bzread" "bzwrite" "call_user_func"
+ "call_user_func_array" "call_user_method" "call_user_method_array" "ceil" "chdir"
+ "checkdate" "checkdnsrr" "chgrp" "chmod" "chop"
+ "chown" "chr" "chroot" "chunk_split" "class_alias"
+ "class_exists" "class_implements" "class_parents" "class_uses" "clearstatcache"
+ "closedir" "closelog" "compact" "connection_aborted" "connection_status"
+ "constant" "convert_cyr_string" "convert_uudecode" "convert_uuencode" "copy"
+ "cos" "cosh" "count" "count_chars" "crc32"
+ "crypt" "ctype_alnum" "ctype_alpha" "ctype_cntrl" "ctype_digit"
+ "ctype_graph" "ctype_lower" "ctype_print" "ctype_punct" "ctype_space"
+ "ctype_upper" "ctype_xdigit" "curl_close" "curl_copy_handle" "curl_errno"
+ "curl_error" "curl_exec" "curl_file_create" "curl_getinfo" "curl_init"
+ "curl_multi_add_handle" "curl_multi_close" "curl_multi_exec" "curl_multi_getcontent" "curl_multi_info_read"
+ "curl_multi_init" "curl_multi_remove_handle" "curl_multi_select" "curl_multi_setopt" "curl_multi_strerror"
+ "curl_reset" "curl_setopt" "curl_setopt_array" "curl_share_close" "curl_share_init"
+ "curl_share_setopt" "curl_strerror" "curl_version" "current" "date"
+ "date_add" "date_create" "date_create_from_format" "date_create_immutable" "date_date_set"
+ "date_default_timezone_get" "date_default_timezone_set" "date_diff" "date_format" "date_get_last_errors"
+ "date_interval_create_from_date_string" "date_interval_format" "date_isodate_set" "date_modify" "date_offset_get"
+ "date_parse" "date_parse_from_format" "date_sub" "date_sun_info" "date_sunrise"
+ "date_sunset" "date_time_set" "date_timestamp_get" "date_timestamp_set" "date_timezone_get"
+ "date_timezone_set" "dcgettext" "dcngettext" "debug_backtrace" "debug_print_backtrace"
+ "debug_zval_dump" "decbin" "dechex" "decoct" "define"
+ "define_syslog_variables" "defined" "deg2rad" "dgettext" "dir"
+ "dirname" "disk_free_space" "disk_total_space" "diskfreespace" "dl"
+ "dngettext" "dns_check_record" "dns_get_mx" "dns_get_record" "dom_import_simplexml"
+ "doubleval" "each" "end" "ereg" "ereg_replace"
+ "eregi" "eregi_replace" "error_get_last" "error_log" "error_reporting"
+ "escapeshellarg" "escapeshellcmd" "exec" "exif_imagetype" "exif_read_data"
+ "exif_tagname" "exif_thumbnail" "exp" "explode" "expm1"
+ "extension_loaded" "ezmlm_hash" "fclose" "feof" "fflush"
+ "fgetc" "fgetcsv" "fgets" "fgetss" "file"
+ "file_exists" "file_get_contents" "file_put_contents" "fileatime" "filectime"
+ "filegroup" "fileinode" "filemtime" "fileowner" "fileperms"
+ "filesize" "filetype" "filter_has_var" "filter_id" "filter_input"
+ "filter_input_array" "filter_list" "filter_var" "filter_var_array" "finfo_buffer"
+ "finfo_close" "finfo_file" "finfo_open" "finfo_set_flags" "floatval"
+ "flock" "floor" "flush" "fmod" "fnmatch"
+ "fopen" "forward_static_call" "forward_static_call_array" "fpassthru" "fprintf"
+ "fputcsv" "fputs" "fread" "fscanf" "fseek"
+ "fsockopen" "fstat" "ftell" "ftok" "ftruncate"
+ "func_get_arg" "func_get_args" "func_num_args" "function_exists" "fwrite"
+ "gc_collect_cycles" "gc_disable" "gc_enable" "gc_enabled" "gc_mem_caches"
+ "gd_info" "get_called_class" "get_cfg_var" "get_class" "get_class_methods"
+ "get_class_vars" "get_current_user" "get_declared_classes" "get_declared_interfaces" "get_declared_traits"
+ "get_defined_constants" "get_defined_functions" "get_defined_vars" "get_extension_funcs" "get_headers"
+ "get_html_translation_table" "get_include_path" "get_included_files" "get_loaded_extensions" "get_magic_quotes_gpc"
+ "get_magic_quotes_runtime" "get_meta_tags" "get_object_vars" "get_parent_class" "get_required_files"
+ "get_resource_type" "getcwd" "getdate" "getenv" "gethostbyaddr"
+ "gethostbyname" "gethostbynamel" "gethostname" "getimagesize" "getimagesizefromstring"
+ "getlastmod" "getmxrr" "getmygid" "getmyinode" "getmypid"
+ "getmyuid" "getopt" "getprotobyname" "getprotobynumber" "getrandmax"
+ "getrusage" "getservbyname" "getservbyport" "gettext" "gettimeofday"
+ "gettype" "glob" "gmdate" "gmmktime" "gmp_abs"
+ "gmp_add" "gmp_and" "gmp_clrbit" "gmp_cmp" "gmp_com"
+ "gmp_div" "gmp_div_q" "gmp_div_qr" "gmp_div_r" "gmp_divexact"
+ "gmp_fact" "gmp_gcd" "gmp_gcdext" "gmp_hamdist" "gmp_init"
+ "gmp_intval" "gmp_invert" "gmp_jacobi" "gmp_legendre" "gmp_mod"
+ "gmp_mul" "gmp_neg" "gmp_nextprime" "gmp_or" "gmp_perfect_square"
+ "gmp_popcount" "gmp_pow" "gmp_powm" "gmp_prob_prime" "gmp_random"
+ "gmp_root" "gmp_rootrem" "gmp_scan0" "gmp_scan1" "gmp_setbit"
+ "gmp_sign" "gmp_sqrt" "gmp_sqrtrem" "gmp_strval" "gmp_sub"
+ "gmp_testbit" "gmp_xor" "gmstrftime" "grapheme_extract" "grapheme_stripos"
+ "grapheme_stristr" "grapheme_strlen" "grapheme_strpos" "grapheme_strripos" "grapheme_strrpos"
+ "grapheme_strstr" "grapheme_substr" "gzclose" "gzcompress" "gzdecode"
+ "gzdeflate" "gzencode" "gzeof" "gzfile" "gzgetc"
+ "gzgets" "gzgetss" "gzinflate" "gzopen" "gzpassthru"
+ "gzputs" "gzread" "gzrewind" "gzseek" "gztell"
+ "gzuncompress" "gzwrite" "hash" "hash_algos" "hash_copy"
+ "hash_equals" "hash_file" "hash_final" "hash_hmac" "hash_hmac_file"
+ "hash_init" "hash_pbkdf2" "hash_update" "hash_update_file" "hash_update_stream"
+ "header" "header_register_callback" "header_remove" "headers_list" "headers_sent"
+ "hebrev" "hebrevc" "hex2bin" "hexdec" "html_entity_decode"
+ "htmlentities" "htmlspecialchars" "htmlspecialchars_decode" "http_build_query" "http_response_code"
+ "hypot" "iconv" "iconv_get_encoding" "iconv_mime_decode" "iconv_mime_decode_headers"
+ "iconv_mime_encode" "iconv_set_encoding" "iconv_strlen" "iconv_strpos" "iconv_strrpos"
+ "iconv_substr" "idate" "idn_to_ascii" "idn_to_utf8" "ignore_user_abort"
+ "image2wbmp" "image_type_to_extension" "image_type_to_mime_type" "imageaffine" "imageaffinematrixconcat"
+ "imageaffinematrixget" "imagealphablending" "imageantialias" "imagearc" "imagechar"
+ "imagecharup" "imagecolorallocate" "imagecolorallocatealpha" "imagecolorat" "imagecolorclosest"
+ "imagecolorclosestalpha" "imagecolorclosesthwb" "imagecolordeallocate" "imagecolorexact" "imagecolorexactalpha"
+ "imagecolormatch" "imagecolorresolve" "imagecolorresolvealpha" "imagecolorset" "imagecolorsforindex"
+ "imagecolorstotal" "imagecolortransparent" "imageconvolution" "imagecopy" "imagecopymerge"
+ "imagecopymergegray" "imagecopyresampled" "imagecopyresized" "imagecreate" "imagecreatefromgd"
+ "imagecreatefromgd2" "imagecreatefromgd2part" "imagecreatefromgif" "imagecreatefromjpeg" "imagecreatefrompng"
+ "imagecreatefromstring" "imagecreatefromwbmp" "imagecreatefromwebp" "imagecreatefromxbm" "imagecreatetruecolor"
+ "imagecrop" "imagecropauto" "imagedashedline" "imagedestroy" "imageellipse"
+ "imagefill" "imagefilledarc" "imagefilledellipse" "imagefilledpolygon" "imagefilledrectangle"
+ "imagefilltoborder" "imagefilter" "imageflip" "imagefontheight" "imagefontwidth"
+ "imageftbbox" "imagefttext" "imagegammacorrect" "imagegd" "imagegd2"
+ "imagegif" "imageinterlace" "imageistruecolor" "imagejpeg" "imagelayereffect"
+ "imageline" "imageloadfont" "imagepalettecopy" "imagepng" "imagepolygon"
+ "imagerectangle" "imagerotate" "imagesavealpha" "imagescale" "imagesetbrush"
+ "imagesetinterpolation" "imagesetpixel" "imagesetstyle" "imagesetthickness" "imagesettile"
+ "imagestring" "imagestringup" "imagesx" "imagesy" "imagetruecolortopalette"
+ "imagettfbbox" "imagettftext" "imagetypes" "imagewbmp" "imagewebp"
+ "imap_8bit" "imap_alerts" "imap_base64" "imap_binary" "imap_body"
+ "imap_bodystruct" "imap_check" "imap_clearflag_full" "imap_close" "imap_createmailbox"
+ "imap_delete" "imap_deletemailbox" "imap_errors" "imap_expunge" "imap_fetch_overview"
+ "imap_fetchbody" "imap_fetchheader" "imap_fetchstructure" "imap_gc" "imap_header"
+ "imap_headerinfo" "imap_last_error" "imap_list" "imap_listmailbox" "imap_mail"
+ "imap_mail_copy" "imap_mail_move" "imap_mailboxmsginfo" "imap_msgno" "imap_num_msg"
+ "imap_num_recent" "imap_open" "imap_ping" "imap_qprint" "imap_renamemailbox"
+ "imap_reopen" "imap_search" "imap_setflag_full" "imap_status" "imap_subscribe"
+ "imap_timeout" "imap_uid" "imap_undelete" "imap_unsubscribe" "imap_utf8"
+ "implode" "import_request_variables" "in_array" "inet_ntop" "inet_pton"
+ "ini_alter" "ini_get" "ini_get_all" "ini_restore" "ini_set"
+ "intdiv" "interface_exists" "intl_error_name" "intl_get_error_code" "intl_get_error_message"
+ "intl_is_failure" "intval" "ip2long" "iptcembed" "iptcparse"
+ "is_a" "is_array" "is_bool" "is_callable" "is_dir"
+ "is_double" "is_executable" "is_file" "is_finite" "is_float"
+ "is_infinite" "is_int" "is_integer" "is_link" "is_long"
+ "is_nan" "is_null" "is_numeric" "is_object" "is_readable"
+ "is_real" "is_resource" "is_scalar" "is_soap_fault" "is_string"
+ "is_subclass_of" "is_uploaded_file" "is_writable" "is_writeable" "iterator_apply"
+ "iterator_count" "iterator_to_array" "join" "jpeg2wbmp" "json_decode"
+ "json_encode" "json_last_error" "json_last_error_msg" "key" "key_exists"
+ "krsort" "ksort" "lcfirst" "lcg_value" "lchgrp"
+ "lchown" "ldap_add" "ldap_bind" "ldap_close" "ldap_compare"
+ "ldap_connect" "ldap_control_paged_result" "ldap_control_paged_result_response" "ldap_count_entries" "ldap_delete"
+ "ldap_dn2ufn" "ldap_err2str" "ldap_errno" "ldap_error" "ldap_escape"
+ "ldap_explode_dn" "ldap_first_attribute" "ldap_first_entry" "ldap_first_reference" "ldap_free_result"
+ "ldap_get_attributes" "ldap_get_dn" "ldap_get_entries" "ldap_get_option" "ldap_get_values"
+ "ldap_get_values_len" "ldap_list" "ldap_mod_add" "ldap_mod_del" "ldap_mod_replace"
+ "ldap_modify" "ldap_modify_batch" "ldap_next_attribute" "ldap_next_entry" "ldap_next_reference"
+ "ldap_parse_reference" "ldap_parse_result" "ldap_read" "ldap_rename" "ldap_search"
+ "ldap_set_option" "ldap_set_rebind_proc" "ldap_sort" "ldap_start_tls" "ldap_unbind"
+ "levenshtein" "libxml_clear_errors" "libxml_disable_entity_loader" "libxml_get_errors" "libxml_get_last_error"
+ "libxml_set_streams_context" "libxml_use_internal_errors" "link" "linkinfo" "localeconv"
+ "localtime" "log" "log10" "log1p" "long2ip"
+ "lstat" "ltrim" "magic_quotes_runtime" "mail" "mailparse_determine_best_xfer_encoding"
+ "mailparse_msg_create" "mailparse_msg_extract_part" "mailparse_msg_extract_part_file" "mailparse_msg_extract_whole_part_file" "mailparse_msg_free"
+ "mailparse_msg_get_part" "mailparse_msg_get_part_data" "mailparse_msg_get_structure" "mailparse_msg_parse" "mailparse_msg_parse_file"
+ "mailparse_rfc822_parse_addresses" "mailparse_stream_encode" "mailparse_uudecode_all" "max" "mb_check_encoding"
+ "mb_convert_case" "mb_convert_encoding" "mb_convert_kana" "mb_convert_variables" "mb_decode_mimeheader"
+ "mb_decode_numericentity" "mb_detect_encoding" "mb_detect_order" "mb_encode_mimeheader" "mb_encode_numericentity"
+ "mb_encoding_aliases" "mb_ereg" "mb_ereg_match" "mb_ereg_replace" "mb_ereg_search"
+ "mb_ereg_search_getpos" "mb_ereg_search_getregs" "mb_ereg_search_init" "mb_ereg_search_pos" "mb_ereg_search_regs"
+ "mb_ereg_search_setpos" "mb_eregi" "mb_eregi_replace" "mb_get_info" "mb_http_input"
+ "mb_http_output" "mb_internal_encoding" "mb_language" "mb_list_encodings" "mb_output_handler"
+ "mb_parse_str" "mb_preferred_mime_name" "mb_regex_encoding" "mb_regex_set_options" "mb_send_mail"
+ "mb_split" "mb_strcut" "mb_strimwidth" "mb_stripos" "mb_stristr"
+ "mb_strlen" "mb_strpos" "mb_strrchr" "mb_strrichr" "mb_strripos"
+ "mb_strrpos" "mb_strstr" "mb_strtolower" "mb_strtoupper" "mb_strwidth"
+ "mb_substitute_character" "mb_substr" "mb_substr_count" "mcrypt_cbc" "mcrypt_cfb"
+ "mcrypt_create_iv" "mcrypt_decrypt" "mcrypt_ecb" "mcrypt_enc_get_algorithms_name" "mcrypt_enc_get_block_size"
+ "mcrypt_enc_get_iv_size" "mcrypt_enc_get_key_size" "mcrypt_enc_get_modes_name" "mcrypt_enc_get_supported_key_sizes" "mcrypt_enc_is_block_algorithm"
+ "mcrypt_enc_is_block_algorithm_mode" "mcrypt_enc_is_block_mode" "mcrypt_enc_self_test" "mcrypt_encrypt" "mcrypt_generic"
+ "mcrypt_generic_deinit" "mcrypt_generic_end" "mcrypt_generic_init" "mcrypt_get_block_size" "mcrypt_get_cipher_name"
+ "mcrypt_get_iv_size" "mcrypt_get_key_size" "mcrypt_list_algorithms" "mcrypt_list_modes" "mcrypt_module_close"
+ "mcrypt_module_get_algo_block_size" "mcrypt_module_get_algo_key_size" "mcrypt_module_get_supported_key_sizes" "mcrypt_module_is_block_algorithm" "mcrypt_module_is_block_algorithm_mode"
+ "mcrypt_module_is_block_mode" "mcrypt_module_open" "mcrypt_module_self_test" "mcrypt_ofb" "md5"
+ "md5_file" "mdecrypt_generic" "memory_get_peak_usage" "memory_get_usage" "metaphone"
+ "method_exists" "microtime" "mime_content_type" "min" "mkdir"
+ "mktime" "money_format" "move_uploaded_file" "msg_get_queue" "msg_queue_exists"
+ "msg_receive" "msg_remove_queue" "msg_send" "msg_set_queue" "msg_stat_queue"
+ "mt_getrandmax" "mt_rand" "mt_srand" "mysql_affected_rows" "mysql_client_encoding"
+ "mysql_close" "mysql_connect" "mysql_create_db" "mysql_data_seek" "mysql_db_name"
+ "mysql_db_query" "mysql_drop_db" "mysql_errno" "mysql_error" "mysql_escape_string"
+ "mysql_fetch_array" "mysql_fetch_assoc" "mysql_fetch_field" "mysql_fetch_lengths" "mysql_fetch_object"
+ "mysql_fetch_row" "mysql_field_flags" "mysql_field_len" "mysql_field_name" "mysql_field_seek"
+ "mysql_field_table" "mysql_field_type" "mysql_free_result" "mysql_get_client_info" "mysql_get_host_info"
+ "mysql_get_proto_info" "mysql_get_server_info" "mysql_info" "mysql_insert_id" "mysql_list_dbs"
+ "mysql_list_fields" "mysql_list_processes" "mysql_list_tables" "mysql_num_fields" "mysql_num_rows"
+ "mysql_pconnect" "mysql_ping" "mysql_query" "mysql_real_escape_string" "mysql_result"
+ "mysql_select_db" "mysql_set_charset" "mysql_stat" "mysql_tablename" "mysql_thread_id"
+ "mysql_unbuffered_query" "mysqli_connect" "mysqli_escape_string" "mysqli_report" "natcasesort"
+ "natsort" "next" "ngettext" "nl2br" "nl_langinfo"
+ "number_format" "ob_clean" "ob_end_clean" "ob_end_flush" "ob_flush"
+ "ob_get_clean" "ob_get_contents" "ob_get_flush" "ob_get_length" "ob_get_level"
+ "ob_get_status" "ob_iconv_handler" "ob_implicit_flush" "ob_list_handlers" "ob_start"
+ "octdec" "opendir" "openlog" "openssl_cipher_iv_length" "openssl_csr_export"
+ "openssl_csr_export_to_file" "openssl_csr_get_public_key" "openssl_csr_get_subject" "openssl_csr_new" "openssl_csr_sign"
+ "openssl_decrypt" "openssl_digest" "openssl_encrypt" "openssl_error_string" "openssl_free_key"
+ "openssl_get_cipher_methods" "openssl_get_curve_names" "openssl_get_md_methods" "openssl_get_privatekey" "openssl_get_publickey"
+ "openssl_open" "openssl_pkcs12_export" "openssl_pkcs12_export_to_file" "openssl_pkcs12_read" "openssl_pkcs7_decrypt"
+ "openssl_pkcs7_encrypt" "openssl_pkcs7_sign" "openssl_pkcs7_verify" "openssl_pkey_export" "openssl_pkey_export_to_file"
+ "openssl_pkey_free" "openssl_pkey_get_details" "openssl_pkey_get_private" "openssl_pkey_get_public" "openssl_pkey_new"
+ "openssl_private_decrypt" "openssl_private_encrypt" "openssl_public_decrypt" "openssl_public_encrypt" "openssl_random_pseudo_bytes"
+ "openssl_seal" "openssl_sign" "openssl_verify" "openssl_x509_check_private_key" "openssl_x509_checkpurpose"
+ "openssl_x509_export" "openssl_x509_export_to_file" "openssl_x509_free" "openssl_x509_parse" "openssl_x509_read"
+ "ord" "output_add_rewrite_var" "output_reset_rewrite_vars" "pack" "parse_ini_file"
+ "parse_ini_string" "parse_str" "parse_url" "passthru" "password_get_info"
+ "password_hash" "password_needs_rehash" "password_verify" "pathinfo" "pclose"
+ "pcntl_alarm" "pcntl_exec" "pcntl_fork" "pcntl_getpriority" "pcntl_setpriority"
+ "pcntl_signal" "pcntl_signal_dispatch" "pcntl_sigprocmask" "pcntl_wait" "pcntl_waitpid"
+ "pcntl_wexitstatus" "pcntl_wifexited" "pcntl_wifsignaled" "pcntl_wifstopped" "pcntl_wstopsig"
+ "pcntl_wtermsig" "pfsockopen" "pg_affected_rows" "pg_cancel_query" "pg_client_encoding"
+ "pg_close" "pg_connect" "pg_connection_busy" "pg_connection_reset" "pg_connection_status"
+ "pg_dbname" "pg_escape_bytea" "pg_escape_identifier" "pg_escape_literal" "pg_escape_string"
+ "pg_execute" "pg_fetch_all" "pg_fetch_all_columns" "pg_fetch_array" "pg_fetch_assoc"
+ "pg_fetch_object" "pg_fetch_result" "pg_fetch_row" "pg_field_is_null" "pg_field_name"
+ "pg_field_num" "pg_field_prtlen" "pg_field_size" "pg_field_table" "pg_field_type"
+ "pg_field_type_oid" "pg_free_result" "pg_get_pid" "pg_get_result" "pg_host"
+ "pg_last_error" "pg_last_notice" "pg_last_oid" "pg_num_fields" "pg_num_rows"
+ "pg_options" "pg_parameter_status" "pg_pconnect" "pg_ping" "pg_port"
+ "pg_prepare" "pg_query" "pg_query_params" "pg_result_error" "pg_result_error_field"
+ "pg_result_seek" "pg_result_status" "pg_send_execute" "pg_send_prepare" "pg_send_query"
+ "pg_send_query_params" "pg_set_client_encoding" "pg_set_error_verbosity" "pg_transaction_status" "pg_unescape_bytea"
+ "pg_version" "php_ini_loaded_file" "php_ini_scanned_files" "php_sapi_name" "php_uname"
+ "phpinfo" "phpversion" "pi" "png2wbmp" "popen"
+ "pos" "posix_access" "posix_ctermid" "posix_errno" "posix_get_last_error"
+ "posix_getcwd" "posix_getegid" "posix_geteuid" "posix_getgid" "posix_getgrgid"
+ "posix_getgrnam" "posix_getgroups" "posix_getlogin" "posix_getpgid" "posix_getpgrp"
+ "posix_getpid" "posix_getppid" "posix_getpwnam" "posix_getpwuid" "posix_getrlimit"
+ "posix_getsid" "posix_getuid" "posix_initgroups" "posix_isatty" "posix_kill"
+ "posix_mkfifo" "posix_mknod" "posix_setegid" "posix_seteuid" "posix_setgid"
+ "posix_setpgid" "posix_setsid" "posix_setuid" "posix_strerror" "posix_times"
+ "posix_ttyname" "posix_uname" "pow" "preg_filter" "preg_grep"
+ "preg_last_error" "preg_match" "preg_match_all" "preg_quote" "preg_replace"
+ "preg_replace_callback" "preg_replace_callback_array" "preg_split" "prev" "print_r"
+ "printf" "proc_close" "proc_get_status" "proc_nice" "proc_open"
+ "proc_terminate" "property_exists" "putenv" "quoted_printable_decode" "quoted_printable_encode"
+ "quotemeta" "rad2deg" "rand" "random_bytes" "random_int"
+ "range" "rawurldecode" "rawurlencode" "read_exif_data" "readdir"
+ "readfile" "readgzfile" "readline" "readline_add_history" "readline_clear_history"
+ "readline_completion_function" "readline_info" "readline_read_history" "readline_write_history" "readlink"
+ "realpath" "register_shutdown_function" "rename" "reset" "restore_error_handler"
+ "restore_exception_handler" "restore_include_path" "rewind" "rewinddir" "rmdir"
+ "round" "rsort" "rtrim" "scandir" "sem_acquire"
+ "sem_get" "sem_release" "sem_remove" "serialize" "session_cache_expire"
+ "session_cache_limiter" "session_commit" "session_decode" "session_destroy" "session_encode"
+ "session_get_cookie_params" "session_id" "session_module_name" "session_name" "session_regenerate_id"
+ "session_register_shutdown" "session_save_path" "session_set_cookie_params" "session_set_save_handler" "session_start"
+ "session_status" "session_unset" "session_write_close" "set_error_handler" "set_exception_handler"
+ "set_file_buffer" "set_include_path" "set_magic_quotes_runtime" "set_time_limit" "setcookie"
+ "setlocale" "setrawcookie" "settype" "sha1" "sha1_file"
+ "shell_exec" "shm_attach" "shm_detach" "shm_get_var" "shm_has_var"
+ "shm_put_var" "shm_remove" "shm_remove_var" "shmop_close" "shmop_delete"
+ "shmop_open" "shmop_read" "shmop_size" "shmop_write" "shuffle"
+ "similar_text" "simplexml_import_dom" "simplexml_load_file" "simplexml_load_string" "sin"
+ "sinh" "sizeof" "sleep" "socket_accept" "socket_bind"
+ "socket_clear_error" "socket_close" "socket_connect" "socket_create" "socket_create_listen"
+ "socket_create_pair" "socket_get_option" "socket_get_status" "socket_getpeername" "socket_getsockname"
+ "socket_last_error" "socket_listen" "socket_read" "socket_recv" "socket_recvfrom"
+ "socket_select" "socket_send" "socket_sendto" "socket_set_block" "socket_set_blocking"
+ "socket_set_nonblock" "socket_set_option" "socket_set_timeout" "socket_shutdown" "socket_strerror"
+ "socket_write" "sodium_add" "sodium_bin2hex" "sodium_compare" "sodium_crypto_aead_aes256gcm_decrypt"
+ "sodium_crypto_aead_aes256gcm_encrypt" "sodium_crypto_aead_aes256gcm_is_available" "sodium_crypto_aead_aes256gcm_keygen" "sodium_crypto_aead_chacha20poly1305_decrypt" "sodium_crypto_aead_chacha20poly1305_encrypt"
+ "sodium_crypto_aead_chacha20poly1305_ietf_decrypt" "sodium_crypto_aead_chacha20poly1305_ietf_encrypt" "sodium_crypto_aead_chacha20poly1305_ietf_keygen" "sodium_crypto_aead_chacha20poly1305_keygen" "sodium_crypto_aead_xchacha20poly1305_ietf_decrypt"
+ "sodium_crypto_aead_xchacha20poly1305_ietf_encrypt" "sodium_crypto_aead_xchacha20poly1305_ietf_keygen" "sodium_crypto_auth" "sodium_crypto_auth_keygen" "sodium_crypto_auth_verify"
+ "sodium_crypto_box" "sodium_crypto_box_keypair" "sodium_crypto_box_keypair_from_secretkey_and_publickey" "sodium_crypto_box_open" "sodium_crypto_box_publickey"
+ "sodium_crypto_box_publickey_from_secretkey" "sodium_crypto_box_seal" "sodium_crypto_box_seal_open" "sodium_crypto_box_secretkey" "sodium_crypto_box_seed_keypair"
+ "sodium_crypto_generichash" "sodium_crypto_generichash_final" "sodium_crypto_generichash_init" "sodium_crypto_generichash_keygen" "sodium_crypto_generichash_update"
+ "sodium_crypto_kdf_derive_from_key" "sodium_crypto_kdf_keygen" "sodium_crypto_kx_client_session_keys" "sodium_crypto_kx_keypair" "sodium_crypto_kx_publickey"
+ "sodium_crypto_kx_secretkey" "sodium_crypto_kx_seed_keypair" "sodium_crypto_kx_server_session_keys" "sodium_crypto_pwhash" "sodium_crypto_pwhash_scryptsalsa208sha256"
+ "sodium_crypto_pwhash_scryptsalsa208sha256_str" "sodium_crypto_pwhash_scryptsalsa208sha256_str_verify" "sodium_crypto_pwhash_str" "sodium_crypto_pwhash_str_verify" "sodium_crypto_scalarmult"
+ "sodium_crypto_scalarmult_base" "sodium_crypto_secretbox" "sodium_crypto_secretbox_keygen" "sodium_crypto_secretbox_open" "sodium_crypto_shorthash"
+ "sodium_crypto_shorthash_keygen" "sodium_crypto_sign" "sodium_crypto_sign_detached" "sodium_crypto_sign_ed25519_pk_to_curve25519" "sodium_crypto_sign_ed25519_sk_to_curve25519"
+ "sodium_crypto_sign_keypair" "sodium_crypto_sign_keypair_from_secretkey_and_publickey" "sodium_crypto_sign_open" "sodium_crypto_sign_publickey" "sodium_crypto_sign_publickey_from_secretkey"
+ "sodium_crypto_sign_secretkey" "sodium_crypto_sign_seed_keypair" "sodium_crypto_sign_verify_detached" "sodium_crypto_stream" "sodium_crypto_stream_keygen"
+ "sodium_crypto_stream_xor" "sodium_hex2bin" "sodium_increment" "sodium_memcmp" "sodium_memzero"
+ "sort" "soundex" "spl_autoload" "spl_autoload_call" "spl_autoload_extensions"
+ "spl_autoload_functions" "spl_autoload_register" "spl_autoload_unregister" "spl_classes" "spl_object_hash"
+ "split" "spliti" "sprintf" "sql_regcase" "sqrt"
+ "srand" "sscanf" "stat" "str_getcsv" "str_ireplace"
+ "str_pad" "str_repeat" "str_replace" "str_rot13" "str_shuffle"
+ "str_split" "str_word_count" "strcasecmp" "strchr" "strcmp"
+ "strcoll" "strcspn" "stream_bucket_append" "stream_bucket_make_writeable" "stream_bucket_new"
+ "stream_bucket_prepend" "stream_context_create" "stream_context_get_default" "stream_context_get_options" "stream_context_get_params"
+ "stream_context_set_default" "stream_context_set_option" "stream_context_set_params" "stream_copy_to_stream" "stream_filter_append"
+ "stream_filter_prepend" "stream_filter_register" "stream_filter_remove" "stream_get_contents" "stream_get_filters"
+ "stream_get_line" "stream_get_meta_data" "stream_get_transports" "stream_get_wrappers" "stream_is_local"
+ "stream_register_wrapper" "stream_resolve_include_path" "stream_select" "stream_set_blocking" "stream_set_chunk_size"
+ "stream_set_read_buffer" "stream_set_timeout" "stream_set_write_buffer" "stream_socket_accept" "stream_socket_client"
+ "stream_socket_enable_crypto" "stream_socket_get_name" "stream_socket_pair" "stream_socket_recvfrom" "stream_socket_sendto"
+ "stream_socket_server" "stream_socket_shutdown" "stream_wrapper_register" "stream_wrapper_restore" "stream_wrapper_unregister"
+ "strftime" "strip_tags" "stripcslashes" "stripos" "stripslashes"
+ "stristr" "strlen" "strnatcasecmp" "strnatcmp" "strncasecmp"
+ "strncmp" "strpbrk" "strpos" "strptime" "strrchr"
+ "strrev" "strripos" "strrpos" "strspn" "strstr"
+ "strtok" "strtolower" "strtotime" "strtoupper" "strtr"
+ "strval" "substr" "substr_compare" "substr_count" "substr_replace"
+ "symlink" "sys_get_temp_dir" "sys_getloadavg" "syslog" "system"
+ "tan" "tanh" "tempnam" "textdomain" "time"
+ "time_nanosleep" "time_sleep_until" "timezone_abbreviations_list" "timezone_identifiers_list" "timezone_location_get"
+ "timezone_name_from_abbr" "timezone_name_get" "timezone_offset_get" "timezone_open" "timezone_transitions_get"
+ "timezone_version_get" "tmpfile" "touch" "trait_exists" "trigger_error"
+ "trim" "uasort" "ucfirst" "ucwords" "uksort"
+ "umask" "uniqid" "unlink" "unpack" "unserialize"
+ "urldecode" "urlencode" "use_soap_error_handler" "user_error" "usleep"
+ "usort" "utf8_decode" "utf8_encode" "var_dump" "var_export"
+ "version_compare" "vfprintf" "vprintf" "vsprintf" "wddx_add_vars"
+ "wddx_deserialize" "wddx_packet_end" "wddx_packet_start" "wddx_serialize_value" "wddx_serialize_vars"
+ "wordwrap" "xhprof_disable" "xhprof_enable" "xhprof_sample_disable" "xhprof_sample_enable"
+ "xml_error_string" "xml_get_current_byte_index" "xml_get_current_column_number" "xml_get_current_line_number" "xml_get_error_code"
+ "xml_parse" "xml_parse_into_struct" "xml_parser_create" "xml_parser_create_ns" "xml_parser_free"
+ "xml_parser_get_option" "xml_parser_set_option" "xml_set_character_data_handler" "xml_set_default_handler" "xml_set_element_handler"
+ "xml_set_end_namespace_decl_handler" "xml_set_external_entity_ref_handler" "xml_set_notation_decl_handler" "xml_set_object" "xml_set_processing_instruction_handler"
+ "xml_set_start_namespace_decl_handler" "xml_set_unparsed_entity_decl_handler" "zend_version" "zip_close" "zip_entry_close"
+ "zip_entry_compressedsize" "zip_entry_compressionmethod" "zip_entry_filesize" "zip_entry_name" "zip_entry_open"
+ "zip_entry_read" "zip_open" "zip_read" "zlib_decode" "zlib_encode"))
+ symbol-end))
+
+(defun hack--in-xhp-p (pos)
+ "Is POS inside an XHP expression?
+
+Returns t if we're in a text context, and nil if we're
+interpolating inside the XHP expression."
+ (let ((expression-start-pos
+ (get-text-property pos 'hack-xhp-expression)))
+ (and expression-start-pos
+ (not (hack--in-xhp-interpolation-p pos expression-start-pos)))))
+
+(defun hack--in-xhp-interpolation-p (pos xhp-start-pos)
+ "Is POS inside an interpolation inside XHP?"
+ (let* ((ppss (save-excursion (syntax-ppss pos)))
+ (paren-pos (nth 1 ppss)))
+ (and
+ paren-pos
+ (< xhp-start-pos paren-pos)
+ (eq (char-after paren-pos) ?\{))))
+
+(defun hack--search-forward-no-xhp (regex limit)
+ "Search forward for REGEX up to LIMIT, ignoring occurrences inside XHP blocks."
+ (let ((end-pos nil)
+ (match-data nil))
+ (save-excursion
+ (while (and (not end-pos)
+ (re-search-forward regex limit t))
+ (unless (hack--in-xhp-p (point))
+ (setq match-data (match-data))
+ (setq end-pos (point)))))
+ (when match-data
+ (set-match-data match-data)
+ (goto-char end-pos))))
+
+(defun hack--search-forward-keyword (limit)
+ "Search forward from point for an occurrence of a keyword."
+ ;; Keywords in PHP are case insensitive. In Hack, it's a syntax
+ ;; error if you use the wrong case, but they're still reserved (so
+ ;; you can't call a function CLASS).
+ (let ((case-fold-search t))
+ (hack--search-forward-no-xhp hack--keyword-regex limit)))
+
+(defun hack--search-forward-type (limit)
+ "Search forward from point for an occurrence of a type name."
+ (let ((case-fold-search nil))
+ (hack--search-forward-no-xhp hack--type-regex limit)))
+
+(defconst hack--user-constant-regex
+ (rx
+ symbol-start
+ ;; Constants are all in upper case, and cannot start with a
+ ;; digit.
+ (seq (any upper "_")
+ (+ (any upper "_" digit)))
+ symbol-end))
+
+(defun hack--search-forward-constant (limit)
+ "Search forward from point for an occurrence of a constant."
+ ;; true, false and null are case insensitive.
+ (let ((case-fold-search t))
+ (hack--search-forward-no-xhp
+ ;; null is also a type in Hack, but we highlight it as a
+ ;; constant. It's going to occur more often as a value than a type.
+ (regexp-opt '("null" "true" "false") 'symbols)
+ limit)))
+
+(defun hack--search-forward-user-constant (limit)
+ "Search forward from point for an occurrence of a constant."
+ (hack--search-forward-no-xhp hack--user-constant-regex
+ limit))
+
+(defun hack--search-forward-variable (limit)
+ "Search forward from point for an occurrence of a variable."
+ (hack--search-forward-no-xhp
+ (rx symbol-start
+ "$" (group (+ (or (syntax word) (syntax symbol))))
+ symbol-end)
+ limit))
+
+(defun hack--search-forward-built-in-function (limit)
+ "Search forward from point for an occurrence of a global built-in function."
+ (hack--search-forward-no-xhp hack--built-in-fun-regex limit))
+
+(defun hack--search-forward-function-name (limit)
+ "Search forward from point for an occurrence of a function name."
+ (hack--search-forward-no-xhp
+ (rx symbol-start
+ "function"
+ symbol-end
+ (+ space)
+ (group
+ symbol-start
+ (+ (or (syntax word) (syntax symbol)))
+ symbol-end))
+ limit))
+
+(defun hack--search-forward-dollardollar (limit)
+ "Search forward from point for an occurrence of $$."
+ (hack--search-forward-no-xhp
+ (rx symbol-start "$$" symbol-end)
+ limit))
+
+(defvar hack-font-lock-keywords
+ `(
+ (,hack--header-regex
+ (1 font-lock-keyword-face)
+ (2 font-lock-keyword-face t t))
+ ;; Handle XHP first, so we don't confuse vec
with the vec
+ ;; keyword.
+ (hack-font-lock-xhp
+ (0 'default))
+
+ (hack--search-forward-keyword
+ (0 'font-lock-keyword-face))
+
+ (hack--search-forward-constant
+ (0 'font-lock-constant-face))
+
+ (hack--search-forward-type
+ (1 'font-lock-type-face))
+
+ ;; We use font-lock-variable-name-face for consistency with c-mode.
+ (hack--search-forward-user-constant
+ (0 'font-lock-variable-name-face))
+
+ ;; $$ is a special variable used with the pipe operator
+ ;; |>. Highlight the entire string, to avoid confusion with $s.
+ (hack--search-forward-dollardollar
+ (0 'font-lock-variable-name-face))
+ ;; Highlight variables that start with $, e.g. $foo. Don't
+ ;; highlight the $, to make the name easier to read (consistent with php-mode).
+ (hack--search-forward-variable
+ (1 'font-lock-variable-name-face))
+
+ (hack--search-forward-built-in-function
+ (1 'font-lock-builtin-face))
+
+ (hack--search-forward-function-name
+ 1 font-lock-function-name-face)
+
+ (hack-font-lock-fallthrough
+ (1 'font-lock-keyword-face t))
+ (hack-font-lock-unsafe
+ (1 'error t))
+ (hack-font-lock-unsafe-expr
+ (1 'error t))
+ (hack-font-lock-fixme
+ (1 'error t))
+ (hack-font-lock-ignore-error
+ (1 'error t))
+
+ ;; TODO: It would be nice to highlight interpolation operators in
+ ;; Str\format or metacharacters in regexp literals too.
+ (hack-font-lock-interpolate
+ (0 font-lock-variable-name-face t))
+ (hack-font-lock-interpolate-complex
+ (0 font-lock-variable-name-face t))))
+
+(defvar hack-mode-syntax-table
+ (let ((table (make-syntax-table)))
+ ;; Characters that are punctuation, not symbol elements.
+ (modify-syntax-entry ?- "." table)
+ (modify-syntax-entry ?< "." table)
+ (modify-syntax-entry ?> "." table)
+ (modify-syntax-entry ?+ "." table)
+ (modify-syntax-entry ?& "." table)
+ (modify-syntax-entry ?| "." table)
+ (modify-syntax-entry ?= "." table)
+
+ ;; Treat \ as punctuation, so we can navigate between different
+ ;; parts of a namespace reference.
+ (modify-syntax-entry ?\\ "." table)
+
+ ;; Strings
+ (modify-syntax-entry ?\" "\"" table)
+ (modify-syntax-entry ?\\ "\\" table)
+ (modify-syntax-entry ?' "\"" table)
+
+ ;; Comments of the form
+ ;; # This is a single-line comment.
+ ;; Tag these as comment sequence b.
+ (modify-syntax-entry ?# "< b" table)
+
+ ;; / can start both // and /* style comments. When it's a second
+ ;; character, it's a single line comment, so also tag as comment
+ ;; sequence b.
+ (modify-syntax-entry ?/ ". 124b" table)
+
+ ;; * is the second character in /* comments, and the first
+ ;; character in the end */.
+ (modify-syntax-entry ?* ". 23" table)
+
+ ;; Newlines end both # and // comments. Ensure we support both
+ ;; unix and dos style newlines.
+ (modify-syntax-entry ?\n "> b" table)
+ (modify-syntax-entry ?\^m "> b" table)
+
+ ;; < and > are paired delimiters.
+ (modify-syntax-entry ?< "(>" table)
+ (modify-syntax-entry ?> ")<" table)
+
+ table))
+
+(defun hack--comment-prefix (s)
+ "Extract the leading '* ' from '* foo'."
+ (when (string-match
+ (rx bol (0+ space) (? "/") "*" (1+ space))
+ s)
+ (match-string 0 s)))
+
+(defun hack--wrap-comment-inner (s)
+ "Given a string of the form:
+
+* a very long sentence here...
+
+wrap it to:
+
+* a very long
+* sentence here..."
+ (let* ((prefix (hack--comment-prefix s))
+ (lines (s-lines s))
+ (stripped-lines
+ (mapcar
+ (lambda (line) (s-chop-prefix prefix line))
+ lines)))
+ (with-temp-buffer
+ (insert (s-join "\n" stripped-lines))
+ (goto-char (point-min))
+ (fill-paragraph)
+
+ (let* ((wrapped-lines (s-lines (buffer-string)))
+ (prefixed-lines
+ (mapcar
+ (lambda (line) (concat prefix line))
+ wrapped-lines)))
+ (s-join "\n" prefixed-lines)))))
+
+(defun hack--fill-paragraph-star ()
+ "Fill paragraph when point is inside a /* comment."
+ (let* ((line-start-pos (line-beginning-position))
+ (comment-start (nth 8 (syntax-ppss)))
+ (comment-end nil))
+ ;; Put a property on the text at point, so we can put point back afterwards.
+ (put-text-property (point) (1+ (point)) 'hack-fill-start-pos t)
+ (save-excursion
+ (search-forward "*/")
+ (setq comment-end (point)))
+ (save-excursion
+ (save-restriction
+ ;; Narrow to the comment, to ensure we don't move beyond the end.
+ (narrow-to-region comment-start comment-end)
+
+ ;; Move over all the non-empty comment lines, considering * to
+ ;; be an empty line.
+ (while (and
+ (not (eobp))
+ (not (looking-at
+ (rx
+ bol
+ (? "/")
+ (0+ space) (? "*")
+ (0+ space)
+ eol))))
+ (forward-line))
+
+ (if (eobp)
+ ;; Don't add the */ to our wrapped comment.
+ (progn
+ (forward-line -1)
+ (end-of-line))
+ ;; Exclude the trailing newline.
+ (backward-char))
+
+ (let ((contents (buffer-substring line-start-pos (point))))
+ (delete-region line-start-pos (point))
+ (insert (hack--wrap-comment-inner contents)))))
+ (while (and (not (eobp))
+ (not (get-text-property (point) 'hack-fill-start-pos)))
+ (forward-char))
+ (when (get-text-property (point) 'hack-fill-start-pos)
+ (remove-text-properties (point) (1+ (point)) '(hack-fill-start-pos nil)))))
+
+(defun hack-fill-paragraph (&optional _justify)
+ "Fill the paragraph at point."
+ (let* ((ppss (syntax-ppss))
+ (in-comment-p (nth 4 ppss))
+ (comment-start (nth 8 ppss))
+ (comment-end nil)
+ (in-star-comment-p nil))
+ (when in-comment-p
+ (save-excursion
+ (goto-char comment-start)
+ (when (looking-at (rx "/*"))
+ (setq in-star-comment-p t))))
+ (if in-star-comment-p
+ (progn
+ (hack--fill-paragraph-star)
+ t)
+ ;; Returning nil means that `fill-paragraph' will run, which is
+ ;; sufficient for // comments.
+ nil)))
+
+(defvar hack-xhp-indent-debug-on nil)
+
+(defun hack-xhp-indent-debug (&rest args)
+ "Log ARGS if ‘hack-xhp-indent-debug-on’ is set."
+ (if hack-xhp-indent-debug-on
+ (apply 'message args)))
+
+(defun hack-xhp-in-code-p ()
+ "Return non-nil if point is currently in code,
+i.e. not in a comment or string."
+ (let* ((ppss (syntax-ppss))
+ (in-string (nth 3 ppss))
+ (in-comment (nth 4 ppss)))
+ (and (not in-comment)
+ (not in-string))))
+
+(defun hack-xhp-indent-previous-semi (limit)
+ "Search backward from point for the last semicolon.
+Return nil if no semicolons were found before we reached position LIMIT.
+Ignore semicolons in strings and comments."
+ (if (not limit)
+ (setq limit (point-min)))
+ (when (> (point) limit)
+ (let (res
+ (keep-going t))
+ (save-excursion
+ (while keep-going
+ (setq keep-going nil)
+
+ (when (search-backward ";" limit t)
+ (if (hack-xhp-in-code-p)
+ (setq keep-going t)
+ ;; semi found, done.
+ (setq res (point)))))
+ res))))
+
+(defun hack-xhp-backward-whitespace ()
+ "Move backwards until point is not on whitespace."
+ (catch 'done
+ (while t
+ (when (bobp)
+ (throw 'done nil))
+
+ (let ((prev-char (char-after (1- (point))))
+ (prev-syntax (syntax-after (1- (point)))))
+ (unless (or (eq prev-char ?\n)
+ ;; 0 is the syntax code for whitespace.
+ (eq 0 (car-safe prev-syntax))
+ ;; Ignore whitespace in comments.
+ (nth 4 (syntax-ppss)))
+ (throw 'done nil)))
+
+ (backward-char))))
+
+(defun hack-xhp-enclosing-brace-pos ()
+ "Return the position of the innermost enclosing brace before point."
+ (nth 1 (syntax-ppss)))
+
+(defun hack-xhp-indent-offset ()
+ "If point is inside an XHP expression, return the correct indentation amount.
+Return nil otherwise."
+ (let* ((start-pos (point))
+ (min-brace
+ (save-excursion
+ ;; get out of anything being typed that might confuse the parsing
+ (beginning-of-line)
+ (hack-xhp-enclosing-brace-pos)))
+ (min (save-excursion
+ (or
+ (hack-xhp-indent-previous-semi min-brace)
+ min-brace
+ ;; skip past (point) min)
+ (re-search-backward hack-xhp-start-regex min t)
+ (hack-xhp-in-code-p))
+ (setq
+ base-indent
+ ;; decide from this context if indentation should
+ ;; be initially adjusted.
+ (+
+ ;; start with the indentation at this elt
+ (current-indentation)
+ ;; at the matched xhp element, figure out if the
+ ;; indentation should be modified
+ ;; TODO(abrady) too lazy to parse forward properly, these
+ ;; work fine for now.
+ (cond
+ ;; CASE 1: matched elt is closed or self-closing e.g.
+ ;; or a 1-line enclosed stmt: foo
+ ((save-excursion
+ (beginning-of-line)
+ (or
+ (re-search-forward "" (line-end-position) t)
+ (re-search-forward "/> *$" start-pos t)
+ (re-search-forward "--> *$" start-pos t)))
+ 0)
+ ;; DEFAULT: increase indent
+ (t hack-indent-offset))))))
+ ;; STEP 2: indentation adjustment based on what user has typed so far
+ (if base-indent
+ ;; STEP 2.1: we found indentation to adjust. use the current
+ ;; context to determine how it should be adjusted
+ (cond
+ ;; CASE 0: indenting an attribute
+ ((looking-at "^ *[a-zA-Z_-]+")
+ base-indent)
+ ;; CASE 1: Terminating a multiline php block is a special
+ ;; case where we should default to php indentation as if we
+ ;; were inside the braces
+ ;; e.g.
+ ((save-excursion
+ (and
+ (not (re-search-forward "^ *<" (line-end-position) t))
+ (re-search-forward "}> *$" (line-end-position) t)))
+ (hack-xhp-indent-debug "terminating php block")
+ nil)
+ ;; CASE 2: user is indenting a closing block, so out-dent
+ ;; e.g.
+ ;;
+ ;;
+ ((save-excursion
+ (beginning-of-line)
+ (re-search-forward "^ *" (line-end-position) t))
+ (+ base-indent (- hack-indent-offset)))
+ ;; CASE 3: if this happens to be /> on its own
+ ;; line, reduce indent (coding standard)
+ ((save-excursion
+ (goto-char start-pos)
+ (re-search-forward "^ */> *" (line-end-position) t))
+ (+ base-indent (- hack-indent-offset)))
+ ;; CASE 4: close of xhp passed to a function, e.g.
+ ;; foo(
+ ;;
+ ;; );
+ ((save-excursion
+ (re-search-forward "^ *);" (line-end-position) t))
+ (+ base-indent (- hack-indent-offset)))
+ ;; DEFAULT: no modification.
+ (t base-indent))
+ ;; STEP 2.2: FIRST STATEMENT AFTER XHP. if we're after
+ ;; the close of an xhp statement it still messes up the php
+ ;; indentation, so check that here and override
+ (cond
+ ;; CASE 1: multiline self-enclosing tag or closing tag
+ ;; e.g.
+ ;; ;
+ ;; - or -
+ ;;
+ ;; ...
+ ;;
;
+ ((save-excursion
+ (hack-xhp-backward-whitespace)
+ (and
+ (looking-back "\\(/>\\|\\);" nil)
+ ;; don't match single-line xhp $foo = ;
+ (not (re-search-backward "^ *\\$" (line-beginning-position) t))))
+ ;; previous statement IS xhp. check what user has typed so
+ ;; far
+ (+
+ (save-excursion (hack-xhp-backward-whitespace) (current-indentation))
+ (cond
+ ;; CASE 0: user typed a brace. outdent even more
+ ((looking-at ".*}") (* -2 hack-indent-offset))
+ ;; CASE 1: close of case in a switch stmt, e.g. case FOO:
+ ((looking-at ".*: *$") (* -2 hack-indent-offset))
+ ;; DEFAULT
+ (t (- hack-indent-offset)))))
+ ;; DEFAULT: not first stmt after xhp, indent normally
+ (t nil)))))
+
+(defun hack--indent-preserve-point (offset)
+ "Indent the current line by OFFSET spaces.
+Ensure point is still on the same part of the line afterwards."
+ (let ((point-offset (- (current-column) (current-indentation))))
+ (indent-line-to offset)
+
+ ;; 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)))))
+
+(defun hack--paren-depth-for-indent (pos)
+ "Return the number of parentheses around POS.
+Repeated parens on the same line are consider a single paren."
+ (let ((depth 0)
+ ;; Keep tracking of which line we saw each paren on. Since
+ ;; calculating line number is O(n), just track the start
+ ;; position of each line.
+ (prev-paren-line-start nil))
+ (save-excursion
+ (goto-char pos)
+ (catch 'done
+ (while t
+ (let* ((ppss (syntax-ppss))
+ (paren-start (nth 1 ppss)))
+ (if paren-start
+ (progn
+ (goto-char paren-start)
+ (unless (eq (line-beginning-position) prev-paren-line-start)
+ (setq depth (1+ depth)))
+ (setq prev-paren-line-start (line-beginning-position)))
+ (throw 'done t))))))
+ depth))
+
+(defun hack--ends-with-infix-p (str)
+ "Does STR end with an infix operator?"
+ (string-match-p
+ (rx
+ space
+ ;; https://docs.hhvm.com/hack/expressions-and-operators/operator-precedence
+ (or "*" "/" "%" "+" "-" "."
+ "<<" ">>" "<" "<=" ">" ">="
+ "==" "!=" "===" "!==" "<=>"
+ "&" "^" "|" "&&" "||" "?:" "??" "|>"
+ "=" "+=" "-=" ".=" "*=" "/=" "%=" "<<=" ">>=" "&=" "^=" "|="
+ "instanceof" "is" "as" "?as")
+ (0+ space)
+ line-end)
+ str))
+
+(defun hack--current-line ()
+ "The current line enclosing point."
+ (buffer-substring
+ (line-beginning-position) (line-end-position)))
+
+(defun hack--switch-count ()
+ "Return a count of the switch blocks that enclose point.
+
+Given the code, where | is point:
+
+function foo() {
+ if ($bar) {
+ switch ($baz) {
+ |
+ }
+ }
+}
+
+Then this function returns 1."
+ (let ((switches 0)
+ enclosing-paren-pos)
+ (save-excursion
+ (setq enclosing-paren-pos (nth 1 (syntax-ppss)))
+ (while enclosing-paren-pos
+ (goto-char enclosing-paren-pos)
+ (let* ((line (s-trim (hack--current-line)))
+ (symbols (s-split (rx symbol-end) line t))
+ (symbol (car-safe symbols)))
+ (when (string= symbol "switch")
+ (setq switches (1+ switches))))
+ (setq enclosing-paren-pos (nth 1 (syntax-ppss)))))
+ switches))
+
+(defun hack-indent-line ()
+ "Indent the current line of Hack code.
+Preserves point position in the line where possible."
+ (interactive)
+ (if (hack--in-xhp-p (point))
+ (let ((indent (hack-xhp-indent-offset)))
+ (when indent
+ (hack--indent-preserve-point indent)))
+ (hack--indent-line)))
+
+(defun hack--indent-line ()
+ "Indent the current line of non-XHP Hack code."
+ (let* ((syntax-bol (syntax-ppss (line-beginning-position)))
+ (in-multiline-string-p (nth 3 syntax-bol))
+ (point-offset (- (current-column) (current-indentation)))
+ (paren-depth (hack--paren-depth-for-indent (line-beginning-position)))
+
+ (ppss (syntax-ppss (line-beginning-position)))
+ (current-paren-pos (nth 1 ppss))
+ (text-after-paren
+ (when current-paren-pos
+ (save-excursion
+ (goto-char current-paren-pos)
+ (buffer-substring
+ (1+ current-paren-pos)
+ (line-end-position)))))
+ (in-multiline-comment-p (nth 4 ppss))
+ (current-line (hack--current-line)))
+ ;; If the current line is just a closing paren, unindent by one level.
+ (when (and
+ (not in-multiline-comment-p)
+ (string-match-p (rx bol (0+ space) (or ")" "}")) current-line))
+ (setq paren-depth (1- paren-depth)))
+ (cond
+ ;; Don't indent inside heredoc/nowdoc strings.
+ (in-multiline-string-p
+ nil)
+ ;; In multiline comments, ensure the leading * is indented by one
+ ;; more space. For example:
+ ;; /*
+ ;; * <- this line
+ ;; */
+ (in-multiline-comment-p
+ ;; Don't modify lines that don't start with *, to avoid changing the indentation of commented-out code.
+ (when (or (string-match-p (rx bol (0+ space) "*") current-line)
+ (string= "" current-line))
+ (hack--indent-preserve-point (1+ (* hack-indent-offset paren-depth)))))
+ ;; Indent according to the last paren position, if there is text
+ ;; after the paren. For example:
+ ;; foo(bar,
+ ;; baz, <- this line
+ ((and
+ text-after-paren
+ (not (string-match-p (rx bol (0+ space) eol) text-after-paren)))
+ (let (open-paren-column)
+ (save-excursion
+ (goto-char current-paren-pos)
+ (setq open-paren-column (current-column)))
+ (hack--indent-preserve-point (1+ open-paren-column))))
+ ;; Indent according to the amount of nesting.
+ (t
+ (let ((current-line (s-trim current-line))
+ prev-line)
+ (save-excursion
+ ;; Try to go back one line.
+ (when (zerop (forward-line -1))
+ (setq prev-line
+ (buffer-substring (line-beginning-position) (line-end-position)))))
+
+ ;; Increase indent if the past line ended with an infix
+ ;; operator, so we get
+ ;; $x =
+ ;; foo();
+ (when (and prev-line
+ (hack--ends-with-infix-p prev-line))
+ (setq paren-depth (1+ paren-depth)))
+
+ ;; Increase indent for lines that are method calls or pipe expressions.
+ ;;
+ ;; $foo
+ ;; ->bar(); <- this line
+ (when (or (s-starts-with-p "->" current-line)
+ (s-starts-with-p "?->" current-line)
+ (s-starts-with-p "|>" current-line))
+ (setq paren-depth (1+ paren-depth))))
+
+ ;; Inside switch statements.
+ (let ((switch-count (hack--switch-count)))
+ (when (> switch-count 0)
+ ;; Expressions inside switch statements should be further
+ ;; indented, so they are underneath the case or default.
+ (setq paren-depth (+ paren-depth switch-count))
+
+ ;; The case or default line should be outdented one step.
+ (when (string-match-p (rx bol (* whitespace) (or "case" "default:" "}"))
+ current-line)
+ (setq paren-depth (1- paren-depth)))))
+
+ (hack--indent-preserve-point (* hack-indent-offset paren-depth))))
+ ;; 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)))))
+
+(defalias 'hack-xhp-indent-line 'hack-indent-line)
+
+;; hh_server can choke if you symlink your www root
+(setq find-file-visit-truename t)
+
+;; Ensure that we use `hack-mode' for .php files, but put the
+;; association at the end of the list so `php-mode' wins (if installed).
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.php$" . hack-mode) t)
+
+;; These extensions are hack-specific.
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.hhi$" . hack-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.hack$" . hack-mode))
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.hck$" . hack-mode))
+
+(defun hack-format ()
+ "Format the current buffer or region with hackfmt."
+ (interactive)
+ (if (use-region-p)
+ (hack--format-region)
+ (hack-format-buffer)))
+
+(defun hack--format-region ()
+ "Format the active region."
+ (interactive)
+ (let* ((start (save-restriction
+ (widen)
+ (region-beginning)))
+ (end (save-restriction
+ (widen)
+ (region-end)))
+ (command
+ (format "%s --range %d %d %s"
+ hack-hackfmt-name
+ start end (buffer-file-name))))
+ (shell-command-on-region
+ start
+ end
+ command nil t)))
+
+(defun hack-format-buffer ()
+ "Format the current buffer with hackfmt."
+ (interactive)
+ (let ((src-buf (current-buffer))
+ (src (buffer-string))
+ (start-line (line-number-at-pos (point)))
+ (start-column (current-column))
+ (output-buf (get-buffer-create "*hackfmt*")))
+ (with-current-buffer output-buf
+ (erase-buffer)
+ (insert src)
+ (if (zerop
+ (call-process-region (point-min) (point-max)
+ hack-hackfmt-name t t nil))
+ (progn
+ (unless (string= (buffer-string) src)
+ ;; We've changed something, so update the source buffer.
+ (copy-to-buffer src-buf (point-min) (point-max)))
+ (kill-buffer))
+ (error "Hackfmt failed, see *hackfmt* buffer for details")))
+ ;; Do our best to restore point position.
+ (goto-char (point-min))
+ (forward-line (1- start-line))
+ (forward-char start-column)))
+
+(defun hack--maybe-format ()
+ (when hack-format-on-save
+ (hack-format-buffer)))
+
+(defun hack-enable-format-on-save ()
+ "Enable automatic formatting on the current hack-mode buffer.."
+ (interactive)
+ (setq-local hack-format-on-save t))
+
+(defun hack-disable-format-on-save ()
+ "Disable automatic formatting on the current hack-mode buffer.."
+ (interactive)
+ (setq-local hack-format-on-save nil))
+
+;;;###autoload
+(define-derived-mode hack-mode prog-mode "Hack"
+ "Major mode for editing Hack code.
+
+\\{hack-mode-map\\}"
+ ;; Remove any old text properties, so we don't get stuck with
+ ;; incorrect regions of hack-xhp-expression.
+ (set-text-properties (point-min) (point-max) nil)
+
+ (setq-local font-lock-defaults '(hack-font-lock-keywords))
+ (set (make-local-variable 'syntax-propertize-function)
+ hack--syntax-propertize-function)
+
+ (setq-local compile-command (concat hack-client-program-name " --from emacs"))
+ (setq-local indent-line-function #'hack-indent-line)
+ (setq-local comment-start "// ")
+ (setq-local fill-paragraph-function #'hack-fill-paragraph)
+ (setq imenu-generic-expression
+ ;; TODO: distinguish functions from methods.
+ `(("Function"
+ ,(rx
+ line-start
+ (? (* space) (seq symbol-start (or "private" "protected" "public") symbol-end))
+ (? (* space) (seq symbol-start "static" symbol-end))
+ (? (* space) (seq symbol-start "async" symbol-end))
+ (* space)
+ symbol-start "function" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)
+ ("Class"
+ ,(rx
+ line-start
+ (? (* space) (seq symbol-start "abstract" symbol-end))
+ (? (* space) (seq symbol-start "final" symbol-end))
+ (* space)
+ symbol-start "class" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)
+ ("Interface"
+ ,(rx symbol-start "interface" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)
+ ("Trait"
+ ,(rx symbol-start "trait" symbol-end
+ (+ space)
+ (group (seq symbol-start (+? any) symbol-end)))
+ 1)))
+
+ (add-hook 'before-save-hook #'hack--maybe-format nil t))
+
+
+
+(provide 'hack-mode)
+;;; hack-mode.el ends here
diff --git a/src/parse/syntax.rs b/src/parse/syntax.rs
index b519cb006..81b5e61c0 100644
--- a/src/parse/syntax.rs
+++ b/src/parse/syntax.rs
@@ -695,6 +695,11 @@ pub fn split_words_and_numbers(s: &str) -> Vec<&str> {
res
}
+/// Given the text `content` from a comment or strings, split it into
+/// MatchedPos values for the novel and unchanged words.
+///
+/// If there is negligible text in common with `opposite_content`,
+/// treat the whole `content` as a single novel region.
fn split_atom_words(
content: &str,
pos: SingleLineSpan,
@@ -709,6 +714,17 @@ fn split_atom_words(
let content_parts = split_words_and_numbers(content);
let other_parts = split_words_and_numbers(opposite_content);
+ let word_diffs = myers_diff::slice_by_hash(&content_parts, &other_parts);
+
+ if !has_common_words(&word_diffs) {
+ return vec![MatchedPos {
+ kind: MatchKind::Novel {
+ highlight: TokenKind::Atom(kind),
+ },
+ pos,
+ }];
+ }
+
let content_newlines = NewlinePositions::from(content);
let opposite_content_newlines = NewlinePositions::from(opposite_content);
@@ -716,7 +732,7 @@ fn split_atom_words(
let mut opposite_offset = 0;
let mut res = vec![];
- for diff_res in myers_diff::slice_by_hash(&content_parts, &other_parts) {
+ for diff_res in word_diffs {
match diff_res {
myers_diff::DiffResult::Left(word) => {
// This word is novel to this side.
@@ -765,6 +781,34 @@ fn split_atom_words(
res
}
+/// Are there sufficient common words that we should only highlight
+/// individual changed words?
+fn has_common_words(word_diffs: &Vec>) -> bool {
+ let mut word_count = 0;
+ for word_diff in word_diffs {
+ match word_diff {
+ myers_diff::DiffResult::Both(word, _) => {
+ // If we have at least one long word (i.e. not just
+ // punctuation), that's sufficient.
+ if word.len() > 2 {
+ return true;
+ }
+
+ // If we have lots of common short words, not just the
+ // beginning/end comment delimiter, that qualifies
+ // too.
+ word_count += 1;
+ if word_count > 4 {
+ return true;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ false
+}
+
impl MatchedPos {
fn new(
ck: ChangeKind,
@@ -1135,15 +1179,15 @@ mod tests {
}
#[test]
- fn test_split_comment_words_basic() {
- let content = "abc";
+ fn test_split_atom_words() {
+ let content = "abc def";
let pos = SingleLineSpan {
line: 0.into(),
start_col: 0,
- end_col: 3,
+ end_col: 7,
};
- let opposite_content = "def";
+ let opposite_content = "abc";
let opposite_pos = SingleLineSpan {
line: 0.into(),
start_col: 0,
@@ -1159,16 +1203,38 @@ mod tests {
);
assert_eq!(
res,
- vec![MatchedPos {
- kind: MatchKind::NovelWord {
- highlight: TokenKind::Atom(AtomKind::Comment),
+ vec![
+ MatchedPos {
+ kind: MatchKind::NovelLinePart {
+ highlight: TokenKind::Atom(AtomKind::Comment),
+ self_pos: SingleLineSpan {
+ line: 0.into(),
+ start_col: 0,
+ end_col: 3
+ },
+ opposite_pos: vec![SingleLineSpan {
+ line: 0.into(),
+ start_col: 0,
+ end_col: 3
+ }]
+ },
+ pos: SingleLineSpan {
+ line: 0.into(),
+ start_col: 0,
+ end_col: 3
+ },
},
- pos: SingleLineSpan {
- line: 0.into(),
- start_col: 0,
- end_col: 3
+ MatchedPos {
+ kind: MatchKind::NovelWord {
+ highlight: TokenKind::Atom(AtomKind::Comment),
+ },
+ pos: SingleLineSpan {
+ line: 0.into(),
+ start_col: 4,
+ end_col: 7
+ },
},
- },]
+ ]
);
}