Delete empty local-variable block ad prop line

Delete local variable block or prop line when it became empty
after deleting a variable.  (Bug#81516)

* lisp/files-x.el (modify-file-local-variable)
(modify-file-local-variable-prop-line): Delete empty block or
prop-line.
* test/lisp/files-x-tests.el (files-x-test-add-file-local-variable)
(files-x-test-add-file-local-variable-prop-line)
(files-x-test-delete-file-local-variable)
(files-x-test-delete-file-local-variable-prop-line): New tests.
This commit is contained in:
Daniel Mendler 2026-07-30 07:53:17 +02:00 committed by Eli Zaretskii
parent fb6ad8aa78
commit 8b8aa7ced6
2 changed files with 228 additions and 4 deletions

View file

@ -201,7 +201,8 @@ the user how to make the new value take effect."
(match-beginning 0)))
(suffix (buffer-substring (point) (line-end-position)))
(prefix-re (concat "^" (regexp-quote prefix)))
(suffix-re (concat (regexp-quote suffix) "$")))
(suffix-re (concat (regexp-quote suffix) "$"))
(deleted nil))
;; Find or add missing "End:".
(forward-line 1)
@ -224,7 +225,17 @@ the user how to make the new value take effect."
(while (re-search-forward
(format "%s%S:.*%s" prefix-re variable suffix-re) end t)
(delete-region (match-beginning 0) (1+ (match-end 0)))
(setq replaced-pos (point)))))
(setq replaced-pos (point)
deleted t))
;; Delete empty local variables block.
(when (and (eq op 'delete) deleted (= beg end))
(save-excursion
(delete-region (progn (goto-char beg)
(forward-line -1)
(point))
(progn (goto-char end)
(forward-line 1)
(point)))))))
;; Add a new variable/value pair. Add `mode' to the start, add new
;; variable to the end, and add a replaced variable to its last location.
@ -295,7 +306,7 @@ from the -*- line ignoring the input argument VALUE.
If optional variable INTERACTIVE is non-nil, display a message telling
the user how to make the new value take effect."
(catch 'exit
(let ((beg (point)) end replaced-pos)
(let ((beg (point)) end replaced-pos deleted)
(unless enable-local-variables
(throw 'exit (message "File-local variables are disabled")))
@ -357,6 +368,7 @@ the user how to make the new value take effect."
;; Replace or delete MODENAME
(progn
(when (member op '(add-or-replace delete))
(setq deleted t)
(delete-region (match-beginning 1) (match-end 1)))
(when (eq op 'add-or-replace)
(goto-char (match-beginning 1))
@ -387,7 +399,8 @@ the user how to make the new value take effect."
(skip-chars-forward " \t;")
(when (eq key variable)
(delete-region (match-beginning 0) (point))
(setq replaced-pos (point)))))))
(setq replaced-pos (point)
deleted t))))))
;; Add a new variable/value pair. Add `mode' to the start, add new
;; variable to the end, and add a replaced variable to its last location.
(when (eq op 'add-or-replace)
@ -406,6 +419,28 @@ the user how to make the new value take effect."
(insert (format "%S: %S;" variable value))
(unless (eq (char-after) ?\s) (insert " ")))))
;; Delete empty prop-line.
(when (and (eq op 'delete) deleted (= beg end))
(save-excursion
;; First delete -*- -*-
(delete-region
(progn
(goto-char beg)
(search-backward "-*-" (line-beginning-position))
(point))
(progn
(goto-char end)
(search-forward "-*-" (line-end-position))
(point)))
;; And then delete line if comment is empty.
(goto-char (line-beginning-position))
(when (looking-at-p
(concat
"^[ \t]*\\(?:" (regexp-quote (or comment-start ";"))
"\\)+[ \t]*" (regexp-quote (or comment-end ""))
"[ \t]*$"))
(delete-region (point) (progn (forward-line 1) (point))))))
(when interactive
(modify-file-local-variable-message variable value op)))))

View file

@ -638,5 +638,194 @@ or `executable-find', their default value must be used nonetheless."
`(connection-local-profile-alist ',clpa now)
`(connection-local-criteria-alist ',clca now)))))
(ert-deftest files-x-test-add-file-local-variable ()
"Test adding and replacing file local variables."
;; Simple adding
(with-temp-buffer
(add-file-local-variable 'foo 1)
(add-file-local-variable 'bar 2)
(should (equal (buffer-string)
"\n;; Local Variables:\n;; foo: 1\n;; bar: 2\n;; End:\n")))
;; Adding in c-mode
(with-temp-buffer
(c-mode)
(setq-local comment-start "/* " comment-end " */")
(add-file-local-variable 'foo 1)
(add-file-local-variable 'bar 2)
(should (equal (buffer-string)
(concat "\n/* Local Variables: */\n"
"/* foo: 1 */\n"
"/* bar: 2 */\n"
"/* End: */\n"))))
;; Replacing
(with-temp-buffer
(add-file-local-variable 'foo 1)
(add-file-local-variable 'bar 2)
(add-file-local-variable 'foo t)
(add-file-local-variable 'bar nil)
(should (equal (buffer-string)
"\n;; Local Variables:\n;; foo: t\n;; bar: nil\n;; End:\n")))
;; Replacing with prefix/suffix
(with-temp-buffer
(insert ";; PRE Local Variables: SUF\n"
";; PRE foo: t SUF\n"
";; PRE bar: nil SUF\n"
";; PRE End: SUF\n")
(add-file-local-variable 'foo t)
(add-file-local-variable 'bar nil)
(should (equal (buffer-string)
(concat ";; PRE Local Variables: SUF\n"
";; PRE foo: t SUF\n"
";; PRE bar: nil SUF\n"
";; PRE End: SUF\n")))))
(ert-deftest files-x-test-delete-file-local-variable ()
"Test deleting file local variables."
;; Simple deleting
(with-temp-buffer
(setq-local comment-start ";;" comment-end "")
(insert "BEFORE\n"
";; Local Variables:\n"
";; foo: t\n"
";; bar: nil\n"
";; End:\n"
"AFTER")
(delete-file-local-variable 'foo)
(should (equal (buffer-string)
(concat "BEFORE\n"
";; Local Variables:\n"
";; bar: nil\n"
";; End:\n"
"AFTER")))
(delete-file-local-variable 'bar)
(should (equal (buffer-string) "BEFORE\nAFTER")))
;; Deleting in c-mode
(with-temp-buffer
(c-mode)
(setq-local comment-start "/* " comment-end " */")
(insert "BEFORE\n"
"/* Local Variables: */\n"
"/* foo: t */\n"
"/* bar: nil */\n"
"/* End: */\n"
"AFTER")
(delete-file-local-variable 'foo)
(should (equal (buffer-string)
(concat "BEFORE\n"
"/* Local Variables: */\n"
"/* bar: nil */\n"
"/* End: */\n"
"AFTER")))
(delete-file-local-variable 'bar)
(should (equal (buffer-string) "BEFORE\nAFTER")))
;; Ensure that other blocks are untouched
(with-temp-buffer
(setq-local comment-start ";;" comment-end "")
(insert ";; Local Variables:\n"
";; foo: t\n"
";; End:\n"
";; Local Variables:\n"
";; PRESERVE\n"
";; End:\n")
(delete-file-local-variable 'foo)
(should (equal (buffer-string)
";; Local Variables:\n;; PRESERVE\n;; End:\n")))
;; Deleting with prefix/suffix
(with-temp-buffer
(setq-local comment-start ";;" comment-end "")
(insert "BEFORE\n"
";; PRE Local Variables: SUF\n"
";; PRE foo: t SUF\n"
";; PRE bar: nil SUF\n"
";; PRE End: SUF\n"
"AFTER")
(delete-file-local-variable 'foo)
(should (equal (buffer-string)
(concat "BEFORE\n"
";; PRE Local Variables: SUF\n"
";; PRE bar: nil SUF\n"
";; PRE End: SUF\n"
"AFTER")))
(delete-file-local-variable 'bar)
(should (equal (buffer-string) "BEFORE\nAFTER"))))
(ert-deftest files-x-test-add-file-local-variable-prop-line ()
"Test adding and replacing file local variables in the prop-line."
;; Simple adding
(with-temp-buffer
(emacs-lisp-mode)
(add-file-local-variable-prop-line 'foo 1)
(add-file-local-variable-prop-line 'bar 2)
(should (equal (buffer-string) ";; -*- foo: 1; bar: 2; -*-\n")))
;; Adding in c-mode
(with-temp-buffer
(c-mode)
(setq-local comment-start "/* " comment-end " */")
(add-file-local-variable-prop-line 'foo 1)
(add-file-local-variable-prop-line 'bar 2)
(should (equal (buffer-string) "/* -*- foo: 1; bar: 2; -*- */\n")))
;; Adding to mode variable
(with-temp-buffer
(emacs-lisp-mode)
(insert ";; -*- org-mode -*-\n")
(add-file-local-variable-prop-line 'foo 1)
(should (equal (buffer-string) ";; -*- mode: org-mode; foo: 1; -*-\n")))
;; Simple replacing
(with-temp-buffer
(emacs-lisp-mode)
(add-file-local-variable-prop-line 'foo 1)
(add-file-local-variable-prop-line 'bar 2)
(add-file-local-variable-prop-line 'foo t)
(add-file-local-variable-prop-line 'bar nil)
(should (equal (buffer-string) ";; -*- foo: t; bar: nil; -*-\n")))
;; Replacing with prefix/suffix
(with-temp-buffer
(emacs-lisp-mode)
(insert ";; PRE -*- foo: 1; bar: 2; -*- SUF\n")
(add-file-local-variable-prop-line 'foo t)
(add-file-local-variable-prop-line 'bar nil)
(should (equal (buffer-string)
";; PRE -*- foo: t; bar: nil; -*- SUF\n"))))
(ert-deftest files-x-test-delete-file-local-variable-prop-line ()
"Test deleting file local variables in prop-line."
;; Simple deleting
(with-temp-buffer
(emacs-lisp-mode)
(insert ";; -*- foo: t; bar: nil -*-\n")
(delete-file-local-variable-prop-line 'foo)
(should (equal (buffer-string) ";; -*- bar: nil -*-\n"))
(delete-file-local-variable-prop-line 'bar)
(should (equal (buffer-string) "")))
;; Deleting mode variable
(with-temp-buffer
(emacs-lisp-mode)
(insert ";; -*- org-mode -*-\n")
(delete-file-local-variable-prop-line 'mode)
(should (equal (buffer-string) "")))
;; Deleting in c-mode
(with-temp-buffer
(c-mode)
(setq-local comment-start "/* " comment-end " */")
(insert "/* -*- foo: t; bar: nil -*- */\n")
(delete-file-local-variable-prop-line 'foo)
(should (equal (buffer-string) "/* -*- bar: nil -*- */\n"))
(delete-file-local-variable-prop-line 'bar)
(should (equal (buffer-string) "")))
;; Ensure that other prop lines are untouched
(with-temp-buffer
(emacs-lisp-mode)
(insert ";; -*- foo: t -*-\n;; -*- -*-")
(delete-file-local-variable-prop-line 'foo)
(should (equal (buffer-string) ";; -*- -*-")))
;; Deleting with prefix/suffix
(with-temp-buffer
(emacs-lisp-mode)
(insert ";; PRE -*- foo: t; bar: nil -*- SUF\n")
(delete-file-local-variable-prop-line 'foo)
(should (equal (buffer-string) ";; PRE -*- bar: nil -*- SUF\n"))
(delete-file-local-variable-prop-line 'bar)
(should (equal (buffer-string) ";; PRE SUF\n"))))
(provide 'files-x-tests)
;;; files-x-tests.el ends here