Subject: bug#36516: Wrong dynamic abbrev expansion after space

From: Juri Linkov <[email-masked]>
Date: Sun, 28 Jul 2019 23:47:53 +0300

>> 0. emacs -Q
>> 1. Type these two lines:
>> str 1
>> str 2
>> 2. On the third line type:
>>  s                      ;; self-insert-command
>>  M-/                    ;; dabbrev-expand
>>  SPC                    ;; self-insert-command
>>  C-M-/                  ;; dabbrev-completion
>>
>> This correctly displays all available completions
>> “str 1” and “str 2”.  But ‘s M-/ SPC M-/ M-/’ doesn't get
>> the second completion.
>
> ’s M-/ SPC M-/’ doesn’t do a normal completion, it does this (from 
> the manual):
>
>     After you have expanded a dynamic abbrev, you can copy additional
>     words that follow the expansion in its original context. Simply type
>     <SPC> M-/ for each additional word you want to copy. The spacing and
>     punctuation between words is copied along with the words.

Oh, this special-casing unfortunately breaks the natural workflow.

> I suppose that subsequent M-/’s are undocumented, but the current
> behaviour is, IMO, definitely confusing as it seems to search other
> buffers for completions even though there are valid completions in the
> current buffer.

Maybe it's possible to break out of the vicious cycle of ‘SPC M-/’
repetitions by making ‘SPC M-/’ look like a normal expansion, e.g.:

diff --git a/test/lisp/dabbrev-tests.el b/test/lisp/dabbrev-tests.el
index a6ab2e7201..39fd37db1d 100644
--- a/test/lisp/dabbrev-tests.el
+++ b/test/lisp/dabbrev-tests.el
@@ -40,3 +40,27 @@
      ;; M-/ SPC M-/ M-/
      (execute-kbd-macro "\257 \257\257"))
    (should (string= (buffer-string) "ab  x\nab y\nab  y"))))
+
+(ert-deftest dabbrev-with-space-word ()
+  "Test for bug#36516.
+Check that the ‘<SPC> M-/’ feature still works as documented
+in `(emacs) Dynamic Abbrevs'."
+  (with-temp-buffer
+   (insert "sub a b\nstr a c\ns")
+   (save-window-excursion
+     (set-window-buffer nil (current-buffer))
+     ;; M-/ M-/ SPC M-/ SPC M-/
+     (execute-kbd-macro "\257\257 \257 \257"))
+   (should (string= (buffer-string) "sub a b\nstr a c\nsub a b"))))
+
+(ert-deftest dabbrev-without-space-word ()
+  "Test for bug#36516.
+Check that ‘<SPC> M-/ M-/’ doesn't conflict with the ‘<SPC> M-/’ feature
+documented in `(emacs) Dynamic Abbrevs'."
+  (with-temp-buffer
+   (insert "str 1\nstr 2\ns")
+   (save-window-excursion
+     (set-window-buffer nil (current-buffer))
+     ;; M-/ SPC M-/ M-/
+     (execute-kbd-macro "\257 \257\257"))
+   (should (string= (buffer-string) "str 1\nstr 2\nstr 1"))))
diff --git a/lisp/dabbrev.el b/lisp/dabbrev.el
index 23abe7ae16..ffb1b7d819 100644
--- a/lisp/dabbrev.el
+++ b/lisp/dabbrev.el
@@ -472,43 +472,35 @@ dabbrev-expand
 		 (markerp dabbrev--last-abbrev-location)
 		 (marker-position dabbrev--last-abbrev-location)
 		 (= (point) (1+ dabbrev--last-abbrev-location)))
-	    (progn
-	      ;; The "abbrev" to expand is just the space.
-	      (setq abbrev " ")
-	      (save-excursion
-		(save-restriction
-		  (widen)
-		  (if dabbrev--last-buffer
-		      (set-buffer dabbrev--last-buffer))
-		  ;; Find the end of the last "expansion" word.
-		  (if (or (eq dabbrev--last-direction 1)
-			  (and (eq dabbrev--last-direction 0)
-			       (< dabbrev--last-expansion-location (point))))
-		      (setq dabbrev--last-expansion-location
-			    (+ dabbrev--last-expansion-location
-			       (length dabbrev--last-expansion))))
-		  (goto-char dabbrev--last-expansion-location)
-		  ;; Take the following word, with intermediate separators,
-		  ;; as our expansion this time.
-		  (re-search-forward
-		   (concat "\\(?:" dabbrev--abbrev-char-regexp "\\)+"))
-		  (setq expansion (buffer-substring-no-properties
-				   dabbrev--last-expansion-location (point)))
-
-		  ;; Record the end of this expansion, in case we repeat this.
-		  (setq dabbrev--last-expansion-location (point))))
-	      ;; Indicate that dabbrev--last-expansion-location is
-	      ;; at the end of the expansion.
-	      (setq dabbrev--last-direction -1))
+            (save-excursion
+	      (save-restriction
+	        (widen)
+	        (if dabbrev--last-buffer
+		    (set-buffer dabbrev--last-buffer))
+	        ;; Find the end of the last "expansion" word.
+	        (if (or (eq dabbrev--last-direction 1)
+		        (and (eq dabbrev--last-direction 0)
+			     (< dabbrev--last-expansion-location (point))))
+		    (setq dabbrev--last-expansion-location
+			  (+ dabbrev--last-expansion-location
+			     (length dabbrev--last-expansion))))
+	        (goto-char dabbrev--last-expansion-location)
+	        ;; Take the following word, with intermediate separators,
+	        ;; as our expansion this time.
+	        (re-search-forward
+	         (concat "\\(?:" dabbrev--abbrev-char-regexp "\\)+"))
 
+	        ;; Record the end of this expansion, in case we repeat this.
+	        (setq dabbrev--last-expansion-location (point))))
 	  ;; We have a different abbrev to expand.
-	  (dabbrev--reset-global-variables)
-	  (setq direction (if (null arg)
-			      (if dabbrev-backward-only 1 0)
-			    (prefix-numeric-value arg)))
-	  (setq abbrev (dabbrev--abbrev-at-point))
-	  (setq record-case-pattern t)
-	  (setq old nil)))
+          (dabbrev--reset-global-variables))
+
+	(setq direction (if (null arg)
+			    (if dabbrev-backward-only 1 0)
+			  (prefix-numeric-value arg)))
+	(setq abbrev (dabbrev--abbrev-at-point))
+	(setq record-case-pattern t)
+	(setq old nil))
 
       ;;--------------------------------
       ;; Find the expansion