Subject: bug#7937: nxml-mode indenting bug fix.

From: Stephen Meister <[email-masked]>
Date: Tue, 28 Mar 2023 15:05:05 -0400

Hi,
  I've tried to put a fix in place for bug 7937 (and possibly 7768
unintentionally).  The repair I've made will cause changes in the way that
nxml indents lines under normal circumstances.  I'm not sure if a change
like this is acceptable given that I'm only trying to fix a bug.  The
behavior change is most visible in a situation such as:

<root>
  <a><b><c>
    inner
  </c>
</b>
  </a>
</root>

Which would now be indented as:
<root>
  <a><b><c>
        inner
      </c>
    </b>
  </a>
</root>

There are additional tests which show more of the indenting changes as well.

This is my first bug fix, please let me know if I've missed anything
(procedurally or otherwise).  I'm happy to rework/restructure this based on
any suggestions.

-steve
diff --git a/lisp/nxml/nxml-mode.el b/lisp/nxml/nxml-mode.el
index 3869d0327fd..74108fa9d73 100644
--- a/lisp/nxml/nxml-mode.el
+++ b/lisp/nxml/nxml-mode.el
@@ -1327,7 +1327,8 @@ nxml-compute-indent-from-matching-start-tag
 its line.  Otherwise return nil."
   (save-excursion
     (back-to-indentation)
-    (let ((bol (point)))
+    (let ((bol (point))
+          bol-token-type)
       (let ((inhibit-field-text-motion t))
 	(end-of-line))
       (skip-chars-backward " \t")
@@ -1338,6 +1339,7 @@ nxml-compute-indent-from-matching-start-tag
 	       (save-excursion
 		 (goto-char bol)
 		 (nxml-token-after)
+                 (setq bol-token-type xmltok-type)
 		 (= xmltok-start bol))
 	       (eq xmltok-type 'data))
 	   (condition-case nil
@@ -1352,15 +1354,19 @@ nxml-compute-indent-from-matching-start-tag
 	     (goto-char xmltok-start)
 	     (skip-chars-backward " \t")
 	     (bolp))
-	   (current-indentation)))))
+	   (+ (current-indentation)
+              ;; in the case of a line starting with data, keep the
+              ;; indent level above the starting tag.
+              (if (eq bol-token-type 'data)
+                  nxml-child-indent
+                0))))))
 
 (defun nxml-compute-indent-from-previous-line ()
   "Compute the indent for a line using the indentation of a previous line."
   (save-excursion
     (end-of-line)
     (let ((eol (point))
-	  bol prev-bol ref
-	  before-context after-context)
+	  bol prev-bol ref)
       (back-to-indentation)
       (setq bol (point))
       (catch 'indent
@@ -1378,60 +1384,48 @@ nxml-compute-indent-from-previous-line
 		   (not (or (= xmltok-start (point))
 			    (eq xmltok-type 'data))))))
 	(setq ref (point))
-	;; Now scan over tokens until the end of the line to be indented.
-	;; Determine the context before and after the beginning of the
-	;; line.
-	(while (< (point) eol)
-	  (nxml-tokenize-forward)
-	  (cond ((<= bol xmltok-start)
-		 (setq after-context
-		       (nxml-merge-indent-context-type after-context)))
-		((and (<= (point) bol)
-		      (not (and (eq xmltok-type 'partial-start-tag)
-				(= (point) bol))))
-		 (setq before-context
-		       (nxml-merge-indent-context-type before-context)))
-		((eq xmltok-type 'data)
-		 (setq before-context
-		       (nxml-merge-indent-context-type before-context))
-		 (setq after-context
-		       (nxml-merge-indent-context-type after-context)))
-		;; If in the middle of a token that looks inline,
-		;; then indent relative to the previous non-blank line
-		((eq (nxml-merge-indent-context-type before-context)
-		     'mixed)
-		 (goto-char prev-bol)
-		 (throw 'indent (current-column)))
-		(t
-		 (throw 'indent
-			(nxml-compute-indent-in-token bol))))
-	  (skip-chars-forward " \t\r\n"))
-	(goto-char ref)
-	(+ (current-column)
-	   (* nxml-child-indent
-	      (+ (if (eq before-context 'start-tag) 1 0)
-		 (if (eq after-context 'end-tag) -1 0))))))))
-
-(defun nxml-merge-indent-context-type (context)
-  "Merge the indent context type CONTEXT with the token in `xmltok-type'.
-Return the merged indent context type.  An indent context type is
-either nil or one of the symbols `start-tag', `end-tag', `markup',
-`comment', `mixed'."
-  (cond ((memq xmltok-type '(start-tag partial-start-tag))
-	 (if (memq context '(nil start-tag comment))
-	     'start-tag
-	   'mixed))
-	((memq xmltok-type '(end-tag partial-end-tag))
-	 (if (memq context '(nil end-tag comment))
-	     'end-tag
-	   'mixed))
-	((eq xmltok-type 'comment)
-	 (cond ((memq context '(start-tag end-tag comment))
-		context)
-	       (context 'mixed)
-	       (t 'comment)))
-	(context 'mixed)
-	(t 'markup)))
+        (let ((depth-before 0)
+              (depth-after 0)
+              start-type-before)
+          (nxml-tokenize-forward)
+          (when (> (point) bol)         ; one token spans this and the line before
+            (throw 'indent (nxml-compute-indent-in-token bol)))
+          
+          ;; Scan over the prevous line to determine the change in element depth
+          (setq start-type-before xmltok-type)
+          (while (< (point) bol)        ; examine all tags on the previous line
+            (cond ((eq xmltok-type 'partial-start-tag)
+                   (throw 'indent (nxml-compute-indent-in-token bol)))
+                  ((eq xmltok-type 'start-tag)
+                   (cl-incf depth-before))
+                  ((eq xmltok-type 'end-tag)
+                   (cl-incf depth-before -1)))
+            (skip-chars-forward " \t\r\n")
+            (nxml-tokenize-forward))
+          
+          ;; Scan over the current line to determine the change in element depth
+          (while (and (<= (point) eol)        ; examine all tags on the current line.
+                      (< (point) (point-max)))
+            (cond ((eq  xmltok-type 'start-tag)
+                   (cl-incf depth-after))
+                  ((eq xmltok-type 'end-tag)
+                   (cl-incf depth-after -1)))
+            (skip-chars-forward " \t\r\n")
+            (nxml-tokenize-forward))
+          
+          (goto-char ref)
+	  (+ (current-column)
+	     (* nxml-child-indent
+                (+ (max 0 depth-before)
+                   (min 0 depth-after)
+                   ;; if our before-line started as a data element we
+                   ;; must back out that indentation as well as long
+                   ;; as we are not beginning additional tags
+                   (if (and (memq start-type-before '(data cdata-section))
+                            (>= depth-after 0)
+                            (< depth-before 0))
+                       -1
+                     0)))))))))
 
 (defun nxml-compute-indent-in-token (pos)
   "Return the indent for a line that starts inside a token.
diff --git a/test/lisp/nxml/nxml-mode-tests.el b/test/lisp/nxml/nxml-mode-tests.el
index 973f2ebb67e..532c39e8608 100644
--- a/test/lisp/nxml/nxml-mode-tests.el
+++ b/test/lisp/nxml/nxml-mode-tests.el
@@ -31,6 +31,17 @@ nxml-mode-tests-correctly-indented-string
 
 (ert-deftest nxml-indent-line-after-attribute ()
   (should (nxml-mode-tests-correctly-indented-string "
+<settings xmlns=\"http://maven.apache.org/SETTINGS/1.0.0\"
+          xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
+          xsi:schemaLocation=\"http://maven.apache.org/SETTINGS/1.0.0
+                              https://maven.apache.org/xsd/settings-1.0.0.xsd\">
+  <mirrors one=\"two\"
+           three=\"four\">
+    ...
+  </mirrors>
+</settings>
+"))
+  (should (nxml-mode-tests-correctly-indented-string "
 <settings
     xmlns=\"http://maven.apache.org/SETTINGS/1.0.0\"
     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
@@ -155,5 +166,100 @@ nxml-mode-test-comment-bug-17264
       ;; Inside comment
       (should (eq (nth 4 (syntax-ppss)) t)))))
 
+(ert-deftest nxml-mode-test-indent-line-after-data ()
+  "Test indenting of lines after data or cdata"
+  (should (nxml-mode-tests-correctly-indented-string "
+<kml>
+  <description>
+    more</description>
+  <abc>data</abc>
+
+  <description>
+    more
+    <abc>data</abc>
+  </description>
+
+  <description>
+    more
+    <abc>
+      data
+    </abc>
+  </description>
+
+  <description>
+    <![CDATA[multi
+             line
+             data]]></description>
+  <abc>data</abc>
+
+  <description>
+    <![CDATA[multi
+             line
+             data]]>
+    <abc>data</abc>
+  </description>
+  <description>
+    <![CDATA[multi
+             line
+             data]]>
+    <abc>
+      data
+    </abc>
+  </description>
+</kml>
+")))
+
+
+(ert-deftest nxml-mode-test-multiple-start-tags-single-line ()
+  "Test for indent depth where multiple tags are on one line."
+  (should (nxml-mode-tests-correctly-indented-string "
+<kml>
+  <Document>
+    <Folder><name>abc</name>
+      <Folder><name>123</name>
+      </Folder>
+    </Folder>
+  </Document>
+</kml>"))
+  (should (nxml-mode-tests-correctly-indented-string "
+<kml>
+  <Document>
+    <Folder><name>abc</name>
+      <Folder></Folder>
+    </Folder>
+  </Document>
+</kml>"))
+  (should (nxml-mode-tests-correctly-indented-string "
+<kml>
+  <document></document>
+  <elm>
+    data
+  </elmh>
+  <a><b><c>
+        <d>data</d>
+  </c></b></a>
+  <a><b><c><inner /><d>
+          <e></e>
+  </d><inner /></c></b></a>
+  <single />
+  <one><two><three>
+        four
+      </three>
+    </two>
+  </one>
+  <one>
+    <two>
+      <three>
+        four
+  </three></two></one>
+  <one><two>
+      three<four>
+        five
+      </four>
+    </two>
+  </one>
+</kml>
+")))
+
 (provide 'nxml-mode-tests)
 ;;; nxml-mode-tests.el ends here