1

I'd like to have a command that moves down to the first line of the next string of nonwhitespace characters along the current column, and the analogous commands that move upward, and to the last line of the block.

Basically, what I'd like to achieve is this kind of motion:

https://github.com/machakann/vim-columnmove(source)

The command for moving downward to the first line, for instance, should move along the current column, jumping to the next line that is neither so short that it doesn't reach the current column, nor so indented that it contains only whitespace up to the current column. For example, if I have the cursor on the “s” of “string” in the text below:

  do i = 1, n
     print *, "Some string"
  end do
  ! something else
  if (x > y) then
     print *, "Some more text"
  end if

it should move to the "m" of "more". If I start from the "o" in the first "do", it should move to the "n" in "end do", and then to the "n" in "end if".

Notes:

  • the point can (and should) jump into whitespace if it's surrounded by text, for example,
    start from here →|
    short line.
    end up here →    |       some text               
  • this question is similar to this one for which I have not found solutions yet. This question is different though, as the behaviour described here is not that of a vertical analogue of forward-word.

Updates

Here are the functions that I use (they're based on Omar's answer):

(defun column-move (direction arg)
  "If DIRECTION is ‘up’, move the point up to the first line that contains
non-whitespace characters on both sides of the current column, below a line
that doesn't.

If DIRECTION is ‘down’, move the point down to the last line that contains
non-whitespace characters on both sides of the current column, above a line
that doesn't.

Do it ARG times."
  (dotimes (i arg)
    (let (destination
          (goal-column (current-column))
          (some-non-ws-p
           (lambda ()
             (and (= (current-column) goal-column)
                  (let ((pt (+ (if (eolp) 0 1) (point))))
                    (save-excursion
                      (beginning-of-line)
                      (re-search-forward "\\S-" pt t)))))))
      (cond
       ((eq direction 'up)
        (condition-case x
            (save-excursion
              (previous-line) ; avoids getting stuck on a line. Note (from ‘C-h f previous-line’): This function is for interactive use only; in Lisp code use ‘forward-line’ with negative argument instead.
              (while (not (funcall some-non-ws-p))
                (previous-line))
              (while (funcall some-non-ws-p)
                (previous-line))
              (next-line)
              (setq destination (point)))
          (beginning-of-buffer
           (save-excursion
             (beginning-of-buffer) ; Note (from ‘C-h f beginning-of-buffer’): This function is for interactive use only; in Lisp code use ‘(goto-char (point-min))’ instead.
             (move-to-column goal-column)
             (if (funcall some-non-ws-p)
                 (setq destination (point))
               (signal (car x) (cdr x)))))))
       ((eq direction 'down)
        (condition-case x
            (save-excursion
              (next-line) ; avoids getting stuck on a line.
              (while (not (funcall some-non-ws-p))
                (next-line))
              (while (funcall some-non-ws-p)
                (next-line))
              (previous-line)
              (setq destination (point)))
          (end-of-buffer
           (save-excursion
             (end-of-buffer) ; Note (from ‘C-h f end-of-buffer’): This function is for interactive use only; in Lisp code use ‘(goto-char (point-max))’ instead.
             (move-to-column goal-column)
             (if (funcall some-non-ws-p)
                 (setq destination (point))
               (signal (car x) (cdr x))))))))
      (when destination (goto-char destination)))))

(defun column-move-up (&optional arg)
  "Move the point up to the next beginning of a vertical string along the current
column. With negative prefix argument, move it down to the next end instead. (It
works as a sort of vertical analogue to ‘left-word’.)

By “vertical string” here it is meant a string of non-whitespace characters
along a column, separated from others by either a short line (a line that
doesn't reach the column where the string is) or an indented line (a line
consinsting entirely of whitespace up to and including the column where the
string is)."
  (interactive "^p")
  (cond
   ((>= arg 0)
    (column-move 'up arg))
   ((< arg 0)
    (column-move 'down (- arg)))))
(global-set-key (kbd "C-S-p")  #'column-move-up)
(global-set-key (kbd "M-<up>") #'column-move-up)

(defun column-move-down (&optional arg)
  "Move the point down to the next end of a vertical string along the current
column. With negative prefix argument, move it up to the next beginning instead.
(It works as a sort of vertical analogue to ‘right-word’.)

By “vertical string” here it is meant a string of non-whitespace characters
along a column, separated from others by either a short line (a line that
doesn't reach the column where the string is) or an indented line (a line
consinsting entirely of whitespace up to and including the column where the
string is)."
  (interactive "^p")
  (cond
   ((>= arg 0)
    (column-move 'down arg))
   ((< arg 0)
    (column-move 'up (- arg)))))
(global-set-key (kbd "C-S-n")    #'column-move-down)
(global-set-key (kbd "M-<down>") #'column-move-down)

There's a new package that works somewhat like this thing, see https://gitlab.com/ideasman42/emacs-spatial-navigate

1 Answer 1

1

I initially misunderstood the requirements, but believe this updated function does the right thing and also corrects the bug the old function had at the beginning of the line:

EDIT: updated to only move the point when a suitable line is found:

(defun down-stantonly ()
  (interactive)
  (let (destination
        (goal-column (current-column))
        (some-non-ws-p
         (lambda ()
           (and (= (current-column) goal-column)
                (let ((pt (+ (if (eolp) 0 1) (point))))
                  (save-excursion
                    (beginning-of-line)
                    (re-search-forward "\\S-" pt t)))))))
    (condition-case x
        (save-excursion
          (while (funcall some-non-ws-p)
            (next-line))
          (while (not (funcall some-non-ws-p))
            (next-line))
          (setq destination (point)))
      (end-of-buffer
       (signal (car x) (cdr x))))
    (when destination (goto-char destination))))
8
  • Almost! The point should jump over the block it's currently in and land in the first line of the next block. For example, if the point is on the "d" of down-stantonly in the first line of the function definition, it should jump straight to the "n" at the beginning of next-line in the last line. Also, it doesn't work properly when the point starts from column 0. Thanks for the help! Aug 17, 2018 at 8:15
  • Oh, sorry @ArchStanton, I didn't read carefully. I thought you wanted to move vertically to the next character not entirely preceded by whitespace (so, from the d in down-stantonly to the r in interactive). I'll fix it when I'm at a physical keyboard.
    – Omar
    Aug 17, 2018 at 13:13
  • @ArchStanton, please try the new function.
    – Omar
    Aug 17, 2018 at 15:00
  • Many thanks :-) I've tried to adapt it to all movements up and down and to the first and last line (see the updated question). Do you think it's ok? Aug 18, 2018 at 10:15
  • It seems to me that the only rough spot now is that when the function doesn't find a suitable line to land on (it happens toward the end of the buffer), it jumps straight to the end of the buffer. I'd say the correct behaviour there would be to keep the point where it is and print a warning message, but I'd rather not complicate the code much just to handle this case. Is it feasible? Aug 18, 2018 at 16:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.