summaryrefslogtreecommitdiff
path: root/modules/custom-file-buffer.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-26 17:40:28 -0500
committerCraig Jennings <c@cjennings.net>2025-10-26 17:40:28 -0500
commitc700bc14f091a346351474ebce24aa32e0dca814 (patch)
tree82556caf6206c8e3b5836556e4402888eca66dea /modules/custom-file-buffer.el
parent1a6365b94352c966a9d0bc63ccf9e7727ec2d6e1 (diff)
feat+test:custom-file-buffer: add tests and safety refactoring
Add 106 unit tests with full coverage for move-buffer-and-file (51 tests) and rename-buffer-and-file (55 tests). Refactor both functions using interactive/non-interactive split pattern for simpler testing and reusability. Changes: - Split cj/move-buffer-and-file and cj/rename-buffer-and-file into internal implementations (cj/--*) and interactive wrappers - Add ok-if-exists parameter with user confirmation to prevent data loss - Fix bugs: return values, path expansion, string-match arg order, regex - Add test utilities for proper buffer cleanup and isolation - Document interactive/non-interactive pattern in quality-engineer.org - Document error message testing guidelines in quality-engineer.org All 106 tests passing.
Diffstat (limited to 'modules/custom-file-buffer.el')
-rw-r--r--modules/custom-file-buffer.el79
1 files changed, 63 insertions, 16 deletions
diff --git a/modules/custom-file-buffer.el b/modules/custom-file-buffer.el
index 6ed19d73..a56edf18 100644
--- a/modules/custom-file-buffer.el
+++ b/modules/custom-file-buffer.el
@@ -81,35 +81,82 @@ Sends directly to the system spooler with no header."
;; ------------------------- Buffer And File Operations ------------------------
-(defun cj/move-buffer-and-file (dir)
- "Move both current buffer and the file it visits to DIR."
- (interactive "DMove buffer and file (to new directory): ")
+(defun cj/--move-buffer-and-file (dir &optional ok-if-exists)
+ "Internal implementation: Move buffer and file to DIR.
+If OK-IF-EXISTS is nil and target exists, signal an error.
+If OK-IF-EXISTS is non-nil, overwrite existing file.
+Returns t on success, nil if buffer not visiting a file."
(let* ((name (buffer-name))
(filename (buffer-file-name))
+ (dir (expand-file-name dir))
(dir
- (if (string-match dir "\\(?:/\\|\\\\)$")
+ (if (string-match "[/\\\\]$" dir)
(substring dir 0 -1) dir))
(newname (concat dir "/" name)))
(if (not filename)
- (message "Buffer '%s' is not visiting a file!" name)
- (progn (copy-file filename newname 1) (delete-file filename)
- (set-visited-file-name newname) (set-buffer-modified-p nil) t))))
+ (progn
+ (message "Buffer '%s' is not visiting a file!" name)
+ nil)
+ (progn (copy-file filename newname ok-if-exists)
+ (delete-file filename)
+ (set-visited-file-name newname)
+ (set-buffer-modified-p nil)
+ t))))
+
+(defun cj/move-buffer-and-file (dir)
+ "Move both current buffer and the file it visits to DIR.
+When called interactively, prompts for confirmation if target file exists."
+ (interactive (list (read-directory-name "Move buffer and file (to new directory): ")))
+ (let* ((target (expand-file-name (buffer-name) (expand-file-name dir))))
+ (condition-case err
+ (cj/--move-buffer-and-file dir nil)
+ (file-already-exists
+ (if (yes-or-no-p (format "File %s exists; overwrite? " target))
+ (cj/--move-buffer-and-file dir t)
+ (message "File not moved"))))))
+
+(defun cj/--rename-buffer-and-file (new-name &optional ok-if-exists)
+ "Internal implementation: Rename buffer and file to NEW-NAME.
+NEW-NAME can be just a basename or a full path to move to different directory.
+If OK-IF-EXISTS is nil and target exists, signal an error.
+If OK-IF-EXISTS is non-nil, overwrite existing file.
+Returns t on success, nil if buffer not visiting a file."
+ (let ((filename (buffer-file-name))
+ (new-basename (file-name-nondirectory new-name)))
+ (if (not filename)
+ (progn
+ (message "Buffer '%s' is not visiting a file!" (buffer-name))
+ nil)
+ ;; Check if a buffer with the new name already exists
+ (when (and (get-buffer new-basename)
+ (not (eq (get-buffer new-basename) (current-buffer))))
+ (error "A buffer named '%s' already exists" new-basename))
+ ;; Expand new-name to absolute path (preserves directory if just basename)
+ (let ((expanded-name (expand-file-name new-name
+ (file-name-directory filename))))
+ (rename-file filename expanded-name ok-if-exists)
+ (rename-buffer new-basename)
+ (set-visited-file-name expanded-name)
+ (set-buffer-modified-p nil)
+ t))))
(defun cj/rename-buffer-and-file (new-name)
- "Rename both current buffer and the file it visits to NEW-NAME."
+ "Rename both current buffer and the file it visits to NEW-NAME.
+When called interactively, prompts for confirmation if target file exists."
(interactive
(list (if (not (buffer-file-name))
(user-error "Buffer '%s' is not visiting a file!" (buffer-name))
(read-string "Rename buffer and file (to new name): "
(file-name-nondirectory (buffer-file-name))))))
- (let ((filename (buffer-file-name)))
- (if (get-buffer new-name)
- (message "A buffer named '%s' already exists!" new-name)
- (progn
- (rename-file filename new-name 1)
- (rename-buffer new-name)
- (set-visited-file-name new-name)
- (set-buffer-modified-p nil)))))
+ (condition-case err
+ (cj/--rename-buffer-and-file new-name nil)
+ (file-already-exists
+ (if (yes-or-no-p (format "File %s exists; overwrite? " new-name))
+ (cj/--rename-buffer-and-file new-name t)
+ (message "File not renamed")))
+ (error
+ ;; Handle buffer-already-exists and other errors
+ (message "%s" (error-message-string err)))))
(defun cj/delete-buffer-and-file ()
"Kill the current buffer and delete the file it visits."