dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

commit 87219d8702b9a1dab9d137ddcab1ac18a20d2236
parent 86032cc79804fd516655992b611f6a721a0085e5
Author: Alex Balgavy <alex@balgavy.eu>
Date:   Wed, 16 Mar 2022 19:13:02 +0100

emacs: sync org agenda via caldav (and some reorganising)

Diffstat:
Memacs/config.org | 1555+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 801 insertions(+), 754 deletions(-)

diff --git a/emacs/config.org b/emacs/config.org @@ -191,40 +191,17 @@ This lets me jump to any position in Emacs rather quickly, sometimes it's useful (("C-:" . 'avy-goto-char-timer))) #+end_src -** org -In org mode, I want to use bullets instead of stars, so I also install ~org-bullets~. -Furthermore, tags were getting cut off, so I manually set the best column to display them. - -#+begin_src emacs-lisp - (defun za/settings-org-mode () - "My settings for org mode" - (org-bullets-mode 1) - (za/toggle-wrap t) - (org-indent-mode) - (setq org-tags-column (- 10 (window-total-width))) - ;; Realign tags - (org-set-tags-command '(4))) - - (defun za/settings-org-agenda-mode () - "My settings for org agenda mode" - (setq org-agenda-tags-column (- 10 (window-total-width)))) -#+end_src +** Org +*** Installation +Install Org and require additional components that I use. #+begin_src emacs-lisp (use-package org :config - (unless (package-installed-p 'org-bullets) - (package-refresh-contents) - (package-install 'org-bullets)) - (use-package org-bullets) (require 'org-tempo) (require 'org-habit) (require 'org-agenda) - :init - (add-hook 'org-mode-hook #'za/settings-org-mode) - (add-hook 'org-agenda-mode-hook #'za/settings-org-agenda-mode) - :bind (("C-c a" . org-agenda) ("C-c n" . org-capture) @@ -232,1108 +209,1178 @@ Furthermore, tags were getting cut off, so I manually set the best column to dis ("C-c l" . org-store-link))) #+end_src -To be able to link to emails via notmuch, I use ol-notmuch: +*** Nicer bullets +In org mode, I want to use bullets instead of stars, so I also install ~org-bullets~. #+begin_src emacs-lisp - (use-package ol-notmuch :quelpa) + (use-package org-bullets) #+end_src -** calfw -Basically provides a way to show the org agenda as a standard GUI calendar app would. +*** Org mode settings +Furthermore, tags were getting cut off, so I manually set the best column to display them. #+begin_src emacs-lisp - (use-package calfw - :config - (use-package calfw-org) - (setq cfw:org-overwrite-default-keybinding t) - (setq calendar-week-start-day 1)) + (defun za/settings-org-mode () + "My settings for org mode" + (org-bullets-mode 1) + (za/toggle-wrap t) + (org-indent-mode) + (setq org-tags-column (- 10 (window-total-width))) + ;; Realign tags + (org-set-tags-command '(4))) + + (add-hook 'org-mode-hook #'za/settings-org-mode) #+end_src -** lean-mode -Specifically for the Lean prover. -I also install company-lean and helm-lean, which are suggested on the [[https://github.com/leanprover/lean-mode][Github page]]. -Then I map company-complete only for lean-mode. +*** Enable linking to email via notmuch +To be able to link to emails via notmuch, I use ol-notmuch: #+begin_src emacs-lisp - (use-package lean-mode - :config - (use-package company-lean) - :hook - (lean-mode . (lambda () (define-key lean-mode-map (kbd "S-SPC") #'company-complete)))) + (use-package ol-notmuch :quelpa) #+end_src -** magit +*** Agenda & GTD +**** Agenda mode settings +Fix tag display by dynamically calculating the column. + #+begin_src emacs-lisp - (use-package magit) -#+end_src + (defun za/settings-org-agenda-mode () + "My settings for org agenda mode" + (setq org-agenda-tags-column (- 10 (window-total-width)))) -** vterm -Emacs has a bunch of built-in terminal emulators. -And they all suck. -(OK not really, eshell is alright, but not for interactive terminal programs like newsboat/neomutt) + (add-hook 'org-agenda-mode-hook #'za/settings-org-agenda-mode) +#+end_src -Also use emacsclient inside vterm as an editor, because that'll open documents in the existing Emacs session. -And I'm not gonna be a heretic and open Vim inside of Emacs. +**** Set file locations +Which files should be included in the agenda (I have to use ~list~ to evaluate the variables, because org-agenda-files expects strings): #+begin_src emacs-lisp - (use-package vterm - :hook - (vterm-mode . (lambda () (unless server-process (server-start))))) + (setq org-agenda-files (list za/org-life-main + za/org-life-inbox + za/org-life-tickler)) #+end_src -I'll bind a key to start a vterm or switch to the running vterm: +Convenience functions to make opening the main file faster: #+begin_src emacs-lisp - (defun switch-to-vterm () "Switch to a running vterm, or start one and switch to it." - (interactive) - (if (get-buffer vterm-buffer-name) - (switch-to-buffer vterm-buffer-name) - (vterm))) - (global-set-key (kbd "C-c t") 'switch-to-vterm) + (defun gtd () "GTD: main file" (interactive) (find-file za/org-life-main)) + (defun gtd-inbox () "GTD: inbox" (interactive) (find-file za/org-life-inbox)) + (defun gtd-archive () "GTD: archive" (interactive) (find-file za/org-life-archive)) + (defun gtd-someday () "GTD: someday" (interactive) (find-file za/org-life-someday)) + (defun gtd-reference () "GTD: reference" (interactive) (find-file za/org-life-reference)) + (defun gtd-tickler () "GTD: tickler" (interactive) (find-file za/org-life-tickler)) #+end_src -** sr-speedbar -Make speed bar show in the current frame. +Bind keys to those functions: #+begin_src emacs-lisp - (use-package sr-speedbar - :config - (setq sr-speedbar-right-side nil) - (define-key speedbar-mode-map (kbd "q") 'sr-speedbar-close)) - + (global-set-key (kbd "C-c g i") 'gtd-inbox) + (global-set-key (kbd "C-c g g") 'gtd) + (global-set-key (kbd "C-c g a") 'gtd-archive) + (global-set-key (kbd "C-c g s") 'gtd-someday) + (global-set-key (kbd "C-c g r") 'gtd-reference) + (global-set-key (kbd "C-c g t") 'gtd-tickler) #+end_src - -Jump to speedbar. sr-speedbar-exist-p can be void, so I check if it's bound first. -If it's not bound, or if it's false, first open the speedbar. -Then, select it. +**** Refiling & archiving +Where I want to be able to move subtrees (doesn't include inbox because I never refile to that, and the archive has its own keybining): #+begin_src emacs-lisp - (global-set-key (kbd "C-c F") (lambda () (interactive) - (if (or (not (boundp 'sr-speedbar-exist-p)) - (not (sr-speedbar-exist-p))) - (sr-speedbar-open)) - (sr-speedbar-select-window))) + (setq org-refile-targets `((,za/org-life-main :maxlevel . 3) + (,za/org-life-someday :level . 1) + (,za/org-life-tickler :maxlevel . 2) + (,za/org-life-reference :maxlevel . 2))) #+end_src -** expand-region -Expand the selected region semantically. +I want to archive to a specific file, in a date tree: #+begin_src emacs-lisp - (use-package expand-region - :bind ("C-=" . er/expand-region)) + (setq org-archive-location (concat za/org-life-archive "::datetree/")) #+end_src -** flycheck -Install flycheck, and enable it by default in certain major modes: +Include the destination file as an element in the path to a heading, and to use the full paths as completion targets rather than just the heading text itself: #+begin_src emacs-lisp - (use-package flycheck - :hook (sh-mode . flycheck-mode)) + (setq org-refile-use-outline-path 'file) #+end_src -** anki-editor -Some extra keybindings that are not set up by default. -anki-editor doesn't provide a keymap so I have to set one up here: +Tell Org that I don’t want to complete in steps; I want Org to generate all of the possible completions and present them at once (necessary for Helm/Ivy): #+begin_src emacs-lisp - (use-package anki-editor - :config - (defvar anki-editor-mode-map (make-sparse-keymap)) - (add-to-list 'minor-mode-map-alist (cons 'anki-editor-mode - anki-editor-mode-map)) + (setq org-outline-path-complete-in-steps nil) +#+end_src - (setq anki-editor-use-math-jax t) +Allow me to tack new heading names onto the end of my outline path, and if I am asking to create new ones, make me confirm it: - :hook - (anki-editor-mode . (lambda () - (define-key anki-editor-mode-map (kbd "C-c t") #'org-property-next-allowed-value) - (define-key anki-editor-mode-map (kbd "C-c i") #'anki-editor-insert-note) - (define-key anki-editor-mode-map (kbd "C-c p") #'anki-editor-push-notes) - (define-key anki-editor-mode-map (kbd "C-c c") #'anki-editor-cloze-dwim)))) +#+begin_src emacs-lisp + (setq org-refile-allow-creating-parent-nodes 'confirm) #+end_src -** rainbow-mode -'rainbow-mode' lets you visualise hex colors: +**** Quick capture +Quick capture lets me send something to my inbox very quickly, without thinking about where it should go. +The inbox is processed later. + +Templates for quick capture: #+begin_src emacs-lisp - (use-package rainbow-mode - :hook (emacs-lisp-mode . rainbow-mode)) + (setq org-capture-templates `(("t" "Todo [inbox]" entry + (file ,za/org-life-inbox) + "* TODO %i%?") + + ("s" "Save for read/watch/listen" entry + (file+headline ,za/org-life-tickler "Read/watch/listen") + "* TODO %?[[%^{link}][%^{description}]] %^G"))) #+end_src -** pdf-tools -A better replacement for DocView: +**** Todo & custom agenda views +Todo keywords based on the GTD system (pipe separates incomplete from complete). +Apart from the logging-on-done configured [[*Logging][below]], I also want to log a note & timestamp when I start waiting on something. +In ~org-todo-keywords~, ~@~ means note+timestamp, ~!~ means timestamp, ~@/!~ means note+timestamp on state entry and timestamp on leave. #+begin_src emacs-lisp - (use-package pdf-tools - :config - (setq-default pdf-annot-default-annotation-properties '((t - (label . "Alex Balgavy")) - (text - (icon . "Note") - (color . "#0088ff")) - (highlight - (color . "yellow")) - (squiggly - (color . "orange")) - (strike-out - (color . "red")) - (underline - (color . "blue")))) - :hook - (pdf-annot-list-mode . pdf-annot-list-follow-minor-mode) - (pdf-annot-edit-contents-minor-mode . org-mode) - (pdf-view-mode . (lambda () (display-line-numbers-mode 0))) - (pdf-view-mode . (lambda () (define-key pdf-isearch-minor-mode-map (kbd "C-s") #'isearch-forward)))) - (pdf-tools-install) + (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w@)" "SOMEDAY(s)" "|" "DONE(d)" "CANCELLED(c)")) + org-todo-keyword-faces '(("TODO" . org-todo) + ("NEXT" . org-todo) + ("WAITING" . org-todo) + ("SOMEDAY" . org-todo) + ("DONE" . org-done) + ("CANCELLED" . org-done))) #+end_src -** virtualenvwrapper -Like virtualenvwrapper.sh, but for Emacs. +I decided that projects will not be TODO items, but their progress will be tracked with a progress cookie ([x/y]). This function converts an item to a project: it adds a PROJECT tag, sets the progress indicator to count all checkboxes in sub-items (only TODO items), and removes any existing TODO keywords. Finally, PROJECT tags shouldn't be inherited (i.e. subtasks shouldn't be marked as projects). #+begin_src emacs-lisp - (use-package virtualenvwrapper - :config - (venv-initialize-interactive-shells) - (venv-initialize-eshell) - (setq venv-location "~/.config/virtualenvs")) + (defun za/mark-as-project () + "This function makes sure that the current heading has + (1) the tag PROJECT + (2) the property COOKIE_DATA set to \"todo recursive\" + (3) a leading progress indicator" + (interactive) + (org-set-property "TODO" "") + (org-toggle-tag "PROJECT" 'on) + (org-set-property "COOKIE_DATA" "todo recursive") + (org-back-to-heading t) + (forward-whitespace 1) + (insert "[/] ") + (org-update-statistics-cookies nil)) #+end_src -** org-ref +Only the top-level project headlines should be tagged as projects, so disable inheritance of that tag: + #+begin_src emacs-lisp - (use-package org-ref) + (setq org-tags-exclude-from-inheritance '("PROJECT")) #+end_src -** org-noter +Define a function to skip items if they're part of a project (i.e. one of their parents has a "PROJECT" tag). +The problem is, the "PROJECT" tag isn't inherited. So, we temporarily disable excluding from inheritance, just for the ~org-get-tags~ call. Then check if "PROJECT" is one of the tags. + #+begin_src emacs-lisp - (use-package org-noter) + (defun za/skip-if-in-project () + "Skip items that are part of a project" + (let ((subtree-end (save-excursion (org-end-of-subtree t))) + (item-tags (let ((org-tags-exclude-from-inheritance nil)) (org-get-tags)))) + (if (member "PROJECT" item-tags) + subtree-end + nil))) + #+end_src -** hl-todo -I want to highlight TODO keywords in comments: +Also, define a function to skip tasks (trees) that are not habits (i.e. don't have the STYLE property ~habit~): #+begin_src emacs-lisp - (use-package hl-todo - :custom-face - (hl-todo ((t (:inherit hl-todo :underline t)))) - :config - (setq hl-todo-keyword-faces - '(("TODO" . "#ff7060") - ("FIXME" . "#caa000"))) - (global-hl-todo-mode t)) + (defun za/skip-unless-habit () + "Skip trees that are not habits" + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (if (string= (org-entry-get nil "STYLE") "habit") + nil + subtree-end))) #+end_src -** undo-tree -Sometimes it's better to look at undo history as a tree: + +And one to skip tasks that /are/ habits: #+begin_src emacs-lisp - (use-package undo-tree - :config - (global-undo-tree-mode)) -#+end_src -** ledger -#+begin_src emacs-lisp - (use-package ledger-mode - :mode ("\\.ledger\\'") - :config - (setq ledger-clear-whole-transactions t - ledger-reconcile-default-commodity "eur")) + (defun za/skip-if-habit () + "Skip trees that are not habits" + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (if (string= (org-entry-get nil "STYLE") "habit") + subtree-end + nil))) #+end_src -Custom reports: + +And another function, to skip tasks that are blocked: #+begin_src emacs-lisp - (custom-set-variables - '(ledger-reports - '(("budget-last-month" "ledger -f %(ledger-file) --period \"last month\" budget ^expenses -X eur") - ("budget-this-month" "ledger -f %(ledger-file) --period \"this month\" budget ^expenses -X eur") - ("expenses-this-month-vs-budget" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur --budget") - ("expenses-last-month-vs-budget" "ledger -f %(ledger-file) --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur --budget") - ("expenses-vs-income-last-month" "ledger -f %(ledger-file) --period \"last month\" --period-sort \"(amount)\" bal ^expenses ^income -X eur") - ("expenses-last-month" "ledger -f %(ledger-file) --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur") - ("expenses-this-month" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur") - ("expenses-vs-income-this-month" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur") - ("expenses-this-month" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur") - ("bal-assets-czk" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal Assets Liabilities -X czk") - ("bal-assets" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal Assets Liabilities") - ("bal" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal -B") - ("bal-assets-eur" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal Assets Liabilities -X eur") - ("reg" "%(binary) -f %(ledger-file) reg") - ("payee" "%(binary) -f %(ledger-file) reg @%(payee)") - ("account" "%(binary) -f %(ledger-file) reg %(account)"))) - ) + (defun za/skip-if-blocked () + "Skip trees that are blocked by previous tasks" + (let ((subtree-end (save-excursion (org-end-of-subtree t)))) + (if (org-entry-blocked-p) + subtree-end + nil))) #+end_src -** osm -#+begin_src emacs-lisp - (use-package osm - :bind (("C-c M h" . osm-home) - ("C-c M s" . osm-search) - ("C-c M v" . osm-server) - ("C-c M t" . osm-goto) - ("C-c M x" . osm-gpx-show) - ("C-c M j" . osm-bookmark-jump)) - :custom - ;; Take a look at the customization group `osm' for more options. - (osm-server 'default) ;; Configure the tile server - (osm-copyright nil) ;; Display the copyright information +Create custom agenda view based on those keywords. +Agenda views are made up of blocks, appearing in the order that you declare them. +The first two strings are what shows up in the agenda dispatcher (the key to press and the description). - :init - ;; Load Org link support - (with-eval-after-load 'org - (require 'osm-ol))) -#+end_src -* Interface -** Start debugger on error #+begin_src emacs-lisp - ;; (toggle-debug-on-error t) -#+end_src + (setq org-agenda-custom-commands + '(("n" "Next actions" + ((todo "NEXT" ((org-agenda-overriding-header "Next actions:"))))) + ("W" "Waiting" + ((todo "WAITING" ((org-agenda-overriding-header "Waiting:"))))) + ("S" . "Saved for later...") + ("Sw" "Saved to watch" + ((tags-todo "WATCH" ((org-agenda-overriding-header "To watch:"))))) + ("Sr" "Saved to read" + ((tags-todo "READ" ((org-agenda-overriding-header "To read:"))))) + ("Sl" "Saved to listen" + ((tags-todo "LISTEN" ((org-agenda-overriding-header "To listen:"))))) -** Messages -Hide some messages I don't need, and add a list of recent files. + ("a" . "Agenda with schedule only...") + ("aw" "This week" + ((agenda "" ((org-agenda-span 'week))))) + ("ad" "Today" + ((agenda "" ((org-agenda-span 'day))))) + ("at" "Tomorrow" + ((agenda "" ((org-agenda-span 'day) + (org-agenda-start-day "+1d"))))) -#+begin_src emacs-lisp - (recentf-mode) - (setq inhibit-startup-message t - initial-major-mode #'org-mode - initial-scratch-message - (concat "Welcome to Emacs\n\n" - "Recent:\n" - (mapconcat - (lambda (x) (format "- [[%s]]" x)) recentf-list "\n") - "\n\nELISP Evaluation area:\n#+begin_src emacs-lisp\n\n#+end_src")) + ("w" "Week Agenda + Next Actions" + ((agenda "" ((org-agenda-overriding-header "Week agenda:"))) + (todo "NEXT" ((org-agenda-overriding-header "Next actions:"))))) -#+end_src + ("o" "Month agenda" + ((agenda "" ((org-agenda-overriding-header "Month agenda:") + (org-agenda-span 'month))))) -** Appearance -*** Cursor line -Highlight the current line: + ("d" "Day Agenda + Next Actions + Habits" + ((agenda "" ((org-agenda-overriding-header "Day:") + (org-agenda-span 'day) + (org-habit-show-habits nil))) + (todo "NEXT" ((org-agenda-overriding-header "Next actions:"))) + (agenda "" ((org-agenda-overriding-header "Habits:") + (org-agenda-span 'day) + (org-agenda-use-time-grid nil) + (org-agenda-skip-function 'za/skip-unless-habit) + (org-habit-show-habits t) (org-habit-show-habits-only-for-today nil) + (org-habit-show-all-today t))) + (todo "WAITING" ((org-agenda-overriding-header "Waiting:"))))) -#+begin_src emacs-lisp - (global-hl-line-mode) -#+end_src -*** Matching parentheses -Don't add a delay to show matching parenthesis. -Must come before show-paren-mode enable. + ("p" "Projects" + ((tags "PROJECT" ((org-agenda-overriding-header "Projects:") + (org-agenda-prefix-format '((tags . " %i %-22(let ((deadline (org-entry-get nil \"DEADLINE\"))) (if deadline deadline \"\"))"))) + (org-agenda-sorting-strategy '((tags deadline-up alpha-down))))))) -#+begin_src emacs-lisp - (setq show-paren-delay 0) + ("f" "Finished tasks that aren't in a project" + ((tags "TODO=\"DONE\"|TODO=\"CANCELLED\"" ((org-agenda-overriding-header "Finished tasks:") + (org-agenda-skip-function 'za/skip-if-in-project))))) + + ;; Useful thread for opening calfw: https://github.com/kiwanami/emacs-calfw/issues/18 + ("c" "Calendar view" (lambda (&rest _) + (interactive) + (let ((org-agenda-skip-function 'za/skip-if-habit)) + (cfw:open-org-calendar)))))) #+end_src -Show matching parentheses: +In calfw, I don't want to show habits: #+begin_src emacs-lisp - (show-paren-mode t) + (add-hook 'cfw:calendar-mode-hook (setq-local org-agenda-skip-function 'za/skip-if-habit)) #+end_src -*** Cursor -The default box cursor isn't really accurate, because the cursor is actually between letters, not on a letter. -So, I want a bar instead of a box: +**** Logging for tasks +I want to log into the LOGBOOK drawer (useful when I want to take quick notes): #+begin_src emacs-lisp - (setq-default cursor-type '(bar . 4) - cursor-in-non-selected-windows 'hollow) + (setq org-log-into-drawer "LOGBOOK") #+end_src -(I use ~setq-default~ here because cursor-type is automatically buffer-local when it's set) - -*** Line numbers -Relative line numbers: +I also want to log when I finish a task (useful for archiving). +Furthermore, when I'm done, I want to add a note (any important +workarounds/tips). And when I reschedule, I want to know the reason. +I can disable logging on state change for a specific task by adding ~:LOGGING: nil~ to the ~:PROPERTIES:~ drawer. #+begin_src emacs-lisp - (setq display-line-numbers-type 'relative) - (global-display-line-numbers-mode) + (setq org-log-done 'note + org-log-reschedule 'note) #+end_src -Function to hide them: +I want to hide drawers on startup. This variable has options: +- 'overview': Top-level headlines only. +- 'content': All headlines. +- 'showall': No folding on any entry. +- 'show2levels: Headline levels 1-2. +- 'show3levels: Headline levels 1-3. +- 'show4levels: Headline levels 1-4. +- 'show5levels: Headline levels 1-5. +- 'showeverything: Show even drawer contents. #+begin_src emacs-lisp - (defun za/hide-line-numbers () - "Hide line numbers" - (display-line-numbers-mode 0)) + (setq org-startup-folded 'content) #+end_src -Don't display them in specific modes. For each of the modes in -'mode-hooks', add a function to hide line numbers when the mode -activates (which triggers the 'mode'-hook). + +**** Task ordering +Some tasks should be ordered, i.e. they should be done in steps. +Those have the ~:ORDERED: t~ setting in ~:PROPERTIES:~, and it should be enforced: #+begin_src emacs-lisp - (let ((mode-hooks '(doc-view-mode-hook vterm-mode-hook mpc-status-mode-hook mpc-tagbrowser-mode-hook))) - (mapc - (lambda (mode-name) - (add-hook mode-name #'za/hide-line-numbers)) - mode-hooks)) + (setq org-enforce-todo-dependencies t) #+end_src -*** Modeline -I want to show the time and date in the modeline: + +Furthermore, tasks that are ordered and can't be done yet because of previous steps should be dimmed in the agenda: #+begin_src emacs-lisp - (setq display-time-day-and-date t ; also the date - display-time-default-load-average nil ; don't show load average - display-time-format "%I:%M%p %e %b (%a)") ; "HR:MIN(AM/PM) day-of-month Month (Day)" - (display-time-mode 1) ; enable time mode + (setq org-agenda-dim-blocked-tasks t) #+end_src -And to set the modeline format: +I might also want to set ~org-enforce-todo-checkbox-dependencies~, but not convinced on that one yet. + +**** Time tracking & effort +Time tracking should be done in its own drawer: #+begin_src emacs-lisp - (setq-default mode-line-format '("%e" mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-frame-identification mode-line-buffer-identification " " mode-line-position - (vc-mode vc-mode) - " " mode-line-modes mode-line-misc-info mode-line-end-spaces)) + (setq org-clock-into-drawer "CLOCK") #+end_src -I want to hide certain modes from the modeline, they're always on: +And to customize how clock tables work: #+begin_src emacs-lisp - (use-package diminish - :config - (let ((modes-to-hide '(ivy-mode counsel-mode which-key-mode hl-todo-mode undo-tree-mode ivy-posframe-mode))) - (mapc (lambda (mode-name) (diminish mode-name)) modes-to-hide)) - (diminish 'view-mode " 👓")) + (setq org-clocktable-defaults '(:lang "en" :scope agenda-with-archives :wstart 1 :mstart 1 :compact t :maxlevel nil)) + (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel nil)) #+end_src -*** Tab bar -Only show tab bar if there's more than 1 tab: + +I want to set effort in hours:minutes: #+begin_src emacs-lisp - (setq tab-bar-show 1) + (add-to-list 'org-global-properties '("Effort_ALL" . "0:05 0:10 0:15 0:20 0:30 0:45 1:00 1:30 2:00 4:00 6:00 8:00")) #+end_src -** Buffer displaying +I want column view to look like this: -So, this is a bit hard to grok. But basically the alist contains a -regular expression to match a buffer name, then a list of functions to -use in order for displaying the list, and then options for those functions (each of which is an alist). +| To do | Task | Tags | Sum of time elapsed | Sum of time estimated (effort) | +|--------------+-----------+------+---------------------+--------------------------------| +| todo keyword | task name | tags | sum of clock | sum of estimated time | +| ... | ... | ... | ... | ... | #+begin_src emacs-lisp - (setq - ;; Maximum number of side-windows to create on (left top right bottom) - window-sides-slots '(0 ;; left - 1 ;; top - 3 ;; right - 1 ) ;; bottom + (setq org-columns-default-format "%7TODO (To Do) %32ITEM(Task) %TAGS(Tags) %11CLOCKSUM_T(Clock) %8Effort(Effort){:}") +#+end_src - display-buffer-alist '( - ;; Right side - ("\\*Help\\*" - (display-buffer-reuse-window display-buffer-in-side-window) - (side . right) - (slot . -1) - (inhibit-same-window . t)) - ("\\*Async Shell Command\\*" - (display-buffer-reuse-window display-buffer-in-side-window) - (side . right) - (slot . 0) - (inhibit-same-window . t)) - ("magit-process: .*" - (display-buffer-reuse-window display-buffer-in-side-window) - (side . right) - (slot . 0) - (inhibit-same-window . t)) +Fix column alignment in agenda. Unfortunately that means I have to +decrease the size of the date-today font. But I don't have any other +solution atm. - ;; Top side - ("\\*Info\\*" - (display-buffer-reuse-window display-buffer-in-side-window) - (side . top) - (slot . 0)) - ("\\*Man .*\\*" - (display-buffer-reuse-window display-buffer-in-side-window) - (side . top) - (slot . 0)) +#+begin_src emacs-lisp + (set-face-attribute 'org-column nil + :height (face-attribute 'default :height) + :family (face-attribute 'default :family)) + (set-face-attribute 'org-agenda-date-today nil + :height (face-attribute 'default :height)) +#+end_src - ;; Bottom - ("\\*Flycheck errors\\*" - (display-buffer-reuse-window display-buffer-in-side-window) - (side . bottom) - (slot . 0)))) +**** Calculate time since timestamp +#+begin_src emacs-lisp + (defun za/org-time-since () + "Print the amount of time between the timestamp at point and the current date and time." + (interactive) + (unless (org-at-timestamp-p 'lax) + (user-error "Not at timestamp")) + + (when (org-at-timestamp-p 'lax) + (let ((timestamp (match-string 0))) + (with-temp-buffer + (insert timestamp + "--" + (org-time-stamp '(16))) + (org-evaluate-time-range))))) #+end_src -And a way to toggle those side windows: +*** Custom functions +**** Get number of headlines in a file +#+begin_src emacs-lisp + (defun za/org-count-headlines-in-file (level filename) + "Count number of level LEVEL headlines in FILENAME. If LEVEL is 0, count all." + (let ((headline-str (cond ((zerop level) "^\*+") + (t (format "^%s " (apply 'concat (make-list level "\\*"))))))) + (save-mark-and-excursion + (with-temp-buffer + (insert-file-contents filename) + (count-matches headline-str (point-min) (point-max)))))) +#+end_src +**** Yank URL #+begin_src emacs-lisp - (global-set-key (kbd "C-c w") (lambda () (interactive) (window-toggle-side-windows))) + (defun org-yank-link-url () + (interactive) + (kill-new (org-element-property :raw-link (org-element-context)))) + + (define-key org-mode-map (kbd "C-c M-y") 'org-yank-link-url) #+end_src -* Emacs file locations -** Auto-Save files -By default, auto-save files ("#file#") are placed in the same directory as the file itself. -I want to put this all in some unified place: +*** Tempo expansions #+begin_src emacs-lisp - (let ((saves-directory "~/.local/share/emacs/saves/")) - (unless (file-directory-p saves-directory) - (make-directory saves-directory)) - (setq auto-save-file-name-transforms - `((".*" ,saves-directory t)))) + (add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp")) + (add-to-list 'org-structure-template-alist '("sb" . "src bibtex")) + (add-to-list 'org-structure-template-alist '("ss" . "src sh")) #+end_src -** Backup files -By default, backup files (those with a tilde) are saved in the same directory as the currently edited file. -This setting puts them in ~/.local/share/emacs/backups. +*** Catch invisible edits +Sometimes when text is folded away, I might accidentally edit text inside of it. +This option prevents that. +I wanted to do 'smart', but that has a 'fixme' so it might change in the future... +Instead, show what's being edited, but don't perform the edit. #+begin_src emacs-lisp - (let ((backups-directory "~/.local/share/emacs/backups")) - (unless (file-directory-p backups-directory) - (make-directory backups-directory)) - (setq backup-directory-alist `(("." . ,backups-directory))) - (setq backup-by-copying t)) + (setq org-catch-invisible-edits 'show-and-error) #+end_src -** Custom settings file -Both commands are necessary. -First one tells Emacs where to save customizations. -The second one actually loads them. +*** Notification +macOS doesn't have dbus. So I use terminal-notifier for functions like org-notify: #+begin_src emacs-lisp - (setq custom-file (expand-file-name (concat user-emacs-directory "custom.el"))) - (load custom-file) + (if (and (eq system-type 'darwin) + (executable-find "terminal-notifier")) + (setq org-show-notification-handler + (lambda (str) (start-process "terminal-notifier" nil (executable-find "terminal-notifier") + "-title" "Timer done" + "-message" str + "-group" "org.gnu.Emacs" + "-sender" "org.gnu.Emacs")))) #+end_src -* Editor -** Overwrite selection on typing -Normally, when I select something and start typing, Emacs clears the selection, i.e. it deselects and inserts text after the cursor. -I want to replace the selection. +*** org-caldav +This lets me sync my Org agenda to my CalDAV server. +The main reason is because Orgzly doesn't have a calendar view and can't (yet) search for events on a specific day, so if someone asks "are you free on that day", it's a bit hard for me to answer if I don't have my computer with me. +This way, I can just check my calendar. #+begin_src emacs-lisp - (delete-selection-mode t) + (use-package org-caldav) #+end_src -** Strip trailing whitespace -You can show trailing whitespace by setting show-trailing-whitespace to 't'. -But I want to automatically strip trailing whitespace. -Luckily there's already a function for that, I just need to call it in a hook: +A lot of these variables are from my secret.el file, they're not something I can share publicly. +I use ~/.authinfo.gpg to store authorization info for the server. #+begin_src emacs-lisp - (add-hook 'before-save-hook #'delete-trailing-whitespace) + (setq org-caldav-url za/caldav-url + org-caldav-calendar-id za/caldav-org-calendar-id + za/org-life-calendar-inbox (concat za/org-life-dir "calendar-inbox.org") + org-caldav-inbox za/org-life-calendar-inbox + org-caldav-files (cons (car (split-string org-archive-location "::")) org-agenda-files) + org-icalendar-include-todo 'all + org-icalendar-use-deadline '(event-if-todo event-if-not-todo todo-due) + org-icalendar-use-scheduled '(todo-start event-if-todo event-if-not-todo)) + #+end_src -** Formatting & indentation -Show a tab as 8 spaces: +I don't want to export habits, because those will just clutter up my calendar. +The calendar is supposed to be for one-off stuff, or rarely repeating stuff. +Yes, I have to manually add the "HABIT" tag to every habit. +Perhaps nicer would be to exclude based on the property ~:STYLE: habit~, but I haven't figured that one out yet. #+begin_src emacs-lisp - (setq-default tab-width 8) + (setq org-caldav-exclude-tags '("HABIT"))) #+end_src -Never insert tabs with indentation by default: +Maybe check [[https://old.reddit.com/r/orgmode/comments/8rl8ep/making_orgcaldav_useable/e0sb5j0/][this]] for a way to sync on save. +** calfw +Basically provides a way to show the org agenda as a standard GUI calendar app would. #+begin_src emacs-lisp - (setq-default indent-tabs-mode nil) + (use-package calfw + :config + (use-package calfw-org) + (setq cfw:org-overwrite-default-keybinding t) + (setq calendar-week-start-day 1)) #+end_src -Allow switching between the two easily: +** lean-mode +Specifically for the Lean prover. +I also install company-lean and helm-lean, which are suggested on the [[https://github.com/leanprover/lean-mode][Github page]]. +Then I map company-complete only for lean-mode. #+begin_src emacs-lisp - (defun indent-tabs () - (interactive) - (setq indent-tabs-mode t)) - (defun indent-spaces () - (interactive) - (setq indent-tabs-mode nil)) + (use-package lean-mode + :config + (use-package company-lean) + :hook + (lean-mode . (lambda () (define-key lean-mode-map (kbd "S-SPC") #'company-complete)))) #+end_src -Indentation for various modes: - +** magit #+begin_src emacs-lisp - (setq-default sh-basic-offset 2 - c-basic-offset 4) + (use-package magit) #+end_src -** Wrapping +** vterm +Emacs has a bunch of built-in terminal emulators. +And they all suck. +(OK not really, eshell is alright, but not for interactive terminal programs like newsboat/neomutt) -A function to toggle wrapping: +Also use emacsclient inside vterm as an editor, because that'll open documents in the existing Emacs session. +And I'm not gonna be a heretic and open Vim inside of Emacs. #+begin_src emacs-lisp - (make-variable-buffer-local 'za/wrapping) ; wrapping changes per buffer - - (defun za/toggle-wrap (&optional enable) - "Toggle line wrapping settings. With ENABLE a positive number, enable wrapping. If ENABLE is negative or zero, disable wrapping." - (interactive "P") ; prefix arg in raw form + (use-package vterm + :hook + (vterm-mode . (lambda () (unless server-process (server-start))))) +#+end_src - ;; If an argument is provided, prefix or otherwise - (if enable - (let ((enable (cond ((numberp enable) - enable) - ((booleanp enable) - (if enable 1 0)) - ((or (listp enable) (string= "-" enable)) - (prefix-numeric-value enable))))) - ;; If zero or negative, we want to disable wrapping, so pretend it's currently enabled. - ;; And vice versa. - (cond ((<= enable 0) (setq za/wrapping t)) - ((> enable 0) (setq za/wrapping nil))))) +I'll bind a key to start a vterm or switch to the running vterm: +#+begin_src emacs-lisp + (defun switch-to-vterm () "Switch to a running vterm, or start one and switch to it." + (interactive) + (if (get-buffer vterm-buffer-name) + (switch-to-buffer vterm-buffer-name) + (vterm))) + (global-set-key (kbd "C-c t") 'switch-to-vterm) +#+end_src - (let ((disable-wrapping (lambda () - (visual-line-mode -1) - (toggle-truncate-lines t))) - (enable-wrapping (lambda () - (toggle-truncate-lines -1) - (visual-line-mode)))) +** sr-speedbar +Make speed bar show in the current frame. - ;; If za/wrapping is not locally set, infer its values from the enabled modes - (unless (boundp 'za/wrapping) - (setq za/wrapping (and visual-line-mode - (not truncate-lines)))) +#+begin_src emacs-lisp + (use-package sr-speedbar + :config + (setq sr-speedbar-right-side nil) + (define-key speedbar-mode-map (kbd "q") 'sr-speedbar-close)) - ;; Toggle wrapping based on current value - (cond (za/wrapping - (funcall disable-wrapping) - (setq za/wrapping nil) - (message "Wrapping disabled.")) - (t - (funcall enable-wrapping) - (setq za/wrapping t) - (message "Wrapping enabled."))))) #+end_src -And a keybinding to toggle wrapping: +Jump to speedbar. sr-speedbar-exist-p can be void, so I check if it's bound first. +If it's not bound, or if it's false, first open the speedbar. +Then, select it. #+begin_src emacs-lisp - (global-set-key (kbd "C-c q w") #'za/toggle-wrap) + (global-set-key (kbd "C-c F") (lambda () (interactive) + (if (or (not (boundp 'sr-speedbar-exist-p)) + (not (sr-speedbar-exist-p))) + (sr-speedbar-open)) + (sr-speedbar-select-window))) #+end_src -I want to wrap text at window boundary for some modes: +** expand-region +Expand the selected region semantically. #+begin_src emacs-lisp - (defun za/settings-help-mode () - "Help mode settings" - (za/toggle-wrap t)) + (use-package expand-region + :bind ("C-=" . er/expand-region)) #+end_src +** flycheck +Install flycheck, and enable it by default in certain major modes: + #+begin_src emacs-lisp - (add-hook 'help-mode-hook #'za/settings-help-mode) + (use-package flycheck + :hook (sh-mode . flycheck-mode)) #+end_src -** Pulse line -When you switch windows, Emacs can flash the cursor briefly to guide your eyes; I like that. -Set some options for pulsing: +** anki-editor +Some extra keybindings that are not set up by default. +anki-editor doesn't provide a keymap so I have to set one up here: #+begin_src emacs-lisp - (setq pulse-iterations 10) - (setq pulse-delay 0.05) + (use-package anki-editor + :config + (defvar anki-editor-mode-map (make-sparse-keymap)) + (add-to-list 'minor-mode-map-alist (cons 'anki-editor-mode + anki-editor-mode-map)) + + (setq anki-editor-use-math-jax t) + + :hook + (anki-editor-mode . (lambda () + (define-key anki-editor-mode-map (kbd "C-c t") #'org-property-next-allowed-value) + (define-key anki-editor-mode-map (kbd "C-c i") #'anki-editor-insert-note) + (define-key anki-editor-mode-map (kbd "C-c p") #'anki-editor-push-notes) + (define-key anki-editor-mode-map (kbd "C-c c") #'anki-editor-cloze-dwim)))) #+end_src -Define the pulse function: +** rainbow-mode +'rainbow-mode' lets you visualise hex colors: #+begin_src emacs-lisp - (defun pulse-line (&rest _) - "Pulse the current line." - (pulse-momentary-highlight-one-line (point))) + (use-package rainbow-mode + :hook (emacs-lisp-mode . rainbow-mode)) #+end_src -Run it in certain cases: scrolling up/down, recentering, switching windows. -'dolist' binds 'command' to each value in the list in turn, and runs the body. -'advice-add' makes the pulse-line function run after 'command'. +** pdf-tools +A better replacement for DocView: #+begin_src emacs-lisp - (dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window)) - (advice-add command :after #'pulse-line)) + (use-package pdf-tools + :config + (setq-default pdf-annot-default-annotation-properties '((t + (label . "Alex Balgavy")) + (text + (icon . "Note") + (color . "#0088ff")) + (highlight + (color . "yellow")) + (squiggly + (color . "orange")) + (strike-out + (color . "red")) + (underline + (color . "blue")))) + :hook + (pdf-annot-list-mode . pdf-annot-list-follow-minor-mode) + (pdf-annot-edit-contents-minor-mode . org-mode) + (pdf-view-mode . (lambda () (display-line-numbers-mode 0))) + (pdf-view-mode . (lambda () (define-key pdf-isearch-minor-mode-map (kbd "C-s") #'isearch-forward)))) + (pdf-tools-install) #+end_src -And set the pulse color: +** virtualenvwrapper +Like virtualenvwrapper.sh, but for Emacs. #+begin_src emacs-lisp - (custom-set-faces '(pulse-highlight-start-face ((t (:background "CadetBlue2"))))) + (use-package virtualenvwrapper + :config + (venv-initialize-interactive-shells) + (venv-initialize-eshell) + (setq venv-location "~/.config/virtualenvs")) #+end_src -** Pager toggle keybinding -M-x view-mode enables pager behavior. -I want read-only files to automatically use pager mode: +** org-ref +#+begin_src emacs-lisp + (use-package org-ref) +#+end_src +** org-noter #+begin_src emacs-lisp - (setq view-read-only t) + (use-package org-noter) #+end_src -** Mail mode for neomutt -When editing a message from neomutt, I want to use mail mode. -Even though I won't be sending the email from there, I like the syntax highlighting :) + +** hl-todo +I want to highlight TODO keywords in comments: #+begin_src emacs-lisp - (add-to-list 'auto-mode-alist '("/neomutt-" . mail-mode)) + (use-package hl-todo + :custom-face + (hl-todo ((t (:inherit hl-todo :underline t)))) + :config + (setq hl-todo-keyword-faces + '(("TODO" . "#ff7060") + ("FIXME" . "#caa000"))) + (global-hl-todo-mode t)) #+end_src -** Zap up to char -It's more useful for me to be able to delete up to a character instead of to and including a character: +** undo-tree +Sometimes it's better to look at undo history as a tree: #+begin_src emacs-lisp - (global-set-key (kbd "M-z") 'zap-up-to-char) + (use-package undo-tree + :config + (global-undo-tree-mode)) #+end_src -** Expansion/completion -Use hippie expand instead of dabbrev-expand: +** ledger +#+begin_src emacs-lisp + (use-package ledger-mode + :mode ("\\.ledger\\'") + :config + (setq ledger-clear-whole-transactions t + ledger-reconcile-default-commodity "eur")) +#+end_src + +Custom reports: #+begin_src emacs-lisp - (global-set-key (kbd "M-/") 'hippie-expand) + (custom-set-variables + '(ledger-reports + '(("budget-last-month" "ledger -f %(ledger-file) --period \"last month\" budget ^expenses -X eur") + ("budget-this-month" "ledger -f %(ledger-file) --period \"this month\" budget ^expenses -X eur") + ("expenses-this-month-vs-budget" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur --budget") + ("expenses-last-month-vs-budget" "ledger -f %(ledger-file) --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur --budget") + ("expenses-vs-income-last-month" "ledger -f %(ledger-file) --period \"last month\" --period-sort \"(amount)\" bal ^expenses ^income -X eur") + ("expenses-last-month" "ledger -f %(ledger-file) --period \"last month\" --period-sort \"(amount)\" bal ^expenses -X eur") + ("expenses-this-month" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^expenses -X eur") + ("expenses-vs-income-this-month" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur") + ("expenses-this-month" "ledger -f %(ledger-file) --period \"this month\" --period-sort \"(amount)\" bal ^income ^expenses -X eur") + ("bal-assets-czk" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal Assets Liabilities -X czk") + ("bal-assets" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal Assets Liabilities") + ("bal" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal -B") + ("bal-assets-eur" "ledger [[ledger-mode-flags]] -f %(ledger-file) bal Assets Liabilities -X eur") + ("reg" "%(binary) -f %(ledger-file) reg") + ("payee" "%(binary) -f %(ledger-file) reg @%(payee)") + ("account" "%(binary) -f %(ledger-file) reg %(account)"))) + ) #+end_src +** osm +#+begin_src emacs-lisp + (use-package osm + :bind (("C-c M h" . osm-home) + ("C-c M s" . osm-search) + ("C-c M v" . osm-server) + ("C-c M t" . osm-goto) + ("C-c M x" . osm-gpx-show) + ("C-c M j" . osm-bookmark-jump)) -** Prefer newer file loading + :custom + ;; Take a look at the customization group `osm' for more options. + (osm-server 'default) ;; Configure the tile server + (osm-copyright nil) ;; Display the copyright information + + :init + ;; Load Org link support + (with-eval-after-load 'org + (require 'osm-ol))) +#+end_src +* Interface +** Start debugger on error #+begin_src emacs-lisp - (setq load-prefer-newer t) + ;; (toggle-debug-on-error t) #+end_src -** Automatically find tags file -When opening a file in a git repo, try to discover the etags file: +** Messages +Hide some messages I don't need, and add a list of recent files. #+begin_src emacs-lisp - (defun current-tags-file () - "Get current tags file" - (let* ((tagspath ".git/etags") - (git-root (locate-dominating-file (buffer-file-name) tagspath))) - (if git-root - (expand-file-name tagspath git-root)))) + (recentf-mode) + (setq inhibit-startup-message t + initial-major-mode #'org-mode + initial-scratch-message + (concat "Welcome to Emacs\n\n" + "Recent:\n" + (mapconcat + (lambda (x) (format "- [[%s]]" x)) recentf-list "\n") + "\n\nELISP Evaluation area:\n#+begin_src emacs-lisp\n\n#+end_src")) - (setq default-tags-table-function #'current-tags-file) #+end_src -There's probably a better way to write this. I need to ask Reddit for feedback at some point. +** Appearance +*** Cursor line +Highlight the current line: -** Semantic mode -Set default submodes: +#+begin_src emacs-lisp + (global-hl-line-mode) +#+end_src +*** Matching parentheses +Don't add a delay to show matching parenthesis. +Must come before show-paren-mode enable. #+begin_src emacs-lisp - (setq semantic-default-submodes '(global-semantic-idle-scheduler-mode ; reparse buffer when idle - global-semanticdb-minor-mode ; maintain database - global-semantic-idle-summary-mode)) ; show information (e.g. types) about tag at point - ;; global-semantic-stickyfunc-mode)) ; show current func in header line + (setq show-paren-delay 0) #+end_src -Add some keybindings: +Show matching parentheses: #+begin_src emacs-lisp - (with-eval-after-load 'semantic - (define-key semantic-mode-map (kbd "C-c , .") #'semantic-ia-show-summary)) + (show-paren-mode t) #+end_src -SemanticDB is written into ~/.emacs.d/semanticdb/. - -Enable semantic mode for major modes: +*** Cursor +The default box cursor isn't really accurate, because the cursor is actually between letters, not on a letter. +So, I want a bar instead of a box: #+begin_src emacs-lisp - (defun za/settings-c-mode () - "C mode settings" - (semantic-mode 1)) + (setq-default cursor-type '(bar . 4) + cursor-in-non-selected-windows 'hollow) #+end_src + +(I use ~setq-default~ here because cursor-type is automatically buffer-local when it's set) + +*** Line numbers +Relative line numbers: + #+begin_src emacs-lisp - (let ((mode-hooks [c-mode-common-hook])) - (mapc (lambda (mode-name) - (add-hook mode-name #'za/settings-c-mode)) - mode-hooks)) + (setq display-line-numbers-type 'relative) + (global-display-line-numbers-mode) #+end_src -** Forward-word and forward-to-word -Change M-f to stop at the start of the word: +Function to hide them: + +#+begin_src emacs-lisp + (defun za/hide-line-numbers () + "Hide line numbers" + (display-line-numbers-mode 0)) +#+end_src +Don't display them in specific modes. For each of the modes in +'mode-hooks', add a function to hide line numbers when the mode +activates (which triggers the 'mode'-hook). #+begin_src emacs-lisp - (global-set-key (kbd "M-f") 'forward-to-word) + (let ((mode-hooks '(doc-view-mode-hook vterm-mode-hook mpc-status-mode-hook mpc-tagbrowser-mode-hook))) + (mapc + (lambda (mode-name) + (add-hook mode-name #'za/hide-line-numbers)) + mode-hooks)) #+end_src - -Bind C-M-S-F to the old functionality of M-f (stop at end of word) +*** Modeline +I want to show the time and date in the modeline: #+begin_src emacs-lisp - (global-set-key (kbd "C-M-S-F") 'forward-word) + (setq display-time-day-and-date t ; also the date + display-time-default-load-average nil ; don't show load average + display-time-format "%I:%M%p %e %b (%a)") ; "HR:MIN(AM/PM) day-of-month Month (Day)" + (display-time-mode 1) ; enable time mode #+end_src -** Rectangle insert string +And to set the modeline format: + #+begin_src emacs-lisp - (global-set-key (kbd "C-x r I") 'string-insert-rectangle) - (global-set-key (kbd "C-x r R") 'replace-rectangle) + (setq-default mode-line-format '("%e" mode-line-front-space mode-line-mule-info mode-line-client mode-line-modified mode-line-remote mode-line-frame-identification mode-line-buffer-identification " " mode-line-position + (vc-mode vc-mode) + " " mode-line-modes mode-line-misc-info mode-line-end-spaces)) #+end_src -** End sentences with one space -Emacs uses the rather old-fashioned convention of treating a period followed by double spaces as end of sentence. However, it is more common these days to end sentences with a period followed by a single space. -Let a period followed by a single space be treated as end of sentence: +I want to hide certain modes from the modeline, they're always on: #+begin_src emacs-lisp - (setq sentence-end-double-space nil) + (use-package diminish + :config + (let ((modes-to-hide '(ivy-mode counsel-mode which-key-mode hl-todo-mode undo-tree-mode ivy-posframe-mode))) + (mapc (lambda (mode-name) (diminish mode-name)) modes-to-hide)) + (diminish 'view-mode " 👓")) #+end_src +*** Tab bar +Only show tab bar if there's more than 1 tab: -* Org mode -** Get number of headlines in a file #+begin_src emacs-lisp - (defun za/org-count-headlines-in-file (level filename) - "Count number of level LEVEL headlines in FILENAME. If LEVEL is 0, count all." - (let ((headline-str (cond ((zerop level) "^\*+") - (t (format "^%s " (apply 'concat (make-list level "\\*"))))))) - (save-mark-and-excursion - (with-temp-buffer - (insert-file-contents filename) - (count-matches headline-str (point-min) (point-max)))))) + (setq tab-bar-show 1) #+end_src -** Agenda & GTD -*** Set file locations -Which files should be included in the agenda (I have to use ~list~ to evaluate the variables, because org-agenda-files expects strings): +** Buffer displaying + +So, this is a bit hard to grok. But basically the alist contains a +regular expression to match a buffer name, then a list of functions to +use in order for displaying the list, and then options for those functions (each of which is an alist). #+begin_src emacs-lisp - (setq org-agenda-files (list za/org-life-main - za/org-life-inbox - za/org-life-tickler)) -#+end_src + (setq + ;; Maximum number of side-windows to create on (left top right bottom) + window-sides-slots '(0 ;; left + 1 ;; top + 3 ;; right + 1 ) ;; bottom -Convenience functions to make opening the main file faster: + display-buffer-alist '( + ;; Right side + ("\\*Help\\*" + (display-buffer-reuse-window display-buffer-in-side-window) + (side . right) + (slot . -1) + (inhibit-same-window . t)) + ("\\*Async Shell Command\\*" + (display-buffer-reuse-window display-buffer-in-side-window) + (side . right) + (slot . 0) + (inhibit-same-window . t)) + ("magit-process: .*" + (display-buffer-reuse-window display-buffer-in-side-window) + (side . right) + (slot . 0) + (inhibit-same-window . t)) -#+begin_src emacs-lisp - (defun gtd () "GTD: main file" (interactive) (find-file za/org-life-main)) - (defun gtd-inbox () "GTD: inbox" (interactive) (find-file za/org-life-inbox)) - (defun gtd-archive () "GTD: archive" (interactive) (find-file za/org-life-archive)) - (defun gtd-someday () "GTD: someday" (interactive) (find-file za/org-life-someday)) - (defun gtd-reference () "GTD: reference" (interactive) (find-file za/org-life-reference)) + ;; Top side + ("\\*Info\\*" + (display-buffer-reuse-window display-buffer-in-side-window) + (side . top) + (slot . 0)) + ("\\*Man .*\\*" + (display-buffer-reuse-window display-buffer-in-side-window) + (side . top) + (slot . 0)) + + ;; Bottom + ("\\*Flycheck errors\\*" + (display-buffer-reuse-window display-buffer-in-side-window) + (side . bottom) + (slot . 0)))) #+end_src -Bind keys to those functions: +And a way to toggle those side windows: #+begin_src emacs-lisp - (global-set-key (kbd "C-c g i") 'gtd-inbox) - (global-set-key (kbd "C-c g g") 'gtd) - (global-set-key (kbd "C-c g a") 'gtd-archive) - (global-set-key (kbd "C-c g s") 'gtd-someday) - (global-set-key (kbd "C-c g r") 'gtd-reference) + (global-set-key (kbd "C-c w") (lambda () (interactive) (window-toggle-side-windows))) #+end_src -*** Refiling & archiving -Where I want to be able to move subtrees (doesn't include inbox because I never refile to that, and the archive has its own keybining): +* Emacs file locations +** Auto-Save files +By default, auto-save files ("#file#") are placed in the same directory as the file itself. +I want to put this all in some unified place: #+begin_src emacs-lisp - (setq org-refile-targets `((,za/org-life-main :maxlevel . 3) - (,za/org-life-someday :level . 1) - (,za/org-life-tickler :maxlevel . 2) - (,za/org-life-reference :maxlevel . 2))) + (let ((saves-directory "~/.local/share/emacs/saves/")) + (unless (file-directory-p saves-directory) + (make-directory saves-directory)) + (setq auto-save-file-name-transforms + `((".*" ,saves-directory t)))) #+end_src -I want to archive to a specific file, in a date tree: +** Backup files +By default, backup files (those with a tilde) are saved in the same directory as the currently edited file. +This setting puts them in ~/.local/share/emacs/backups. #+begin_src emacs-lisp - (setq org-archive-location (concat za/org-life-archive "::datetree/")) + (let ((backups-directory "~/.local/share/emacs/backups")) + (unless (file-directory-p backups-directory) + (make-directory backups-directory)) + (setq backup-directory-alist `(("." . ,backups-directory))) + (setq backup-by-copying t)) #+end_src -Include the destination file as an element in the path to a heading, and to use the full paths as completion targets rather than just the heading text itself: +** Custom settings file +Both commands are necessary. +First one tells Emacs where to save customizations. +The second one actually loads them. #+begin_src emacs-lisp - (setq org-refile-use-outline-path 'file) + (setq custom-file (expand-file-name (concat user-emacs-directory "custom.el"))) + (load custom-file) #+end_src -Tell Org that I don’t want to complete in steps; I want Org to generate all of the possible completions and present them at once (necessary for Helm/Ivy): +* Editor +** Overwrite selection on typing +Normally, when I select something and start typing, Emacs clears the selection, i.e. it deselects and inserts text after the cursor. +I want to replace the selection. #+begin_src emacs-lisp - (setq org-outline-path-complete-in-steps nil) + (delete-selection-mode t) #+end_src -Allow me to tack new heading names onto the end of my outline path, and if I am asking to create new ones, make me confirm it: +** Strip trailing whitespace +You can show trailing whitespace by setting show-trailing-whitespace to 't'. +But I want to automatically strip trailing whitespace. +Luckily there's already a function for that, I just need to call it in a hook: #+begin_src emacs-lisp - (setq org-refile-allow-creating-parent-nodes 'confirm) + (add-hook 'before-save-hook #'delete-trailing-whitespace) #+end_src +** Formatting & indentation -*** Quick capture -Quick capture lets me send something to my inbox very quickly, without thinking about where it should go. -The inbox is processed later. - -Templates for quick capture: +Show a tab as 8 spaces: #+begin_src emacs-lisp - (setq org-capture-templates `(("t" "Todo [inbox]" entry - (file ,za/org-life-inbox) - "* TODO %i%?") - - ("s" "Save for read/watch/listen" entry - (file+headline ,za/org-life-tickler "Read/watch/listen") - "* TODO %?[[%^{link}][%^{description}]] %^G"))) + (setq-default tab-width 8) #+end_src -*** Todo & custom agenda views -Todo keywords based on the GTD system (pipe separates incomplete from complete). -Apart from the logging-on-done configured [[*Logging][below]], I also want to log a note & timestamp when I start waiting on something. -In ~org-todo-keywords~, ~@~ means note+timestamp, ~!~ means timestamp, ~@/!~ means note+timestamp on state entry and timestamp on leave. +Never insert tabs with indentation by default: #+begin_src emacs-lisp - (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "WAITING(w@)" "SOMEDAY(s)" "|" "DONE(d)" "CANCELLED(c)")) - org-todo-keyword-faces '(("TODO" . org-todo) - ("NEXT" . org-todo) - ("WAITING" . org-todo) - ("SOMEDAY" . org-todo) - ("DONE" . org-done) - ("CANCELLED" . org-done))) + (setq-default indent-tabs-mode nil) #+end_src -I decided that projects will not be TODO items, but their progress will be tracked with a progress cookie ([x/y]). This function converts an item to a project: it adds a PROJECT tag, sets the progress indicator to count all checkboxes in sub-items (only TODO items), and removes any existing TODO keywords. Finally, PROJECT tags shouldn't be inherited (i.e. subtasks shouldn't be marked as projects). +Allow switching between the two easily: #+begin_src emacs-lisp - (defun za/mark-as-project () - "This function makes sure that the current heading has - (1) the tag PROJECT - (2) the property COOKIE_DATA set to \"todo recursive\" - (3) a leading progress indicator" + (defun indent-tabs () (interactive) - (org-set-property "TODO" "") - (org-toggle-tag "PROJECT" 'on) - (org-set-property "COOKIE_DATA" "todo recursive") - (org-back-to-heading t) - (forward-whitespace 1) - (insert "[/] ") - (org-update-statistics-cookies nil)) + (setq indent-tabs-mode t)) + (defun indent-spaces () + (interactive) + (setq indent-tabs-mode nil)) #+end_src -Only the top-level project headlines should be tagged as projects, so disable inheritance of that tag: +Indentation for various modes: #+begin_src emacs-lisp - (setq org-tags-exclude-from-inheritance '("PROJECT")) + (setq-default sh-basic-offset 2 + c-basic-offset 4) #+end_src -Define a function to skip items if they're part of a project (i.e. one of their parents has a "PROJECT" tag). -The problem is, the "PROJECT" tag isn't inherited. So, we temporarily disable excluding from inheritance, just for the ~org-get-tags~ call. Then check if "PROJECT" is one of the tags. +** Wrapping + +A function to toggle wrapping: #+begin_src emacs-lisp - (defun za/skip-if-in-project () - "Skip items that are part of a project" - (let ((subtree-end (save-excursion (org-end-of-subtree t))) - (item-tags (let ((org-tags-exclude-from-inheritance nil)) (org-get-tags)))) - (if (member "PROJECT" item-tags) - subtree-end - nil))) + (make-variable-buffer-local 'za/wrapping) ; wrapping changes per buffer + + (defun za/toggle-wrap (&optional enable) + "Toggle line wrapping settings. With ENABLE a positive number, enable wrapping. If ENABLE is negative or zero, disable wrapping." + (interactive "P") ; prefix arg in raw form + + ;; If an argument is provided, prefix or otherwise + (if enable + (let ((enable (cond ((numberp enable) + enable) + ((booleanp enable) + (if enable 1 0)) + ((or (listp enable) (string= "-" enable)) + (prefix-numeric-value enable))))) + ;; If zero or negative, we want to disable wrapping, so pretend it's currently enabled. + ;; And vice versa. + (cond ((<= enable 0) (setq za/wrapping t)) + ((> enable 0) (setq za/wrapping nil))))) + + + (let ((disable-wrapping (lambda () + (visual-line-mode -1) + (toggle-truncate-lines t))) + (enable-wrapping (lambda () + (toggle-truncate-lines -1) + (visual-line-mode)))) + + ;; If za/wrapping is not locally set, infer its values from the enabled modes + (unless (boundp 'za/wrapping) + (setq za/wrapping (and visual-line-mode + (not truncate-lines)))) + ;; Toggle wrapping based on current value + (cond (za/wrapping + (funcall disable-wrapping) + (setq za/wrapping nil) + (message "Wrapping disabled.")) + (t + (funcall enable-wrapping) + (setq za/wrapping t) + (message "Wrapping enabled."))))) #+end_src -Also, define a function to skip tasks (trees) that are not habits (i.e. don't have the STYLE property ~habit~): +And a keybinding to toggle wrapping: #+begin_src emacs-lisp - (defun za/skip-unless-habit () - "Skip trees that are not habits" - (let ((subtree-end (save-excursion (org-end-of-subtree t)))) - (if (string= (org-entry-get nil "STYLE") "habit") - nil - subtree-end))) + (global-set-key (kbd "C-c q w") #'za/toggle-wrap) #+end_src -And one to skip tasks that /are/ habits: +I want to wrap text at window boundary for some modes: #+begin_src emacs-lisp - (defun za/skip-if-habit () - "Skip trees that are not habits" - (let ((subtree-end (save-excursion (org-end-of-subtree t)))) - (if (string= (org-entry-get nil "STYLE") "habit") - subtree-end - nil))) + (defun za/settings-help-mode () + "Help mode settings" + (za/toggle-wrap t)) #+end_src - -And another function, to skip tasks that are blocked: - #+begin_src emacs-lisp - (defun za/skip-if-blocked () - "Skip trees that are blocked by previous tasks" - (let ((subtree-end (save-excursion (org-end-of-subtree t)))) - (if (org-entry-blocked-p) - subtree-end - nil))) + (add-hook 'help-mode-hook #'za/settings-help-mode) #+end_src -Create custom agenda view based on those keywords. -Agenda views are made up of blocks, appearing in the order that you declare them. -The first two strings are what shows up in the agenda dispatcher (the key to press and the description). +** Pulse line +When you switch windows, Emacs can flash the cursor briefly to guide your eyes; I like that. +Set some options for pulsing: #+begin_src emacs-lisp - (setq org-agenda-custom-commands - '(("n" "Next actions" - ((todo "NEXT" ((org-agenda-overriding-header "Next actions:"))))) - ("W" "Waiting" - ((todo "WAITING" ((org-agenda-overriding-header "Waiting:"))))) - ("S" . "Saved for later...") - ("Sw" "Saved to watch" - ((tags-todo "WATCH" ((org-agenda-overriding-header "To watch:"))))) - ("Sr" "Saved to read" - ((tags-todo "READ" ((org-agenda-overriding-header "To read:"))))) - ("Sl" "Saved to listen" - ((tags-todo "LISTEN" ((org-agenda-overriding-header "To listen:"))))) - - ("a" . "Agenda with schedule only...") - ("aw" "This week" - ((agenda "" ((org-agenda-span 'week))))) - ("ad" "Today" - ((agenda "" ((org-agenda-span 'day))))) - ("at" "Tomorrow" - ((agenda "" ((org-agenda-span 'day) - (org-agenda-start-day "+1d"))))) - - ("w" "Week Agenda + Next Actions" - ((agenda "" ((org-agenda-overriding-header "Week agenda:"))) - (todo "NEXT" ((org-agenda-overriding-header "Next actions:"))))) - - ("o" "Month agenda" - ((agenda "" ((org-agenda-overriding-header "Month agenda:") - (org-agenda-span 'month))))) - - ("d" "Day Agenda + Next Actions + Habits" - ((agenda "" ((org-agenda-overriding-header "Day:") - (org-agenda-span 'day) - (org-habit-show-habits nil))) - (todo "NEXT" ((org-agenda-overriding-header "Next actions:"))) - (agenda "" ((org-agenda-overriding-header "Habits:") - (org-agenda-span 'day) - (org-agenda-use-time-grid nil) - (org-agenda-skip-function 'za/skip-unless-habit) - (org-habit-show-habits t) (org-habit-show-habits-only-for-today nil) - (org-habit-show-all-today t))) - (todo "WAITING" ((org-agenda-overriding-header "Waiting:"))))) - - ("p" "Projects" - ((tags "PROJECT" ((org-agenda-overriding-header "Projects:") - (org-agenda-prefix-format '((tags . " %i %-22(let ((deadline (org-entry-get nil \"DEADLINE\"))) (if deadline deadline \"\"))"))) - (org-agenda-sorting-strategy '((tags deadline-up alpha-down))))))) - - ("f" "Finished tasks that aren't in a project" - ((tags "TODO=\"DONE\"|TODO=\"CANCELLED\"" ((org-agenda-overriding-header "Finished tasks:") - (org-agenda-skip-function 'za/skip-if-in-project))))) - - ;; Useful thread for opening calfw: https://github.com/kiwanami/emacs-calfw/issues/18 - ("c" "Calendar view" (lambda (&rest _) - (interactive) - (let ((org-agenda-skip-function 'za/skip-if-habit)) - (cfw:open-org-calendar)))))) + (setq pulse-iterations 10) + (setq pulse-delay 0.05) #+end_src -In calfw, I don't want to show habits: +Define the pulse function: #+begin_src emacs-lisp - (add-hook 'cfw:calendar-mode-hook (setq-local org-agenda-skip-function 'za/skip-if-habit)) + (defun pulse-line (&rest _) + "Pulse the current line." + (pulse-momentary-highlight-one-line (point))) #+end_src -*** Logging -I want to log into the LOGBOOK drawer (useful when I want to take quick notes): +Run it in certain cases: scrolling up/down, recentering, switching windows. +'dolist' binds 'command' to each value in the list in turn, and runs the body. +'advice-add' makes the pulse-line function run after 'command'. #+begin_src emacs-lisp - (setq org-log-into-drawer "LOGBOOK") + (dolist (command '(scroll-up-command scroll-down-command recenter-top-bottom other-window)) + (advice-add command :after #'pulse-line)) #+end_src -I also want to log when I finish a task (useful for archiving). -Furthermore, when I'm done, I want to add a note (any important -workarounds/tips). And when I reschedule, I want to know the reason. -I can disable logging on state change for a specific task by adding ~:LOGGING: nil~ to the ~:PROPERTIES:~ drawer. +And set the pulse color: #+begin_src emacs-lisp - (setq org-log-done 'note - org-log-reschedule 'note) + (custom-set-faces '(pulse-highlight-start-face ((t (:background "CadetBlue2"))))) #+end_src -I want to hide drawers on startup. This variable has options: -- 'overview': Top-level headlines only. -- 'content': All headlines. -- 'showall': No folding on any entry. -- 'show2levels: Headline levels 1-2. -- 'show3levels: Headline levels 1-3. -- 'show4levels: Headline levels 1-4. -- 'show5levels: Headline levels 1-5. -- 'showeverything: Show even drawer contents. +** Pager toggle keybinding +M-x view-mode enables pager behavior. +I want read-only files to automatically use pager mode: #+begin_src emacs-lisp - (setq org-startup-folded 'content) + (setq view-read-only t) #+end_src - -*** Task ordering -Some tasks should be ordered, i.e. they should be done in steps. -Those have the ~:ORDERED: t~ setting in ~:PROPERTIES:~, and it should be enforced: +** Mail mode for neomutt +When editing a message from neomutt, I want to use mail mode. +Even though I won't be sending the email from there, I like the syntax highlighting :) #+begin_src emacs-lisp - (setq org-enforce-todo-dependencies t) + (add-to-list 'auto-mode-alist '("/neomutt-" . mail-mode)) #+end_src - -Furthermore, tasks that are ordered and can't be done yet because of previous steps should be dimmed in the agenda: +** Zap up to char +It's more useful for me to be able to delete up to a character instead of to and including a character: #+begin_src emacs-lisp - (setq org-agenda-dim-blocked-tasks t) + (global-set-key (kbd "M-z") 'zap-up-to-char) #+end_src - -I might also want to set ~org-enforce-todo-checkbox-dependencies~, but not convinced on that one yet. - -*** Time tracking & effort -Time tracking should be done in its own drawer: +** Expansion/completion +Use hippie expand instead of dabbrev-expand: #+begin_src emacs-lisp - (setq org-clock-into-drawer "CLOCK") + (global-set-key (kbd "M-/") 'hippie-expand) #+end_src -And to customize how clock tables work: - +** Prefer newer file loading #+begin_src emacs-lisp - (setq org-clocktable-defaults '(:lang "en" :scope agenda-with-archives :wstart 1 :mstart 1 :compact t :maxlevel nil)) - (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel nil)) + (setq load-prefer-newer t) #+end_src -I want to set effort in hours:minutes: +** Automatically find tags file +When opening a file in a git repo, try to discover the etags file: #+begin_src emacs-lisp - (add-to-list 'org-global-properties '("Effort_ALL" . "0:05 0:10 0:15 0:20 0:30 0:45 1:00 1:30 2:00 4:00 6:00 8:00")) + (defun current-tags-file () + "Get current tags file" + (let* ((tagspath ".git/etags") + (git-root (locate-dominating-file (buffer-file-name) tagspath))) + (if git-root + (expand-file-name tagspath git-root)))) + + (setq default-tags-table-function #'current-tags-file) #+end_src -I want column view to look like this: +There's probably a better way to write this. I need to ask Reddit for feedback at some point. -| To do | Task | Tags | Sum of time elapsed | Sum of time estimated (effort) | -|--------------+-----------+------+---------------------+--------------------------------| -| todo keyword | task name | tags | sum of clock | sum of estimated time | -| ... | ... | ... | ... | ... | +** Semantic mode +Set default submodes: #+begin_src emacs-lisp - (setq org-columns-default-format "%7TODO (To Do) %32ITEM(Task) %TAGS(Tags) %11CLOCKSUM_T(Clock) %8Effort(Effort){:}") + (setq semantic-default-submodes '(global-semantic-idle-scheduler-mode ; reparse buffer when idle + global-semanticdb-minor-mode ; maintain database + global-semantic-idle-summary-mode)) ; show information (e.g. types) about tag at point + ;; global-semantic-stickyfunc-mode)) ; show current func in header line #+end_src -Fix column alignment in agenda. Unfortunately that means I have to -decrease the size of the date-today font. But I don't have any other -solution atm. +Add some keybindings: #+begin_src emacs-lisp - (set-face-attribute 'org-column nil - :height (face-attribute 'default :height) - :family (face-attribute 'default :family)) - (set-face-attribute 'org-agenda-date-today nil - :height (face-attribute 'default :height)) + (with-eval-after-load 'semantic + (define-key semantic-mode-map (kbd "C-c , .") #'semantic-ia-show-summary)) #+end_src -*** Calculate time since timestamp -#+begin_src emacs-lisp - (defun za/org-time-since () - "Print the amount of time between the timestamp at point and the current date and time." - (interactive) - (unless (org-at-timestamp-p 'lax) - (user-error "Not at timestamp")) +SemanticDB is written into ~/.emacs.d/semanticdb/. - (when (org-at-timestamp-p 'lax) - (let ((timestamp (match-string 0))) - (with-temp-buffer - (insert timestamp - "--" - (org-time-stamp '(16))) - (org-evaluate-time-range))))) +Enable semantic mode for major modes: + +#+begin_src emacs-lisp + (defun za/settings-c-mode () + "C mode settings" + (semantic-mode 1)) +#+end_src +#+begin_src emacs-lisp + (let ((mode-hooks [c-mode-common-hook])) + (mapc (lambda (mode-name) + (add-hook mode-name #'za/settings-c-mode)) + mode-hooks)) #+end_src -** Tempo expansions +** Forward-word and forward-to-word +Change M-f to stop at the start of the word: #+begin_src emacs-lisp - (add-to-list 'org-structure-template-alist '("se" . "src emacs-lisp")) - (add-to-list 'org-structure-template-alist '("sb" . "src bibtex")) - (add-to-list 'org-structure-template-alist '("ss" . "src sh")) + (global-set-key (kbd "M-f") 'forward-to-word) #+end_src -** Yank URL -#+begin_src emacs-lisp - (defun org-yank-link-url () - (interactive) - (kill-new (org-element-property :raw-link (org-element-context)))) +Bind C-M-S-F to the old functionality of M-f (stop at end of word) - (define-key org-mode-map (kbd "C-c M-y") 'org-yank-link-url) +#+begin_src emacs-lisp + (global-set-key (kbd "C-M-S-F") 'forward-word) #+end_src -** Catch invisible edits -Sometimes when text is folded away, I might accidentally edit text inside of it. -This option prevents that. -I wanted to do 'smart', but that has a 'fixme' so it might change in the future... -Instead, show what's being edited, but don't perform the edit. - +** Rectangle insert string #+begin_src emacs-lisp - (setq org-catch-invisible-edits 'show-and-error) + (global-set-key (kbd "C-x r I") 'string-insert-rectangle) + (global-set-key (kbd "C-x r R") 'replace-rectangle) #+end_src +** End sentences with one space +Emacs uses the rather old-fashioned convention of treating a period followed by double spaces as end of sentence. However, it is more common these days to end sentences with a period followed by a single space. -** Notification -macOS doesn't have dbus. So I use terminal-notifier for functions like org-notify: +Let a period followed by a single space be treated as end of sentence: #+begin_src emacs-lisp - (if (and (eq system-type 'darwin) - (executable-find "terminal-notifier")) - (setq org-show-notification-handler - (lambda (str) (start-process "terminal-notifier" nil (executable-find "terminal-notifier") - "-title" "Timer done" - "-message" str - "-group" "org.gnu.Emacs" - "-sender" "org.gnu.Emacs")))) + (setq sentence-end-double-space nil) #+end_src * Markdown