From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Tom Tromey Newsgroups: gmane.emacs.devel Subject: Re: html, css, and js modes working together Date: Sun, 12 Feb 2017 19:48:11 -0700 Message-ID: <87inoe24d0.fsf@tromey.com> References: <87o9ynarz3.fsf@tromey.com> <877f4z6i8n.fsf@tromey.com> <87poipzr0l.fsf@tromey.com> <87efz44o47.fsf@tromey.com> <87r3331hdg.fsf@tromey.com> <5a7078f5-3dbc-acc5-f293-6200e77f73b2@yandex.ru> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Trace: blaine.gmane.org 1486954577 19851 195.159.176.226 (13 Feb 2017 02:56:17 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Mon, 13 Feb 2017 02:56:17 +0000 (UTC) User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/25.1.91 (gnu/linux) Cc: Tom Tromey , Stefan Monnier , emacs-devel@gnu.org To: Dmitry Gutov Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Mon Feb 13 03:56:05 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1cd6o8-0004UG-0D for ged-emacs-devel@m.gmane.org; Mon, 13 Feb 2017 03:56:04 +0100 Original-Received: from localhost ([::1]:54273 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cd6oD-0005SW-OR for ged-emacs-devel@m.gmane.org; Sun, 12 Feb 2017 21:56:09 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:37731) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cd6o5-0005SQ-Hr for emacs-devel@gnu.org; Sun, 12 Feb 2017 21:56:04 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cd6ny-0005jX-KU for emacs-devel@gnu.org; Sun, 12 Feb 2017 21:55:58 -0500 Original-Received: from qproxy5-pub.mail.unifiedlayer.com ([69.89.21.30]:45485) by eggs.gnu.org with smtp (Exim 4.71) (envelope-from ) id 1cd6ny-0005hy-1M for emacs-devel@gnu.org; Sun, 12 Feb 2017 21:55:54 -0500 Original-Received: (qmail 25721 invoked by uid 0); 13 Feb 2017 02:55:49 -0000 Original-Received: from unknown (HELO CMOut01) (10.0.90.82) by qproxy5.mail.unifiedlayer.com with SMTP; 13 Feb 2017 02:55:49 -0000 Original-Received: from box522.bluehost.com ([74.220.219.122]) by CMOut01 with id k2oC1u00p2f2jeq012oFwe; Sun, 12 Feb 2017 19:48:19 -0700 X-Authority-Analysis: v=2.1 cv=U+QBU4bu c=1 sm=1 tr=0 a=GsOEXm/OWkKvwdLVJsfwcA==:117 a=GsOEXm/OWkKvwdLVJsfwcA==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=IkcTkHD0fZMA:10 a=n2v9WMKugxEA:10 a=vaJtXVxTAAAA:8 a=mDV3o1hIAAAA:8 a=F1M1JGbBZR4SeQYnnv8A:9 a=QEXdDO2ut3YA:10 a=WyacwRpqJ6QA:10 a=L03L2QfmqWoA:10 a=NWVoK91CQyQA:10 a=hCt-GehETBxEYQOojhlW:22 a=_FVE-zBwftR9WsbkzFJk:22 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=Content-Transfer-Encoding:Content-Type:MIME-Version:Message-ID: In-Reply-To:Date:References:Subject:Cc:To:From:Sender:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=eoqNKn26WB9O/6Dzsatb4botFiu1YGn+SdlDqND7p7M=; b=W2tC2H64eWfKjJaZpZ4taN6hOq S33DY39vvkVyNrWGY93eK7rRgIMEXgmHhExj8FsaMl/V+X4eseoCrWwq+DR5wsVT1s4m6KbMUvEDb k1yBtH6z416MhiBpp42dAIs7/; Original-Received: from 174-16-128-54.hlrn.qwest.net ([174.16.128.54]:58866 helo=bapiya) by box522.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.87) (envelope-from ) id 1cd6gW-0002D4-C6; Sun, 12 Feb 2017 19:48:12 -0700 X-Attribution: Tom In-Reply-To: <5a7078f5-3dbc-acc5-f293-6200e77f73b2@yandex.ru> (Dmitry Gutov's message of "Mon, 13 Feb 2017 03:59:05 +0200") X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - box522.bluehost.com X-AntiAbuse: Original Domain - gnu.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - tromey.com X-BWhitelist: no X-Source-IP: 174.16.128.54 X-Exim-ID: 1cd6gW-0002D4-C6 X-Source: X-Source-Args: X-Source-Dir: X-Source-Sender: 174-16-128-54.hlrn.qwest.net (bapiya) [174.16.128.54]:58866 X-Source-Auth: tom+tromey.com X-Email-Count: 3 X-Source-Cap: ZWx5bnJvYmk7ZWx5bnJvYmk7Ym94NTIyLmJsdWVob3N0LmNvbQ== X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x [fuzzy] X-Received-From: 69.89.21.30 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:212317 Archived-At: >>>>> "Dmitry" =3D=3D Dmitry Gutov writes: >> I've appended the current code. Dmitry> I'm getting "Symbol=E2=80=99s value as variable is void: Dmitry> sgml-syntax-propertize-rules". Does it still require parts of the Dmitry> previous patch? Yeah, sorry, I only appended the new mode's file since I thought you only wanted it to see what it was doing. Here's my current patch, now with pre- and post-command hooks and flyspell integration. Font lock mostly works but has some oddities, and I still didn't fix the html comment/syntax bug that Cl=C3=A9ment found. This is way smaller than I thought it would end up being, basically I think because syntax-propertizing is a nice tool. Tom diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el index 4d02b75..7baccbc 100644 --- a/lisp/emacs-lisp/smie.el +++ b/lisp/emacs-lisp/smie.el @@ -123,6 +123,8 @@ =20 (eval-when-compile (require 'cl-lib)) =20 +(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)))) =20 (defun smie-indent-close () ;; Align close paren with opening paren. diff --git a/lisp/files.el b/lisp/files.el index b7d1048..77c1e41 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -2422,7 +2422,7 @@ auto-mode-alist (lambda (elt) (cons (purecopy (car elt)) (cdr elt))) `(;; do this first, so that .html.pl is Polish html, not Perl - ("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . html-mode) + ("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-mode) ("\\.svgz?\\'" . image-mode) ("\\.svgz?\\'" . xml-mode) ("\\.x[bp]m\\'" . image-mode) @@ -2784,8 +2784,8 @@ magic-fallback-mode-alist comment-re "*" "\\(?:!DOCTYPE[ \t\r\n]+[^>]*>[ \t\r\n]*<[ \t\r\n]*" comment-re "*\\)?" "[Hh][Tt][Mm][Ll]")) - . html-mode) - (". + +;;; Code: + +(eval-and-compile + (require 'sgml-mode)) +(require 'js) +(require 'css-mode) +(require 'prog-mode) +(require 'font-lock) + +(cl-defstruct mhtml--submode + ;; Name of this submode. + name + ;; HTML end tag. + end-tag + ;; Syntax table. + syntax-table + ;; Propertize function. + propertize + ;; Keymap. + keymap + ;; Captured locals that are set when entering a region. + crucial-captured-locals + ;; Other captured local variables; these are not set when entering a + ;; region but let-bound during certain operations, e.g., + ;; indentation. + captured-locals) + +(defconst mhtml--crucial-variable-prefix + (regexp-opt '("comment-" "uncomment-" "electric-indent-" + "smie-" "forward-sexp-function")) + "Regexp matching the prefix of \"crucial\" buffer-locals we want to capt= ure.") + +(defconst mhtml--variable-prefix + (regexp-opt '("font-lock-" "indent-line-function" "major-mode")) + "Regexp matching the prefix of buffer-locals we want to capture.") + +(defun mhtml--construct-submode (mode &rest args) + "A wrapper for make-mhtml--submode that computes the buffer-local variab= les." + (let ((captured-locals nil) + (crucial-captured-locals nil) + (submode (apply #'make-mhtml--submode args))) + (with-temp-buffer + (funcall mode) + ;; Make sure font lock is all set up. + (font-lock-set-defaults) + (dolist (iter (buffer-local-variables)) + (when (string-match mhtml--crucial-variable-prefix + (symbol-name (car iter))) + (push iter crucial-captured-locals)) + (when (string-match mhtml--variable-prefix (symbol-name (car iter)= )) + (push iter captured-locals))) + (setf (mhtml--submode-crucial-captured-locals submode) + crucial-captured-locals) + (setf (mhtml--submode-captured-locals submode) captured-locals)) + submode)) + +(defun mhtml--mark-buffer-locals (submode) + (dolist (iter (mhtml--submode-captured-locals submode)) + (make-local-variable (car iter)))) + +(defvar-local mhtml--crucial-variables nil + "List of all crucial variable symbols.") + +(defun mhtml--mark-crucial-buffer-locals (submode) + (dolist (iter (mhtml--submode-crucial-captured-locals submode)) + (make-local-variable (car iter)) + (push (car iter) mhtml--crucial-variables))) + +(defconst mhtml--css-submode + (mhtml--construct-submode 'css-mode + :name "CSS" + :end-tag "" + :syntax-table css-mode-syntax-table + :propertize css-syntax-propertize-function + :keymap css-mode-map)) + +(defconst mhtml--js-submode + (mhtml--construct-submode 'js-mode + :name "JS" + :end-tag "" + :syntax-table js-mode-syntax-table + :propertize #'js-syntax-propertize + :keymap js-mode-map)) + +(defmacro mhtml--with-locals (submode &rest body) + (declare (indent 1)) + `(cl-progv + (when submode (mapcar #'car (mhtml--submode-captured-locals submode= ))) + (when submode (mapcar #'cdr (mhtml--submode-captured-locals submode= ))) + (cl-progv + (when submode (mapcar #'car (mhtml--submode-crucial-captured-loca= ls + submode))) + (when submode (mapcar #'cdr (mhtml--submode-crucial-captured-loca= ls + submode))) + ,@body))) + +(defun mhtml--submode-lighter () + "Mode-line lighter indicating the current submode." + (let ((submode (get-text-property (point) 'mhtml-submode))) + (if submode + (mhtml--submode-name submode) + ""))) + +(defun mhtml--submode-fontify-one-region (submode beg end &optional loudly) + (if submode + (mhtml--with-locals submode + (save-restriction + (narrow-to-region beg end) + (font-lock-set-defaults) + (font-lock-default-fontify-region (point-min) (point-max) loudly= ))) + (font-lock-set-defaults) + (font-lock-default-fontify-region beg end loudly))) + +(defun mhtml--submode-fontify-region (beg end loudly) + (while (< beg end) + (let ((submode (get-text-property beg 'mhtml-submode)) + (this-end (next-single-property-change beg 'mhtml-submode + nil end))) + (mhtml--submode-fontify-one-region submode beg this-end loudly) + (setq beg this-end)))) + +(defvar-local mhtml--last-submode nil + "Record the last visited submode, so the cursor-sensor function +can function properly.") + +(defvar-local mhtml--stashed-crucial-variables nil + "Alist of stashed values of the crucial variables.") + +(defun mhtml--stash-crucial-variables () + (setq mhtml--stashed-crucial-variables + (mapcar (lambda (sym) + (cons sym (buffer-local-value sym (current-buffer)))) + mhtml--crucial-variables))) + +(defun mhtml--map-in-crucial-variables (alist) + (dolist (item alist) + (set (car item) (cdr item)))) + +(defun mhtml--pre-command () + (let ((submode (get-text-property (point) 'mhtml-submode))) + (unless (eq submode mhtml--last-submode) + ;; If we're entering a submode, and the previous submode was + ;; nil, then stash the current values first. This lets the user + ;; at least modify some values directly. FIXME maybe always + ;; stash into the current mode? + (when (and submode (not mhtml--last-submode)) + (mhtml--stash-crucial-variables)) + (mhtml--map-in-crucial-variables + (if submode + (mhtml--submode-crucial-captured-locals submode) + mhtml--stashed-crucial-variables)) + (setq mhtml--last-submode submode)))) + +(defun mhtml--syntax-propertize-submode (submode end) + (save-excursion + (when (search-forward (mhtml--submode-end-tag submode) end t) + (setq end (match-beginning 0)))) + (set-text-properties (point) end + (list 'mhtml-submode submode + 'syntax-table (mhtml--submode-syntax-table su= bmode) + ;; We want local-map here so that we act + ;; more like the sub-mode and don't + ;; override minor mode maps. + 'local-map (mhtml--submode-keymap submode))) + (funcall (mhtml--submode-propertize submode) (point) end) + (goto-char end)) + +(defun mhtml-syntax-propertize (start end) + (goto-char start) + (when (get-text-property (point) 'mhtml-submode) + (mhtml--syntax-propertize-submode (get-text-property (point) 'mhtml-su= bmode) + end)) + (funcall + (syntax-propertize-rules + ("" + (0 (ignore + (goto-char (match-end 0)) + (mhtml--syntax-propertize-submode mhtml--css-submode end)))) + ("" + (0 (ignore + (goto-char (match-end 0)) + (mhtml--syntax-propertize-submode mhtml--js-submode end)))) + sgml-syntax-propertize-rules) + ;; Make sure to handle the situation where + ;; mhtml--syntax-propertize-submode moved point. + (point) end)) + +(defun mhtml-indent-line () + "Indent the current line as HTML, JS, or CSS, according to its context." + (interactive) + (let ((submode (save-excursion + (back-to-indentation) + (get-text-property (point) 'mhtml-submode)))) + (if submode + (save-restriction + (let* ((region-start (previous-single-property-change (point) + 'mhtml-sub= mode)) + (base-indent (save-excursion + (goto-char region-start) + (sgml-calculate-indent)))) + (narrow-to-region region-start (point-max)) + (let ((prog-indentation-context (list base-indent + (cons (point-min) nil) + nil))) + (mhtml--with-locals submode + ;; indent-line-function was rebound by + ;; mhtml--with-locals. + (funcall indent-line-function))))) + ;; HTML. + (sgml-indent-line)))) + +(defun mhtml--flyspell-check-word () + (let ((submode (get-text-property (point) 'mhtml-submode))) + (if submode + (flyspell-generic-progmode-verify) + t))) + +;;;###autoload +(define-derived-mode mhtml-mode html-mode + '((sgml-xml-mode "XHTML+" "HTML+") (:eval (mhtml--submode-lighter))) + "Major mode based on `html-mode', but works with embedded JS and CSS. + +Code inside a