diff options
| author | Jackson Ray Hamilton | 2019-03-23 20:14:29 -0700 |
|---|---|---|
| committer | Jackson Ray Hamilton | 2019-04-08 22:48:22 -0700 |
| commit | bf37078df2cbea3a44a641ddbe40f11339c135a2 (patch) | |
| tree | cff66452d2d9ca2fbe4d676462fe76c66a311819 | |
| parent | 339be7c00790fb407cc8449fa8f59baa792cbe69 (diff) | |
| download | emacs-bf37078df2cbea3a44a641ddbe40f11339c135a2.tar.gz emacs-bf37078df2cbea3a44a641ddbe40f11339c135a2.zip | |
Automatically detect JSX in JavaScript files
* lisp/files.el (auto-mode-alist): Simply enable
javascript-mode (js-mode) when opening “.jsx” files, since the “.jsx”
file extension will be used as an indicator of JSX syntax by js-mode,
and more code is likely to work in js-mode than js-jsx-mode, and we
probably want to guide users to use js-mode (with js-jsx-syntax)
instead. Code that used to work exclusively in js-jsx-mode (if anyone
ever wrote any) ought to be updated to work in js-mode too when
js-jsx-syntax is set to t.
* lisp/progmodes/js.el (js-jsx-detect-syntax, js-jsx-regexps)
(js-jsx--detect-and-enable, js-jsx--detect-after-change): New
variables and functions for detecting and enabling JSX.
(js-jsx-syntax): Update docstring with respect to the widened scope of
the effects and use of this variable.
(js-syntactic-mode-name, js--update-mode-name)
(js--idly-update-mode-name, js-jsx-enable): New variable and functions
for indicating when JSX is enabled.
(js-mode): Detect and enable JSX. Print all enabled syntaxes after
the mode name whenever Emacs goes idle; this ensures lately-enabled
syntaxes are evident.
(js-jsx-mode): Update mode name for consistency with the state in
which JSX is enabled in js-mode. Update docstring to suggest
alternative means of using JSX without this mode. Going forward, it
may be best to gently guide users away from js-jsx-mode, since a “one
mode per syntax extension” model would not scale well if more syntax
extensions were to be simultaneously supported (e.g. Facebook’s
“Flow”).
| -rw-r--r-- | lisp/files.el | 3 | ||||
| -rw-r--r-- | lisp/progmodes/js.el | 119 |
2 files changed, 115 insertions, 7 deletions
diff --git a/lisp/files.el b/lisp/files.el index 1dae57593a0..b81550e297c 100644 --- a/lisp/files.el +++ b/lisp/files.el | |||
| @@ -2705,9 +2705,8 @@ ARC\\|ZIP\\|LZH\\|LHA\\|ZOO\\|[JEW]AR\\|XPI\\|RAR\\|CBR\\|7Z\\)\\'" . archive-mo | |||
| 2705 | ("\\.dbk\\'" . xml-mode) | 2705 | ("\\.dbk\\'" . xml-mode) |
| 2706 | ("\\.dtd\\'" . sgml-mode) | 2706 | ("\\.dtd\\'" . sgml-mode) |
| 2707 | ("\\.ds\\(ss\\)?l\\'" . dsssl-mode) | 2707 | ("\\.ds\\(ss\\)?l\\'" . dsssl-mode) |
| 2708 | ("\\.jsm?\\'" . javascript-mode) | 2708 | ("\\.js[mx]?\\'" . javascript-mode) |
| 2709 | ("\\.json\\'" . javascript-mode) | 2709 | ("\\.json\\'" . javascript-mode) |
| 2710 | ("\\.jsx\\'" . js-jsx-mode) | ||
| 2711 | ("\\.[ds]?vh?\\'" . verilog-mode) | 2710 | ("\\.[ds]?vh?\\'" . verilog-mode) |
| 2712 | ("\\.by\\'" . bovine-grammar-mode) | 2711 | ("\\.by\\'" . bovine-grammar-mode) |
| 2713 | ("\\.wy\\'" . wisent-grammar-mode) | 2712 | ("\\.wy\\'" . wisent-grammar-mode) |
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el index df2c41332e7..0bba8159c18 100644 --- a/lisp/progmodes/js.el +++ b/lisp/progmodes/js.el | |||
| @@ -574,10 +574,30 @@ then the \".\"s will be lined up: | |||
| 574 | :safe 'booleanp | 574 | :safe 'booleanp |
| 575 | :group 'js) | 575 | :group 'js) |
| 576 | 576 | ||
| 577 | (defcustom js-jsx-detect-syntax t | ||
| 578 | "When non-nil, automatically detect whether JavaScript uses JSX. | ||
| 579 | `js-jsx-syntax' (which see) may be made buffer-local and set to | ||
| 580 | t. The detection strategy can be customized by adding elements | ||
| 581 | to `js-jsx-regexps', which see." | ||
| 582 | :version "27.1" | ||
| 583 | :type 'boolean | ||
| 584 | :safe 'booleanp | ||
| 585 | :group 'js) | ||
| 586 | |||
| 577 | (defcustom js-jsx-syntax nil | 587 | (defcustom js-jsx-syntax nil |
| 578 | "When non-nil, parse JavaScript with consideration for JSX syntax. | 588 | "When non-nil, parse JavaScript with consideration for JSX syntax. |
| 579 | This fixes indentation of JSX code in some cases. It is set to | 589 | |
| 580 | be buffer-local when in `js-jsx-mode'." | 590 | This enables proper font-locking and indentation of code using |
| 591 | Facebook’s “JSX” syntax extension for JavaScript, for use with | ||
| 592 | Facebook’s “React” library. Font-locking is like sgml-mode. | ||
| 593 | Indentation is also like sgml-mode, although some indentation | ||
| 594 | behavior may differ slightly to align more closely with the | ||
| 595 | conventions of the React developer community. | ||
| 596 | |||
| 597 | When `js-mode' is already enabled, you should call | ||
| 598 | `js-jsx-enable' to set this variable. | ||
| 599 | |||
| 600 | It is set to be buffer-local (and t) when in `js-jsx-mode'." | ||
| 581 | :version "27.1" | 601 | :version "27.1" |
| 582 | :type 'boolean | 602 | :type 'boolean |
| 583 | :safe 'booleanp | 603 | :safe 'booleanp |
| @@ -4223,6 +4243,79 @@ If one hasn't been set, or if it's stale, prompt for a new one." | |||
| 4223 | (when temp-name | 4243 | (when temp-name |
| 4224 | (delete-file temp-name)))))) | 4244 | (delete-file temp-name)))))) |
| 4225 | 4245 | ||
| 4246 | ;;; Syntax extensions | ||
| 4247 | |||
| 4248 | (defvar js-syntactic-mode-name t | ||
| 4249 | "If non-nil, print enabled syntaxes in the mode name.") | ||
| 4250 | |||
| 4251 | (defun js--update-mode-name () | ||
| 4252 | "Print enabled syntaxes if `js-syntactic-mode-name' is t." | ||
| 4253 | (when js-syntactic-mode-name | ||
| 4254 | (setq mode-name (concat "JavaScript" | ||
| 4255 | (if js-jsx-syntax "+JSX" ""))))) | ||
| 4256 | |||
| 4257 | (defun js--idly-update-mode-name () | ||
| 4258 | "Update `mode-name' whenever Emacs goes idle. | ||
| 4259 | In case `js-jsx-syntax' is updated, especially by features of | ||
| 4260 | Emacs like .dir-locals.el or file variables, this ensures the | ||
| 4261 | modeline eventually reflects which syntaxes are enabled." | ||
| 4262 | (let (timer) | ||
| 4263 | (setq timer | ||
| 4264 | (run-with-idle-timer | ||
| 4265 | 0 t | ||
| 4266 | (lambda (buffer) | ||
| 4267 | (if (buffer-live-p buffer) | ||
| 4268 | (with-current-buffer buffer | ||
| 4269 | (js--update-mode-name)) | ||
| 4270 | (cancel-timer timer))) | ||
| 4271 | (current-buffer))))) | ||
| 4272 | |||
| 4273 | (defun js-jsx-enable () | ||
| 4274 | "Enable JSX in the current buffer." | ||
| 4275 | (interactive) | ||
| 4276 | (setq-local js-jsx-syntax t) | ||
| 4277 | (js--update-mode-name)) | ||
| 4278 | |||
| 4279 | (defvar js-jsx-regexps | ||
| 4280 | (list "\\_<\\(?:var\\|let\\|const\\|import\\)\\_>.*?React") | ||
| 4281 | "Regexps for detecting JSX in JavaScript buffers. | ||
| 4282 | When `js-jsx-detect-syntax' is non-nil and any of these regexps | ||
| 4283 | match text near the beginning of a JavaScript buffer, | ||
| 4284 | `js-jsx-syntax' (which see) will be made buffer-local and set to | ||
| 4285 | t.") | ||
| 4286 | |||
| 4287 | (defun js-jsx--detect-and-enable (&optional arbitrarily) | ||
| 4288 | "Detect if JSX is likely to be used, and enable it if so. | ||
| 4289 | Might make `js-jsx-syntax' buffer-local and set it to t. Matches | ||
| 4290 | from the beginning of the buffer, unless optional arg ARBITRARILY | ||
| 4291 | is non-nil. Return t after enabling, nil otherwise." | ||
| 4292 | (when (or (and (buffer-file-name) | ||
| 4293 | (string-match-p "\\.jsx\\'" (buffer-file-name))) | ||
| 4294 | (and js-jsx-detect-syntax | ||
| 4295 | (save-excursion | ||
| 4296 | (unless arbitrarily | ||
| 4297 | (goto-char (point-min))) | ||
| 4298 | (catch 'match | ||
| 4299 | (mapc | ||
| 4300 | (lambda (regexp) | ||
| 4301 | (if (re-search-forward regexp 4000 t) (throw 'match t))) | ||
| 4302 | js-jsx-regexps) | ||
| 4303 | nil)))) | ||
| 4304 | (js-jsx-enable) | ||
| 4305 | t)) | ||
| 4306 | |||
| 4307 | (defun js-jsx--detect-after-change (beg end _len) | ||
| 4308 | "Detect if JSX is likely to be used after a change. | ||
| 4309 | This function is intended for use in `after-change-functions'." | ||
| 4310 | (when (<= end 4000) | ||
| 4311 | (save-excursion | ||
| 4312 | (goto-char beg) | ||
| 4313 | (beginning-of-line) | ||
| 4314 | (save-restriction | ||
| 4315 | (narrow-to-region (point) end) | ||
| 4316 | (when (js-jsx--detect-and-enable 'arbitrarily) | ||
| 4317 | (remove-hook 'after-change-functions #'js-jsx--detect-after-change t)))))) | ||
| 4318 | |||
| 4226 | ;;; Main Function | 4319 | ;;; Main Function |
| 4227 | 4320 | ||
| 4228 | ;;;###autoload | 4321 | ;;;###autoload |
| @@ -4259,6 +4352,12 @@ If one hasn't been set, or if it's stale, prompt for a new one." | |||
| 4259 | ;; Frameworks | 4352 | ;; Frameworks |
| 4260 | (js--update-quick-match-re) | 4353 | (js--update-quick-match-re) |
| 4261 | 4354 | ||
| 4355 | ;; Syntax extensions | ||
| 4356 | (unless (js-jsx--detect-and-enable) | ||
| 4357 | (add-hook 'after-change-functions #'js-jsx--detect-after-change nil t)) | ||
| 4358 | (js--update-mode-name) ; If `js-jsx-syntax' was set from outside. | ||
| 4359 | (js--idly-update-mode-name) | ||
| 4360 | |||
| 4262 | ;; Imenu | 4361 | ;; Imenu |
| 4263 | (setq imenu-case-fold-search nil) | 4362 | (setq imenu-case-fold-search nil) |
| 4264 | (setq imenu-create-index-function #'js--imenu-create-index) | 4363 | (setq imenu-create-index-function #'js--imenu-create-index) |
| @@ -4304,10 +4403,20 @@ If one hasn't been set, or if it's stale, prompt for a new one." | |||
| 4304 | ) | 4403 | ) |
| 4305 | 4404 | ||
| 4306 | ;;;###autoload | 4405 | ;;;###autoload |
| 4307 | (define-derived-mode js-jsx-mode js-mode "JSX" | 4406 | (define-derived-mode js-jsx-mode js-mode "JavaScript+JSX" |
| 4308 | "Major mode for editing JSX." | 4407 | "Major mode for editing JavaScript+JSX. |
| 4408 | |||
| 4409 | Simply makes `js-jsx-syntax' buffer-local and sets it to t. | ||
| 4410 | |||
| 4411 | `js-mode' may detect and enable support for JSX automatically if | ||
| 4412 | it appears to be used in a JavaScript file. You could also | ||
| 4413 | customize `js-jsx-regexps' to improve that detection; or, you | ||
| 4414 | could set `js-jsx-syntax' to t in your init file, or in a | ||
| 4415 | .dir-locals.el file, or using file variables; or, you could call | ||
| 4416 | `js-jsx-enable' in `js-mode-hook'. You may be better served by | ||
| 4417 | one of the aforementioned options instead of using this mode." | ||
| 4309 | :group 'js | 4418 | :group 'js |
| 4310 | (setq-local js-jsx-syntax t)) | 4419 | (js-jsx-enable)) |
| 4311 | 4420 | ||
| 4312 | ;;;###autoload (defalias 'javascript-mode 'js-mode) | 4421 | ;;;###autoload (defalias 'javascript-mode 'js-mode) |
| 4313 | 4422 | ||