summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/custom-buffer-file.el55
-rw-r--r--tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el187
-rw-r--r--tests/test-custom-buffer-file-copy-to-top-of-buffer.el186
3 files changed, 417 insertions, 11 deletions
diff --git a/modules/custom-buffer-file.el b/modules/custom-buffer-file.el
index 2d2fa919..cc4787d9 100644
--- a/modules/custom-buffer-file.el
+++ b/modules/custom-buffer-file.el
@@ -9,11 +9,17 @@
;; - moving/renaming/deleting buffer files
;; - diffing buffer contents with saved file version
;; - copying file paths and file:// links to the kill ring
-;; - copying entire buffer contents
+;; - copying buffer contents (whole buffer, to top of buffer, to bottom of buffer)
;; - clearing buffer contents from point to top or bottom.
;;
;; The PostScript printing auto-detects the system print spooler (lpr or lp)
-;; and prints with face/syntax highlighting. Bound to keymap prefix ~C-; b~.
+;; and prints with face/syntax highlighting.
+;;
+;; Keybindings under ~C-; b~:
+;; - Copy buffer content submenu at ~C-; b c~
+;; - ~C-; b c w~ copy whole buffer
+;; - ~C-; b c t~ copy from beginning to point
+;; - ~C-; b c b~ copy from point to end
;;
;;; Code:
@@ -200,6 +206,24 @@ is created. A message is displayed when done."
(kill-new contents)
(message "Buffer contents copied to kill ring")))
+(defun cj/copy-to-bottom-of-buffer ()
+ "Copy text from point to the end of the buffer to the kill ring.
+Point and mark are left exactly where they were. No transient region
+is created. A message is displayed when done."
+ (interactive)
+ (let ((contents (buffer-substring-no-properties (point) (point-max))))
+ (kill-new contents)
+ (message "Copied from point to end of buffer")))
+
+(defun cj/copy-to-top-of-buffer ()
+ "Copy text from the beginning of the buffer to point to the kill ring.
+Point and mark are left exactly where they were. No transient region
+is created. A message is displayed when done."
+ (interactive)
+ (let ((contents (buffer-substring-no-properties (point-min) (point))))
+ (kill-new contents)
+ (message "Copied from beginning of buffer to point")))
+
(defun cj/clear-to-bottom-of-buffer ()
"Delete all text from point to the end of the current buffer.
This does not save the deleted text in the kill ring."
@@ -241,6 +265,13 @@ TODO: Future integration with difftastic for structural diffs (Method 3)."
;; --------------------------- Buffer And File Keymap --------------------------
+;; Copy buffer content sub-keymap
+(defvar-keymap cj/copy-buffer-content-map
+ :doc "Keymap for copy buffer content operations."
+ "w" #'cj/copy-whole-buffer
+ "b" #'cj/copy-to-bottom-of-buffer
+ "t" #'cj/copy-to-top-of-buffer)
+
;; Buffer & file operations prefix and keymap
(defvar-keymap cj/buffer-and-file-map
:doc "Keymap for buffer and file operations."
@@ -249,17 +280,16 @@ TODO: Future integration with difftastic for structural diffs (Method 3)."
"p" #'cj/print-buffer-ps
"d" #'cj/delete-buffer-and-file
"D" #'cj/diff-buffer-with-file
- "c" #'cj/copy-whole-buffer
+ "c" cj/copy-buffer-content-map
"n" #'cj/copy-buffer-name
+ "l" #'cj/copy-link-to-buffer-file
+ "P" #'cj/copy-path-to-buffer-file-as-kill
"t" #'cj/clear-to-top-of-buffer
"b" #'cj/clear-to-bottom-of-buffer
"x" #'erase-buffer
"s" #'mark-whole-buffer
"S" #'write-file ;; save as
- "g" #'revert-buffer
-
- "l" #'cj/copy-link-to-buffer-file
- "P" #'cj/copy-path-to-buffer-file-as-kill)
+ "g" #'revert-buffer)
(keymap-set cj/custom-keymap "b" cj/buffer-and-file-map)
(with-eval-after-load 'which-key
@@ -270,16 +300,19 @@ TODO: Future integration with difftastic for structural diffs (Method 3)."
"C-; b p" "print to PS"
"C-; b d" "delete file"
"C-; b D" "diff buffer with file"
- "C-; b c" "copy buffer"
+ "C-; b c" "buffer copy menu"
+ "C-; b c w" "copy whole buffer"
+ "C-; b c b" "copy to bottom"
+ "C-; b c t" "copy to top"
"C-; b n" "copy buffer name"
+ "C-; b l" "copy file link"
+ "C-; b P" "copy file path"
"C-; b t" "clear to top"
"C-; b b" "clear to bottom"
"C-; b x" "erase buffer"
"C-; b s" "select whole buffer"
"C-; b S" "save as"
- "C-; b g" "revert buffer"
- "C-; b l" "copy file link"
- "C-; b P" "copy file path"))
+ "C-; b g" "revert buffer"))
(provide 'custom-buffer-file)
diff --git a/tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el b/tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el
new file mode 100644
index 00000000..0c41761e
--- /dev/null
+++ b/tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el
@@ -0,0 +1,187 @@
+;;; test-custom-buffer-file-copy-to-bottom-of-buffer.el --- Tests for cj/copy-to-bottom-of-buffer -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/copy-to-bottom-of-buffer function from custom-buffer-file.el
+;;
+;; This function copies all text from point to the end of the current buffer
+;; to the kill ring without modifying the buffer.
+
+;;; Code:
+
+(require 'ert)
+(require 'testutil-general)
+
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Stub ps-print package
+(provide 'ps-print)
+
+;; Now load the actual production module
+(require 'custom-buffer-file)
+
+;;; Setup and Teardown
+
+(defun test-copy-to-bottom-setup ()
+ "Set up test environment."
+ (setq kill-ring nil))
+
+(defun test-copy-to-bottom-teardown ()
+ "Clean up test environment."
+ (setq kill-ring nil))
+
+;;; Normal Cases
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-normal-point-in-middle-copies-to-end ()
+ "Should copy from point to end when point in middle."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (goto-char (point-min))
+ (forward-line 1) ; Point at start of "Line 2"
+ (let ((original-content (buffer-string)))
+ (cj/copy-to-bottom-of-buffer)
+ ;; Buffer should be unchanged
+ (should (equal (buffer-string) original-content))
+ ;; Kill ring should contain from point to end
+ (should (equal (car kill-ring) "Line 2\nLine 3"))))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-normal-single-line-copies-partial ()
+ "Should copy partial line content from middle of line."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello World")
+ (goto-char (point-min))
+ (forward-char 6) ; Point after "Hello "
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "Hello World"))
+ (should (equal (car kill-ring) "World")))
+ (test-copy-to-bottom-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-point-at-beginning-copies-all ()
+ "Should copy entire buffer when point at beginning."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (goto-char (point-min))
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
+ (should (equal (car kill-ring) "Line 1\nLine 2\nLine 3")))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-point-at-end-copies-empty ()
+ "Should copy empty string when point at end."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (goto-char (point-max))
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
+ (should (equal (car kill-ring) "")))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-empty-buffer-copies-empty ()
+ "Should copy empty string in empty buffer."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) ""))
+ (should (equal (car kill-ring) "")))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-point-second-to-last-char-copies-one ()
+ "Should copy last character when point at second-to-last."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello")
+ (goto-char (1- (point-max))) ; Before 'o'
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "Hello"))
+ (should (equal (car kill-ring) "o")))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-unicode-content-copies-correctly ()
+ "Should handle unicode content correctly."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello 👋\nمرحبا\nWorld")
+ (goto-char (point-min))
+ (forward-line 1)
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "Hello 👋\nمرحبا\nWorld"))
+ (should (equal (car kill-ring) "مرحبا\nWorld")))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-narrowed-buffer-respects-narrowing ()
+ "Should respect narrowing and only copy within narrowed region."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3\nLine 4")
+ (goto-char (point-min))
+ (forward-line 1)
+ (let ((start (point)))
+ (forward-line 2)
+ (narrow-to-region start (point))
+ (goto-char (point-min))
+ (forward-line 1) ; Point at "Line 3"
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "Line 2\nLine 3\n"))
+ (should (equal (car kill-ring) "Line 3\n"))))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-whitespace-only-copies-whitespace ()
+ "Should copy whitespace-only content."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert " \n\t\t\n ")
+ (goto-char (point-min))
+ (forward-char 4) ; After first newline
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) " \n\t\t\n "))
+ (should (equal (car kill-ring) "\t\t\n ")))
+ (test-copy-to-bottom-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-boundary-single-character-copies-char ()
+ "Should copy single character buffer."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "x")
+ (goto-char (point-min))
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (buffer-string) "x"))
+ (should (equal (car kill-ring) "x")))
+ (test-copy-to-bottom-teardown)))
+
+;;; Error Cases
+
+(ert-deftest test-custom-buffer-file-copy-to-bottom-of-buffer-error-read-only-buffer-succeeds ()
+ "Should work in read-only buffer since it doesn't modify content."
+ (test-copy-to-bottom-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Read-only content")
+ (read-only-mode 1)
+ (goto-char (point-min))
+ (cj/copy-to-bottom-of-buffer)
+ (should (equal (car kill-ring) "Read-only content")))
+ (test-copy-to-bottom-teardown)))
+
+(provide 'test-custom-buffer-file-copy-to-bottom-of-buffer)
+;;; test-custom-buffer-file-copy-to-bottom-of-buffer.el ends here
diff --git a/tests/test-custom-buffer-file-copy-to-top-of-buffer.el b/tests/test-custom-buffer-file-copy-to-top-of-buffer.el
new file mode 100644
index 00000000..0f09f26d
--- /dev/null
+++ b/tests/test-custom-buffer-file-copy-to-top-of-buffer.el
@@ -0,0 +1,186 @@
+;;; test-custom-buffer-file-copy-to-top-of-buffer.el --- Tests for cj/copy-to-top-of-buffer -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; Tests for the cj/copy-to-top-of-buffer function from custom-buffer-file.el
+;;
+;; This function copies all text from the beginning of the buffer to point
+;; to the kill ring without modifying the buffer.
+
+;;; Code:
+
+(require 'ert)
+(require 'testutil-general)
+
+;; Add modules directory to load path
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+
+;; Stub dependencies before loading the module
+(defvar cj/custom-keymap (make-sparse-keymap)
+ "Stub keymap for testing.")
+
+;; Stub ps-print package
+(provide 'ps-print)
+
+;; Now load the actual production module
+(require 'custom-buffer-file)
+
+;;; Setup and Teardown
+
+(defun test-copy-to-top-setup ()
+ "Set up test environment."
+ (setq kill-ring nil))
+
+(defun test-copy-to-top-teardown ()
+ "Clean up test environment."
+ (setq kill-ring nil))
+
+;;; Normal Cases
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-normal-point-in-middle-copies-from-beginning ()
+ "Should copy from beginning to point when point in middle."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (goto-char (point-min))
+ (forward-line 2) ; Point at start of "Line 3"
+ (let ((original-content (buffer-string)))
+ (cj/copy-to-top-of-buffer)
+ ;; Buffer should be unchanged
+ (should (equal (buffer-string) original-content))
+ ;; Kill ring should contain from beginning to point
+ (should (equal (car kill-ring) "Line 1\nLine 2\n"))))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-normal-single-line-copies-partial ()
+ "Should copy partial line content from beginning to middle of line."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello World")
+ (goto-char (point-min))
+ (forward-char 5) ; Point after "Hello"
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "Hello World"))
+ (should (equal (car kill-ring) "Hello")))
+ (test-copy-to-top-teardown)))
+
+;;; Boundary Cases
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-point-at-end-copies-all ()
+ "Should copy entire buffer when point at end."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (goto-char (point-max))
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
+ (should (equal (car kill-ring) "Line 1\nLine 2\nLine 3")))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-point-at-beginning-copies-empty ()
+ "Should copy empty string when point at beginning."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3")
+ (goto-char (point-min))
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "Line 1\nLine 2\nLine 3"))
+ (should (equal (car kill-ring) "")))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-empty-buffer-copies-empty ()
+ "Should copy empty string in empty buffer."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) ""))
+ (should (equal (car kill-ring) "")))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-point-at-second-char-copies-one ()
+ "Should copy first character when point at second character."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello")
+ (goto-char (1+ (point-min))) ; After 'H'
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "Hello"))
+ (should (equal (car kill-ring) "H")))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-unicode-content-copies-correctly ()
+ "Should handle unicode content correctly."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Hello 👋\nمرحبا\nWorld")
+ (goto-char (point-min))
+ (forward-line 2) ; Point at start of "World"
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "Hello 👋\nمرحبا\nWorld"))
+ (should (equal (car kill-ring) "Hello 👋\nمرحبا\n")))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-narrowed-buffer-respects-narrowing ()
+ "Should respect narrowing and only copy within narrowed region."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Line 1\nLine 2\nLine 3\nLine 4")
+ (goto-char (point-min))
+ (forward-line 1)
+ (let ((start (point)))
+ (forward-line 2)
+ (narrow-to-region start (point))
+ (goto-char (point-max)) ; Point at end of narrowed region
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "Line 2\nLine 3\n"))
+ (should (equal (car kill-ring) "Line 2\nLine 3\n"))))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-whitespace-only-copies-whitespace ()
+ "Should copy whitespace-only content."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert " \n\t\t\n ")
+ (goto-char (point-min))
+ (forward-char 7) ; After second newline
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) " \n\t\t\n "))
+ (should (equal (car kill-ring) " \n\t\t\n")))
+ (test-copy-to-top-teardown)))
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-boundary-single-character-copies-char ()
+ "Should copy single character buffer."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "x")
+ (goto-char (point-max))
+ (cj/copy-to-top-of-buffer)
+ (should (equal (buffer-string) "x"))
+ (should (equal (car kill-ring) "x")))
+ (test-copy-to-top-teardown)))
+
+;;; Error Cases
+
+(ert-deftest test-custom-buffer-file-copy-to-top-of-buffer-error-read-only-buffer-succeeds ()
+ "Should work in read-only buffer since it doesn't modify content."
+ (test-copy-to-top-setup)
+ (unwind-protect
+ (with-temp-buffer
+ (insert "Read-only content")
+ (goto-char (point-max))
+ (read-only-mode 1)
+ (cj/copy-to-top-of-buffer)
+ (should (equal (car kill-ring) "Read-only content")))
+ (test-copy-to-top-teardown)))
+
+(provide 'test-custom-buffer-file-copy-to-top-of-buffer)
+;;; test-custom-buffer-file-copy-to-top-of-buffer.el ends here