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.

  • (Don't quote lambdas.) – Drew Dec 21 '17 at 15:58
  • comment deleted – Ruy Dec 22 '17 at 1:26
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.

  • If you want the char, and not the string, just remove the string function. – Erik Sjöstrand Dec 21 '17 at 14:15
  • Dear @Erik, please see the last part of my question, which I added as a consequence of your answer. – Ruy Dec 21 '17 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. – Erik Sjöstrand Dec 21 '17 at 16:36
  • Thanks @Erik, setting temporary-goal-column to nil did the trick. – Ruy Dec 22 '17 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.

  • An alternative to move-to-column could be line-move-to-column, which will return nil if we end up inside a tab character. – Erik Sjöstrand Dec 21 '17 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 Dec 21 '17 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 Dec 22 '17 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 Dec 22 '17 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 Dec 22 '17 at 1:10

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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