protesilaos.dotfiles/emacs/.emacs.d/prot-lisp/prot-comment.el
2026-04-21 15:55:09 +03:00

150 lines
5.2 KiB
EmacsLisp

;;; prot-comment.el --- Extensions newcomment.el for my dotemacs -*- lexical-binding: t -*-
;; Copyright (C) 2021-2026 Protesilaos
;; Author: Protesilaos <info@protesilaos.com>
;; URL: https://protesilaos.com/emacs/dotemacs
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This covers my newcomment.el extras, for use in my Emacs setup:
;; https://protesilaos.com/emacs/dotemacs.
;;; Code:
(require 'newcomment)
(require 'prot-common)
(defgroup prot-comment ()
"Extensions for newcomment.el."
:group 'comment)
(defcustom prot-comment-keywords
'("TODO" "NOTE" "XXX" "REVIEW" "FIXME")
"List of strings with keywords used by `prot-comment-timestamp-keyword'."
:type '(repeat string)
:group 'prot-comment)
(defcustom prot-comment-timestamp-format-concise "%F"
"Specifier for date in `prot-comment-timestamp-keyword'.
Refer to the doc string of `format-time-string' for the available
options."
:type 'string
:group 'prot-comment)
(defcustom prot-comment-timestamp-format-verbose "%F %T %z"
"Like `prot-comment-timestamp-format-concise', but longer."
:type 'string
:group 'prot-comment)
;;;###autoload
(defun prot-comment (n)
"Comment N lines, defaulting to the current one.
When the region is active, comment its lines instead."
(interactive "p")
(if (use-region-p)
(comment-or-uncomment-region (region-beginning) (region-end))
(comment-line n)))
(make-obsolete 'prot-comment-comment-dwim 'prot-comment "2023-09-28")
(defvar prot-comment--keyword-hist '()
"Minibuffer history of `prot-comment--keyword-prompt'.")
(defun prot-comment--keyword-prompt (keywords)
"Prompt for candidate among KEYWORDS (per `prot-comment-timestamp-keyword')."
(let ((def (car prot-comment--keyword-hist)))
(completing-read
(format "Select keyword [%s]: " def)
keywords nil nil nil 'prot-comment--keyword-hist def)))
(defun prot-comment--format-date (verbose)
"Format date using `format-time-string'.
VERBOSE has the same meaning as `prot-comment-timestamp-keyword'."
(format-time-string
(if verbose
prot-comment-timestamp-format-verbose
prot-comment-timestamp-format-concise)))
(defun prot-comment--timestamp (keyword &optional verbose)
"Format string using current time and KEYWORD.
VERBOSE has the same meaning as `prot-comment-timestamp-keyword'."
(format "%s %s: " keyword (prot-comment--format-date verbose)))
(defun prot-comment--format-comment (string)
"Format comment STRING per `prot-comment-timestamp-keyword'.
STRING is a combination of a keyword and a time stamp."
(concat comment-start
(make-string comment-add (string-to-char comment-start))
comment-padding
string
comment-end))
(defun prot-comment--maybe-newline ()
"Call `newline' if current line is not empty.
Check `prot-comment-timestamp-keyword' for the rationale."
(unless (prot-common-line-regexp-p 'empty 1)
(save-excursion (newline))))
;;;###autoload
(defun prot-comment-timestamp-keyword (keyword &optional verbose)
"Add timestamped comment with KEYWORD.
When called interactively, the list of possible keywords is that
of `prot-comment-keywords', though it is possible to input
arbitrary text.
If point is at the beginning of the line or if line is empty (no
characters at all or just indentation), the comment is started
there in accordance with `comment-style'. Any existing text
after the point will be pushed to a new line and will not be
turned into a comment.
If point is anywhere else on the line and the line is not empty,
the comment is appended to the line with `comment-indent'.
The comment is always formatted as DELIMITER KEYWORD DATE:, with
the date format being controlled by the variable
`prot-comment-timestamp-format-concise'. DELIMITER is the value
of `comment-start', as defined by the current major mode.
With optional VERBOSE argument (such as a prefix argument), use
an alternative date format, as specified by
`prot-comment-timestamp-format-verbose'."
(interactive
(list
(prot-comment--keyword-prompt prot-comment-keywords)
current-prefix-arg))
(let ((string (prot-comment--timestamp keyword verbose))
(beg (point)))
(cond
((prot-common-line-regexp-p 'empty)
(insert (prot-comment--format-comment string)))
((eq beg (line-beginning-position))
(insert (prot-comment--format-comment string))
(indent-region beg (point))
(prot-comment--maybe-newline))
(t
(comment-indent t)
(insert (concat " " string))))))
(provide 'prot-comment)
;;; prot-comment.el ends here