From 77c344c9f18408fd4b84c126c82ccf1707289c71 Mon Sep 17 00:00:00 2001 From: fpi Date: Wed, 29 Jan 2020 17:53:34 +0100 Subject: A fresh emacs config --- emacs-init.org | 2885 ++++++++++++++++++++++++++++++++++++++++++++++++++ emacs-private.el.gpg | Bin 0 -> 784 bytes init-exwm.org | 412 +++++++ 3 files changed, 3297 insertions(+) create mode 100644 emacs-init.org create mode 100644 emacs-private.el.gpg create mode 100644 init-exwm.org diff --git a/emacs-init.org b/emacs-init.org new file mode 100644 index 0000000..f72403f --- /dev/null +++ b/emacs-init.org @@ -0,0 +1,2885 @@ +#+PROPERTY: header-args:emacs-lisp :tangle tangle/emacs-init.el :results silent +* Overview +** About this document +This files contains all the elisp code normally placed in the .emacs +file. It and the =init.el= file are then symlinked to my =~/.emacs.d/= +directory. Instead of symlinking the files could also be directly +tangled to =~/.emacs.d/=. +#+BEGIN_SRC shell :results silent +ln -sf $(pwd)/emacs-init.org ~/.emacs.d/ +ln -sf $(pwd)/tangle/emacs-init.el ~/.emacs.d/ +ln -sf $(pwd)/emacs-private.el.gpg ~/.emacs.d/ +ln -sf $(pwd)/tangle/init.el ~/.emacs.d/ +#+END_SRC + +An often seen setup is to use ~org-babel-load-file~ in =init.el= to +load this =.org= configuration file. Instead I chose to auto-tangle +the =.org= file on every save and load the tangled =.el= file +directly. This saves time on startup as tangling does not occur +everytime and also allows for more flexibility regarding tangling file +locations in this configuration file. Namely I can include the content +of =init.el= in this file without problems. + +I use =.org= configuration files also for my other dotfiles. To ensure +they are tangled upon save I use this function. +#+BEGIN_SRC emacs-lisp +(defun fpi/tangle-dotfiles () + "If the current file is in '~/.dotfiles' tangle all code blocks." + (when (equal (file-name-directory (directory-file-name buffer-file-name)) + (expand-file-name "git/projects/dotfiles/" (getenv "HOME"))) + (org-babel-tangle) + (message "%s tangled" buffer-file-name))) + +(add-hook 'after-save-hook #'fpi/tangle-dotfiles) +#+END_SRC + +To share this configuration publicly, even though it contains private +information, several solutions are possible. To keep everything in one +file, [[elisp:(find-library "org-crypt")][org-crypt]] is a possible solution. Marking the headings with +private information with the tag =:crypt:= and adding the following to +=init.el= works as a basic setup for =org-crypt=. Also make sure to +disable ~buffer-auto-save-file-name~ in this file. +#+begin_src emacs-lisp :tangle no :eval never +(require 'org-crypt) +(org-crypt-use-before-save-magic) +(setq org-tags-exclude-from-inheritance (quote ("crypt"))) +(setq org-crypt-key my-gpg-key-id) +#+end_src +To properly tangle this configuration all blocks need to be decrypted +before tangling. Given the structure of =org-babel-tangle= this leads +to saving the file twice each time. The first time unencrypted and +then after tangling is complete once again encrypted. The following +code can achieve this. +#+begin_src emacs-lisp :tangle no :eval never +;; Make sure buffers are decrypted before tangling +(defun save-buffer-without-tangle () + (interactive) + (let ((b (current-buffer))) + (with-temp-buffer + (let ((after-save-hook (remove 'fpi/tangle-dotfiles after-save-hook))) + (with-current-buffer b + (let ((after-save-hook (remove 'fpi/tangle-dotfiles after-save-hook))) + (save-buffer))))))) + +;; before tangling: decrypt all and save without encrypt +(advice-add 'org-babel-tangle :before '(lambda (&rest r) + (remove-hook 'before-save-hook 'org-encrypt-entries t) + (org-decrypt-entries) + (save-buffer-without-tangle))) +;; after tangling: encrypt all entries and re-add hook +(advice-add 'org-babel-tangle :after '(lambda (&rest r) + (org-encrypt-entries) + (add-hook 'before-save-hook 'org-encrypt-entries nil t) + (save-buffer-without-tangle))) +#+end_src +Unfortunately this leads to unusable diffs in =git= for the encrypted +parts. The approach of using a separate =.el.gpg= or =.org.gpg= file +has the same problem. But git can be told to decrypt =.gpg= files +before creating the diff using the following settings (see [[https://magit.vc/manual/magit/How-to-show-diffs-for-gpg_002dencrypted-files_003f.html][here]]). +#+begin_src shell +git config --global diff.gpg.textconv "gpg --no-tty --decrypt" +echo "*.gpg filter=gpg diff=gpg" > .gitattributes +#+end_src +A similar behaviour can be achieved using [[https://github.com/AGWA/git-crypt][git-crypt]]. I save private +details in =emacs-private.el.gpg= and load this file here. +#+begin_src emacs-lisp +(setq secret-file (expand-file-name "emacs-private.el.gpg" + user-emacs-directory)) +(load secret-file) +#+end_src + +This is the content of =init.el=. Notice the ~:tangle tange/init.el~ +header argument in the source code. +#+begin_src emacs-lisp :tangle tangle/init.el +(require 'package) +(package-initialize) +;; (setq safe-local-variable-values (list (cons 'buffer-auto-save-file-name nil) + ;; (cons 'header-line-format " "))) +(setq vc-follow-symlinks t) +(load (expand-file-name "emacs-init.el" user-emacs-directory)) +#+end_src + +I always wanted to reorganize my old init file with >5000 lines, but +never managed to do it completely. So I decided to start from scratch. +The structure and some of the base content is loosely based on the +[[https://gitlab.com/protesilaos/dotemacs/][config of Protesilaos Stavrou]]. Several functions and definitions are +from other configs as well. They are mentioned in the appropriate +places. + +Notable configs: +- [[https://gitlab.com/protesilaos/dotemacs/][Protesilaos Stavrou]] +- [[http://doc.rix.si/cce/cce.html][Ryan Rix]] +- [[http://doc.norang.ca/org-mode.html][Bernt Hansen]] + +* Base settings +** Setup some paths +#+BEGIN_SRC emacs-lisp +(add-to-list 'load-path "~/.emacs.d/lisp") +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) +(add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") nil) +#+END_SRC +** Meta packages +Packages that don't do anything by themselves, but can be used to help +with other package definition and customization. +*** Use-package +#+begin_src emacs-lisp +(unless (package-installed-p 'use-package) + (package-refresh-contents) + (package-install 'use-package)) +(eval-when-compile + (require 'use-package)) +#+end_src +*** Hydra +#+begin_src emacs-lisp +(use-package hydra + :ensure t) +#+end_src +This package allows hydra definitions in use-package. +#+begin_src emacs-lisp +(use-package use-package-hydra + :ensure t) +#+end_src +*** which-key +In Emacs you can press =?= or =C-h= after starting a key combination +to get a list of available commands. =which-key= shows these in a +small popup, which I think is more handy. +#+begin_src emacs-lisp +(use-package which-key + :ensure t + :custom (which-key-idle-delay 0.4) + :config (which-key-mode 1)) +#+end_src +*** Try +Sometimes I stumble over a package and want to try it out without +commiting to it and installing it fully – possibly forgetting to +remove it. =Try= installs packages temporarily for this emacs session +only. +#+begin_src emacs-lisp +(use-package try + :ensure t) +#+end_src +** GUI Interface +Disable most of the user interface. + +#+BEGIN_SRC emacs-lisp +(use-package emacs + :config + (tooltip-mode -1) + (tool-bar-mode -1) + (menu-bar-mode -1) + (scroll-bar-mode -1) + ) +#+END_SRC +In /awesomewm/ and other tiling window managers the emacs window +leaves a gap at the bottom. This removes it. +#+BEGIN_SRC emacs-lisp +(setq frame-resize-pixelwise t) +#+END_SRC +*** Remove mode line clutter +#+begin_src emacs-lisp +(use-package delight + :ensure t + :after use-package) +#+end_src +If removing mode symbols with =delight= is not enough, the mode line +can also be completely removed by setting ~mode-line-format~ to ~nil~. +=hide-mode-line= is a small minor mode that can toggle the mode-line +on and off. I added ~redraw-display~, because i had problems with the +mode-line not being redisplayed, when turning the mode off even though +it calls ~force-mode-line-update~. +#+begin_src emacs-lisp +(use-package hide-mode-line + :ensure t + :hook + (hide-mode-line-mode . redraw-display) + (help-mode . hide-mode-line-mode)) +(global-set-key (kbd "C-c m") 'hide-mode-line-mode) +#+end_src +** Font +I am still not quite sure on my choice of font. + +=fpi/set-font= is a safe way to choose a font based on +availability. When starting with =emacs --daemon= it does not work as +=(font-family-list)= won't return anything. +#+begin_src emacs-lisp :tangle no + (use-package emacs + :config + (defun fpi/set-font () + (interactive) + (cond + ((member "Hack" (font-family-list)e) + (add-to-list 'default-frame-alist '(font . "Hack-12"))) + ((member "Source Code Pro" (font-family-list)) + (add-to-list 'default-frame-alist '(font . "Source Code Pro-12"))))) + (add-to-list 'default-frame-alist '(font . "Hack-12")) + ;; :hook (after-init . fpi/set-font) + ) +#+end_src + +Instead of the above code I set the font directly using =set-face-attribute=. +#+begin_src emacs-lisp +(set-face-attribute 'default nil :font "Hack-11") +#+end_src + +** Theme +=hc-zenburn= is the theme I chose for a long time. Lately I started to +appreciate light themes more. [[https://gitlab.com/protesilaos/modus-themes][modus-operandi]] is an interesting light +theme promising high color contrast. I ended up using the +=spacemacs-light= and =spacemacs-dark= themes. +#+begin_src emacs-lisp +(package-install 'spacemacs-theme) +#+end_src + +To do some custom adjustments to it I use the following macro which +adds an advice to ~load-theme~. +#+begin_src emacs-lisp +(defmacro set-pair-faces (themes consts faces-alist) + "Macro for pair setting of custom faces. +THEMES name the pair (theme-one theme-two). CONSTS sets the variables like + ((sans-font \"Some Sans Font\") ...). FACES-ALIST has the actual faces +like: + ((face1 theme-one-attr theme-two-atrr) + (face2 theme-one-attr nil ) + (face3 nil theme-two-attr) + ...)" + (defmacro get-proper-faces () + `(let* (,@consts) + (backquote ,faces-alist))) + `(progn + ,@(mapcar + (lambda (theme) + `(defadvice load-theme + (after ,(gensym theme) last (loaded-theme &rest args) activate) + (when (equal loaded-theme (quote ,theme)) + (custom-theme-set-faces + (quote ,theme) ;; maybe instead use =user= theme? + ,@(cl-remove-if + (lambda (x) (equal x "NA")) + (mapcar + (lambda (face) + (let ((face-name (car face)) + (face-attrs (nth (cl-position theme themes) (cdr face)))) + (if face-attrs + `(quote (,face-name ((t ,face-attrs)))) + "NA"))) (get-proper-faces))) + )))) + themes))) +#+end_src + +The above macro can be used like this. +#+begin_src emacs-lisp +(set-pair-faces + ;; Themes to cycle in + (spacemacs-dark spacemacs-light) + ;; Variables + ((bg-white "#fbf8ef") + (bg-light "#222425") + (bg-dark "#1c1e1f") + (bg-darker "#1c1c1c") + (fg-white "#ffffff") + (shade-white "#efeae9") + (fg-light "#655370") + (dark-cyan "#008b8b") + (region-dark "#2d2e2e") + (region "#39393d") + (slate "#8FA1B3") + (keyword "#f92672") + (comment "#525254") + (builtin "#fd971f") + (purple "#9c91e4") + (doc "#727280") + (type "#66d9ef") + (string "#b6e63e") + (gray-dark "#999") + (gray "#bbb") + (sans-font "Source Sans Pro") + (serif-font "Merriweather") + (et-font "EtBookOt") + (sans-mono-font "Hack") + ;; (serif-mono-font "Verily Serif Mono") + (serif-mono-font "cmu typewriter text") + ) + + ;; (set-face-attribute 'default nil :font "Hack-11") +;; (set-face-attribute 'variable-pitch nil :font "EtBookOt-11") + ;; Settings + ((default + () + (:foreground ,bg-dark)) + (variable-pitch + (:family ,sans-font) + (:family ,et-font + :background nil + :foreground ,bg-dark + :height 1.2)) + (header-line + (:background nil :inherit nil) + (:background nil :inherit nil)) + ;; (company-tooltip + ;; (:background ,bg-darker + ;; :foreground ,gray) + ;; nil) + ;; (company-scrollbar-fg + ;; (:background ,comment) + ;; nil) + ;; (company-scrollbar-bg + ;; (:background ,bg-darker) + ;; nil) + ;; (company-tooltip-common + ;; (:foreground ,keyword) + ;; nil) + ;; (company-tootip-annotation + ;; (:foreground ,type) + ;; nil) + ;; (company-tooltip-selection + ;; (:background ,region) + ;; nil) + (show-paren-match + (:background ,keyword + :foreground ,bg-dark) + nil) + (magit-section-heading + (:foreground ,keyword) + nil) + (magit-header-line + (:background nil + :foreground ,bg-dark + :box nil) + (:background nil + :foreground ,bg-white + :box nil)) + (magit-diff-hunk-heading + (:background ,comment + :foreground ,gray) + nil) + (magit-diff-hunk-heading-highlight + (:background ,comment + :foreground ,fg-white) + nil) + (tooltip + (:foreground ,gray + :background ,bg-darker) + nil) + (mode-line + (:background ,bg-darker) + (:background ,bg-white + :box nil)) + (mode-line-inactive + nil + (:box nil)) + (powerline-active1 + nil + (:background ,bg-white)) + (powerline-active2 + nil + (:background ,bg-white)) + (powerline-inactive1 + nil + (:background ,bg-white)) + (powerline-inactive2 + nil + (:background ,bg-white)) + (highlight + (:background ,region + :foreground ,fg-white) + (:background ,shade-white)) + (hl-line + (:background ,region-dark) + nil) + (org-document-title + (:inherit variable-pitch + :height 1.3 + :weight normal + :foreground ,gray) + (:inherit nil + :family ,et-font + :height 1.8 + :foreground ,bg-dark + :underline nil)) + (org-document-info + (:foreground ,gray + :slant italic) + (:height 1.2 + :slant italic)) + (org-level-1 + (:inherit variable-pitch + :height 1.3 + :weight bold + :foreground ,keyword + :background ,bg-dark) + (:inherit nil + :family ,et-font + :height 1.6 + :weight normal + :slant normal + :foreground ,bg-dark)) + (org-level-2 + (:inherit variable-pitch + :weight bold + :height 1.2 + :foreground ,gray + :background ,bg-dark) + (:inherit nil + :family ,et-font + :weight normal + :height 1.3 + :slant italic + :foreground ,bg-dark)) + (org-level-3 + (:inherit variable-pitch + :weight bold + :height 1.1 + :foreground ,slate + :background ,bg-dark) + (:inherit nil + :family ,et-font + :weight normal + :slant italic + :height 1.2 + :foreground ,bg-dark)) + (org-level-4 + (:inherit variable-pitch + :weight bold + :height 1.1 + :foreground ,slate + :background ,bg-dark) + (:inherit nil + :family ,et-font + :weight normal + :slant italic + :height 1.1 + :foreground ,bg-dark)) + (org-level-5 + (:inherit variable-pitch + :weight bold + :height 1.1 + :foreground ,slate + :background ,bg-dark) + nil) + (org-level-6 + (:inherit variable-pitch + :weight bold + :height 1.1 + :foreground ,slate + :background ,bg-dark) + nil) + (org-level-7 + (:inherit variable-pitch + :weight bold + :height 1.1 + :foreground ,slate + :background ,bg-dark) + nil) + (org-level-8 + (:inherit variable-pitch + :weight bold + :height 1.1 + :foreground ,slate + :background ,bg-dark) + nil) + (org-headline-done + (:strike-through t) + (:family ,et-font + :strike-through t)) + (org-quote + (:background ,bg-dark + :family ,sans-mono-font) + nil) + (org-block + (:background ,bg-dark + :family ,sans-mono-font) + (:background nil + :height 0.9 + :foreground ,bg-dark + :family ,sans-mono-font)) + (org-block-begin-line + (:background ,bg-dark) + (:background nil + :height 0.8 + :family ,sans-mono-font + :foreground ,slate)) + (org-block-end-line + (:background ,bg-dark) + (:background nil + :height 0.8 + :family ,sans-mono-font + :foreground ,slate)) + (org-document-info-keyword + (:foreground ,comment) + (:height 0.8 + :foreground ,gray)) + (org-link + (:underline nil + :weight normal + :foreground ,slate) + (:foreground ,bg-dark)) + (org-special-keyword + (:height 0.9 + :foreground ,comment) + (:family ,sans-mono-font + :height 0.8)) + (org-todo + (:foreground ,builtin + :background ,bg-dark) + nil) + (org-done + (:inherit variable-pitch + :foreground ,dark-cyan + :background ,bg-dark) + nil) + (org-agenda-current-time + (:foreground ,slate) + nil) + (org-hide + nil + (:foreground ,bg-white)) + (org-indent + (:inherit org-hide) + (:inherit (org-hide fixed-pitch))) + (org-time-grid + (:foreground ,comment) + nil) + (org-warning + (:foreground ,builtin) + nil) + (org-date + nil + (:family ,sans-mono-font + :height 0.8)) + (org-agenda-structure + (:height 1.3 + :foreground ,doc + :weight normal + :inherit variable-pitch) + nil) + (org-agenda-date + (:foreground ,doc + :inherit variable-pitch) + (:inherit variable-pitch + :height 1.1)) + (org-agenda-date-today + (:height 1.5 + :foreground ,keyword + :inherit variable-pitch) + nil) + (org-agenda-date-weekend + (:inherit org-agenda-date) + nil) + (org-scheduled + (:foreground ,gray) + nil) + (org-upcoming-deadline + (:foreground ,keyword) + nil) + (org-scheduled-today + (:foreground ,fg-white) + nil) + (org-scheduled-previously + (:foreground ,slate) + nil) + (org-agenda-done + (:inherit nil + :strike-through t + :foreground ,doc) + (:strike-through t + :foreground ,doc)) + (org-ellipsis + (:underline nil + :foreground ,comment) + (:underline nil + :foreground ,comment)) + (org-tag + (:foreground ,doc) + (:foreground ,doc)) + (org-table + (:background nil + :family ,sans-mono-font) + (:family ,serif-mono-font + :height 0.9 + :background ,bg-white)) + (org-code + (:inherit font-lock-builtin-face) + (:inherit nil + :family ,serif-mono-font + :foreground ,comment + :height 0.9)) + (font-latex-sectioning-0-face + (:foreground ,type + :height 1.2) + nil) + (font-latex-sectioning-1-face + (:foreground ,type + :height 1.1) + nil) + (font-latex-sectioning-2-face + (:foreground ,type + :height 1.1) + nil) + (font-latex-sectioning-3-face + (:foreground ,type + :height 1.0) + nil) + (font-latex-sectioning-4-face + (:foreground ,type + :height 1.0) + nil) + (font-latex-sectioning-5-face + (:foreground ,type + :height 1.0) + nil) + (font-latex-verbatim-face + (:foreground ,builtin) + nil) + (spacemacs-normal-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (spacemacs-evilified-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (spacemacs-lisp-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (spacemacs-emacs-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (spacemacs-motion-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (spacemacs-visual-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (spacemacs-hybrid-face + (:background ,bg-dark + :foreground ,fg-white) + nil) + (bm-persistent-face + (:background ,dark-cyan + :foreground ,fg-white) + nil) + (helm-selection + (:background ,region) + nil) + (helm-match + (:foreground ,keyword) + nil) + (cfw:face-title + (:height 2.0 + :inherit variable-pitch + :weight bold + :foreground ,doc) + nil) + (cfw:face-holiday + (:foreground ,builtin) + nil) + (cfw:face-saturday + (:foreground ,doc + :weight bold) + nil) + (cfw:face-sunday + (:foreground ,doc) + nil) + (cfw:face-periods + (:foreground ,dark-cyan) + nil) + (cfw:face-annotation + (:foreground ,doc) + nil) + (cfw:face-select + (:background ,region) + nil) + (cfw:face-toolbar-button-off + (:foreground ,doc) + nil) + (cfw:face-toolbar-button-on + (:foreground ,type + :weight bold) + nil) + (cfw:face-day-title + (:foreground ,doc) + nil) + (cfw:face-default-content + (:foreground ,dark-cyan) + nil) + (cfw:face-disable + (:foreground ,doc) + nil) + (cfw:face-today + (:background ,region + :weight bold) + nil) + (cfw:face-toolbar + (:inherit default) + nil) + (cfw:face-today-title + (:background ,keyword + :foreground ,fg-white) + nil) + (cfw:face-grid + (:foreground ,comment) + nil) + (cfw:face-header + (:foreground ,keyword + :weight bold) + nil) + (cfw:face-default-day + (:foreground ,fg-white) + nil) + (dired-subtree-depth-1-face + (:background nil) + nil) + (dired-subtree-depth-2-face + (:background nil) + nil) + (dired-subtree-depth-3-face + (:background nil) + nil) + (dired-subtree-depth-4-face + (:background nil) + nil) + (dired-subtree-depth-5-face + (:background nil) + nil) + (dired-subtree-depth-6-face + (:background nil) + nil) + (nlinum-current-line + (:foreground ,builtin) + (:foreground ,bg-dark)) + (vertical-border + (:background ,region + :foreground ,region) + nil) + (which-key-command-description-face + (:foreground ,type) + nil) + (flycheck-error + (:background nil) + nil) + (flycheck-warning + (:background nil) + nil) + (font-lock-string-face + (:foreground ,string) + nil) + (font-lock-comment-face + (:foreground ,doc + :slant italic) + (:background nil + :foreground ,doc + :slant italic)) + (elfeed-search-unread-title-face + (:weight bold) + (:weight bold)) + (helm-ff-symlink + (:foreground ,slate) + nil) + (region + (:background ,region) + nil) + (header-line + (:background nil + :inherit nil) + (:background nil + :inherit nil)))) +#+end_src + +Finally load the theme. +#+begin_src emacs-lisp +(load-theme 'spacemacs-light t) +#+end_src +** User info +Set ~user-full-name~ and ~user-mail-address~. These are set in +[[file:emacs-private.el.gpg::1][emacs-private.el.gpg]]. + +#+begin_src emacs-lisp +(setq user-full-name private/user-full-name + user-mail-address private/user-mail-address) +#+end_src + +** Desktop module +This saves the state emacs was in. +#+begin_src emacs-lisp +(use-package desktop + :init + (setq desktop-dirname user-emacs-directory) + (setq desktop-base-file-name "desktop") + (setq desktop-globals-to-clear nil) + (setq desktop-missing-file-warning t) + (setq desktop-restore-eager 5) + (setq desktop-restore-frames nil) + (setq desktop-save 'ask-if-new) + :config + (desktop-save-mode 1)) +#+end_src +** Customize +#+BEGIN_SRC emacs-lisp +(use-package cus-edit + :custom + (custom-file (expand-file-name "custom.el" user-emacs-directory)) + :hook + (after-init . (lambda () + (unless (file-exists-p custom-file) + (write-region "" nil custom-file)) + (load custom-file)))) +#+END_SRC +** File and input history +*** Recentf +#+begin_src emacs-lisp +(use-package recentf + :init + (setq recentf-save-file (expand-file-name "recentf" user-emacs-directory)) + (setq recentf-max-menu-items 10) + (setq recentf-max-saved-items 200) + (setq recentf-show-file-shortcuts-flag nil) + :config + (recentf-mode 1)) +#+end_src +*** Minibuffer +#+begin_src emacs-lisp +(use-package savehist + :init + (setq savehist-file (expand-file-name "savehist" user-emacs-directory)) + (setq history-length 1000) + (setq savehist-save-minibuffer-history t) + :config + (savehist-mode 1)) +#+end_src +*** Point +Remember where point is in a file. +#+begin_src emacs-lisp +(use-package saveplace + :init + (setq save-place-file (expand-file-name "saveplace" user-emacs-directory)) + :config + (save-place-mode 1)) +#+end_src +*** Backups +#+begin_src emacs-lisp +(use-package emacs + :custom + (backup-directory-alist '(("." . "~/.emacs.d/backups"))) + (version-control t) + (delete-old-versions t) + (kept-new-versions 6) + (kept-old-versions 2) + (create-lockfiles nil)) +#+end_src +** Personal keymap + +Unfortunately =C-c [a-z]= is not always a safe place for user-defined +key bindings. I use a special keymap to aggregate common functions. I +rebind the =C-z= binding for this. +*** Toggle map to toggle common options +This was inspired from [[http://endlessparentheses.com/the-toggle-map-and-wizardry.html][this post]] and I bind it to a key on my personal keymap. +#+BEGIN_SRC emacs-lisp :results silent +(define-prefix-command 'fpi/toggle-map) +(define-key fpi/toggle-map "c" #'column-number-mode) +;;(define-key fpi/toggle-map "d" #'toggle-debug-on-error) +(define-key fpi/toggle-map "f" #'auto-fill-mode) +(define-key fpi/toggle-map "l" #'scroll-lock-mode) +(define-key fpi/toggle-map "s" #'flyspell-mode) +(define-key fpi/toggle-map "t" #'toggle-truncate-lines) +(define-key fpi/toggle-map "q" #'toggle-debug-on-quit) +(define-key fpi/toggle-map "r" #'dired-toggle-read-only) +(autoload 'dired-toggle-read-only "dired" nil t) +(define-key fpi/toggle-map "w" #'whitespace-mode) +(define-key fpi/toggle-map "W" #'whitespace-toggle-options) +#+END_SRC +*** fpi-map +#+BEGIN_SRC emacs-lisp +(define-prefix-command 'fpi-map) +(unbind-key (kbd "C-z")) +(global-set-key (kbd "C-z") 'fpi-map) + +;;(define-key fpi-map (kbd "1") 'org-global-cycle) +(define-key fpi-map (kbd "a") 'org-agenda-show-agenda-and-todo) +(define-key fpi-map (kbd "b") 'bury-buffer) +(define-key fpi-map (kbd "c") 'compile) +;;(define-key fpi-map (kbd "u") 'multiple-cursors-hydra/body) +(define-key fpi-map (kbd "e") 'elfeed) +(define-key fpi-map (kbd "h") 'dfeich/context-hydra-launcher) +(define-key fpi-map (kbd "m") 'notmuch) +(define-key fpi-map (kbd "t") fpi/toggle-map) +(define-key fpi-map (kbd "n") 'sauron-toggle-hide-show) +(define-key fpi-map (kbd "j") (lambda () (interactive) (find-file org-journal-file))) +#+END_SRC + +* Selection and search methods +** Completion frameworks +Having used ido, ivy, icicles and helm in the past, I'm trying to +settle for something simple and go back to ido. The settings below +are for now mostly copied from [[https://gitlab.com/protesilaos/dotemacs/][Protesilaos Stavrou]]. +#+BEGIN_SRC emacs-lisp +(use-package ido + :init + (setq ido-everywhere t) + (setq ido-enable-flex-matching t) + (setq ido-enable-regexp nil) + (setq ido-enable-prefix nil) + (setq ido-all-frames nil) + (setq ido-buffer-disable-smart-matches t) + (setq ido-completion-buffer "*Ido Completions*") + (setq ido-completion-buffer-all-completions nil) + (setq ido-confirm-unique-completion nil) + (setq ido-create-new-buffer 'prompt) + (setq ido-default-buffer-method 'selected-window) + (setq ido-default-file-method 'selected-window) + (setq ido-enable-last-directory-history t) + (setq ido-use-filename-at-point nil) + (setq ido-use-url-at-point nil) + (setq ido-use-virtual-buffers t) + (setq ido-use-faces t) + (setq ido-max-window-height 1) + (setq ido-decorations + '(" " + " " + " | " + " | …" + "[" + "]" + " [No match]" + " [Matched]" + " [Not readable]" + " [Too big]" + " [Confirm]" + " " + " ")) + (setq ido-auto-merge-work-directories-length -1) + :config + (ido-mode 1) + :hook + (minibuffer-setup . (lambda () + (visual-line-mode 1) + (setq-local truncate-lines nil) + (setq-local resize-mini-windows nil) + (setq-local max-mini-window-height 1)))) +#+END_SRC + +#+BEGIN_SRC emacs-lisp +(use-package ido-completing-read+ + :ensure t + :after ido + :config + (ido-ubiquitous-mode 1)) +#+END_SRC +*** amx +Ido completion for =M-x=. +#+BEGIN_SRC emacs-lisp :tangle no +(use-package amx + :ensure t + :after (ido ido-completing-read+) + :init + (setq amx-backend 'ido) + (setq amx-save-file "~/.emacs.d/amx-items") + (setq amx-history-length 10) + (setq amx-show-key-bindings nil) + :config + (amx-mode 1)) +#+END_SRC +** isearch enhancements + +Once again this is mostly taken from [[https://gitlab.com/protesilaos/dotemacs/][Protesilaos Stavrou]]. + +#+BEGIN_SRC emacs-lisp +(use-package isearch + :init + (setq search-whitespace-regexp ".*") + ;; Or use the following for non-greedy matches + ;; (setq search-whitespace-regexp ".*?") + (setq isearch-lax-whitespace t) + (setq isearch-regexp-lax-whitespace nil) + :config + (defun prot/isearch-mark-and-exit () + "Marks the current search string. Can be used as a building +block for a more complex chain, such as to kill a region, or +place multiple cursors." + (interactive) + (push-mark isearch-other-end t 'activate) + (setq deactivate-mark nil) + (isearch-done)) + + (defun stribb/isearch-region (&optional not-regexp no-recursive-edit) + "If a region is active, make this the isearch default search +pattern." + (interactive "P\np") + (when (use-region-p) + (let ((search (buffer-substring-no-properties + (region-beginning) + (region-end)))) + (message "stribb/ir: %s %d %d" search (region-beginning) (region-end)) + (setq deactivate-mark t) + (isearch-yank-string search)))) + (advice-add 'isearch-forward-regexp :after 'stribb/isearch-region) + (advice-add 'isearch-forward :after 'stribb/isearch-region) + (advice-add 'isearch-backward-regexp :after 'stribb/isearch-region) + (advice-add 'isearch-backward :after 'stribb/isearch-region) + + (defun contrib/isearchp-remove-failed-part-or-last-char () + "Remove failed part of search string, or last char if successful. +Do nothing if search string is empty to start with." + (interactive) + (if (equal isearch-string "") + (isearch-update) + (if isearch-success + (isearch-delete-char) + (while (isearch-fail-pos) (isearch-pop-state))) + (isearch-update))) + + (defun contrib/isearch-done-opposite-end (&optional nopush edit) + "End current search in the opposite side of the match. +Particularly useful when the match does not fall within the +confines of word boundaries (e.g. multiple words)." + (interactive) + (funcall #'isearch-done nopush edit) + (when isearch-other-end (goto-char isearch-other-end))) + + :bind (:map isearch-mode-map + ("C-SPC" . prot/isearch-mark-and-exit) + ("DEL" . contrib/isearchp-remove-failed-part-or-last-char) + ("" . contrib/isearch-done-opposite-end))) +#+END_SRC +* Directory, project, buffer, window management +** Dired +*** Base settings +- Always do recursive copies and deletions. +- Be smart about searching file names or the whole buffer. +- Use the system trash for now. +- Customize dired output switches. +- Dont try to be smart about rename and copy target locations when + having two open dired buffers. Setting the target to the other + directory is just as easy using =M-n= twice. +- Hide details by default. =(= to toggle. +- Highlight current line. +- Let the relevant =find= commands use case-insensitive names. +- Enable asynchronous mode for copying/renaming. +#+BEGIN_SRC emacs-lisp +(use-package dired + :custom + (dired-recursive-copies 'always) + (dired-recursive-deletes 'always) + (dired-isearch-filenames 'dwim) + (delete-by-moving-to-trash t) + (dired-listing-switches "-AFlh --group-directories-first") + (dired-dwim-target nil) + :hook + (dired-mode . dired-hide-details-mode) + (dired-mode . hl-line-mode) + (dired-mode . auto-revert-mode)) + +(use-package find-dired + :after dired + :custom + (find-ls-option ;; applies to `find-name-dired' + '("-ls" . "-AFlv --group-directories-first")) + (find-name-arg "-iname")) + +(use-package async + :ensure t) + +(use-package dired-async + :after (dired async) + :config + (dired-async-mode 1)) +#+END_SRC +*** Narrowing +#+BEGIN_SRC emacs-lisp +(use-package dired-narrow + :ensure t + :after dired + :bind (:map dired-mode-map + ("SPC" . dired-narrow-regexp))) +#+END_SRC +*** wdired +Start with =C-x C-q=. +- Allow to change permissions. +- Interpret forward slash in renamed files as new subdirectory to + create. + +#+BEGIN_SRC emacs-lisp +(use-package wdired + :after dired + :init + (setq wdired-allow-to-change-permissions t) + (setq wdired-create-parent-directories t)) +#+END_SRC +*** peep-dired (file previews including images) +By default, dired does not show previews of files, while =image-dired= +is intended for a different purpose. We just want to toggle the +behaviour while inside a regular dired buffer. + +#+BEGIN_SRC emacs-lisp +(use-package peep-dired + :ensure t + :after dired + :bind (:map dired-mode-map + ("P" . peep-dired)) + :custom + (peep-dired-cleanup-on-disable t) + (peep-dired-ignored-extensions + '("mkv" "webm" "mp4" "mp3" "ogg" "iso"))) +#+END_SRC +*** dired-x +Some additional features that are shipped with Emacs. + +#+BEGIN_SRC emacs-lisp +(use-package dired-x + :after dired + :bind (("C-x C-j" . dired-jump) + ("C-x 4 C-j" . dired-jump-other-window)) + :hook + (dired-mode . (lambda () + (setq dired-clean-confirm-killing-deleted-buffers t)))) +#+END_SRC + + +*** dired-subtree ++ The tab key will expand or contract the subdirectory at point. ++ =C-TAB= will behave just like org-mode handles its headings: hit it + once to expand a subdir at point, twice to do it recursively, thrice + to contract the tree. ++ I also have Shift-TAB for contracting the subtree /when the point is + inside of it/. + +At any rate, this does not override the action of inserting a +subdirectory listing in the current dired buffer (with =i= over the +target dir). + +#+BEGIN_SRC emacs-lisp +(use-package dired-subtree + :ensure t + :after dired + :bind (:map dired-mode-map + ("" . dired-subtree-toggle) + ("" . dired-subtree-cycle) + ("" . dired-subtree-remove))) +#+END_SRC +*** dired-sidebar +Open a small sidebar window showing the current directory. +#+BEGIN_SRC emacs-lisp +(use-package dired-sidebar + :bind (("C-x C-n" . dired-sidebar-toggle-sidebar)) + :ensure t + :commands (dired-sidebar-toggle-sidebar) + :hook + (dired-sidebar-mode . (lambda () + (unless (file-remote-p default-directory) + (auto-revert-mode)))) + :config + ;; (setq dired-sidebar-theme 'vscode) + (setq dired-sidebar-use-term-integration t)) +#+END_SRC + +*** dired-du +Recursive directory sizes. Toggle with =C-x M-r=. This will take a +while for directories with lots of nested files. +#+BEGIN_SRC emacs-lisp +(use-package dired-du + :ensure t + :config (setq dired-du-size-format 't)) +#+END_SRC +** Magit + +#+BEGIN_SRC emacs-lisp +(use-package magit + :ensure t + :custom (magit-completing-read-function 'magit-ido-completing-read) + :init (global-magit-file-mode)) +#+END_SRC + +The following package is configured in accordance with the guidelines +provided by this article on [[https://chris.beams.io/posts/git-commit/][writing a Git commit message]]. + +#+BEGIN_SRC emacs-lisp +(use-package git-commit + :after magit + :custom + (git-commit-fill-column 72) + (git-commit-summary-max-length 50) + (git-commit-known-pseudo-headers + '("Signed-off-by" + "Acked-by" + "Modified-by" + "Cc" + "Suggested-by" + "Reported-by" + "Tested-by" + "Reviewed-by")) + (git-commit-style-convention-checks + '(non-empty-second-line + overlong-summary-line))) +#+END_SRC + +Only highlight the changes within a line, not the whole line. + +#+BEGIN_SRC emacs-lisp +(use-package magit-diff + :after magit + :custom + (magit-diff-refine-hunk 'all)) +#+END_SRC +*** diff-hl +Indicates changed lines in the left fringe. The Hydra can be used to +navigate and revert hunks directly from the buffer. Use =g= to open +=magit-status=. I also bind this hydra to =g= in my personal keymap. + +#+begin_src emacs-lisp +(use-package diff-hl + :ensure t + :defer t + :bind (:map fpi-map ("g" . hydra-diff-hl/body)) + :init (global-diff-hl-mode 1) + :config (defhydra hydra-diff-hl (:body-pre (diff-hl-mode 1) + :hint nil) + " + Diff-hl: + _n_: next hunk _s_tage hunk _g_: Magit status + _p_: previous hunk _r_evert hunk _q_uit + ^ ^ _P_opup hunk _Q_uit and deactivate git-gutter + _a_: first hunk + _e_: last hunk _A_mend mode + " + ("n" diff-hl-next-hunk) + ("p" diff-hl-previous-hunk) + ("a" (progn (goto-char (point-min)) + (diff-hl-next-hunk))) + ("e" (progn (goto-char (point-max)) + (diff-hl-previous-hunk))) + ("s" git-gutter:stage-hunk) + ("r" diff-hl-revert-hunk) + ("P" diff-hl-diff-goto-hunk) + ("A" diff-hl-amend-mode) + ("g" magit-status :color blue) + ("q" nil :color blue) + ("Q" (diff-hl-mode -1) + :color blue)) +) +#+end_src +*** gitflow +Add support for [[https://nvie.com/posts/a-successful-git-branching-model/][gitflow]]. +#+begin_src emacs-lisp +(use-package magit-gitflow + :ensure t + :hook (magit-mode . turn-on-magit-gitflow)) +#+end_src +** Projectile + +#+BEGIN_SRC emacs-lisp +(use-package projectile + :ensure t + :delight '(:eval (concat " " (projectile-project-name))) + :init + (setq projectile-project-search-path '("~/git/projects/")) + (setq projectile-indexing-method 'alien) + (setq projectile-enable-caching t) + (setq projectile-completion-system 'ido) + :config + (projectile-mode 1) + :bind (("C-c p" . projectile-command-map))) +#+END_SRC +** Working with buffers + +This renames buffers with the same name and uniqifies them using angled +brackets containing their path. +#+BEGIN_SRC emacs-lisp +(use-package uniquify + :custom + (uniquify-buffer-name-style 'forward) + (uniquify-strip-common-suffix t) + (uniquify-after-kill-buffer-p t)) +#+END_SRC +*** ibuffer + +#+BEGIN_SRC emacs-lisp +(use-package ibuffer + :custom + (ibuffer-display-summary nil) + (ibuffer-use-other-window nil) + (ibuffer-auto-mode -1) + :bind ("C-x C-b" . ibuffer) + :hook + (ibuffer-mode . ibuffer-auto-mode)) +#+END_SRC + +Sort buffers in project groups using projectile. +#+BEGIN_SRC emacs-lisp :tangle no +(use-package ibuffer-projectile + :ensure t + :after (ibuffer projectile) + :hook + (ibuffer-mode . (lambda () + (ibuffer-projectile-set-filter-groups) + (unless (eq ibuffer-sorting-mode 'recency) + (ibuffer-do-sort-by-recency))))) +#+END_SRC +=ibuffer-projectile= updates can be fairly slow. =ibuffer-vc= provides +better performance. +#+begin_src emacs-lisp +(use-package ibuffer-vc + :ensure t + :custom + (ibuffer-formats + '((mark modified read-only vc-status-mini " " + (name 18 18 :left :elide) + " " + (size 9 -1 :right) + " " + (mode 16 16 :left :elide) + " " + (vc-status 16 16 :left) + " " + vc-relative-file))) + :hook + (ibuffer . (lambda () + (ibuffer-vc-set-filter-groups-by-vc-root) + (unless (eq ibuffer-sorting-mode 'alphabetic) + (ibuffer-do-sort-by-alphabetic))))) +#+end_src +** Window configuration +=fit-window-to-buffer= automatically shrinks the current buffer based +on the amount of displayed text. +#+begin_src emacs-lisp +(use-package window + :custom + (fit-window-to-buffer-horizontally t) + :bind (:map fpi-map ("s" . fit-window-to-buffer))) +#+end_src +*** window-numbering +This is a nice package for easy window focus switching. I prefer it +over =windmove=, as it does not interfere with org keybindings. +#+begin_src emacs-lisp +(use-package window-numbering + :ensure t + :config (window-numbering-mode 1)) +#+end_src +*** Winner-mode +#+begin_src emacs-lisp +(use-package winner + :hook (after-init . winner-mode) + :hydra (winner-hydra + (global-map "C-c" :color red) + "Winner undo/redo" + ("" winner-undo "undo") + ("" winner-redo "redo")) + :bind (:map winner-mode-map + ("C-c " . winner-hydra/winner-undo) + ("C-c " . winner-hydra/winner-redo))) +#+end_src +* Applications and utilities +** Calendar +Some basic calendar options for date format und location to provide +correct sunrise/-set times. +#+begin_src emacs-lisp +(use-package calendar + :custom + (calendar-date-style 'european) + (calendar-latitude 52.3667) + (calendar-longitude 9.7167)) +#+end_src + +Set the holidays to consider. I only use german and christian +holidays. Note the =:init= keyword. The individual holiday lists have +to be set before =holidays= is loaded and ~calendar-holidays~ is +initialized. +#+begin_src emacs-lisp +(use-package holidays + :init + (setq holiday-bahai-holidays nil + holiday-christian-holidays + (quote + ((holiday-float 12 0 -4 "1. Advent" 24) + (holiday-float 12 0 -3 "2. Advent" 24) + (holiday-float 12 0 -2 "3. Advent" 24) + (holiday-float 12 0 -1 "4. Advent" 24) + (holiday-fixed 12 25 "1. Weihnachtstag") + (holiday-fixed 12 26 "2. Weihnachtstag") + (holiday-fixed 1 6 "Heilige Drei Könige") + (holiday-easter-etc -48 "Rosenmontag") + (holiday-easter-etc -2 "Karfreitag") + (holiday-easter-etc 0 "Ostersonntag") + (holiday-easter-etc 1 "Ostermontag") + (holiday-easter-etc 39 "Christi Himmelfahrt") + (holiday-easter-etc 49 "Pfingstsonntag") + (holiday-easter-etc 50 "Pfingstmontag") + (holiday-easter-etc 60 "Fronleichnam") + (holiday-fixed 8 15 "Mariae Himmelfahrt") + (holiday-fixed 11 1 "Allerheiligen") + (holiday-float 11 0 1 "Totensonntag" 20))) + holiday-general-holidays + (quote + ((holiday-fixed 1 1 "Neujahr") + (holiday-fixed 2 14 "Valentinstag") + (holiday-fixed 5 1 "1. Mai") + (holiday-float 5 0 2 "Muttertag") + (holiday-fixed 10 3 "Tag der Deutschen Einheit"))) + holiday-hebrew-holidays nil + holiday-islamic-holidays nil + holiday-oriental-holidays nil)) +(use-package solar + :custom + (solar-n-hemi-seasons '("Frühlingsanfang" "Sommeranfang" "Herbstanfang" "Winteranfang"))) +#+end_src +** PDFs +=PDF-Tools= provides better rendering than =DocView=, which is only +png based. It also provides pdf syncing with a tex source. To use this +make sure to compile the tex document with the option ~--synctex=1~. + +#+BEGIN_SRC emacs-lisp +(use-package pdf-tools + :ensure t + :config + (setq pdf-info-epdfinfo-program (concat user-emacs-directory "epdfinfo")) + (pdf-tools-install)) +#+END_SRC + +Add support for pdf annotations. +#+BEGIN_SRC emacs-lisp +(use-package pdf-annot + :bind (:map pdf-annot-minor-mode-map ("C-c C-a d" . pdf-annot-delete))) +#+END_SRC +** Latex +#+begin_src emacs-lisp +(use-package auctex + :ensure t) +#+end_src + +=cdlatex= depends on =texmath.el=. The docstring of =cdlatex= says +=texmath= is supposed to be part of Emacs. However my installation +does not have it. So =auctex= has to deliver this dependency instead. +#+begin_src emacs-lisp +(use-package cdlatex + :ensure t + :custom + (cdlatex-env-alist + (list '("equation*" "\\begin{equation*}\nAUTOLABEL\n?\n\\end{equation*}" nil) + '("tikzpicture" "\\begin{tikzpicture}\nAUTOLABEL\n?\n\\end{tikzpicture}" nil) + '("circuitikz" "\\begin{circuitikz}\nAUTOLABEL\n?\n\\end{circuitikz}" nil)))) +#+end_src +** Programming languages +*** Emacs lisp +=Speed of thought= makes writing lisp so easy. No more snippets +needed. +#+begin_src emacs-lisp +(use-package sotlisp + :ensure t + :init + (add-hook 'emacs-lisp-mode-hook 'speed-of-thought-mode)) +#+end_src +** Org mode +Org is the mode you never need to leave, if you do not want to. My org +TODO and clocking setup is largely inspired by [[http://doc.rix.si/cce/cce-org.html][Ryan Rix's]] and [[http://doc.norang.ca/org-mode.html][Bernt +Hansen's]] configs. +- Scale latex previews :: The default is just a little bit too + small +- org-plus-contrib :: Install the =org-plus-contrib= package which + contains many extra org-modules. +- Startup indented :: Enable =org-indent-mode= in every org file. This + shows the content of headings indented to the headings level, but + does not actually insert whitespace at the start of the line. +- Enable Speed commands :: With the custom function speed commands are + enabled on any star of an headline. +- Set fast tag selection :: By defining default tags they can be set + just with one key press, similar to TODO states. +- Code blocks :: Open code blocks in the current window and use native + settings for the code blocks. +- Custom link abbrevs :: Define any expansion and use them as normal + org links like [[ddg:emacs]]. +- Babel languages :: Enable more languages to use in org-babel blocks. +- Youtube links :: See [[http://endlessparentheses.com/embedding-youtube-videos-with-org-mode-links.html][this blog post]] for more info. +- Ellipsis :: I currently use =" "= and previously used ="⚡⚡⚡"=. +#+begin_src emacs-lisp +(use-package org + :ensure org-plus-contrib + :defer t + :bind + (("C-c c" . org-capture) + ("C-c a" . org-agenda) + ("C-c l" . org-store-link)) + :custom + (org-format-latex-options '(:foreground default :background default :scale 1.5 :html-foreground "Black" :html-background "Transparent" :html-scale 1.0 :matchers + ("begin" "$1" "$" "$$" "\\(" "\\["))) + (org-catch-invisible-edits 'smart) + (org-agenda-diary-file "~/sync/diary.org") + (org-startup-indented t) + (org-use-speed-commands (lambda () (and (looking-at org-outline-regexp) (looking-back "^\**")))) + (org-pretty-entities t) + (org-fast-tag-selection-single-key t) + (org-tag-alist (quote ((:startgroup) + ("@errand" . ?e) + ("@office" . ?o) + ("@home" . ?H) + (:endgroup) + ("IDLE" . ?i) + ("shelf" . ?s) + ("soon" . ?t) + ("project" . ?p) + ;; ("HOLD" . ?h) + ;; ("PERSONAL" . ?P) + ("WORK" . ?W) + ;; ("ORG" . ?O) + ("crypt" . ?E) + ("NOTE" . ?n) + ;; ("CANCELLED" . ?c) + ("FLAGGED" . ??) + ))) + (org-link-abbrev-alist + '(("google" . "http://www.google.com/search?q=") + ("ddg" . "https://duckduckgo.com/?q=") + ("gmap" . "http://maps.google.com/maps?q=%s") + ("omap" . "http://nominatim.openstreetmap.org/search?q=%s&polygon=1"))) + (org-ellipsis " ") + :config + (add-hook 'org-mode-hook 'turn-on-org-cdlatex) + (add-to-list 'org-structure-template-alist (cons "f" "figure")) + (add-to-list 'org-tags-exclude-from-inheritance "MARKED")) +#+end_src +#+begin_src emacs-lisp +(use-package ob + :config (org-babel-do-load-languages + 'org-babel-load-languages + '((ruby . t) + (python . t) + ;;(ipython . t) + (emacs-lisp . t) + (octave . t) + (gnuplot . t) + (dot . t) + (spice . t) + (C . t) + (calc . t) + (latex . t) + (matlab . t) + (shell . t) + (lua . t) + (org . t) + (js . t) + (ditaa . t) + (plantuml . t) + ;; (hvm . t) + (ledger . t) + ))) +#+end_src +#+BEGIN_SRC emacs-lisp +(use-package org-noter + :ensure t + :bind (:map org-mode-map ("C-c o" . org-noter)) + :custom (org-noter-default-notes-file-names '("notes.org")) + ) +#+END_SRC + +#+begin_src emacs-lisp +(use-package ox + :custom + (org-export-with-broken-links 'match) + (org-export-backends '(ascii beamer html icalendar latex man md odt org groff koma-letter))) +(use-package org-pdfview +:ensure t) +(use-package org-id + :custom (org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)) +(use-package org-clock + :custom + (org-clock-out-remove-zero-time-clocks t) + (org-clock-persist 'history) + (org-clock-history-length 30) + :init + (org-clock-persistence-insinuate) + ) +(use-package org-src + :custom + (org-src-window-setup 'current-window) + (org-src-fontify-natively t) + (org-src-tab-acts-natively t) + (org-edit-src-content-indentation 0) +) +(use-package org-agenda + :custom + (org-agenda-files (quote ("~/s/s.org" "~/sync" "~/.emacs.d/gcal.org" "~/.emacs.d/tr.org" "~/n.org"))) + (org-deadline-warning-days 14) + (org-agenda-start-on-weekday nil) + (org-agenda-span 14) + (org-agenda-start-day "+0d") + (org-agenda-include-diary nil) + (org-agenda-sticky t) + (org-agenda-todo-ignore-deadlines 'near) ;; or future? + (org-agenda-todo-ignore-scheduled 'future) + (org-agenda-tags-todo-honor-ignore-options t) + (org-agenda-todo-list-sublevels t) ;; nil to exclude sublevels of todos + (org-agenda-sorting-strategy '((agenda habit-down time-up priority-down category-keep) + (todo priority-down category-keep) + (tags priority-down category-keep) + (search category-keep))) + (org-agenda-skip-scheduled-if-done t) + (org-agenda-dim-blocked-tasks t) + (org-agenda-custom-commands + '(("n" "Agenda and all TODOs" + ((agenda) (tags-todo "+soon") + (tags-todo "+shelve") + (tags-todo "+habit") + (todo "IDLE") + (tags-todo "-habit-shelve-soon-idle"))) + ("r" "Refile entries" ((tags "+REFILE"))) + ("i" "Idle Actions" + ((tags-todo "IDLE-READLIST-WATCH" + ((org-agenda-overriding-header "Idle Tasks") + (org-agenda-skip-function 'bh/skip-project-tasks) + (org-agenda-sorting-strategy + '(todo-state-down effort-up)))) + (tags-todo "READLIST" + ((org-agenda-overriding-header "Idle Reading List") + (org-agenda-sorting-strategy + '(todo-state-down effort-up)))) + (tags-todo "WATCH" + ((org-agenda-overriding-header "Things to Watch") + (org-agenda-skip-function 'bh/skip-project-tasks) + (org-agenda-sorting-strategy + '(todo-state-down effort-up)))))))) + ) +(use-package ob-core + :custom + (org-confirm-babel-evaluate nil)) +(use-package org-screenshot) +(use-package org-collector) +(use-package ox) +(use-package org-notmuch) +(use-package org-expiry + :custom + (org-expiry-handler-function 'org-expiry-archive-subtree)) +(use-package org-habit) +#+end_src +#+begin_src emacs-lisp +(use-package org-inlinetask) +#+end_src +=org-bullets= provides better headline bullets. +Here is a list of nice ones: ◉, ○, ►, •. The default ones are ~'("◉" "○" "✸" "✿")~. +#+begin_src emacs-lisp +(use-package org-bullets + :ensure t + :custom (org-bullets-bullet-list '(" ")) + :config (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))) +#+end_src +Use imagemagick and standalone class for latex preview. +#+begin_src emacs-lisp +(setq org-preview-latex-default-process 'imagemagick) +(setq + org-format-latex-header +"\\documentclass{standalone} +\\usepackage[usenames]{color} +[PACKAGES] +[DEFAULT-PACKAGES] +\\pagestyle{empty} % do not remove") +#+end_src +*** ox-reveal +#+BEGIN_SRC emacs-lisp +(use-package ox-reveal + :ensure t) +(use-package reveal) +(setq org-reveal-root (concat "file:///home/fpi/" "reveal.js")) +;;(setq org-reveal-root "http://cdn.jsdelivr.net/reveal.js/3.0.0/") +#+END_SRC + +*** Org-Capture +#+BEGIN_SRC emacs-lisp +(use-package org-capture + :custom ( + (org-journal-file (format "~/sync/journal/%s.org" (nth 2 (calendar-current-date)))) + (org-capture-templates + `(("j" "Journal") + ("jj" "Journal Entry (Link)" + entry + (file+olp+datetree + ,org-journal-file) + ;; "** %<%H:%M> %a\n %i%? \n%:description\n%:elfeed-entry-content\n%:elfeed-entry-date\n%:elfeed-entry-meta\n%:elfeed-entry-title\n%:elfeed-entry-enclosures\n%:elfeed-entry-tags" ) + "** %<%H:%M> %a +%i%?" ) + ("je" "Journal Entry" + entry + (file+olp+datetree + ,org-journal-file) + "** %<%H:%M> %? +%i" ) + + + + ;; ("a" "Appointment" entry (file "~/sync/a.org") + ;; "* %i%?%(and (org-id-get-create) nil)\n:PROPERTIES:\n:CREATED: %U%(when %a \"\n:SOURCE: %a\")\n:END:\n%^t") + ;; ("t" "Soonish task" entry (file "~/sync/refile.org") + ;; "* NEXT %?%(and (org-id-get-create) nil)\n:PROPERTIES:\n:CREATED: %U%(when %a \"\n:SOURCE: %a\")\n:END:\n%i") + ;; ("s" "Shelve something" entry (file+headline "~/sync/t.org" "Shelf") + ;; "* NEXT %?%(and (org-id-get-create) nil)\n:PROPERTIES:\n:CREATED: %U%(when %a \"\n:SOURCE: %a\")\n:END:\n%i") + ;; ;; ("r" "respond" entry (file "~/sync/refile.org") + ;; ;; "* NEXT Respond to %:from on %:subject\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n" :clock-in t :clock-resume t :immediate-finish t) + ;; ("r" "respond" entry (file "~/sync/refile.org") + ;; "* NEXT Respond to %:from on %:subject\n:PROPERTIES:\n:CREATED: %U\n:END:\n%a\n" :immediate-finish t) + ;; ("n" "note" entry (file "~/sync/refile.org") + ;; "* %? :NOTE:\n%U\n%a\n" :clock-in t :clock-resume t) + ;; ("j" "Journal/Interruptions" entry (file+olp+datetree "~/sync/diary.org") + ;; "* %?\n%U\n" :clock-in t :clock-resume t) + ;; ("h" "Habit" entry (file "~/sync/refile.org") + ;; "* NEXT %?\n%U\n%a\nSCHEDULED: %(format-time-string \"%<<%Y-%m-%d %a .+1d/3d>>\")\n:PROPERTIES:\n:STYLE: habit\n:REPEAT_TO_STATE: NEXT\n:END:\n") + ;; ("m" "Meeting" entry (file "~/sync/refile.org") + ;; "* MEETING with %? :MEETING:\n%U" :clock-in t :clock-resume t) + ;; ("p" "Phone call" entry (file "~/sync/refile.org") + ;; "* PHONE %? :PHONE:\n%U" :clock-in t :clock-resume t) + + ;; ("c" "Item to Current Clocked Task" item (clock) + ;; "%i%?" :empty-lines 1) + ;; ("K" "Kill-ring to Current Clocked Task" plain (clock) + ;; "%c" :immediate-finish t :empty-lines 1) + + ;; ("p" "Gcal Appointment" entry (file "~/.emacs.d/gcal.org") + ;; "* %?\n%^T\n") + + ;; ("z" "Zettel" entry (file "~/zettel.org") + ;; "* %i%? %(and (org-id-get-create) nil) + ;; :PROPERTIES:\n :CREATED: %u\n :END:\n ") + + ;; ("l" "Ledger") + ;; ("lb" "Bank" plain (file ,(format "~/.personal/f/%s.ledger" (format-time-string "%Y"))) + ;; ,my/org-ledger-card-template + ;; :empty-lines 1 + ;; :immediate-finish t) + ;; ("lc" "Cash" plain (file ,(format "~/.personal/f/%s.ledger" (format-time-string "%Y"))) + ;; ,my/org-ledger-cash-template + ;; :empty-lines 1 + ;; :immediate-finish t) + ) + ))) +#+END_SRC +*** Ricing +#+begin_src emacs-lisp +(setq line-spacing 0.1) +(setq header-line-format " ") +;; (set-face-attribute 'header-line nil :height 50) ;; make buffer-local first + +;; side padding +(lambda () + (progn + (setq left-margin-width 2 + right-margin-width 2) + (set-window-buffer nil (current-buffer)))) + +;; try writeroom-mode +#+end_src +*** Org crypt +A small function to toggle the encryption state of the current entry. +#+begin_src emacs-lisp +(use-package org-crypt + :config + (defun fpi/org-toggle-crypt-entry () + "Encrypt/Decrypt current headline." + (interactive) + (require 'epg) + (when (eq major-mode 'org-mode) + (unless (org-before-first-heading-p) + (org-with-wide-buffer + (org-back-to-heading t) + (org-end-of-meta-data) + (if (looking-at-p "-----BEGIN PGP MESSAGE-----") + (org-decrypt-entry) + (org-encrypt-entry)))))) + (define-key fpi/toggle-map "e" #'fpi/org-toggle-crypt-entry)) +#+end_src +*** org-ref +#+begin_src emacs-lisp +(use-package org-ref + :ensure t + :custom + (org-ref-bibliography-notes "~/s/ma/notes.org") + (org-ref-default-bibliography '("~/s/ma/ma.bib")) + (org-ref-pdf-directory "~/s/ma/lit/")) +#+end_src +*** Todo settings +- WAITING tasks are waiting on the completion of other tasks +- NEXT tasks can be picked up +- INPROGRESS are current tasks with time clocked +- DONE are complete tasks +- ICEBOX tasks are on ice for whatever reason + +TODO->DONE cycle is for habits.\\ +Idle states cover things to do for time in between, checking the +inbox, reading news, … + +Phonecalls? + +#+BEGIN_SRC dot :file /tmp/todo.png +digraph hierarch{ + node [shape=box] + // Tasks, Projects + PLANNING -> NEXT, INPROGRESS, ICEBOX + WAITING -> NEXT -> INPROGRESS -> DONE, WAITING, ICEBOX + NEXT -> WAITING -> INPROGRESS, ICEBOX + NEXT -> ICEBOX, DONE + + // stuff for idle time + IDLE -> IDLE + //NEXT -> DONE + + // Phonecalls, Meetings + PHONE -> DONE, CANCELED + MEETING -> DONE, CANCELED +} +#+END_SRC + +#+RESULTS: +[[file:/tmp/todo.png]] + +#+BEGIN_SRC emacs-lisp +(setq org-todo-keywords '((sequence "PLANNING(p)" "NEXT(n)" "INPROGRESS(i)" "WAITING(w@/!)" "|" "ICEBOX(x@)" "DONE(d)") + (sequence "PHONE(P)" "MEETING(m)" "|" "CANCELLED(c)") + (sequence "TODO(t)" "|" "DONE(d)") + (sequence "IDLE(a)"))) +(setq org-use-fast-todo-selection t) + + +(setq org-todo-keyword-faces + '(("NEXT" :foreground "light blue" :weight bold) + ("INPROGRESS" :foreground "burlywood" :weight bold) + ("DONE" :foreground "forest green" :weight bold) + ("WAITING" :foreground "orange" :weight bold) + ("ICEBOX" :foreground "orange" :weight normal) + ("CANCELLED" :foreground "forest green" :weight bold) + ("MEETING" :foreground "yellow" :weight bold) + ("PHONE" :foreground "yellow" :weight bold) + ("IDLE" :foreground "magenta" :weight bold))) +#+END_SRC + +Switch a todo entry from NEXT to INPROGRESS when clocking in. +#+begin_src emacs-lisp +(setq org-clock-in-switch-to-state 'bh/clock-in-to-inprogress) + +(defun bh/clock-in-to-inprogress (kw) + "Switch a task from NEXT to INPROGRESS when clocking in. +Skips capture tasks, projects, and subprojects. +Switch projects and subprojects from NEXT back to TODO" + (when (not (and (boundp 'org-capture-mode) org-capture-mode)) + (cond + ((and (member (org-get-todo-state) (list "NEXT")) + (bh/is-task-p)) + "INPROGRESS") + ((and (member (org-get-todo-state) (list "NEXT")) + (bh/is-project-p)) + "INPROGRESS")))) +#+end_src + +**** State changes +Track state changes to done +#+begin_src emacs-lisp +(setq org-log-done 'time) +#+end_src +*** Toggle drawer visibility +#+begin_src emacs-lisp +(setq fpi/org-meta-heading-info-store nil) +(make-variable-buffer-local 'fpi/org-meta-heading-info-store) +(defun mw-org-hide-meta-heading-info () + "Hide meta data following headings." + (interactive) + (org-save-outline-visibility t + (save-excursion + ;; (widen) + ;; (org-cycle '(64)) + ;; (org-show-all '(drawers)) ; expand all props before make invisible to avoid ellipses. + (goto-char (point-min)) + (unless (org-at-heading-p) (outline-next-heading)) + (while (not (eobp)) + (let ((beg (1+ (progn (end-of-line) (point)))) + (end (1- (progn (org-end-of-meta-data t) (point))))) + (when (< beg end) + (push (make-overlay beg end) fpi/org-meta-heading-info-store) + (overlay-put (car fpi/org-meta-heading-info-store) 'invisible t))) + (when (not (org-at-heading-p)) + (outline-next-heading)))))) + +(defun mw-org-show-meta-info-lines () + "Show meta info." + (interactive) + (mapc #'delete-overlay fpi/org-meta-heading-info-store) + (setq fpi/org-meta-heading-info-store nil)) + + +(defun fpi/org-toggle-meta-info-lines () + (interactive) + (if fpi/org-meta-heading-info-store + (mw-org-show-meta-info-lines) + (mw-org-hide-meta-heading-info))) +(define-key fpi/toggle-map "m" #'fpi/org-toggle-meta-info-lines) +#+end_src +*** Workflow +My current workflow is largely inspired by [[http://doc.rix.si/cce/cce-org.html][Ryan Rix's]] and [[http://doc.norang.ca/org-mode.html][Bernt +Hansen's]] configs. + +First set the ids of some default and often referenced tasks. +#+begin_src emacs-lisp +(setq fpi/organization-task-id "52ac704f-9cc4-4291-9721-aa3cd3b34fae") +(setq fpi/lunch-task "e3d95e3b-416d-4265-835b-1ba57aa84704" + fpi/break-task "fede843d-02fc-4cdd-8a63-91905e727dab" + ;; fpi/prep-task "9d6279b8-c921-46e7-8ee4-b4d367dca1e0" + fpi/morning-flow "2bec1c12-2ee5-4f50-9eac-a018ca081d7d" + ) +#+end_src + +This hydra contains most parts of my current workflow. It has +everything from going to certain headlines, clocking time and +capturing notes. +#+begin_src emacs-lisp +(defhydra hydra-workflow (:hint nil) + " +Searching ----------> Do stuff --------> Do Stuff 2 -------> Workflow ---------------> Nar/Wid ------------------> +_i_: In-file headings _d_: Clock in _c_: Capture _m_: Prep meeting notes _n_: Narrow to Subtree +_h_: All headings _e_: Email _<_: Last Task _M_: Mail meeting notes _w_: Widen +_a_: Select an Agenda _l_: Lunch _j_: Jump Clock _B_: BBDB search _r_: Narrow to region +_g_: Go to active clock _b_: Break _P_: Insert BBDB _c_: Capture _t_: Show TODO Entries + _k_: Morning prep _z_: Capture Note _s_: *scratch* + _o_: Clock out _S_: Org Scratch +" + ("<" bh/clock-in-last-task) + ("a" org-agenda :exit t) + ("B" bbdb) + ("b" rrix/clock-in-break-task) ;; TODO + ("c" org-capture) + ("d" bh/punch-in) + ("e" rrix/clock-in-email-task) ;; TODO + ("g" org-clock-goto) + ("h" cce/org-goto-agenda-heading) + ("i" org-goto) + ("j" (progn + (interactive) + (setq current-prefix-arg '(4)) + (call-interactively 'org-clock-in))) + ("k" rrix/clock-morning-prep) + ("l" rrix/clock-in-lunch-task) + ("M" bh/mail-subtree) ;; TODO ;; checkout org-mime + ("m" bh/prepare-meeting-notes) ;; TODO + ("n" bh/narrow-to-subtree) ;; TODO + ("o" bh/punch-out) + ("P" bh/phone-call) ;; TODO + ("r" narrow-to-region) ;; neccessary? + ("S" bh/make-org-scratch) ;; neccessary? + ("s" bh/switch-to-scratch) ;; neccessary? + ("t" bh/org-todo) ;; neccessary? + ("w" bh/widen) ;; neccessary? + ("z" cce/note-to-clock)) + +(define-key fpi-map (kbd "f") 'hydra-workflow/body) +#+end_src +Basic flow: +1. Start your work by clocking in with ~bh/punch-in~. This sets a + predefined "Organizational" entry as default clocking entry and + clocks you in on it. +2. To start planning your day go to "Morning prep" or directly start + working on something and clock in on it using either "Jump clock" + or ~org-clock-in~ normally. +3. Do stuff. Change clocks, capture stuff, take notes, take breaks, … +4. At the end of the day clock out with ~bh/punch-out~. + +While punched in org continues to clock your time. Each time you clock +out of an entry it clocks you in on the parent entry or the default +organizational task. +#+begin_src emacs-lisp +(defun bh/clock-out-maybe () + (when (and bh/keep-clock-running + (not org-clock-clocking-in) + (marker-buffer org-clock-default-task) + (not org-clock-resolving-clocks-due-to-idleness)) + (rrix/clock-in-sibling-or-parent-task))) + +(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append) + +(defun rrix/clock-in-sibling-or-parent-task () + "Move point to the parent (project) task if any and clock in" + (let ((parent-task) + (parent-task-is-flow) + (sibling-task) + (curpoint (point))) + (save-excursion + (save-restriction + (widen) + (outline-back-to-heading) + (org-cycle) + (while (and (not parent-task) (org-up-heading-safe)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (goto-char curpoint) + (while (and (not sibling-task) (org-get-next-sibling)) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq sibling-task (point)))) + (setq parent-task-is-flow (cdr (assoc "FLOW" + (org-entry-properties parent-task)))) + (cond ((and sibling-task + parent-task-is-flow) + (org-with-point-at sibling-task + (org-clock-in)) + (org-clock-goto)) + (parent-task + (org-with-point-at parent-task + (org-clock-in)) + (org-clock-goto)) + (t (when bh/keep-clock-running + (bh/clock-in-default-task)))))))) +(defun bh/clock-in-default-task () + (save-excursion + (org-with-point-at org-clock-default-task + (org-clock-in) + (org-clock-goto)))) +#+end_src +Define the punch-in and punch-out functions. +#+begin_src emacs-lisp +(defun bh/punch-in (arg) + (interactive "p") + (setq bh/keep-clock-running t) + (if (equal major-mode 'org-agenda-mode) + (let* ((marker (org-get-at-bol 'org-hd-marker)) + (tags (org-with-point-at marker (org-get-tags-at)))) + (if (and (eq arg 4) tags) + (org-agenda-clock-in '(16)) + (bh/clock-in-organization-task-as-default))) + (save-restriction + (widen) + (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4)) + (org-clock-in '(16)) + (bh/clock-in-organization-task-as-default))))) + +(defun bh/clock-in-organization-task-as-default () + (interactive) + (org-with-point-at (org-id-find fpi/organization-task-id 'marker) + (org-clock-in '(16)))) + +(defun bh/punch-out () + (interactive) + (setq bh/keep-clock-running nil) + (when (org-clock-is-active) + (org-clock-out)) + (org-agenda-remove-restriction-lock)) +#+end_src +Clocking into a task by id and some default clock-in functions. +The separate functions are needed so they can be used in the hydra. +#+begin_src emacs-lisp +(defun bh/clock-in-task-by-id (id) + "Clock in a task by id" + (org-with-point-at (org-id-find id 'marker) + (org-clock-in nil))) +(setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id) + +(defun rrix/clock-in-lunch-task () + (interactive) + (bh/clock-in-task-by-id fpi/lunch-task) + (org-clock-goto) + (org-add-note)) +(defun rrix/clock-in-break-task () + (interactive) + (bh/clock-in-task-by-id fpi/break-task) + (org-agenda nil "i")) + +(defun rrix/clock-morning-prep () + (interactive) + (bh/clock-in-task-by-id fpi/morning-flow) + (org-clock-goto) + ;; (bh/narrow-to-subtree) + ) +#+end_src +Function to clock into the last task. +#+begin_src emacs-lisp +(defun bh/clock-in-last-task (arg) + "Clock in the interrupted task if there is one +Skip the default task and get the next one. +A prefix arg forces clock in of the default task." + (interactive "p") + (let ((clock-in-to-task + (cond + ((eq arg 4) org-clock-default-task) + ((and (org-clock-is-active) + (equal org-clock-default-task (cadr org-clock-history))) + (caddr org-clock-history)) + ((org-clock-is-active) (cadr org-clock-history)) + ((equal org-clock-default-task (car org-clock-history)) (cadr org-clock-history)) + (t (car org-clock-history))))) + (widen) + (org-with-point-at clock-in-to-task + (org-clock-in nil)))) +#+end_src +Add a note to the current clock +#+begin_src emacs-lisp +(defun cce/note-to-clock () + "Add a note to the currently clocked task." + (interactive) + (save-window-excursion + (org-clock-goto) + (org-add-note))) +#+END_SRC +Go to any heading in an agenda file (or more specifically in any file +included in 'org-refile-targets) +#+begin_src emacs-lisp +(defun cce/org-goto-agenda-heading (&optional prompt) + (interactive) + (let* ((location (org-refile-get-location (or prompt "Goto"))) + (file (cadr location)) + (marker (car (last location)))) + (find-file file) + (goto-char marker) + (org-show-context) + (current-buffer))) +#+END_SRC + +**** Filter functions +Various functions to determine if the current entry is a task, a +project or neither. +#+begin_src emacs-lisp +(defun bh/is-task-p () + "Any task with a todo keyword and no subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task (not has-subtask))))) +(defun bh/is-project-p () + "Any task with a todo keyword subtask" + (save-restriction + (widen) + (let ((has-subtask) + (subtree-end (save-excursion (org-end-of-subtree t))) + (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1))) + (save-excursion + (forward-line 1) + (while (and (not has-subtask) + (< (point) subtree-end) + (re-search-forward "^\*+ " subtree-end t)) + (when (member (org-get-todo-state) org-todo-keywords-1) + (setq has-subtask t)))) + (and is-a-task has-subtask)))) +(defun bh/find-project-task () + "Move point to the parent (project) task if any" + (save-restriction + (widen) + (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point)))) + (while (org-up-heading-safe) + (when (member (nth 2 (org-heading-components)) org-todo-keywords-1) + (setq parent-task (point)))) + (goto-char parent-task) + parent-task))) +(defun bh/is-project-subtree-p () + "Any task with a todo keyword that is in a project subtree. +Callers of this function already widen the buffer view." + (let ((task (save-excursion (org-back-to-heading 'invisible-ok) + (point)))) + (save-excursion + (bh/find-project-task) + (if (equal (point) task) + nil + t)))) +(defun bh/skip-project-tasks () + "Show non-project tasks. +Skip project and sub-project tasks, habits, and project related tasks." + (save-restriction + (widen) + (let* ((subtree-end (save-excursion (org-end-of-subtree t)))) + (cond + ((bh/is-project-p) + subtree-end) + ((org-is-habit-p) + subtree-end) + ((bh/is-project-subtree-p) + subtree-end) + (t + nil))))) +#+end_src +** Shell +#+begin_src emacs-lisp +(use-package shell + :commands (shell shell-command)) +#+end_src +To open and hide a shell quickly I use =shell-pop=. +#+begin_src emacs-lisp +(use-package shell-pop + :ensure t + :bind (("C-!" . shell-pop)) + :custom + (shell-pop-shell-type (quote ("shell" "*shell*" (lambda nil (shell))))) + (shell-pop-term-shell "/bin/bash")) +#+end_src +** Proced +Built-in process monitor. +#+BEGIN_SRC emacs-lisp +(use-package proced + :commands proced + :custom + (proced-toggle-auto-update t) + (proced-auto-update-interval 1) + (proced-descend t) + (proced-filter 'user)) +#+END_SRC +** Pass +Emacs interface & mode for the password manager [[https://www.passwordstore.org/][pass/password-store]]. +The emacs =pass= package provides a nice buffer listing all stored +passwords files and also a good mode to edit them. The +=password-store= package provides functions to copy and edit +individual password files. I'm not sure which one I'll end up using. I +bind a small function to copy a password or a field if called with a +prefix to my custom keymap. +#+BEGIN_SRC emacs-lisp +(use-package pass + :ensure t) +#+END_SRC +#+begin_src emacs-lisp +(use-package password-store + :ensure t + :commands (password-store-copy + password-store-edit + password-store-insert) + :custom (password-store-time-before-clipboard-restore 30) + :config (defun fpi/password-store-copy-pass-or-field (&optional arg) + (interactive "P") + (if arg + (call-interactively 'password-store-copy-field) + (call-interactively 'password-store-copy))) + :bind (:map fpi-map ("p" . fpi/password-store-copy-pass-or-field))) +#+end_src +*** auth-password-store/auth-source-pass +A password-store backend for the Emacs [[info:auth#Top][auth-source]] library which +normally uses the =~/.authinfo= file. For correct setup of +password-store files see [[https://rkm.id.au/2015/07/07/integrating-password-store-with-emacs/#fnr.1][here]] and in its [[https://github.com/DamienCassou/auth-password-store][github repo]]. For remote hosts +they need to contain the host and user info. The port is most of the +time inferred. The filename must also include the hostname. For +multiple users on the same host either use =user1@host.gpg= and +=user2@host.gpg= or =host/user1.gpg=, =host/user2.gpg=. +#+CAPTION: Example =pass= entry for use with =auth-source-pass= +#+begin_src pass-view + +host: localhost +user: root +#+end_src + +#+BEGIN_SRC emacs-lisp +(use-package auth-source-pass + :ensure t + :config (auth-source-pass-enable)) +#+END_SRC +** Ledger +Here is a good [[https://www.reddit.com/r/emacs/comments/8x4xtt][reddit thread]] about using ledger +#+BEGIN_SRC emacs-lisp +(use-package ledger-mode + :ensure t + :init (setq ledger-clear-whole-transactions 1) + :mode "\\.dat\\'" + "\\.ledger\\'") +;; (use-package flycheck-ledger +;; :ensure t +;; :after ledger-mode) +#+END_SRC + +I also use some =org-capture= templates to quickly capture +transactions. They are defined in [[file:emacs-private.el.gpg::4][emacs-private.el.gpg]]. + +** Elfeed + +#+BEGIN_SRC emacs-lisp +(use-package elfeed + :ensure t + :init + (setq elfeed-db-directory "~/.emacs.d/elfeed") + :custom + (elfeed-enclosure-default-dir "~/Downloads") + (elfeed-search-clipboard-type 'CLIPBOARD) + (elfeed-search-title-max-width (current-fill-column)) + (elfeed-search-title-min-width 30) + (elfeed-search-trailing-width 16) + (elfeed-show-truncate-long-urls t) + (elfeed-show-unique-buffers t) + :config + (defalias 'elfeed-toggle-star + (elfeed-expose #'elfeed-search-toggle-all 'star)) + (defun fpi/elfeed-search-show-entry-in-bg (entry) + (interactive (list (elfeed-search-selected :ignore-region))) + (elfeed-search-show-entry entry) + (bury-buffer)) + :bind + (:map elfeed-search-mode-map + ("m" . elfeed-toggle-star) + ("o" . fpi/elfeed-search-show-entry-in-bg) + ("j" . mz/make-and-run-elfeed-hydra) + )) +#+END_SRC +Some feeds I want to automatically mark as read. This way I can look +at them whenever I want to, but they don't show up in the unread search. +#+BEGIN_SRC emacs-lisp +(defun elfeed-mark-all-as-read () + (interactive) + (save-excursion + (mark-whole-buffer) + (elfeed-search-untag-all-unread))) +(defun elfeed-mark-search-read (search-string) + "Mark all results of SEARCH-STRING as read." + (interactive) + (elfeed) + (let ((filter elfeed-search-filter)) + (elfeed-search-set-filter search-string) + (elfeed-mark-all-as-read) + (elfeed-search-set-filter filter) + (bury-buffer))) +#+END_SRC +Now execute this whenever feeds are fetched +#+BEGIN_SRC emacs-lisp +(defun my/elfeed-mark-read (entry) + "Tag ENTRY as read if it contains certain tags" + (when (member 'tRaffic (elfeed-entry-tags entry)) + (elfeed-untag entry 'unread) + )) +(add-hook 'elfeed-new-entry-hook 'my/elfeed-mark-read) +#+END_SRC +*** Elfeed Org +Load elfeed org after adding ~my/elfeed-mark-read~ to +~elfeed-new-entry-hook~. New entries need to get tagged by elfeed org +first before marking them unread based on their tag. +#+BEGIN_SRC emacs-lisp +(use-package elfeed-org + :ensure t + :config + (elfeed-org) + (setq rmh-elfeed-org-files (list "~/.emacs.d/elfeed.org"))) +#+END_SRC +*** Hydra +This creates a smart hydra based on all available tags (see +https://cestlaz.github.io/posts/using-emacs-31-elfeed-3/). +#+BEGIN_SRC emacs-lisp +(defun z/hasCap (s) "" + (let ((case-fold-search nil)) + (string-match-p "[[:upper:]]" s) + )) +(defun z/get-hydra-option-key (s) + "returns single upper case letter (converted to lower) or first" + (interactive) + (let ( (loc (z/hasCap s))) + (if loc + (downcase (substring s loc (+ loc 1))) + (substring s 0 1) + ))) + +;; (active blogs cs eDucation emacs local misc sports star tech unread webcomics) +(defun mz/make-elfeed-cats (tags) + "Returns a list of lists. Each one is line for the hydra configuratio in the form + (c function hint)" + (interactive) + (mapcar (lambda (tag) + (let* ( + (tagstring (symbol-name tag)) + (c (z/get-hydra-option-key tagstring)) + ) + (list c (append '(elfeed-search-set-filter) (list (format "@6-months-ago +%s" tagstring) ))tagstring ))) + tags)) +(defmacro mz/make-elfeed-hydra () + `(defhydra mz/hydra-elfeed () + "filter" + ,@(mz/make-elfeed-cats (elfeed-db-get-all-tags)) + ("*" (elfeed-search-set-filter "@6-months-ago +star") "Starred") + ("M" elfeed-toggle-star "Mark") + ("A" (elfeed-search-set-filter "@6-months-ago") "All") + ("T" (elfeed-search-set-filter "@1-day-ago") "Today") + ("Q" bjm/elfeed-save-db-and-bury "Quit Elfeed" :color blue) + ("q" nil "quit" :color blue) + )) +(defun mz/make-and-run-elfeed-hydra () + "" + (interactive) + (mz/make-elfeed-hydra) + (mz/hydra-elfeed/body)) +#+END_SRC + +*** Youtube to Vlc +Open a entry with vlc +#+BEGIN_SRC emacs-lisp +(defface elfeed-youtube + '((t :foreground "#a9f")) + "Marks YouTube videos in Elfeed." + :group 'elfeed) + +(push '(youtube elfeed-youtube) + elfeed-search-face-alist) + +(defun elfeed-show-vlc () + "Play the current entry with vlc." + (interactive) + (pop-to-buffer (shell-command (format "vlc %s &" (elfeed-entry-link elfeed-show-entry))))) + +(defun elfeed-search-vlc () + "Play the current entry with vlc." + (interactive) + (let ((entries (elfeed-search-selected))) + (dolist (entry entries) + (shell-command (format "vlc %s &" (elfeed-entry-link entry))) + (elfeed-untag entry 'unread) + (elfeed-search-update-entry entry) + (unless (use-region-p) (forward-line))))) + +(define-key elfeed-show-mode-map "v" 'elfeed-show-vlc) +(define-key elfeed-search-mode-map "v" 'elfeed-search-vlc) +#+END_SRC +** Plotting data + +=gnuplot= is a great option for plotting any kind of data, no matter +where it comes from. + +#+begin_src emacs-lisp +(use-package gnuplot + :ensure t) +(use-package gnuplot-mode + :ensure t) +#+end_src +** HTML renderer +=shr= is the /Simple HTML renderer/ library, which Emacs uses to +display HTML. It is used by elfeed, notmuch and a variety of other +tools. + +- Open links in =eww= instead of the system browser +- Limit the entry width to the same as =fill-column= +- Limit the size of images + +#+BEGIN_SRC emacs-lisp + ;; (lambda (url &rest args) + ;; (if args + ;; (browse-url-default-browser url) + ;; (eww-browse-url url))) + +(use-package shr + :commands (eww eww-browse-url) + :custom + (browse-url-browser-function 'eww-browse-url) + (browse-url-generic-program "firefox") + (shr-external-browser 'browse-url-generic) + (shr-width (current-fill-column)) + (shr-max-image-proportion 0.4) + (shr-use-colors nil) + (shr-use-fonts nil) ) +#+END_SRC + +Support for HTML code blocks with proper syntax highlighting. See [[https://github.com/xuchunyang/shr-tag-pre-highlight.el][its GitHub project page]]. +#+BEGIN_SRC emacs-lisp +(use-package shr-tag-pre-highlight + :ensure t + :after shr + :config + (add-to-list 'shr-external-rendering-functions + '(pre . shr-tag-pre-highlight)) + (when (version< emacs-version "26") + (with-eval-after-load 'eww + (advice-add 'eww-display-html :around + 'eww-display-html--override-shr-external-rendering-functions)))) +#+END_SRC +** Email +For the setup of external mail specific programs see [[file:mail.org]]. +*** Sending mail +I use =msmtp= to send mail. + +- Infer the =envelope-from= header and therefore the adress to send + the mail from based on the /From/ header argument. +- Add a =-f username= flag to the sendmail command line call, as msmtp + needs it. +- Enable footnote-mode when entering message-mode. + +#+begin_src emacs-lisp +(use-package message + :custom + (message-send-mail-function 'message-send-mail-with-sendmail) + (message-sendmail-envelope-from 'header) + (message-sendmail-f-is-evil nil) + (message-kill-buffer-on-exit t) + :hook (message-mode . footnote-mode)) +(use-package sendmail + :custom + (send-mail-function 'smtpmail-send-it) + (sendmail-program "/usr/bin/msmtp") + (mail-specify-envelope-from t) + (mail-envelope-from 'header)) +#+end_src + +*** MUA/Notmuch + +After using =mu4e= as my mail user agent for a while I switched to +=notmuch=. I like the tagging based approach and the easy & great +searching. + +- Show newest messages first. +- Set archive tags to remove/set upon archiving a mail with =a=. +- Setup some saved searched. +- Set my signature (defined in [[file:emacs-private.el.gpg::13][emacs-private.el.gpg]]). +- Set the FCC directories where sent mails should be saved to (also + defined in [[file:emacs-private.el.gpg::20][emacs-private.el.gpg]].). +- Setup format=flowed +#+BEGIN_SRC emacs-lisp +(use-package notmuch + :ensure t + :custom + (notmuch-search-oldest-first nil) + (notmuch-archive-tags '("-inbox" "-td" "+archived")) + (notmuch-saved-searches + '((:name "inbox" :query "tag:inbox" :key "i") + (:name "unread (inb)" :query "(tag:unread and tag:inbox) or tag:td" :key "u") + (:name "unread (tot)" :query "tag:unread and date:6month..0month" :key "U") + (:name "flagged" :query "tag:flagged" :key "f") + (:name "sent" :query "tag:sent" :key "s") + (:name "drafts" :query "tag:draft" :key "d") + (:name "all mail" :query "*" :key "a") + (:name "tr" :query "tag:tr and date:6month..0month" :key "t"))) + (message-signature private/message-signature) + (notmuch-fcc-dirs private/notmuch-fcc-dirs)) +#+END_SRC +**** f=f +Hard new lines are identified using a ~hard~ text property and +displayed as =⏎=. We need to make sure all newlines inserted by +message initialization (signature, ...) also have this text property. +For now I use this bad code. +#+BEGIN_SRC emacs-lisp +(use-package messages-are-flowing + :ensure t + :config (add-hook 'message-mode-hook 'messages-are-flowing-use-and-mark-hard-newlines)) +(defun message-insert-signature (&optional force) + (interactive) + (goto-char (point-max)) + (let ((point (point))) + (message-insert-signature-original force) + (goto-char point) + (while (search-forward "\n" nil t) + (set-hard-newline-properties (- (point) 1) (point))))) + +(defun message-insert-signature-original (&optional force) + "Insert a signature. See documentation for variable `message-signature'. " + (interactive (list 0)) + (let* ((signature + (cond + ((and (null message-signature) + (eq force 0)) + (save-excursion + (goto-char (point-max)) + (not (re-search-backward message-signature-separator nil t)))) + ((and (null message-signature) + force) + t) + ((functionp message-signature) + (funcall message-signature)) + ((listp message-signature) + (eval message-signature)) + (t message-signature))) + signature-file) + (setq signature + (cond ((stringp signature) + signature) + ((and (eq t signature) message-signature-file) + (setq signature-file + (if (and message-signature-directory + ;; don't actually use the signature directory + ;; if message-signature-file contains a path. + (not (file-name-directory + message-signature-file))) + (expand-file-name message-signature-file + message-signature-directory) + message-signature-file)) + (file-exists-p signature-file)))) + (when signature + (goto-char (point-max)) + ;; Insert the signature. + (unless (bolp) + (newline)) + (when message-signature-insert-empty-line + (newline)) + (insert "-- ") + (newline) + (if (eq signature t) + (insert-file-contents signature-file) + (insert signature)) + (goto-char (point-max)) + (or (bolp) (newline))))) +#+END_SRC +** Context aware hydra +[[https://dfeich.github.io/www/org-mode/emacs/2018/05/10/context-hydra.html][dfeich]] has a nice post on this. Basically it launches a specific hydra +based on the current mode and context around point. +#+BEGIN_SRC emacs-lisp +(defun dfeich/context-hydra-launcher () + "A launcher for hydras based on the current context." + (interactive) + (cl-case major-mode + ((org-mode org-journal-mode) + (let* ((elem (org-element-context)) + (etype (car elem)) + (type (org-element-property :type elem))) + (cl-case etype + (headline (jmm/org-edna-hydra/body)) + (src-block (hydra-babel-helper/body)) + (link (hydra-org-link-helper/body)) + ((table-row table-cell) (hydra-org-table-helper/body) ) + (t (message "No specific hydra for %s/%s" etype type) + (hydra-org-default/body))))) + ('bibtex-mode (org-ref-bibtex-hydra/body)) + ('ibuffer-mode (hydra-ibuffer-main/body)) + (t (message "No hydra for this major mode: %s" major-mode)))) + +;;; *** org mode hydras +(defhydra hydra-org-default (:color pink :hint nil) + " +Org default hydra + +_l_ insert template from last src block +_s_ insert src block ref with helm + +_q_ quit +" + ("l" fpi/copy-last-src-block-head :color blue) + ("s" helm-lib-babel-insert :color blue) + ("q" nil :color blue)) + + +(defhydra hydra-org-link-helper (:color pink :hint nil) + " +org link helper +_b_ backward slurp _f_ forward slurp _n_ next link +_m_ backward barf _g_ forward barf _p_ previous link +_t_ terminal at path + +_q_ quit +" + ("b" org-link-edit-backward-slurp) + ("f" org-link-edit-forward-slurp) + ("m" org-link-edit-backward-barf) + ("g" org-link-edit-forward-barf) + ("n" org-next-link) + ("p" org-previous-link) + ("t" fpi/gnome-terminal-at-link :color blue) + ("q" nil :color blue)) + +(defhydra hydra-org-table-helper (:color pink :hint nil) + " +org table helper +_r_ recalculate _w_ wrap region _c_ toggle coordinates +_i_ iterate table _t_ transpose _D_ toggle debugger +_B_ iterate buffer _E_ export table _d_ edit field +_e_ eval formula _s_ sort lines + +_q_ quit +" + ("E" org-table-export :color blue) + ("s" org-table-sort-lines) + ("d" org-table-edit-field) + ("e" org-table-eval-formula) + ("r" org-table-recalculate) + ("i" org-table-iterate) + ("B" org-table-iterate-buffer-tables) + ("w" org-table-wrap-region) + ("D" org-table-toggle-formula-debugger) + ("t" org-table-transpose-table-at-point) + ("c" org-table-toggle-coordinate-overlays :color blue) + ("q" nil :color blue)) + +(defhydra hydra-babel-helper (:color pink :hint nil) + " +org babel src block helper functions +_n_ next _i_ info _I_ insert header +_p_ prev _c_ check +_h_ goto head _E_ expand +^ ^ _s_ split +_q_ quit _r_ remove result _e_ examplify region + +" + ("i" org-babel-view-src-block-info) + ("I" org-babel-insert-header-arg) + ("c" org-babel-check-src-block :color blue) + ("s" org-babel-demarcate-block :color blue) + ("n" org-babel-next-src-block) + ("p" org-babel-previous-src-block) + ("E" org-babel-expand-src-block :color blue) + ("e" org-babel-examplify-region :color blue) + ("r" org-babel-remove-result :color blue) + ("h" org-babel-goto-src-block-head) + ("q" nil :color blue)) + + +;;; *** ibuffer hydra +;; from https://github.com/abo-abo/hydra/wiki/Ibuffer +(defhydra hydra-ibuffer-main (:color pink :hint nil) + " + ^Navigation^ | ^Mark^ | ^Actions^ | ^View^ +-^----------^-+-^----^--------+-^-------^--------+-^----^------- + _p_: ʌ | _m_: mark | _D_: delete | _g_: refresh + _RET_: visit | _u_: unmark | _S_: save | _s_: sort + _n_: v | _*_: specific | _a_: all actions | _/_: filter +-^----------^-+-^----^--------+-^-------^--------+-^----^------- +" + ("n" ibuffer-forward-line) + ("RET" ibuffer-visit-buffer :color blue) + ("p" ibuffer-backward-line) + + ("m" ibuffer-mark-forward) + ("u" ibuffer-unmark-forward) + ("*" hydra-ibuffer-mark/body :color blue) + + ("D" ibuffer-do-delete) + ("S" ibuffer-do-save) + ("a" hydra-ibuffer-action/body :color blue) + + ("g" ibuffer-update) + ("s" hydra-ibuffer-sort/body :color blue) + ("/" hydra-ibuffer-filter/body :color blue) + + ("o" ibuffer-visit-buffer-other-window "other window" :color blue) + ("q" nil "quit" :color blue)) + +(defhydra hydra-ibuffer-mark (:color teal :columns 5 + :after-exit (hydra-ibuffer-main/body)) + "Mark" + ("*" ibuffer-unmark-all "unmark all") + ("M" ibuffer-mark-by-mode "mode") + ("m" ibuffer-mark-modified-buffers "modified") + ("u" ibuffer-mark-unsaved-buffers "unsaved") + ("s" ibuffer-mark-special-buffers "special") + ("r" ibuffer-mark-read-only-buffers "read-only") + ("/" ibuffer-mark-dired-buffers "dired") + ("e" ibuffer-mark-dissociated-buffers "dissociated") + ("h" ibuffer-mark-help-buffers "help") + ("z" ibuffer-mark-compressed-file-buffers "compressed") + ("b" hydra-ibuffer-main/body "back" :color blue)) + +(defhydra hydra-ibuffer-action (:color teal :columns 4 + :after-exit + (if (eq major-mode 'ibuffer-mode) + (hydra-ibuffer-main/body))) + "Action" + ("A" ibuffer-do-view "view") + ("E" ibuffer-do-eval "eval") + ("F" ibuffer-do-shell-command-file "shell-command-file") + ("I" ibuffer-do-query-replace-regexp "query-replace-regexp") + ("H" ibuffer-do-view-other-frame "view-other-frame") + ("N" ibuffer-do-shell-command-pipe-replace "shell-cmd-pipe-replace") + ("M" ibuffer-do-toggle-modified "toggle-modified") + ("O" ibuffer-do-occur "occur") + ("P" ibuffer-do-print "print") + ("Q" ibuffer-do-query-replace "query-replace") + ("R" ibuffer-do-rename-uniquely "rename-uniquely") + ("T" ibuffer-do-toggle-read-only "toggle-read-only") + ("U" ibuffer-do-replace-regexp "replace-regexp") + ("V" ibuffer-do-revert "revert") + ("W" ibuffer-do-view-and-eval "view-and-eval") + ("X" ibuffer-do-shell-command-pipe "shell-command-pipe") + ("b" nil "back")) + +(defhydra hydra-ibuffer-sort (:color amaranth :columns 3) + "Sort" + ("i" ibuffer-invert-sorting "invert") + ("a" ibuffer-do-sort-by-alphabetic "alphabetic") + ("v" ibuffer-do-sort-by-recency "recently used") + ("s" ibuffer-do-sort-by-size "size") + ("f" ibuffer-do-sort-by-filename/process "filename") + ("m" ibuffer-do-sort-by-major-mode "mode") + ("b" hydra-ibuffer-main/body "back" :color blue)) + +(defhydra hydra-ibuffer-filter (:color amaranth :columns 4) + "Filter" + ("m" ibuffer-filter-by-used-mode "mode") + ("M" ibuffer-filter-by-derived-mode "derived mode") + ("n" ibuffer-filter-by-name "name") + ("c" ibuffer-filter-by-content "content") + ("e" ibuffer-filter-by-predicate "predicate") + ("f" ibuffer-filter-by-filename "filename") + (">" ibuffer-filter-by-size-gt "size") + ("<" ibuffer-filter-by-size-lt "size") + ("/" ibuffer-filter-disable "disable") + ("b" hydra-ibuffer-main/body "back" :color blue)) +#+END_SRC +* Language settings +End sentences with single spaces. +#+begin_src emacs-lisp +(setq sentence-end-double-space nil) +#+end_src +* Interface +** General +#+begin_src emacs-lisp +(use-package emacs + :custom + (vc-follow-symlinks t) + (echo-keystrokes 0.25) + (auto-revert-verbose nil) + :config + (defalias 'yes-or-no-p 'y-or-n-p) + (put 'dired-find-alternate-file 'disabled nil) + (put 'narrow-to-region 'disabled nil)) +#+end_src +** Parentheses +#+begin_src emacs-lisp +(use-package paren + :custom + (show-paren-style 'mixed) + (show-paren-when-point-in-periphery t) + (show-paren-when-point-inside-paren t) + :config + (show-paren-mode 1)) +#+end_src +** Whitespace +I do not really care about spaces versus tabs most of the time. I only +want it to be consistent within a file. +#+begin_src emacs-lisp +(use-package emacs + :custom + (indent-tabs-mode nil)) +#+end_src +Instead of =$= use =⏎= to indicate newlines +#+begin_src emacs-lisp +(use-package whitespace +:custom (whitespace-display-mappings '((space-mark 32 + [183] + [46]) + (space-mark 160 + [164] + [95]) + (newline-mark 10 + [9166 10]) ;;[36 10] + (tab-mark 9 + [187 9] + [92 9])))) +#+end_src +** Undo + +Emacs undo mechanic can be confusing. =undo-tree= is a great package +but is prone to corruption and also does not allow undo based on the +active region. + +*** Undo-propose :ARCHIVE: +=undo-propose= shows undo changes in a temporary buffer. For the +keybindings see [[elisp:(which-key-show-full-keymap +'undo-propose-mode-map)]]. =undo-propose-commit= commits the cumulated +undo changes and the traveled undo history to the original buffer. +=undo-propose-squash-commit= omits the history. + +The temporary buffer messes up point and folding in org buffers. +Therefore I don't use it anymore and should someday look into how this +temporary buffer is created. + +#+begin_src emacs-lisp :tangle no +(use-package undo-propose + :ensure t + :bind (("C-/" . undo-propose))) +#+end_src +** Electric stuff +#+begin_src emacs-lisp +(use-package electric + :init + (setq electric-pair-inhibit-predicate 'electric-pair-conservative-inhibit) + (setq electric-pair-pairs '((34 . 34) + (8216 . 8217) + (8220 . 8221) + (171 . 187))) + (setq electric-pair-skip-self 'electric-pair-default-skip-self) + (setq electric-quote-context-sensitive t) + (setq electric-quote-paragraph t) + (setq electric-quote-string nil) + :config + (electric-indent-mode 1) + (electric-pair-mode 1) + (electric-quote-mode -1)) +#+end_src diff --git a/emacs-private.el.gpg b/emacs-private.el.gpg new file mode 100644 index 0000000..a34ae19 Binary files /dev/null and b/emacs-private.el.gpg differ diff --git a/init-exwm.org b/init-exwm.org new file mode 100644 index 0000000..792dcbe --- /dev/null +++ b/init-exwm.org @@ -0,0 +1,412 @@ +#+PROPERTY: header-args:emacs-lisp :results silent +,#+PROPERTY: header-args:emacs-lisp :tangle tangle/init-exwm.el + +When stating the client from .xinitrc, `save-buffer-kill-terminal' will +force-kill Emacs before it can run through `kill-emacs-hook'. +#+BEGIN_SRC emacs-lisp +(global-set-key (kbd "C-x C-c") 'save-buffers-kill-emacs) +#+END_SRC + + + +set keyboard +#+BEGIN_SRC emacs-lisp +(shell-command "setxkbmap -layout \"de(neo),us,ru,de\"") +#+END_SRC +* functions +#+BEGIN_SRC emacs-lisp +(defun ambrevar/switch-to-last-buffer () + "Switch to last open buffer in current window." + (interactive) + (switch-to-buffer (other-buffer (current-buffer) 1))) + +(defun ambrevar/toggle-window-dedicated () + "Toggle whether the current active window is dedicated or not. +Run it in each window you want to 'freeze', i.e. prevent Emacs +from acting on it." + (interactive) + (message + (if (let ((window (get-buffer-window (current-buffer)))) + (set-window-dedicated-p window + (not (window-dedicated-p window)))) + "Window '%s' is dedicated" + "Window '%s' is normal") + (current-buffer))) + +(defun ambrevar/toggle-window-split () + "Switch between vertical and horizontal split. +It only works for frames with exactly two windows." + (interactive) + (if (= (count-windows) 2) + (let* ((this-win-buffer (window-buffer)) + (next-win-buffer (window-buffer (next-window))) + (this-win-edges (window-edges (selected-window))) + (next-win-edges (window-edges (next-window))) + (this-win-2nd (not (and (<= (car this-win-edges) + (car next-win-edges)) + (<= (cadr this-win-edges) + (cadr next-win-edges))))) + (splitter + (if (= (car this-win-edges) + (car (window-edges (next-window)))) + 'split-window-horizontally + 'split-window-vertically))) + (delete-other-windows) + (let ((first-win (selected-window))) + (funcall splitter) + (if this-win-2nd (other-window 1)) + (set-window-buffer (selected-window) this-win-buffer) + (set-window-buffer (next-window) next-win-buffer) + (select-window first-win) + (if this-win-2nd (other-window 1)))))) + +(defun ambrevar/toggle-single-window () + "Un-maximize current window. +If multiple windows are active, save window configuration and +delete other windows. If only one window is active and a window +configuration was previously save, restore that configuration." + (interactive) + (if (= (count-windows) 1) + (when single-window--last-configuration + (set-window-configuration single-window--last-configuration)) + (setq single-window--last-configuration (current-window-configuration)) + (delete-other-windows))) +#+END_SRC +** Window swapping +#+BEGIN_SRC emacs-lisp +(defun ambrevar/swap-windows (&optional w1 w2) + "If 2 windows are up, swap them. +Else if W1 is a window, swap it with current window. +If W2 is a window too, swap both." + (interactive) + (unless (or (= 2 (count-windows)) + (windowp w1) + (windowp w2)) + (error "Ambiguous window selection")) + (let* ((w1 (or w1 (car (window-list)))) + (w2 (or w2 + (if (eq w1 (car (window-list))) + (nth 1 (window-list)) + (car (window-list))))) + (b1 (window-buffer w1)) + (b2 (window-buffer w2)) + (s1 (window-start w1)) + (s2 (window-start w2))) + (with-temp-buffer + ;; Some buffers like EXWM buffers can only be in one live buffer at once. + ;; Switch to a dummy buffer in w2 so that we don't display any buffer twice. + (set-window-buffer w2 (current-buffer)) + (set-window-buffer w1 b2) + (set-window-buffer w2 b1)) + (set-window-start w1 s2) + (set-window-start w2 s1)) + (select-window w1)) + +(defun ambrevar/swap-windows-left () + "Swap current window with the window to the left." + (interactive) + (ambrevar/swap-windows (window-in-direction 'left))) +(defun ambrevar/swap-windows-below () + "Swap current window with the window below." + (interactive) + (ambrevar/swap-windows (window-in-direction 'below))) +(defun ambrevar/swap-windows-above () + "Swap current window with the window above." + (interactive) + (ambrevar/swap-windows (window-in-direction 'above))) +(defun ambrevar/swap-windows-right () + "Swap current window with the window to the right." + (interactive) + (ambrevar/swap-windows (window-in-direction 'right))) +#+END_SRC +** Volume & Brightness +#+BEGIN_SRC emacs-lisp +(defun exwm-brightness (incdec) + (shell-command (concat "xbacklight " incdec "10")) + (notifications-notify :title (substring (shell-command-to-string "xbacklight") 0 -1) + :replaces-id 6969 + :urgency 'low + :timeout 550)) + +(defun exwm-volume (incdec) + (notifications-notify + :title (format + "Volume %s" + (substring + (shell-command-to-string + (format "amixer -D pulse set Master 5%%%s|tail -n 1|cut -d '[' -f 2|cut -d ']' -f 1" + incdec)) 0 -1)) + :replaces-id 6968 + :urgency 'low + :timeout 550)) +(defun exwm-togglemute () + (interactive) + (notifications-notify + :title (format + "Volume %s" + (substring + (shell-command-to-string + "amixer -D pulse set Master toggle|tail -n 1|cut -d '[' -f 3|cut -d ']' -f 1") 0 -1)) + :replaces-id 6968 + :urgency 'low + :timeout 550)) +#+END_SRC +** XF86 Multimedia keys +#+BEGIN_SRC emacs-lisp +(defun exwm-xf86audio (cmd) + ;; Control Spotify + (shell-command (concat "dbus-send --type=method_call --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player." cmd))) +#+END_SRC +** Browser switching +#+BEGIN_SRC emacs-lisp +(defun fpi/helm-exwm-switch (class &optional program other-window) + "Switch to some EXWM windows belonging to CLASS. +If current window is not showing CLASS, switch to the last open CLASS window. +If there is none, start PROGRAM. + +If PROGRAM is nil, it defaults to CLASS. +With prefix argument or if OTHER-WINDOW is non-nil, open in other window." + ;; If current window is not in `exwm-mode' we switch to it. Therefore we must + ;; also make sure that current window is not a Helm buffer, otherwise calling + ;; this function will lose focus in Helm. + (unless helm-alive-p + (setq program (or program class) + other-window (or other-window current-prefix-arg)) + (let ((filter (lambda () + (member (downcase (or exwm-class-name "")) class)))) + (if (and (eq major-mode 'exwm-mode) + (funcall filter)) + (let ((helm-buffer-details-flag nil)) + (helm-exwm filter)) + (let ((last (buffer-list))) + (while (and last + (not (with-current-buffer (car last) + (and (eq major-mode 'exwm-mode) + (funcall filter))))) + (setq last (cdr last))) + (if last + (funcall (if other-window 'switch-to-buffer-other-window 'switch-to-buffer) (car last)) + (when other-window (select-window (split-window-sensibly))) + (start-process-shell-command program nil program))))))) + +(defun fpi/helm-exwm-switch-browser () + "Switch to some `browse-url-generic-program' windows. + +See `helm-exwm-switch'." + (interactive) + (fpi/helm-exwm-switch (quote ("qutebrowser" + "firefox")) + browse-url-generic-program)) +#+END_SRC +* config +Time & Battery display +#+BEGIN_SRC emacs-lisp +(display-time) +(display-battery-mode) +#+END_SRC +Rename buffer to window title.\\ +Spotify's title does not include "spotify" while playing music so just +append it. +#+BEGIN_SRC emacs-lisp +(defun fpie/exwm-rename-buffer-to-title () + (let ((newname (if (string-match "Spotify" (buffer-name)) + (concat exwm-title " - Spotify") + exwm-title))) + (exwm-workspace-rename-buffer newname))) + +(add-hook 'exwm-update-title-hook 'fpie/exwm-rename-buffer-to-title) +#+END_SRC +#+BEGIN_SRC emacs-lisp +(add-hook 'exwm-floating-setup-hook 'exwm-layout-hide-mode-line) +(add-hook 'exwm-floating-exit-hook 'exwm-layout-show-mode-line) +#+END_SRC + +Non-floating resizing with mouse +#+BEGIN_SRC emacs-lisp +(setq window-divider-default-bottom-width 2 + window-divider-default-right-width 2) +(window-divider-mode) +#+END_SRC +System tray +#+BEGIN_SRC emacs-lisp +(require 'exwm-systemtray) +(exwm-systemtray-enable) +(setq exwm-systemtray-height 16) +#+END_SRC ++auto focus+ +#+BEGIN_SRC emacs-lisp :tangle no +(setq mouse-autoselect-window t + focus-follows-mouse t) +#+END_SRC +List all buffers +#+BEGIN_SRC emacs-lisp +(setq exwm-workspace-show-all-buffers t) +(setq exwm-layout-show-all-buffers t) +#+END_SRC +** Helm +#+BEGIN_SRC emacs-lisp :results silent +(with-eval-after-load 'helm + ;; Need `with-eval-after-load' here since 'helm-map is not defined in 'helm-config. + (define-key helm-map (kbd "s-\\") 'helm-toggle-resplit-and-swap-windows) + (exwm-input--set-key (kbd "s-p") 'helm-run-external-command) + (exwm-input-set-key (kbd "s-c") 'helm-resume) + (exwm-input-set-key (kbd "s-b") 'helm-mini) + (exwm-input-set-key (kbd "s-f") 'helm-find-files) + (exwm-input-set-key (kbd "s-F") 'helm-locate) + ;;(when (fboundp 'ambrevar/helm-locate-meta) + ;; (exwm-input-set-key (kbd "s-F") #'ambrevar/helm-locate-meta)) + ;;(exwm-input-set-key (kbd "s-g") 'ambrevar/helm-grep-git-or-ag) + ;;(exwm-input-set-key (kbd "s-G") 'ambrevar/helm-grep-git-all-or-ag) + ) + +(use-package helm-exwm) +(exwm-input-set-key (kbd "s-w") #'fpi/helm-exwm-switch-browser) +(exwm-input-set-key (kbd "s-W") #'helm-exwm-switch-browser-other-window) +#+END_SRC +** Keys +Global bindings +#+BEGIN_SRC emacs-lisp +(exwm-input-set-key (kbd "s-K") #'exwm-reset) +(exwm-input-set-key (kbd "s-x") #'exwm-input-toggle-keyboard) + +(exwm-input-set-key (kbd "s-s") #'windmove-left) +(exwm-input-set-key (kbd "s-n") #'windmove-down) +(exwm-input-set-key (kbd "s-r") #'windmove-up) +(exwm-input-set-key (kbd "s-t") #'windmove-right) + +(exwm-input-set-key (kbd "M-s") #'ace-jump-word-mode) +(exwm-input-set-key (kbd "s-B") #'ibuffer-list-buffers) +(exwm-input-set-key (kbd "s-X") #'kill-this-buffer) + +(exwm-input-set-key (kbd "s-M") #'exwm-workspace-switch) + +(exwm-input-set-key (kbd "s-\\") 'ambrevar/toggle-window-split) +(exwm-input-set-key (kbd "s-S") 'ambrevar/swap-windows-left) +(exwm-input-set-key (kbd "s-N") 'ambrevar/swap-windows-below) +(exwm-input-set-key (kbd "s-R") 'ambrevar/swap-windows-above) +(exwm-input-set-key (kbd "s-T") 'ambrevar/swap-windows-right) + +(exwm-input-set-key (kbd "s-") #'ambrevar/switch-to-last-buffer) +(exwm-input-set-key (kbd "s-") (lambda () + (interactive) + (start-process "term" nil "tilix"))) +(exwm-input-set-key (kbd "s-h") 'bury-buffer) + +(exwm-input-set-key (kbd "s-g") 'previous-buffer) +(exwm-input-set-key (kbd "s-G") 'next-buffer) +#+END_SRC +#+BEGIN_SRC emacs-lisp +(exwm-input-set-key (kbd "s-!") 'helm-pass) +#+END_SRC +Volume & Brightness +#+BEGIN_SRC emacs-lisp +(exwm-input-set-key [XF86AudioLowerVolume] (lambda () (interactive) (exwm-volume "-"))) +(exwm-input-set-key [XF86AudioRaiseVolume] (lambda () (interactive) (exwm-volume "+"))) +(exwm-input-set-key [XF86AudioMute] 'exwm-togglemute) +(exwm-input-set-key [XF86MonBrightnessUp] (lambda () (interactive) (exwm-brightness "+"))) +(exwm-input-set-key [XF86MonBrightnessDown] (lambda () (interactive) (exwm-brightness "-"))) +#+END_SRC +XF86 Multimedia Keys +#+BEGIN_SRC emacs-lisp +(exwm-input--set-key [XF86AudioPlay] (lambda () (interactive) (exwm-xf86audio "PlayPause"))) +(exwm-input--set-key [XF86AudioPause] (lambda () (interactive) (exwm-xf86audio "PlayPause"))) +(exwm-input--set-key [XF86AudioNext] (lambda () (interactive) (exwm-xf86audio "Next"))) +(exwm-input--set-key [XF86AudioPrev] (lambda () (interactive) (exwm-xf86audio "Previous"))) +#+END_SRC +*** Local bindings +#+BEGIN_SRC emacs-lisp +(push ?\s- exwm-input-prefix-keys) +(define-key exwm-mode-map (kbd "s-SPC") #'exwm-floating-toggle-floating) +(define-key exwm-mode-map (kbd "s-i") #'follow-delete-other-windows-and-split) ;; any useful? +(define-key exwm-mode-map (kbd "s-o") #'ambrevar/toggle-single-window) +(define-key exwm-mode-map (kbd "s-O") #'exwm-layout-toggle-fullscreen) + +(define-key exwm-mode-map (kbd "C-q") #'exwm-input-send-next-key) +#+END_SRC +Allow access to my personal keymap. +#+BEGIN_SRC emacs-lisp +(push ?\C-z exwm-input-prefix-keys) +#+END_SRC + +*** Simulation keys +#+BEGIN_SRC emacs-lisp +(setq exwm-input-simulation-keys + '(([?\C-b] . [left]) + ([?\C-f] . [right]) + ([?\C-p] . [up]) + ([?\C-n] . [down]) + ([?\C-a] . [home]) + ([?\C-e] . [end]) + ([?\M-v] . [prior]) + ([?\C-v] . [next]) + ([?\C-d] . [delete]))) + ;;([?\C-k] . [S-end delete]))) ; doesn't work in tilix +#+END_SRC +** Multiple monitors +#+BEGIN_SRC emacs-lisp +(require 'exwm-randr) +(setq exwm-randr-workspace-output-plist + '(0 "DP1" 1 "HDMI1" 2 "HDMI2" 3 "eDP1")) +(exwm-randr-enable) +#+END_SRC +** Configure helm-raise-command +~(shell-command "emacsclient -e ...")~ does not work. Advice +~helm-run-or-raise~ instead and overshadow ~shell-command~. + +For now ~helm-run-or-raise~ is redefined after helm is loaded in +~emacs-init.org~ instead of advised. +#+begin_src emacs-lisp +(defun fpi/get-proc-buffers (proc) + (let ((cand (helm-exwm-candidates))) + (remove + nil (mapcar + (lambda (c) + (if (equal + (downcase proc) + (downcase (with-current-buffer c (or exwm-class-name "")))) + c + nil)) cand)))) +(defun fpi/switch-to-proc-buffer (proc) + (switch-to-buffer (car (fpi/get-proc-buffers proc)))) + +;; (setq helm-raise-command "emacsclient -e '(fpi/switch-to-proc-buffer \"%s\")'") +(setq helm-raise-command t) +#+end_src +** Screenshots +UncleDave has a nice exwm configuration in his [[https://github.com/daedreth/UncleDavesEmacs/blob/master/config.org][config]]. These snippets +are taken from there. + +A nice alternative for screenshots in org-mode is ~org-screenshot.el~. +It uses ~scrot~ to take screenshots of windows and insert a link the +image into the current org buffer. + +*** Screenshotting the entire screen +#+BEGIN_SRC emacs-lisp + (defun daedreth/take-screenshot () + "Takes a fullscreen screenshot of the current workspace" + (interactive) + (when window-system + (loop for i downfrom 3 to 1 do + (progn + (message (concat (number-to-string i) "...")) + (sit-for 1))) + (message "Cheese!") + (sit-for 1) + (start-process "screenshot" nil "import" "-window" "root" + (concat (getenv "HOME") "/" (subseq (number-to-string (float-time)) 0 10) ".png")) + (message "Screenshot taken!"))) + (global-set-key (kbd "") 'daedreth/take-screenshot) +#+END_SRC + +*** Screenshotting a region +#+BEGIN_SRC emacs-lisp +(defun daedreth/take-screenshot-region () + "Takes a screenshot of a region selected by the user." + (interactive) + (when window-system + (call-process "import" nil nil nil ".newScreen.png") + (call-process "convert" nil nil nil ".newScreen.png" "-shave" "1x1" + (concat (getenv "HOME") "/" (subseq (number-to-string (float-time)) 0 10) ".png")) + (call-process "rm" nil nil nil ".newScreen.png"))) +;; (global-set-key (kbd "") 'daedreth/take-screenshot-region) +#+END_SRC -- cgit v1.2.3