-
Notifications
You must be signed in to change notification settings - Fork 4
/
voicemacs-extend-dired.el
225 lines (176 loc) · 7.85 KB
/
voicemacs-extend-dired.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
(require 'dired)
(require 'cl)
(require 'voicemacs-lib)
(require 'voicemacs-base)
(defvar-local voicemacs--dired-number-overlays '()
"A list of currently active Dired number overlays")
(defface voicemacs-dired-numbers-face
;; '((t :inherit 'font-lock-keyword-face))
;; '((t :inherit 'font-lock-variable-name-face))
'((t :inherit 'font-lock-type-face))
"Face used for Dired selection numbers.")
(defun voicemacs--dired-remove-overlays ()
"Remove all overlays in the current buffer."
(mapc 'delete-overlay voicemacs--dired-number-overlays)
(setq voicemacs--dired-number-overlays '()))
(defun voicemacs--dired-insert-number-overlay (number max-width)
"Insert a single number overlay at the current position."
(let ((overlay (make-overlay (point) (point))))
;; Add this property so we can find the candidate by number later.
(overlay-put overlay 'number-value number)
(overlay-put
overlay
'after-string
(propertize (concat (voicemacs--pad-string (number-to-string number)
max-width)
" ")
'face
'(voicemacs-dired-numbers-face default)))
(push overlay voicemacs--dired-number-overlays)))
(defun voicemacs--dired-establish-width ()
"Determine the maximum width needed for numbers."
(save-excursion
(goto-char 0)
(let ((num-files 0))
(while (zerop (forward-line))
;; We only want to label files & directories - not other lines.
(when (dired-move-to-filename)
(cl-incf num-files)))
(length (number-to-string num-files)))))
(defun voicemacs--dired-insert-numbers (&rest _)
"Number all candidates in the current buffer.
Resets numbers if they already exist. Will only work in Dired
buffers."
(voicemacs--dired-remove-overlays)
(let ((max-number-width (voicemacs--dired-establish-width)) ;; TODO: Find this dynamically?
(current-number 1))
(save-excursion
(goto-char 0)
;; Forward-line returns 1 if it fails, 0 if it succeeds.
(while (zerop (forward-line))
;; We only want to label files & directories - not the lines before.
;;
;; `dired-move-to-filename' will be truthy iff there's a file on the
;; line. (This also moves the point to the right position to insert
;; the number.)
(when (dired-move-to-filename)
(voicemacs--dired-insert-number-overlay
current-number max-number-width)
(cl-incf current-number))))))
;; TODO: Could make this more functional
(defun voicemacs--move-to-dired-item (number)
"Move to a numbered dired item.
`NUMBER' - the number of the item."
(cl-assert (and (integerp number) (>= number 1)) t)
(unless voicemacs--dired-number-overlays
(error "No numbered dired candidates available."))
(catch 'item-found
(mapc (lambda (overlay)
(when (eq (overlay-get overlay 'number-value) number)
(goto-char (overlay-start overlay))
(dired-move-to-filename)
(throw 'item-found t)))
voicemacs--dired-number-overlays)
(error (format "Dired item with number `%s' was not found." number))))
(defun voicemacs-dired-handle-return (&optional prefix)
(interactive "P")
;; Causes weird hangs if we try to find the file in the same action, so it's disabled for now.
(if prefix
(voicemacs--move-to-dired-item prefix)
(call-interactively #'dired-find-file)))
(defun voicemacs-dired-handle-tab (&optional prefix)
(if prefix
(voicemacs--move-to-dired-item prefix)
(call-interactively #'indent-for-tab-command)))
(defun voicemacs-dired-move-to-item (&optional item-number)
"Move cursor to a numbered item in the dired buffer."
(interactive "P")
(cl-assert (eq major-mode 'dired-mode))
(voicemacs--move-to-dired-item item-number)
;; Ensure the move is shown to the user immediately.
(redisplay t)
;; Dired does not respond to subsequent commands if they come too quickly, so
;; we sleep and redisplay.
;;
;; TODO: This might be a Windows-only issue.
(sleep-for 0.1)
(redisplay t))
(voicemacs-expose-function #'voicemacs-dired-move-to-item)
(defmacro voicemacs--dired-mapc (&rest body)
"Run `BODY' within every dired buffer in turn."
`(mapc (lambda (buffer)
(with-current-buffer buffer
(when (eq major-mode 'dired-mode)
,@body)))
(buffer-list)))
(defun voicemacs--dired-renumber-buffer (buffer)
"Insert numbers into a specific `BUFFER'."
(when (bufferp buffer)
(with-current-buffer buffer
(voicemacs--dired-insert-numbers))))
(defun voicemacs--dired-queue-renumber ()
"Queue a renumbering of the current buffer.
Defer to avoid duplicating work, and for compatibility with
certain dired rendering schemes (e.g. Doom Emacs hides \".\" &
\"..\" after candidates are inserted, but that won't remove their
number overlays)."
(voicemacs--queue-once #'voicemacs--dired-renumber-buffer
:args (list (current-buffer))))
(defun voicemacs--dired-numbers-mode-setup ()
(add-hook 'dired-after-readin-hook #'voicemacs--dired-queue-renumber)
(voicemacs--dired-mapc
(voicemacs--dired-insert-numbers))
;; Make it easier to jump to the numbers with keyboard.
;; FIXME: This rebind will be permanent. Make it enable/disable predictably.
(define-key dired-mode-map (kbd "RET") #'voicemacs-dired-handle-return)
(define-key dired-mode-map (kbd "TAB") #'voicemacs-dired-handle-tab))
(defun voicemacs--dired-numbers-mode-teardown ()
(remove-hook 'dired-after-readin-hook #'voicemacs--dired-queue-renumber)
(voicemacs--dired-mapc
(voicemacs--dired-remove-overlays)))
(define-minor-mode voicemacs-dired-numbers-mode
"When active, `dired' candidates will be numbered."
:group 'voicemacs
:global t
:lighter nil
:after-hook (if voicemacs-dired-numbers-mode
(voicemacs--dired-numbers-mode-setup)
(voicemacs--dired-numbers-mode-teardown)))
(defvar voicemacs--dired-digits-so-far ""
"The digits that have been pressed so far in the current numeric key sequence.
This is only used with `TODO: when key accumulation active'.")
(defconst voicemacs--dired-process-digit-commands '()
"All the digit processing command symbols.")
;; Create commands to process each digit
(dotimes (n 10) ; 0-9
(eval
`(let* ((n-str (format "%s" ,n))
(command-symbol (intern (s-concat "voicemacs-dired-process-digit-" n-str))))
(eval
`(progn
(defun ,command-symbol ()
,(format "Process the digit %s as a quickmove command in `voicemacs-dired-mode'." n-str)
(interactive)
(unless (member last-command voicemacs--dired-process-digit-commands)
(setq voicemacs--dired-digits-so-far ""))
(setq voicemacs--dired-digits-so-far (s-concat voicemacs--dired-digits-so-far ,n-str))
(condition-case err
(voicemacs-dired-move-to-item (string-to-number voicemacs--dired-digits-so-far))
(error
;; If there's no valid target, we restart the sequence. This is a
;; convenience feature so the user doesn't always need to tap a
;; different key to restart the sequence.
(message "Error moving to target")
(setq voicemacs--dired-digits-so-far "")
(voicemacs-dired-move-to-item ,n)
;; Note we only store the digit as part of the list if it succeeded. Otherwise, all subsequent movements will fail.
(setq voicemacs--dired-digits-so-far ,n-str))))
(add-to-list 'voicemacs--dired-process-digit-commands ',command-symbol)
;; TODO 1: Bind the key properly, probably just in `voicemacs-dired-numbers-mode'
(map! :mode dired-mode
,n-str #',command-symbol)
)
t))
t))
(provide 'voicemacs-extend-dired)
;;; voicemacs-extend-dired.el ends here