Back to the index

Emacs Dotfile

Table of Contents

Emacs configuration

Usage

To use this configuration add the following lines to init.el

This will compile the file to emacs.el and load it

; (setq max/note-directory "c:/Dropbox/notes/")
; (setq max/app-directory "c:/Dropbox/apps/")
; (setq max/beeminder-username "blablabla")
; (setq max/beeminder-auth-token "blaX10322bla")

;; the strange extension is necessary, otherwise tangle doesn't like it, not checked why
; (org-babel-load-file (concat max/note-directory "zetl/Emacs Dotfile.org.txt"))

Startup Speed

First, set a hook to tell us the startup speed, so we know what we're working with. To speed things up, make the GC threshold nice and high, so prevent pauses. We reset it to 2MB at the end of the init file, to ensure we don't get infrequent but very long pauses in normal usage.

;; Use a hook so the message doesn't get clobbered by other messages.
(add-hook 'emacs-startup-hook
          (lambda ()
            (message "Emacs ready in %s with %d garbage collections."
                     (format "%.2f seconds"
                             (float-time
                              (time-subtract after-init-time before-init-time)))
                     gcs-done)
            (setq gc-cons-threshold (* 2 1000 1000))))
(setq gc-cons-threshold (* 100 1000 1000))

If we want to go further in future, we can benchmark the startup: https://github.com/jschaf/esup

Startup maximized

(add-to-list 'default-frame-alist '(fullscreen . maximized))

Set sensible defaults

;; some defaults from https://sam217pa.github.io/2016/09/02/how-to-build-your-own-spacemacs/ while we get things setup
;; revisit when we have sane, healthy, vi keys
(setq delete-old-versions -1 )          ; delete excess backup versions silently
(setq version-control t )               ; use version control
(setq vc-make-backup-files t )          ; make backups file even when in version controlled dir
(setq vc-follow-symlinks t )                                   ; don't ask for confirmation when opening symlinked file
(setq backup-directory-alist `(("." . "~/.emacs.d/backups")) ) ; which directory to put backups file
(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-saves/" t)) ) ;transform backups file name
(setq inhibit-startup-screen t )        ; inhibit useless and old-school startup screen
(setq ring-bell-function 'ignore )      ; silent bell when you make a mistake
(setq coding-system-for-read 'utf-8 )   ; use utf-8 by default
(setq coding-system-for-write 'utf-8 )
(setq sentence-end-double-space nil)    ; sentence SHOULD end with only a point.
(setq default-fill-column 80)           ; toggle wrapping text at the 80th character
(show-paren-mode 1)

Try and reload files from disk if they have changed (checks every auto-revert-interval). This is primarily to pick up changes from files opened from dropbox.

(global-auto-revert-mode t)

Hide the menus, set the frame title to the current project name

(tool-bar-mode 0)
(menu-bar-mode 0)
(scroll-bar-mode 0)
(setq frame-title-format '((:eval (projectile-project-name))))

Set up use-package

Set up use-package to make it easier to install packages and manage their configuration

; path where we will load .el files not available through melpa
(add-to-list 'load-path (concat max/app-directory "lisp"))

  (require 'package)
  (setq package-archives '(("org"       . "http://orgmode.org/elpa/")
                           ("gnu"       . "http://elpa.gnu.org/packages/")
                           ("melpa"     . "https://melpa.org/packages/")))

  (package-initialize)

  ;; Bootstrap `use-package'
  (unless (package-installed-p 'use-package) ; unless it is already installed
    (package-refresh-contents) ; updage packages archive
    (package-install 'use-package)) ; and install the most recent version of use-package

  (require 'use-package)

Always install packages automaticlly if they are not found, to make it easier when we set up a new emacs install

(require 'use-package-ensure)
(setq use-package-always-ensure t)

If packages are unable to install you might need to run the below, to update the repos

; (package-refresh-contents)

Which Key

Show the available completions in the minibuffer 1sec after hitting a prefix key (SPC-o, comma, etc.)

(use-package which-key
  :config
  (which-key-mode))

Evil General

(use-package general)

(use-package evil
:init
  (setq evil-want-keybinding nil) ; required by evil-collection
  :config
  (setq-default evil-symbol-word-search t) ; handle elisp functions etc. as expected
  (evil-mode 1)) ;; ahhhhhh, better

(use-package evil-escape
  :config
  (setq-default evil-escape-key-sequence "wk")
  (setq-default evil-escape-delay 0.2)
  (evil-escape-mode 1))

Theme and fonts

(use-package spacemacs-theme
  :no-require t
  :config
  (load-theme 'spacemacs-light t) ; the t is for NO-CONFIRM

  ;; default fonts
  (set-face-attribute 'default nil :family "Consolas" :height 105 :foreground "#332222")
  (set-face-attribute 'fixed-pitch nil :family "Consolas" :height 105 :foreground "#332222")
  (set-face-attribute 'variable-pitch nil :family "Verdana" :height 105)
  (set-face-attribute 'font-lock-comment-face nil :background nil :foreground "#66AA66")

  ;; Chinese fonts
  (set-fontset-font "fontset-default" 'han (font-spec :family "SimSun" :size 16))

  (set-face-attribute 'org-agenda-done nil :weight 'normal)
  (set-face-attribute 'org-block nil :background "#F2F2F2")
  ; (set-face-attribute 'org-hide nil :foreground "#FF0000" :height 1.5) ; this does not work, why? trying to add padding to headings

  ;; make the block delimiters less obvious
  (set-face-attribute 'org-block-begin-line nil :height 0.8 :slant 'normal :background nil :underline nil)
  (set-face-attribute 'org-block-end-line nil :height 0.8 :slant 'normal :background nil)

  ;; make the current time line more obvious in the agenda
  (set-face-attribute 'org-agenda-current-time nil :background "#DDDDDD" :foreground "#FFFFFF")

  ;; pretty lambdas
  (global-prettify-symbols-mode t) 
  )

Use non-monospace fonts for markdown and org mode, this defun will be used in hooks for those modes

;; use non-monospace for markdown and org files
(defun set-buffer-variable-pitch ()
  (interactive)
  (variable-pitch-mode t)
  (visual-line-mode)
  (setq-default line-spacing 3)
  (set-face-attribute 'org-table nil :inherit 'fixed-pitch) ; TODO can move these into the theme config?
  (set-face-attribute 'org-code nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-block nil :inherit 'fixed-pitch))

Try and ensure that the org-indent-mode padding works in non-fixed-pitch mode (without this, the indentation will be off)

(require 'org-indent)   
(set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))

Add some padding to the headings (level 1-3) (Could refactor this) Tried to do this by changing line-height of org-hide but it changed the line height of the entire buffer somehow. Currentl doing it by drawing a box the same color as the bacground around each heading

(add-hook
 'org-mode-hook
 '(lambda ()
    (progn
      (set-face-attribute 'org-level-1 nil :box `(:line-width 5 :color ,(face-attribute 'default :background) :style nil))
      (set-face-attribute 'org-level-2 nil :box `(:line-width 5 :color ,(face-attribute 'default :background) :style nil)))))

Modeline

(column-number-mode 1)
(setq-default
 mode-line-format
 '("%e "
   mode-line-buffer-identification
   ; "%f " ; full file name
   "%* "
   mode-line-position
   evil-mode-line-tag
   "      "
   mode-line-misc-info))

Helm

This makes it easier to work with my zetl. A common task is inserting a link to a different file. The below function creates a relative link (to the current file) that will open when hitting RET, and set the name of the link to the filename (with the extension removed).

For example, inserting a link to zetl/Animals.txt to zetl/Chicken.txt would result in a link to file:Chicken.txt named 'Chicken'. If the file were ~resources/ChickenCalls.txt the link would be file:../resources/ChickenCalls.txt

The links are relative to ensure they work regardless of the surrounding folder structure.

; try and guess the name of the link from the file
  (defun max/helm-files-insert-as-org-link-guessname (candidate)
    (insert (format " [[file:%s][%s]]" 
                    (file-relative-name candidate (file-name-directory (buffer-file-name))) 
                    (file-name-sans-extension (file-name-nondirectory candidate)))))

  (defun max/helm-ff-run-insert-org-link-guessname ()
    (interactive)
    (with-helm-alive-p
      (helm-exit-and-execute-action 'max/helm-files-insert-as-org-link-guessname)))

Use helm everwhere. Use helm-org-rifle to navigate to a heading. Use helm-ag to find files

(use-package helm
  :config
  (require 'helm-config)
  (setq helm-mode-fuzzy-match t)
  (setq helm-M-x-fuzzy-match t)
  (helm-mode 1))

  ; to search within org files
(use-package helm-ag
:config
(setq helm-ag-base-command (concat max/app-directory "ag.exe --vimgrep --ignore-case")))

(use-package helm-org-rifle) ; to search for headings

Use helm-flx to improve fuzzy matching

(use-package helm-flx
  :after helm
  :config
  (helm-flx-mode nil))

Projectile

Use projectile and helm-projectile to find files in projects

(use-package projectile)

(use-package helm-projectile
  :config
  ;; we need to tell helm this action will work in the projectile files list source
  (helm-add-action-to-source "Insert org link (guess name)" 'max/helm-files-insert-as-org-link-guessname helm-source-projectile-files-list)

  ;; helm specific
  (general-define-key
   :keymaps 'helm-map
   ;; flip persistent with select action (use TAB to view docs, peek at file)
   [escape] 'helm-keyboard-quit
   "<tab>" 'helm-execute-persistent-action
   "C-z" 'helm-select-action
   "C-l" 'max/helm-ff-run-insert-org-link-guessname
   "C-i" 'helm-copy-to-buffer
   )

  (setq projectile-require-project-root nil)
  (setq projectile-mode-line-function '(lambda () (format " Proj[%s]" (projectile-project-name))))
  (setq projectile-indexing-method 'alien)
  (setq projectile-enable-caching t) ; can feel the speed difference with this off
  (setq projectile-files-cache-expire (* 60 60)) ; expire every hour, ensure it picks up new files from dropbox etc.
  ; (setq projectile-generic-command "fd . -0")
  (setq-default projectile-generic-command (concat max/app-directory "fd.exe . -0"))
  (projectile-mode)
  (helm-projectile-on)
  )

Markdown Mode

(use-package markdown-mode
  :hook (markdown-mode . set-buffer-variable-pitch)
)

Elixir Mode

(use-package elixir-mode)

Spell Check

  (use-package ispell
    :config
    (setenv "LANG" "en_GB.UTF-8")
    (setq ispell-program-name (concat max/app-directory "hunspell/bin/hunspell.exe"))
    (setq ispell-dictionary "en_GB")
    (setq ispell-really-hunspell t) ; allow it to use hunspell extensions
    (setq flyspell-issue-message-flag nil) ; do not message for errors in a buffer, speedup (possibly) 

    (dolist (hook '(text-mode-hook org-mode-hook markdown-mode-hook))
      (add-hook hook (lambda () (flyspell-mode 1))))

    (defun flyspell-check-next-highlighted-word ()
      "Custom function to spell check next highlighted word"
      (interactive)
      (flyspell-goto-next-error)
      (ispell-word))
)

Move lines around

(defun move-line-up ()
  "Move up the current line."
  (interactive)
  (transpose-lines 1)
  (forward-line -2)
  (indent-according-to-mode))

(defun move-line-down ()
  "Move down the current line."
  (interactive)
  (forward-line 1)
  (transpose-lines 1)
  (forward-line -1)
  (indent-according-to-mode))

  (general-define-key
   :states '(normal visual motion insert emacs)
   "C-S-<up>" 'move-line-up
   "C-S-<down>" 'move-line-down)

Rename File and Buffer

From Steve Yegge https://sites.google.com/site/steveyegge2/my-dot-emacs-file

(defun rename-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME." (interactive "sNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not filename)
        (message "Buffer '%s' is not visiting a file!" name)
      (if (get-buffer new-name)
          (message "A buffer named '%s' already exists!" new-name)
        (progn
          (rename-file filename new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil))))))

Surround

(use-package evil-surround
:config
  (global-evil-surround-mode 1))

Dos2unix

(defun dos2unix ()
  "Replace DOS eolns CR LF with Unix eolns CR"
  (interactive)
    (goto-char (point-min))
      (while (search-forward "\r" nil t) (replace-match "")))

Expand Region

(use-package expand-region) 

The Rest

  ;; TODO: install https://github.com/jacktasia/dumb-jump#supported-languages
  ;; TODO looks cool https://www.emacswiki.org/emacs/UndoTree


  (custom-set-faces
   ;; custom-set-faces was added by Custom.
   ;; If you edit it by hand, you could mess it up, so be careful.
   ;; Your init file should contain only one such instance.
   ;; If there is more than one, they won't work right.
   '(fixed-pitch ((t (:family "Consolas"))))
   '(markdown-code-face ((t (:inherit fixed-pitch :background "gray90" :family "Consolas"))))
   '(org-agenda-done ((t (:weight normal :height 1.0))))
   '(org-level-1 ((t (:foreground "#3a81c3" :weight normal :height 1.5))))
   '(org-level-2 ((t (:foreground "#3a81c3" :weight normal :height 1.2))))
   '(org-level-3 ((t (:foreground "#3a81c3" :weight normal :height 1.0))))
   '(org-level-4 ((t (:foreground "#3a81c3" :weight normal :height 1.0))))
   '(org-scheduled-today ((t (:weight normal :height 1.0))))
   '(variable-pitch ((t (:family "Verdana")))))

(defun unfill-paragraph ()
  (interactive)
  (let ((fill-column (point-max)))
    (fill-paragraph nil)))

(defun unfill-region (start end)
  (interactive "r")
  (let ((fill-column (point-max)))
    (fill-region start end)))

Shackle

Make popup windows do what I want them to do

(use-package shackle
   :config
   (setq shackle-rules
   '((" *Org todo*" :align below :select t)
   ("\\*.*Help\\*" :regexp t :align below :size 0.33 :select t)))
   (shackle-mode t)
)

Org mode

Functions

(defun org-agenda-show-home (&optional arg)
  (interactive "P")
  (org-agenda arg "h"))
(defun org-agenda-show-work (&optional arg)
  (interactive "P")
  (org-agenda arg "w"))
(defun org-agenda-show-backlog (&optional arg)
  (interactive "P")
  (org-agenda arg "b"))

;; archive all done tasks in the subtree
(defun org-archive-done-tasks ()
  (interactive)
  (org-map-entries
   (lambda ()
     (org-archive-subtree)
     (setq org-map-continue-from (outline-previous-heading)))
   "TODO=\"DONE\"|TODO=\"KILL\"" 'tree))

;; stop org mode opening new frames whenever neotree is open, thank god for this
;; https://github.com/Alexander-Miller/treemacs/issues/121
;; without this, shackle will not work
(defun org-switch-to-buffer-other-window (&rest args)
  "Same as the original, but lacking the wrapping call to `org-no-popups'"
  (apply 'switch-to-buffer-other-window args))

Settings

(setq-default org-default-notes-file (concat max/note-directory "todo/inbox.org"))
(setq-default org-todo-keywords (quote ((sequence "WAIT(w)" "PROJ(p)") (sequence "FOCUS(f)") (sequence "TODO(t)" "|" "DONE(d)" "KILL(k)"))))
(setq-default org-hide-emphasis-markers t) ;; hide ** __ // markers
(setq org-startup-indented t) ;; hide-stars and indent text to the level of the heading
(setq org-odd-level-only nil)
;; When hitting alt-return on a header, please create a new one without messing up the one I'm standing on.
(setq-default org-insert-heading-respect-content t)
(setq org-refile-allow-creating-parent-nodes 'confirm)
(setq org-return-follows-link t)
(setq org-fontify-quote-and-verse-blocks t) ; allow us to change font of quotes
(setq org-refile-targets '((org-agenda-files :maxlevel . 5)))
(setq org-refile-use-outline-path t)
(setq org-startup-folded nil)
(setq org-log-repeat nil) ;; don't log a time when marking a repeating task as DONE, only one-offs
(setq-default org-catch-invisible-edits 'smart)
(setq org-log-done 'time) ; save a closed date on done items
(setq org-agenda-start-on-weekday nil) ;; start from the current day, not Monday
(setq org-yank-adjusted-subtrees t) ; should indent subtrees correctly when pasting
(setq org-outline-path-complete-in-steps nil)
;; (setq org-archive-location "archive.org_archive::* From %s") ; archive everything in a single file
(setq org-archive-location "archive.org_archive::datetree/") ; archive everything in a date-tree in a single file
;; this next one is not working for me, it creates a new header under the current subtree, even if there is an existing top-level header it could use
; (setq org-archive-location "::* Archive") ; archive in the current file in a new header
(setq-default org-agenda-window-setup 'current-window)
(setq-default org-agenda-files (list (concat max/note-directory "todo/")))
(add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode))
(advice-add 'org-agenda-quit :before 'org-save-all-org-buffers)

Replace Templates

(add-to-list 'org-structure-template-alist '("sh" "#+BEGIN_SRC sh\n?\n#+END_SRC"))
(add-to-list 'org-structure-template-alist '("sql" "#+BEGIN_SRC sql\n?\n#+END_SRC"))
(add-to-list 'org-structure-template-alist '("el" "#+BEGIN_SRC emacs-lisp\n?\n#+END_SRC"))
(add-to-list 'org-structure-template-alist '("t" "#+FILETAGS: ?"))

Agenda commands

(setq org-agenda-custom-commands
      '(("h" "Home"
         ((agenda "" ((org-agenda-span 1) (org-agenda-show-log t) (org-agenda-overriding-header "Today")))
          (todo "TODO" (
                        (org-agenda-files `(,(concat max/note-directory "todo/inbox.org") ,(concat max/note-directory "todo/phone.org")))
                        (org-agenda-overriding-header "Inbox")))
                                        ;(tags "priorities" (
                                        ;                   (org-agenda-entry-text-mode t)
                                        ;                   (org-agenda-overriding-header "")))
                                        ; this does not work currently, do not seem to be able to set entry-text-mode for a single section
          (todo "FOCUS|PROJ" (
                              (org-agenda-overriding-header "Projects")))
          (todo "WAIT" (
                        (org-agenda-overriding-header "Pending Projects")))


                                        ;(todo "NEXT" ((org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled)) (org-agenda-overriding-header "Unscheduled project next-tasks")))
                                        ;(stuck "" ((org-agenda-overriding-header "Stuck projects and unprocessed inbox items")))
          (agenda "" ((org-agenda-span 14) (org-agenda-overriding-header "Next 2 weeks (except habits) f/b to change weeks")  (org-agenda-start-day "+1d") (org-agenda-show-future-repeats 'next))))
         ((org-agenda-tag-filter-preset '("-WORK")))
         )
        ("w" "Work"
         ((agenda "" ((org-agenda-span 1) (org-agenda-show-log t) (org-agenda-overriding-header "Today")))
                                        ; (todo "NEXT" ((org-agenda-overriding-header "Work next-tasks")))
          (todo "" ((org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled)) (org-agenda-overriding-header "Unscheduled TODOs")))
          (agenda "" ((org-agenda-span 5) (org-agenda-overriding-header "Upcoming") (org-agenda-start-day "+1d"))))
         ((org-agenda-tag-filter-preset '("+WORK")))
         )
        ("b" "Backlog"
         ((todo "TODO" (
                        (org-agenda-files `(,(concat max/note-directory "todo/home.org")))
                        (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled))
                        (org-agenda-overriding-header "Unscheduled Home Tasks")))))
        ))

Capture Templates

 (setq org-capture-templates
       `(("i" "Inbox" entry (file org-default-notes-file)
          "* TODO %?\n")
         ("t" "Todo" entry (file+olp ,(concat max/note-directory "todo/home.org") "Home" "One-off")
          "*** TODO %?\n")
         ("w" "Work Todo" entry (file+olp ,(concat max/note-directory "todo/work.org") "TRP" "One-off")
          "*** TODO %?\n")
         ("c" "Weekly checkup" entry (file+headline ,(concat max/note-directory "todo/home.org") "Project Checkups")
          "** TODO Weekly checkup %t
*Did you work toward your foci this week?*
%?

*Should any foci change?*

*Check home backlog (SPC o b) and schedule tasks*

*Check there are at most 3 open projects*

*Cleanup each open project, schedule tasks*

*Check calendar*

*<question from last week>*

*A question to be answered by myself next week*"

          )))

Putting it all together

(use-package org
  :config
  (defun org-agenda-show-home (&optional arg)
    (interactive "P")
    (org-agenda arg "h"))
  (defun org-agenda-show-work (&optional arg)
    (interactive "P")
    (org-agenda arg "w"))
  (defun org-agenda-show-backlog (&optional arg)
    (interactive "P")
    (org-agenda arg "b"))

  ;; archive all done tasks in the subtree
  (defun org-archive-done-tasks ()
    (interactive)
    (org-map-entries
     (lambda ()
       (org-archive-subtree)
       (setq org-map-continue-from (outline-previous-heading)))
     "TODO=\"DONE\"|TODO=\"KILL\"" 'tree))

  ;; stop org mode opening new frames whenever neotree is open, thank god for this
  ;; https://github.com/Alexander-Miller/treemacs/issues/121
  ;; without this, shackle will not work
  (defun org-switch-to-buffer-other-window (&rest args)
    "Same as the original, but lacking the wrapping call to `org-no-popups'"
    (apply 'switch-to-buffer-other-window args))
  (setq-default org-default-notes-file (concat max/note-directory "todo/inbox.org"))
  (setq-default org-todo-keywords (quote ((sequence "WAIT(w)" "PROJ(p)") (sequence "FOCUS(f)") (sequence "TODO(t)" "|" "DONE(d)" "KILL(k)"))))
  (setq-default org-hide-emphasis-markers t) ;; hide ** __ // markers
  (setq org-startup-indented t) ;; hide-stars and indent text to the level of the heading
  (setq org-odd-level-only nil)
  ;; When hitting alt-return on a header, please create a new one without messing up the one I'm standing on.
  (setq-default org-insert-heading-respect-content t)
  (setq org-refile-allow-creating-parent-nodes 'confirm)
  (setq org-return-follows-link t)
  (setq org-fontify-quote-and-verse-blocks t) ; allow us to change font of quotes
  (setq org-refile-targets '((org-agenda-files :maxlevel . 5)))
  (setq org-refile-use-outline-path t)
  (setq org-startup-folded nil)
  (setq org-log-repeat nil) ;; don't log a time when marking a repeating task as DONE, only one-offs
  (setq-default org-catch-invisible-edits 'smart)
  (setq org-log-done 'time) ; save a closed date on done items
  (setq org-agenda-start-on-weekday nil) ;; start from the current day, not Monday
  (setq org-yank-adjusted-subtrees t) ; should indent subtrees correctly when pasting
  (setq org-outline-path-complete-in-steps nil)
  ;; (setq org-archive-location "archive.org_archive::* From %s") ; archive everything in a single file
  (setq org-archive-location "archive.org_archive::datetree/") ; archive everything in a date-tree in a single file
  ;; this next one is not working for me, it creates a new header under the current subtree, even if there is an existing top-level header it could use
  ; (setq org-archive-location "::* Archive") ; archive in the current file in a new header
  (setq-default org-agenda-window-setup 'current-window)
  (setq-default org-agenda-files (list (concat max/note-directory "todo/")))
  (add-to-list 'auto-mode-alist '("\\.txt\\'" . org-mode))
  (advice-add 'org-agenda-quit :before 'org-save-all-org-buffers)
  (add-to-list 'org-structure-template-alist '("sh" "#+BEGIN_SRC sh\n?\n#+END_SRC"))
  (add-to-list 'org-structure-template-alist '("sql" "#+BEGIN_SRC sql\n?\n#+END_SRC"))
  (add-to-list 'org-structure-template-alist '("el" "#+BEGIN_SRC emacs-lisp\n?\n#+END_SRC"))
  (add-to-list 'org-structure-template-alist '("t" "#+FILETAGS: ?"))
   (setq org-capture-templates
         `(("i" "Inbox" entry (file org-default-notes-file)
            "* TODO %?\n")
           ("t" "Todo" entry (file+olp ,(concat max/note-directory "todo/home.org") "Home" "One-off")
            "*** TODO %?\n")
           ("w" "Work Todo" entry (file+olp ,(concat max/note-directory "todo/work.org") "TRP" "One-off")
            "*** TODO %?\n")
           ("c" "Weekly checkup" entry (file+headline ,(concat max/note-directory "todo/home.org") "Project Checkups")
            "** TODO Weekly checkup %t
  *Did you work toward your foci this week?*
  %?

  *Should any foci change?*

  *Check home backlog (SPC o b) and schedule tasks*

  *Check there are at most 3 open projects*

  *Cleanup each open project, schedule tasks*

  *Check calendar*

  *<question from last week>*

  *A question to be answered by myself next week*"

            )))
  (setq org-agenda-custom-commands
        '(("h" "Home"
           ((agenda "" ((org-agenda-span 1) (org-agenda-show-log t) (org-agenda-overriding-header "Today")))
            (todo "TODO" (
                          (org-agenda-files `(,(concat max/note-directory "todo/inbox.org") ,(concat max/note-directory "todo/phone.org")))
                          (org-agenda-overriding-header "Inbox")))
                                          ;(tags "priorities" (
                                          ;                   (org-agenda-entry-text-mode t)
                                          ;                   (org-agenda-overriding-header "")))
                                          ; this does not work currently, do not seem to be able to set entry-text-mode for a single section
            (todo "FOCUS|PROJ" (
                                (org-agenda-overriding-header "Projects")))
            (todo "WAIT" (
                          (org-agenda-overriding-header "Pending Projects")))


                                          ;(todo "NEXT" ((org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled)) (org-agenda-overriding-header "Unscheduled project next-tasks")))
                                          ;(stuck "" ((org-agenda-overriding-header "Stuck projects and unprocessed inbox items")))
            (agenda "" ((org-agenda-span 14) (org-agenda-overriding-header "Next 2 weeks (except habits) f/b to change weeks")  (org-agenda-start-day "+1d") (org-agenda-show-future-repeats 'next))))
           ((org-agenda-tag-filter-preset '("-WORK")))
           )
          ("w" "Work"
           ((agenda "" ((org-agenda-span 1) (org-agenda-show-log t) (org-agenda-overriding-header "Today")))
                                          ; (todo "NEXT" ((org-agenda-overriding-header "Work next-tasks")))
            (todo "" ((org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled)) (org-agenda-overriding-header "Unscheduled TODOs")))
            (agenda "" ((org-agenda-span 5) (org-agenda-overriding-header "Upcoming") (org-agenda-start-day "+1d"))))
           ((org-agenda-tag-filter-preset '("+WORK")))
           )
          ("b" "Backlog"
           ((todo "TODO" (
                          (org-agenda-files `(,(concat max/note-directory "todo/home.org")))
                          (org-agenda-skip-function '(org-agenda-skip-entry-if 'scheduled))
                          (org-agenda-overriding-header "Unscheduled Home Tasks")))))
          ))

  :hook (org-mode . set-buffer-variable-pitch)
  )

Add default heading to new org files (OFF)

When a new org file is created, auto-create a heading based on the file name (useful for zetls)

Have disabled this since I am no longer adding top level headings to zetls

;; (use-package autoinsert
;;   :config
;;   (setq-default auto-inset-query nil) ; don't ask me, just do it
;;   (add-to-list 'auto-insert-alist
;;                ;; Org file
;;                '(("\\.\\(txt\\|org\\)\\'" . "Org file")
;;                  nil
;;                  "* " (file-name-nondirectory (file-name-sans-extension buffer-file-name)) "\n"
;;                  ))
;;     (auto-insert-mode t)
;;   )

Exporting

(use-package ox-jira)
(use-package ox-gfm)
(use-package htmlize) ; for syntax highlighting in exports

Creating backlinks

This is used to add backlinks to zetl files to help navigation.

If we needed to purge the old links for whatever reason, could do something like sed -i '/Linkback: \[\[file:/d' *.txt

(defun max/get-linked-files-in-buffer ()
  "Returns absolute path to all zetl files linked from the buffer"
  (delete-dups
   (org-element-map (org-element-parse-buffer) 'link
     (lambda (link)
       (when (string= (org-element-property :type link) "file")
         (expand-file-name (org-element-property :path link)))))))

(defun max/create-backlink-to-buffer (link)
  "Creates an org-style back link to the current buffer"
  (format "[[file:%s][%s]]" 
          (file-relative-name (buffer-file-name) (file-name-directory link))
          (file-name-sans-extension (file-name-nondirectory (buffer-file-name)))))

(defun max/string-exists-in-file (string file)
  "Return nil if the string is not found, or its position if found"
  (with-temp-buffer
    (insert-file-contents file)
    (search-forward string nil t)))

(defun max/create-backlinks ()
  "In the current buffer, find all zetl links and add a Linkback to the current buffer in each"
  (mapcar 
   (lambda (link) 
     (let ((org-link (max/create-backlink-to-buffer link)))
       (unless (max/string-exists-in-file org-link link)
         (progn
           (message (concat "Creating linkback from " link))
           (write-region (concat "\n- Linkback: " org-link) nil link 'append)))))
   (max/get-linked-files-in-buffer)))

(defun max/clean-backlinks ()
  "In the current buffer, run dos2unix and remove all existing backlinks"
  (dos2unix)
  (delete-matching-lines "- Linkback: \\[\\[file:" (point-min) (point-max))
  (end-of-buffer)
  (delete-blank-lines)
  (save-buffer))

(defun max/create-backlinks-for-all-zetl-files (&optional properties)
  (org-save-all-org-buffers)
  (let ((all-files (remove-if
                    (lambda (f) (or 
                            (file-directory-p f) 
                            (string= f (concat max/note-directory "zetl/index.txt")) 
                            (cl-search "Emacs Dotfile" f))) ; only want files, exclude index.txt, exclude dotfile
                    (directory-files-recursively (concat max/note-directory "zetl/") "" t))))
                                        ; clean existing backlinks from _all_ files first (avoids considering the backlinks as links themselves)
    (mapcar 
     (lambda (f)
       (save-excursion 
         (find-file f)
         (max/clean-backlinks)
         (kill-buffer (current-buffer))))
     all-files)
                                        ; then loop through all files again and create the backlinks afresh 
    (mapcar 
     (lambda (f)
       (save-excursion 
         (find-file f)
         (max/create-backlinks)
         (kill-buffer (current-buffer))))
     all-files)))

Publishing to web

This is the header and styling of the website

(setq max/site-index-link "<a href=\"/\">Back to the index</a>")
(setq max/site-preamble
      (concat "<style type=\"text/css\">"
              "body{margin:40px auto;max-width:650px;line-height:1.5;background-color:#fbf8ef;color:#332222;font-family:verdana;font-size:15px;padding:0 15px;}"
              "blockquote{font-style:italic;}"
              "h1,h2,h3,h4{font-weight:normal;text-decoration:underline;}"
              "h1{margin-bottom:1em;}"
              "li{line-height:1.6}"
              "#postamble{padding-top: 20px;border-top: 1px solid;margin-top: 20px;font-style: italic;}"
              "code{background-color:#dbd8cf;}"
              "pre.src{overflow:auto;font-size:14px}"
              "pre.src:before{top:0;right:0}"
              "</style>"
              max/site-index-link))

(setq max/site-postamble (concat "<p>" max/site-index-link "</p><p>Last modified %C. Contact max@maxjmartin.com</p>"))

We define a sitemap function so we can modify the auto-generated one, inserting an intro in the top

  (defun max/file-modified-this-month (filename)
    (let (
          (modified (time-to-seconds (file-attribute-modification-time (file-attributes filename 'string))))
          (last-month (- (float-time) (* 60 60 24 14))))
      (> modified last-month)))

  (defun max/get-filetags (path)
    "Returns alist of filetags or nil
        This includes an 'updated' tag if the file was modified in the last 14 days"
    (let (
          (filetags 
           (with-temp-buffer
             (insert-file-contents path)
             (let ((point (search-forward "#+FILETAGS:" nil t)))
               (if point 
                   (sort
                    (org-tag-string-to-alist (buffer-substring-no-properties point (line-end-position)))
                    (lambda (a b) (car a) (car b)))
                 '())))))
      filetags))

;; This has been removed, since the linkback script also updates files when they are published
;(if (max/file-modified-this-month path) (cons '("updated") filetags) filetags)))

  (defun max/format-sitemap-tags (tags)
    (if tags (concat " /" (mapconcat #'car tags ", ") "/") ""))

  (defun max/org-publish-sitemap-entry (entry style project)
    "Custom sitemap function that includes filetags
              ENTRY is a file name.  STYLE is the style of the sitemap.
              PROJECT is the current project."
    (cond ((not (directory-name-p entry))
           (format "[[file:%s][%s]] %s"
                   entry
                   (org-publish-find-title entry project)
                   (max/format-sitemap-tags (max/get-filetags (concat max/note-directory "zetl/" entry)))))
          ((eq style 'tree)
           ;; Return only last subdir.
           (file-name-nondirectory (directory-file-name entry)))
          (t entry)))

  (defun max/website-sitemap-function (project &optional filelist)
    (let* (
           (sitemap (org-publish-sitemap-default project filelist))
           (header-file (concat max/note-directory "zetl/About the zetl.txt"))
           (header-content (with-temp-buffer
                             (insert-file-contents header-file)
                             (buffer-string))))
      (concat header-content sitemap))
    )

(Some more examples of people's setups https://ogbe.net/blog/blogging_with_org.html https://www.evenchick.com/blog/blogging-with-org-mode.html https://www.john2x.com/blog/blogging-with-orgmode.html <- good sitemap stuff, I think using this we include another org file into the sitemap before it becomes html, to have a real index page also how he puts keywords in the footer here: https://www.john2x.com/emacs.html

(setq org-publish-project-alist
      `(("zetl"
         :base-directory ,(concat max/note-directory "zetl")
         :publishing-directory ,(concat max/note-directory "../zetlsite")
         :preparation-function max/create-backlinks-for-all-zetl-files
         :base-extension "txt"
         :auto-sitemap t
         :exclude "private/"
         :include ("Emacs Dotfile.org")
         :recursive t
         :section-numbers nil
         :sitemap-title "Zetl Index"
         :sitemap-filename "index.txt"
         :sitemap-function max/website-sitemap-function
         :sitemap-format-entry max/org-publish-sitemap-entry
         :html-preamble ,max/site-preamble
         :html-postamble ,max/site-postamble
         :publishing-function org-html-publish-to-html )))

Use the buffername as the title of the page if a title has not been set (could not see a better way to do this)

By default running org-publish only converts updated files. If you can to regenerate everything from scratch, you need to run something like this:

; (org-publish "zetl" t)
(defun max/add-buffer-name-as-title (orig-fun &rest args)
  (let* ((info (car args))
         (buffername (plist-get info :input-buffer))
         (newinfo (if (plist-get info :title) info (plist-put info :title (file-name-sans-extension buffername)))))
    (funcall orig-fun newinfo)))

(advice-add 'org-html--build-meta-info :around #'max/add-buffer-name-as-title)

This bit is horrible! The original code only fixes links for .org files, it does not check the :base-extension. This means that it will leave my inter-zetl links as .txt files, insead of correctly changing them to .html links. To fix, we just override the entire function to change one line! It now handles either .txt. or .org links (the .org part is just for our dotfile, since tangle does not seem to like the .txt extension)

(defun org-html-link (link desc info)
  "Transcode a LINK object from Org to HTML.
  DESC is the description part of the link, or the empty string.
  INFO is a plist holding contextual information.  See
  `org-export-data'."
  (let* ((link-org-files-as-html-maybe
          (lambda (raw-path info)
            ;; Treat links to `file.org' as links to `file.html', if
            ;; needed.  See `org-html-link-org-files-as-html'.
            (cond
             ((and (plist-get info :html-link-org-files-as-html)
                   (or
                    (string= ".txt" ;;;;;;;; THIS WAS .org!
                             (downcase (file-name-extension raw-path ".")))
                    (string= ".org"
                    (downcase (file-name-extension raw-path ".")))))
              (concat (file-name-sans-extension raw-path) "."
                      (plist-get info :html-extension)))
             (t raw-path))))
         (type (org-element-property :type link))
         (raw-path (org-element-property :path link))
         ;; Ensure DESC really exists, or set it to nil.
         (desc (org-string-nw-p desc))
         (path
          (cond
           ((member type '("http" "https" "ftp" "mailto" "news"))
            (url-encode-url (org-link-unescape (concat type ":" raw-path))))
           ((string= type "file")
            ;; During publishing, turn absolute file names belonging
            ;; to base directory into relative file names.  Otherwise,
            ;; append "file" protocol to absolute file name.
            (setq raw-path
                  (org-export-file-uri
                   (org-publish-file-relative-name raw-path info)))
            ;; Possibly append `:html-link-home' to relative file
            ;; name.
            (let ((home (and (plist-get info :html-link-home)
                             (org-trim (plist-get info :html-link-home)))))
              (when (and home
                         (plist-get info :html-link-use-abs-url)
                         (file-name-absolute-p raw-path))
                (setq raw-path (concat (file-name-as-directory home) raw-path))))
            ;; Maybe turn ".org" into ".html".
            (setq raw-path (funcall link-org-files-as-html-maybe raw-path info))
            ;; Add search option, if any.  A search option can be
            ;; relative to a custom-id, a headline title, a name or
            ;; a target.
            (let ((option (org-element-property :search-option link)))
              (cond ((not option) raw-path)
                    ;; Since HTML back-end use custom-id value as-is,
                    ;; resolving is them is trivial.
                    ((eq (string-to-char option) ?#) (concat raw-path option))
                    (t
                     (concat raw-path
                             "#"
                             (org-publish-resolve-external-link
                              option
                              (org-element-property :path link)))))))
           (t raw-path)))
         ;; Extract attributes from parent's paragraph.  HACK: Only do
         ;; this for the first link in parent (inner image link for
         ;; inline images).  This is needed as long as attributes
         ;; cannot be set on a per link basis.
         (attributes-plist
          (let* ((parent (org-export-get-parent-element link))
                 (link (let ((container (org-export-get-parent link)))
                         (if (and (eq (org-element-type container) 'link)
                                  (org-html-inline-image-p link info))
                             container
                           link))))
            (and (eq (org-element-map parent 'link 'identity info t) link)
                 (org-export-read-attribute :attr_html parent))))
         (attributes
          (let ((attr (org-html--make-attribute-string attributes-plist)))
            (if (org-string-nw-p attr) (concat " " attr) ""))))
    (cond
     ;; Link type is handled by a special function.
     ((org-export-custom-protocol-maybe link desc 'html))
     ;; Image file.
     ((and (plist-get info :html-inline-images)
           (org-export-inline-image-p
            link (plist-get info :html-inline-image-rules)))
      (org-html--format-image path attributes-plist info))
     ;; Radio target: Transcode target's contents and use them as
     ;; link's description.
     ((string= type "radio")
      (let ((destination (org-export-resolve-radio-link link info)))
        (if (not destination) desc
          (format "<a href=\"#%s\"%s>%s</a>"
                  (org-export-get-reference destination info)
                  attributes
                  desc))))
     ;; Links pointing to a headline: Find destination and build
     ;; appropriate referencing command.
     ((member type '("custom-id" "fuzzy" "id"))
      (let ((destination (if (string= type "fuzzy")
                             (org-export-resolve-fuzzy-link link info)
                           (org-export-resolve-id-link link info))))
        (pcase (org-element-type destination)
          ;; ID link points to an external file.
          (`plain-text
           (let ((fragment (concat "ID-" path))
                 ;; Treat links to ".org" files as ".html", if needed.
                 (path (funcall link-org-files-as-html-maybe
                                destination info)))
             (format "<a href=\"%s#%s\"%s>%s</a>"
                     path fragment attributes (or desc destination))))
          ;; Fuzzy link points nowhere.
          (`nil
           (format "<i>%s</i>"
                   (or desc
                       (org-export-data
                        (org-element-property :raw-link link) info))))
          ;; Link points to a headline.
          (`headline
           (let ((href (or (org-element-property :CUSTOM_ID destination)
                           (org-export-get-reference destination info)))
                 ;; What description to use?
                 (desc
                  ;; Case 1: Headline is numbered and LINK has no
                  ;; description.  Display section number.
                  (if (and (org-export-numbered-headline-p destination info)
                           (not desc))
                      (mapconcat #'number-to-string
                                 (org-export-get-headline-number
                                  destination info) ".")
                    ;; Case 2: Either the headline is un-numbered or
                    ;; LINK has a custom description.  Display LINK's
                    ;; description or headline's title.
                    (or desc
                        (org-export-data
                         (org-element-property :title destination) info)))))
             (format "<a href=\"#%s\"%s>%s</a>" href attributes desc)))
          ;; Fuzzy link points to a target or an element.
          (_
           (let* ((ref (org-export-get-reference destination info))
                  (org-html-standalone-image-predicate
                   #'org-html--has-caption-p)
                  (number (cond
                           (desc nil)
                           ((org-html-standalone-image-p destination info)
                            (org-export-get-ordinal
                             (org-element-map destination 'link
                               #'identity info t)
                             info 'link 'org-html-standalone-image-p))
                           (t (org-export-get-ordinal
                               destination info nil 'org-html--has-caption-p))))
                  (desc (cond (desc)
                              ((not number) "No description for this link")
                              ((numberp number) (number-to-string number))
                              (t (mapconcat #'number-to-string number ".")))))
             (format "<a href=\"#%s\"%s>%s</a>" ref attributes desc))))))
     ;; Coderef: replace link with the reference name or the
     ;; equivalent line number.
     ((string= type "coderef")
      (let ((fragment (concat "coderef-" (org-html-encode-plain-text path))))
        (format "<a href=\"#%s\" %s%s>%s</a>"
                fragment
                (format "class=\"coderef\" onmouseover=\"CodeHighlightOn(this, \
  '%s');\" onmouseout=\"CodeHighlightOff(this, '%s');\""
                        fragment fragment)
                attributes
                (format (org-export-get-coderef-format path desc)
                        (org-export-resolve-coderef path info)))))
     ;; External link with a description part.
     ((and path desc) (format "<a href=\"%s\"%s>%s</a>"
                              (org-html-encode-plain-text path)
                              attributes
                              desc))
     ;; External link without a description part.
     (path (let ((path (org-html-encode-plain-text path)))
             (format "<a href=\"%s\"%s>%s</a>"
                     path
                     attributes
                     (org-link-unescape path))))
     ;; No path, only description.  Try to do something useful.
     (t (format "<i>%s</i>" desc)))))

Evil Org Mode

(use-package evil-org
:after org
:config 
(add-hook 'org-mode-hook 'evil-org-mode)
  (add-hook 'evil-org-mode-hook
            (lambda ()
              (evil-org-set-key-theme '(textobjects)))))

Org Pomodoro

Use hooks to show a notification when a pomodoro starts and ends.

We use a timer to remind if a pomodoro has still not been chosen 10 minutes after the break ended. And that notification stays showing as long as possible (windows does not seem to respect the timeout anymore unfortunately, so it probably disappears pretty quickly)

(defun show-windows-notification (title text secs)
  (shell-command (concat "powershell -Command \"[void] [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); $objNotifyIcon=New-Object System.Windows.Forms.NotifyIcon; $objNotifyIcon.BalloonTipText='" text "'; $objNotifyIcon.Icon=[system.drawing.systemicons]::Information; $objNotifyIcon.BalloonTipTitle='" title "'; $objNotifyIcon.BalloonTipIcon='None'; $objNotifyIcon.Visible=$True; $objNotifyIcon.ShowBalloonTip(" (number-to-string (* 1000 secs)) ");\"")))

  ; https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getsyscolor
(defun set-windows-background-color (color)
  (shell-command (concat "powershell -command add-type -typedefinition \"\"\"\"using System;`n using System.Runtime.InteropServices;`n public class PInvoke { [DllImport(`\"\"\"user32.dll`\"\"\")] public static extern bool SetSysColors(int cElements, int[] lpaElements, int[] lpaRgbValues); }\"\"\"\"; [PInvoke]::SetSysColors(1, @(1), @(0x" (reverse color) "))")))

(setq pomo-alert-timer nil)

(use-package org-pomodoro
  :config
  (add-hook
   'org-pomodoro-started-hook
   (lambda ()
     (when pomo-alert-timer (cancel-timer pomo-alert-timer))
     (set-windows-background-color "00FF00") ; green for GO
     (show-windows-notification "Pom Started" "A Pom Has Been Started" 1)))

  (add-hook 
   'org-pomodoro-finished-hook 
   (lambda () 
     (set-windows-background-color "0000FF") ; blue for break
     (show-windows-notification "Pom Finished" "Break Time" 3)))

  (add-hook
   'org-pomodoro-break-finished-hook
   (lambda () 
     (set-windows-background-color "FF0000") ; red for 'you should be working'
     (show-windows-notification "Break Finished" "Choose a new pom" 4)
     (setq pomo-alert-timer 
           (run-at-time "5 min" nil (lambda () 
                                      (show-windows-notification "Break Finished!" "Choose a new pom!" 25))))
     ))
  )

Key bindings

;; unbind these since we will use it as our org-mode leader
(general-define-key
 :states '(normal visual emacs)
 "," nil
 "s" nil ; sacrifice 'substitute' for 'snipe'
 "SPC" nil) 

;; j/k and search support in the agenda
(general-define-key
 :keymaps 'org-agenda-mode-map
 "j" 'evil-next-line
 "k" 'evil-previous-line
 "p" 'org-pomodoro
 "s" 'org-agenda-schedule
 "/" 'evil-search-forward ; replaces filtering
 )

;; leader-prefixed
(general-define-key
 :states '(normal visual insert emacs)
 :prefix "SPC"
 :non-normal-prefix "C-SPC"

 ;; common
 "SPC" 'helm-M-x
 "." 'helm-projectile-find-file
 "/" (lambda () (interactive) (helm-ag max/note-directory))
 "b" 'helm-mini
 "k" 'kill-buffer
 "g" 'beeminder-list-goals

 ;; windows
 "w" 'delete-other-windows
 "rf" 'rename-file-and-buffer

 ;; org-mode
 "oc" 'org-capture
 "oe" 'org-export-dispatch
 "oa" 'org-agenda-show-home
 "ow" 'org-agenda-show-work
 "ot" 'org-todo-list
 "ob" 'org-agenda-show-backlog
 "oo" 'helm-org-rifle-agenda-files
 "og" 'org-clock-goto
 "os" (lambda () (interactive) (org-sort-entries nil ?o)) ; sort in TODO order
 )

;; org specific
(general-define-key
 :states '(normal visual insert)
 :keymaps 'org-mode-map
 "C-<return>" 'org-meta-return) ; make C-RET create a new list item

(general-define-key
 :states '(normal visual)
 :keymaps 'org-mode-map
 "$" 'evil-end-of-line ; do not use org-evil-end-of-line, I want real EOL, not visual EOL!
 )

(general-define-key
 :states '(normal visual operator)
 "sw" 'evil-avy-goto-char)


(general-define-key
 :states '(normal visual)
 :keymaps 'org-mode-map
 :prefix ","
 "," 'org-ctrl-c-ctrl-c 
 "s" 'org-schedule
 "f" 'org-show-todo-tree
 "t" 'org-todo
 "`" 'org-edit-special ; edit a source block, file at point, table
 "w" 'er/expand-region
 "ci" 'org-clock-in
 "co" 'org-clock-out
 "cc" 'org-clock-cancel
 "cl" 'org-clock-in-last
 "q" 'org-pomodoro ; q is a backwards p, so the binding makes sense, easier to type than ,p
 "a" 'org-archive-done-tasks
 "r" 'org-refile
 "h" 'outline-up-heading
 "x" 'eval-last-sexp
 "n" 'org-narrow-to-subtree
 "N" 'widen
 )

(general-define-key
 :states '(visual)
 :keymaps 'org-src-mode-map
 :prefix ","
 ";" 'comment-or-uncomment-region 
 )

(general-define-key
 :states '(normal)
 :keymaps 'org-src-mode-map
 :prefix ","
 ";" 'comment-line
 )

(general-define-key
 :states '(normal visual)
 :keymaps 'org-src-mode-map
 :prefix ","
 "`" 'org-edit-src-exit ; finish editing a source block and close the buffer 
 )

(general-define-key
 :keymaps 'beeminder-mode-map
 :states '(normal visual motion insert emacs)
 "r" 'beeminder-reload-goals-list
 "q" 'quit-window
 "TAB" 'beeminder-display-goal-details
 )

;; moving between windows and buffers
(general-define-key
 :states '(normal visual motion insert emacs)
                                        ; "C-S-<left>" 'previous-buffer ;; these conflict with org mode a bit, not sure if needed? SPC-b?
                                        ; "C-S-<right>" 'next-buffer
 "C-<left>" 'evil-window-left
 "C-<right>" 'evil-window-right
 "C-<up>" 'evil-window-up
 "C-<down>" 'evil-window-down)

;; use 'e' so we near the numbers to pick the replacement work
(general-define-key
 :states '(normal visual)
 :prefix ","
 "e" 'flyspell-check-next-highlighted-word
 )

                                        ; unbind RET from evil, so that it works to follow org links
(define-key evil-motion-state-map (kbd "RET") nil)

Easy Motion

I am no longer using avy/evil-motion. I found it too 'surprising', I would need to pause and read the letters before working out what to type to jump to the right place.

Instead, the suboptimal methods of doing dtx then . until you get to the right instance of 'x', seems to work fine. Doing tx followed with ; or , to move between results works well

For moving between lines, I tried displaying relative line numbers, but found it annoying. Instead, using search more.

gj to move between visual lines not real lines is good for line lines.

; (setq display-line-numbers 'relative)
; (setq display-line-numbers-current-absolute nil)
; (set-face-attribute 'line-number nil :family "Consolas")

Beeminder

(the version in melpa is not the one we want, we need the github version!

                                          ; requirements
  (use-package request)
  (use-package anaphora)

                                          ; configure
  (require 'beeminder)
  (setq-default beeminder-username max/beeminder-username)
  (setq-default beeminder-auth-token max/beeminder-auth-token)
  (defun max/beeminder-display-done-marker (goal)
    (if (string= "0" (beeminder-display-string-field goal 'donetoday)) " " "x"))
  (setq-default beeminder-goal-pp-fomat '(
                                           (max/beeminder-display-done-marker) " "
                                           (beeminder-display-string-field slug 20)
                                           " " (beeminder-display-string-field limsum 16)
                                           " " beeminder-display-losedate-human
                                           " " beeminder-display-rate " " beeminder-display-pledge " "))

(add-hook
   'beeminder-mode-hook
   (lambda () (setq line-spacing 6)))

On windows 10 I was getting a 'request exited abnormally with code 2' error, which seems to be due to request.el trying to use curl, but finding an invalid or old version. To fix, we ask request.el to use a different back-end

(setq request-backend 'url-retrieve)

Calendar

launch with cfw:open-org-calendar

(use-package calfw
  :config
  (setq calendar-week-start-day 1)
  )
(use-package calfw-org
  :after calfw)

Evil Collection

(use-package evil-collection
:after evil
:config
(evil-collection-init 'dired)

; overrides
  (general-define-key
   :keymaps 'dired-mode-map
   :states '(normal visual motion emacs)
   "<tab>" 'dired-find-file-other-window) ; instead of S-<return>
)

TODO consider moving these under the relevant use-package

Window Setup

Start with 2 windows side by side

(split-window-horizontally)

Magit

Unfortunately this is horribly slow on windows. Might try again with emacs/git running in WSL

(use-package magit)
(use-package evil-magit)

Tasks

TODO slack?

TODO set up alerts for scheduled tasks

https://github.com/spegoraro/org-alert or could do it like we did for pomodoros?

TODO could have something that checks zetl for bad links, after the conversion? just a script

TODO consider beeminder-mode to track pomos, tracked time, etc.

Could do things like:

  • Beemind completed pomos
  • Beemind clocked time on zetl articles! (beeminder.el supports this)
  • Can beeminder habits by having beeminder.el send a datapoint when a scheduled task is DONE (scheduled daily)
  • Of course, this only works on desktop, not phone

https://github.com/mbork/beeminder.el/

  • TODO part 1: new goal work-pomo, link to any :work tagged pomodoros completed

TODO use deadlines for things like MOT

http://doc.norang.ca/org-mode.html (search deadlines) can set it to show in the agenda, say, 30 days before. Cleaner than adding tasks for '30 days until MOT'

TODO peek file at point would be great for zetl and reviews

When the mode is active, if you are over a link it will peep it in a different buffer? Could just use ffap-other-window org-open-at-point works fine! ./inbox.html

TODO would be nice to show the pomo count on tasks somehow, not sure how difficult?

TODO the auto time tracking of the meetings with capture tasks etc. is very cool. Look into clocking in general

TODO have some perm tasks for time tracking we could link to BM (eg. zetl, other projects – project can just track the TODO item under the project

Will archiving break the tracking though? Maybe should archive within the file, especially for projects –> yes, had a look and it seems to break tracking when you archive to a different file. Maybe we should just have a key to easily sort, then archive manually into the same file?

TODO consider this function to easily mark-done and create followup

TODO modeline file path seems to be 'sticking' to the buffer name if that is too long

TODO add a new agenda for completed tasks, useful for reviews?

TODO [paused] hook into org-pomodoro to run script that updates hosts to lock down certain websites

example code we could look at here: https://github.com/lolownia/org-pomodoro/blob/master/org-pomodoro-pidgin.el It should have different modes, so I can block email usually, but allow it when I'm doing an inbox pomo for example Start music etc.? Should also change something visual on the screen (taskbar color or something) to make it obvious what mode we are in Maybe stop music at end of pomo also?

– looks like powershell scripts are disabled on corporate network, so not going to be as easy as I hoped. Paused for now

TODO look at using the clock-in and clock-in-last versions of org-pomodoro (uses prefix args)

TODO single key to mark agenda item done

search for "Make it easy to mark a task as done" in http://pages.sachachua.com/.emacs.d/Sacha.html#org44fc1f8

TODO automatic timestamping on save, pretty cool

https://github.com/zzamboni/dot-emacs/blob/master/init.org (add-hook 'before-save-hook 'time-stamp)

TODO look at bulk commands, could be useful

TODO generate UML diagrams etc. using source blocks

TODO pomo cancelled should reset the screen color

TODO yasnippets for things like front matter, etc.

TODO consider using hjkl instead of arrows for window movement (C-h is the help prefix, which might be the most useful one we would lose)

TODO search 'custom-face' for how to do face config with use-package, also very clean with the noweb stuff

TODO make agenda less colorful?

TODO org-drill (emacs srs, looks good)

TODO org-protocol, can send stuff to org from other apps (browser etc.)

TODO reconsider our keybindings: what prefix to use, when to use which prefix, differences between in agenda and out of agenda keybindings

TODO when break finished, yes or no p, do you want to continue the same task?

TODO unmap escape to force us to use wk?

TODO limit search depth for refile so we only see level 1/2 headings?

TODO consider turning on evil-collection for all modes, also evil-org to replace our own org-agenda bindings

TODO Try magit

TODO setting for beeminder.el to submit with defaults (1 and default comment unless prefixed)

TODO expand evil-escape (pr?) to also let wk quit beeminder, org agenda, org capture

TODO reader mode by setting a big fringe?

TODO unbind SPC in agenda mode to reveal the bindings beneath?

Useful commands

  • count-words to.. count words
  • describe-bindings or helm-descbinds to show shortcuts in the current buffer
  • % to jump to matching paren
  • M-Spc in agenda mode shows a popup of keybindings to be used in that mode
  • , n narrow to subtree, then , N to widen back out again (useful when working on a single project)
  • evil uses emacs regex under the hood, so \t \d etc. will not work! For 'tab' can use C-q <TAB> (C-q inserts the next character literally)
  • in the agenda, SHIFT-LEFT/RIGHT moves the task forward or back by one day, > prompts for a new date
  • When in helm, C-c TAB will yank the current selection into the buffer!
  • When in M-x helm, TAB will show help for the highlighted function
  • To convert a region to table using tabs as separators, '64' (for the prefix argument) then the org-table-from-region(something like that) function
  • gj and gk to move between visual lines
  • surround mode: ds<obj> (delete), cs<obj><obj> (change) ys<obj> (add). Can also use S<obj> in visual mode. For example ysis" wraps a sentence with quotes. Can use all the usual text objects (word, para, sentence, etc)
  • helm-show-kill-ring allows you to browse kill ring and insert using RET
  • J join line below with this one
  • cc change entire line (of course!) – also S but we are rebinding s for something else
  • ,w then w to expand scope
  • it is more comfortable to use 0 to get to the beginning of the line than ^

Stuff we already use a lot:

  • SPC SPC = M-x!
  • describe-variable for checking current status of hooks, etc.
  • C-y to paste into helm minibuffer
  • SPC o o to jump to an org heading
  • ~ ~ for 'code', + + for 'strikeout'
  • C-g abort, get out of bad state
  • describe-mode to see keybindings for current mode
  • describe-face to check the face under cursor
  • SPC . for fuzzy-find in project
  • SPC / for search using ag in current project
  • C-c C-c (,, in evil) updates stuff https://orgmode.org/manual/The-very-busy-C_002dc-C_002dc-key.html
  • Like vim, C-n to complete based on current buffer
  • g; and g, jump between locations you have edited. `` jumps back to where you last jumped from

Useful table formulas

Conver col 2 to percentage (run once)

Can sort table by putting cursor in the relevant column and using org-table-sort

Back to the index

Last modified 2019-08-16 Fri 09:07. Contact max@maxjmartin.com