summaryrefslogtreecommitdiff
path: root/gnosis-string-edit.el
blob: 836fdddc4484a94803a4c7e51e0d27c17ad50251 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
;;; gnosis-string-edit.el --- gnosis edit strings  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Thanos Apollo

;; Author: Thanos Apollo <[email protected]>
;; Keywords: extensions
;; URL: https://git.thanosapollo.org/gnosis
;; Version: 0.0.1

;; Package-Requires: ((emacs "27.2") (compat "29.1.4.2"))

;; 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:

;; A slightly modified version of string-edit.el.  Gnosis comes with a
;; modified version string-edit.el to make gnosis available to users
;; of Emacs versions < 29.

;;; Code:

(require 'cl-lib)

(defface gnosis-string-edit-prompt
  '((t (:inherit font-lock-comment-face)))
  "Face used on `gnosis-string-edit' help text."
  :group 'text)

(defvar gnosis-string-edit--success-callback)
(defvar gnosis-string-edit--abort-callback)

(defun gnosis-string-edit-ensure-empty-lines (&optional lines)
  "Ensure that there are LINES number of empty lines before point.
If LINES is nil or omitted, ensure that there is a single empty
line before point.

If called interactively, LINES is given by the prefix argument.

If there are more than LINES empty lines before point, the number
of empty lines is reduced to LINES.

If point is not at the beginning of a line, a newline character
is inserted before adjusting the number of empty lines."
  (interactive "p")
  (unless (bolp)
    (insert "\n"))
  (let ((lines (or lines 1))
        (start (save-excursion
                 (if (re-search-backward "[^\n]" nil t)
                     (+ (point) 2)
                   (point-min)))))
    (cond
     ((> (- (point) start) lines)
      (delete-region (point) (- (point) (- (point) start lines))))
     ((< (- (point) start) lines)
      (insert (make-string (- lines (- (point) start)) ?\n))))))

;;;###autoload
(cl-defun gnosis-string-edit (prompt string success-callback
                              &key abort-callback)
  "Switch to a new buffer to edit STRING.
When the user finishes editing (with \\<gnosis-string-edit-mode-map>\\[gnosis-string-edit-done]), SUCCESS-CALLBACK
is called with the resulting string.

If the user aborts (with \\<gnosis-string-edit-mode-map>\\[gnosis-string-edit-abort]), ABORT-CALLBACK (if any) is
called with no parameters.

PROMPT will be inserted at the start of the buffer, but won't be
included in the resulting string.  If PROMPT is nil, no help text
will be inserted.

Also see `read-string-from-buffer'."
  (with-current-buffer (generate-new-buffer "*edit string*")
    (when prompt
      (let ((inhibit-read-only t))
        (insert prompt)
        (gnosis-string-edit-ensure-empty-lines 0)
        (add-text-properties (point-min) (point)
                             (list 'intangible t
                                   'face 'gnosis-string-edit-prompt
                                   'read-only t))
        (insert (propertize (make-separator-line) 'rear-nonsticky t))
        (add-text-properties (point-min) (point)
                             (list 'gnosis-string-edit--prompt t))))
    (let ((start (point)))
      (insert string)
      (goto-char start))

    ;; Use `fit-window-to-buffer' after the buffer is filled with text.
    (pop-to-buffer (current-buffer)
                   '(display-buffer-below-selected
                     (window-height . (lambda (window)
                                        (fit-window-to-buffer window nil 10)))))

    (set-buffer-modified-p nil)
    (setq buffer-undo-list nil)
    (gnosis-string-edit-mode)
    (setq-local gnosis-string-edit--success-callback success-callback)
    (when abort-callback
      (setq-local gnosis-string-edit--abort-callback abort-callback))
    (setq-local header-line-format
                (substitute-command-keys
                 "Type \\<gnosis-string-edit-mode-map>\\[gnosis-string-edit-done] when you've finished editing or \\[gnosis-string-edit-abort] to abort"))
    (message "%s" (substitute-command-keys
                   "Type \\<gnosis-string-edit-mode-map>\\[gnosis-string-edit-done] when you've finished editing"))))

(defvar-keymap gnosis-string-edit-mode-map
  "C-c C-c" #'gnosis-string-edit-done
  "C-c C-k" #'gnosis-string-edit-abort)

(define-derived-mode gnosis-string-edit-mode text-mode "String"
  "Mode for editing strings."
  :interactive nil)

(defun gnosis-string-edit-done ()
  "Finish editing the string and call the callback function.
This will kill the current buffer."
  (interactive)
  (goto-char (point-min))
  ;; Skip past the help text.
  (text-property-search-forward 'gnosis-string-edit--prompt)
  (let ((string (buffer-substring (point) (point-max)))
        (callback gnosis-string-edit--success-callback))
    (quit-window 'kill)
    (funcall callback string)))

(defun gnosis-string-edit-abort ()
  "Abort editing the current string."
  (interactive)
  (let ((callback gnosis-string-edit--abort-callback))
    (quit-window 'kill)
    (when callback
      (funcall callback))))

(provide 'gnosis-string-edit)

;;; gnosis-string-edit.el ends here