My Emacs Config

One config to rule them all

This is my Emacs config for home and work.
Feel free to borrow, copy and hack … I did it myself from different sources!
Hint: To edit lisp blocks use C-c '

Setup of init.el

To get this initialization in orgmode format to work place this snippet in init.el.

(require 'org)
  (org-babel-load-file
  (expand-file-name "config.org"
                    "~/myorg/assets"))

I do not keep theming stuff in my config.org, because it's not workflow related. It just makes Emacs looks nicer.

Package repos

Setup repos

Debian 10 needs this variable to work.

(if my/home
    (setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3"))

Now let's setup repos.

(require 'package)
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))

Use-Package

Install Use-Package, then use it for other packages.

(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(eval-when-compile
      (require 'use-package))

Quelpa

Install Quelpa and Quelpa-Use-Package. Do not forget, that you have to have git installed on the system. Otherwise fetching will not work.

(setq quelpa-checkout-melpa-p nil) ;; use quelpa only for packages on MELPA

(unless (package-installed-p 'quelpa)
  (with-temp-buffer
    (url-insert-file-contents "https://raw.githubusercontent.com/quelpa/quelpa/master/quelpa.el")
    (eval-buffer)
    (quelpa-self-upgrade)))

(quelpa
 '(quelpa-use-package
   :fetcher git
   :url "https://github.com/quelpa/quelpa-use-package.git"))
(require 'quelpa-use-package)

Basics

What system are we running on?

At work I use Windows and at home GNU/Linux.

(defvar my/work (eq system-type 'windows-nt))
(defvar my/home (eq system-type 'gnu/linux))

Interface

Get the interface cleaner.

(setq inhibit-startup-screen t)
(setq inhibit-startup-echo-area-message t)
(setq inhibit-startup-message t)   
(tool-bar-mode 0)
(tooltip-mode  0)
(menu-bar-mode 0)
(scroll-bar-mode 0)
(defalias 'yes-or-no-p 'y-or-n-p)
(global-visual-line-mode 1)
;;(global-hl-line-mode 1)

Unbind Arrow keys, let's try to learn default bindings.

(defun nope () (interactive) (message "Nope!"))

(dolist (key '("<left>" "<right>" "<up>" "<down>"))
  (define-key (current-global-map) (kbd key) 'nope))

Get backups out of the way and remove littering in auto-save-list

Disable backups and auto-save.

(setq auto-save-default nil)
(setq make-backup-files nil)

If not disabled, get them out of the way.

(setq auto-save-list-file-prefix nil)

(if my/work
    (setq temporary-file-directory "C:/Users/khajvaz/AppData/Local/Temp"))

(setq backup-directory-alist
      `((".*" . ,temporary-file-directory)))
(setq auto-save-file-name-transforms
      `((".*" ,temporary-file-directory t)))

Helper functions and bindings

Automaticaly insert matching delimiters.

(electric-pair-mode 1)

Custom Bookmarks, so that they are some on all machines. Do not forget running bookmarks-save after editing bookmarks.

(setq bookmark-default-file "~/myorg/assets/mybookmarks")

Interactive call to revert-buffer. Ignoring the auto-save file and not requesting for confirmation. When the current buffer is modified, the command refuses to revert it, unless you specify the optional argument: force-reverting to true.

(global-set-key
 (kbd "<f5>")
 (lambda (&optional force-reverting)
   (interactive "P")
   ;;(message "force-reverting value is %s" force-reverting)
   (if (or force-reverting (not (buffer-modified-p)))
       (revert-buffer :ignore-auto :noconfirm)
     (error "The buffer has been modified."))))

Spelling

Usage:

  • Use f11 to set dictionary.
  • Press f12 to check spelling in the buffer.
  • Press f10 to go to the next spelling error, ispell shows corrections that be chosen. If not needed skip with SPC.
  • Also if you set the cursor on the spelling error you can use f10 to correct it.
  • If using a mouse, choose the word with the wheel. Then you get correction proposals.
(global-set-key (kbd "<f12>") 'flyspell-buffer)
(global-set-key (kbd "<f11>") 'fd-switch-dictionary)
(global-set-key (kbd "<f10>") 'flyspell-check-next-highlighted-word)

(setq ispell-curent-dictionary "deutsch")
(setq ispell-local-dictionary "deutsch")

(if my/work
    (setq ispell-program-name "~/tools/hunspell/bin/hunspell.exe"))

(defun fd-switch-dictionary()
      (interactive)
      (let* ((dic ispell-current-dictionary)
         (change (if (string= dic "deutsch") "english" "deutsch")))
        (ispell-change-dictionary change)
        (message "Dictionary switched from %s to %s" dic change)))

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

Org Mode

Basics

(setq org-directory "~/myorg")
(add-to-list 'org-modules 'org-habit t)
(setq org-startup-indented t)
(setq org-hide-emphasis-markers t)
(setq org-startup-folded t)
(setq org-log-into-drawer t)
(setq org-todo-keywords
      '((sequence "TODO(t)" "PROG(i/!)" "WAIT(w/!)" "|" "DONE(d/!)" "CANC(c/!)")))

Linking

Classic linking

Generate IDs, so that the links to do not brake.

(require 'org-id)
(setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
(define-key global-map "\C-cl" 'org-store-link)

Backlinks

I am using the package org-super-links and for preview I use the additional package org-super-links-peek.

(use-package org-super-links
  :quelpa (org-super-links :repo "toshism/org-super-links" :fetcher github)
  :bind
  (("C-c s s" . org-super-links-link)
   ("C-c s l" . org-super-links-store-link)
   ("C-c s C-l" . org-super-links-insert-link)
   ("C-c s d" . org-super-links-delete-link))
  :config
  (setq org-super-links-related-into-drawer t))

(use-package org-super-links-peek
  :quelpa (org-super-links-peek :repo "toshism/org-super-links-peek" :fetcher github)
  :bind (("C-c s p" . org-super-links-peek-link)))

Capture

My templates for work and private stuff. Captured Tasks and Notes go in the Inbox and are refiled later.

(define-key global-map (kbd "C-c c") 'org-capture)

(defun create-blog-post ()
          "Create an org file in ~/source/myblog/posts."
          (interactive)
          (let ((name (read-string "Filename: ")))
            (expand-file-name (format "%s.org" name) "~/Nextcloud/Documents/blog/src/posts/")))

(setq org-capture-templates
      '(("t" "Task" entry (file "~/myorg/inbox.org")
         "* TODO %?\n")
        ("n" "Note" entry (file "~/myorg/inbox.org")
                 "* %?\n:PROPERTIES:\n:CREATED: %U\n:END:\n")
        ("p" "Post" plain (file create-blog-post)
         (file "~/myorg/assets/blog_post.org"))
        ))

Agenda

Basic stuff

(define-key global-map "\C-ca" 'org-agenda)
(setq org-agenda-window-setup (quote current-window))
(setq org-agenda-files '("~/myorg/private/private.org" "~/myorg/work/work.org" "~/myorg/inbox.org"))
(setq org-agenda-start-day nil)
(setq org-agenda-span 7)
(setq org-agenda-start-on-weekday nil)
(setq org-agenda-skip-deadline-prewarning-if-scheduled t)
(setq org-agenda-todo-ignore-deadlines (quote all))
(setq org-agenda-todo-ignore-scheduled (quote all))
(setq org-agenda-skip-scheduled-if-done t)
(setq org-agenda-skip-deadline-if-done t)
(setq org-deadline-warning-days 5)
(setq org-habit-preceding-days 14)
(setq org-habit-graph-column 189)
(setq org-agenda-use-time-grid nil)

Custom Agenda Views

  • Agenda idea taken from Aaron Bieber.
  • I mixed that up with the Eisenhower map of priority:
    • A = Urgent AND Important (DO NOW)
    • B = NOT Urgent and Important (DO)
    • C = Urgent and NOT Important (DELEGATE)
    • D = NOT Urgent AND NOT Important (DON'T DO)
  • To focus, I only show scheduled tasks from today. Every task here takes less than a portion of a work day to complete.
  • For stuff I need to work longer on, I use "PROG" state. This takes longer than a portion of a work day to complete.
  • Backlog contains everything that is still not being worked on ("PROG") or has not been schedueled for a specific day. I use priorities B to D in the backlog to sort it:
    • B: Prio 1, do this next when you have time.
    • C: Prio 2, do this at some time in future or delegate.
    • D: Everything else, maybe do not do this at all.
(setq org-highest-priority ?A)
(setq org-lowerst-priority ?D) 
(setq org-default-priority ?D)
(defun air-org-skip-subtree-if-priority (priority)
  (let ((subtree-end (save-excursion (org-end-of-subtree t)))
        (pri-value (* 1000 (- org-lowest-priority priority)))
        (pri-current (org-get-priority (thing-at-point 'line t))))
    (if (<= pri-value pri-current)
        subtree-end
      nil)))

(setq org-agenda-custom-commands
      '(("k" "Agenda today for KEMAL"
         ((tags "PRIORITY<=\"A\""
                ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                 (org-agenda-overriding-header "DO NOW:")))
          (agenda ""
                  ((org-agenda-span 1)))
          (tags-todo "TODO=\"PROG\"" 
                     ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                      (org-agenda-overriding-header "Work on:")))
          (tags "CATEGORY=\"INBOX\"" 
                     ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                      (org-agenda-overriding-header "Refile:")))))
        ("i" "Backlog"
         ((alltodo ""
                   ((org-agenda-skip-function '(or (air-org-skip-subtree-if-priority ?A)
                                                   (org-agenda-skip-entry-if 'todo '("PROG"))
                                                   (org-agenda-skip-if nil '(scheduled deadline))))
                    (org-agenda-sorting-strategy '(priority-down effort-up))
                    (org-agenda-overriding-header "Backlog:")))))
        ;;; Some views for work, to filter stuff for specific recipients
        ("t" "Speak with the TEAM"
         ((tags "team"
                ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                 (org-agenda-overriding-header "Let's talk about:")))))
        ("b" "Speak with the BOSS"
         ((tags "boss"
                ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                 (org-agenda-overriding-header "Let's talk about:")))))
        ("w" "Speak with the SWPG"
         ((tags "swpg"
                ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                 (org-agenda-overriding-header "Let's talk about:")))))
        ("j" "Topics for JFix"
         ((tags "jfix"
                ((org-agenda-skip-function '(org-agenda-skip-entry-if 'todo 'done))
                         (org-agenda-overriding-header "Let's talk about:")))))))

Refile

(setq additional-refile-targets '("~/myorg/private/notes.org" "~/myorg/work/meetings.org" "~/myorg/work/mydev.org" "~/myorg/work/interview.org"))

(setq org-refile-targets
      '((org-agenda-files :maxlevel . 3)
        (additional-refile-targets :maxlevel . 3)))
(setq org-refile-use-outline-path 'file)
(setq org-outline-path-complete-in-steps nil)

Screenshots

To use this make a screenshot with a tool that saves the picture to the clipboard. Then grab this photo from clipboard using org-download. Or install and use flameshot, so that you can edit the screenshot before inserting.

(use-package org-download
  :ensure t
  :config
  (setq org-download-method 'attach
        org-download-screenshot-method "flameshot gui --raw > %s")
  (if my/work
      (setq org-download-screenshot-method "imagemagick/convert"))
  :bind
  (("C-c d i" . org-download-clipboard)
   ("C-c d s" . my-org-download-screenshot-wrapper)))

(defun my-org-download-screenshot-wrapper()
  ;; lower frame before taking the screenshot
  (interactive)
  (lower-frame)
  (org-download-screenshot))

Blog

Get needed packages.

(require 'ox-html)
(require 'ox-publish)
(use-package webfeeder
  :ensure t)
(use-package htmlize
  :ensure t)
(use-package lua-mode
  :ensure t
  :config
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((lua . t))))

Helper stuff

Some stuff from Pavel Panchekha to clear up HTML and additional stuff for HTML5 addaptation to simple.css.

(setq org-html-home/up-format "")
(setq org-html-link-up "")
(setq org-html-link-home "")
(setq org-html-scripts "")
(setq org-html-doctype "html5")
(setq org-html-html5-fancy t)
(setq org-html-container-element "main")

Get stuff for the sitemap, generate RSS, publish.

(defun org-find-category (file)
  ;; find category in the posts
  (with-temp-buffer
    (message file)
    (insert-file-contents file)
    (goto-char (point-min))
    (let ((beg (+ 1 (re-search-forward "^#\\+CATEGORY\:")))
          (end (progn (forward-word) (point))))
      (buffer-substring beg end))))

(defun my-sitemap-format-entry (entry style project)
  ;; format the entries in the sitemap
  (format "%s - [[file:%s][%s]] (%s)"
          (format-time-string "%Y-%m-%d" (org-publish-find-date entry project))
          entry
          (org-publish-find-title entry project)
          (org-find-category
           (expand-file-name entry (plist-get (cdr project) :base-directory)))))

(defun rss-gen()
  ;; generate rss feed
  (interactive)
  (webfeeder-build
   "rss.xml"
   "~/blog/pages/"
   "https://anonimno.codeberg.page/"
   (delete '"blog.html" (directory-files "~/blog/pages/" nil
                                          "\.html"))
   :title "anonimno's blog"
   :description "My blog in RSS"
   :builder 'webfeeder-make-rss))

(defun insert-pubdate (file)
  ;; insert current date as pubdate in the generated sitemap
  (interactive)
  (with-temp-buffer
    (message file)
    (insert-file-contents file)
    (goto-char (point-min))
    (re-search-forward "hidden;\">")
    (insert (shell-command-to-string "echo -n $(date +%Y-%m-%d)"))
    (write-file file)))

(defun my-blog-publish()
  ;; publish the posts
  (interactive)
  (progn
    (org-publish-all)
    (rss-gen)
    (insert-pubdate "~/blog/pages/blog.html") ;; insert pubdate in sitemap
    ;;(let ((msg (read-string "Commit msg: "))))
    (let ((msg (read-string "Commit msg: ")))
      (shell-command (format "%s %s" "~/blog/pages/git_publish.sh" msg)))
    ))

Project setup

(setq org-publish-project-alist
      '(("blog"
         :base-directory "~/blog/src/posts/"
         :base-extension "org"
         :publishing-directory "~/blog/pages/"
         :publishing-function org-html-publish-to-html
         :recursive t
         :exclude "level-.*\\|.*\.draft\.org"
         :section-numbers nil
         :with-toc nil
         :with-sub-superscript nil
         :with-author nil
         :with-date t
         :with-drawers t
         :auto-sitemap t
         :sitemap-filename "blog.org"
         :sitemap-title "Posts"
         :sitemap-sort-files anti-chronologically
         :sitemap-style list
         :sitemap-format-entry my-sitemap-format-entry
         :html-head-include-default-style nil
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"assets/site.css\" />"
         :html-preamble "<nav> <a href=\"/index.html\">Home</a> <a href=\"/blog.html\">Blog</a> <a href=\"https://codeberg.org/anonimno\">Codeberg</a> <a href=\"/blogroll.html\">Blogroll</a> <a href=\"/contact.html\">Contact</a> <a href=\"/rss.xml\">RSS</a> </nav><p class=\"pubdate\" style=\"visibility:hidden;\"> %d </p>"
         :html-postamble "<p><span class=\"date\" style=\"float: left;\">License: <a href= \"https://creativecommons.org/licenses/by-sa/4.0/\">CC BY-SA 4.0</a></span> <span style=\"float: right;\"><a href= \"https://social.tchncs.de/web/accounts/310111\">Discuss on Mastodon</a></span></p>")

        ("assets"
         :base-directory "~/blog/src/assets/"
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|webp"
         :publishing-directory "~/blog/pages/assets/"
         :recursive t
         :publishing-function org-publish-attachment)

        ("all" :components ("blog" "assets"))))

Which-Key

Let's get those keybindings shown, because I tend to forget them.

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

Helm

I find helm to be a nice package with a lot of functionality out-of-the-box that helps me work faster.

(use-package helm
  :ensure t
  :bind
  (("M-x" . 'helm-M-x)
   ("C-x C-f" . 'helm-find-files)
   ("C-s" . 'helm-occur)
   ("C-q" . 'helm-org-ql)
   ("C-x C-b" . 'helm-buffers-list)
   ("M-y" . 'helm-show-kill-ring)
   ("C-x C-p" . 'helm-list-elisp-packages)
   ("C-x r b" . 'helm-bookmarks))
  :config
  (helm-mode)
  (setq helm-autoresize-mode 0)
  (setq helm-split-window-in-side-p 1)
  (setq helm-move-to-line-cycle-in-source 1))

(use-package helm-org
  :ensure t)

(use-package helm-org-ql
  :ensure t)

ERC

Using this to chat on interessting channels like #emacs, #org-mode or #debian.

(setq
 erc-nick "anonimno")

(defun erc-liberachat ()
  (interactive)
  (erc :server "irc.libera.chat" :port "6667"))

(require 'erc-services)
(erc-services-mode 1)

Elfeed

Using elfeed to read news from my Nextcloud.

(use-package elfeed
  :ensure t
  :config
  (global-set-key (kbd "C-x w") 'elfeed)
  (setq elfeed-use-curl t)
  (elfeed-set-timeout 36000)
  (setq elfeed-feeds '(
                       ("owncloud+https://hkemal@moxnet.feste-ip.net"
                         :use-authinfo t))))

(use-package elfeed-protocol
  :ensure t
  :config
  (setq elfeed-protocol-owncloud-maxsize 1000)
  (setq elfeed-protocol-owncloud-update-with-modified-time t)
  (elfeed-protocol-enable))

(use-package elfeed-goodies
  :ensure t
  :config
  (elfeed-goodies/setup)
  (setq elfeed-goodies/entry-pane-position 'bottom))

;; Some additional tweaking is needed on Windows.
(if my/work
    (setq
     elfeed-curl-extra-arguments '("--insecure")
     auth-sources '((:source "c:/daten_lokal/.authinfo"))))

License: CC BY-SA 4.0 Discuss on Mastodon