4

I have a bunch of lines of text and I'm trying to insert text starting at a certain column on each line. For example:

'Some text'            This is ok
'Another longer text'  ask Ram
'weird stuff'          see other file
'more lines'
'even more lines'

I'd like to continue typing text on the further lines, at the same column as the previous lines. The part that's cumbersome is moving to the right place on each line.

Emacs has a way of moving to the same place on each line: set-goal-column. This would work if that column already existed on each line, but that's not the case here.

One way I can work around this (which I'm actually using) is to insert a lot of spaces on each line (replace-regexp $ with a few dozen spaces), then C-x C-n (set-goal-column), and use regular C-n to move between lines. But I'd like to know whether there's a clean way of achieving something better: of setting things up so that after set-goal-column, I can just use C-n to move to the next line, and Emacs will automatically insert the appropriate number of spaces if there weren't enough columns to start with.

ShreevatsaR
  • 880
  • 6
  • 19
  • Do you really want to insert spaces? Do you expect them to go away if you immediately move to the next line? And if so, what happens if the line *already* contained some whitespace? Maybe look to the `artist-mode` source code for inspiration for how to approach this without introducing permanent whitespace unnecessarily... – phils Oct 19 '17 at 02:17
  • 1
    This may also be of use to you: `(when goal-column (move-to-column goal-column t))` – phils Oct 19 '17 at 02:19
  • FYI, I think `line-move-to-column` is what ultimately carries out the normal behaviour. – phils Oct 19 '17 at 02:26
  • @phils I don't mind inserting actual spaces; I anyway have a before-save-hook that strips trailing whitespace (and if I didn't, I wouldn't care about trailing whitespace, so that's not a problem either). And yes I had reached a similar conclusion from looking at the source code (following `line-move-to-column` etc), I think I need to change `line-move-to-column` which does `(move-to-column col)` to do `(move-to-column col t)` instead. Or maybe `(when goal-column (move-to-column goal-column t))` as you suggested. Can you post that as an answer, along with how to “modify” `line-move-to-column`? – ShreevatsaR Oct 19 '17 at 02:31

2 Answers2

1

Here is advice that should do what you want:

(defun goal-column-advice (&rest _args)
  "Advice inserting spaces when needed to obey `goal-column'.

This advice is intended as :after advice for commands
`previous-line' and `next-line'.  Normally, these commands will
attempt to obey any `goal-column' setting, but only on lines that
are long enough that the `goal-column' is not past the end of the
line.  This advice changes the advised command to instead pad the
line with spaces in this circumstance.  If `goal-column' is nil,
the command behaves as if it were not advised."
  (when goal-column (move-to-column goal-column t)))

(advice-add 'previous-line :after #'goal-column-advice)
(advice-add 'next-line     :after #'goal-column-advice)

Thanks to @phils for the comment pointing out the role of move-to-column.

As @phils also notes, this method does leave trailing whitespace in your buffer, which some people may not want. The OP doesn't seem to mind, but others may need to find a different solution.

Aaron Harris
  • 2,664
  • 17
  • 22
  • Thanks for this! It didn't work for some reason, but then I realized it works if I change `:force` to `t`. BTW what do you think of the idea of using `:override` instead of `:after` in `advice-add`? – ShreevatsaR Oct 25 '17 at 21:49
  • You are absolutely right on both counts; I forgot to test after making the `t` => `:force` change, and the advice was initially going on `next-line` and `previous-line`, so I didn't notice that `:override` works too. Answer updated. – Aaron Harris Oct 26 '17 at 02:07
  • On second thought, `:override` isn't quite right, because we should call the original function if `goal-column` is not set. (In testing, it didn't seem to affect `previous-line` or `next-line` at all, though; nevertheless, we should do the right thing.) I've changed it to `:around` advice to correct this. – Aaron Harris Oct 26 '17 at 02:22
  • Okay, I did some more testing and found that none of the advice previously was working correctly on `previous-line`; the spaces were being added, but then point was being moved again after our advice. Putting the advice on `line-move-1` fixes the problem, but needs to be `:after` advice again. Sorry for not doing more testing up front; I'm pretty sure this is right now. – Aaron Harris Oct 26 '17 at 02:55
  • Nope. The last version broke `move-end-of-line` (a.k.a. `C-e`) because that actually sets `goal-column` temporarily internally. I'm coming to the conclusion that the source is too tangled to try doing anything clever here; using `:after` advice on `previous-line` and `next-line` directly is probably the best way to go. – Aaron Harris Oct 26 '17 at 03:05
  • Great idea. Now I just need a way of setting the goal-column and then running this from a keybaord. – vy32 Mar 14 '20 at 16:25
1
;; Here we create the concept of a 'tab-column.'
;; Set the tab column where you want by positioning the cursor and typeing M-+
;; Then move to another line and type M-=, and EMACS will insert enough spaces
;; to get you to the column. This is useful for alinging columns of text without tabs.
;; tab-column is a silly name; I need a better one.

(defun set-tab-column (&rest _args)
  "Sets tab column to current column"
  (interactive "P")
  (setq tab-column (current-column))
  (message "tab column set to %s" tab-column))
(global-set-key (kbd "M-+") 'set-tab-column)
(setq tab-column 0))

(defun space-to-tab-column (&rest _args)
  "Insert spaces to tab-column"
  (interactive "P")
  (let ((count 0))
    (while (> tab-column (current-column))
      (setq count (+ 1 count))
      (insert " ")
      )
    (message "%s spaces inserted" count)
    )
)
(global-set-key (kbd "M-=") 'space-to-tab-column)
vy32
  • 142
  • 8
  • The first function has a close paren in the wrong place. Move the one at the end of `(setq tab-column (current-column)))` to the end of the next line. – nealmcb Apr 10 '20 at 13:00
  • 1
    Thanks, but next time, feel free to edit yourself! – vy32 Apr 10 '20 at 13:15
  • For me, since on this particular site I don't have 2K reputation, it won't let me fix tiny but important errors: "Edits must be at least 6 characters". And note that an error remains: the paren needed to be moved, not deleted. – nealmcb Apr 10 '20 at 13:33
  • Well I appreciate the bug fix! (It was correct in my .emacs file) – vy32 Apr 10 '20 at 14:51
  • Note again: there is now a missing paren at the end of the line `(message "tab column set to %s" tab-column)` The paren that you deleted was just in the wrong place. Yet more evidence that the 6-character edit minimum is counterproductive - see https://meta.stackexchange.com/a/90014/155203 – nealmcb Apr 11 '20 at 15:26
  • 1
    fixed. Sorry! The 10 character limit for comments can also be counter-productive. – vy32 Apr 11 '20 at 16:04