diff options
| -rw-r--r-- | modules/custom-buffer-file.el | 55 | ||||
| -rw-r--r-- | tests/test-custom-buffer-file-copy-to-bottom-of-buffer.el | 187 | ||||
| -rw-r--r-- | tests/test-custom-buffer-file-copy-to-top-of-buffer.el | 186 |
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 |
