unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Yuri Khan <yurivkhan@gmail.com>
To: nyraghu27132@gmail.com, Emacs developers <emacs-devel@gnu.org>
Subject: Re: nXML mode maintenance and enhancement
Date: Wed, 23 May 2018 22:15:05 +0700	[thread overview]
Message-ID: <CAP_d_8XRTOqB8V7RjV0wKD5Y2swX6M6JYPPxg-LnZQCNjeJJjw@mail.gmail.com> (raw)
In-Reply-To: <87d0xmctsk.fsf@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 1568 bytes --]

On Wed, May 23, 2018 at 10:03 PM N. Raghavendra <nyraghu27132@gmail.com>
wrote:

> > There is not and cannot be One True Way to indent XML, for all users and
> > uses of nXML.

> Sure, the user can always change it.  Every mode comes with a default
> indentation style.  I am only suggesting that the default indentation
> rules of nXML conform to those of the SGML and PSGML modes.

And I’m suggesting that text markup languages constitute a small portion of
all XML-based formats, and thus the default rules might be good enough for
the majority of the formats.

> > [Customizing indentation] involves: hooking ‘rng-schema-change-hook’;
> > in the hook function, looking at ‘rng-current-schema’ to see if its
‘caddr’
> > is "html"; and, if so, pointing ‘indent-line-function’ at my own
function
> > that pretty much has to reimplement the whole of ‘nxml-indent-line’ from
> > scratch, in about 250 lines of Elisp.

> I wonder if you can share your code here; perhaps I or someone else can
> try to adapt it into a general function in nXML.

Why not. Warning, personal-use-quality code ahead.

Magic words, for all code in the attached file that is not copied from nxml:

I dedicate any and all copyright interest in this software to the
public domain. I make this dedication for the benefit of the public at
large and to the detriment of my heirs and successors. I intend this
dedication to be an overt act of relinquishment in perpetuity of all
present and future rights to this software under copyright law.

[-- Attachment #2: yk-xhtml-indent.el --]
[-- Type: text/x-emacs-lisp, Size: 10115 bytes --]

(defvar yk-xhtml-zeroindent-tags
  (list "html"
        "title" "style"
        "body" "article" "section"
        "h1" "h2" "h3" "h4" "h5" "h6"
        "p" "pre" "figcaption" "main"
        "a" "em" "strong" "small" "s" "cite" "q" "dfn" "abbr" "data" "time"
        "code" "var" "samp" "kbd" "sub" "sup" "i" "b" "u" "mark"
        "ruby" "rb" "rt" "rtc" "rp" "bdi" "bdo" "span"
        "table" "caption"
        "script")
  "Tags whose elements’ content should not be indented. This
should include inline elements and may include container
elements for which indentation adds no value.")

(defvar yk-xhtml-conditional-indent-tags
  (list "blockquote" "ol" "ul" "dl"
        "ins" "del"
        "embed" "object" "video" "audio" "map"
        "td" "th"
        "button")
  "Tags whose elements’ content should be indented only if there
is no content on the same line with the opening tag. This should
include elements with mixed content model; they will indent if
used as block elements.")

(defvar yk-xhtml-sibling-indent-tags
  '((("dd" "dt") . 1) (("dt" "dd") . -1))
  "Tags which indent relative to their preceding sibling.

Format: List of triples of the form ((TAG SIBLING-TAG) . OFFSET).")

(defvar yk-xhtml-noindent-tags
  (list "style" "script" "pre" "code" "textarea")
  "Tags whose elements’ content should retain its indentation.")


(defun yk-xhtml--noindent-elements ()
  "Return 'noindent if within a noindent element, nil if not.

Destroys: xmltok state."
  (save-excursion
    (back-to-indentation)
    (catch 'yk-xhtml-indent-result
      (while (< (point-min) (point))
        (nxml-token-before)
        (let ((token-start xmltok-start))
          (cond
           ((eq xmltok-type 'start-tag)
            (if (member (xmltok-start-tag-local-name) yk-xhtml-noindent-tags)
                (throw 'yk-xhtml-indent-result 'noindent)
              (goto-char token-start)))
           ((eq xmltok-type 'end-tag)
            (condition-case nil
                (progn
                  (nxml-scan-element-backward (point))
                  (goto-char xmltok-start))
              (nxml-scan-error (goto-char token-start))))
           (t
            (goto-char token-start)))))
      nil)))


(defun yk-xhtml-compute-indent--for-closing-tag ()
  "Return indentation for a single closing tag on a line.

A closing tag indents to its opening tag if both tags are the
only non-whitespace content on their lines.

If the current line does not consist of a single closing tag,
or if there is no matching opening tag, or if there is other
content before the matching opening tag in the same line,
return nil.

Destroys: xmltok state."
  (save-excursion
    (back-to-indentation)
    (let ((bol (point)))
      (end-of-line)
      (skip-chars-backward " \t")
      (and ;; Current line contains only a closing tag
       (= (nxml-token-before) (point))
       (memq xmltok-type '(end-tag partial-end-tag))
       (= xmltok-start bol)
       ;; No content before the matching opening tag on its line
       (let ((tok-end
              (condition-case nil
                  (nxml-scan-element-backward
                   (point) nil
                   (- (point)
                      nxml-end-tag-indent-scan-distance))
                (nxml-scan-error nil))))
         (and tok-end
              ;; (progn
              ;;   (goto-char tok-end)
              ;;   (looking-at "[ \t]*$"))
              (progn
                (goto-char xmltok-start)
                (looking-back "^[ \t]*"))
              ;; If all conditions met, return opening tag indentation
              (current-column)))))))

(defun yk-xhtml-compute-indent--from-preceding-sibling ()
  "Return indentation relative to the preceding sibling element.

If the first token on the current line is an opening tag and the
previous line ends with a closing tag and the matching opening
tag starts a line, return the indentation of the sibling
element’s opening tag adjusted by the offset specified in
`yk-xhtml-sibling-indent-tags' multiplied by
`nxml-child-indent'. Otherwise, return nil.

Destroys: xmltok state."
  (save-excursion
    (catch 'yk-result
      (back-to-indentation)
      (nxml-token-after)
      (unless (eq xmltok-type 'start-tag) (throw 'yk-result nil))
      (let ((this-tag-name (xmltok-start-tag-local-name)))
        (forward-line -1)
        (end-of-line)
        (skip-chars-backward " \t")
        (nxml-token-before)
        (unless (eq xmltok-type 'end-tag) (throw 'yk-result nil))
        (let* ((preceding-tag-name (xmltok-end-tag-local-name))
               (pair (assoc (list this-tag-name preceding-tag-name)
                            yk-xhtml-sibling-indent-tags)))
          (unless pair (throw 'yk-result nil))
          (condition-case nil
              (nxml-scan-element-backward (point))
            (nxml-scan-error (throw 'yk-result nil)))
          (goto-char xmltok-start)
          (unless (looking-back "^[ \t]*") (throw 'yk-result nil))
          (+ (current-column)
             (* nxml-child-indent
                (cdr pair))))))))


(defun yk-xhtml-compute-indent--from-preceding-element ()
  "Return indentation of the preceding block element.

If the opening tag matching the closing tag before point
starts a line, return the indentation of the opening tag.
Otherwise (if there is no matching opening tag, or if there is
non-blank text preceding it on the line), return nil.

Expects: point immediately following a closing tag.

Destroys: point, xmltok state."
  (and (condition-case nil
           (nxml-scan-element-backward
            (point) nil
            (- (point) nxml-end-tag-indent-scan-distance))
         (nxml-scan-error nil))
       (progn
         (goto-char xmltok-start)
         (skip-chars-backward " \t")
         (bolp))
       (progn
         (goto-char xmltok-start)
         (current-column))))

(defun yk-xhtml--opening-tag ()
  "Analyze the opening tag after point.

Return a list of the form (TAG-NAME CLOSED MORE-TEXT), where:

 * TAG-NAME is a string containing the local name of the tag.
 * CLOSED is t if the element is closed before the end of line,
   nil otherwise.
 * MORE-TEXT is t if there is any non-whitespace following
   the opening tag, nil if only whitespace.

Return nil if point is not immediately preceding an opening tag.

Destroys: xmltok state."

  (let ((token-end (nxml-token-after)))
    (and (= xmltok-start (point))
         (eq xmltok-type 'start-tag)
         (let ((tag-name (xmltok-start-tag-local-name))
               (more-text (save-excursion
                            (goto-char token-end)
                            (not (looking-at "[ \t]*$")))))
           (let ((closing-tag-end
                  (condition-case nil
                      (nxml-scan-element-forward (point))
                    (nxml-scan-error nil)))
                 (eol (save-excursion
                        (end-of-line)
                        (point))))
             (list tag-name
                   (and closing-tag-end
                        (<= closing-tag-end eol))
                   more-text))))))

(defun yk-xhtml-compute-indent--from-previous-line ()
  "Compute the indentation based on the previous non-blank line.

 * If there is no previous line, return 0.
 * If the previous line ends with a closing tag
   and the corresponding opening tag starts a line,
   return the indentation of the opening tag.
 * If the previous line starts with text, return its indentation.
 * If the previous line starts with an opening tag:
   * If it is closed on the same line, or
   * if it is listed in `yk-xhtml-zeroindent-tags', or
   * if it is listed in `yk-xhtml-conditional-indent-tags' and
     there is no other text on the same line, return its indentation.
   * Otherwise, return its indentation plus `nxml-child-indent'.

If the previous line starts with an opening tag which is not
closed on the same line, return the indentation of that line plus
`nxml-child-indent'."
  (save-excursion
    (while (and (zerop (forward-line -1))
                (looking-at "[ \t]*$")))
    ;; now either at the first line or at start of a non-whitespace line
    (if (looking-at "[ \t]*$") ;; first line which is blank
        0
      (end-of-line)
      (let ((eol (point)))
        (skip-chars-backward " \t")
        (nxml-token-before)
        (or (and (eq xmltok-type 'end-tag)
                 (yk-xhtml-compute-indent--from-preceding-element))
            (progn
              (back-to-indentation)
              (pcase (yk-xhtml--opening-tag)
                (`(,tag-name ,closed ,more-text)
                 (if (or closed
                         (member tag-name yk-xhtml-zeroindent-tags)
                         (and (not more-text)
                              (member tag-name yk-xhtml-conditional-indent-tags)))
                     (current-column)
                   (+ (current-column) nxml-child-indent)))
                (_ (current-column)))))))))

(defun yk-xhtml--compute-indent ()
  "Compute indentation for the current line."
  (or (yk-xhtml--noindent-elements)
      (yk-xhtml-compute-indent--for-closing-tag)
      (yk-xhtml-compute-indent--from-preceding-sibling)
      (yk-xhtml-compute-indent--from-previous-line)
      'noindent))


(defun yk-xhtml-indent-line ()
  "Indent the current line suitably for XHTML."
  (let ((indent (yk-xhtml--compute-indent))
        (savep (> (point)
                  (save-excursion
                    (back-to-indentation)
                    (point)))))
    (if (not (numberp indent))
        indent
      (if savep
          (save-excursion
            (indent-line-to indent))
        (indent-line-to indent)))))

(defun yk-xhtml-indent--maybe-enable ()
  "Set the current buffer’s indentation function
to `yk-xhtml-indent-line' if the current schema is “html”."
  (and (stringp (caddr rng-current-schema))
       (string= (caddr rng-current-schema) "html")
    (set-variable 'indent-line-function 'yk-xhtml-indent-line 'local)))

(with-eval-after-load 'nxml-mode
  (add-hook 'nxml-mode-hook 'yk-xhtml-indent--maybe-enable))

(provide 'yk-xhtml-indent)

  reply	other threads:[~2018-05-23 15:15 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-23 12:14 nXML mode maintenance and enhancement N. Raghavendra
2018-05-23 14:18 ` Yuri Khan
2018-05-23 14:48   ` N. Raghavendra
2018-05-23 15:15     ` Yuri Khan [this message]
2018-05-23 17:15       ` N. Raghavendra
2018-05-23 16:12 ` Stefan Monnier
2018-05-23 17:34   ` N. Raghavendra
2018-05-23 17:49     ` Stefan Monnier

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAP_d_8XRTOqB8V7RjV0wKD5Y2swX6M6JYPPxg-LnZQCNjeJJjw@mail.gmail.com \
    --to=yurivkhan@gmail.com \
    --cc=emacs-devel@gnu.org \
    --cc=nyraghu27132@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).