emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: html, css, and js modes working together


From: Stefan Monnier
Subject: Re: html, css, and js modes working together
Date: Thu, 02 Feb 2017 09:19:08 -0500
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/26.0.50 (gnu/linux)

> * 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.

In most major modes, the tokenizer needs to be adjusted (IOW, the
default values of smie-*ward-token-functions only exists so it's easier
to get a new major mode half-working): a quick "grep '(smie-setup'
**/*.el" indicates that only ps-mode relies on the default tokenizer.

So yes, it's important for smie-with-rules to set the
for/backward-token-function.

> -    (if (bobp) 0)))
> +    (if (bobp) (prog-first-column))))

Hmm.. if we want to obey prog-indentation-context,
don't we want something like

    (if (and prog-indentation-context
             (<= (point) (caadr prog-indentation-context)))
        (car prog-indentation-context)
      0)

> +(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))

I think it's likely that the implementation of this macro will change
over time, even if it's external appearance doesn't.  So I think we
should make it expand to a code that calls an smie function that does
the actual work.  E.g.

    (defmacro smie-with-rules (spec &rest body)
      (smie-funcall-with-rules (list ,@spec) (lambda () . ,body)))
    (defun smie-funcall-with-rules (spec fun)
      (let ((smie-grammar (car spec))
            (smie-rules-function (cadr spec))
            (indent-line-function #'smie-indent-line))
        (funcall fun)))

Then again, maybe we want something even more generic that's not
specific to SMIE (see below).

> +          (t (prog-first-column)))))

I suspect here as well, we should obey (caadr prog-indentation-context).

> +(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'.")

As pointed out by Clément, these regexps are a bad idea.
Better do something like:

    (defun html-syntax-propertize (start end)
      (goto-char start)
      (when (get-text-property (point) 'html-submode)
        (html--syntax-propertize-submode
         (get-text-property (point) 'html-submode)
         end))
      (funcall
       (syntax-propertize-rules
        ("<style.*?>"
         (0 (ignore (goto-char (match-end 0))
                    (html--syntax-propertize-submode 'css-mode end))))
        ("<script.*?>"
         (0 (ignore (goto-char (match-end 0))
                    (html--syntax-propertize-submode 'js-mode end)))))
       (point) end))


> +    (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)))))

How 'bout instead doing something like:

    (defun html--get-mode-vars (mode)
      (with-temp-buffer
        (funcall mode)
        (buffer-local-variables)))

    (defconst html--css-vars (html--get-mode-vars 'css-mode))
    (defconst html--js-vars (html--get-mode-vars js-mode))

    (defun html--funcall-with-mode-var (vars fun)
      (cl-progv (mapcar #'car vars) (mapcar #'cdr vars) (funcall fun)))

and then do

    ((member tag '("style" "script"))
     (let* ((start (sgml-tag-end context))
            (base-indent (save-excursion
                           (goto-char start)
                           (sgml-calculate-indent)))
            (prog-indentation-context (list base-indent
                                               (cons start nil)
                                               nil))
            (vars (if (equal tag "style") html--css-vars html--js-vars)))
       (cl-progv (mapcar #'car vars) (mapcar #'cdr vars)
         (indent-according-to-mode))))

I'm sure the above will break miserably, because you'll need to tweak
the list of vars that really matter (most likely some of the
buffer-local-variables should not be in html--*-vars), but such an
approach should also help break the cyclic dependencies.


        Stefan




reply via email to

[Prev in Thread] Current Thread [Next in Thread]