Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.org

Paf’s portable Emacs configuration

This is my Emacs config. You need to tangle this into a file that then gets loaded by Emacs: C-c C-v t [org-babel-tangle]. Below, I also explain how this tangling is automated.

Find extensive documentation about how to do this here.

Personal Plans

I regularly try out new packages, this is my current list of things being evaluated. They usually live temporarily in my ~/.emacs until I am happy, in which case I move their config into this file so that it gets replicated on all machines I work on with Emacs.

Currently under review

Make the config work fine when initially installed

F12 does not find any files

  • State “DONE” from “TODO” [2021-06-04 Fri 22:36]
  • State “TODO” from [2021-02-03 Wed 16:52]
because nothing is set in the org-agenda-files

maybe complain when org-directory is not set to an existing directory

  • State “DONE” from “TODO” [2021-08-19 Thu 16:45]
  • State “TODO” from [2021-02-03 Wed 16:53]

Avoid showing the welcome screen if a file was requested on cmdline

  • State “TODO” from [2021-06-04 Fri 23:41]

Tangle the config file into ~/.emacs.d/personal.el and have ~/.emacs.d/init.el load that.

  • State “DONE” from “TODO” [2021-09-13 Mon 23:16]
  • State “TODO” from [2021-08-19 Thu 16:46]

Initialize Emacs

This section sets up Emacs so it can tangle the config, find use-package, and find the ELPA repositories where to get the new packes from.

Info header and startup

Just to add a little information in the tangled file.

;; ===== this file was auto-tangled, only edit the emacs_setup.org =====
;; (profiler-start 'cpu)
source $(dirname $0)/install.sh

Diverse paths

A variable so we can refer to this all over the place.

Maybe there is a way to auto-detect this and generate it with literate programming or such.

(setq emacs-config-directory "~/Emacs")
(setq paf-lisp-directory (expand-file-name "lisp" emacs-config-directory))
(setq paf-emacs-init "~/.emacs.d/init.el")
(setq custom-file "~/.emacs.d/custom.el")
(setq paf-roam-directory (expand-file-name "OrgRoam" org-directory))

MELPA

Make sure we have the package system initialized before we load anything.

(require 'package)
(when (< emacs-major-version 27)
  (package-initialize))

Adding my choice of packages repositories.

#+NAME melpa-setup

(setq package-archives '(
                         ;;("org" . "https://orgmode.org/elpa/")
                         ("melpa" . "https://melpa.org/packages/")
                         ("stable-melpa" . "https://stable.melpa.org/packages/")
                         ("nongnu" . "https://elpa.nongnu.org/nongnu/")
                         ("gnu" . "https://elpa.gnu.org/packages/")
                        ))

Note that ‘melpa’ is needed for these:

  • hc-zenburn-theme
  • column-enforce-mode
  • popup-kill-ring
  • tj3-mode
  • google-c-style
  • git-gutter-fringe+
  • bazel
  • org-clock-convenience
  • ox-reveal

use-package

I use use-package for most configuration, and that needs to be at the top of the file. use-package verifies the presence of the requested package, otherwise installs it, and presents convenient sections for configs of variables, key bindings etc. that happen only if the package is actually loaded.

First, make sure it gets installed if it is not there yet.

;; make sure use-package is installed
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(require 'use-package)
(eval-when-compile (require 'use-package))

tangle-this-config

I set this up to tangle the init org-mode file into the actual Emacs init file as soon as I save it.

(defun tangle-init ()
  "If the current buffer is 'init.org' the code-blocks are
  tangled, and the tangled file is compiled."
  (when (equal (buffer-file-name)
               (expand-file-name "emacs_setup.org" emacs-config-directory))
    ;; Avoid running hooks when tangling.
    (let ((prog-mode-hook nil))
      (org-babel-tangle)
      ;; (byte-compile-file paf-emacs-init)
      )))

(add-hook 'after-save-hook 'tangle-init)

Speedup startup time

Temporarily disable GC

;; Minimize garbage collection during startup
(setq gc-cons-threshold most-positive-fixnum)

;; Lower threshold back to 8 MiB (default is 800kB)
(add-hook 'emacs-startup-hook
          (lambda ()
            (setq gc-cons-threshold (expt 2 23))))

Add the startup profiler

(use-package esup
  :ensure t
  :pin stable-melpa)

Detect System

Some hints about how to do this are here.

;; Detect the current system and capabilities

Personal Initialization

Clear C-p so I can use it as a prefix

Remove C-p that I want to use for me personally as a prefix.

(global-set-key (kbd "C-p") nil) ;; was 'previous-line'

Initial scratch content

(setq initial-scratch-message "; Paf's unsaved ramblings and tests...\n")

Splash screen

 (defun get-resource (name)
   (let* ((resource-dir (expand-file-name "Resources" emacs-config-directory)))
     (expand-file-name name resource-dir)))
 (if window-system
     (progn
	(setq initial-buffer-choice (get-resource "welcome.org"))
	(setq fancy-splash-image (get-resource "paf_emacs.svg"))
	(setq org-startup-with-inline-images t))
   (setq initial-buffer-choice (get-resource "welcome_nox.org")))

Helper Functions

add-hook-run-once

Use instead of add-hook to run it a single time. found here

(defmacro add-hook-run-once (hook function &optional append local)
  "Like add-hook, but remove the hook after it is called"
  (let ((sym (make-symbol "#once")))
    `(progn
       (defun ,sym ()
         (remove-hook ,hook ',sym ,local)
         (funcall ,function))
       (add-hook ,hook ',sym ,append ,local))))

truncate a string

(defun paf/truncate-string (text &optional len ellipsis)
  "Truncate the text to a given length.

When LEN is a number, resulting string is truncated at that length.
If the length is bigger, then '...' is added at the end.

Usage example:

  (setq org-agenda-prefix-format
        '((agenda . \" %(paf/truncate-string (roam-extras/extract-agenda-category) 12) %?-12t %12s\")))

Refer to `org-agenda-prefix-format' for more information."
  (interactive)
  (if (and (numberp len) (> (length text) len))
      (let* ((used-ellipsis (if (eq ellipsis nil) "" ellipsis))
             (ellipsis-length (length used-ellipsis))
             (short-text (substring text 0 (- len ellipsis-length))))
        (format "%s%s" short-text used-ellipsis))
    text))

;; (setq paf-tests/truncate (paf/truncate-string "Here is some long text" 10))

Environment

Browser default

(setq browse-url-generic-program (executable-find "google-chrome")
  browse-url-browser-function 'browse-url-generic)

Setup server

Start the background server, so we can use emacsclient. Check by running (server-running-p).

(require 'server)
(if (and (fboundp 'server-running-p)
         (not (server-running-p)))
    (server-start))

UTF-8

Make Emacs request UTF-8 first when pasting stuff.

(use-package unicode-escape
  :ensure t
  :init
  (setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING)))
(set-language-environment "UTF-8")

Newline (only Unix wanted)

This should automatically convert any files with dos or Mac line endings into Unix style ones. Code found here.

(defun no-junk-please-we-are-unixish ()
  (let ((coding-str (symbol-name buffer-file-coding-system)))
    (when (string-match "-\\(?:dos\\|mac\\)$" coding-str)
      (set-buffer-file-coding-system 'unix))))

(add-hook 'find-file-hook 'no-junk-please-we-are-unixish)

auto revert

Use auto-revert, which reloads a file if it’s updated on disk and not modified in the buffer.

(global-auto-revert-mode 1)

enable upcase- and downcase-region and narrowing

these got disabled in Emacs 19 (!) because they were considered confusing. Re-enabling them here. Use C-x C-u and C-x C-l to effect them.

(put 'upcase-region 'disabled nil)  ;; C-x C-u
(put 'downcase-region 'disabled nil)  ;; C-x C-l (lowercase L)

;; C-x n <key>. Widen with C-x n w
(put 'narrow-to-region 'disabled nil)  ; C-x n n
(put 'narrow-to-defun  'disabled nil)
(put 'narrow-to-page   'disabled nil)

Calendar starts on Monday

;; Calendar starts on Monday
(setq calendar-week-start-day 1)
(setq org-gcal-local-timezone "Europe/Zurich")

Completion

Vertico

Added the completion framework vertico as from the docs, I liked

  1. the writing quaqlity
  2. the fact that they re-use/integrate completely with the built-in completion
  3. the package seems quite orthogonal to other packages, i.e. no need to have a <project>-vertico package to be installed (like helm seems to need).
;; Enable vertico
(use-package vertico
  :ensure t
  :init
  (vertico-mode)
  ;; (setq vertico-resize t)  ;; Grow and shrink the Vertico minibuffer
  (setq vertico-cycle t) ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
  )

;; Use the `orderless' completion style. Additionally enable
;; `partial-completion' for file path expansion. `partial-completion' is
;; important for wildcard support. Multiple files can be opened at once
;; with `find-file' if you enter a wildcard. You may also give the
;; `initials' completion style a try.
(use-package orderless
  :ensure t
  :init
  (setq completion-styles '(substring orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init
  (savehist-mode))

;; A few more useful configurations...
(use-package emacs
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  ;; (setq read-extended-command-predicate
  ;;       #'command-completion-default-include-p)

  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

Marginalia

Also adding more info in the completion buffers with Marginalia.

(use-package marginalia
  :ensure t
  ;; Either bind `marginalia-cycle` globally or only in the minibuffer
  :bind (("M-A" . marginalia-cycle)
         :map minibuffer-local-map
         ("M-A" . marginalia-cycle))
  :init
  (marginalia-mode))

Consult

This package brings some commands based on build-in search. See consult homepage for more details.

;; Example configuration for Consult
(use-package consult
  :ensure t
  :bind (;; C-c bindings (mode-specific-map)
         ("C-c h" . consult-history)
         ("C-c m" . consult-mode-command)
         ("C-c b" . consult-bookmark)
         ("C-c k" . consult-kmacro)
         ;; C-x bindings (ctl-x-map)
         ("C-x M-:" . consult-complex-command)     ;; orig. repeat-complex-command
         ("C-x b" . consult-buffer)                ;; orig. switch-to-buffer
         ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
         ("C-x 5 b" . consult-buffer-other-frame)  ;; orig. switch-to-buffer-other-frame
         ;; Custom M-# bindings for fast register access
         ("M-#" . consult-register-load)
         ("M-'" . consult-register-store)          ;; orig. abbrev-prefix-mark (unrelated)
         ("C-M-#" . consult-register)
         ;; Other custom bindings
         ("M-y" . consult-yank-pop)                ;; orig. yank-pop
         ("<help> a" . consult-apropos)            ;; orig. apropos-command
         ;; M-g bindings (goto-map)
         ("M-g e" . consult-compile-error)
         ("M-g f" . consult-flymake)               ;; Alternative: consult-flycheck
         ("M-g g" . consult-goto-line)             ;; orig. goto-line
         ("M-g M-g" . consult-goto-line)           ;; orig. goto-line
         ("M-g o" . consult-outline)               ;; Alternative: consult-org-heading
         ("M-g m" . consult-mark)
         ("M-g k" . consult-global-mark)
         ("M-g i" . consult-imenu)
         ("M-g I" . consult-imenu-multi)
         ;; M-s bindings (search-map)
         ("M-s f" . consult-find)
         ("M-s F" . consult-locate)
         ("M-s g" . consult-grep)
         ("M-s G" . consult-git-grep)
         ("M-s r" . consult-ripgrep)
         ("M-s l" . consult-line)
         ("M-s L" . consult-line-multi)
         ("M-s m" . consult-multi-occur)
         ("M-s k" . consult-keep-lines)
         ("M-s u" . consult-focus-lines)
         ;; Isearch integration
         ("M-s e" . consult-isearch)
         :map isearch-mode-map
         ("M-e" . consult-isearch)                 ;; orig. isearch-edit-string
         ("M-s e" . consult-isearch)               ;; orig. isearch-edit-string
         ("M-s l" . consult-line)                  ;; needed by consult-line to detect isearch
         ("M-s L" . consult-line-multi))           ;; needed by consult-line to detect isearch

  :init
  ;; Optionally configure the register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  (setq register-preview-delay 0
        register-preview-function #'consult-register-format)

  ;; Optionally tweak the register preview window.
  ;; This adds thin lines, sorting and hides the mode line of the window.
  (advice-add #'register-preview :override #'consult-register-window)

  ;; Optionally replace `completing-read-multiple' with an enhanced version.
  (advice-add #'completing-read-multiple :override #'consult-completing-read-multiple)

  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)

  :config
  ;; Optionally configure preview. The default value
  ;; is 'any, such that any key triggers the preview.
  ;; (setq consult-preview-key 'any)
  ;; (setq consult-preview-key (kbd "M-."))
  ;; (setq consult-preview-key (list (kbd "<S-down>") (kbd "<S-up>")))
  ;; For some commands and buffer sources it is useful to configure the
  ;; :preview-key on a per-command basis using the `consult-customize' macro.
  (consult-customize
   consult-theme
   :preview-key '(:debounce 0.2 any)
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file consult-xref
   consult--source-file consult--source-project-file consult--source-bookmark
   :preview-key (kbd "M-."))

  ;; Optionally configure the narrowing key.
  ;; Both < and C-+ work reasonably well.
  (setq consult-narrow-key "<") ;; (kbd "C-+")

  ;; Optionally make narrowing help available in the minibuffer.
  ;; You may want to use `embark-prefix-help-command' or which-key instead.
  ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)

  ;; Optionally configure a function which returns the project root directory.
  ;; There are multiple reasonable alternatives to chose from.
  ;;;; 1. project.el (project-roots)
  ;;(setq consult-project-root-function
  ;;      (lambda ()
  ;;        (when-let (project (project-current))
  ;;          (car (project-roots project)))))
  ;; 2. projectile.el (projectile-project-root)
  (autoload 'projectile-project-root "projectile")
  (setq consult-project-root-function #'projectile-project-root)
  ;;;; 3. vc.el (vc-root-dir)
  ;; (setq consult-project-root-function #'vc-root-dir)
  ;;;; 4. locate-dominating-file
  ;; (setq consult-project-root-function (lambda () (locate-dominating-file "." ".git")))
)

Managing Buffers

winner-mode

Enables winner-mode. Navigate buffer-window configs with C-c left and C-c right.

(winner-mode 1)

popper.el: deal with popup windows

A minor-mode to deal with lots of popup windows and bring some order in them. See github:popper for more information.

(use-package popper
  :ensure t
  :after projectile
  :bind (("<C-tab>"   . popper-toggle-latest)
         ("<C-S-tab>" . popper-cycle)
         ("<C-M-tab>" . popper-toggle-type))
  :init
  (setq popper-reference-buffers
        '("\\*Messages\\*"
          "\\*Bufler\\*"
          "Output\\*$"
          help-mode
          compilation-mode))
  (setq popper-group-function #'popper-group-by-projectile)
  (popper-mode +1))

perspective

(use-package perspective
  :ensure t
  :bind
  (("C-x C-b" . persp-list-buffers)
   ("C-x b" . persp-switch-to-buffer*)
   ("C-x k" . persp-kill-buffer*)
   )
  :hook (kill-emacs-hook . persp-state-save)
  :config
  (persp-mode)
  (setq persp-state-default-file
        (expand-file-name "perspective.save" user-emacs-directory)))

toggle-maximize-buffer

Temporarily maximize a buffer. found here

(defun toggle-maximize-buffer () "Maximize buffer"
       (interactive)
       (if (= 1 (length (window-list)))
           (jump-to-register '_)
         (progn
           (window-configuration-to-register '_)
           (delete-other-windows))))
;;Map it to a key.
(global-set-key (kbd "M-<f8>") 'toggle-maximize-buffer)

Colors and Look

In terminal mode

(when (display-graphic-p)
  (set-background-color "#ffffff")
  (set-foreground-color "#141312"))

In X11 mode: mouse and window title

(setq frame-title-format "emacs @ %b - %f")
(when window-system
  (mouse-wheel-mode)  ;; enable wheelmouse support by default
  (set-selection-coding-system 'compound-text-with-extensions))

Look: buffer naming

(use-package uniquify
  :init
  (setq uniquify-buffer-name-style 'post-forward-angle-brackets))

Buffer Decorations

Setup the visual cues about the current editing buffer

(column-number-mode t)
(setq visible-bell t)
(setq scroll-step 1)
(setq tool-bar-mode nil)
(setq-default transient-mark-mode t)  ;; highlight selection

nyan-mode

(use-package nyan-mode
  :ensure t
  :bind ("C-p n" . 'nyan-mode))

dynamic cursor colors

The cursor is displayed in different colors, depending on overwrite or insert mode.

(setq hcz-set-cursor-color-color "")
(setq hcz-set-cursor-color-buffer "")

(defun hcz-set-cursor-color-according-to-mode ()
  "change cursor color according to some minor modes."
  ;; set-cursor-color is somewhat costly, so we only call it when needed:
  (let ((color
         (if buffer-read-only "orange"
           (if overwrite-mode "red"
             "green"))))
    (unless (and
             (string= color hcz-set-cursor-color-color)
             (string= (buffer-name) hcz-set-cursor-color-buffer))
      (set-cursor-color (setq hcz-set-cursor-color-color color))
      (setq hcz-set-cursor-color-buffer (buffer-name)))))

(add-hook 'post-command-hook 'hcz-set-cursor-color-according-to-mode)

theme / faces

I really like the high-contract Zenburn theme.

(use-package hc-zenburn-theme
  :ensure t)

;; This makes some of the faces a bit more contrasted.
;; faces for general region highlighting zenburn is too low-key.
(custom-set-faces
 '(highlight ((t (:background "forest green"))))
 '(region ((t (:background "forest green"))))
 '(default ((t (:family "JetBrains Mono" :foundry "JB" :slant normal :weight extra-light :height 98 :width normal)))))

Key Mappings

which-key

This will show the list of the possible completion keys during a longer key sequence.

(use-package which-key
  :ensure t
  :custom (which-key-idle-delay 2.0)
  :config (which-key-mode t))

alternate key mappings

Letting one enter chars that are otherwise difficult in e.g. the minibuffer.

(global-set-key (kbd "C-m") 'newline-and-indent)
(global-set-key (kbd "C-j") 'newline)
(global-set-key [delete] 'delete-char)
(global-set-key [kp-delete] 'delete-char)

home and end

(global-set-key (kbd "<home>") 'beginning-of-line)
(global-set-key (kbd "<end>") 'end-of-line)

Macros

(global-set-key [f3] 'start-kbd-macro)
(global-set-key [f4] 'end-kbd-macro)
(global-set-key [f5] 'call-last-kbd-macro)

Text size

Increase/decrease text size

(define-key global-map (kbd "C-+") 'text-scale-increase)
(define-key global-map (kbd "C--") 'text-scale-decrease)

multiple regions

(global-set-key (kbd "C-M-i") 'iedit-mode)

Moving around buffers

(global-set-key (kbd "C-c <C-left>")  'windmove-left)
(global-set-key (kbd "C-c <C-right>") 'windmove-right)
(global-set-key (kbd "C-c <C-up>")    'windmove-up)
(global-set-key (kbd "C-c <C-down>")  'windmove-down)
(global-set-key (kbd "C-c C-g") 'goto-line)

Moving tabs

;; These are PgUp (<prior>) and PgDown (<next>)
(global-set-key (kbd "C-<prior>")  'tab-bar-switch-to-prev-tab)
(global-set-key (kbd "C-<next>")  'tab-bar-switch-to-next-tab)

multiple-cursors

Configure the shortcuts for multiple cursors

(use-package multiple-cursors
  :ensure t
  :bind (("C-S-c C-S-c" . 'mc/edit-lines)
         ("C->" . 'mc/mark-next-like-this)
         ("C-<" . 'mc/mark-previous-like-this)
         ("C-c C->" . 'mc/mark-all-like-this)))

ace-jump-mode

Let’s one jump around text

(use-package ace-jump-mode
  :ensure t
  :bind (("C-c C-SPC" . 'ace-jump-mode)
         ("C-c C-DEL" . 'ace-jump-mode-pop-mark)))

Hydra

(use-package hydra
  :ensure t)

Editing Style

No tabs, ever. No trailing spaces either.

(setq-default indent-tabs-mode nil)
(setq require-final-newline t)
(setq next-line-add-newlines nil)
(add-hook 'before-save-hook 'delete-trailing-whitespace)

Mark the 80 cols boundary

(use-package column-enforce-mode
  :ensure t
  :config
  (setq column-enforce-column 80)
  :bind ("C-c m" . 'column-enforce-mode))
;; column-enforce-face

Better kill ring

Seen demonstrated by Uncle Dave

(use-package popup-kill-ring
  :ensure t
  :bind ("M-y" . popup-kill-ring))

Cool Packages

annotate-mode

The file-annotations are store externally. Seems to fail with args-out-of-range and then Emacs is confused. (filed issue for this)

Also, it seems to interfere with colorful modes like magit or org-agenda-mode so that I went with a whitelist instead of the wish of a blacklist of modes.

(use-package annotate
  :ensure t
  :bind ("C-c C-A" . 'annotate-annotate)  ;; for ledger-mode, as 'C-c C-a' is taken there.
  :config
  ;;(add-hook 'org-mode 'annotate-mode)
  (add-hook 'csv-mode 'annotate-mode)
  (add-hook 'c-mode 'annotate-mode)
  (add-hook 'c++-mode 'annotate-mode)
  (add-hook 'sh-mode 'annotate-mode)
  (add-hook 'ledger-mode 'annotate-mode)
;;;  (define-globalized-minor-mode global-annotate-mode annotate-mode
;;;    (lambda () (annotate-mode 1)))
;;;  (global-annotate-mode 1)
  )

web-mode

web-mode with config for Polymer editing

(use-package web-mode
  :ensure t
  :mode "\\.html\\'"
  :config
  (setq web-mode-markup-indent-offset 2)
  (setq web-mode-css-indent-offset 2)
  (setq web-mode-code-indent-offset 2))

typescript-mode

(use-package typescript-mode
  :ensure t
  :mode "\\.ts\\'"
  ;; :config
  ;; (setq typescript-indent-level 2)
  )

csv-mode

mode to edit CSV files.

(use-package csv-mode
  :ensure t
  :mode "\\.csv\\'")

protobuf-mode

Mode for Google protocol buffer mode

(use-package protobuf-mode
  :ensure t
  :mode "\\.proto\\'")

rainbow-mode

Colorize color names and hexadecimal codes in the correct color.

(use-package rainbow-mode
  :ensure t)

taskjuggler-mode (tj3-mode)

(use-package ox-taskjuggler
  :load-path (lambda () (expand-file-name paf-lisp-directory)))

(use-package tj3-mode
  :ensure t
  :after ox-taskjuggler
  :config
  (require 'ox-taskjuggler)
  (custom-set-variables
   '(org-taskjuggler-process-command "/usr/bin/tj3 --silent --no-color --output-dir %o %f")
   '(org-taskjuggler-project-tag "PRJ")))
# Install TaskJuggler
  if [[ "$(uname -m)" == "x86_64" ]]; then
    install_pkg tj3
  fi

writeroom-mode

(use-package writeroom-mode
  :ensure t
  :init
  (global-set-key (kbd "C-p w") 'writeroom-mode))

wgrep-mode

(use-package wgrep
  :ensure t)

ledger-mode

Cleanup ledger file

(defun single-lines-only ()
  "replace multiple blank lines with a single one"
  (interactive)
  (goto-char (point-min))
  (while (re-search-forward "\\(^\\s-*$\\)\n" nil t)
    (replace-match "\n")
    (forward-char 1)))

(defun paf/cleanup-ledger-buffer ()
  "Cleanup the ledger file"
  (interactive)
  (delete-trailing-whitespace)
  (single-lines-only)
  (ledger-mode-clean-buffer)
  (ledger-sort-buffer))

Compute formatted sum of region

It actually computes the entire arithmetic expression that is selected, and replaces it with the numerical result.

(defun apply-function-to-region (fn)
  (interactive "XFunction to apply to region: ")
  (save-excursion
    (let* ((beg (region-beginning))
           (end (region-end))
           (had-region (use-region-p))
           (resulting-text
            (funcall
             fn
             (buffer-substring-no-properties beg end)))
           (new-end (+ beg (length resulting-text))))
      (kill-region beg end)
      (insert resulting-text)
      ;; set the active region again if it was set originally.
      (if had-region
          (progn
            (goto-char beg)
            (push-mark new-end)
            (setq mark-active t))))))

(defun paf/sum-amount (expression)
  "Computes the sum from the arith expression given as argument."
  (format "%.2f" (string-to-number (calc-eval expression))))

(defun paf/sum-amount-of-region ()
  "Takes the region as an arithmetic expr, and replaces it with its sum."
  (interactive)
  (if (use-region-p)
      (progn
        (apply-function-to-region 'paf/sum-amount)
        (goto-char (region-end)))))

(global-set-key (kbd "C-p S") 'paf/sum-amount-of-region)

Setup

(use-package ledger-mode
  :ensure t
  :bind ("<f6>" . 'paf/cleanup-ledger-buffer)
  :config
  (setq ledger-reconcile-default-commodity "CHF")
  :init
  (add-hook 'ledger-mode-hook
            (lambda ()
              (setq-local tab-always-indent 'complete)
              (setq-local completion-cycle-threshold t)
              (setq-local ledger-complete-in-steps t))))

hyperbole

I found some gems that explain a bit better what hyperbole is trying to solve. See John Wiegley’s Using hyperbole: a motivation Once more it shows that the most powerful things are not always the most visible nor the easiest to explain.

NOTE assigns hui-search-web to C-c C-/ to not clobber the later used C-c / from OrgMode (org-mode sparse trees). This works because hyperbole will first check if the function is already bound to some key before binding it to the coded default.

(use-package hyperbole
  :ensure t
  :config
  (bind-key "C-c C-/" 'hui-search-web)  ;; bind before calling require
  (custom-set-faces
   '(hbut ((t (:foreground "green yellow"))) t)
   '(hbut-flash ((t (:background "green yellow"
                     :foreground "dark gray"))) t))
  (require 'hyperbole)
  (load-file (expand-file-name "hyperbole-systems.el" paf-lisp-directory)))

ztree

A tree-view navigation of files, with diff tool for directories.

(use-package ztree
  :ensure t)

Google This!

This package enables to get Google search results within Emacs itself. The default map is bound to C-c / so search for thing at point is C-c / g.

(use-package google-this
  :ensure t
  :config
  (setq google-this-browse-url-function 'eww-browse-url)
  (google-this-mode 1))

Coding

VCS

magit

Add the powerful Magit

(use-package magit
  :ensure t
  :defer
  :bind ("C-x g" . 'magit-status))
(use-package magit-todos
  :ensure t
  :defer)

Projectile

Start using projectile. It has the documentation here.

(defun paf/projectile-relative-buf-name ()
  (ignore-errors
    (rename-buffer
     (file-relative-name buffer-file-name (projectile-project-root)))))

(use-package projectile
  :ensure t
  :config
  (projectile-mode 1)
  (setq projectile-sort-order 'modification-time)
  (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
  (add-hook 'find-file-hook 'paf/projectile-relative-buf-name))

(use-package persp-projectile
  :ensure t
  :after (perspective projectile)
  :requires persp-projectile)

Also make sure we do have the faster silver searcher version. This may need you to install the corresponding tool for this, with the following snippet:

if [[ "$(uname)" == "Darwin" ]]; then
  install_pkg -x ag the_silver_searcher
else
  install_pkg -x ag silversearcher-ag
fi

Search the entire project with C-c p s s for a regexp. This let’s you turn the matching results into an editable buffer using C-c C-e. Other keys are listed here.

(use-package ag
  :ensure t)

Code completion

I went with the suggestion found on this Emacs intro guide.

(use-package company
  :ensure t
  :bind (("C-." . company-complete))
  :custom
  (company-idle-delay 0) ;; I always want completion, give it to me asap
  (company-dabbrev-downcase nil "Don't downcase returned candidates.")
  (company-show-numbers t "Numbers are helpful.")
  (company-tooltip-limit 10 "The more the merrier.")
  :config
  (global-company-mode) ;; We want completion everywhere

  ;; use numbers 0-9 to select company completion candidates
  (let ((map company-active-map))
    (mapc (lambda (x) (define-key map (format "%d" x)
                        `(lambda () (interactive) (company-complete-number ,x))))
          (number-sequence 0 9))))

;; Flycheck is the newer version of flymake and is needed to make lsp-mode not freak out.
(use-package flycheck
  :ensure t
  :config
  (add-hook 'prog-mode-hook 'flycheck-mode) ;; always lint my code
  (add-hook 'after-init-hook #'global-flycheck-mode))

;; Package for interacting with language servers
(use-package lsp-mode
  :ensure t
  :commands lsp
  :config
  (setq lsp-prefer-flymake nil ;; Flymake is outdated
        lsp-headerline-breadcrumb-mode nil)) ;; I don't like the symbols on the header a-la-vscode, remove this if you like them.

header/implementation toggle

Switch from header to implementation file quickly.

(add-hook 'c-mode-common-hook
          (lambda ()
            (local-set-key  (kbd "C-c o") 'ff-find-other-file)))

no indentation of namespaces in C++

Essentially, use the Google C++ style formatting.

(use-package google-c-style
  :ensure t
  :config
  (add-hook 'c-mode-common-hook 'google-set-c-style)
  (add-hook 'c-mode-common-hook 'google-make-newline-indent))

;;(use-package flymake-google-cpplint
;;  :ensure t)

ripgrep

This enables searching recursively in projects.

install_pkg -x rg ripgrep
(use-package ripgrep
  :ensure t)
(use-package projectile-ripgrep
  :ensure t
  :requires (ripgrep projectile))

Deduplicate and sort

Help cleanup the includes and using lists. found here

(defun uniquify-region-lines (beg end)
  "Remove duplicate adjacent lines in region."
  (interactive "*r")
  (save-excursion
    (goto-char beg)
    (while (re-search-forward "^\\(.*\n\\)\\1+" end t)
      (replace-match "\\1"))))

(defun paf/sort-and-uniquify-region ()
  "Remove duplicates and sort lines in region."
  (interactive)
  (sort-lines nil (region-beginning) (region-end))
  (uniquify-region-lines (region-beginning) (region-end)))

Simplify cleanup of #include / typedef / using blocks.

(global-set-key (kbd "C-p s") 'paf/sort-and-uniquify-region)

diffing

vdiff let’s one compare buffers or files.

(use-package vdiff
  :ensure t
  :config
  ; This binds commands under the prefix when vdiff is active.
  (define-key vdiff-mode-map (kbd "C-c") vdiff-mode-prefix-map))

yasnippet / abbrev / auto-yasnippet

The key for yasnippet expansion is for me S-TAB to no clash with regular code indentation. The snippets are mode-dependent. See the full documentation.

Some of the keys are listed here. The prefix is C-c &

Commandkey after C-c &
yas-new-snippetC-n
yas-insert-snippetC-s
yas-visit-snippet-fileC-v
(use-package yasnippet
  :ensure t
  :config
  (setq yas-snippet-dirs
        (list (expand-file-name "Yasnippets" emacs-config-directory)))
  (yas-global-mode 1))

(use-package auto-yasnippet
  :ensure t
  :after yasnippet
  :config
  (setq aya-case-fold t)
  (bind-key "C-p C-s c" 'aya-create)
  (bind-key "C-p C-s e" 'aya-expand))

For the abbrev mode, that I use only for correcting typos, I set it up in emacs dir. To add an abbrev after one has typed something wrong, just use C-x a i g (add inverse global) to add the actual text that should have been written.

(use-package abbrev
  :config
  (setq abbrev-file-name "~/.emacs.d/abbrev_defs")
  (setq save-abbrevs 'silent)
  (setq-default abbrev-mode t)
  (if (file-exists-p abbrev-file-name)
      (quietly-read-abbrev-file)))

Selective display

Will fold all text indented more than the position of the cursor at the time the keys are pressed.

(defun set-selective-display-dlw (&optional level)
  "Fold text indented more than the cursor.
   If level is set, set the indent level to level.
   0 displays the entire buffer."
  (interactive "P")
  (set-selective-display (or level (current-column))))

(global-set-key (kbd "C-x $") 'set-selective-display-dlw)

Info in the gutter

Line numbers

(add-hook 'prog-mode-hook 'display-line-numbers-mode)
(setq-default display-line-number-width 3)
(global-set-key (kbd "C-p l") 'display-line-numbers-mode)

git informations

(use-package git-gutter-fringe+
  :ensure t
  :defer
  :if window-system
  :bind ("C-p g" . 'git-gutter+-mode))

Speedup VCS

Regexp matching directory names that are not under VC’s control. The default regexp prevents fruitless and time-consuming attempts to determine the VC status in directories in which filenames are interpreted as hostnames.

(defvar locate-dominating-stop-dir-regexp
  "\\`\\(?:[\\/][\\/][^\\/]+\\|/\\(?:net\\|afs\\|\\.\\.\\.\\)/\\)\\'")

Dealing with numbers

Simple way to increase/decrease a number in code.

(use-package shift-number
  :ensure t
  :bind (("M-+" . shift-number-up)
         ("M-_" . shift-number-down)))

GDB with many windows

Make it so that the source frame placement is forced only when using gdb.

(setq gdb-many-windows t)
(setq gdb-use-separate-io-buffer t)

This should display the source code always in the same window when debugging. Found on Stack Overflow.

(add-to-list 'display-buffer-alist
             (cons 'gdb-source-code-buffer-p
                   (cons 'display-buffer-use-some-window nil)))

(defun gdb-source-code-buffer-p (bufName action)
  "Return whether BUFNAME is a source code buffer and gdb is running."
  (let ((buf (get-buffer bufName)))
    (and buf
          (boundp 'gud-minor-mode)
          (eq gud-minor-mode 'gdbmi)
          (with-current-buffer buf
            (derived-mode-p buf 'c++-mode 'c-mode)))))

This was the longer and faulty version…

; This unfortunately also messes up the regular frame navigation of source code.
(add-to-list 'display-buffer-alist
             (cons 'cdb-source-code-buffer-p
                   (cons 'display-source-code-buffer nil)))

(defun cdb-source-code-buffer-p (bufName action)
  "Return whether BUFNAME is a source code buffer."
  (let ((buf (get-buffer bufName)))
    (and buf
         (with-current-buffer buf
           (derived-mode-p buf 'c++-mode 'c-mode 'csharp-mode 'nxml-mode)))))

(defun display-source-code-buffer (sourceBuf alist)
  "Find a window with source code and set sourceBuf inside it."
  (let* ((curbuf (current-buffer))
         (wincurbuf (get-buffer-window curbuf))
         (win (if (and wincurbuf
                       (derived-mode-p sourceBuf 'c++-mode 'c-mode 'nxml-mode)
                       (derived-mode-p (current-buffer) 'c++-mode 'c-mode 'nxml-mode))
                  wincurbuf
                (get-window-with-predicate
                 (lambda (window)
                   (let ((bufName (buffer-name (window-buffer window))))
                     (or (cdb-source-code-buffer-p bufName nil)
                         (assoc bufName display-buffer-alist)
                         ))))))) ;; derived-mode-p doesn't work inside this, don't know why...
    (set-window-buffer win sourceBuf)
    win))

Here is my cheatsheet for the keyboard commands:

All prefixed with C-x C-a

DomainCommandC-<key>
<l><l><c>
Breakpointsetb
temporaryt
deleted
ExecuteNextn
Step Intos
Return / Finishf
Continue (run)r
StackUp<
Down>
ExecuteUntil current lineu
(rarer)Single instructioni
Jump to current linej

vterm

(when (not (memq window-system '(mac ns)))
  (use-package vterm
    :ensure t
    :init
    (setq vterm-always-compile-module t)
    :config
    (setq vterm-module-cmake-args "-DUSE_SYSTEM_LIBVTERM=no")
    (define-key vterm-mode-map (kbd "<C-backspace>")
      (lambda () (interactive) (vterm-send-key (kbd "C-w")))))

  (use-package vterm-toggle
    :ensure t
    :after vterm))
# Needed to compile vterm first time
if [[ "$(uname -o)" == "Android" ]]; then
  install_pkg -x libtool libtool
else
  install_pkg -x libtool libtool-bin
fi
install_pkg -x cmake cmake
install_pkg -x perl perl

# Also amend the bash config
cat >> ${HOME}/.bashrc <<EOF
# Setup Emacs's VTerm communication
if [[ "\${INSIDE_EMACS}" = 'vterm' ]] \\
    && [[ -n "\${EMACS_VTERM_PATH}" ]] \\
    && [[ -f "\${EMACS_VTERM_PATH}/etc/emacs-vterm-bash.sh" ]]; then
        source "\${EMACS_VTERM_PATH}/etc/emacs-vterm-bash.sh"
fi
EOF

bazel

Adding support for Bazel

(use-package bazel
  :ensure t)

Hiding zones

This helps showing / hiding zones given a regexp.

The isearch+ is weird when interacting with it during incremental search.

(use-package zones
  :ensure t)

(use-package isearch+
  :load-path (lambda () (expand-file-name paf-lisp-directory)))

;;(use-package isearch-prop
;;  :load-path (lambda () (expand-file-name paf-lisp-directory)))

OrgMode

Load all my org stuff, but first org-mode itself.

Init

If variable org-directory is not set yet, map it to my home’s files. You may set this in the ~/.emacs to another value, e.g. (setq org-directory "/ssh:fleury@machine.site.com:OrgFiles")

NEXT This does not seem to work, check out doc about defcustom

  • State “NEXT” from [2019-06-24 Mon 10:10]

Set up org itself

(if (not (boundp 'org-directory))
    (setq org-directory "~/OrgFiles"))

(use-package org
  :ensure nil
  :config
  (add-hook 'org-mode-hook #'(lambda ()
                               (visual-line-mode)
                               (org-indent-mode))))

Packages / Helper Functions / Tools found on the web / worg

org-protocol

Let other tools use emacs client to interact.

(require 'org-protocol)

Setup on Mac

Lots of old and imprecise information found on the web. Here are the important bits done correctly (as of Oct 2021):

Set up the system to handle org-protocol URLs.

Making the system open org-protocol links with a personalized tool is done with a Script saved as an Application as described on this page. You need to find the path to emacsclient which is /Applications/Emacs.app/Contents/MacOS/bin/emacsclient if you install GNU Emacs for Max OSX.

Format the URL to send to Emacs

The browser needs to be instructed to call the given URL with some information abotu the page. This is done with a bookmarklet that consists of only Javascript. I had to inspect the source of org-protocol to find out that the current format (new-style) is a regular URL with query parameters:

org-protocol:///capture?key1=value1&key2=value2

This consists of

  1. The protocol bit org-protocol://
  2. The path bit, which maps to a set of defined sub-protocols. Pre-defined ones are store-link, capture, open-source
  3. The query args, escaped properly to not interfere with URL structure.

Each sub protocol handles a different set of query args: capture :: url (the link to the web page), title (the description of the webpage), body (the initial highlighting on that page) and template (the org capture template key to use) store-link :: url and title (can then be inserted as regular link with C-c C-l) open-source :: url which points to the file to open in a URL format (file:///home/user/.bashrc)

Should this be misformatted in some way, you might get the infamous and unfortunately unhelpful message

Greedy org-protocol handler.  Killing client.
No server buffers remain to edit.

The best way is to craft a correct URL, and call emacsclient on the command line, jsut to assert that this part is working. The browser gives absolutely no hint at what could have gone wrong.

Setup the bookmarklet

The Javascript bookmarklet should then be like this:

"org-protocol://capture?" +
           new URLSearchParams({
                 template: "W",
                 url: location.href,
                 title: document.title,
                 body: selection()})
() => {
  return window.getSelection();
}

Store this following test as the URL part of a browser bookmark.

echo "javascript:location.href=(() => { var selection=${jslib}; return ${urlexpr} })()" \
      | sed -e 's,  //.*,,g' \
      | tr '\n' ' ' \
      | sed -e 's/  */ /g' -e 's/; *;/;/g' -e 's/: /:/g' -e 's/; *}/}/g' -e 's/ *\([({}=,\+;]\) */\1/g' \
      | sed -e 's/userSelection/u/g' -e 's/clonedSelection/c/g' -e 's/range/r/g'
The capture templates

This is only needed for the capture sub-protocol. You need to define a capture template that will be used to insert the blob from what was taken on the webpage.

;; Example capture for plain capture:
(setq org-capture-templates
      `(("W" "Web Clips"
         entry (file+headline ,(org-relative "Inbox.org") "Web Clips")
         "* %:description\n%U\n[[%:link]]\n%:type %:query\n%?%:initial\n")
      ;; possibly other templates
      ))

This defines a W template and accepts several parameters, which are just a mapping from the ones passed to the URL. With the new style of URLs, this mapping is just an needless annoyance though.

URL query argtemplate parameter
url%:link
title%:description
body%:initial
template<not accessible>
%:type
%:orglink

All the other org-mode placeholders are all usable, like %U that inserts an inactive timestamp.

Make it support HTML

Seems there is a way to get the selection HTML on Get HTML Of Selection.

"org-protocol://html-capture?" +
           new URLSearchParams({
                 template: "H",
                 url: location.href,
                 title: document.title,
                 body: selection()})
Make the bookmarklet return HTML
() => {
  var userSelection, range;
  if (window.getSelection) {
    // W3C Ranges
    userSelection = window.getSelection ();
    // Get the range:
    if (userSelection.getRangeAt) {
      range = userSelection.getRangeAt (0);
    } else {
      range = document.createRange ();
      range.setStart (userSelection.anchorNode, userSelection.anchorOffset);
      range.setEnd (userSelection.focusNode, userSelection.focusOffset);
    }
    // And the HTML:
    var clonedSelection = range.cloneContents ();
    var div = document.createElement ('div');
    div.appendChild (clonedSelection);
    return div.innerHTML;
  } else if (document.selection) {
    // Explorer selection, return the HTML
    userSelection = document.selection.createRange ();
    return userSelection.htmlText;
  } else {
    return '';
  }
}
Handle the receiving side of the protocol

This is a cleaned-up version of the new style capture.

Code to process the html-capture protocol
(defun paf/html-to-org-markup (html)
  "Turns HTML markup into Org markup"
  (let ((substitutions '(("+" . " ")
                         ("\n" . " ")
                         ("<p>" . "")
                         ("</p>" . "\n")
                         ("<br/?>" . "\n")
                         ("<code>\\([^<]*\\)</code>" . "=\\1=")
                         ("</?b>" . "*")
                         ("</?i>" . "/")
                         ;; ("" . "")
                         ))
        ;; Cleanup removes unwanted remaining markup.
        (cleanup '(("</?[^>]*>" . " ")
                   ("  *" . " "))))
    (dolist (elt substitutions html)
      (setq html (replace-regexp-in-string (car elt) (cdr elt) html)))
    (dolist (elt cleanup html)
      (setq html (replace-regexp-in-string (car elt) (cdr elt) html)))))

;; An expression to test the above function
;;  (paf/html-to-org-markup "<p>Some+<b>notes</b>+<pre><code>here</code></pre></p>Q")

(defun paf/html-cleanup (html)
  (replace-regexp-in-string "+" " " html))

(defun paf/org-protocol-html-capture (info)
  "Process an org-protocol://html-capture style url with INFO.

The sub-protocol used to reach this function is set in
`org-protocol-protocol-alist'.

This function detects an URL, with the following parameters:
  javascript:location.href = 'org-protocol://html-capture?' +
     new URLSearchParams({
        template: 'W',
        url:location.href,
        title:document.title,
        body:html_selection()})
"
  (let* ((parts
          (pcase (org-protocol-parse-parameters info)
            ;; New style links are parsed as a plist.
            ((let `(,(pred keywordp) . ,_) info) info)))
         (template (or (plist-get parts :template)
                       org-protocol-default-template-key))
         (url (and (plist-get parts :url)
                   (org-protocol-sanitize-uri (plist-get parts :url))))
         (type (and url
                    (string-match "^\\([a-z]+\\):" url)
                    (match-string 1 url)))
         (title (or (paf/html-to-org-markup (plist-get parts :title)) ""))
         (html (or (paf/html-cleanup (plist-get parts :body)) ""))
         (body (or (paf/html-to-org-markup (plist-get parts :body)) ""))
         (orglink
          (if (null url) title
            (org-link-make-string url (or (org-string-nw-p title) url))))
         ;; Avoid call to `org-store-link'.
         (org-capture-link-is-already-stored t))
    ;; Only store link if there's a URL to insert later on.
    (when url (push (list url title) org-stored-links))
    (org-link-store-props :type type
                          :url url
                          :title title
                          :orglink orglink
                          :body body
                          :html html
                          :query parts)
    (raise-frame)
    (org-capture nil template)
    (message "HTML item captured.")
    ;; Make sure we do not return a string, as `server-visit-files',
    ;; through `server-edit', would interpret it as a file name.
    nil))

;; Register the new protocol
(setq org-protocol-protocol-alist
      '(("html-capture"
         :protocol "html-capture"
         :function paf/org-protocol-html-capture)))

This is also a very crude way of doing it. Better would be to detect the presence of PanDoc, and then use that. An idea is here, mostly this code:

(defun kdm/html2org-clipboard ()
  "Convert clipboard contents from HTML to Org and then paste (yank)."
  (interactive)
  (setq cmd "osascript -e 'the clipboard as \"HTML\"' | perl -ne 'print chr foreach unpack(\"C*\",pack(\"H*\",substr($_,11,-3)))' | pandoc -f html -t json | pandoc -f json -t org")
  (kill-new (shell-command-to-string cmd))
  (yank))
Setup the capture template

This new protocol can be handled with this capture template:

;; Example capture for HTML capture:
(setq org-capture-templates
      `(("H" "HTML Clips"
         entry (file+headline "Inbox.org" "HTML Clips")
         "* %:title\n%U\nurl: [[%:url]]\ntype: %:type\n****text\n?%:body\n**** html\n%:html")
      ;; possibly other templates
      ))

This defines a H template and accepts several parameters, which are just a mapping from the ones passed to the URL. This uses the new style URL, as well as no translation of the arguments.

URL query argtemplate parameterWhat is it?
url%:urlThe page’s URL
title%:titleThe page’s title
body%:bodyThe selected part, as org text
html%:htmlThe selected text, as original HTML
template<not accessible>The capture template to use
%:typeThe protocol type of the URL
%:orglinkA ready-to-insert org link to the page

All the other org-mode placeholders are all usable, e.g. %U that inserts an inactive timestamp.

Org-relative file helper function

(defun org-relative (filename)
  "Compute an expanded absolute file path for org files"
  (expand-file-name filename org-directory))

Adjust tags on the right

;; Setting this to t makes org-refile not work...
(setq org-auto-align-tags nil)
(setq org-tags-column 75)

CANCELLED Update org-set-tags-to

  • State “CANCELLED” from “TODO” [2021-09-30 Thu 10:37]
    I use the built-in stuff now.
  • State “TODO” from [2019-01-12 Sat 12:08]
=org-set-tags-to= is gone, and org-set-tags with > 1 args is not working. Not sure what to replace it with though…

Archiving

Make sure archiving preserves the same tree structure, including when archiving subtrees. This is found on github Gist from edgimar

Does not seem to work with archiving org-gcal files.

  • State “TODO” from [2021-06-03 Thu 00:28]
(load-file (expand-file-name "archive-with-ancestors.el"
                             paf-lisp-directory)
;; Set the function to use for org-archive-default  (C-c C-x C-a)
;;(setq org-archive-location (concat org-directory "/Archive/%s_archive::* Archived"))

;; Auto-save the archive buffer
(setq org-archive-subtree-save-file-p t)

;; (setq org-archive-save-context-info '(time etc.))

Refresh Agenda

Refresh org-mode agenda regularly. source on worg There are two functions that supposedly do the same.

(defun kiwon/org-agenda-redo-if-visible ()
  "Call org-agenda-redo function even in the non-agenda buffer."
  (interactive)
  (let ((agenda-window (get-buffer-window org-agenda-buffer-name t)))
    (when agenda-window
      (with-selected-window agenda-window (org-agenda-redo)))))

Agenda Files

;; Make sure org-agenda-files is defined.
(if (eq 0 (length org-agenda-files))
    (setq org-agenda-files
          (list (expand-file-name org-directory)
                (expand-file-name "Auto" org-directory)
                (expand-file-name "Meeting" paf-roam-directory))))

(defun org-get-first-agenda-file ()
  (interactive)
  (let* ((num-files (length org-agenda-files))
         (the-file (if (eq num-files 0)
                       org-directory
                     (elt org-agenda-files 0))))
    (find-file the-file)))

org-gtasks

Should follow this git repo: org-gtasks I have copied a version of the file here, it’s not yet available on MELPA.

To help debug, use this before running things: (setq request-log-level 'debug)

(use-package request
  :ensure t)
(use-package deferred
  :ensure t)
(use-package request-deferred
  :ensure t)
(load-file (expand-file-name "org-gtasks.el" paf-lisp-directory))

;; Helper to sync all task lists for an account
(defun paf/org-gtasks-sync (account_name)
  "Synchronizes all tasklists for the account with given name."
  (interactive)
  (let* ((account (org-gtasks-find-account-by-name account_name)))
    (org-gtasks-pull account "ALL")
    (org-gtasks-push account "ALL")))

I have this currently in my `~/.emacs`:

(use-package org-gtasks
  :init
  (org-gtasks-register-account
     :name "pascal"
     :directory "~/OrgFiles/GTasks/"
     :client-id "XXX"
     :client-secret "XXX"))

Search in org

org-rifle

org-rifle is the swiss-army knife for searching in an org-file.

org-super-agenda

This enables a more fine-grained filtering of the agenda items.

(use-package org-super-agenda
  :ensure t
  :config
  (org-super-agenda-mode t))

org-roam (v2)

My cheat sheet for org-roam

All keys prefixed with C-c n

FunctionC-c n <key>
<l><c>
Toggle side panell
Find/createf
Insert linki
Capturec
Graphg
Switch to bufferb
(use-package org-roam
  :ensure t
  :custom
  (org-roam-directory (file-truename paf-roam-directory))
  :init (setq org-roam-v2-ack t)
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n b" . org-roam-switch-to-buffer)
         ("C-c n f" . org-roam-node-find)
         ("C-c n c" . org-roam-capture)
         ("C-c n g" . org-roam-graph)
         ("C-c n j" . org-roam-dailies-capture-today)
         ("C-c n i" . org-roam-node-insert))
  :config
  (org-roam-db-autosync-enable)
  (require 'org-roam-protocol))

These are some extensions to make org-agenda find info in roma files in a reasonable time. Found on Magnus Therning’s website.

This is currently broken as OrgRoam V2 has changed in a non-backwards-compatible way…

  • State “DONE” from “TODO” [2021-09-27 Mon 20:42]
  • State “TODO” from [2021-07-22 Thu 16:09]
(use-package org-roam-extras
  :load-path (lambda () (expand-file-name "org-roam-extras.el" paf-lisp-directory))
  :after org-roam
  :config
  (setq roam-extras-todo-tag-name "project")
  ;; set the todo tag to roam fiels when they contain tasks
  (add-hook 'find-file-hook #'roam-extras/update-todo-tag)
  (add-hook 'before-save-hook #'roam-extras/update-todo-tag)
  ;; update the agenda-files just before constructing the agenda
  (advice-add 'org-agenda :before #'roam-extras/add-todo-files)
  (advice-add 'org-agenda :after #'roam-extras/restore-todo-files)
  )

EmacSQL will need to get its C-binary compiled, and needs supporting tools. Note that ‘tcc’ for Termux seems not complete enough for the job.

# org-roam needs this binary
if [[ "$(uname -o)" == "Android" ]]; then
    install_pkg -x sqlite3 sqlite
else
    install_pkg -x sqlite3 sqlite3
fi
# Make sure there is a C compiler for emacsql-sqlite
[[ -n "$(which cc)" ]] || install_pkg -x cc clang

org-ref

(use-package org-ref
  :ensure t)

org-clock-convenience

(use-package org-clock-convenience
  :ensure t
  :bind (:map org-agenda-mode-map
           ("<S-right>" . org-clock-convenience-timestamp-up)
           ("<S-left>" . org-clock-convenience-timestamp-down)
           ("[" . org-clock-convenience-fill-gap)
           ("]" . org-clock-convenience-fill-gap-both)))

org-kanban

(use-package org-kanban
  :ensure t)

org-board

Archive entire sites locally with `wget`.

(use-package org-board
  :ensure t
  :config
  (global-set-key (kbd "C-c o") org-board-keymap))

This is the needed tool used to fetch a URL’s content.

# wget used for org-board archiving.
install_pkg -x wget wget

org-reveal

This presentation generator is still under review (by me).

# Install reveal.js
if [[ -d "${HOME}/reveal.js" ]]; then
  echo "Reveal already installed"
else
  (cd ~/ && git clone https://github.com/hakimel/reveal.js.git)
fi
(use-package ox-reveal
  :ensure t
  :after (htmlize)
  :config
  (setq org-reveal-root (expand-file-name "~/reveal.js")))

(use-package htmlize
  :ensure t)

org-crypt

(use-package org-crypt
  :config
  (org-crypt-use-before-save-magic)
  (setq org-tags-exclude-from-inheritance '("crypt")))

iimage (M-I)

Make the display of images a simple key-stroke away.

(defun paf/org-toggle-iimage-in-org ()
  "display images in your org file"
  (interactive)
  (if (face-underline-p 'org-link)
      (set-face-underline 'org-link nil)
    (set-face-underline 'org-link t))
  (iimage-mode 'toggle))

(use-package iimage
  :config
  (add-to-list 'iimage-mode-image-regex-alist
               (cons (concat "\\[\\[file:\\(~?" iimage-mode-image-filename-regex
                             "\\)\\]")  1))
  (add-hook 'org-mode-hook (lambda ()
                             ;; display images
                             (local-set-key "\M-I" 'paf/org-toggle-iimage-in-org)
                            )))

Properties collector

Collect properties into tables. See documentation in the file.

(load-file (expand-file-name "org-collector.el" paf-lisp-directory))

Set date as header for org-gcal event

(defun paf/org-timestamp-in-entry ()
  "Find the first timestamp in an entry.

Returns the timestamp or nil of none was found."
  (interactive)
  (let* ((org-elem (save-excursion
                     (org-back-to-heading)
                     (org-element-at-point)))
         (org-heading (plist-get org-elem 'headline))
         (elem-min (plist-get org-heading :begin))
         (elem-max (plist-get org-heading :end))
         (timestamp-re (org-re-timestamp 'all)))
    (save-excursion
      (goto-char elem-min)
      (if (re-search-forward timestamp-re elem-max t)
          (let ((match-pos (match-beginning 0)))
            (goto-char match-pos)
            (org-element-timestamp-parser)
            ;;(plist-get (org-element-timestamp-parser) 'timestamp)
            )))))

(defun paf/replace-current-heading (new-text)
  "Replace heading at point by a new text."
  (interactive "sNew Title: ")
  (when (org-at-heading-p)
    (let ((hl-text (nth 4 (org-heading-components))))
      (unless (or (null hl-text)
                  (org-string-match-p "^[ \t]*:[^:]+:$" hl-text))
        (beginning-of-line)
        (search-forward hl-text (point-at-eol))
        (replace-string hl-text new-text
         nil (- (point) (length hl-text)) (point))))))

(defun paf/org-gcal-date-as-heading ()
  "This will get the org-gcal date and replace the heading with it."
  (interactive)
  (save-excursion
    (let* ((time-desc (org-gcal--get-time-and-desc))
           (start-date (plist-get time-desc :start))
           (elem-timestamp (paf/org-timestamp-in-entry))
           (formatted-date
            (if start-date
                (org-gcal--format-iso2org start-date org-gcal-local-timezone)
              (org-timestamp-format elem-timestamp (org-time-stamp-format t t)))))
      (org-back-to-heading)
      (paf/replace-current-heading (format "[%s]"
                                           (substring formatted-date 1 (- (length formatted-date) 1)))))
    ))

My Setup

These are mostly org-config specific to me, myself and I.

Key mappings

(global-set-key (kbd "C-c l") 'org-store-link)
(global-set-key (kbd "C-c c") 'org-capture)
(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c b") 'org-iswitchb)
;; added because on Chromoting/i3, Alt-<up> and Alt-<down> are changin window focus.
(add-hook 'org-mode-hook
          (lambda ()
            (local-set-key (kbd "C-<up>") 'org-move-subtree-up)
            (local-set-key (kbd "C-<down>") 'org-move-subtree-down)
            (local-set-key (kbd "C-c l") 'org-store-link)
            (local-set-key (kbd "C-c C-l") 'org-insert-link)))

Colors and faces

This colors the code blocks a bit more visibly.

(set-face-attribute 'org-block-begin-line nil :background "gray17")
(set-face-attribute 'org-block            nil :background "gray23")
(set-face-attribute 'org-block-end-line   nil :background "gray17")

This renders property drawers less “in your face”

(set-face-attribute 'org-drawer   nil
                    :foreground "gray50" ;; "RoyalBlue3"
                    :height 0.7)

This makes the all-day events stand-out and be displayed before the calendar.

(set-face-attribute 'org-agenda-calendar-sexp nil
                    :foreground "dark orange")
;;(setq org-sort-agenda-notime-is-late nil)

Links by ID

(setq org-id-link-to-org-use-id 'create-if-interactive)

Display settings

my config for display.

(setq org-hide-leading-stars 't)
(setq org-log-done 't)
(setq org-startup-folded 't)
(setq org-startup-indented 't)
(setq org-startup-folded 't)
(setq org-ellipsis "...")

(setq org-time-stamp-formats '("<%Y-%m-%d %a>" . "<%Y-%m-%d %a %H:%M>"))
(setq org-time-stamp-custom-formats '("<%Y-%m-%d %a>" . "<%Y-%m-%d %a %H:%M>"))
(use-package org-indent
  :ensure nil
  :custom
  (org-indent-indentation-per-level 2))

org-habit

(use-package org-habit
  :config
  (setq org-habit-graph-column 38)
  (setq org-habit-preceding-days 35)
  (setq org-habit-following-days 10)
  (setq org-habit-show-habits-only-for-today nil))

bash command

(setq org-babel-sh-command "bash")

org-clock properties

clock stuff into a drawer.

(setq org-log-into-drawer t)
(setq org-clock-into-drawer "CLOCK")

open first agenda file

F12 open the first agenda file

(global-set-key [f12] 'org-get-first-agenda-file)
; F12 on Mac OSX displays the dashboard, so add Control F12
(global-set-key [C-f12] 'org-get-first-agenda-file)

org-secretary

This is my version of the org-secretary. I don’t use it anymore.

(use-package paf-secretary
  :load-path paf-lisp-directory
  :bind (("\C-cw" . paf-sec-set-with)
         ("\C-cW" . paf-sec-set-where)
         ("\C-cj" . paf-sec-tag-entry))
  :config
  (setq paf-sec-me "paf"))

(setq org-tag-alist '(("PRJ" . ?p)
                      ("DESIGNDOC" . ?D)
                      ("Milestone" . ?m)
                      ("DESK" . ?d)
                      ("HOME" . ?h)
                      ("VC" . ?v)))

task tracking

Track task dependencies, and dim them in the agenda.

(setq org-enforce-todo-dependencies t)
(setq org-agenda-dim-blocked-tasks 'invisible)

effort & columns mode

(setq org-global-properties
      '(("Effort_ALL". "0 0:10 0:30 1:00 2:00 4:00 8:00 16:00")))
(setq org-columns-default-format
      "%TODO %30ITEM %3PRIORITY %6Effort{:} %10DEADLINE")

org-todo keywords

(setq org-todo-keywords '(
   (sequence "TODO(t!)" "NEXT(n!)" "STARTED(s!)" "WAITING(w!)" "AI(a!)" "|" "DONE(d!)" "CANCELLED(C@)" "DEFERRED(D@)" "SOMEDAY(S!)" "FAILED(F!)" "REFILED(R!)")
   ;; For publications
   (sequence "APPLIED(A!)" "WAITING(w!)" "ACCEPTED" "|" "REJECTED" "PUBLISHED")
   ;; For tracking other's tasks
   (sequence "TASK(m!)" "ACTIVE" "|" "DONE(d!)" "CANCELLED(C@)" )
))

(setq org-tags-exclude-from-inheritance '("PRJ" "REGULAR")
      org-use-property-inheritance '("PRIORITY")
      org-stuck-projects '("+PRJ/-DONE-CANCELLED"
                           ;; it is considered stuck if there is no next action
                           ("NEXT" "STARTED" "TASK") ()))

(setq org-todo-keyword-faces
      '(
        ("TODO" . (:foreground "purple" :weight bold))
        ("TASK" . (:foreground "steelblue" :weight bold))
        ("NEXT" . (:foreground "red" :weight bold))
        ("STARTED" . (:foreground "green" :weight bold))
        ("WAITING" . (:foreground "orange" :weight bold))
        ("SOMEDAY" . (:foreground "steelblue" :weight bold))
        ("REFILED" . (:foreground "gray"))
        ("MAYBE" . (:foreground "steelblue" :weight bold))
        ("AI" . (:foreground "red" :weight bold))
        ("NEW" . (:foreground "orange" :weight bold))
        ("RUNNING" . (:foreground "orange" :weight bold))
        ("WORKED" . (:foreground "green" :weight bold))
        ("FAILED" . (:foreground "red" :weight bold))
        ;; For publications
        ("APPLIED" . (:foreground "orange" :weight bold))
        ("ACCEPTED" . (:foreground "orange" :weight bold))
        ("REJECTED" . (:foreground "red" :weight bold))
        ("PUBLISHED" . (:foreground "green" :weight bold))
        ;; Other stuff
        ("ACTIVE" . (:foreground "darkgreen" :weight bold))
        ("FLAG_GATED" . (:foreground "orange" :weight bold))
        ))

org-agenda

views

(setq org-agenda-block-separator #x2500)
(setq org-agenda-custom-commands
      '(("t" "Hot Today" ((agenda nil ((org-agenda-span 'day)
                                      (org-agenda-skip-function
                                       '(org-agenda-skip-entry-if 'deadline))
                                      ))
                          (agenda nil
                                  ((org-agenda-entry-types '(:deadline))
                                   (org-agenda-span 'day)
                                   (org-agenda-time-grid nil)
                                   (org-agenda-show-all-dates nil)
                                   (org-agenda-format-date "")
                                   (org-deadline-warning-days 7)
                                   (org-agenda-overriding-header "  Deadlines")))
                          (tags-todo "/WAITING"
                                     ((org-agenda-overriding-header "  Waiting")))
                          (tags-todo "/STARTED"
                                     ((org-agenda-overriding-header "  Ongoing")))
                          (tags-todo "/NEXT"
                                     ((org-agenda-overriding-header "  Upcoming")))))
        ("T" "Team Today" ((agenda "" ((org-agenda-span 'day)))
                           (tags-todo "with={.+}"
                                    ((org-super-agenda-groups
                                      '((:auto-property "with")))
                                     (org-agenda-overriding-header "  Talking Points with:"))
                                    )))
        ("r" "Recurring" ((tags "REGULAR"
                                ((org-agenda-overriding-header "\nRecurring\n")))
                          (tags-todo "/WAITING"
                                     ((org-agenda-overriding-header "\nWaiting\n")))
                          (tags-todo "TODO=\"STARTED\""
                                     ((org-agenda-overriding-header "\Ongoing\n")))
                          (tags-todo "/NEXT"
                                     ((org-agenda-overriding-header "\nUpcoming\n")))))
        ("n" "Agenda and all TODO's" ((agenda "")
                                      (alltodo "")))
        ("N" "Next actions" tags-todo "-dowith={.+}/!-TASK-TODO"
         ((org-agenda-todo-ignore-scheduled t)))
        ("h" "Work todos" tags-todo "-dowith={.+}/!-TASK"
         ((org-agenda-todo-ignore-scheduled t)))
        ("H" "All work todos" tags-todo "-personal/!-TASK-CANCELLED"
         ((org-agenda-todo-ignore-scheduled nil)))
        ("A" "Work todos with doat or dowith" tags-todo
         "dowith={.+}/!-TASK"
         ((org-agenda-todo-ignore-scheduled nil)))

        ("p" "Tasks with current WITH and WHERE"
         ((tags-todo (paf-sec-replace-with-where "with={$WITH}" ".+")
                     ((org-agenda-overriding-header
                       (paf-sec-replace-with-where "Tasks with $WITH in $WHERE" "anyone" "any place"))
                      (org-super-agenda-groups
                       '((:name "" :pred paf-sec-limit-to-with-where)
                         (:discard (:anything t)))))
                     )))
        ("j" "TODO dowith and TASK with"
         ((org-sec-with-view "TODO dowith")
          (org-sec-stuck-with-view "TALK with")
          (org-sec-where-view "TODO doat")
          (org-sec-assigned-with-view "TASK with")
          (org-sec-stuck-with-view "STUCK with")
          (todo "STARTED")))
        ("J" "Interactive TODO dowith and TASK with"
         ((org-sec-who-view "TODO dowith")))))

(setq org-agenda-skip-deadline-prewarning-if-scheduled 2)

Display location in agenda

From some help on this page I think this could work:

  (defun paf/replace-many-regexp-in-string (text substitutions)
    "Replaces a set of regexps in the text"
    (dolist (elt substitutions text)
      (setq text (replace-regexp-in-string (car elt) (cdr elt) text))))

  (defcustom paf/my-location "ZRH"
    "Location of the office, to filter agenda locations"
    :type 'string
    :group 'paf)

  (require 'seq)
  (defun paf/cleanup-location (loc)
    "Remove unneeded fluff around location"
    (let* ((clean-loc (paf/replace-many-regexp-in-string loc '(("([^)]*)" . "")
                                                               ("\\[[^]]+\\]" . ""))))
           (locs (seq-filter (lambda (e) (string-match-p (regexp-quote paf/my-location) e))
                             (split-string clean-loc ",")))
           (full-loc (mapconcat (lambda (e) (string-trim e)) locs ", ")))
      (paf/replace-many-regexp-in-string full-loc '(("CH-ZRH" . "ZRH")
                                                    ("\\([0-9]+\\)-\\([[a-zA-Z]+]\\)" . "\\1\\2")
                                                    (" *$" . "")))))

  ;; for testing
  ;; (paf/cleanup-location "CH-ZRH-100-2-2-Riff Raff (2) [GVC, Phone], NYC-9TH-5-F - Packrat [GVC]")

  (defun paf/org-agenda-get-location ()
    "Gets the value of the LOCATION property"
    (let* ((loc (org-entry-get (point) "LOCATION"))
           (clean-loc (if loc (paf/cleanup-location loc) "")))
      (if (> (length clean-loc) 0)
          (concat "[" clean-loc "] ")
        "")))

  (defun paf/org-agenda-get-location-print ()
    "Just prints the value of the extracted location to the echo area.
Used for debugging."
    (interactive)
    (message (concat "'" (paf/org-agenda-get-location) "'")))

Also, to set this after org-mode has loaded (see here):

(with-eval-after-load 'org-agenda
  (add-to-list 'org-agenda-prefix-format
               '(agenda . "  %-12:c%?-12t%(paf/org-agenda-get-location)")))

text formatting

These are some improved rendering of the calendar view.

First, d12frosted’s post handles the category length:

(require 'org-roam-extras)
(defun paf/agenda-category (len)
  (paf/truncate-string (roam-extras/extract-agenda-category) len))
  ;;(paf/truncate-string (or (org-get-category) "") len))

(with-eval-after-load 'org-agenda
  (setq org-agenda-prefix-format
        '((agenda . " %i %-12(paf/agenda-category 12)%?-12t") ;%(paf/org-agenda-get-location)
          (todo . " %i %-12(paf/agenda-category 12)")
          (tags . " %i %-12(paf/agenda-category 12)")
          (search . " %i %-12(paf/agenda-category 12)"))))

(setq org-agenda-prefix-format ‘((agenda . ” %i %-12(paf/agenda-category 12)%?-12t%(paf/org-agenda-get-location)% s”) (todo . ” %i %-12(paf/agenda-category 12)”) (tags . ” %i %-12(paf/agenda-category 12)”) (search . ” %i %-12(paf/agenda-category 12)”)))

Displaying a grid of 3 hours seems better, as show by AbstProcDo’s post:

(setq org-agenda-time-grid
      (quote ((daily today remove-match)
              (600 900 1200 1500 1800 2100)
              " ……………"
              "" ;; ———————————————"
              )))

colors and faces

Make the calendar day info a bit more visible and contrasted.

;; Faces to make the calendar more colorful.
(custom-set-faces
 '(org-agenda-current-time ((t (:inherit org-time-grid :foreground "yellow" :weight bold))))
 '(org-agenda-date ((t (:inherit org-agenda-structure :background "pale green" :foreground "black" :weight bold))))
 '(org-agenda-date-weekend ((t (:inherit org-agenda-date :background "light blue" :weight bold)))))

now marker

A more visible current-time marker in the agenda

(setq org-agenda-current-time-string ">>>>>>>>>> NOW <<<<<<<<<<")

auto-refresh

;; will refresh it only if already visible
(run-at-time nil 180 'kiwon/org-agenda-redo-if-visible)

auto-save org files when idle

This will save them regularly when the idle for more than 10 minutes. It will also not run if when the lambda gets called the idle time is then less than 10 minutes.

(add-hook 'org-mode-hook
          (lambda () (run-with-idle-timer 600 t
                                          (lambda () (if (> (current-idle-time) 600)
                                                         'org-save-all-org-buffers)))))

export

That’s the export function to update the agenda view.

(setq org-agenda-exporter-settings
      '((ps-number-of-columns 2)
        (ps-portrait-mode t)
        (org-agenda-add-entry-text-maxlines 5)
        (htmlize-output-type 'font)))

(defun dmg-org-update-agenda-file (&optional force)
  (interactive)
  (save-excursion
    (save-window-excursion
      (let ((file "~/www/agenda/agenda.html"))
        (org-agenda-list)
        (org-agenda-write file)))))

org-duration

(use-package org-duration
  :config
  (setq org-duration-units
        `(("min" . 1)
          ("h" . 60)
          ("d" . ,(* 60 8))
          ("w" . ,(* 60 8 5))
          ("m" . ,(* 60 8 5 4))
          ("y" . ,(* 60 8 5 4 10)))
        )
  (org-duration-set-regexps))

OrgRoam templates

(setq org-roam-capture-templates `(
   ("n" "Note" entry
    "* %?"
    :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                       "#+title: ${title}\n#+filetags: draft\n\n\n")
    :unnarrowed t)
   ("m" "Meeting Collection" entry
    "* %?%^{type}\n"
    :if-new (file+head "Meeting/%<%Y%m%d%H%M%S>-${slug}.org"
                       "#+title: ${title}\n#+filetags: meeting\n\n\n")
    :unnarrowed t)
   ("r" "Reference" entry
    "* %?\n%U\n\n"
    :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                       "#+title: ${title}\n#+filetags: reference\n\n\n")
    :unnarrowed t)
))

Capture & refile

Capture and refile stuff, with some templates that I think are useful.

Capture

Very nice post on how to get capture templates from a file: Org-capture in Files.

(setq org-default-notes-file (org-relative "Inbox.org"))

(setq org-capture-templates
      `(("t" "Task"
         entry (file+headline ,(org-relative "Inbox.org") "Tasks")
         "* TODO %?\n%U\n\n%x"
         :clock-resume t)
        ;;
        ("i" "Idea"
         entry (file+headline ,(org-relative "Inbox.org") "Ideas")
         "* SOMEDAY %?\n%U\n\n%x"
         :clock-resume t)
        ;;
        ("m" "Meeting"
         entry (file+headline ,(org-relative "Inbox.org") "Meetings")
         "* %?  :MTG:\n%U\n%^{with}p"
         :clock-in t
         :clock-resume t)
        ;;
        ("s" "Stand-up"
         entry (file+headline ,(org-relative "Inbox.org") "Meetings")
         "* Stand-up  :MTG:\n%U\n\n%?"
         :clock-in t
         :clock-resume t)
        ;;
        ("1" "1:1"
         entry (file+headline ,(org-relative "Inbox.org") "Meetings")
         "* 1:1 %^{With}  :MTG:\n%U\n:PROPERTIES:\n:with: %\\1\n:END:\n\n%?"
         :clock-in t
         :clock-resume t)
        ;;
        ("p" "Talking Point"
         entry (file+headline ,(org-relative "Inbox.org") "Talking Points")
         "* AI %?  :TALK:\n%U\n%^{with}p"
         :clock-keep t)
        ;;
        ("j" "Journal"
         entry (file+olp+datetree ,(org-relative "journal.org"))
         "* %?\n%U"
         :clock-in t
         :clock-resume t
         :kill-buffer t)
        ;;
        ("W" "Web Clips"
         entry (file+headline ,(org-relative "Inbox.org") "Web Clips")
         "* %:description\n%U\n[[%:link]]\n%:type %:orglink\n%?%:initial\n")
        ;;
        ("H" "HTML Clips"
         entry (file+headline ,(org-relative "Inbox.org") "Web Clips")
         "* %:title\n%U\n%:orglink\n\n%?%:body\n")
        ;;
        ))

Refile

;; show up to 2 levels for refile targets, in all agenda files
(setq org-refile-targets '((org-agenda-files :maxlevel . 2)))
(setq org-log-refile 'time)

;; from: http://doc.norang.ca/org-mode.html
;; Exclude DONE state tasks from refile targets
(defun bh/verify-refile-target ()
  "Exclude todo keywords with a done state from refile targets"
  (not (member (nth 2 (org-heading-components)) org-done-keywords)))

(setq org-refile-target-verify-function 'bh/verify-refile-target)
Implement my own AI refiling.
(require 'bookmark)

(defun paf/refile-ai-targets ()
  "Does an org-refile-copy of an AI, with some linking and side-effects.

        This does add a link to the original =AI= in the new task, as
        well as mark the =AI= as done, and change the refiled target to a
        =TODO=."
  (interactive)
  (org-back-to-heading)
  ;; without this, org-refile fails to setup bookmarks... go figure.
  (bookmark-maybe-load-default-file)
  (let* ((orig-marker (point-marker))
         ;; make sure the to-be-filed entry has an ID
         (orig-id (org-id-get-create)))
    (save-window-excursion
      (org-refile 3 nil nil "Move AI")  ;; 3 => does a copy and not a move
      ;; Now we are back at the orig location, so force change the ID
      ;; (as it has been copied)
      (org-id-get-create t) ;; t => force change
      (org-todo 'done)
      ;; Now take a link with the new forced ID,
      ;; go to the refiled location, and modify the
      ;; refiled entry.
      (let ((orig-link (org-store-link nil)))
        (org-refile-goto-last-stored)
        (org-todo "TODO")
        (org-entry-put (point) "AI" orig-link)))))

(with-eval-after-load 'org
  (bind-key "C-p a" #'paf/refile-ai-targets org-mode-map))

org-babel

What kind of code block languages do I need

(setq org-confirm-babel-evaluate 'nil) ; Don't ask before executing

(org-babel-do-load-languages
 'org-babel-load-languages
 '(
   (R . t)
   (dot . t)
   (emacs-lisp . t)
   (gnuplot . t)
   (python . t)
   (ledger . t)
   ;;(sh . t)
   (latex . t)
   (shell . t)
  ))

org-export

Add a few formats to the export functionality of org-mode.

(use-package ox-odt
  :defer)
(use-package ox-taskjuggler
  :defer)
(use-package ox-impress-js
  :defer)

plant-uml

Tell where PlantUML is to be found. This needs to be downloaded and installed separately, see the PlantUML website.

You could install the PlantUML JAR file with this snippet:

# Get a version of the PlantUML jar file.
install_pkg -x dot graphviz  # for some diagrams
install_pkg -x wget wget
URL='http://sourceforge.net/projects/plantuml/files/plantuml.jar/download'
DIR="${HOME}/Apps"
if [[ ! -e "${DIR}/plantuml.jar" ]]; then
    [[ -d "${DIR}" ]] || mkdir -p "${DIR}"
    (cd "${DIR}" && wget -O plantuml.jar "${URL}")
    ls -l "${DIR}/plantuml.jar"
fi
(use-package plantuml-mode
  :ensure t
  :config
  (setq plantuml-jar-path "~/Apps/plantuml.jar")
  (setq org-plantuml-jar-path "~/Apps/plantuml.jar")
  ;; Let us edit PlantUML snippets in plantuml-mode within orgmode
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
  ;; make it load this language (for export ?)
  (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))
  ;; and re-display images after update
  (add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images)
  ;; Enable plantuml-mode for PlantUML files
  (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)))

yankpad

Check out the blog post (and the follow-up) and the package docs.

(use-package yankpad
  :ensure t
  :defer
  :init
  (setq yankpad-file (org-relative "Templates/yankpad.org"))
  :config
  (bind-key "C-p y m" 'yankpad-map)
  (bind-key "C-p y e" 'yankpad-expand))

One-time Initial Setup

I have my config in directory ~/Emacs which is where I clone this repository. The config setup is maintained purely in the ~/Emacs/emacs_setup.org file.

To bootstrap the process on a new machine, all you need is to run

cd <dir-where-you-expanded-the-git-repository>
bash ./onetime_setup.sh

In your ~/.emacs file, all you need to add is

;; Setup your Org directory
(setq org-directory "~/OrgFiles")

(load-file "~/.emacs.d/init.el")

Script to bootstrap the whole thing on a new system

# Make git ignore the tangled & updated emacs_setup.el
GIT_ROOT=$(dirname $0)
source ${GIT_ROOT}/bash/install.sh

if [[ -z "$(which git)" ]]; then
    echo "You might need 'git' for this to work ! (how did you get here?)"
    exit 1
fi
if  [[ -z "$(which emacs)" ]]; then
    echo "You might need 'emacs' for this to be useful ! Installing..."
    install_pkg -x emacs emacs
fi

# Maybe this is a new install, .emacs does not exist
for file in ~/.emacs ~/.emacs.d/custom.el; do
    test -e ${file} || mkdir -p $(dirname ${file}) && touch ${file}
done

# Initial tangle of files, saying no to vterm compilation
echo "Initial tangling..."
(cd ${GIT_ROOT} && emacs --batch --load "lisp/first_time_tangle.el")

# Add the load-file as the first thing in the user's ~/.emacs
# If not yet added.
declare lines=$(grep ';; lisp/dot_emacs.el' ~/.emacs | wc -l)
if (( lines < 1 )); then
    echo "Setup .emacs ..."
    echo ";; lisp/dot_emacs.el" > ~/.emacs.new
    cat "${GIT_ROOT}/lisp/dot_emacs.el" >> ~/.emacs.new
    cat ~/.emacs >> ~/.emacs.new
    mv ~/.emacs.new ~/.emacs
      echo "Added loading the config in your ~/.emacs"
else
    echo "Config in your ~/.emacs already set up!"
fi

# Install system dependencies from the tangled script
echo "Checking dependencies"
bash ${GIT_ROOT}/bash/install_deps.sh

# Load the init, let it install whatever is missing.
echo "Get Emacs to load fist time..."
emacs --batch --load "~/.emacs"

This script is then used to install the needed packages on the system.

set -e

# Trick to make it work on Termux
#which "ls" || pkg install debianutils

# This is a bit of heuristics to find out what the install system is
# They are attempted in this order, put the least likely first.
declare -a PKG_MGRS=("pkg" "brew" "apt-get")

PKG_PREFIX_apt_get="sudo"
PKG_POSTFIX_apt_get="-y"

for pkg in "${PKG_MGRS[@]}"; do
    if [[ -x "$(which ${pkg})" ]]; then
        INSTALLER="${pkg}"
        break
    fi
done
if [[ -z "${INSTALLER}" ]]; then
    echo "Did not find a suitable installer (tried ${PKG_MGRS[@]})"
    exit 1
fi

# This is the function to call to install anything. It can optionally
# check for a binary and avoid installing if it's found.  install_pkg
# [-x <binary>] <package>
function install_pkg() {
    if [[ "$1" == "-x" ]]; then
        local binary="$(which $2)"
        if [[ -n "${binary}" && -x "${binary}" ]]; then
            echo "Found $2 (${binary}), nothing to install for $3."
            return
        fi
        shift 2
    fi

    local token=$(echo -n ${INSTALLER} | tr -c '0-9a-zA-Z_' '_')
    local prefix_var="PKG_PREFIX_${token}"
    local postfix_var="PKG_POSTFIX_${token}"

    echo "Trying: ${INSTALLER} install $*"
    ${!prefix_var} $(which ${INSTALLER}) ${!postfix_var} install "$@"
}

detect the system better

  • State “TODO” from [2021-02-03 Wed 16:57]
currently it does not a good job of figuring out the diff between OSX, Linux, Termux (Android) and Windows.

Recompile all packages

This will force-recompile everything in ~/.emacs.d/elpa/... Just run M-: and then enter this:

(byte-recompile-directory package-user-dir nil 'force)

or simply C-x C-e at the end of that line.

The End

(load-file custom-file)
;; ============ end of emacs_setup.org ====================
;;  (profiler-stop)
;;  (profiler-report)

About

My personal Emacs configs, consisting mostly of configuration of free tools.

Resources

License

Releases

No releases published

Packages

No packages published