aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/progmodes
diff options
context:
space:
mode:
authorStephen Leake2019-04-11 14:00:02 -0700
committerStephen Leake2019-04-11 14:00:02 -0700
commit7ba7def5caf7ec9d9bebffff489f0a658229fbda (patch)
treee0cfcb59937ca0528fb81769d7d48a904a91f5dc /lisp/progmodes
parent7768581172e11be52b1fcd8224f4594e126bbdb7 (diff)
parentde238b39e335c6814283faa171b35145f124edf2 (diff)
downloademacs-7ba7def5caf7ec9d9bebffff489f0a658229fbda.tar.gz
emacs-7ba7def5caf7ec9d9bebffff489f0a658229fbda.zip
Merge commit 'de238b39e335c6814283faa171b35145f124edf2'
Diffstat (limited to 'lisp/progmodes')
-rw-r--r--lisp/progmodes/bug-reference.el6
-rw-r--r--lisp/progmodes/compile.el5
-rw-r--r--lisp/progmodes/grep.el12
-rw-r--r--lisp/progmodes/js.el1200
-rw-r--r--lisp/progmodes/python.el40
-rw-r--r--lisp/progmodes/sh-script.el3
-rw-r--r--lisp/progmodes/verilog-mode.el6
7 files changed, 983 insertions, 289 deletions
diff --git a/lisp/progmodes/bug-reference.el b/lisp/progmodes/bug-reference.el
index 759db1f5686..813ecbe3847 100644
--- a/lisp/progmodes/bug-reference.el
+++ b/lisp/progmodes/bug-reference.el
@@ -1,4 +1,4 @@
1;; bug-reference.el --- buttonize bug references 1;; bug-reference.el --- buttonize bug references -*- lexical-binding: t; -*-
2 2
3;; Copyright (C) 2008-2019 Free Software Foundation, Inc. 3;; Copyright (C) 2008-2019 Free Software Foundation, Inc.
4 4
@@ -91,7 +91,7 @@ The second subexpression should match the bug reference (usually a number)."
91(bug-reference-set-overlay-properties) 91(bug-reference-set-overlay-properties)
92 92
93(defun bug-reference-unfontify (start end) 93(defun bug-reference-unfontify (start end)
94 "Remove bug reference overlays from region." 94 "Remove bug reference overlays from the region between START and END."
95 (dolist (o (overlays-in start end)) 95 (dolist (o (overlays-in start end))
96 (when (eq (overlay-get o 'category) 'bug-reference) 96 (when (eq (overlay-get o 'category) 'bug-reference)
97 (delete-overlay o)))) 97 (delete-overlay o))))
@@ -99,7 +99,7 @@ The second subexpression should match the bug reference (usually a number)."
99(defvar bug-reference-prog-mode) 99(defvar bug-reference-prog-mode)
100 100
101(defun bug-reference-fontify (start end) 101(defun bug-reference-fontify (start end)
102 "Apply bug reference overlays to region." 102 "Apply bug reference overlays to the region between START and END."
103 (save-excursion 103 (save-excursion
104 (let ((beg-line (progn (goto-char start) (line-beginning-position))) 104 (let ((beg-line (progn (goto-char start) (line-beginning-position)))
105 (end-line (progn (goto-char end) (line-end-position)))) 105 (end-line (progn (goto-char end) (line-end-position))))
diff --git a/lisp/progmodes/compile.el b/lisp/progmodes/compile.el
index 5bfb0bf9018..1a0d9bdbb70 100644
--- a/lisp/progmodes/compile.el
+++ b/lisp/progmodes/compile.el
@@ -562,7 +562,7 @@ LINE, END-LINE, COL, and END-COL can also be functions of no argument
562that return the corresponding line or column number. They can assume REGEXP 562that return the corresponding line or column number. They can assume REGEXP
563has just been matched, and should correspondingly preserve this match data. 563has just been matched, and should correspondingly preserve this match data.
564 564
565f/usr/shaTYPE is 2 or nil for a real error or 1 for warning or 0 for info. 565TYPE is 2 or nil for a real error or 1 for warning or 0 for info.
566TYPE can also be of the form (WARNING . INFO). In that case this 566TYPE can also be of the form (WARNING . INFO). In that case this
567will be equivalent to 1 if the WARNING'th subexpression matched 567will be equivalent to 1 if the WARNING'th subexpression matched
568or else equivalent to 0 if the INFO'th subexpression matched. 568or else equivalent to 0 if the INFO'th subexpression matched.
@@ -2056,8 +2056,7 @@ by replacing the first word, e.g., `compilation-scroll-output' from
2056 (if (boundp 'byte-compile-bound-variables) 2056 (if (boundp 'byte-compile-bound-variables)
2057 (memq (cdr v) byte-compile-bound-variables))) 2057 (memq (cdr v) byte-compile-bound-variables)))
2058 `(set (make-local-variable ',(car v)) ,(cdr v)))) 2058 `(set (make-local-variable ',(car v)) ,(cdr v))))
2059 '(compilation-buffer-name-function 2059 '(compilation-directory-matcher
2060 compilation-directory-matcher
2061 compilation-error 2060 compilation-error
2062 compilation-error-regexp-alist 2061 compilation-error-regexp-alist
2063 compilation-error-regexp-alist-alist 2062 compilation-error-regexp-alist-alist
diff --git a/lisp/progmodes/grep.el b/lisp/progmodes/grep.el
index c0f47159c95..8c7a58fd8bd 100644
--- a/lisp/progmodes/grep.el
+++ b/lisp/progmodes/grep.el
@@ -959,8 +959,16 @@ substitution string. Note dynamic scoping of variables.")
959The pattern can include shell wildcards. As whitespace triggers 959The pattern can include shell wildcards. As whitespace triggers
960completion when entering a pattern, including it requires 960completion when entering a pattern, including it requires
961quoting, e.g. `\\[quoted-insert]<space>'." 961quoting, e.g. `\\[quoted-insert]<space>'."
962 (let* ((bn (or (buffer-file-name) 962 (let* ((grep-read-files-function (get major-mode 'grep-read-files))
963 (replace-regexp-in-string "<[0-9]+>\\'" "" (buffer-name)))) 963 (file-name-at-point
964 (run-hook-with-args-until-success 'file-name-at-point-functions))
965 (bn (if grep-read-files-function
966 (funcall grep-read-files-function)
967 (or (if (and (stringp file-name-at-point)
968 (not (file-directory-p file-name-at-point)))
969 file-name-at-point)
970 (buffer-file-name)
971 (replace-regexp-in-string "<[0-9]+>\\'" "" (buffer-name)))))
964 (fn (and bn 972 (fn (and bn
965 (stringp bn) 973 (stringp bn)
966 (file-name-nondirectory bn))) 974 (file-name-nondirectory bn)))
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 4d91da73340..a0adaa84eeb 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -50,7 +50,6 @@
50(require 'imenu) 50(require 'imenu)
51(require 'moz nil t) 51(require 'moz nil t)
52(require 'json) 52(require 'json)
53(require 'sgml-mode)
54(require 'prog-mode) 53(require 'prog-mode)
55 54
56(eval-when-compile 55(eval-when-compile
@@ -66,7 +65,7 @@
66 65
67;;; Constants 66;;; Constants
68 67
69(defconst js--name-start-re "[a-zA-Z_$]" 68(defconst js--name-start-re (concat "[[:alpha:]_$]")
70 "Regexp matching the start of a JavaScript identifier, without grouping.") 69 "Regexp matching the start of a JavaScript identifier, without grouping.")
71 70
72(defconst js--stmt-delim-chars "^;{}?:") 71(defconst js--stmt-delim-chars "^;{}?:")
@@ -572,6 +571,119 @@ then the \".\"s will be lined up:
572 :safe 'booleanp 571 :safe 'booleanp
573 :group 'js) 572 :group 'js)
574 573
574(defcustom js-jsx-detect-syntax t
575 "When non-nil, automatically detect whether JavaScript uses JSX.
576`js-jsx-syntax' (which see) may be made buffer-local and set to
577t. The detection strategy can be customized by adding elements
578to `js-jsx-regexps', which see."
579 :version "27.1"
580 :type 'boolean
581 :safe 'booleanp
582 :group 'js)
583
584(defcustom js-jsx-syntax nil
585 "When non-nil, parse JavaScript with consideration for JSX syntax.
586
587This enables proper font-locking and indentation of code using
588Facebook’s “JSX” syntax extension for JavaScript, for use with
589Facebook’s “React” library. Font-locking is like sgml-mode.
590Indentation is also like sgml-mode, although some indentation
591behavior may differ slightly to align more closely with the
592conventions of the React developer community.
593
594When `js-mode' is already enabled, you should call
595`js-jsx-enable' to set this variable.
596
597It is set to be buffer-local (and t) when in `js-jsx-mode'."
598 :version "27.1"
599 :type 'boolean
600 :safe 'booleanp
601 :group 'js)
602
603(defcustom js-jsx-align->-with-< t
604 "When non-nil, “>” will be indented to the opening “<” in JSX.
605
606When this is enabled, JSX indentation looks like this:
607
608 <element
609 attr=\"\"
610 >
611 </element>
612 <input
613 />
614
615When this is disabled, JSX indentation looks like this:
616
617 <element
618 attr=\"\"
619 >
620 </element>
621 <input
622 />"
623 :version "27.1"
624 :type 'boolean
625 :safe 'booleanp
626 :group 'js)
627
628(defcustom js-jsx-indent-level nil
629 "When non-nil, indent JSX by this value, instead of like JS.
630
631Let `js-indent-level' be 4. When this variable is also set to
632nil, JSX indentation looks like this (consistent):
633
634 return (
635 <element>
636 <element>
637 Hello World!
638 </element>
639 </element>
640 )
641
642Alternatively, when this variable is also set to 2, JSX
643indentation looks like this (different):
644
645 return (
646 <element>
647 <element>
648 Hello World!
649 </element>
650 </element>
651 )"
652 :version "27.1"
653 :type 'integer
654 :safe (lambda (x) (or (null x) (integerp x)))
655 :group 'js)
656;; This is how indentation behaved out-of-the-box until Emacs 27. JSX
657;; indentation was controlled with `sgml-basic-offset', which defaults
658;; to 2, whereas `js-indent-level' defaults to 4. Users who had the
659;; same values configured for both their HTML and JS indentation would
660;; luckily get consistent JSX indentation; most others were probably
661;; unhappy. I’d be surprised if anyone actually wants different
662;; indentation levels, but just in case, here’s a way back to that.
663
664(defcustom js-jsx-attribute-offset 0
665 "Specifies a delta for JSXAttribute indentation.
666
667Let `js-indent-level' be 2. When this variable is also set to 0,
668JSXAttribute indentation looks like this:
669
670 <element
671 attribute=\"value\">
672 </element>
673
674Alternatively, when this variable is also set to 2, JSXAttribute
675indentation looks like this:
676
677 <element
678 attribute=\"value\">
679 </element>
680
681This variable is like `sgml-attribute-offset'."
682 :version "27.1"
683 :type 'integer
684 :safe 'integerp
685 :group 'js)
686
575;;; KeyMap 687;;; KeyMap
576 688
577(defvar js-mode-map 689(defvar js-mode-map
@@ -1485,6 +1597,102 @@ point of view of font-lock. It applies highlighting directly with
1485 ;; Matcher always "fails" 1597 ;; Matcher always "fails"
1486 nil) 1598 nil)
1487 1599
1600;; It wouldn’t be sufficient to font-lock JSX with mere regexps, since
1601;; a JSXElement may be nested inside a JS expression within the
1602;; boundaries of a parent JSXOpeningElement, and such a hierarchy
1603;; ought to be fontified like JSX, JS, and JSX respectively:
1604;;
1605;; <div attr={void(<div></div>) && void(0)}></div>
1606;;
1607;; <div attr={ ← JSX
1608;; void( ← JS
1609;; <div></div> ← JSX
1610;; ) && void(0) ← JS
1611;; }></div> ← JSX
1612;;
1613;; `js-syntax-propertize' unambiguously identifies JSX syntax,
1614;; including when it’s nested.
1615;;
1616;; Using a matcher function for each relevant part, retrieve match
1617;; data recorded as syntax properties for fontification.
1618
1619(defconst js-jsx--font-lock-keywords
1620 `((js-jsx--match-tag-name 0 font-lock-function-name-face t)
1621 (js-jsx--match-attribute-name 0 font-lock-variable-name-face t)
1622 (js-jsx--match-text 0 'default t) ; “Undo” keyword fontification.
1623 (js-jsx--match-tag-beg)
1624 (js-jsx--match-tag-end)
1625 (js-jsx--match-expr))
1626 "JSX font lock faces and multiline text properties.")
1627
1628(defun js-jsx--match-tag-name (limit)
1629 "Match JSXBoundaryElement names, until LIMIT."
1630 (when js-jsx-syntax
1631 (let ((pos (next-single-char-property-change (point) 'js-jsx-tag-name nil limit))
1632 value)
1633 (when (and pos (> pos (point)))
1634 (goto-char pos)
1635 (or (and (setq value (get-text-property pos 'js-jsx-tag-name))
1636 (progn (set-match-data value) t))
1637 (js-jsx--match-tag-name limit))))))
1638
1639(defun js-jsx--match-attribute-name (limit)
1640 "Match JSXAttribute names, until LIMIT."
1641 (when js-jsx-syntax
1642 (let ((pos (next-single-char-property-change (point) 'js-jsx-attribute-name nil limit))
1643 value)
1644 (when (and pos (> pos (point)))
1645 (goto-char pos)
1646 (or (and (setq value (get-text-property pos 'js-jsx-attribute-name))
1647 (progn (set-match-data value) t))
1648 (js-jsx--match-attribute-name limit))))))
1649
1650(defun js-jsx--match-text (limit)
1651 "Match JSXText, until LIMIT."
1652 (when js-jsx-syntax
1653 (let ((pos (next-single-char-property-change (point) 'js-jsx-text nil limit))
1654 value)
1655 (when (and pos (> pos (point)))
1656 (goto-char pos)
1657 (or (and (setq value (get-text-property pos 'js-jsx-text))
1658 (progn (set-match-data value)
1659 (put-text-property (car value) (cadr value) 'font-lock-multiline t)
1660 t))
1661 (js-jsx--match-text limit))))))
1662
1663(defun js-jsx--match-tag-beg (limit)
1664 "Match JSXBoundaryElements from start, until LIMIT."
1665 (when js-jsx-syntax
1666 (let ((pos (next-single-char-property-change (point) 'js-jsx-tag-beg nil limit))
1667 value)
1668 (when (and pos (> pos (point)))
1669 (goto-char pos)
1670 (or (and (setq value (get-text-property pos 'js-jsx-tag-beg))
1671 (progn (put-text-property pos (cdr value) 'font-lock-multiline t) t))
1672 (js-jsx--match-tag-beg limit))))))
1673
1674(defun js-jsx--match-tag-end (limit)
1675 "Match JSXBoundaryElements from end, until LIMIT."
1676 (when js-jsx-syntax
1677 (let ((pos (next-single-char-property-change (point) 'js-jsx-tag-end nil limit))
1678 value)
1679 (when (and pos (> pos (point)))
1680 (goto-char pos)
1681 (or (and (setq value (get-text-property pos 'js-jsx-tag-end))
1682 (progn (put-text-property value pos 'font-lock-multiline t) t))
1683 (js-jsx--match-tag-end limit))))))
1684
1685(defun js-jsx--match-expr (limit)
1686 "Match JSXExpressionContainers, until LIMIT."
1687 (when js-jsx-syntax
1688 (let ((pos (next-single-char-property-change (point) 'js-jsx-expr nil limit))
1689 value)
1690 (when (and pos (> pos (point)))
1691 (goto-char pos)
1692 (or (and (setq value (get-text-property pos 'js-jsx-expr))
1693 (progn (put-text-property pos value 'font-lock-multiline t) t))
1694 (js-jsx--match-expr limit))))))
1695
1488(defconst js--font-lock-keywords-3 1696(defconst js--font-lock-keywords-3
1489 `( 1697 `(
1490 ;; This goes before keywords-2 so it gets used preferentially 1698 ;; This goes before keywords-2 so it gets used preferentially
@@ -1596,7 +1804,10 @@ point of view of font-lock. It applies highlighting directly with
1596 (forward-symbol -1) 1804 (forward-symbol -1)
1597 (end-of-line)) 1805 (end-of-line))
1598 '(end-of-line) 1806 '(end-of-line)
1599 '(0 font-lock-variable-name-face)))) 1807 '(0 font-lock-variable-name-face)))
1808
1809 ;; jsx (when enabled)
1810 ,@js-jsx--font-lock-keywords)
1600 "Level three font lock for `js-mode'.") 1811 "Level three font lock for `js-mode'.")
1601 1812
1602(defun js--inside-pitem-p (pitem) 1813(defun js--inside-pitem-p (pitem)
@@ -1722,9 +1933,414 @@ This performs fontification according to `js--class-styles'."
1722 'syntax-table (string-to-syntax "\"/")) 1933 'syntax-table (string-to-syntax "\"/"))
1723 (goto-char end))))) 1934 (goto-char end)))))
1724 1935
1936(defconst js--unary-keyword-re
1937 (js--regexp-opt-symbol '("await" "delete" "typeof" "void" "yield"))
1938 "Regexp matching unary operator keywords.")
1939
1940(defun js--unary-keyword-p (string)
1941 "Check if STRING is a unary operator keyword in JavaScript."
1942 (string-match-p js--unary-keyword-re string))
1943
1944;; Adding `syntax-multiline' text properties to JSX isn’t sufficient
1945;; to identify multiline JSX when first typing it. For instance, if
1946;; the user is typing a JSXOpeningElement for the first time…
1947;;
1948;; <div
1949;; ^ (point)
1950;;
1951;; …and the user inserts a line break after the tag name (before the
1952;; JSXOpeningElement starting on that line has been unambiguously
1953;; identified as such), then the `syntax-propertize' region won’t be
1954;; extended backwards to the start of the JSXOpeningElement:
1955;;
1956;; <div ← This line wasn’t JSX when last edited.
1957;; attr=""> ← Despite completing the JSX, the next
1958;; ^ `syntax-propertize' region wouldn’t magically
1959;; extend back a few lines.
1960;;
1961;; Therefore, to try and recover from this scenario, parse backward
1962;; from “>” to try and find the start of JSXBoundaryElements, and
1963;; extend the `syntax-propertize' region there.
1964
1965(defun js--syntax-propertize-extend-region (start end)
1966 "Extend the START-END region for propertization, if necessary.
1967For use by `syntax-propertize-extend-region-functions'."
1968 (if js-jsx-syntax (js-jsx--syntax-propertize-extend-region start end)))
1969
1970(defun js-jsx--syntax-propertize-extend-region (start end)
1971 "Extend the START-END region for propertization, if necessary.
1972If any “>” in the region appears to be the end of a tag starting
1973before the start of the region, extend region backwards to the
1974start of that tag so parsing may proceed from that point.
1975For use by `syntax-propertize-extend-region-functions'."
1976 (let (new-start
1977 forward-sexp-function ; Use the Lisp version.
1978 parse-sexp-lookup-properties) ; Fix backward-sexp error here.
1979 (catch 'stop
1980 (goto-char start)
1981 (while (re-search-forward ">" end t)
1982 (catch 'continue
1983 ;; Check if this is really a right shift bitwise operator
1984 ;; (“>>” or “>>>”).
1985 (unless (or (eq (char-before (1- (point))) ?>)
1986 (eq (char-after) ?>))
1987 (save-excursion
1988 (backward-char)
1989 (while (progn (if (= (point) (point-min)) (throw 'continue nil))
1990 (/= (char-before) ?<))
1991 (skip-chars-backward " \t\n")
1992 (if (= (point) (point-min)) (throw 'continue nil))
1993 (cond
1994 ((memq (char-before) '(?\" ?\' ?\` ?\}))
1995 (condition-case nil
1996 (backward-sexp)
1997 (scan-error (throw 'continue nil))))
1998 ((memq (char-before) '(?\/ ?\=)) (backward-char))
1999 ((looking-back js--dotted-name-re (line-beginning-position) t)
2000 (goto-char (match-beginning 0)))
2001 (t (throw 'continue nil))))
2002 (when (< (point) start)
2003 (setq new-start (1- (point)))
2004 (throw 'stop nil)))))))
2005 (if new-start (cons new-start end))))
2006
2007;; When applying syntax properties, since `js-syntax-propertize' uses
2008;; `syntax-propertize-rules' to parse JSXBoundaryElements iteratively
2009;; and statelessly, whenever we exit such an element, we need to
2010;; determine the JSX depth. If >0, then we know we to apply syntax
2011;; properties to JSXText up until the next JSXBoundaryElement occurs.
2012;; But if the JSX depth is 0, then—importantly—we know to NOT parse
2013;; the following code as JSXText, rather propertize it as regular JS
2014;; as long as warranted.
2015;;
2016;; Also, when indenting code, we need to know if the code we’re trying
2017;; to indent is on the 2nd or later line of multiline JSX, in which
2018;; case the code is indented according to XML-like JSX conventions.
2019;;
2020;; For the aforementioned reasons, we find ourselves needing to
2021;; determine whether point is enclosed in JSX or not; and, if so,
2022;; where the JSX is. The following functions provide that knowledge.
2023
2024(defconst js-jsx--tag-start-re
2025 (concat "\\(" js--dotted-name-re "\\)\\(?:"
2026 ;; Whitespace is only necessary if an attribute implies JSX.
2027 "\\(?:\\s-\\|\n\\)*[{/>]"
2028 "\\|"
2029 "\\(?:\\s-\\|\n\\)+" js--name-start-re
2030 "\\)")
2031 "Regexp unambiguously matching a JSXOpeningElement.")
2032
2033(defun js-jsx--matched-tag-type ()
2034 "Determine if the last “<” was a JSXBoundaryElement and its type.
2035Return `close' for a JSXClosingElement/JSXClosingFragment match,
2036return `self-closing' for some self-closing JSXOpeningElements,
2037else return `other'."
2038 (cond
2039 ((= (char-after) ?/) (forward-char) 'close) ; JSXClosingElement/JSXClosingFragment
2040 ((= (char-after) ?>) (forward-char) 'other) ; JSXOpeningFragment
2041 ((and (looking-at js-jsx--tag-start-re) ; JSXOpeningElement
2042 (not (js--unary-keyword-p (match-string 1))))
2043 (goto-char (match-end 0))
2044 (if (= (char-before) ?/) 'self-closing 'other))))
2045
2046(defconst js-jsx--self-closing-re "/\\s-*>"
2047 "Regexp matching the end of a self-closing JSXOpeningElement.")
2048
2049(defun js-jsx--matching-close-tag-pos ()
2050 "Return position of the closer of the opener before point.
2051Assuming a JSXOpeningElement or a JSXOpeningFragment is
2052immediately before point, find a matching JSXClosingElement or
2053JSXClosingFragment, skipping over any nested JSXElements to find
2054the match. Return nil if a match can’t be found."
2055 (let ((tag-stack 1) tag-pos type last-pos pos)
2056 (catch 'stop
2057 (while (and (re-search-forward "<\\s-*" nil t) (not (eobp)))
2058 (when (setq tag-pos (match-beginning 0)
2059 type (js-jsx--matched-tag-type))
2060 (when last-pos
2061 (setq pos (point))
2062 (goto-char last-pos)
2063 (while (re-search-forward js-jsx--self-closing-re pos 'move)
2064 (setq tag-stack (1- tag-stack))))
2065 (if (eq type 'close)
2066 (progn
2067 (setq tag-stack (1- tag-stack))
2068 (when (= tag-stack 0)
2069 (throw 'stop tag-pos)))
2070 ;; JSXOpeningElements that we know are self-closing aren’t
2071 ;; added to the stack at all (because point is already
2072 ;; past that syntax).
2073 (unless (eq type 'self-closing)
2074 (setq tag-stack (1+ tag-stack))))
2075 (setq last-pos (point)))))))
2076
2077(defun js-jsx--enclosing-tag-pos ()
2078 "Return beginning and end of a JSXElement about point.
2079Look backward for a JSXElement that both starts before point and
2080also ends at/after point. That may be either a self-closing
2081JSXElement or a JSXOpeningElement/JSXClosingElement pair."
2082 (let ((start (point)) tag-beg tag-beg-pos tag-end-pos close-tag-pos)
2083 (while
2084 (and
2085 (setq tag-beg (js--backward-text-property 'js-jsx-tag-beg))
2086 (progn
2087 (setq tag-beg-pos (point)
2088 tag-end-pos (cdr tag-beg))
2089 (not
2090 (or
2091 (and (eq (car tag-beg) 'self-closing)
2092 (< start tag-end-pos))
2093 (and (eq (car tag-beg) 'open)
2094 (or (< start tag-end-pos)
2095 (progn
2096 (unless
2097 ;; Try to read a cached close position,
2098 ;; but it might not be available yet.
2099 (setq close-tag-pos
2100 (get-text-property (point) 'js-jsx-close-tag-pos))
2101 (save-excursion
2102 (goto-char tag-end-pos)
2103 (setq close-tag-pos (js-jsx--matching-close-tag-pos)))
2104 (when close-tag-pos
2105 ;; Cache the close position to make future
2106 ;; searches faster.
2107 (put-text-property
2108 (point) (1+ (point))
2109 'js-jsx-close-tag-pos close-tag-pos)))
2110 ;; The JSXOpeningElement may be unclosed, else
2111 ;; the closure must occur at/after the start
2112 ;; point (otherwise, a miscellaneous previous
2113 ;; JSXOpeningElement has been found, so keep
2114 ;; looking backwards for an enclosing one).
2115 (or (not close-tag-pos) (<= start close-tag-pos)))))))))
2116 ;; Don’t return the last tag pos, as it wasn’t enclosing.
2117 (setq tag-beg nil close-tag-pos nil))
2118 (and tag-beg (list tag-beg-pos tag-end-pos close-tag-pos))))
2119
2120(defun js-jsx--at-enclosing-tag-child-p ()
2121 "Return t if point is at an enclosing tag’s child."
2122 (let ((pos (save-excursion (js-jsx--enclosing-tag-pos))))
2123 (and pos (>= (point) (nth 1 pos)))))
2124
2125;; We implement `syntax-propertize-function' logic fully parsing JSX
2126;; in order to provide very accurate JSX indentation, even in the most
2127;; complex cases (e.g. to indent JSX within a JS expression within a
2128;; JSXAttribute…), as over the years users have requested this. Since
2129;; we find so much information during this parse, we later use some of
2130;; the useful bits for font-locking, too.
2131;;
2132;; Some extra effort is devoted to ensuring that no code which could
2133;; possibly be valid JS is ever misinterpreted as partial JSX, since
2134;; that would be regressive.
2135;;
2136;; We first parse trying to find the minimum number of components
2137;; necessary to unambiguously identify a JSXBoundaryElement, even if
2138;; it is a partial one. If a complete one is parsed, we move on to
2139;; parse any JSXText. When that’s terminated, we unwind back to the
2140;; `syntax-propertize-rules' loop so the next JSXBoundaryElement can
2141;; be parsed, if any, be it an opening or closing one.
2142
2143(defun js-jsx--put-syntax-table (start end value)
2144 "Set syntax-table text property from START to END as VALUE.
2145Redundantly set the value to two properties, syntax-table and
2146js-jsx-syntax-table. Derivative modes that remove syntax-table
2147text properties may recover the value from the second property." ; i.e. js2-mode
2148 (add-text-properties start end (list 'syntax-table value
2149 'js-jsx-syntax-table value)))
2150
2151(defun js-jsx--text-range (beg end)
2152 "Identify JSXText within a “>/{/}/<” pair."
2153 (when (> (- end beg) 0)
2154 (save-excursion
2155 (goto-char beg)
2156 (while (and (skip-chars-forward " \t\n" end) (< (point) end))
2157 ;; Comments and string quotes don’t serve their usual
2158 ;; syntactic roles in JSXText; make them plain punctuation to
2159 ;; negate those roles.
2160 (when (or (= (char-after) ?/) ; comment
2161 (= (syntax-class (syntax-after (point))) 7)) ; string quote
2162 (js-jsx--put-syntax-table (point) (1+ (point)) '(1)))
2163 (forward-char)))
2164 ;; Mark JSXText so it can be font-locked as non-keywords.
2165 (put-text-property beg (1+ beg) 'js-jsx-text (list beg end (current-buffer)))
2166 ;; Ensure future propertization beginning from within the
2167 ;; JSXText determines JSXText context from earlier lines.
2168 (put-text-property beg end 'syntax-multiline t)))
2169
2170;; In order to respect the end boundary `syntax-propertize-function'
2171;; sets, care is taken in the following functions to abort parsing
2172;; whenever that boundary is reached.
2173
2174(defun js-jsx--syntax-propertize-tag-text (end)
2175 "Determine if JSXText is before END and propertize it.
2176Text within an open/close tag pair may be JSXText. Temporarily
2177interrupt JSXText by JSXExpressionContainers, and terminate
2178JSXText when another JSXBoundaryElement is encountered. Despite
2179terminations, all JSXText will be identified once all the
2180JSXBoundaryElements within an outermost JSXElement’s tree have
2181been propertized."
2182 (let ((text-beg (point))
2183 forward-sexp-function) ; Use Lisp version.
2184 (catch 'stop
2185 (while (re-search-forward "[{<]" end t)
2186 (js-jsx--text-range text-beg (1- (point)))
2187 (cond
2188 ((= (char-before) ?{)
2189 (let (expr-beg expr-end)
2190 (condition-case nil
2191 (save-excursion
2192 (backward-char)
2193 (setq expr-beg (point))
2194 (forward-sexp)
2195 (setq expr-end (point)))
2196 (scan-error nil))
2197 ;; Recursively propertize the JSXExpressionContainer’s
2198 ;; (possibly-incomplete) expression.
2199 (js-syntax-propertize (1+ expr-beg) (if expr-end (min (1- expr-end) end) end))
2200 ;; Ensure future propertization beginning from within the
2201 ;; (possibly-incomplete) expression can determine JSXText
2202 ;; context from earlier lines.
2203 (put-text-property expr-beg (1+ expr-beg) 'js-jsx-expr (or expr-end end)) ; font-lock
2204 (put-text-property expr-beg (if expr-end (min expr-end end) end) 'syntax-multiline t) ; syntax-propertize
2205 ;; Exit the JSXExpressionContainer if that’s possible,
2206 ;; else move to the end of the propertized area.
2207 (goto-char (if expr-end (min expr-end end) end))))
2208 ((= (char-before) ?<)
2209 (backward-char) ; Ensure the next tag can be propertized.
2210 (throw 'stop nil)))
2211 (setq text-beg (point))))))
2212
2213(defconst js-jsx--attribute-name-re (concat js--name-start-re
2214 "\\(?:\\s_\\|\\sw\\|-\\)*")
2215 "Like `js--name-re', but matches “-” as well.")
2216
2217(defun js-jsx--syntax-propertize-tag (end)
2218 "Determine if a JSXBoundaryElement is before END and propertize it.
2219Disambiguate JSX from inequality operators and arrow functions by
2220testing for syntax only valid as JSX."
2221 (let ((tag-beg (1- (point))) tag-end (type 'open)
2222 name-beg name-match-data expr-attribute-beg unambiguous
2223 forward-sexp-function) ; Use Lisp version.
2224 (catch 'stop
2225 (while (and (< (point) end)
2226 (progn (skip-chars-forward " \t\n" end)
2227 (< (point) end)))
2228 (cond
2229 ((= (char-after) ?>)
2230 ;; Make the closing “>” a close parenthesis.
2231 (js-jsx--put-syntax-table (point) (1+ (point)) '(5))
2232 (forward-char)
2233 (setq unambiguous t)
2234 (throw 'stop nil))
2235 ;; Handle a JSXSpreadChild (“<Foo {...bar}”) or a
2236 ;; JSXExpressionContainer as a JSXAttribute value
2237 ;; (“<Foo bar={…}”). Check this early in case continuing a
2238 ;; JSXAttribute parse.
2239 ((or (and name-beg (= (char-after) ?{))
2240 (setq expr-attribute-beg nil))
2241 (setq unambiguous t) ; JSXExpressionContainer post tag name ⇒ JSX
2242 (when expr-attribute-beg
2243 ;; Remember that this JSXExpressionContainer is part of a
2244 ;; JSXAttribute, as that can affect its expression’s
2245 ;; indentation.
2246 (put-text-property
2247 (point) (1+ (point)) 'js-jsx-expr-attribute expr-attribute-beg)
2248 (setq expr-attribute-beg nil))
2249 (let (expr-end)
2250 (condition-case nil
2251 (save-excursion
2252 (forward-sexp)
2253 (setq expr-end (point)))
2254 (scan-error nil))
2255 (forward-char)
2256 (if (>= (point) end) (throw 'stop nil))
2257 (skip-chars-forward " \t\n" end)
2258 (if (>= (point) end) (throw 'stop nil))
2259 (if (= (char-after) ?}) (forward-char) ; Shortcut to bail.
2260 ;; Recursively propertize the JSXExpressionContainer’s
2261 ;; expression.
2262 (js-syntax-propertize (point) (if expr-end (min (1- expr-end) end) end))
2263 ;; Exit the JSXExpressionContainer if that’s possible,
2264 ;; else move to the end of the propertized area.
2265 (goto-char (if expr-end (min expr-end end) end)))))
2266 ((= (char-after) ?/)
2267 ;; Assume a tag is an open tag until a slash is found, then
2268 ;; figure out what type it actually is.
2269 (if (eq type 'open) (setq type (if name-beg 'self-closing 'close)))
2270 (forward-char))
2271 ((and (not name-beg) (looking-at js--dotted-name-re))
2272 ;; Don’t match code like “if (i < await foo)”
2273 (if (js--unary-keyword-p (match-string 0)) (throw 'stop nil))
2274 ;; Save boundaries for later fontification after
2275 ;; unambiguously determining the code is JSX.
2276 (setq name-beg (match-beginning 0)
2277 name-match-data (match-data))
2278 (goto-char (match-end 0)))
2279 ((and name-beg (looking-at js-jsx--attribute-name-re))
2280 (setq unambiguous t) ; Non-unary name followed by 2nd name ⇒ JSX
2281 ;; Save JSXAttribute’s name’s match data for font-locking later.
2282 (put-text-property (match-beginning 0) (1+ (match-beginning 0))
2283 'js-jsx-attribute-name (match-data))
2284 (goto-char (match-end 0))
2285 (if (>= (point) end) (throw 'stop nil))
2286 (skip-chars-forward " \t\n" end)
2287 (if (>= (point) end) (throw 'stop nil))
2288 ;; “=” is optional for null-valued JSXAttributes.
2289 (when (= (char-after) ?=)
2290 (forward-char)
2291 (if (>= (point) end) (throw 'stop nil))
2292 (skip-chars-forward " \t\n" end)
2293 (if (>= (point) end) (throw 'stop nil))
2294 ;; Skip over strings (if possible). Any
2295 ;; JSXExpressionContainer here will be parsed in the
2296 ;; next iteration of the loop.
2297 (if (memq (char-after) '(?\" ?\' ?\`))
2298 (progn
2299 ;; Record the string’s position so derived modes
2300 ;; applying syntactic fontification atypically
2301 ;; (e.g. js2-mode) can recognize it as part of JSX.
2302 (put-text-property (point) (1+ (point)) 'js-jsx-string t)
2303 (condition-case nil
2304 (forward-sexp)
2305 (scan-error (throw 'stop nil))))
2306 ;; Save JSXAttribute’s beginning in case we find a
2307 ;; JSXExpressionContainer as the JSXAttribute’s value which
2308 ;; we should associate with the JSXAttribute.
2309 (setq expr-attribute-beg (match-beginning 0)))))
2310 ;; There is nothing more to check; this either isn’t JSX, or
2311 ;; the tag is incomplete.
2312 (t (throw 'stop nil)))))
2313 (when unambiguous
2314 ;; Save JSXBoundaryElement’s name’s match data for font-locking.
2315 (if name-beg (put-text-property name-beg (1+ name-beg) 'js-jsx-tag-name name-match-data))
2316 ;; Make the opening “<” an open parenthesis.
2317 (js-jsx--put-syntax-table tag-beg (1+ tag-beg) '(4))
2318 ;; Prevent “out of range” errors when typing at the end of a buffer.
2319 (setq tag-end (if (eobp) (1- (point)) (point)))
2320 ;; Mark beginning and end of tag for font-locking.
2321 (put-text-property tag-beg (1+ tag-beg) 'js-jsx-tag-beg (cons type tag-end))
2322 (put-text-property tag-end (1+ tag-end) 'js-jsx-tag-end tag-beg)
2323 ;; Use text properties to extend the syntax-propertize region
2324 ;; backward to the beginning of the JSXBoundaryElement in the
2325 ;; future. Typically the closing angle bracket could suggest
2326 ;; extending backward, but that would also involve more rigorous
2327 ;; parsing, and the closing angle bracket may not even exist yet
2328 ;; if the JSXBoundaryElement is still being typed.
2329 (put-text-property tag-beg (1+ tag-end) 'syntax-multiline t))
2330 (if (js-jsx--at-enclosing-tag-child-p) (js-jsx--syntax-propertize-tag-text end))))
2331
2332(defconst js-jsx--text-properties
2333 (list
2334 'js-jsx-tag-beg nil 'js-jsx-tag-end nil 'js-jsx-close-tag-pos nil
2335 'js-jsx-tag-name nil 'js-jsx-attribute-name nil 'js-jsx-string nil
2336 'js-jsx-text nil 'js-jsx-expr nil 'js-jsx-expr-attribute nil
2337 'js-jsx-syntax-table nil)
2338 "Plist of text properties added by `js-syntax-propertize'.")
2339
1725(defun js-syntax-propertize (start end) 2340(defun js-syntax-propertize (start end)
1726 ;; JavaScript allows immediate regular expression objects, written /.../. 2341 ;; JavaScript allows immediate regular expression objects, written /.../.
1727 (goto-char start) 2342 (goto-char start)
2343 (if js-jsx-syntax (remove-text-properties start end js-jsx--text-properties))
1728 (js-syntax-propertize-regexp end) 2344 (js-syntax-propertize-regexp end)
1729 (funcall 2345 (funcall
1730 (syntax-propertize-rules 2346 (syntax-propertize-rules
@@ -1748,7 +2364,8 @@ This performs fontification according to `js--class-styles'."
1748 (put-text-property (match-beginning 1) (match-end 1) 2364 (put-text-property (match-beginning 1) (match-end 1)
1749 'syntax-table (string-to-syntax "\"/")) 2365 'syntax-table (string-to-syntax "\"/"))
1750 (js-syntax-propertize-regexp end))))) 2366 (js-syntax-propertize-regexp end)))))
1751 ("\\`\\(#\\)!" (1 "< b"))) 2367 ("\\`\\(#\\)!" (1 "< b"))
2368 ("<" (0 (ignore (if js-jsx-syntax (js-jsx--syntax-propertize-tag end))))))
1752 (point) end)) 2369 (point) end))
1753 2370
1754(defconst js--prettify-symbols-alist 2371(defconst js--prettify-symbols-alist
@@ -1774,6 +2391,11 @@ This performs fontification according to `js--class-styles'."
1774 (js--regexp-opt-symbol '("in" "instanceof"))) 2391 (js--regexp-opt-symbol '("in" "instanceof")))
1775 "Regexp matching operators that affect indentation of continued expressions.") 2392 "Regexp matching operators that affect indentation of continued expressions.")
1776 2393
2394(defun js-jsx--looking-at-start-tag-p ()
2395 "Non-nil if a JSXOpeningElement immediately follows point."
2396 (let ((tag-beg (get-text-property (point) 'js-jsx-tag-beg)))
2397 (and tag-beg (memq (car tag-beg) '(open self-closing)))))
2398
1777(defun js--looking-at-operator-p () 2399(defun js--looking-at-operator-p ()
1778 "Return non-nil if point is on a JavaScript operator, other than a comma." 2400 "Return non-nil if point is on a JavaScript operator, other than a comma."
1779 (save-match-data 2401 (save-match-data
@@ -1796,7 +2418,9 @@ This performs fontification according to `js--class-styles'."
1796 (js--backward-syntactic-ws) 2418 (js--backward-syntactic-ws)
1797 ;; We might misindent some expressions that would 2419 ;; We might misindent some expressions that would
1798 ;; return NaN anyway. Shouldn't be a problem. 2420 ;; return NaN anyway. Shouldn't be a problem.
1799 (memq (char-before) '(?, ?} ?{)))))))) 2421 (memq (char-before) '(?, ?} ?{)))))
2422 ;; “<” isn’t necessarily an operator in JSX.
2423 (not (and js-jsx-syntax (js-jsx--looking-at-start-tag-p))))))
1800 2424
1801(defun js--find-newline-backward () 2425(defun js--find-newline-backward ()
1802 "Move backward to the nearest newline that is not in a block comment." 2426 "Move backward to the nearest newline that is not in a block comment."
@@ -1816,6 +2440,10 @@ This performs fontification according to `js--class-styles'."
1816 (setq result nil))) 2440 (setq result nil)))
1817 result)) 2441 result))
1818 2442
2443(defun js-jsx--looking-back-at-end-tag-p ()
2444 "Non-nil if a JSXClosingElement immediately precedes point."
2445 (get-text-property (point) 'js-jsx-tag-end))
2446
1819(defun js--continued-expression-p () 2447(defun js--continued-expression-p ()
1820 "Return non-nil if the current line continues an expression." 2448 "Return non-nil if the current line continues an expression."
1821 (save-excursion 2449 (save-excursion
@@ -1833,12 +2461,19 @@ This performs fontification according to `js--class-styles'."
1833 (and (js--find-newline-backward) 2461 (and (js--find-newline-backward)
1834 (progn 2462 (progn
1835 (skip-chars-backward " \t") 2463 (skip-chars-backward " \t")
1836 (or (bobp) (backward-char)) 2464 (and
1837 (and (> (point) (point-min)) 2465 ;; The “>” at the end of any JSXBoundaryElement isn’t
1838 (save-excursion (backward-char) (not (looking-at "[/*]/\\|=>"))) 2466 ;; part of a continued expression.
1839 (js--looking-at-operator-p) 2467 (not (and js-jsx-syntax (js-jsx--looking-back-at-end-tag-p)))
1840 (and (progn (backward-char) 2468 (progn
1841 (not (looking-at "\\+\\+\\|--\\|/[/*]")))))))))) 2469 (or (bobp) (backward-char))
2470 (and (> (point) (point-min))
2471 (save-excursion
2472 (backward-char)
2473 (not (looking-at "[/*]/\\|=>")))
2474 (js--looking-at-operator-p)
2475 (and (progn (backward-char)
2476 (not (looking-at "\\+\\+\\|--\\|/[/*]"))))))))))))
1842 2477
1843(defun js--skip-term-backward () 2478(defun js--skip-term-backward ()
1844 "Skip a term before point; return t if a term was skipped." 2479 "Skip a term before point; return t if a term was skipped."
@@ -2064,23 +2699,182 @@ indentation is aligned to that column."
2064 (when comma-p 2699 (when comma-p
2065 (goto-char (1+ declaration-keyword-end)))))))) 2700 (goto-char (1+ declaration-keyword-end))))))))
2066 2701
2067(defconst js--line-terminating-arrow-re "\\s-*=>\\s-*\\(/[/*]\\|$\\)" 2702(defconst js--line-terminating-arrow-re "=>\\s-*\\(/[/*]\\|$\\)"
2068 "Regexp matching the last \"=>\" (arrow) token on a line. 2703 "Regexp matching the last \"=>\" (arrow) token on a line.
2069Whitespace and comments around the arrow are ignored.") 2704Whitespace and comments around the arrow are ignored.")
2070 2705
2071(defun js--looking-at-broken-arrow-function-p () 2706(defun js--broken-arrow-terminates-line-p ()
2072 "Helper function for `js--proper-indentation'. 2707 "Helper function for `js--proper-indentation'.
2073Return t if point is at the start of a (possibly async) arrow 2708Return t if the last non-comment, non-whitespace token of the
2074function and the last non-comment, non-whitespace token of the 2709current line is the \"=>\" token (of an arrow function)."
2075current line is the \"=>\" token." 2710 (let ((from (point)))
2076 (when (looking-at "\\s-*async\\s-*") 2711 (end-of-line)
2077 (goto-char (match-end 0))) 2712 (re-search-backward js--line-terminating-arrow-re from t)))
2078 (cond 2713
2079 ((eq (char-after) ?\() 2714;; When indenting, we want to know if the line is…
2080 (forward-list) 2715;;
2081 (looking-at-p js--line-terminating-arrow-re)) 2716;; - within a multiline JSXElement, or
2082 (t (looking-at-p 2717;; - within a string in a JSXBoundaryElement, or
2083 (concat js--name-re js--line-terminating-arrow-re))))) 2718;; - within JSXText, or
2719;; - within a JSXAttribute’s multiline JSXExpressionContainer.
2720;;
2721;; In these cases, special XML-like indentation rules for JSX apply.
2722;; If JS is nested within JSX, then indentation calculations may be
2723;; combined, such that JS indentation is “relative” to the JSX’s.
2724;;
2725;; Therefore, functions below provide such contextual information, and
2726;; `js--proper-indentation' may call itself once recursively in order
2727;; to finish calculating that “relative” JS+JSX indentation.
2728
2729(defun js-jsx--context ()
2730 "Determine JSX context and move to enclosing JSX."
2731 (let ((pos (point))
2732 (parse-status (syntax-ppss))
2733 (enclosing-tag-pos (js-jsx--enclosing-tag-pos)))
2734 (when enclosing-tag-pos
2735 (if (< pos (nth 1 enclosing-tag-pos))
2736 (if (nth 3 parse-status)
2737 (list 'string (nth 8 parse-status))
2738 (list 'tag (nth 0 enclosing-tag-pos) (nth 1 enclosing-tag-pos)))
2739 (list 'text (nth 0 enclosing-tag-pos) (nth 2 enclosing-tag-pos))))))
2740
2741(defun js-jsx--contextual-indentation (line context)
2742 "Calculate indentation column for LINE from CONTEXT.
2743The column calculation is based off of `sgml-calculate-indent'."
2744 (pcase (nth 0 context)
2745
2746 ('string
2747 ;; Go back to previous non-empty line.
2748 (while (and (> (point) (nth 1 context))
2749 (zerop (forward-line -1))
2750 (looking-at "[ \t]*$")))
2751 (if (> (point) (nth 1 context))
2752 ;; Previous line is inside the string.
2753 (current-indentation)
2754 (goto-char (nth 1 context))
2755 (1+ (current-column))))
2756
2757 ('tag
2758 ;; Special JSX indentation rule: a “dangling” closing angle
2759 ;; bracket on its own line is indented at the same level as the
2760 ;; opening angle bracket of the JSXElement. Otherwise, indent
2761 ;; JSXAttribute space like SGML.
2762 (if (and
2763 js-jsx-align->-with-<
2764 (progn
2765 (goto-char (nth 2 context))
2766 (and (= line (line-number-at-pos))
2767 (looking-back "^\\s-*/?>" (line-beginning-position)))))
2768 (progn
2769 (goto-char (nth 1 context))
2770 (current-column))
2771 ;; Indent JSXAttribute space like SGML.
2772 (goto-char (nth 1 context))
2773 ;; Skip tag name:
2774 (skip-chars-forward " \t")
2775 (skip-chars-forward "^ \t\n")
2776 (skip-chars-forward " \t")
2777 (if (not (eolp))
2778 (current-column)
2779 ;; This is the first attribute: indent.
2780 (goto-char (+ (nth 1 context) js-jsx-attribute-offset))
2781 (+ (current-column) (or js-jsx-indent-level js-indent-level)))))
2782
2783 ('text
2784 ;; Indent to reflect nesting.
2785 (goto-char (nth 1 context))
2786 (+ (current-column)
2787 ;; The last line isn’t nested, but the rest are.
2788 (if (or (not (nth 2 context)) ; Unclosed.
2789 (< line (line-number-at-pos (nth 2 context))))
2790 (or js-jsx-indent-level js-indent-level)
2791 0)))
2792
2793 ))
2794
2795(defun js-jsx--enclosing-curly-pos ()
2796 "Return position of enclosing “{” in a “{/}” pair about point."
2797 (let ((parens (reverse (nth 9 (syntax-ppss)))) paren-pos curly-pos)
2798 (while
2799 (and
2800 (setq paren-pos (car parens))
2801 (not (when (= (char-after paren-pos) ?{)
2802 (setq curly-pos paren-pos)))
2803 (setq parens (cdr parens))))
2804 curly-pos))
2805
2806(defun js-jsx--goto-outermost-enclosing-curly (limit)
2807 "Set point to enclosing “{” at or closest after LIMIT."
2808 (let (pos)
2809 (while
2810 (and
2811 (setq pos (js-jsx--enclosing-curly-pos))
2812 (if (>= pos limit) (goto-char pos))
2813 (> pos limit)))))
2814
2815(defun js-jsx--expr-attribute-pos (start limit)
2816 "Look back from START to LIMIT for a JSXAttribute."
2817 (save-excursion
2818 (goto-char start) ; Skip the first curly.
2819 ;; Skip any remaining enclosing curlies until the JSXElement’s
2820 ;; beginning position; the last curly ought to be one of a
2821 ;; JSXExpressionContainer, which may refer to its JSXAttribute’s
2822 ;; beginning position (if it has one).
2823 (js-jsx--goto-outermost-enclosing-curly limit)
2824 (get-text-property (point) 'js-jsx-expr-attribute)))
2825
2826(defvar js-jsx--indent-col nil
2827 "Baseline column for JS indentation within JSX.")
2828
2829(defvar js-jsx--indent-attribute-line nil
2830 "Line relative to which indentation uses JSX as a baseline.")
2831
2832(defun js-jsx--expr-indentation (parse-status pos col)
2833 "Indent using PARSE-STATUS; relative to POS, use base COL.
2834To indent a JSXExpressionContainer’s expression, calculate the JS
2835indentation, using JSX indentation as the base column when
2836indenting relative to the beginning line of the
2837JSXExpressionContainer’s JSXAttribute (if any)."
2838 (let* ((js-jsx--indent-col col)
2839 (js-jsx--indent-attribute-line
2840 (if pos (line-number-at-pos pos))))
2841 (js--proper-indentation parse-status)))
2842
2843(defun js-jsx--indentation (parse-status)
2844 "Helper function for `js--proper-indentation'.
2845Return the proper indentation of the current line if it is part
2846of a JSXElement expression spanning multiple lines; otherwise,
2847return nil."
2848 (let ((current-line (line-number-at-pos))
2849 (curly-pos (js-jsx--enclosing-curly-pos))
2850 nth-context context expr-p beg-line col
2851 forward-sexp-function) ; Use the Lisp version.
2852 ;; Find the immediate context for indentation information, but
2853 ;; keep going to determine that point is at the N+1th line of
2854 ;; multiline JSX.
2855 (save-excursion
2856 (while
2857 (and
2858 (setq nth-context (js-jsx--context))
2859 (progn
2860 (unless context
2861 (setq context nth-context)
2862 (setq expr-p (and curly-pos (< (point) curly-pos))))
2863 (setq beg-line (line-number-at-pos))
2864 (and
2865 (= beg-line current-line)
2866 (or (not curly-pos) (> (point) curly-pos)))))))
2867 ;; When on the second or later line of JSX, indent as JSX,
2868 ;; possibly switching back to JS indentation within
2869 ;; JSXExpressionContainers, possibly using the JSX as a base
2870 ;; column while switching back to JS indentation.
2871 (when (and context (> current-line beg-line))
2872 (save-excursion
2873 (setq col (js-jsx--contextual-indentation current-line context)))
2874 (if expr-p
2875 (js-jsx--expr-indentation
2876 parse-status (js-jsx--expr-attribute-pos curly-pos (nth 1 context)) col)
2877 col))))
2084 2878
2085(defun js--proper-indentation (parse-status) 2879(defun js--proper-indentation (parse-status)
2086 "Return the proper indentation for the current line." 2880 "Return the proper indentation for the current line."
@@ -2089,6 +2883,8 @@ current line is the \"=>\" token."
2089 (cond ((nth 4 parse-status) ; inside comment 2883 (cond ((nth 4 parse-status) ; inside comment
2090 (js--get-c-offset 'c (nth 8 parse-status))) 2884 (js--get-c-offset 'c (nth 8 parse-status)))
2091 ((nth 3 parse-status) 0) ; inside string 2885 ((nth 3 parse-status) 0) ; inside string
2886 ((when (and js-jsx-syntax (not js-jsx--indent-col))
2887 (save-excursion (js-jsx--indentation parse-status))))
2092 ((eq (char-after) ?#) 0) 2888 ((eq (char-after) ?#) 0)
2093 ((save-excursion (js--beginning-of-macro)) 4) 2889 ((save-excursion (js--beginning-of-macro)) 4)
2094 ;; Indent array comprehension continuation lines specially. 2890 ;; Indent array comprehension continuation lines specially.
@@ -2113,7 +2909,7 @@ current line is the \"=>\" token."
2113 (goto-char (nth 1 parse-status)) ; go to the opening char 2909 (goto-char (nth 1 parse-status)) ; go to the opening char
2114 (if (or (not js-indent-align-list-continuation) 2910 (if (or (not js-indent-align-list-continuation)
2115 (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)") 2911 (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
2116 (save-excursion (forward-char) (js--looking-at-broken-arrow-function-p))) 2912 (save-excursion (forward-char) (js--broken-arrow-terminates-line-p)))
2117 (progn ; nothing following the opening paren/bracket 2913 (progn ; nothing following the opening paren/bracket
2118 (skip-syntax-backward " ") 2914 (skip-syntax-backward " ")
2119 (when (eq (char-before) ?\)) (backward-list)) 2915 (when (eq (char-before) ?\)) (backward-list))
@@ -2125,17 +2921,24 @@ current line is the \"=>\" token."
2125 (and switch-keyword-p 2921 (and switch-keyword-p
2126 in-switch-p))) 2922 in-switch-p)))
2127 (indent 2923 (indent
2128 (cond (same-indent-p 2924 (+
2129 (current-column)) 2925 (cond
2130 (continued-expr-p 2926 ((and js-jsx--indent-attribute-line
2131 (+ (current-column) (* 2 js-indent-level) 2927 (eq js-jsx--indent-attribute-line
2132 js-expr-indent-offset)) 2928 (line-number-at-pos)))
2133 (t 2929 js-jsx--indent-col)
2134 (+ (current-column) js-indent-level 2930 (t
2135 (pcase (char-after (nth 1 parse-status)) 2931 (current-column)))
2136 (?\( js-paren-indent-offset) 2932 (cond (same-indent-p 0)
2137 (?\[ js-square-indent-offset) 2933 (continued-expr-p
2138 (?\{ js-curly-indent-offset))))))) 2934 (+ (* 2 js-indent-level)
2935 js-expr-indent-offset))
2936 (t
2937 (+ js-indent-level
2938 (pcase (char-after (nth 1 parse-status))
2939 (?\( js-paren-indent-offset)
2940 (?\[ js-square-indent-offset)
2941 (?\{ js-curly-indent-offset))))))))
2139 (if in-switch-p 2942 (if in-switch-p
2140 (+ indent js-switch-indent-offset) 2943 (+ indent js-switch-indent-offset)
2141 indent))) 2944 indent)))
@@ -2151,193 +2954,6 @@ current line is the \"=>\" token."
2151 (+ js-indent-level js-expr-indent-offset)) 2954 (+ js-indent-level js-expr-indent-offset))
2152 (t (prog-first-column))))) 2955 (t (prog-first-column)))))
2153 2956
2154;;; JSX Indentation
2155
2156(defsubst js--jsx-find-before-tag ()
2157 "Find where JSX starts.
2158
2159Assume JSX appears in the following instances:
2160- Inside parentheses, when returned or as the first argument
2161 to a function, and after a newline
2162- When assigned to variables or object properties, but only
2163 on a single line
2164- As the N+1th argument to a function
2165
2166This is an optimized version of (re-search-backward \"[(,]\n\"
2167nil t), except set point to the end of the match. This logic
2168executes up to the number of lines in the file, so it should be
2169really fast to reduce that impact."
2170 (let (pos)
2171 (while (and (> (point) (point-min))
2172 (not (progn
2173 (end-of-line 0)
2174 (when (or (eq (char-before) 40) ; (
2175 (eq (char-before) 44)) ; ,
2176 (setq pos (1- (point))))))))
2177 pos))
2178
2179(defconst js--jsx-end-tag-re
2180 (concat "</" sgml-name-re ">\\|/>")
2181 "Find the end of a JSX element.")
2182
2183(defconst js--jsx-after-tag-re "[),]"
2184 "Find where JSX ends.
2185This complements the assumption of where JSX appears from
2186`js--jsx-before-tag-re', which see.")
2187
2188(defun js--jsx-indented-element-p ()
2189 "Determine if/how the current line should be indented as JSX.
2190
2191Return `first' for the first JSXElement on its own line.
2192Return `nth' for subsequent lines of the first JSXElement.
2193Return `expression' for an embedded JS expression.
2194Return `after' for anything after the last JSXElement.
2195Return nil for non-JSX lines.
2196
2197Currently, JSX indentation supports the following styles:
2198
2199- Single-line elements (indented like normal JS):
2200
2201 var element = <div></div>;
2202
2203- Multi-line elements (enclosed in parentheses):
2204
2205 function () {
2206 return (
2207 <div>
2208 <div></div>
2209 </div>
2210 );
2211 }
2212
2213- Function arguments:
2214
2215 React.render(
2216 <div></div>,
2217 document.querySelector('.root')
2218 );"
2219 (let ((current-pos (point))
2220 (current-line (line-number-at-pos))
2221 last-pos
2222 before-tag-pos before-tag-line
2223 tag-start-pos tag-start-line
2224 tag-end-pos tag-end-line
2225 after-tag-line
2226 parens paren type)
2227 (save-excursion
2228 (and
2229 ;; Determine if we're inside a jsx element
2230 (progn
2231 (end-of-line)
2232 (while (and (not tag-start-pos)
2233 (setq last-pos (js--jsx-find-before-tag)))
2234 (while (forward-comment 1))
2235 (when (= (char-after) 60) ; <
2236 (setq before-tag-pos last-pos
2237 tag-start-pos (point)))
2238 (goto-char last-pos))
2239 tag-start-pos)
2240 (progn
2241 (setq before-tag-line (line-number-at-pos before-tag-pos)
2242 tag-start-line (line-number-at-pos tag-start-pos))
2243 (and
2244 ;; A "before" line which also starts an element begins with js, so
2245 ;; indent it like js
2246 (> current-line before-tag-line)
2247 ;; Only indent the jsx lines like jsx
2248 (>= current-line tag-start-line)))
2249 (cond
2250 ;; Analyze bounds if there are any
2251 ((progn
2252 (while (and (not tag-end-pos)
2253 (setq last-pos (re-search-forward js--jsx-end-tag-re nil t)))
2254 (while (forward-comment 1))
2255 (when (looking-at js--jsx-after-tag-re)
2256 (setq tag-end-pos last-pos)))
2257 tag-end-pos)
2258 (setq tag-end-line (line-number-at-pos tag-end-pos)
2259 after-tag-line (line-number-at-pos after-tag-line))
2260 (or (and
2261 ;; Ensure we're actually within the bounds of the jsx
2262 (<= current-line tag-end-line)
2263 ;; An "after" line which does not end an element begins with
2264 ;; js, so indent it like js
2265 (<= current-line after-tag-line))
2266 (and
2267 ;; Handle another case where there could be e.g. comments after
2268 ;; the element
2269 (> current-line tag-end-line)
2270 (< current-line after-tag-line)
2271 (setq type 'after))))
2272 ;; They may not be any bounds (yet)
2273 (t))
2274 ;; Check if we're inside an embedded multi-line js expression
2275 (cond
2276 ((not type)
2277 (goto-char current-pos)
2278 (end-of-line)
2279 (setq parens (nth 9 (syntax-ppss)))
2280 (while (and parens (not type))
2281 (setq paren (car parens))
2282 (cond
2283 ((and (>= paren tag-start-pos)
2284 ;; Curly bracket indicates the start of an embedded expression
2285 (= (char-after paren) 123) ; {
2286 ;; The first line of the expression is indented like sgml
2287 (> current-line (line-number-at-pos paren))
2288 ;; Check if within a closing curly bracket (if any)
2289 ;; (exclusive, as the closing bracket is indented like sgml)
2290 (cond
2291 ((progn
2292 (goto-char paren)
2293 (ignore-errors (let (forward-sexp-function)
2294 (forward-sexp))))
2295 (< current-line (line-number-at-pos)))
2296 (t)))
2297 ;; Indicate this guy will be indented specially
2298 (setq type 'expression))
2299 (t (setq parens (cdr parens)))))
2300 t)
2301 (t))
2302 (cond
2303 (type)
2304 ;; Indent the first jsx thing like js so we can indent future jsx things
2305 ;; like sgml relative to the first thing
2306 ((= current-line tag-start-line) 'first)
2307 ('nth))))))
2308
2309(defmacro js--as-sgml (&rest body)
2310 "Execute BODY as if in sgml-mode."
2311 `(with-syntax-table sgml-mode-syntax-table
2312 (let (forward-sexp-function
2313 parse-sexp-lookup-properties)
2314 ,@body)))
2315
2316(defun js--expression-in-sgml-indent-line ()
2317 "Indent the current line as JavaScript or SGML (whichever is farther)."
2318 (let* (indent-col
2319 (savep (point))
2320 ;; Don't whine about errors/warnings when we're indenting.
2321 ;; This has to be set before calling parse-partial-sexp below.
2322 (inhibit-point-motion-hooks t)
2323 (parse-status (save-excursion
2324 (syntax-ppss (point-at-bol)))))
2325 ;; Don't touch multiline strings.
2326 (unless (nth 3 parse-status)
2327 (setq indent-col (save-excursion
2328 (back-to-indentation)
2329 (if (>= (point) savep) (setq savep nil))
2330 (js--as-sgml (sgml-calculate-indent))))
2331 (if (null indent-col)
2332 'noindent
2333 ;; Use whichever indentation column is greater, such that the sgml
2334 ;; column is effectively a minimum
2335 (setq indent-col (max (js--proper-indentation parse-status)
2336 (+ indent-col js-indent-level)))
2337 (if savep
2338 (save-excursion (indent-line-to indent-col))
2339 (indent-line-to indent-col))))))
2340
2341(defun js-indent-line () 2957(defun js-indent-line ()
2342 "Indent the current line as JavaScript." 2958 "Indent the current line as JavaScript."
2343 (interactive) 2959 (interactive)
@@ -2349,23 +2965,9 @@ Currently, JSX indentation supports the following styles:
2349 (when (> offset 0) (forward-char offset))))) 2965 (when (> offset 0) (forward-char offset)))))
2350 2966
2351(defun js-jsx-indent-line () 2967(defun js-jsx-indent-line ()
2352 "Indent the current line as JSX (with SGML offsets). 2968 "Indent the current line as JavaScript+JSX."
2353i.e., customize JSX element indentation with `sgml-basic-offset',
2354`sgml-attribute-offset' et al."
2355 (interactive) 2969 (interactive)
2356 (let ((indentation-type (js--jsx-indented-element-p))) 2970 (let ((js-jsx-syntax t)) (js-indent-line)))
2357 (cond
2358 ((eq indentation-type 'expression)
2359 (js--expression-in-sgml-indent-line))
2360 ((or (eq indentation-type 'first)
2361 (eq indentation-type 'after))
2362 ;; Don't treat this first thing as a continued expression (often a "<" or
2363 ;; ">" causes this misinterpretation)
2364 (cl-letf (((symbol-function #'js--continued-expression-p) 'ignore))
2365 (js-indent-line)))
2366 ((eq indentation-type 'nth)
2367 (js--as-sgml (sgml-indent-line)))
2368 (t (js-indent-line)))))
2369 2971
2370;;; Filling 2972;;; Filling
2371 2973
@@ -3856,6 +4458,77 @@ If one hasn't been set, or if it's stale, prompt for a new one."
3856 (when temp-name 4458 (when temp-name
3857 (delete-file temp-name)))))) 4459 (delete-file temp-name))))))
3858 4460
4461;;; Syntax extensions
4462
4463(defvar js-syntactic-mode-name t
4464 "If non-nil, print enabled syntaxes in the mode name.")
4465
4466(defun js--syntactic-mode-name-part ()
4467 "Return a string like “[JSX]” when `js-jsx-syntax' is enabled."
4468 (if js-syntactic-mode-name
4469 (let (syntaxes)
4470 (if js-jsx-syntax (push "JSX" syntaxes))
4471 (if syntaxes
4472 (concat "[" (mapconcat #'identity syntaxes ",") "]")
4473 ""))
4474 ""))
4475
4476(defun js-use-syntactic-mode-name ()
4477 "Print enabled syntaxes if `js-syntactic-mode-name' is t.
4478Modes deriving from `js-mode' should call this to ensure that
4479their `mode-name' updates to show enabled syntax extensions."
4480 (when (stringp mode-name)
4481 (setq mode-name `(,mode-name (:eval (js--syntactic-mode-name-part))))))
4482
4483(defun js-jsx-enable ()
4484 "Enable JSX in the current buffer."
4485 (interactive)
4486 (setq-local js-jsx-syntax t))
4487
4488;; To make discovering and using syntax extensions features easier for
4489;; users (who might not read the docs), try to safely and
4490;; automatically enable syntax extensions based on heuristics.
4491
4492(defvar js-jsx-regexps
4493 (list "\\_<\\(?:var\\|let\\|const\\|import\\)\\_>.*?React")
4494 "Regexps for detecting JSX in JavaScript buffers.
4495When `js-jsx-detect-syntax' is non-nil and any of these regexps
4496match text near the beginning of a JavaScript buffer,
4497`js-jsx-syntax' (which see) will be made buffer-local and set to
4498t.")
4499
4500(defun js-jsx--detect-and-enable (&optional arbitrarily)
4501 "Detect if JSX is likely to be used, and enable it if so.
4502Might make `js-jsx-syntax' buffer-local and set it to t. Matches
4503from the beginning of the buffer, unless optional arg ARBITRARILY
4504is non-nil. Return t after enabling, nil otherwise."
4505 (when (or (and (buffer-file-name)
4506 (string-match-p "\\.jsx\\'" (buffer-file-name)))
4507 (and js-jsx-detect-syntax
4508 (save-excursion
4509 (unless arbitrarily
4510 (goto-char (point-min)))
4511 (catch 'match
4512 (mapc
4513 (lambda (regexp)
4514 (if (re-search-forward regexp 4000 t) (throw 'match t)))
4515 js-jsx-regexps)
4516 nil))))
4517 (js-jsx-enable)
4518 t))
4519
4520(defun js-jsx--detect-after-change (beg end _len)
4521 "Detect if JSX is likely to be used after a change.
4522This function is intended for use in `after-change-functions'."
4523 (when (<= end 4000)
4524 (save-excursion
4525 (goto-char beg)
4526 (beginning-of-line)
4527 (save-restriction
4528 (narrow-to-region (point) end)
4529 (when (js-jsx--detect-and-enable 'arbitrarily)
4530 (remove-hook 'after-change-functions #'js-jsx--detect-after-change t))))))
4531
3859;;; Main Function 4532;;; Main Function
3860 4533
3861;;;###autoload 4534;;;###autoload
@@ -3871,6 +4544,10 @@ If one hasn't been set, or if it's stale, prompt for a new one."
3871 '(font-lock-syntactic-face-function 4544 '(font-lock-syntactic-face-function
3872 . js-font-lock-syntactic-face-function))) 4545 . js-font-lock-syntactic-face-function)))
3873 (setq-local syntax-propertize-function #'js-syntax-propertize) 4546 (setq-local syntax-propertize-function #'js-syntax-propertize)
4547 (add-hook 'syntax-propertize-extend-region-functions
4548 #'syntax-propertize-multiline 'append 'local)
4549 (add-hook 'syntax-propertize-extend-region-functions
4550 #'js--syntax-propertize-extend-region 'append 'local)
3874 (setq-local prettify-symbols-alist js--prettify-symbols-alist) 4551 (setq-local prettify-symbols-alist js--prettify-symbols-alist)
3875 4552
3876 (setq-local parse-sexp-ignore-comments t) 4553 (setq-local parse-sexp-ignore-comments t)
@@ -3878,6 +4555,7 @@ If one hasn't been set, or if it's stale, prompt for a new one."
3878 4555
3879 ;; Comments 4556 ;; Comments
3880 (setq-local comment-start "// ") 4557 (setq-local comment-start "// ")
4558 (setq-local comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
3881 (setq-local comment-end "") 4559 (setq-local comment-end "")
3882 (setq-local fill-paragraph-function #'js-fill-paragraph) 4560 (setq-local fill-paragraph-function #'js-fill-paragraph)
3883 (setq-local normal-auto-fill-function #'js-do-auto-fill) 4561 (setq-local normal-auto-fill-function #'js-do-auto-fill)
@@ -3888,6 +4566,11 @@ If one hasn't been set, or if it's stale, prompt for a new one."
3888 ;; Frameworks 4566 ;; Frameworks
3889 (js--update-quick-match-re) 4567 (js--update-quick-match-re)
3890 4568
4569 ;; Syntax extensions
4570 (unless (js-jsx--detect-and-enable)
4571 (add-hook 'after-change-functions #'js-jsx--detect-after-change nil t))
4572 (js-use-syntactic-mode-name)
4573
3891 ;; Imenu 4574 ;; Imenu
3892 (setq imenu-case-fold-search nil) 4575 (setq imenu-case-fold-search nil)
3893 (setq imenu-create-index-function #'js--imenu-create-index) 4576 (setq imenu-create-index-function #'js--imenu-create-index)
@@ -3898,8 +4581,7 @@ If one hasn't been set, or if it's stale, prompt for a new one."
3898 c-paragraph-separate "$" 4581 c-paragraph-separate "$"
3899 c-block-comment-prefix "* " 4582 c-block-comment-prefix "* "
3900 c-line-comment-starter "//" 4583 c-line-comment-starter "//"
3901 c-comment-start-regexp "/[*/]\\|\\s!" 4584 c-comment-start-regexp "/[*/]\\|\\s!")
3902 comment-start-skip "\\(//+\\|/\\*+\\)\\s *")
3903 (setq-local comment-line-break-function #'c-indent-new-comment-line) 4585 (setq-local comment-line-break-function #'c-indent-new-comment-line)
3904 (setq-local c-block-comment-start-regexp "/\\*") 4586 (setq-local c-block-comment-start-regexp "/\\*")
3905 (setq-local comment-multi-line t) 4587 (setq-local comment-multi-line t)
@@ -3932,19 +4614,33 @@ If one hasn't been set, or if it's stale, prompt for a new one."
3932 ;;(syntax-propertize (point-max)) 4614 ;;(syntax-propertize (point-max))
3933 ) 4615 )
3934 4616
3935;;;###autoload 4617;; Since we made JSX support available and automatically-enabled in
3936(define-derived-mode js-jsx-mode js-mode "JSX" 4618;; the base `js-mode' (for ease of use), now `js-jsx-mode' simply
3937 "Major mode for editing JSX. 4619;; serves as one other interface to unconditionally enable JSX in
3938 4620;; buffers, mostly for backwards-compatibility.
3939To customize the indentation for this mode, set the SGML offset 4621;;
3940variables (`sgml-basic-offset', `sgml-attribute-offset' et al.) 4622;; Since it is probably more common for packages to integrate with
3941locally, like so: 4623;; `js-mode' than with `js-jsx-mode', it is therefore probably
4624;; slightly better for users to use one of the many other methods for
4625;; enabling JSX syntax. But using `js-jsx-mode' can’t be that bad
4626;; either, so we won’t bother users with an obsoletion warning.
3942 4627
3943 (defun set-jsx-indentation () 4628;;;###autoload
3944 (setq-local sgml-basic-offset js-indent-level)) 4629(define-derived-mode js-jsx-mode js-mode "JavaScript"
3945 (add-hook \\='js-jsx-mode-hook #\\='set-jsx-indentation)" 4630 "Major mode for editing JavaScript+JSX.
4631
4632Simply makes `js-jsx-syntax' buffer-local and sets it to t.
4633
4634`js-mode' may detect and enable support for JSX automatically if
4635it appears to be used in a JavaScript file. You could also
4636customize `js-jsx-regexps' to improve that detection; or, you
4637could set `js-jsx-syntax' to t in your init file, or in a
4638.dir-locals.el file, or using file variables; or, you could call
4639`js-jsx-enable' in `js-mode-hook'. You may be better served by
4640one of the aforementioned options instead of using this mode."
3946 :group 'js 4641 :group 'js
3947 (setq-local indent-line-function #'js-jsx-indent-line)) 4642 (js-jsx-enable)
4643 (js-use-syntactic-mode-name))
3948 4644
3949;;;###autoload (defalias 'javascript-mode 'js-mode) 4645;;;###autoload (defalias 'javascript-mode 'js-mode)
3950 4646
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index 5d0d03d5029..b05f9a33e90 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -675,7 +675,7 @@ Which one will be chosen depends on the value of
675 675
676(defconst python-syntax-propertize-function 676(defconst python-syntax-propertize-function
677 (syntax-propertize-rules 677 (syntax-propertize-rules
678 ((python-rx string-delimiter) 678 ((rx (or "\"\"\"" "'''"))
679 (0 (ignore (python-syntax-stringify)))))) 679 (0 (ignore (python-syntax-stringify))))))
680 680
681(define-obsolete-variable-alias 'python--prettify-symbols-alist 681(define-obsolete-variable-alias 'python--prettify-symbols-alist
@@ -701,35 +701,27 @@ is used to limit the scan."
701 701
702(defun python-syntax-stringify () 702(defun python-syntax-stringify ()
703 "Put `syntax-table' property correctly on single/triple quotes." 703 "Put `syntax-table' property correctly on single/triple quotes."
704 (let* ((num-quotes (length (match-string-no-properties 1))) 704 (let* ((ppss (save-excursion (backward-char 3) (syntax-ppss)))
705 (ppss (prog2 705 (string-start (and (eq t (nth 3 ppss)) (nth 8 ppss)))
706 (backward-char num-quotes) 706 (quote-starting-pos (- (point) 3))
707 (syntax-ppss) 707 (quote-ending-pos (point)))
708 (forward-char num-quotes))) 708 (cond ((or (nth 4 ppss) ;Inside a comment
709 (string-start (and (not (nth 4 ppss)) (nth 8 ppss))) 709 (and string-start
710 (quote-starting-pos (- (point) num-quotes)) 710 ;; Inside of a string quoted with different triple quotes.
711 (quote-ending-pos (point)) 711 (not (eql (char-after string-start)
712 (num-closing-quotes 712 (char-after quote-starting-pos)))))
713 (and string-start 713 ;; Do nothing.
714 (python-syntax-count-quotes
715 (char-before) string-start quote-starting-pos))))
716 (cond ((and string-start (= num-closing-quotes 0))
717 ;; This set of quotes doesn't match the string starting
718 ;; kind. Do nothing.
719 nil) 714 nil)
720 ((not string-start) 715 ((nth 5 ppss)
716 ;; The first quote is escaped, so it's not part of a triple quote!
717 (goto-char (1+ quote-starting-pos)))
718 ((null string-start)
721 ;; This set of quotes delimit the start of a string. 719 ;; This set of quotes delimit the start of a string.
722 (put-text-property quote-starting-pos (1+ quote-starting-pos) 720 (put-text-property quote-starting-pos (1+ quote-starting-pos)
723 'syntax-table (string-to-syntax "|"))) 721 'syntax-table (string-to-syntax "|")))
724 ((= num-quotes num-closing-quotes) 722 (t
725 ;; This set of quotes delimit the end of a string. 723 ;; This set of quotes delimit the end of a string.
726 (put-text-property (1- quote-ending-pos) quote-ending-pos 724 (put-text-property (1- quote-ending-pos) quote-ending-pos
727 'syntax-table (string-to-syntax "|")))
728 ((> num-quotes num-closing-quotes)
729 ;; This may only happen whenever a triple quote is closing
730 ;; a single quoted string. Add string delimiter syntax to
731 ;; all three quotes.
732 (put-text-property quote-starting-pos quote-ending-pos
733 'syntax-table (string-to-syntax "|")))))) 725 'syntax-table (string-to-syntax "|"))))))
734 726
735(defvar python-mode-syntax-table 727(defvar python-mode-syntax-table
diff --git a/lisp/progmodes/sh-script.el b/lisp/progmodes/sh-script.el
index dd3a6fa411e..853a3500ee1 100644
--- a/lisp/progmodes/sh-script.el
+++ b/lisp/progmodes/sh-script.el
@@ -2905,8 +2905,7 @@ STRING This is ignored for the purposes of calculating
2905 (setq align-point (point)))) 2905 (setq align-point (point))))
2906 (or (bobp) 2906 (or (bobp)
2907 (forward-char -1)) 2907 (forward-char -1))
2908 ;; FIXME: This charset looks too much like a regexp. --Stef 2908 (skip-chars-forward "*0-9?[]a-z")
2909 (skip-chars-forward "[a-z0-9]*?")
2910 ) 2909 )
2911 ((string-match "[])}]" x) 2910 ((string-match "[])}]" x)
2912 (setq x (sh-safe-forward-sexp -1)) 2911 (setq x (sh-safe-forward-sexp -1))
diff --git a/lisp/progmodes/verilog-mode.el b/lisp/progmodes/verilog-mode.el
index 7b9c3921fba..9226291ffbb 100644
--- a/lisp/progmodes/verilog-mode.el
+++ b/lisp/progmodes/verilog-mode.el
@@ -14263,13 +14263,13 @@ and the case items."
14263(defun verilog-sk-define-signal () 14263(defun verilog-sk-define-signal ()
14264 "Insert a definition of signal under point at top of module." 14264 "Insert a definition of signal under point at top of module."
14265 (interactive "*") 14265 (interactive "*")
14266 (let* ((sig-re "[a-zA-Z0-9_]*") 14266 (let* ((sig-chars "a-zA-Z0-9_")
14267 (v1 (buffer-substring 14267 (v1 (buffer-substring
14268 (save-excursion 14268 (save-excursion
14269 (skip-chars-backward sig-re) 14269 (skip-chars-backward sig-chars)
14270 (point)) 14270 (point))
14271 (save-excursion 14271 (save-excursion
14272 (skip-chars-forward sig-re) 14272 (skip-chars-forward sig-chars)
14273 (point))))) 14273 (point)))))
14274 (if (not (member v1 verilog-keywords)) 14274 (if (not (member v1 verilog-keywords))
14275 (save-excursion 14275 (save-excursion