Readers may have noticed that I’ve not been updating the site. This is for two main reasons: primarily I’ve just got too busy with other things, but also my Emacs setup has settled down and I’m not finding time to tweak it so I don’t have much to write about. I want to keep the site alive as a (hopefully) useful archive of information for the community, but I’ve moved it to a free hosting platform, so you will notice that my URL http://pragmaticemacs.com now redirects to https://pragmaticemacs.wordpress.com. I hope people continue to find the site useful!
I recently discovered a nice package called poporg that allows you to pop open a temporary org-mode window when you are editing code comments so you can write them using nice org-mode formatting. When you are done, you close the window and go back to your code with the newly edited comment. This is a bit antithetical to embedding source code in org-mode files to document the code that way, but in fact I find I do a bit of both and still like to have comments in my code, so I really appreciate being able to format them nicely.
(use-package poporg :bind (("C-c /" . poporg-dwim)))
With this, hitting
C-c / in your source code in or near a comment will open the org-mode window and the same key combo will close it again.
I use org-mode to manage my to-do list with priorities and deadlines but inevitably I have multiple items without a specific deadline or scheduled date and that have the same priority. These appear in my agenda in the order in which they were added to my to-do list, but I’ll sometimes want to change that order. This can be done temporarily using
M-DOWN in the agenda view, but these changes are lost when the agenda is refreshed.
I came up with a two-part solution to this. The main part is a generic function to move the subtree at the current point to be the top item of all subtrees of the same level. Here is the function:
(defun bjm/org-headline-to-top () "Move the current org headline to the top of its section" (interactive) ;; check if we are at the top level (let ((lvl (org-current-level))) (cond ;; above all headlines so nothing to do ((not lvl) (message "No headline to move")) ((= lvl 1) ;; if at top level move current tree to go above first headline (org-cut-subtree) (beginning-of-buffer) ;; test if point is now at the first headline and if not then ;; move to the first headline (unless (looking-at-p "*") (org-next-visible-heading 1)) (org-paste-subtree)) ((> lvl 1) ;; if not at top level then get position of headline level above ;; current section and refile to that position. Inspired by ;; https://gist.github.com/alphapapa/2cd1f1fc6accff01fec06946844ef5a5 (let* ((org-reverse-note-order t) (pos (save-excursion (outline-up-heading 1) (point))) (filename (buffer-file-name)) (rfloc (list nil filename nil pos))) (org-refile nil nil rfloc))))))
This will move any to-do item to the top of all of the items at the same level as that item. This is equivalent to putting the cursor on the headline you want to move and hitting
M-UP until you reach the top of the section.
Now I want to be able to run this from the agenda-view, which is accomplished with the following function, which I then bind to the key
1 in the agenda view.
(defun bjm/org-agenda-item-to-top () "Move the current agenda item to the top of the subtree in its file" (interactive) ;; save buffers to preserve agenda (org-save-all-org-buffers) ;; switch to buffer for current agenda item (org-agenda-switch-to) ;; move item to top (bjm/org-headline-to-top) ;; go back to agenda view (switch-to-buffer (other-buffer (current-buffer) 1)) ;; refresh agenda (org-agenda-redo) ) ;; bind to key 1 (define-key org-agenda-mode-map (kbd "1") 'bjm/org-agenda-item-to-top)
Now in my agenda view, I just hit
1 on a particular item and it is moved permanently to the top of its level (with deadlines and priorities still taking precedence in the final sorting order).
There are several ways of running a shell inside Emacs. I don’t find that I need to use it very often as I do so much within Emacs these days, but when I do it’s handy to quickly bring up the shell, run a command the then dismiss it again. The shell-pop package does this very smartly. One key combo (I use
C-t) pops up a shell window for the directory containing the file you are currently editing, and then
C-t dismisses the shell window when you are done.
The github page has lots of details on how to configure it, and I use a fairly minimal setup and use the
ansi-term terminal emulator and
zsh as my shell. Here is my configuration:
(use-package shell-pop :bind (("C-t" . shell-pop)) :config (setq shell-pop-shell-type (quote ("ansi-term" "*ansi-term*" (lambda nil (ansi-term shell-pop-term-shell))))) (setq shell-pop-term-shell "/bin/zsh") ;; need to do this manually or not picked up by `shell-pop' (shell-pop--set-shell-type 'shell-pop-shell-type shell-pop-shell-type))
The last line is needed but I can’t remember where I got it from!
Once I started tweaking pdf-tools I couldn’t resist a few more bits of streamlining my workflow. I wanted to be able to commit an annotation by hitting
RET rather than
C-c C-c and then use
RET to actually enter a newline, a bit like in many messaging apps. The problem was that the necessary commands are in
pdf-annot-edit-contents-minor-mode-map which is a key map that is not available until that minor mode is loaded. This causes an error if you try to define a key in that map in the same way as I did for the keys to add annotations. This is remedied by wrapping the definitions in
with-eval-after-load which waits to execute the code until the file that defines the
pdf-annot mode is loaded.
The other thing I wanted to do was have the pdf file auto-save every time I commit an annotation. I did this by advising the function
pdf-annot-edit-contents-commit to run
save-buffer afterwards. This also had to be inside
with-eval-after-load as the
pdf-annot-edit-contents-commit function is not available to be advised until the file defining the
pdf-annot mode is loaded. The only other issue I had was that when the advice to
save-buffer, it passed its argument (the text of the annotation) to
save-buffer which then failed. I hacked around this by writing a simple wrapper to save buffer that ignores any arguments. There is probably a better way to do this!
Putting it all together with my previous tweaks, my
pdf-tools config looks like this:
;; wrapper for save-buffer ignoring arguments (defun bjm/save-buffer-no-args () "Save buffer ignoring arguments" (save-buffer)) (use-package pdf-tools :pin manual ;;manually update :config ;; initialise (pdf-tools-install) (setq-default pdf-view-display-size 'fit-page) ;; automatically annotate highlights (setq pdf-annot-activate-created-annotations t) ;; use isearch instead of swiper (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward) ;; turn off cua so copy works (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0))) ;; more fine-grained zooming (setq pdf-view-resize-factor 1.1) ;; keyboard shortcuts (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation) (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation) (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete) ;; wait until map is available (with-eval-after-load "pdf-annot" (define-key pdf-annot-edit-contents-minor-mode-map (kbd "<return>") 'pdf-annot-edit-contents-commit) (define-key pdf-annot-edit-contents-minor-mode-map (kbd "<S-return>") 'newline) ;; save after adding comment (advice-add 'pdf-annot-edit-contents-commit :after 'bjm/save-buffer-no-args)))
I’ve added a couple more tweaks to my pdf-tools configuration.
First, I’ve found that CUA mode interferes with the ability to copy text from the pdf, so let’s turn it off in
;; turn off cua so copy works (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0)))
Next, I want more fine grained zooming with
- than the default 25%, so I’ll set it to 10%
;; more fine-grained zooming (setq pdf-view-resize-factor 1.1)
Finally, for my most commonly used annotation tools (adding a highlight, adding a text note and deleting an annotation), I want quicker shortcuts. With the following I just hit
D respectively for those tools (instead of e.g.
C-c C-a h to highlight).
;; keyboard shortcuts (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation) (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation) (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete)
Putting it all together, my current setup looks like this:
(use-package pdf-tools :pin manual ;; manually update :config ;; initialise (pdf-tools-install) ;; open pdfs scaled to fit page (setq-default pdf-view-display-size 'fit-page) ;; automatically annotate highlights (setq pdf-annot-activate-created-annotations t) ;; use normal isearch (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward) ;; turn off cua so copy works (add-hook 'pdf-view-mode-hook (lambda () (cua-mode 0))) ;; more fine-grained zooming (setq pdf-view-resize-factor 1.1) ;; keyboard shortcuts (define-key pdf-view-mode-map (kbd "h") 'pdf-annot-add-highlight-markup-annotation) (define-key pdf-view-mode-map (kbd "t") 'pdf-annot-add-text-annotation) (define-key pdf-view-mode-map (kbd "D") 'pdf-annot-delete))
The pdf-tools packages allows you to read and annotate PDF documents in Emacs. The installation process is described on the github page, but on my Mac, I needed to install
poppler (I used macports) and add
to my zshrc. Then after installing the package in Emacs with the following code
(use-package pdf-tools :pin manual ;; manually update :config ;; initialise (pdf-tools-install) ;; open pdfs scaled to fit page (setq-default pdf-view-display-size 'fit-page) ;; automatically annotate highlights (setq pdf-annot-activate-created-annotations t) ;; use normal isearch (define-key pdf-view-mode-map (kbd "C-s") 'isearch-forward))
M-x pdf-tools-install, ignored an error message about epdfinfo and then restarted emacs, and all was well.
I use the
:pin manual option in
use-package to stop
pdf-tools being automatically updated when I update the rest of my packages, since it would need the installation command and restart each time it updated.
There are lots of nice features in
pdf-tools but I’ll just mention the ones I use most often. You can search pdf files like normal buffers, but the enhanced search tool swiper doesn’t work with
pdf-tools so I set
C-s to call the normal
isearch when in this mode.
You can also make annotations in
pdf-tools and I set my configuration above so that when an annotation (like highlighting some text) is created, a buffer opens prompting for a text note to go with the annotation. So for example, to highlight a comment on some text I
- Select the text in the pdf (mouse required for this unfortunately)
C-c C-a hto highlight it yellow
- Type some notes in the annotation buffer that pops up and use
C-c C-cto complete the annotation.
Other useful annotations are
C-c C-a t and then mouse click to add a text note somewhere to the pdf page,
C-c C-a o to strike-through text, and
C-c C-a D and then click to delete an annotation.
Saving the pdf buffer as normal saves all the annotations, and they will be readable in any PDF viewer so this works well when collaborating with the unenlightened!
Here’s a tiny and basic tip. If you want you Emacs to flash at you instead of beeping for an error, add the following to your emacs config file
;; turn on visible bell (setq visible-bell t)
By default, Emacs’ file browser/manager dired usually presents you with a flat list of files in a given directory. Entering a subdirectory then opens a new buffer with the listing of the subdirectory. Sometimes you might want to be able to see the contents of the subdirectory and the current directory in the same view. Many GUI file browsers visualise this with a tree structure with nodes that can be expanded or collapsed. In Emacs there is a built-in function
dired-insert-subdir that inserts a listing of the subdirectory under the cursor, at the bottom of the current buffer instead of in a new buffer, but I’ve never found that very helpful.
The dired-subtree package (part of the magnificent dired hacks) improves on this by allowing you to expand subdirectories in place, like a tree structure. To install the package, use the following code:
(use-package dired-subtree :config (bind-keys :map dired-mode-map ("i" . dired-subtree-insert) (";" . dired-subtree-remove)))
This sets up the keybinds so that in dired, hitting
i on a subdirectory expands it in place with an indented listing. You can expand sub-subdirectories in the same way, and so on. Hitting
; inside an expanded subdirectory collapses it.
Add the following to your emacs config file to make the cursor the full width of the character it is under – e.g. the full width of a TAB character.
;; make cursor the width of the character it is under ;; i.e. full width of a TAB (setq x-stretch-cursor t)