Blog

For the English part of the blog, see Content AND Presentation.

2023-03-25 Using a thesaurus from within Emacs

Now that I started to devote more time to writing, I need a thesaurus more and more often. What I usually did was to go to my browser and use thesaurus.com – but as we all know, leaving Emacs is always a pain. I fired M-x package-list-packages and it turned out that there are several packages to look up synonyms from the comfort of Emacs.

I tried a few and decided that I like two of them most. (That doesn’t mean that the rest are necessarily bad, you check them out, too!) One of them is mw-thesaurus, which uses the Merriam-Webster thesaurus under the hood. Its main advantage is that it displays a nice Org buffer with examples of use, synonyms, antonyms and related words. The other one is le-thesaurus, which uses https://thesaurus.com/. It has much more bare-bones output, but can replace the word at point with a selected synonym. (Having to select from dozens of options is not the best experience possible, though.)

I’m not sure which one I’m going to use, but the options are there.

(Added over a week later.) It turns out that I use the Merriam-Webster package a bit more often. However, I discovered an issue with it. In order to make the thesaurus buffer look better, it sets org-hide-emphasis-markers to t. The disadvantage is that it is a global variable, and it affects all the Emacs buffers. I commented out the relevant piece of code so that it won’t bother me anymore. The interesting thing, though, is how I found out. It didn’t take me too long to notice that something is messing with org-hide-emphasis-markers. Trouble is, I did not associate it with the thesaurus package at all. So, I fired debug-on-variable-change, gave that variable name… and forgot about it. The next day (or maybe even later) I finally launched the thesaurus code and everything clicked. This is yet another example of how Emacs can be helpful. Imagine you install some plugin for … [insert the name of your favorite editor here] and it starts to change some setting which doesn’t feel obviously related to what it’s doing. How much time and effort would you need to debug this issue? Whereas with Emacs it was a few minutes of work (and a few hours or days waiting for the problem to manifest itself). Of course, the fact that I restart Emacs very rarely helped (though I could have put the debug-on-variable-change in my init.el, too).

Anyway, Emacs – as usual – is a fantastic piece of software that helps users, not hinders them.

CategoryEnglish, CategoryBlog, CategoryEmacs

Comments on this page

2023-03-20 Manually entering clocking data

As I alluded many times in the past, I use Org mode’s clocking feature almost all the time. However, there are times I don’t. One of such cases is a rare situation when I don’t have access to my computer. Since I started commuting using public transport, I do some reading in a streetcar or bus. When I get to my laptop again, I want to update my clocking data. How to do that?

One way is to use one of the many Org mode mobile solutions. I’ve been looking at Organice for some time. There is, however, one thing that stops me from using it – I am not sure if I really want to have a lot of my private (maybe even a bit sensitive) data on a mobile device, which can be easily lost or stolen. (This really happens. However stupid it sounds, I did lose my mobile phone once, many years ago. And a second time a few weeks ago. And my wife had her phone stolen.) Also, I do not need to clock in/out without access to my computer very often, so for now a manual solution will do.

(defun org-clock-enter-manually (begin end)
  "Enter a clock item manually."
  (interactive (list (org-read-date t t) (org-read-date t t)))
  (save-excursion
    (org-clock-find-position nil)
    (insert-before-markers-and-inherit "\n")
    (backward-char 1)
    (insert-and-inherit org-clock-string " ")
    (org-insert-time-stamp begin t t)
    (org-insert-time-stamp end t t "--")
    (org-evaluate-time-range t)
    (beeminder-org-submit-clock-at-point)))

This is an extremely simplistic code – notice the boring, uniform indentation, which means it doesn’t even need any kind of flow control like loops or conditionals. It just reads the begin and end time, puts it in the suitable place (using function calls I found looking at org-clock-in source code), computes the difference between the two points in time, and finally sends these data to Beeminder.

And that’s it. Until I find a satisfying automated solution (maybe just have the parts of my Org files I really need in Organice?), this will must do.

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

2023-03-11 Adding my TODOs to agenda

Last month I wrote about my way of managing TODOs. It occurred to me that I could make it even better. Why not add my set of TODOs to look into to my agenda? I have already constructed a regex matching them. Unfortunately, the section of the manual dealing with custom agenda views and search was a bit vague, so I decided to ask on the mailing list. Alas, I got no answers, which didn’t worry me too much – I just decided that I’ll try to find the answer myself.

Another thing I wanted to achieve was harnessing the randomness. I didn’t like the fact that the “middle” tasks were selected at random every time I generated the list, so I came up with the idea of seeding the random number generator with today’s date. Elisp’s random function allows for setting the seed, but only as a global state, so I decided to use cl​’s cl-random, which accepts the seed (more or less) as the second parameter. I paired this with (time-to-days nil) which gives the same integer every day.

This is still not ideal – if I add or remove an item from the TODO list, the count will be different so even on the same day, another set will be selected. I can live with that, though, especially that I don’t add (or remove) stuff from these lists all the time. My workflow is that whenever I find something interesting I’d like to dig deeper into later, I put it in the capture.org file. Then, usually once a day, I go through the new items there and assign them to various buckets.

Anyway, here is the code. An astute reader will notice that a few functions are changed from what what they used to be in the previous post – this is expected, since I now need a separate function producing just the list of strings to be fed either to the agenda or to the command showing just the tasks. Also, it turned ot that instead of the :title property, I need :raw-value, which gives very similar results, but not adorned by string properties etc. (For some reason, it broke in certain circumstances – I’m not sure why, since I didn’t investigate it deeper.)

  (require 'cl-lib)

  (defun org-get-first-random-last (first random last)
    "Return FIRST first headlines, RANDOM random and LAST last ones.
  For simplicity, the random ones are chosen from all of them,
  including the first/last ones.  Also, headlines on all levels are
  considered, effectively flattening the current subtree for the
  purpose of finding the ones to show."
    (let* ((headlines (cdr (org-map-entries
                            (lambda ()
                              (org-element-property
                               :raw-value
                               (org-element-at-point)))
                            nil
                            'tree
                            'archive 'comment)))
           (length (length headlines))
           (head (seq-take headlines first))
           (tail (seq-drop headlines (- length last)))
           (random-state (cl-make-random-state
                          (time-to-days nil)))
           (belly (cl-loop repeat random
                           collect (seq-elt headlines
                                            (cl-random
                                             length
                                             random-state)))))
      (seq-concatenate 'list head belly tail)))

  (defun org-show-first-random-last (first random last)
    "Show FIRST first headlines, RANDOM random and LAST last ones.
See `org-get-first-random-last'."
    (interactive (let ((arg (prefix-numeric-value current-prefix-arg)))
                   (list arg arg arg)))
    (org-sparse-tree-from-list
     (org-get-first-random-last first random last)))

  (defun org-list-future-tasks (when count)
    "Return a list of future tasks.
  WHEN is a string matching a top headline in `future.org'.
  COUNT is the number of first, random and last tasks."
    (with-current-buffer "future.org"
      (save-excursion
        (goto-char (point-min))
        (let (pos)
          (while
              (progn
                (setq pos (point))
                (org-forward-heading-same-level 1 t)
                (and (/= pos (point))
                     (not (string= when
                                   (org-element-property
                                    :raw-value
                                    (org-element-at-point))))))))
        (org-get-first-random-last count count count))))

And here is my agenda command (with some irrelevant settings cut out):

(defun mbork-agenda-short ()
  "My personal agenda command."
  (interactive)
  (let ((org-agenda-span 15)
        (org-agenda-use-time-grid nil))
    (org-agenda-run-series
     "Agenda, tasks, projects"
     `(((agenda "")
        (search ,(format "{%s}"
                         (replace-regexp-in-string
                          "[{}]" "."
                          (regexp-opt
                           (org-list-future-tasks "Soon" 2))))))))))

You might be wondering about the mysterious replace-regexp-in-string. As I started to use this code, I noticed that it errors out when one of the selected entries contains curly braces. The reason is not hard to find – Org requires the regex to be put within curly braces, and apparently it cannot contain them inside. Maybe they can be escaped somehow – I tried, but failed. So I decided to go the easy route and substitute periods for them, in the hope that making the regex more allowing won’t match too many headlines. (And even if it does, it is not a big problem – it just means that on some days, I will see a few headlines more, something I can definitely live with.)

It is still not perfect – for example, the heading for the six tasks below the agenda proper is the constructed regex instead of something more descriptive. Org mode could allow for a purely decorative entry in the block agenda so that I could make it nicer visually. That, however, is not a necessary thing, and I can definitely live without it.

So, from now on my agenda is richer. We’ll see how that helps me accomplish my goals!

CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode

Comments on this page

More...