0

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.

Update

Here are the functions that implement all the movements, based on @Omar's answer. They can probably be written in a better way, but they work.

(defun column-move-down-first-line (arg)
  "Move the point to the beginning of the next vertical string along the current
column.

A vertical string here is a string of non-whitespace characters along a one
character wide column, separated from other vertical strings 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")
  (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 ((> arg 0)
           (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)))
          ((< arg 0)
           (condition-case x
               (save-excursion
                 (previous-line) ; Avoids getting stuck on a line.
                 (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)
                (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-first-line (arg)
  "Move the point to the beginning of the previous vertical string along the
current column.

A vertical string here is a string of non-whitespace characters along a one
character wide column, separated from other vertical strings 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")
  (column-move-down-first-line (- arg)))

(defun column-move-down-last-line (arg)
  "Move the point to the end of the next vertical string along the current column.

A vertical string here is a string of non-whitespace characters along a one
character wide column, separated from other vertical strings 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")
  (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 ((> arg 0)
           (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)
                (move-to-column goal-column)
                (if (funcall some-non-ws-p)
                  (setq destination (point))
                  (signal (car x) (cdr x))))))
           (when destination (goto-char destination)))
          ((< arg 0)
           (condition-case x
               (save-excursion
                 (while (funcall some-non-ws-p)
                   (previous-line))
                 (while (not (funcall some-non-ws-p))
                   (previous-line))
                 (setq destination (point)))
             (beginning-of-buffer
              (signal (car x) (cdr x))))
           (when destination (goto-char destination))))))

(defun column-move-up-last-line (arg)
  "Move the point to the end of the previous vertical string along the current
column.

A vertical string here is a string of non-whitespace characters along a one
character wide column, separated from other vertical strings 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")
  (column-move-down-last-line (- arg)))
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))))
  • 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! – Arch Stanton Aug 17 '18 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 '18 at 13:13
  • @ArchStanton, please try the new function. – Omar Aug 17 '18 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? – Arch Stanton Aug 18 '18 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? – Arch Stanton Aug 18 '18 at 16:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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