[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
html, css, and js modes working together
From: |
Tom Tromey |
Subject: |
html, css, and js modes working together |
Date: |
Tue, 31 Jan 2017 13:34:40 -0700 |
I tried to send this last night but it bounced for some reason.
This patch changes the html, css, and js modes to work together a bit.
With this, the contents of a <style> element are syntax-highlighted and
indented according to css-mode rules, and the contents of a <script>
element are syntax-highlighted and indented according to js-mode rules.
I'd appreciate comments on this approach.
This required small changes in SMIE and a minor unrelated change to
break the now-cyclic sgml/css-mode dependency. I chose to break the
sgml/js cyclic dependency in a different way; neither one seems really
great. Maybe moving html-mode to its own file would help.
* I didn't read SMIE deeply enough to see if the indentation parts
relied heavily on the forward/backward-a-token rules. If so then the
new macro will need some additional work.
* I used the existing (but apparently unused) prog-indentation-context
variable in this. I was initially skeptical of using a global
variable but it did turn out to be pretty handy for SMIE.
* This work doesn't address the need for per-region font locking at all.
That would be nice to have. I'd like to hear if there are plans for
how to do this.
* Likewise there are other things provided by a major mode that aren't
handled here, for example imenu, add-log-current-defun-function,
comment-*, electric indent keys, ... probably more that I am not
thinking of or that css-mode isn't using. It might also be nice if
dir-locals.el settings for css-mode and js-mode affected html-mode as
well.
* Not sure but maybe I also need to define
syntax-propertize-extend-region-functions now?
Tom
diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el
index 4d02b75..d793eca 100644
--- a/lisp/emacs-lisp/smie.el
+++ b/lisp/emacs-lisp/smie.el
@@ -123,6 +123,8 @@
(eval-when-compile (require 'cl-lib))
+(require 'prog-mode)
+
(defgroup smie nil
"Simple Minded Indentation Engine."
:group 'languages)
@@ -1455,7 +1457,7 @@ smie-indent-bob
;; Start the file at column 0.
(save-excursion
(forward-comment (- (point)))
- (if (bobp) 0)))
+ (if (bobp) (prog-first-column))))
(defun smie-indent-close ()
;; Align close paren with opening paren.
@@ -1838,6 +1840,16 @@ smie-auto-fill
(funcall do-auto-fill)))))
+(defmacro with-smie-rules (spec &rest body)
+ "Temporarily set up SMIE indentation and evaluate BODY.
+SPEC is of the form (GRAMMAR RULES-FUNCTION); see `smie-setup'.
+BODY is evaluated with the relevant SMIE variables temporarily bound."
+ (declare (indent 1))
+ `(let ((smie-grammar ,(car spec))
+ (smie-rules-function ,(cadr spec))
+ (indent-line-function #'smie-indent-line))
+ ,@body))
+
(defun smie-setup (grammar rules-function &rest keywords)
"Setup SMIE navigation and indentation.
GRAMMAR is a grammar table generated by `smie-prec2->grammar'.
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 74dd4ad..f3d90a9 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -53,6 +53,7 @@
(require 'moz nil t)
(require 'json nil t)
(require 'sgml-mode)
+(require 'prog-mode)
(eval-when-compile
(require 'cl-lib)
@@ -2102,7 +2103,7 @@ js--proper-indentation
((js--continued-expression-p)
(+ js-indent-level js-expr-indent-offset))
- (t 0))))
+ (t (prog-first-column)))))
;;; JSX Indentation
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 19f74da..14bacdc 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -33,7 +33,6 @@
;;; Code:
(require 'seq)
-(require 'sgml-mode)
(require 'smie)
(require 'eww)
@@ -869,10 +868,6 @@ css--complete-property-value
(append '("inherit" "initial" "unset")
(css--property-values property))))))))
-(defvar css--html-tags (mapcar #'car html-tag-alist)
- "List of HTML tags.
-Used to provide completion of HTML tags in selectors.")
-
(defvar css--nested-selectors-allowed nil
"Non-nil if nested selectors are allowed in the current mode.")
(make-variable-buffer-local 'css--nested-selectors-allowed)
@@ -900,6 +895,8 @@ css--foreign-completions
(funcall (symbol-value extractor))))
(buffer-list))))
+(declare-function html-memoized-tag-list "sgml-mode")
+
(defun css--complete-selector ()
"Complete part of a CSS selector at point."
(when (or (= (nth 0 (syntax-ppss)) 0) css--nested-selectors-allowed)
@@ -916,7 +913,7 @@ css--complete-selector
(css--foreign-completions 'css-class-list-function))
((eq start-char ?#)
(css--foreign-completions 'css-id-list-function))
- (t css--html-tags))))))))))
+ (t (html-memoized-tag-list)))))))))))
(defun css-completion-at-point ()
"Complete current symbol at point.
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el
index e148b06..676ebcf 100644
--- a/lisp/textmodes/sgml-mode.el
+++ b/lisp/textmodes/sgml-mode.el
@@ -32,7 +32,9 @@
;;; Code:
+(require 'css-mode)
(require 'dom)
+(require 'prog-mode)
(require 'seq)
(require 'subr-x)
(eval-when-compile
@@ -341,19 +343,23 @@ sgml-font-lock-keywords-2
(defvar sgml-font-lock-keywords sgml-font-lock-keywords-1
"Rules for highlighting SGML code. See also `sgml-tag-face-alist'.")
+(eval-when-compile
+ (defconst sgml-syntax-propertize-rules
+ (syntax-propertize-precompile-rules
+ ;; Use the `b' style of comments to avoid interference with the -- ... --
+ ;; comments recognized when `sgml-specials' includes ?-.
+ ;; FIXME: beware of <!--> blabla <!--> !!
+ ("\\(<\\)!--" (1 "< b"))
+ ("--[ \t\n]*\\(>\\)" (1 "> b"))
+ ;; Double quotes outside of tags should not introduce strings.
+ ;; Be careful to call `syntax-ppss' on a position before the one we're
+ ;; going to change, so as not to need to flush the data we just computed.
+ ("\"" (0 (if (prog1 (zerop (car (syntax-ppss (match-beginning 0))))
+ (goto-char (match-end 0)))
+ (string-to-syntax ".")))))))
+
(defconst sgml-syntax-propertize-function
- (syntax-propertize-rules
- ;; Use the `b' style of comments to avoid interference with the -- ... --
- ;; comments recognized when `sgml-specials' includes ?-.
- ;; FIXME: beware of <!--> blabla <!--> !!
- ("\\(<\\)!--" (1 "< b"))
- ("--[ \t\n]*\\(>\\)" (1 "> b"))
- ;; Double quotes outside of tags should not introduce strings.
- ;; Be careful to call `syntax-ppss' on a position before the one we're
- ;; going to change, so as not to need to flush the data we just computed.
- ("\"" (0 (if (prog1 (zerop (car (syntax-ppss (match-beginning 0))))
- (goto-char (match-end 0)))
- (string-to-syntax ".")))))
+ (syntax-propertize-rules sgml-syntax-propertize-rules)
"Syntactic keywords for `sgml-mode'.")
;; internal
@@ -2024,6 +2030,13 @@ html-tag-alist
("wbr" t)))
"Value of `sgml-tag-alist' for HTML mode.")
+(defvar html-tag-list (mapcar #'car html-tag-alist)
+ "List of HTML tags.")
+
+;;;###autoload
+(defun html-memoized-tag-list ()
+ html-tag-list)
+
(defvar html-tag-help
`(,@sgml-tag-help
("a" . "Anchor of point or link elsewhere")
@@ -2233,6 +2246,68 @@ html-current-buffer-ids
ids))))
+;; js.el is loaded when entering html-mode.
+(defvar js-mode-syntax-table)
+(declare-function js-indent-line "js")
+(declare-function js-syntax-propertize "js")
+
+(defconst html-syntax-propertize-function
+ (syntax-propertize-rules
+ ("<style.*?>\\(\\(\n\\|.\\)*?\\)</style>"
+ (1
+ (prog1 css-mode-syntax-table
+ (let ((start (nth 2 (match-data)))
+ (end (nth 3 (match-data))))
+ (funcall css-syntax-propertize-function start end)
+ (add-text-properties start end '(syntax-multiline t))))))
+ ("<script.*?>\\(\\(\n\\|.\\)*?\\)</script>"
+ (1
+ (prog1 js-mode-syntax-table
+ (let ((start (nth 2 (match-data)))
+ (end (nth 3 (match-data))))
+ (js-syntax-propertize start end)
+ (add-text-properties start end '(syntax-multiline t))))))
+ sgml-syntax-propertize-rules)
+ "Syntactic keywords for `html-mode'.")
+
+(defun html-indent-line ()
+ "Indent the current line as HTML."
+ (interactive)
+ (let* ((context (save-excursion (car (last (sgml-get-context)))))
+ (tag (when (and context
+ (eq (sgml-tag-type context) 'open)
+ (> (point) (sgml-tag-start context)))
+ (sgml-tag-name context))))
+ (cond
+ ((equal tag "style")
+ ;; CSS.
+ (save-restriction
+ (let ((base-indent (save-excursion
+ (goto-char (sgml-tag-end context))
+ (sgml-calculate-indent))))
+ (narrow-to-region (sgml-tag-end context) (point-max))
+ (let ((prog-indentation-context (list base-indent
+ (cons (point-min) nil)
+ nil)))
+ (with-smie-rules (css-smie-grammar #'css-smie-rules)
+ (smie-indent-line))))))
+ ((equal tag "script")
+ ;; Javascript.
+ (save-restriction
+ (let ((base-indent (save-excursion
+ (goto-char (sgml-tag-end context))
+ (sgml-calculate-indent))))
+ (narrow-to-region (sgml-tag-end context) (point-max))
+ (let ((prog-indentation-context (list base-indent
+ (cons (point-min) nil)
+ nil)))
+ (js-indent-line)))))
+ (t
+ ;; HTML.
+ (sgml-indent-line)))))
+
+
+
;;;###autoload
(define-derived-mode html-mode sgml-mode '(sgml-xml-mode "XHTML" "HTML")
"Major mode based on SGML mode for editing HTML documents.
@@ -2270,6 +2345,8 @@ html-mode
(eval-after-load \"sgml-mode\" \\='(aset sgml-char-names ?\\=' nil))
\\{html-mode-map}"
+ ;; A hack to avoid a circular dependency.
+ (require 'js)
(setq-local sgml-display-text html-display-text)
(setq-local sgml-tag-face-alist html-tag-face-alist)
(setq-local sgml-tag-alist html-tag-alist)
@@ -2281,6 +2358,11 @@ html-mode
(lambda () (char-before (match-end 0))))
(setq-local add-log-current-defun-function #'html-current-defun-name)
(setq-local sentence-end-base "[.?!][]\"'”)}]*\\(<[^>]*>\\)*")
+ (setq-local indent-line-function #'html-indent-line)
+
+ (setq-local syntax-propertize-function html-syntax-propertize-function)
+ (add-hook 'syntax-propertize-extend-region-functions
+ #'syntax-propertize-multiline 'append 'local)
(when (fboundp 'libxml-parse-html-region)
(defvar css-class-list-function)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- html, css, and js modes working together,
Tom Tromey <=