unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* html, css, and js modes working together
@ 2017-01-31 20:34 Tom Tromey
  2017-02-01  7:29 ` Clément Pit-Claudel
                   ` (3 more replies)
  0 siblings, 4 replies; 55+ messages in thread
From: Tom Tromey @ 2017-01-31 20:34 UTC (permalink / raw)
  To: Emacs discussions

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))))
 
 \f
+;; 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)))))
+
+\f
+
 ;;;###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)



^ permalink raw reply related	[flat|nested] 55+ messages in thread

end of thread, other threads:[~2017-04-06 14:12 UTC | newest]

Thread overview: 55+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-01-31 20:34 html, css, and js modes working together Tom Tromey
2017-02-01  7:29 ` Clément Pit-Claudel
2017-02-07  4:33   ` Tom Tromey
2017-02-10  2:31     ` Tom Tromey
2017-02-02 14:19 ` Stefan Monnier
     [not found]   ` <87mvdy7f2g.fsf@tromey.com>
2017-02-07 14:33     ` Stefan Monnier
2017-02-06  3:08 ` Dmitry Gutov
2017-02-06  3:26   ` Tom Tromey
2017-02-06  3:46     ` Dmitry Gutov
2017-02-06  6:50       ` Clément Pit-Claudel
2017-02-06 14:17       ` Stefan Monnier
2017-02-06 20:25         ` Tom Tromey
2017-02-06 20:58           ` Dmitry Gutov
2017-02-06 22:42             ` Stefan Monnier
2017-02-06 23:51         ` Lennart Borgman
2017-02-07  3:40   ` Tom Tromey
2017-02-07 11:28     ` Dmitry Gutov
2017-02-09 23:45 ` Tom Tromey
2017-02-10 20:05   ` Stefan Monnier
2017-02-10 21:15     ` Tom Tromey
2017-02-10 23:45       ` Stefan Monnier
2017-02-11 17:46         ` Tom Tromey
2017-02-11 20:20           ` Stefan Monnier
2017-02-12 16:17             ` Dmitry Gutov
2017-02-12 16:20           ` Dmitry Gutov
2017-02-12 16:52             ` Tom Tromey
2017-02-13  1:12               ` Dmitry Gutov
2017-02-13  1:59               ` Dmitry Gutov
2017-02-13  2:48                 ` Tom Tromey
2017-02-14  1:34                   ` Dmitry Gutov
2017-03-19 17:30                     ` Tom Tromey
2017-03-21  9:15                       ` Dmitry Gutov
2017-03-24  3:18                         ` Tom Tromey
2017-03-24 12:02                           ` Stefan Monnier
2017-03-24 12:08                           ` Toon Claes
2017-03-24 12:59                             ` Noam Postavsky
2017-03-24 14:17                               ` Tom Tromey
2017-04-05 22:01                           ` Tom Tromey
2017-04-06  2:28                             ` Leo Liu
2017-04-06 14:12                             ` Dmitry Gutov
2017-02-11 17:39       ` Tom Tromey
2017-02-11 19:48         ` Stefan Monnier
2017-02-12  3:49           ` Tom Tromey
2017-02-12  5:26             ` Stefan Monnier
2017-02-12  6:14               ` Tom Tromey
2017-02-12  7:02                 ` Stefan Monnier
2017-02-12  7:22                   ` Tom Tromey
2017-02-12 11:14             ` martin rudalics
2017-02-12 16:01               ` Eli Zaretskii
2017-02-12 16:32                 ` Tom Tromey
2017-02-12 17:14                   ` Stefan Monnier
2017-02-12 17:36                     ` Tom Tromey
2017-02-12 18:17                   ` Eli Zaretskii
2017-02-12 16:59                 ` Stefan Monnier
2017-02-11  0:28   ` Please ack Richard Stallman

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).