Search code examples
regexemacsalignment

Function to align tables using commas outside of quotation


So I wrote a function to do some table alignment like so:

{"aa,aa"    , "sdjhjh" , "bb,asd"}
{"asdffasd" , "fsd,d"  , "sdfsdf"}

The code is here:

(defun align-table (beg end)
  (interactive "r")
  (goto-char beg)
  (replace-chars beg end "|" ?,)
  (align-regexp beg end "\\(\\s-*\\)|" 1 1 1)
  (replace-chars beg end "," ?|))

It works by finding all the commas outside quotes in the specified region and replaces them with a pipe, the pipe is then used to align the table using align-regexp and then it finds all the pipes and replaces them with commas again.

The issue is that the alignment process changes the size of the region and my second call to the replace-chars function misses the last few pipe characters it is supposed to replace as they occur after the end point.

Here are the functions that do the work

(defun find-char (start end char)
 (interactive "r")
 (save-excursion
   (goto-char start)
   (let (matches)
    (while (< (point) end)
     (cond ((= (char-after) char)
            (push (point) matches)
            (forward-char))
           ((looking-at "\"")
            (forward-sexp))
           (t
             (forward-char))))
     (nreverse matches))))

(defun replace-chars (beg end sep char)
  (interactive "r")
  (let ((comma-list (find-char beg end char)))
    (print comma-list)
    (dolist (point comma-list)
      (goto-char point)
      (delete-char 1)
      (insert sep))))

Can anyone think of a way to align the tables with the commas outside of strings?


Solution

  • I can't get your function to work exactly as your example shows, and I'm not interested in figuring out why. But I think I can answer your actual question.

    Use narrowing and operate on the entire narrowed buffer:

    (defun align-table (beg end)
      (interactive "r")
      (save-excursion
        (save-restriction
          (narrow-to-region beg end)
          (goto-char (point-min))
          (replace-chars (point-min) (point-max) "|" ?,)
          (align-regexp (point-min) (point-max) "\\(\\s-*\\)|" 1 1 1)
          (replace-chars (point-min) (point-max) "," ?|))))
    

    A possibly good way to find only commas outside strings is to leverage fontlock and syntax-ppss (e.g. (nth 3 (syntax-ppss pos)) roughly means "fontlock thinks position pos is in a string").