For the English part of the blog, see Content AND Presentation.
A few years ago I wrote about a simple utility I wrote for myself to copy the “current location” in the project (that is, the filename and optionally a line number) to the system clipboard so that I can yank (paste) it in e.g. my company chat system (which I have to use outside Emacs). I’m using it all the time, and it’s very useful, but it’s still a bit limited.
Here’s one gripe I had with it. The full path is great when there is more than one file with the same name in the project. That happens, but not really often. I decided that only having the base name of the file (that is, the name without the directory part) would be more useful if that base name is unique throughout the project.
Getting the list of all files in the current project should be fairly easy – that’s what project-find-file
does. I jumped to project.el
to see if I can find the functions I need, and it turned out that indeed I can. (While searching for that, I realized that I need to finally read about Elisp’s generic functions to be able to better understand what is going on in the code that uses them.) After a few minutes of poking around I came up with this simple form: (project-files (project-current))
. What I needed next was to check if a given basename is unique in the project. This can be accomplished pretty easily, too – it is enough to use file-name-nondirectory
to strip the path from the names of all the files in the project and count the files with the given base name.
(defun copy-current-location (no-line-number) "Show the current location and put it into the kill ring. Here, \"location\" means the filename and line number (after a colon). Use the filename relative to the parent of the current VC root directory, so it starts with the main project dir. With \\[universal-argument], the line number is omitted." (interactive "P") (let* ((file-name (file-relative-name (or buffer-file-name dired-directory) (file-name-concat (vc-root-dir) ".."))) (file-base-name (file-name-nondirectory file-name)) (count (length (cl-remove-if-not (lambda (filename) (string= file-base-name (file-name-nondirectory filename))) (project-files (project-current))))) (line-number (line-number-at-pos nil t)) (location (format (if (or no-line-number (eq major-mode 'dired-mode)) "%s" "%s:%s") (if (= count 1) file-base-name file-name) line-number))) (kill-new location) (message location)))
I admit that it might be even better if instead of the full path for non-unique file names, only the shortest prefix making it unique would be added – but given how rarely non-unique filenames happen in my case, I think it’s not worth it to implement this.
After using this code for some time, I noticed it still has one flaw, though. The whole trick with only copying the base name does not work with directories. Also, in the (perhaps rare, but still possible) case where the project contains both a file and a directory with the same name, the full path is not used. Of course, this is because project-files
does not include directories in its output.
I lurked in project.el source for a bit more and found this nice trick:
(delete-dups (mapcar #'file-name-directory all-files))
where all-files
is the result of evaluating (project-files
(project-current))
.
It takes the list of all files in the project, takes the directory part of each of them, and removes duplicates (which will inevitably pop up for any directory containing more than one file). It is still not ideal. One issue is that it won’t find any empty directory. This is not really a problem, since empty directories are pretty rare nowadays – Git also skips them, so if someone really insists on having an “empty” directory in some Git-managed project, the convention is to put an empty files called .gitkeep
in it and commit it. A bigger problem is that if the project happens to have somewhere a file and a directory of the same name, they still will be treated differently, since file-name-directory
leaves the slash at the end (at least on GNU/Linux), and hence no full path will be used. The solution is to apply directory-file-name
, which (again, on GNU/Linux) just removes the trailing slash (if present). (I must mention here that I find the naming if these two functions a bit funny, although it is perfectly logical.) Since Emacs Lisp – a bit surprisingly – does not seem to have a general function composition operator, I decided to use a lambda:
(delete-dups (append all-files (mapcar (lambda (name) (directory-file-name (file-name-directory name))) all-files)))
Of course, I could use Magnar Sveen’s dash and its -compose function, too, but I preferred to avoid an external dependency here.
So, this this the final version of my code.
(defun copy-current-location (no-line-number) "Show the current location and put it into the kill ring. Here, \"location\" means the filename and line number (after a colon). Use the filename relative to the parent of the current VC root directory, so it starts with the main project dir. With \\[universal-argument], the line number is omitted." (interactive "P") (let* ((file-name (file-relative-name (or buffer-file-name dired-directory) (file-name-concat (vc-root-dir) ".."))) (file-base-name (file-name-nondirectory file-name)) (all-files (project-files (project-current))) (all-files-and-dirs (delete-dups (append all-files (mapcar (lambda (name) (directory-file-name (file-name-directory name))) all-files)))) (count (length (cl-remove-if-not (lambda (filename) (string= file-base-name (file-name-nondirectory filename))) all-files-and-dirs))) (line-number (line-number-at-pos nil t)) (location (format (if (or no-line-number (eq major-mode 'dired-mode)) "%s" "%s:%s") (if (= count 1) file-base-name file-name) line-number))) (kill-new location) (message location)))
As usual, let me finish with a mention of my book, Hacking your way around in Emacs, which is a short Elisp textbook meant as a “next step” after the great Introduction to programming in Emacs Lisp by the late Robert J. Chassell, which is an excellent and very gentle introduction to Emacs Lisp. If you’d like to learn to write little (and possibly even larger!) utilities like this one, both books may be a valuable resource – check them out! (Introduction to programming in Emacs Lisp is free and Hacking your way around in Emacs is reasonably priced, with the option of getting a refund if it turns out to be not for you.)
Happy hacking!
So, after ditching pomodoros and creating ketchup.el I noticed a problem with my workflow. Let me start with explaining what it looks like. Warning: a long-ish story follows.
When I’m at the office and I’m “in the flow”, it doesn’t bother me that instead of taking a break after 25 minutes, I work for, say, 35 minutes. But the situation is very different in the morning. For over 3 years now I’ve been using a system where I plan my morning very precisely – it is an extremely valuable time, since everyone else at home is sleeping and so I can focus. Also, my morning schedule is very repetitive now (which I consider a good thing). Of course, it’s all tied to Beeminder and my personal productivity system, so – for example – today (when I’m writing this) I have to weigh myself before 5:15, then spend 25 minutes writing before 5:45, then spend 25 minutes learning before 6:15, and then buy groceries before 6:45. This is all quite optimized – for example, the grocery store where I live opens at 6:00, but experience taught me that sometimes many customers come exactly then, so I have to wait in a line. By trial and error I found out that at about 6:15-6:25 there are usually fewer customers there, so I planned my morning around that fact to minimize time spent waiting in the line. After I’m back with the groceries, I commit the changes in my Org mode files to Git and run my backup script so that my files are safe, take a look at my Org agenda for the day and that’s it – when all this is done, my son needs to get up, eat his breakfast etc. in order to get to school on time, so the focused time is over. The next item in the schedule is leaving home with him until 7:25 – we have about 4 minutes to get to the bus stop and the bus to school leaves at about 7:30-7:32 (depending on the traffic), so we have a small buffer (and in fact, catching the next bus is also enough to get to school on time, so the buffer is actually even a bit larger). You get the idea – this is all very thoughtfully planned. And before you raise the objection that I’m making a robot out of an 8-year-old kid, let me assure you that I’m not – I’m also doing my best to stay flexible, so that if something unexpected happens (and it sometimes does) and we leave home at 7:35 instead of 7:25, that’s ok, too. The lesson for my boy is not “we have to stick to our schedule no matter what”, but rather “this schedule exists so that we don’t have to be nervous about getting to school on time” and “it has buffers built-in so that even if we leave 10 minutes later, we’ll still get there before the classes begin”. (Also, “a bit of play time is also included, even in the morning before school”, which teaches a great lesson – if you plan your time well, mornings can be also fun instead of just stressful!) In fact, this very rigid structure does not make me feel like a mindless robot following orders even if it’s followed to a minute. Quite the contrary – it gives me very nice feeling of satisfaction and a huge motivation boost. Looking past my morning at about 6:15 every morning and realizing that I spent almost an hour working on projects I find personally valuable (95% of the time, I work on my personal things in the mornings) feels great. Before I implemented this system, it sometimes happened that I managed to get up early, all pumped up about how the morning is going to be productive, and then wasted half of it reading emails or thinking what I should do next. That was very frustrating! Now I hardly ever need to make these decisions – they are already made the day before, so I don’t suffer from analysis paralysis. Also, instead of a giant, terrifying deadline for some project looming a few weeks (or months) from now, I have a small and friendly deadline every morning. (“Friendly” here means that if I occasionally miss it by getting up later or just being lazy and not doing what I have planned, it doesn’t really matter much – my projects move on every week anyway, so not working on them for one day every few weeks is nothing bad.) This way I get all the good things deadlines bring (a feeling of motivation and urgency) without the bad things (serious consequences of missing them). What’s not to like?
Ok, back on topic. Can you see where the problem is? Now that I don’t use tomato.el anymore, I don’t get the notification that 25 minutes have passed since I started working. But that is crucial in the morning, since then I don’t have the luxury of working longer than these 25 minutes. On the other hand, this is explicitly what I don’t want when I’m at the office!
My first thought was to use the Org timer and start it manually when I start working in the 25-minute slot in the morning. I added this lambda to my Org-related hydra:
(lambda () (interactive) (when org-timer-start-time (org-timer-stop)) (org-timer-set-timer 25))
(I probably should have made it a named function – I don’t usually like interactive lambdas, but I find them acceptable in places like hydras.) It’s a simple wrapper around Org timer, always setting it to 25 minutes. It’s far from ideal, though. When a timer is already running, it just cancels it and says “Timer stopped”. What if I have a timer (for some reason) and don’t want to cancel it? And even if not, I don’t like polluting the echo area and the *Messages*
buffer. The most important problem, however, is that I need to remember to use it.
Of course, then it hit me. This is Emacs, folks! It can do whatever I want! Why not retain the notification after 25 minutes, but only, say, when starting clocking on a ketchup-enabled goal between 4:00 and 8:00? (I very rarely start working before 5:00, but sometimes I do, so let’s have a nice buffer even if for some reason I start working really early. Also, at 8:00 I’m usually on my way to the office, so it seems a nice cut-off moment between the “work in shorter bursts before actual work” and “more time do do focused stuff at work”.) Of course, I don’t want to make this part of ketchup.el
, it’s extremely specific to my personal workflow, and ketchup.el
might be useful for someone else. How do I do that? I could reuse the Org timer (again), with its drawbacks I mentioned, or I can set up a generic Emacs timer to do what I want (like I did with tomato.el). At first, I wanted to go with the Org timer because it’s simple and the probability that I use it for anything else early in the morning is extremely low. Then I took a look at org-timer.el
and org-clock.el
and decided that run-with-timer
is simple enough that I would use it.
My code here is very simplistic and proof-of-concept-ish. For example, I decided against turning the feature into a minor mode. As I said, this is something that I don’t expect anyone but me to use, and I don’t expect myself to ever want to turn it off, so there’s no point. The code only supports starting and ending the “morning” at full hours (4 and 8 in my case). On the other hand, I decided not to hardcode these hours and make them into a defcustom
– if I ever want to change these hours, I won’t have to look for them in the code, they’ll be easy to find.
(defcustom ketchup-morning-morning '(4 . 7) "Hours demarcating the morning, as a cons.") (defvar ketchup-morning-work-stretch (* ketchup-stretch-duration ketchup-stretch-step)) (defvar ketchup-morning--timer) (defun ketchup-morning-run-timer () "Run a 25 minutes timer, but only between in the morning." (when (and (org-entry-get (point) "ketchup" t) (<= (car ketchup-morning-morning) (decoded-time-hour (decode-time (current-time))) (cdr ketchup-morning-morning))) (setq ketchup-morning--timer (run-with-timer (* ketchup-morning-work-stretch 60) nil #'org-notify (format "%s minutes have passed" ketchup-morning-work-stretch) org-clock-sound)))) (defun ketchup-morning-cancel-timer () "Cancel the morning ketchup timer" (when (timerp ketchup-morning--timer) (cancel-timer ketchup-morning--timer))) (add-hook 'org-clock-in-hook #'ketchup-morning-run-timer) (add-hook 'org-clock-out-hook #'ketchup-morning-cancel-timer) (add-hook 'org-clock-cancel-hook #'ketchup-morning-cancel-timer)
As you can see, nothing really mysterious happens here. As usual, if you think you’d like to be able to code little sweet utilities like this for yourself, I highly recommend two sources – the fantastic book Introduction to programming in Emacs Lisp by the late Robert J. Chassell, which will teach you the basics of Emacs Lisp, and my ebook Hacking your way around in Emacs, which covers some more advanced stuff (and comes with a 60-days refund policy in case you are disappointed with it).
Have fun using Emacs and automating your workflow!
CategoryEnglish, CategoryBlog, CategoryEmacs, CategoryOrgMode
I have a script which performs several operations on some remote server, issuing a number of ssh
commands, more or less like this:
scp project.zip scp://some_server/~ ssh ssh://some_server rm -rf ~/project-old ssh ssh://some_server cp -r ~/project ~/project-old ssh ssh://some_server unzip -o ~/project.zip -d ~/project
This obviously works, but is not very optimized. One obvious bottleneck is that every ssh
command needs to create a connection to the remote server, which takes some time. What if it were possible to open the connection once and then reuse it whenever the need arises?
Well, it turns out that not only is this possible, but much easier than I thought. In fact, in the simplest case you don’t even have to change the script at all! Just add something like this to the ~/.ssh/config
file:
Host some_server HostName ... User ... IdentityFile ... ControlMaster auto ControlPath ~/.ssh/%r@%h:%p ControlPersist 1m
This way, the first ssh
command will create a connection and each subsequent command will reuse it. The connection will close automatically after 60 seconds of inactivity. How cool is that? (In the case of my script, I was able to gain a speedup of about 50%!)
I am not 100% sure about the security implications of this solution, though – I don’t see any obvious security holes, but I’m not an expert on that. One thing that might look dangerous is the fact that the connection remains open for one minute after the script is done. If you want to explicitly close it instead of waiting, just say ssh -O
exit some_server
.