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:
(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