Subject: Re: hexl mode update

From: Vivek Dasmohapatra <[email-masked]>
Date: Tue, 21 Jul 2009 01:44:58 +0100 (BST)

Severity: wishlist
Tags: patch

[ Resent from
  http://lists.gnu.org/archive/html/emacs-devel/2009-07/msg01028.html ]

On Thu, 16 Jul 2009, Stefan Monnier wrote:

>>> Now would be a good time to submit it,
>> Ok. Is there any particular branch/repo/etc you'd like a patch against?
>
> CVS trunk.  Note that I haven't looked at your code yet.  Please send it
> to [email-masked].

Here it is. The user can select the "word size" hexl will use with defcustom
(it currently screws up if you change this size behind hexl's back, ie 
after hexlifying a buffer but before unhexlifying it: this could be fixed 
by saving the word size used in a a local variable in the hexl buffer,
let me know if you think this is important enough to add). Other than that, 
the hexl code doesn't seem to have changed since I last updated the patch
from emacs 22 to emacs 23.

The word-size could also be chosen at invocation time via a prefix arg, but 
I have not done this yet either: Again, let me know if you think it's
worth adding.

I originally prepared this patch for a friend who was debugging something 
and needed other word sizes: Upon investigation I discovered that the
hexl binary had support for the word sizes 8, 16, 32 and 64 bits but the
hexl mode code only supported one of those - hence this patch.
diff --git a/lisp/hexl.el b/lisp/hexl.el
index 2cdd449..0eebe55 100644
--- a/lisp/hexl.el
+++ b/lisp/hexl.el
@@ -51,6 +51,13 @@
   "Edit a file in a hex dump format using the hexl filter."
   :group 'data)
 
+(defcustom hexl-bits 16
+  "The bit grouping that hexl will use"
+  :type '(choice (const 8 )
+                 (const 16)
+                 (const 32)
+                 (const 64))
+  :group 'hexl)
 
 (defcustom hexl-program "hexl"
   "The program that will hexlify and dehexlify its stdin.
@@ -135,6 +142,15 @@ Quoting cannot be used, so the arguments cannot themselves contain spaces."
 
 (put 'hexl-mode 'mode-class 'special)
 
+;; 10 chars for the "address: "
+;; 32 chars for the hexlified bytes
+;; 1 char for the space 
+;; 16 chars for the character display
+;; X chars for the spaces (128 bits divided by the hexl-bits)
+;; 1 char for the newline.
+(defun hexl-line-displen ()
+  (+ 60 (/ 128 (or hexl-bits 16))))
+
 ;;;###autoload
 (defun hexl-mode (&optional arg)
   "\\<hexl-mode-map>A mode for editing binary files in hex dump format.
@@ -213,27 +229,33 @@ You can use \\[hexl-find-file] to visit a file in Hexl mode.
   (unless (eq major-mode 'hexl-mode)
     (let ((modified (buffer-modified-p))
 	  (inhibit-read-only t)
-	  (original-point (- (point) (point-min))))
+	  (original-point (- (point) (point-min)))
+	  max-address)
       (and (eobp) (not (bobp))
 	   (setq original-point (1- original-point)))
-      ;; If `hexl-mode' is invoked with an argument the buffer is assumed to
-      ;; be in hexl format.
-      (when (memq arg '(1 nil))
+      (if (not (memq arg '(1 nil)))
+	  ;; if no argument then we guess at hexl-max-address
+          (setq max-address 
+                (+ (* (/ (1- (buffer-size)) (hexl-line-displen)) 16) 15))
+        (setq max-address (1- (buffer-size)))
 	;; If the buffer's EOL type is -dos, we need to account for
 	;; extra CR characters added when hexlify-buffer writes the
 	;; buffer to a file.
         ;; FIXME: This doesn't take into account multibyte coding systems.
 	(when (eq (coding-system-eol-type buffer-file-coding-system) 1)
-          (setq original-point (+ (count-lines (point-min) (point))
+          (setq max-address (+ (count-lines (point-min) (point-max))
+			       max-address))
+	  ;; But if there's no newline at the last line, we are off by
+	  ;; one; adjust.
+	  (or (eq (char-before (point-max)) ?\n)
+	      (setq max-address (1- max-address)))
+	  (setq original-point (+ (count-lines (point-min) (point))
 				  original-point))
 	  (or (bolp) (setq original-point (1- original-point))))
         (hexlify-buffer)
         (restore-buffer-modified-p modified))
-      (set (make-local-variable 'hexl-max-address)
-           (let* ((full-lines (/ (buffer-size) 68))
-                  (last-line (% (buffer-size) 68))
-                  (last-line-bytes (% last-line 52)))
-             (+ last-line-bytes (* full-lines 16) -1)))
+      (make-local-variable 'hexl-max-address)
+      (setq hexl-max-address max-address)
       (condition-case nil
 	  (hexl-goto-address original-point)
 	(error nil)))
@@ -436,19 +458,23 @@ Ask the user for confirmation."
 (defun hexl-current-address (&optional validate)
   "Return current hexl-address."
   (interactive)
-  (let ((current-column (- (% (- (point) (point-min) -1) 68) 11))
+  (let ((current-column 
+         (- (% (- (point) (point-min) -1) (hexl-line-displen)) 11))
 	(hexl-address 0))
     (if (< current-column 0)
 	(if validate
 	    (error "Point is not on a character in the file")
 	  (setq current-column 0)))
     (setq hexl-address
-	  (+ (* (/ (- (point) (point-min) -1) 68) 16)
-	     (if (>= current-column 41)
-		 (- current-column 41)
-	       (/ (- current-column  (/ current-column 5)) 2))))
+	  (+ (* (/ (- (point) (point-min) -1) 
+                   (hexl-line-displen)) 16)
+	     (if (>= current-column (- (hexl-ascii-start-column) 10))
+		 (- current-column (- (hexl-ascii-start-column) 10))
+               (/ (- current-column 
+                     (/ current-column (1+ (/ hexl-bits 4)))) 2) )) )
     (when (interactive-p)
-      (message "Current address is %d/0x%08x" hexl-address hexl-address))
+      (message "Current address is [column: %d] %d/0x%08x" 
+               current-column hexl-address hexl-address))
     hexl-address))
 
 (defun hexl-print-current-point-info ()
@@ -457,10 +483,18 @@ This function is intended to be used as eldoc callback."
   (let ((addr (hexl-current-address)))
     (format "Current address is %d/0x%08x" addr addr)))
 
+(defun hexl-ascii-start-column () 
+  "Column at which the ascii display starts."
+  (+ 43 (/ 128 hexl-bits)))
+
 (defun hexl-address-to-marker (address)
   "Return buffer position for ADDRESS."
   (interactive "nAddress: ")
-  (+ (* (/ address 16) 68) 10 (point-min) (/ (* (% address 16) 5) 2)))
+  (let ((N (* (% address 16) 2)))
+    (+ (* (/ address 16) (hexl-line-displen)) ; hexl line no * display length
+       10                      ; 10 chars for the "address: " prefix
+       (point-min)             ; base offset (point usually starts at 1, not 0)
+       (+ N (/ N (/ hexl-bits 4))) )) ) ; char offset into hexl display line
 
 (defun hexl-goto-address (address)
   "Go to hexl-mode (decimal) address ADDRESS.
@@ -505,7 +539,7 @@ Signal error if HEX-ADDRESS is out of range."
   (hexl-goto-address (- (hexl-current-address) arg)))
 
 (defun hexl-forward-char (arg)
-  "Move to right ARG bytes (left if ARG negative) in hexl-mode."
+  "Move right ARG bytes (left if ARG negative) in hexl-mode."
   (interactive "p")
   (hexl-goto-address (+ (hexl-current-address) arg)))
 
@@ -544,7 +578,7 @@ Signal error if HEX-ADDRESS is out of range."
 		       address)))
 
 (defun hexl-forward-short (arg)
-  "Move to right ARG shorts (left if ARG negative) in hexl-mode."
+  "Move right ARG shorts (left if ARG negative) in hexl-mode."
   (interactive "p")
   (hexl-backward-short (- arg)))
 
@@ -583,7 +617,7 @@ Signal error if HEX-ADDRESS is out of range."
 		       address)))
 
 (defun hexl-forward-word (arg)
-  "Move to right ARG words (left if ARG negative) in hexl-mode."
+  "Move right ARG words (left if ARG negative) in hexl-mode."
   (interactive "p")
   (hexl-backward-word (- arg)))
 
@@ -630,7 +664,7 @@ With prefix arg N, puts point N bytes of the way from the true beginning."
 (defun hexl-beginning-of-line ()
   "Goto beginning of line in hexl mode."
   (interactive)
-  (goto-char (+ (* (/ (point) 68) 68) 11)))
+  (goto-char (+ (* (/ (point) (hexl-line-displen)) (hexl-line-displen)) 11)))
 
 (defun hexl-end-of-line ()
   "Goto end of line in hexl mode."
@@ -675,12 +709,12 @@ If there's no byte at the target address, move to the first or last line."
     (recenter 0)))
 
 (defun hexl-beginning-of-1k-page ()
-  "Go to beginning of 1KB boundary."
+  "Go to the beginning of the current 1 KiB block"
   (interactive)
   (hexl-goto-address (logand (hexl-current-address) -1024)))
 
 (defun hexl-end-of-1k-page ()
-  "Go to end of 1KB boundary."
+  "Go to the end of the current 1 KiB block."
   (interactive)
   (hexl-goto-address (let ((address (logior (hexl-current-address) 1023)))
 		       (if (> address hexl-max-address)
@@ -710,6 +744,15 @@ You may also type octal digits, to insert a character with that code."
 
 ;00000000: 0011 2233 4455 6677 8899 aabb ccdd eeff  0123456789ABCDEF
 
+(defun hexl-options (&optional test)
+  (let ((opts (or test hexl-options)))
+    (when (memq hexl-bits '(8 16 32 64))
+      (when (string-match "\\(.*\\)-group-by-[0-9]+-bits\\(.*\\)" opts)
+        (setq opts (concat (match-string 1 opts) 
+                           (match-string 2 opts))) ) 
+      (setq opts (format "%s -group-by-%d-bits " opts hexl-bits)) )
+    opts))
+
 ;;;###autoload
 (defun hexlify-buffer ()
   "Convert a binary buffer to hexl format.
@@ -732,7 +775,7 @@ This discards the buffer's undo information."
            (mapcar (lambda (s)
                      (if (not (multibyte-string-p s)) s
                        (encode-coding-string s locale-coding-system)))
-                   (split-string hexl-options)))
+                   (split-string (hexl-options))))
     (if (> (point) (hexl-address-to-marker hexl-max-address))
 	(hexl-goto-address hexl-max-address))))
 
@@ -749,7 +792,7 @@ This discards the buffer's undo information."
 	(buffer-undo-list t))
     (apply 'call-process-region (point-min) (point-max)
 	   (expand-file-name hexl-program exec-directory)
-	   t t nil "-de" (split-string hexl-options))))
+	   t t nil "-de" (split-string (hexl-options)))))
 
 (defun hexl-char-after-point ()
   "Return char for ASCII hex digits at point."
@@ -846,12 +889,15 @@ CH must be a unibyte character whose value is between 0 and 255."
   (let ((address (hexl-current-address t)))
     (while (> num 0)
       (let ((hex-position
-	     (+ (* (/ address 16) 68)
+	     (+ (* (/ address 16) (hexl-line-displen))
 		10 (point-min)
 		(* 2 (% address 16))
 		(/ (% address 16) 2)))
 	    (ascii-position
-	     (+ (* (/ address 16) 68) 51 (point-min) (% address 16)))
+	     (+ (* (/ address 16) (hexl-line-displen)) 
+                (hexl-ascii-start-column) 
+                (point-min) 
+                (% address 16)))
 	    at-ascii-position)
 	(if (= (point) ascii-position)
 	    (setq at-ascii-position t))
@@ -867,7 +913,7 @@ CH must be a unibyte character whose value is between 0 and 255."
 	(if at-ascii-position
 	    (progn
 	      (beginning-of-line)
-	      (forward-char 51)
+	      (forward-char (hexl-ascii-start-column))
 	      (forward-char (% address 16)))))
       (setq num (1- num)))))
 
@@ -985,7 +1031,7 @@ This function is assumed to be used as callback function for `hl-line-mode'."
 
 (defun hexl-follow-ascii-find ()
   "Find and highlight the ASCII element corresponding to current point."
-  (let ((pos (+ 51
+  (let ((pos (+ (hexl-ascii-start-column)
 		(- (point) (current-column))
 		(mod (hexl-current-address) 16))))
     (move-overlay hexl-ascii-overlay pos (1+ pos))