YAT - yet another template strategy

| categories: orgmode, emacs | tags:

I have another need for a template that is dynamically evaluated. I previously wrote about this here , and today I am going to do a variation of the theme. We will still use a syntax of $(expression), but a new approach to evaluating the expression. I saw this interesting function to evaluate and replace an s-expression in a buffer Eval and Replace - Emacs Redux . I am going use that to replace a template expression in a string, with a little variation to avoid replacing non-sexp variations, e.g. $(. Here we go.

(defun eval-and-replace ()
  "Replace the preceding sexp with its value."
  (interactive)
  (backward-kill-sexp)
  (condition-case nil
      (princ (eval (read (current-kill 0)))
             (current-buffer))
    (error (message "Invalid expression")
           (insert (concat "$" (current-kill 0))))))

(defun j-format (s)
  "Replace all instances of $(expression) in S with the evaluated
expression."
  (with-temp-buffer
    (insert s)
    (goto-char (point-min))
    (while (re-search-forward "$(" nil t)
      (backward-char)
      (when (sexp-at-point)
        ;; get rid of the $
        (delete-char -1)
        ;; go to the end of the sexp and then eval-and-replace it.
        (end-of-sexp)
        (eval-and-replace)))
    ;; return the formatted text.
    (buffer-string)))


(let ((some-var "You got me"))
  (j-format "Test of 4 + 5 = $(+ 4 5). $(  $(foobar). $(progn (setq x 5) \"\")
and then we have 2x=$(prin1 (* 2 x)).

some-var = $(print some-var)"))
Test of 4 + 5 = 9. $(  $(foobar).
and then we have 2x=10.

some-var = You got me

That seems pretty ok. I obviously have not tested it extensively, but it looks pretty promising.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Saving the current restriction and restoring it while following links

| categories: orgmode, emacs | tags:

On the org-mode mailing list there has been some discussion about following id links. The issue is that if your buffer is narrowed, clicking on the link does not change the restriction to actually take you to the entry. This is debatably desirable. If I click on a link, I want it to go where it points. But, I might also like to go back to my narrowed view. So here consider how to save the state of narrowing, and restore it. We modify the function that opens an id link to save the restriction, and widen the buffer if necessary.

Saving the restriction seems easy, we just save a marker to point, and the point-min and point-max. We save the marker for a convenient way to get the buffer, and perhaps the actual point. We advise the C-c & function to restore the restriction after we leave it. This should fix the restriction in whatever buffer we undid it in.

Here is the code that seems to work for me. Thanks to Rasmus for the idea on saving the restriction data.

(defvar *saved-restriction* nil
 "A global var containing the current restriction.
Returns (current-buffer point-min point-max")

(defun save-current-restriction ()
  "Save the current restriction at point."
  (setq *saved-restriction*
        (if (buffer-narrowed-p)
            (list (current-buffer) (point-min) (point-max))
          nil)))

(defun restore-saved-restriction ()
  "Restore the last saved restriction."
  (when *saved-restriction*
    (set-buffer (car *saved-restriction*))
    (narrow-to-region (nth 1 *saved-restriction*)
                      (nth 2 *saved-restriction*)))
  (setq *saved-restriction* nil))

;' actually modify this function to save the restriction, and widen if needed.
(defun org-id-open (id)
  "Go to the entry with id ID."
  (org-mark-ring-push)
  (let ((m (org-id-find id 'marker))
        cmd)
    (unless m
      (error "Cannot find entry with ID \"%s\"" id))
    ;; Use a buffer-switching command in analogy to finding files
    (setq cmd
          (or
           (cdr
            (assq
             (cdr (assq 'file org-link-frame-setup))
             '((find-file . switch-to-buffer)
               (find-file-other-window . switch-to-buffer-other-window)
               (find-file-other-frame . switch-to-buffer-other-frame))))
           'switch-to-buffer-other-window))
    (if (not (equal (current-buffer) (marker-buffer m)))
        (funcall cmd (marker-buffer m)))
    (save-current-restriction)
    (when (> m (point-max))
      (widen))
    (goto-char m)
    (move-marker m nil)
    (org-show-context)))


;; And we advise the function going back to restore the restriction.
(defadvice org-mark-ring-goto (after restore-my-restriction () activate)
  "Restore narrowing."
  (restore-saved-restriction))
org-mark-ring-goto

This seems to preserve restrictions in the current buffer and in other buffers, as long as I use C-c & to invoke org-mark-ring goto. I am not sure how easy it would be to make this work for all links. Each link has its own function for following so I am not sure we can easily get them all to do this unless there is some high level function to advise like org-mouse-down-mouse or something similar. It also has the limitation that the restoration only occurs using org-mark-ring-goto, unless you specifically run the (restore-saved-restriction) function yourself. That could be made an interactive function for that purpose. Otherwise, this seems like a reasonable approach.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Line numbers in org-mode code blocks

| categories: orgmode, emacs | tags:

Some of my students have wanted to show line numbers in code blocks. This is especially useful for when you run a Python block, and you get an error message with a line number in it. Right now, to figure out which line that is, you have to into the code block, type C-c ' to get into edit mode, and turn line numbers on. We look into how to achieve that here.

You may want to see the video here: https://www.youtube.com/watch?v=kinWijGzXms .

First, we need to get the region that is the code block. We can find some info in the org-element, but, the :begin and :end include lines we don't want, like the header lines, and the results. But, we can get the beginning, and maybe from there search forward to the block. Run this code block to see where the point goes.

;; a boring comment

(progn
  (+ 40 2))

(goto-char (org-element-property :begin (org-element-context)))
(re-search-forward (regexp-quote (org-element-property :value (org-element-context))))
(goto-char (match-beginning 0))
;; number of lines in block. The last carriage return doesn't count.
(1- (length (s-split "\n" (org-element-property :value (org-element-context)))))
9

So, we can get the number of lines, and move the point to the first line. For numbers, we will use overlays. Here is a simple way to put a number at the beginning of a line.

(let (ov)
  (beginning-of-line)
  (setq ov (make-overlay (point) (point)))
  (overlay-put ov 'before-string "1"))
1

The next thing to do is make a function that puts a number at the beginning of a line. We might as well store these overlays in a variable, so they are easy to remove later. This is just for exploration of how to do it. Later we combine all these pieces together.

(defvar number-line-overlays '()
  "List of overlays for line numbers.")

(make-variable-buffer-local 'number-line-overlays)

(defun number-line (N)
 "Put an overlay at the beginning of a line."
  (beginning-of-line)
  (let (ov)
    (setq ov (make-overlay (point) (point)))
    (overlay-put ov 'before-string (format "%3s" (number-to-string N)))
    (add-to-list 'number-line-overlays ov)))

(number-line 4)
#<overlay from 1782 to 1782 in blog.org>

That looks promising. Let's make a function to clear those overlays. It is so easy it may not even be worth writing.

(defun number-line-clear ()
  (mapc 'delete-overlay number-line-overlays)
  (setq number-line-overlays '()))

(number-line-clear)

Finally, we are ready to hack up the code block numbering code. The numbers will not automatically update, so we will write a function that numbers the block, but only temporarily. Any key press will get rid of the numbers so we can get back to work. I am going to go ahead and make this a stand-alone function and block.

(defvar number-line-overlays '()
  "List of overlays for line numbers.")

(make-variable-buffer-local 'number-line-overlays)

(defun number-line-src-block ()
  (interactive)
  (save-excursion
    (let* ((src-block (org-element-context))
           (nlines (- (length
                       (s-split
                        "\n"
                        (org-element-property :value src-block)))
                      1)))
      (goto-char (org-element-property :begin src-block))
      (re-search-forward (regexp-quote (org-element-property :value src-block)))
      (goto-char (match-beginning 0))

      (loop for i from 1 to nlines
            do
            (beginning-of-line)
            (let (ov)
              (setq ov (make-overlay (point) (point)))
              (overlay-put ov 'before-string (format "%3s" (number-to-string i)))
              (add-to-list 'number-line-overlays ov))
            (next-line))))

  ;; now read a char to clear them
  (read-key "Press a key to clear numbers.")
  (mapc 'delete-overlay number-line-overlays)
  (setq number-line-overlays '()))

(number-line-src-block)

I am not sure how to get the numbers to automatically update smoothly like they do in linum-mode. That code uses a lot of hooks to make updates work, and embeds them in a minor mode to get rid of them. It also puts them in the fringe I think, but it is not clear how that is done.

We could modify what happens after the numbers are put on, e.g. pressing numbers might jump to a line, or some other kind of functionality. I don't have a critical need for this right now, so I didn't explore it more. Let me know if you have any good ideas for it!

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Automatic latex image toggling when cursor is on a fragment

| categories: orgmode | tags:

There was a recent suggestion on the org-mode mailing list to make it possible to toggle individual equations in org-mode when the cursor is on them, and have them toggle back when the mouse is off. Presumably, this would let you edit the equation and see the result very easily.

The strategy to enable this is to use add a function to the post-command function hook. The function will store the last fragment/environment you were on, and compare it to where you are now. If they are different the function will put the overlay back on the previous point, and do something appropriate at the current point, e.g. nothing if you are not on a fragment, or remove the overlay of the fragment you are on. The function will get run after every command, so we make sure we are in org-mode first!

Here are some example equations.

Here is a sentence with an equation \(f^{2x}=3\) and another form \(e^x = 20\) in it.

Here is a standalone equation environment.

\begin{equation} a + b \sqrt{5o} \end{equation}

And now, \[15 + 7 = 12\],

It is easiest to see this in a video here: https://www.youtube.com/watch?v=E0s3PDBqsEc

(defvar org-latex-fragment-last nil
  "Holds last fragment/environment you were on.")

(defun org-latex-fragment-toggle ()
  "Toggle a latex fragment image "
  (and (eq 'org-mode major-mode)
       (let* ((el (org-element-context))
              (el-type (car el)))
         (cond
          ;; were on a fragment and now on a new fragment
          ((and
            ;; fragment we were on
            org-latex-fragment-last
            ;; and are on a fragment now
            (or
             (eq 'latex-fragment el-type)
             (eq 'latex-environment el-type))
            ;; but not on the last one this is a little tricky. as you edit the
            ;; fragment, it is not equal to the last one. We use the begin
            ;; property which is less likely to change for the comparison.
            (not (= (org-element-property :begin el)
                    (org-element-property :begin org-latex-fragment-last))))
           ;; go back to last one and put image back
           (save-excursion
             (goto-char (org-element-property :begin org-latex-fragment-last))
             (org-preview-latex-fragment))
           ;; now remove current image
           (goto-char (org-element-property :begin el))
           (let ((ov (loop for ov in org-latex-fragment-image-overlays
                           if
                           (and
                            (<= (overlay-start ov) (point))
                            (>= (overlay-end ov) (point)))
                           return ov)))
             (when ov
               (delete-overlay ov)))
           ;; and save new fragment
           (setq org-latex-fragment-last el))

          ;; were on a fragment and now are not on a fragment
          ((and
            ;; not on a fragment now
            (not (or
                  (eq 'latex-fragment el-type)
                  (eq 'latex-environment el-type)))
            ;; but we were on one
            org-latex-fragment-last)
           ;; put image back on
           (save-excursion
             (goto-char (org-element-property :begin org-latex-fragment-last))
             (org-preview-latex-fragment))
           ;; unset last fragment
           (setq org-latex-fragment-last nil))

          ;; were not on a fragment, and now are
          ((and
            ;; we were not one one
            (not org-latex-fragment-last)
            ;; but now we are
            (or
             (eq 'latex-fragment el-type)
             (eq 'latex-environment el-type)))
           (goto-char (org-element-property :begin el))
           ;; remove image
           (let ((ov (loop for ov in org-latex-fragment-image-overlays
                           if
                           (and
                            (<= (overlay-start ov) (point))
                            (>= (overlay-end ov) (point)))
                           return ov)))
             (when ov
               (delete-overlay ov)))
           (setq org-latex-fragment-last el))))))


(add-hook 'post-command-hook 'org-latex-fragment-toggle)
org-latex-fragment-toggle matlab-start-block-highlight-timer eldoc-schedule-timer

I think there could be another way to do this with text properties, e.g. point-left and point-entered, but that would require those properties to be set on the fragments. I might try that approach another day.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

A checkbox list in org-mode with one value

| categories: orgmode, emacs | tags:

A while ago I had a need for a checklist in org-mode where only one value would be checked at a time. Like a radio button in a browser form. That isn't as far as I know a feature yet, but it was not hard to achieve thanks to the org-element api. My simple idea is to make a function that will be added to the org-checkbox-statistics-hook. The function will uncheck all the boxes, and recheck the one you just clicked with a hybrid of manipulating the cursor and inserting characters with org-element code. We will use an attribute on the checklist to indicate it is a "radio" list. This seems like a feature that might already exist, but I couldn't find it.

Here is the code we run. First, we make sure we are on a plain list that has an attr_org property of ":radio", that way this won't apply to all lists, just the radio lists. Then, we loop through each element in the structure, and if it is checked, we replace [X] with [ ]. Then, we reinsert the X and delete a space, which puts [X] where we originally clicked, or used C-c C-c. Finally, we add it to the hook, so it only gets run when a checkbox is changed via clicking with org-mouse, or C-c C-c. Of course, this doesn't work if you type X in the box.

(require 'dash)
(defun check-hook-fn ()
  (when (-contains? (org-element-property
                     :attr_org
                     (org-element-property :parent (org-element-context)))
                    ":radio")
    (save-excursion
      (loop for el in (org-element-property :structure (org-element-context))
            do
            (goto-char (car el))
            (when (re-search-forward "\\[X\\]" (line-end-position) t)
              (replace-match "[ ]"))))
    (forward-char)
    (insert "X")
    (delete-char 1)))

(add-hook 'org-checkbox-statistics-hook 'check-hook-fn)
check-hook-fn

Here is a regular checklist. You can check as many as you want.

  • [X] one
  • [X] two
  • [ ] three

Now, here is a radio checklist. Only one item at a time can be checked. Nice!

  • [ ] a
  • [ ] b
  • [X] c

It is worth noting here that if we put a name on the list, it becomes an addressable data source. First we need this convenient function to get the data associated with a named list.

(defun org-get-plain-list (name)
  "Get the org-element representation of a plain-list with NAME."
  (catch 'found
    (org-element-map
        (org-element-parse-buffer)
        'plain-list
      (lambda (plain-list)
        (when
            (string= name (org-element-property :name plain-list))
          (throw 'found plain-list))))))
org-get-plain-list

Now, let's use that to get the value of the checked item in the "test" list. We define the item as everything after the [X] and get it from a regular expression match.

(defun get-radio-list-value (list-name)
  "Return the value of the checked item in a radio list."
  (save-excursion
    (loop for el in (org-element-property
                     :structure
                     (org-get-plain-list list-name))
          if (string= (nth 4 el) "[X]")
          return (progn
                   (let ((item (buffer-substring (car el) (car (last el)))))
                     (string-match "\\[X\\]\\(.*\\)$" item)
                     (match-string 1 item))))))

(get-radio-list-value "test")
c

Perfect. This has lots of potential applications. Data collection and quizzes come to mind, with associated ability to autograde and aggregate the data!

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter
« Previous Page -- Next Page »