summaryrefslogtreecommitdiff
path: root/modules/custom-line-paragraph.el
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-10-12 14:41:58 -0500
committerCraig Jennings <c@cjennings.net>2025-10-12 14:42:29 -0500
commit147d5c265edf0615d2aa1d65319963d7399711de (patch)
tree84d272d0840c50940e06365eed5ce4935ec47748 /modules/custom-line-paragraph.el
parent5db41b5a2dc7107f2d3bd2c226e1dbc3a91719e8 (diff)
maint: remaining custom functions grouped into separate modules
Diffstat (limited to 'modules/custom-line-paragraph.el')
-rw-r--r--modules/custom-line-paragraph.el127
1 files changed, 127 insertions, 0 deletions
diff --git a/modules/custom-line-paragraph.el b/modules/custom-line-paragraph.el
new file mode 100644
index 00000000..334aa8d2
--- /dev/null
+++ b/modules/custom-line-paragraph.el
@@ -0,0 +1,127 @@
+;;; custom-line-paragraph.el --- -*- coding: utf-8; lexical-binding: t; -*-
+
+;;; Commentary:
+;;
+
+;;; Code:
+
+(use-package expand-region
+ :demand t) ;; used w/in join paragraph
+
+(defun cj/join-line-or-region ()
+ "Join lines in the active region or join the current line with the previous one."
+ (interactive)
+ (if (use-region-p)
+ (let ((beg (region-beginning))
+ (end (copy-marker (region-end))))
+ (goto-char beg)
+ (while (< (point) end)
+ (join-line 1))
+ (goto-char end)
+ (newline))
+ ;; No region - only join if there's a previous line
+ (when (> (line-number-at-pos) 1)
+ (join-line))
+ (newline)))
+
+(defun cj/join-paragraph ()
+ "Join all lines in the current paragraph using `cj/join-line-or-region'."
+ (interactive)
+ (er/mark-paragraph) ;; from package expand region
+ (cj/join-line-or-region (region-beginning)(region-end))
+ (forward-line))
+
+(defun cj/duplicate-line-or-region (&optional comment)
+ "Duplicate the current line or active region below.
+
+Comment the duplicated text when optional COMMENT is non-nil."
+ (interactive "P")
+ (let* ((b (if (region-active-p) (region-beginning) (line-beginning-position)))
+ (e (if (region-active-p) (region-end) (line-end-position)))
+ (lines (split-string (buffer-substring-no-properties b e) "\n")))
+ (save-excursion
+ (goto-char e)
+ (dolist (line lines)
+ (open-line 1)
+ (forward-line 1)
+ (insert line)
+ ;; If the COMMENT prefix argument is non-nil, comment the inserted text
+ (when comment
+ (comment-region (line-beginning-position) (line-end-position)))))))
+
+(defun cj/remove-duplicate-lines-region-or-buffer ()
+ "Remove duplicate lines in the region or buffer, keeping the first occurrence.
+
+Operate on the active region when one exists; otherwise operate on the whole buffer."
+ (interactive)
+ (let ((start (if (use-region-p) (region-beginning) (point-min)))
+ (end (if (use-region-p) (region-end) (point-max))))
+ (save-excursion
+ (let ((end-marker (copy-marker end)))
+ (while
+ (progn
+ (goto-char start)
+ (re-search-forward "^\\(.*\\)\n\\(\\(.*\n\\)*\\)\\1\n" end-marker t))
+ (replace-match "\\1\n\\2"))))))
+
+
+(defun cj/remove-lines-containing (text)
+ "Remove all lines containing TEXT.
+
+If region is active, operate only on the region, otherwise on entire buffer.
+The operation is undoable."
+ (interactive "sRemove lines containing: ")
+ (save-excursion
+ (save-restriction
+ (let ((region-active (use-region-p))
+ (count 0))
+ (when region-active
+ (narrow-to-region (region-beginning) (region-end)))
+ (goto-char (point-min))
+ ;; Count lines before deletion
+ (while (re-search-forward (regexp-quote text) nil t)
+ (setq count (1+ count))
+ (beginning-of-line)
+ (forward-line))
+ ;; Go back and delete
+ (goto-char (point-min))
+ (delete-matching-lines (regexp-quote text))
+ ;; Report what was done
+ (message "Removed %d line%s containing '%s' from %s"
+ count
+ (if (= count 1) "" "s")
+ text
+ (if region-active "region" "buffer"))))))
+
+(defun cj/underscore-line ()
+ "Underline the current line by inserting a row of characters below it.
+
+If the line is empty or contains only whitespace, abort with a message."
+ (interactive)
+ (let ((line (buffer-substring-no-properties
+ (line-beginning-position)
+ (line-end-position))))
+ (if (string-match-p "^[[:space:]]*$" line)
+ (message "Line empty or only whitespace. Aborting.")
+ (let* ((char (read-char "Enter character for underlining: "))
+ (len (save-excursion
+ (goto-char (line-end-position))
+ (current-column))))
+ (save-excursion
+ (end-of-line)
+ (insert "\n" (make-string len char)))))))
+
+
+;; Line & paragraph operations prefix and keymap
+(define-prefix-command 'cj/line-and-paragraph-map nil
+ "Keymap for line and paragraph manipulation.")
+(define-key cj/custom-keymap "l" 'cj/line-and-paragraph-map)
+(define-key cj/line-and-paragraph-map "j" 'cj/join-line-or-region)
+(define-key cj/line-and-paragraph-map "J" 'cj/join-paragraph)
+(define-key cj/line-and-paragraph-map "d" 'cj/duplicate-line-or-region)
+(define-key cj/line-and-paragraph-map "R" 'cj/remove-duplicate-lines-region-or-buffer)
+(define-key cj/line-and-paragraph-map "r" 'cj/remove-lines-containing)
+(define-key cj/line-and-paragraph-map "u" 'cj/underscore-line)
+
+(provide 'custom-line-paragraph)
+;;; custom-line-paragraph.el ends here.