]> code.delx.au - gnu-emacs-elpa/blob - packages/nhexl-mode/nhexl-mode.el
447b185fa83fecc0d87ed0c7ccd6a08aaed6b74f
[gnu-emacs-elpa] / packages / nhexl-mode / nhexl-mode.el
1 ;;; nhexl-mode.el --- Minor mode to edit files via hex-dump format -*- lexical-binding: t -*-
2
3 ;; Copyright (C) 2010, 2012 Free Software Foundation, Inc.
4
5 ;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
6 ;; Keywords: data
7 ;; Version: 0.1
8
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
13
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22 ;;; Commentary:
23
24 ;; This implements a similar functionality to `hexl-mode', but
25 ;; using a different implementation technique, which makes it usable
26 ;; as a "plain" minor mode. I.e. it works on any buffer, does not
27 ;; mess with the undo boundary or even with the major mode.
28 ;;
29 ;; In theory it could also work just fine even on very large buffers,
30 ;; although in practice it seems to make the display engine suffer.
31
32 ;; Todo:
33 ;; - Clicks on the hex side should put point at the right place.
34
35 ;;; Code:
36
37 (eval-when-compile (require 'cl))
38 (require 'hexl) ;For faces.
39
40 (defgroup nhexl nil
41 "Edit a file in a hex dump format."
42 :group 'data)
43
44 (defvar nhexl-line-width 16
45 "Number of bytes per line.")
46
47 (defvar nhexl--display-table
48 (let ((dt (make-display-table)))
49 ;; (aset dt ?\n [?␊])
50 (aset dt ?\t [?␉])
51 dt))
52
53 (defvar nhexl--saved-vars nil)
54 (make-variable-buffer-local 'nhexl--saved-vars)
55 (defvar nhexl--point nil)
56 (make-variable-buffer-local 'nhexl--point)
57
58 ;;;###autoload
59 (define-minor-mode nhexl-mode
60 "Minor mode to edit files via hex-dump format"
61 :lighter " NHexl"
62 (if (not nhexl-mode)
63 (progn
64 (dolist (varl nhexl--saved-vars)
65 (set (make-local-variable (car varl)) (cdr varl)))
66 (kill-local-variable 'nhexl--saved-vars)
67 (jit-lock-unregister #'nhexl--jit)
68 (remove-hook 'after-change-functions #'nhexl--change-function 'local)
69 (remove-hook 'post-command-hook #'nhexl--post-command 'local)
70 ;; FIXME: This will conflict with any other use of `display'.
71 (with-silent-modifications
72 (put-text-property (point-min) (point-max) 'display nil))
73 (remove-overlays (point-min) (point-max) 'nhexl t))
74 (unless (local-variable-p 'nhexl--saved-vars)
75 (dolist (var '(buffer-display-table buffer-invisibility-spec
76 overwrite-mode header-line-format))
77 (push (cons var (symbol-value var)) nhexl--saved-vars)))
78 (setq nhexl--point (point))
79 (setq header-line-format '(:eval (nhexl--header-line)))
80 (binary-overwrite-mode 1)
81 (setq buffer-invisibility-spec ())
82 (set (make-local-variable 'buffer-display-table) nhexl--display-table)
83 (jit-lock-register #'nhexl--jit)
84 (add-hook 'change-major-mode-hook (lambda () (nhexl-mode -1)) nil 'local)
85 (add-hook 'post-command-hook #'nhexl--post-command nil 'local)
86 (add-hook 'after-change-functions #'nhexl--change-function nil 'local)))
87
88 (defun nhexl--change-function (beg end len)
89 ;; Jit-lock already takes care of refreshing the changed area, so we
90 ;; only have to make sure the tail's addresses are refreshed when
91 ;; text is inserted/removed.
92 (when (/= len (- end beg))
93 (put-text-property beg (point-max) 'fontified nil)))
94
95 (defvar nhexl--overlay-counter 100)
96 (make-variable-buffer-local 'nhexl--overlay-counter)
97
98 (defun nhexl--debug-count-ols ()
99 (let ((i 0))
100 (dolist (ol (overlays-in (point-min) (point-max)))
101 (when (overlay-get ol 'nhexl) (incf i)))
102 i))
103
104 (defun nhexl--flush-overlays (buffer)
105 (with-current-buffer buffer
106 (kill-local-variable 'nhexl--overlay-counter)
107 ;; We've created many overlays in this buffer, which can slow
108 ;; down operations significantly. Let's flush them.
109 ;; An easy way to flush them is
110 ;; (remove-overlays min max 'nhexl t)
111 ;; (put-text-property min max 'fontified nil)
112 ;; but if the visible part of the buffer requires more than
113 ;; nhexl--overlay-counter overlays, then we'll inf-loop.
114 ;; So let's be more careful about removing overlays.
115 (let ((windows (get-buffer-window-list nil nil t))
116 (start (point-min))
117 (zero (save-restriction (widen) (point-min)))
118 (debug-count (nhexl--debug-count-ols)))
119 (with-silent-modifications
120 (while (< start (point-max))
121 (let ((end (point-max)))
122 (dolist (window windows)
123 (cond
124 ((< start (1- (window-start window)))
125 (setq end (min (1- (window-start window)) end)))
126 ((< start (1+ (window-end window)))
127 (setq start (1+ (window-end window))))))
128 ;; Round to multiple of nhexl-line-width.
129 (setq start (+ zero (* (ceiling (- start zero) nhexl-line-width)
130 nhexl-line-width)))
131 (setq end (+ zero (* (truncate (- end zero) nhexl-line-width)
132 nhexl-line-width)))
133 (when (< start end)
134 (remove-overlays start end 'nhexl t)
135 (put-text-property start end 'fontified nil)
136 (setq start (+ end nhexl-line-width))))))
137 (let ((debug-new-count (nhexl--debug-count-ols)))
138 (message "Flushed %d overlays, %d remaining"
139 (- debug-count debug-new-count) debug-new-count)))))
140
141 (defun nhexl--make-line (from next zero)
142 (let* ((nextpos (min next (point-max)))
143 (bufstr (buffer-substring from nextpos))
144 (i -1)
145 (s (concat
146 (unless (eq zero from) "\n")
147 (format (propertize "%08x:" 'face
148 (if (or (< nhexl--point from)
149 (>= nhexl--point next))
150 'hexl-address-region
151 '(highlight hexl-address-region)))
152 (- from zero))
153 (propertize " " 'display '(space :align-to 12))
154 (mapconcat (lambda (c)
155 (setq i (1+ i))
156 ;; FIXME: In multibyte buffers,
157 ;; do something clever about
158 ;; non-ascii chars.
159 (let ((s (format "%02x" c)))
160 (when (eq nhexl--point (+ from i))
161 (put-text-property 0 (length s)
162 'face 'highlight
163 s))
164 (if (zerop (mod i 2))
165 s (concat s " "))))
166 bufstr
167 "")
168 (if (> next nextpos)
169 (make-string (+ (/ (1+ (- next nextpos)) 2)
170 (* (- next nextpos) 2))
171 ?\s))
172 (propertize " " 'display
173 `(space :align-to
174 ,(+ (/ (* nhexl-line-width 5) 2)
175 12 3))))))
176 (font-lock-append-text-property 0 (length s) 'face 'default s)
177 s))
178
179 (defun nhexl--jit (from to)
180 (let ((zero (save-restriction (widen) (point-min))))
181 (setq from (+ zero (* (truncate (- from zero) nhexl-line-width)
182 nhexl-line-width)))
183 (setq to (+ zero (* (ceiling (- to zero) nhexl-line-width)
184 nhexl-line-width)))
185 (remove-overlays from (min to (point-max)) 'nhexl t)
186 (save-excursion
187 (goto-char from)
188 (while (search-forward "\n" to t)
189 (put-text-property (match-beginning 0) (match-end 0)
190 'display (copy-sequence "␊"))))
191 (while (< from to)
192
193 (decf nhexl--overlay-counter)
194 (when (and (= nhexl--overlay-counter 0)
195 ;; If the user enabled jit-lock-stealth fontification, then
196 ;; removing overlays is just a waste since
197 ;; jit-lock-stealth will restore them anyway.
198 (not jit-lock-stealth-time))
199 ;; (run-with-idle-timer 0 nil 'nhexl--flush-overlays (current-buffer))
200 )
201
202 (let* ((next (+ from nhexl-line-width))
203 (ol (make-overlay from next))
204 (s (nhexl--make-line from next zero)))
205 (overlay-put ol 'nhexl t)
206 (overlay-put ol 'face 'hexl-ascii-region)
207 (overlay-put ol 'before-string s)
208 (setq from next)))))
209
210 (defun nhexl--header-line ()
211 ;; FIXME: merge with nhexl--make-line.
212 (let* ((zero (save-restriction (widen) (point-min)))
213 (text
214 (let ((tmp ()))
215 (dotimes (i nhexl-line-width)
216 (push (if (< i 10) (+ i ?0) (+ i -10 ?a)) tmp))
217 (apply 'string (nreverse tmp))))
218 (pos (mod (- nhexl--point zero) nhexl-line-width))
219 (i -1))
220 (put-text-property pos (1+ pos) 'face 'highlight text)
221 (concat
222 (propertize " " 'display '(space :align-to 0))
223 "Address:"
224 (propertize " " 'display '(space :align-to 12))
225 (mapconcat (lambda (c)
226 (setq i (1+ i))
227 (let ((s (string c c)))
228 (when (eq i pos)
229 (put-text-property 0 (length s)
230 'face 'highlight
231 s))
232 (if (zerop (mod i 2)) s
233 (concat
234 s (propertize " " 'display
235 `(space :align-to
236 ,(+ (/ (* i 5) 2) 12 3)))))))
237 text
238 "")
239 (propertize " " 'display
240 `(space :align-to
241 ,(+ (/ (* nhexl-line-width 5) 2)
242 12 3)))
243 text)))
244
245
246 (defun nhexl--post-command ()
247 (when (/= (point) nhexl--point)
248 (let ((zero (save-restriction (widen) (point-min)))
249 (oldpoint nhexl--point))
250 (setq nhexl--point (point))
251 (with-silent-modifications
252 (nhexl--jit (point) (1+ (point)))
253 (if (/= (truncate (- (point) zero) nhexl-line-width)
254 (truncate (- oldpoint zero) nhexl-line-width))
255 (nhexl--jit oldpoint (1+ oldpoint)))))))
256
257
258 (provide 'nhexl-mode)
259 ;;; nhexl-mode.el ends here