-
Notifications
You must be signed in to change notification settings - Fork 11
/
exwm-edit.el
231 lines (202 loc) · 9.17 KB
/
exwm-edit.el
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
;;; exwm-edit.el --- Edit mode for EXWM -*- lexical-binding: t; -*-
;; Author: Ag Ibragimov
;; URL: https://github.com/agzam/exwm-edit
;; Created: 2018-05-16
;; Keywords: convenience
;; License: GPL v3
;; Package-Requires: ((emacs "27.1"))
;; Version: 0.0.4-pre
;;; Commentary:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Similar to atomic-chrome https://github.com/alpha22jp/atomic-chrome
;; except this package is made to work with EXWM https://github.com/ch11ng/exwm
;; and it works with any editable element of any app
;;
;; The idea is very simple - when you press the keybinding,
;; it simulates [C-a (select all) + C-c (copy)],
;; then opens a buffer and yanks (pastes) the content so you can edit it,
;; after you done - it grabs (now edited text) and pastes back to where it's started
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 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 <http://www.gnu.org/licenses/>.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Code:
(require 'exwm)
(defvar exwm-edit--last-window-configuration nil
"Window configuration before popping to \"exwm-edit\" buffer.")
(defvar exwm-edit-last-kill nil
"Used to check if the text box is empty.
If this is the same value as (car KILL-RING) returns after copying the text-box,
the text box might be empty (because empty text boxes don't add to the KILL-RING).")
(defvar exwm-edit-yank-delay 0.3
"The delay to use when yanking into the Emacs buffer.
It takes a while for copy in exwm to transfer to Emacs yank.
If this is too low an old yank may be used instead.")
(defvar exwm-edit-paste-delay 0.05
"The delay to use when pasting text back into the exwm buffer.
If this is too low the text might not be pasted into the exwm buffer")
(defvar exwm-edit-clean-kill-ring-delay 0.10
"The delay to clean the `kill-ring' after pasting the text back
to the exwm-buffer.")
(defgroup exwm-edit nil
"Edit mode for EXWM"
:group 'applications
:prefix "exwm-edit-")
(defcustom exwm-edit-display-buffer-action '(display-buffer-pop-up-window)
"Display buffer action for \"*exwm-edit*\" buffers.
Passed to `display-buffer', which see."
:type display-buffer--action-custom-type)
(defcustom exwm-edit-copy-over-contents t
"If non-nil, copy over the contents of the exwm text box.
This is then inserted into the `exwm-edit' buffer."
:type 'boolean)
(defcustom exwm-edit-compose-hook nil
"Customizable hook, runs after `exwm-edit--compose' buffer created."
:type 'hook)
(defcustom exwm-edit-compose-minibuffer-hook nil
"Customizable hook, runs after `exwm-edit--compose-minibuffer' buffer created."
:type 'hook)
(defcustom exwm-edit-before-finish-hook nil
"Customizable hook, runs before `exwm-edit--finish'."
:type 'hook)
(defcustom exwm-edit-before-cancel-hook nil
"Customizable hook, runs before `exwm-edit--cancel'."
:type 'hook)
(defun exwm-edit--finish ()
"Called when done editing buffer created by `exwm-edit--compose'."
(interactive)
(run-hooks 'exwm-edit-before-finish-hook)
(let ((text (filter-buffer-substring (point-min) (point-max))))
(kill-buffer)
(exwm-edit--send-to-exwm-buffer text)))
(defun exwm-edit--send-to-exwm-buffer (text)
"Sends TEXT to the exwm window."
(kill-new text)
(set-window-configuration exwm-edit--last-window-configuration)
(setq exwm-edit--last-window-configuration nil)
(exwm-input--set-focus (exwm--buffer->id (window-buffer (selected-window))))
(if (string= text "")
;; If everything is deleted in the exwm-edit buffer, then simply delete the selected text in the exwm buffer
(run-with-timer exwm-edit-paste-delay nil (lambda () (exwm-input--fake-key 'delete)))
(run-with-timer exwm-edit-paste-delay nil (lambda ()
(exwm-input--fake-key ?\C-v)
;; Clean up the kill ring
;; It needs to be run on a timer because of some reason
(run-with-timer exwm-edit-clean-kill-ring-delay nil (lambda ()
(pop kill-ring)
;; Kill-ring weirdness
(if kill-ring
(kill-new (car kill-ring))
(kill-new ""))))))))
(defun exwm-edit--cancel ()
"Called to cancel editing in a buffer created by `exwm-edit--compose'."
(interactive)
(run-hooks 'exwm-edit-before-cancel-hook)
(kill-buffer)
(set-window-configuration exwm-edit--last-window-configuration)
(setq exwm-edit--last-window-configuration nil)
(exwm-input--set-focus (exwm--buffer->id (window-buffer (selected-window))))
(exwm-input--fake-key 'right)
(when kill-ring
(kill-new (car kill-ring))))
(defvar exwm-edit-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c '") 'exwm-edit--finish)
(define-key map (kbd "C-c C-'") 'exwm-edit--finish)
(define-key map (kbd "C-c C-c") 'exwm-edit--finish)
(define-key map [remap save-buffer] 'exwm-edit--finish)
(define-key map (kbd "C-c C-k") 'exwm-edit--cancel)
map)
"Keymap for minor mode `exwm-edit-mode'.")
(define-minor-mode exwm-edit-mode
"Minor mode enabled in `exwm-edit--compose' buffer"
:init-value nil
:interactive nil
:lighter " exwm-edit"
:keymap exwm-edit-mode-map
(if exwm-edit-mode
;; Re-enable the minor mode when changing major mode so the
;; major mode's keybindings don't shadow `exwm-edit-mode-map'.
(add-hook 'after-change-major-mode-hook
#'exwm-edit-mode nil 'local)
(remove-hook 'after-change-major-mode-hook
#'exwm-edit-mode 'local)))
;; Putting permanent-local means that switching major-mode doesn't
;; reset the variable `exwm-edit-mode' to nil.
(put 'exwm-edit-mode 'permanent-local t)
(defun exwm-edit--buffer-title (str)
"`exwm-edit' buffer title based on STR."
(format "*exwm-edit %s *" str))
(defun exwm-edit--yank ()
"Yank text to Emacs buffer with check for empty strings."
(run-with-timer exwm-edit-yank-delay nil
(lambda ()
(let* ((clip-raw (gui-get-selection 'CLIPBOARD 'UTF8_STRING))
(clip (when clip-raw (substring-no-properties clip-raw))))
(when clip
(unless (and exwm-edit-last-kill (string= exwm-edit-last-kill clip))
(insert clip)))))))
;;;###autoload
(defun exwm-edit--compose (&optional no-copy)
"Edit text in an EXWM app.
If NO-COPY is non-nil, don't copy over the contents of the exwm text box"
(interactive)
(let* ((title (exwm-edit--buffer-title (buffer-name)))
(existing (get-buffer title))
(inhibit-read-only t)
(save-interprogram-paste-before-kill t)
(selection-coding-system 'utf-8)) ; required for multilang-support
(when (derived-mode-p 'exwm-mode)
(setq exwm-edit--last-window-configuration (current-window-configuration))
(if existing
(switch-to-buffer-other-window existing)
(exwm-input--fake-key ?\C-a)
(unless (or no-copy (not exwm-edit-copy-over-contents))
(when (gui-get-selection 'CLIPBOARD 'UTF8_STRING)
(setq exwm-edit-last-kill (substring-no-properties (gui-get-selection 'CLIPBOARD 'UTF8_STRING))))
(exwm-input--fake-key ?\C-c))
(with-current-buffer (get-buffer-create title)
(run-hooks 'exwm-edit-compose-hook)
(exwm-edit-mode 1)
(pop-to-buffer (current-buffer) exwm-edit-display-buffer-action)
(setq-local header-line-format
(substitute-command-keys
"Edit, then exit with `\\[exwm-edit--finish]' or cancel with \ `\\[exwm-edit--cancel]'"))
(unless (or no-copy (not exwm-edit-copy-over-contents))
(exwm-edit--yank)))))))
;;;###autoload
(defun exwm-edit--compose-minibuffer (&optional completing-read-entries no-copy)
"Edit text in an EXWM app.
If COMPLETING-READ-ENTRIES is non-nil, feed that list into the collection
parameter of `completing-read'
If NO-COPY is non-nil, don't copy over the contents of the exwm text box"
(interactive)
(let* ((title (exwm-edit--buffer-title (buffer-name)))
(inhibit-read-only t)
(save-interprogram-paste-before-kill t)
(selection-coding-system 'utf-8)) ; required for multilang-support
(when (derived-mode-p 'exwm-mode)
(setq exwm-edit--last-window-configuration (current-window-configuration))
(exwm-input--fake-key ?\C-a)
(unless (or no-copy (not exwm-edit-copy-over-contents))
(when (gui-get-selection 'CLIPBOARD 'UTF8_STRING)
(setq exwm-edit-last-kill (substring-no-properties (gui-get-selection 'CLIPBOARD 'UTF8_STRING))))
(exwm-input--fake-key ?\C-c)
(exwm-edit--yank))
(run-hooks 'exwm-edit-compose-minibuffer-hook)
(exwm-edit--send-to-exwm-buffer
(completing-read "exwm-edit: " completing-read-entries)))))
(provide 'exwm-edit)
;;; exwm-edit.el ends here