2

I am writing an Emacs Lisp function which needs to know the character directly under point in the line below, returning nil when no such character exists. I thus defined the function char-below, which seems to work well except that it breaks down in case the line below has tabs.

(defun char-below ()      
  (condition-case nil
      (aref
        (buffer-substring-no-properties (line-beginning-position 2) (line-end-position 2))
        (- (point) (line-beginning-position)))
    (error nil)))

Is there a built-in elisp function to accomplish this? If not, is there a cleverer way to write char-below, accounting for the presence of tabs?


EDIT: I tried a solution based on next-line but there seems to be a problem with using next-line in Lisp code, as observed in its doc-string:

"This function is for interactive use only; in Lisp code use forward-line instead."

A way to illustrate the problem is by defining the following key-binding, using the char-below function proposed by Erik Sjöstrand, which is supposed to display the character below in the message area and move forward one char.

 (global-set-key (kbd "<f12>")
   (lambda ()
     (interactive)
     (message (char-below))
     (forward-char)))

Curiously, repeated pressed of the key f12 thus defined keep messaging the same character below the position point was at the time of the FIRST invocation of f12.

I think this is because next-line uses goal columns, and somehow the goal column is not updated when the above key is repeated.


EDIT 2

By the way, Erik initially proposed the function char-below as follows

(defun char-below ()
  (ignore-errors
    (save-excursion
      (next-line)
      (string (following-char)))))

and this is the function that causes the error I mentioned above. Since then Eric has edited his function inserting the command (setq temporary-goal-column nil) which fixes this issue.

2
  • (Don't quote lambdas.)
    – Drew
    Commented Dec 21, 2017 at 15:58
  • comment deleted
    – Ruy
    Commented Dec 22, 2017 at 1:26

2 Answers 2

2

Maybe something like this? I haven't tested it with tabs though...

(defun char-below ()
  (setq temporary-goal-column nil) 
  (ignore-errors
    (save-excursion
      (next-line)
      (string (following-char)))))

If you want it to return nil when there's no char below, because the row below is shorter than the current position, this is a modification:

(defun char-below ()
  (setq temporary-goal-column nil) 
  (ignore-errors
    (save-excursion
      (next-line)
      (unless (eolp)
        (string (following-char))))))

It also returns nil when the char at the row below is the end of the line, which might not be what you want.

4
  • If you want the char, and not the string, just remove the string function. Commented Dec 21, 2017 at 14:15
  • Dear @Erik, please see the last part of my question, which I added as a consequence of your answer.
    – Ruy
    Commented Dec 21, 2017 at 15:42
  • The problem was with temporary-goal-column. If you don't want to use next-line, you can use line-move-visual or line-move. Commented Dec 21, 2017 at 16:36
  • Thanks @Erik, setting temporary-goal-column to nil did the trick.
    – Ruy
    Commented Dec 22, 2017 at 0:59
2
(defun char-below ()
  (let ((col  (current-column)))
    (save-excursion
      (if (not (zerop (forward-line 1)))
          nil
        (move-to-column col)
        (and (not (eolp))  (char-after))))))

But you don't say what you mean by "it breaks down in case the line below has tabs" or "accounting for the presence of tabs".

If you want to consider that any tab chars present are in fact replaced by space chars, then you can use move-to-column with optional arg FORCE to convert tab chars to spaces. In that case you'll need to also undo that change if you don't want it to persist.

Another thing unclear about your question: you ask for the character (which the code here gives you), but the code you supply doesn't even try to return the character - it tries to return its position.

5
  • An alternative to move-to-column could be line-move-to-column, which will return nil if we end up inside a tab character. Commented Dec 21, 2017 at 16:38
  • @ErikSjöstrand: 1. It's not clear what behavior is requested when there is a tab char. 2. line-move-to-column ignores invisible text. Is that good or bad here? Nothing in the question says it should (or should not) be ignored.
    – Drew
    Commented Dec 21, 2017 at 18:40
  • Hi @Drew, thanks for your answer. I did not know about the built-in functions current-column and move-to-column, which essentially solve my problem.
    – Ruy
    Commented Dec 22, 2017 at 1:05
  • @Drew, regarding your comment about my code returning the position rather than the character, you might not have noticed the call do aref on the string built with buffer-substring.
    – Ruy
    Commented Dec 22, 2017 at 1:07
  • @Drew, my code does not work if there are tabs on the line below in a column to the left of point, due to my naive application of aref, but with move-to-column the problem disappears. Thanks.
    – Ruy
    Commented Dec 22, 2017 at 1:10

Your Answer

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

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