1

I wrote this macro for my .emacs file to trace its progress during loading:

(defmacro checkpoint ()
  `(message "Reached line %i" (1+ `,(current-line))))

So I placed

(checkpoint)

throughout .emacs.

The function current-line returns a zero-based line number that obviously should be evaluated at runtime. Here's the output:

Reached line 1
Reached line 1
Reached line 1
Reached line 1
Reached line 1282
Reached line 1282
Reached line 1094
Reached line 947
Reached line 4

How can I properly expand (current-line) in the macro?


;; -*- mode: emacs-lisp; lexical-binding: t; -*-

;; (Re)load the present file by placing point after the closing
;; parenthesis of the following expression in this comment, then pressing
;; C-x C-e (eval-last-sexp)
;;    (load-file "./malformed-macro.el")

(require 'display-line-numbers)
(display-line-numbers-mode)

(require 'array)  ; for current-line

(defmacro checkpoint ()
  "Show that execution has reached the line containing `checkpoint'."
  `(message "(checkpoint)                                        = %i"
            (1+ `,(current-line))))

;;                          *****************************************
;;                          * Actual line number in this here file: * --+
;;                          *****************************************   |
;;                                                                      |
;;                                                                      |
;; I expected (current-line) to return different values when expanded   |
;; in following two lines.  Image my surprise!                          v
(message "(1+ (current-line))                                 = %i"
         (1+ (current-line)))                                        ; 26
(message "(1+ (current-line))                                 = %i"
         (1+ (current-line)))                                        ; 28

;; I already knew there was a problem with my macro yielding the same
;; expansion when appearing on different lines, though I didn't suspect
;; (current-line) to be the culprit:
(checkpoint)                                                         ; 33
(checkpoint)                                                         ; 34

;; Here is the implementation of current-line from array.el, commented
;; out:
;;    (defun array-current-line ()
;;      "Return the current buffer line at point.  The first line is 0."
;;      (count-lines (point-min) (line-beginning-position)))
;;    (define-obsolete-function-alias
;;      'current-line #'array-current-line "29.1")

;; Here I have inserted in-line the expression that current-line
;; returns:
(message "(count-lines (point-min) (line-beginning-position)) = %i"
         (count-lines (point-min) (line-beginning-position)))        ; 47
(message "(count-lines (point-min) (line-beginning-position)) = %i"
         (count-lines (point-min) (line-beginning-position)))        ; 49

;; Very interesting.  We still get the same value, although the
;; expressions occur on different lines.

;; So is the problem with  (point-min) or (line-beginning-position)
;; or both?  Let's find out.

;; The point-min function returns "the minimum permissible value of
;; point in the current buffer.  This is 1, unless narrowing (a buffer
;; restriction) is in effect."
(message "(point-min)                                         = %S"
         (point-min))                                                ; 61
(message "(point-min)                                         = %S"
         (point-min))                                                ; 63

;; point-min did indeed consistently return 1.

;; The line-beginning-position function returns "the position of the
;; first character in the current line/field."
(message "(line-beginning-position)                           = %S"
         (line-beginning-position))                                  ; 70
(message "(line-beginning-position)                           = %S"
         (line-beginning-position))                                  ; 72

;; It looks to me as though line-beginning-position does not behave
;; as advertised.
9
  • I don't have a function current-line here - what library does that come from?
    – Tyler
    Commented May 29 at 18:07
  • current-line is an alias for ‘array-current-line’ in ‘array.el’. This function is obsolete since 29.1; use ‘array-current-line’ instead. (current-line) Return the current buffer line at point. The first line is 0. Probably introduced at or before Emacs version 26.1. Commented May 29 at 19:36
  • I can't reproduce your problem. Can you provide an elisp file that has this problem when you evaluate it?
    – Tyler
    Commented May 29 at 20:29
  • 1
    The answer from @db48x is correct, and also explains why (checkpoint) works interactively, but not when loading a file.
    – Tyler
    Commented May 31 at 13:45
  • 1
    My reply to 68275 was certainly not hostile or useless.
    – db48x
    Commented Jun 5 at 1:08

2 Answers 2

3

This is a moderately frequent confusion. current-line and point tell you the location of the cursor in the current buffer, that is true. But when you load a file, the current buffer does not hold the source code of the file. The current buffer stays whatever it was before you called load. It could be an email or an image or a document; anything. The position of the cursor could be anywhere within that buffer. Thus, current-line does not tell you what line of the source is currently being compiled or executed.

Emacs Lisp simply does not have any way of retrieving this information on a line–by–line basis. However, all functions do have an associated source file and position. You can use symbol-file to look it up. See its source code for more information.

0

This solution is based on preprocessing the current buffer.

Assume that the text below is in a file named "solution.el". At the bottom of the file are three expressions, commented out. To try out this solution, evaluate the first to load the file, the second to replace occurrences of "(checkpoint)" in the current buffer, and the third to restore them.

I have some ideas about adding hooks to 'replace-checkpoints and 'restore-checkpoints, but I have not yet tested them.

;;; solution.el --- An implementation of checkpoint based on preprocessing the current buffer.  -*- mode: emacs-lisp; lexical-binding: t; -*-

(require 'display-line-numbers)
(display-line-numbers-mode)

;; Make (checkpoint) harmless:
(defun checkpoint ()
  "Show that execution has reached the line containing `checkpoint'."
  (ignore))

(defun replace-or-restore-checkpoints (replace-p)
  "Replace occurrences of \"(checkpoint)\" in BUFFER."
  (let* ((search-pattern     (if replace-p "^(checkpoint)$" "(message \"Reached line [[:digit:]]+\")"))
         (replacement-string (if replace-p nil "(checkpoint)"))
         (replacement-format (if replace-p "(message \"Reached line %i\")" nil))
         (continue          t)
         (rc))
    ;; Move through the buffer, copying text that does not match search-pattern, and
    ;; replacing instances of it:
    (goto-char (point-min))
    (while continue
      (setq continue (re-search-forward search-pattern nil t))
      (when continue
        (when replace-p
          (setq replacement-string (format replacement-format (1+ (current-line)))))
        (replace-match replacement-string)))
    (save-buffer)))

(defun replace-checkpoints ()
  "Replace occurrences of \"(checkpoint)\" in BUFFER."
  (replace-or-restore-checkpoints t))

(defun restore-checkpoints ()
  "Restore occurrences of \"(checkpoint)\" in BUFFER."
  (replace-or-restore-checkpoints nil))





(checkpoint)



(checkpoint)








(checkpoint)

;; (load-file "./solution.el")
;; (replace-checkpoints)
;; (restore-checkpoints)

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.