|
|
#+title: emacs init file |
|
|
#+author: Jacopo De Simoi |
|
|
#+email: jacopods@math.utoronto.ca |
|
|
#+options: *:t ::t |
|
|
|
|
|
* TODO Tasks that need attention |
|
|
|
|
|
** TODO BUG!!!!!!!!!!!! invalid target location, and the capture is gone. |
|
|
** TODO C-a does not work as expected when the subtree is expanded |
|
|
** TODO migrate from patching org-mode to a more sustainable option |
|
|
the issue is that I wanted to make sure that nothing would break by |
|
|
managing conflicts by hand, but this is not happening in practice |
|
|
** TODO can we run a task with an org-link? |
|
|
For instance, I want to call kmail in "zero inbox proton" |
|
|
** DONE improve helm-mu-contacs |
|
|
- [ ] Manage ignore list |
|
|
We can advise ~ helm-mu-contacts-transformer~ and remove the |
|
|
candidates |
|
|
- [ ] Implement action for ignoring contacts |
|
|
|
|
|
** TODO remove duplicates in contacts list with double spaces and such |
|
|
** DONE take care of the "dear To" when completing the name in an email |
|
|
** DONE make an alist for names to nicknames when completing the name… |
|
|
** TODO how to "open with" attachments with mu4e? |
|
|
** DONE investigate what is wrong with inline-pdf image width |
|
|
I need to cycle the mode on/off to make it work as intended |
|
|
** TODO fix the issue with highlighting bleeding over the margin for |
|
|
~ace-window~ |
|
|
** TODO The window management is sometimes frustrating |
|
|
** TODO Autosort token for some subtrees |
|
|
** TODO Work on a "welcome" read-only org page for each activity in |
|
|
which we put links to what should be done in that activity. For |
|
|
instance |
|
|
|
|
|
** TODO revert org-files on frame activation. |
|
|
What was the deal with auto-revert-mode again? |
|
|
** TODO explore [[https://blog.modelworks.ch/managing-papers-with-org-mode-and-reftex/][this possibility]] about org-mode paper managing |
|
|
|
|
|
|
|
|
* COMMENT About indentation in this file |
|
|
This file will be indented with ~org-adapt-indentation~ set to t |
|
|
|
|
|
* TODO Look at tangling at |
|
|
[http://thewanderingcoder.com/2015/02/literate-emacs-configuration/] |
|
|
|
|
|
although I'd rather do something like a local variable evaluation such as |
|
|
# -*- eval: (add-hook 'before-save-hook icalendar-export-to-ics nil t) -*- |
|
|
Or makefile approach |
|
|
[https://emacs.stackexchange.com/questions/27126/is-it-possible-to-org-bable-tangle-an-org-file-from-the-command-line] |
|
|
|
|
|
See also [http://orgmode.org/manual/Extracting-source-code.html#Extracting-source-code] |
|
|
|
|
|
* About org-mode |
|
|
I am insisting to use my own branch of org-mode; in order to have |
|
|
this work, we need to run the following code before |
|
|
anything else. Add it to ~early-init.el~. This file is tangled |
|
|
every time emacs starts, so that's fine. |
|
|
|
|
|
#+begin_src emacs-lisp :tangle "early-init.el" |
|
|
; this is required for using my branch of org-mode |
|
|
(add-to-list 'load-path "~/clones/org-mode/lisp") |
|
|
(require 'org-loaddefs) |
|
|
#+end_src |
|
|
|
|
|
* Introduction |
|
|
** ~setq~ vs ~setq-default~ |
|
|
Some variables are local, others are not; for non buffer-local |
|
|
variables the two commands are equivalent; for the buffer-local variables, |
|
|
setq acts on the current buffer, whereas setq-default sets the default |
|
|
value of the variable for buffers that do not have a local value. |
|
|
|
|
|
:source: [https://stackoverflow.com/a/18173666] |
|
|
|
|
|
** Why not ~package.el~ |
|
|
it is convenient, but it is not git-based; ~borg.el~ is promising, but it is |
|
|
way complicated; I can do everything by hand as soon as it is neatly |
|
|
~org~-anized. In the end we use a few, but I plan to phase them out and |
|
|
replace them with git submodules |
|
|
|
|
|
* Files and dirs |
|
|
Move ~Customize~ to a separate file |
|
|
#+begin_src emacs-lisp |
|
|
(setq custom-file "~/.emacs.d/custom.el") |
|
|
(load custom-file 'noerror) |
|
|
#+end_src |
|
|
|
|
|
Add relevant dirs to load path [This is somewhat undesirable; I should |
|
|
uniformize the path-loading business at some point…] |
|
|
#+begin_src emacs-lisp |
|
|
(let ((default-directory "~/.emacs.d/")) |
|
|
(normal-top-level-add-subdirs-to-load-path) |
|
|
(normal-top-level-add-to-load-path '("biblio.el" |
|
|
"fringe-helper.el" |
|
|
"multiple-cursors.el" |
|
|
"expand-region.el" |
|
|
"dash.el" |
|
|
"webpaste.el" |
|
|
"highlight-parentheses.el"))) |
|
|
|
|
|
#+end_src |
|
|
|
|
|
Put autosave files (ie #foo#) and backup files (ie foo~) in ~/.emacs.d/. |
|
|
create the autosave dir if necessary, since emacs won't. |
|
|
#+begin_src emacs-lisp |
|
|
(make-directory "~/.emacs.d/autosaves/" t) |
|
|
(make-directory "~/.emacs.d/backup/" t) |
|
|
#+end_src |
|
|
|
|
|
* Themes |
|
|
Use (my clone of) the solarized theme |
|
|
#+begin_src emacs-lisp |
|
|
(add-to-list 'custom-theme-load-path "/home/jacopods/.emacs.d/emacs-color-theme-solarized") |
|
|
(load-theme 'lunarized t) |
|
|
|
|
|
#+end_src |
|
|
|
|
|
Use patched terminus font (this overrides the settings in Customize, |
|
|
but I never could it work otherwise). See the special treatment |
|
|
later on for dealing with bold weight. To avoid flickering one |
|
|
should also set the font in .Xresources |
|
|
#+begin_src emacs-lisp |
|
|
(set-face-font 'default "Hackminus") |
|
|
(set-face-font 'fixed-pitch "Hackminus") |
|
|
(set-face-font 'fixed-pitch-serif "Hackminus") |
|
|
(set-face-font 'variable-pitch "Hackminus") |
|
|
#+end_src |
|
|
|
|
|
* Global settings |
|
|
** server name |
|
|
I prefer to keep one server for each KDE activity, so |
|
|
set the server name to be the name of the current activity |
|
|
#+begin_src emacs-lisp |
|
|
; (setq server-name (kde-current-activity-name)) |
|
|
#+end_src |
|
|
** Global helper functions |
|
|
This is a helper function used to set several global keys given in |
|
|
provided as a parameter in ~binding-alist~ |
|
|
#+begin_src emacs-lisp |
|
|
(defun global-set-key-alist (binding-alist) |
|
|
"This function iterates over the binding-alist, which should be an alist of key and binding and sets each binding in the global-key-map" |
|
|
(dolist (binding binding-alist) |
|
|
(global-set-key (kbd (car binding)) (cdr binding)))) |
|
|
|
|
|
(defun define-key-alist (keymap binding-alist) |
|
|
"This function iterates over the binding-alist, which should be an alist of key and binding and sets each binding in the global-key-map" |
|
|
(dolist (binding binding-alist) |
|
|
(define-key keymap (kbd (car binding)) (cdr binding)))) |
|
|
#+end_src |
|
|
This is a hack to remove slant and bold from all faces |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/fixup-faces () |
|
|
(interactive) |
|
|
(mapc (lambda (face) |
|
|
(set-face-attribute face nil :weight 'normal) |
|
|
(set-face-attribute face nil :slant 'normal)) |
|
|
(face-list)) |
|
|
(set-face-font 'default "Hackminus")) |
|
|
#+end_src |
|
|
This function saves the current buffer after the specified amount |
|
|
of idle time, unless something calls it again and then the timer |
|
|
restarts |
|
|
#+begin_src emacs-lisp |
|
|
(defun save--buffer-buf (buf) |
|
|
(with-current-buffer buf (save-buffer))) |
|
|
(defun save-buffer-with-timer (secs) |
|
|
(when (and (boundp 'save-timer) save-timer) |
|
|
(cancel-timer save-timer)) |
|
|
(setq-local save-timer (run-with-idle-timer secs nil |
|
|
#'save--buffer-buf (current-buffer)))) |
|
|
#+end_src |
|
|
Message-and-wait |
|
|
#+begin_src emacs-lisp |
|
|
(defun debug-message (s) |
|
|
(message s) |
|
|
(sleep-for 0 75)) |
|
|
#+end_src |
|
|
** Cosmetics |
|
|
Prefer a minimal appearance: no menu, toolbar or scroll-bars; no |
|
|
splash screens or messages |
|
|
#+begin_src emacs-lisp |
|
|
(menu-bar-mode -1) |
|
|
(tool-bar-mode 0) |
|
|
(scroll-bar-mode -1) |
|
|
(setq inhibit-startup-screen t |
|
|
inhibit-startup-message t) |
|
|
#+end_src |
|
|
a blinking cursor keeps the gpu awake; add global hl-line mode to more |
|
|
easily spot the cursor |
|
|
#+begin_src emacs-lisp |
|
|
(blink-cursor-mode 0) |
|
|
(set-default 'cursor-type 'box) |
|
|
#+end_src |
|
|
Set frame title |
|
|
#+begin_src emacs-lisp |
|
|
(setq frame-title-format |
|
|
'((buffer-file-name "%f" |
|
|
(dired-directory dired-directory "%b")) " · emacs")) |
|
|
#+end_src |
|
|
Themed tooltips |
|
|
#+begin_src emacs-lisp |
|
|
(setq x-gtk-use-system-tooltips nil) |
|
|
#+end_src |
|
|
Set =fill-column= to 72, which happens to be good for third-tiled frames |
|
|
and it also is a reasonable standard |
|
|
#+begin_src emacs-lisp |
|
|
(setq default-fill-column 72) |
|
|
#+end_src |
|
|
Highlight sexp (apparently I am not even using this) |
|
|
#+begin_src emacs-lisp |
|
|
;(require 'highlight-sexps) |
|
|
#+end_src |
|
|
Try to get along with large margins |
|
|
#+begin_src emacs-lisp |
|
|
(setq-default left-margin-width 3 |
|
|
right-margin-width 3 |
|
|
helm-left-margin-width left-margin-width |
|
|
helm-buffers-left-margin-width left-margin-width) |
|
|
(fringe-mode 10) |
|
|
;(set-window-buffer nil (current-buffer)) |
|
|
#+end_src |
|
|
Setup [[https://github.com/purcell/page-break-lines][page-break-lines]] |
|
|
#+begin_src emacs-lisp |
|
|
(require 'page-break-lines) |
|
|
(setq page-break-lines-char ?· |
|
|
page-break-lines-lighter "") |
|
|
(add-to-list 'page-break-lines-modes 'latex-mode) |
|
|
(add-to-list 'page-break-lines-modes 'TeX-latex-mode) |
|
|
(add-to-list 'page-break-lines-modes 'tex-mode) |
|
|
(global-page-break-lines-mode) |
|
|
#+end_src |
|
|
** Mouseless |
|
|
Disable mouse interaction with emacs |
|
|
#+begin_src emacs-lisp |
|
|
(mouse-wheel-mode -1) |
|
|
(setq mouse-autoselect-window nil |
|
|
mouse-yank-at-point nil |
|
|
focus-follows-mouse nil |
|
|
mouse-highlight nil) |
|
|
#+end_src |
|
|
*** TODO Find out what of the above is necessary given the disable-mouse mode below |
|
|
Use the ~disable-mouse~ package by Steve Purcell available at |
|
|
[https://github.com/purcell/disable-mouse] |
|
|
#+begin_src emacs-lisp |
|
|
(require 'disable-mouse) |
|
|
(global-disable-mouse-mode) |
|
|
#+end_src |
|
|
** Window behavior |
|
|
Prevent compilation window eating up other windows |
|
|
#+begin_src emacs-lisp |
|
|
(setq compilation-window-height 12 |
|
|
compilation-scroll-output nil) |
|
|
#+end_src |
|
|
Prevent any automatic splitting to split vertically |
|
|
#+begin_src emacs-lisp |
|
|
(setq split-height-threshold 0) |
|
|
#+end_src |
|
|
*** Display-buffer rules |
|
|
Make sure that manual buffer switch also obey display actions |
|
|
#+begin_src emacs-lisp |
|
|
(setq switch-to-buffer-obey-display-actions t) |
|
|
#+end_src |
|
|
First, treat ~mu4e-headers~ properly |
|
|
#+begin_src emacs-lisp |
|
|
(add-to-list 'display-buffer-alist |
|
|
'("\\*mu4e-headers\\*" |
|
|
display-buffer-reuse-window)) |
|
|
(add-to-list 'display-buffer-alist |
|
|
'("\\*mu4e-main\\*" |
|
|
display-buffer-reuse-window |
|
|
(dedicated . t) |
|
|
(inhibit-same-window . nil))) |
|
|
(add-to-list 'display-buffer-alist |
|
|
'("\\*compilation\\*" display-buffer-no-window |
|
|
(allow-no-window . t))) |
|
|
#+end_src |
|
|
Then, treat the ~calendar~ |
|
|
#+begin_src emacs-lisp |
|
|
(add-to-list 'display-buffer-alist |
|
|
'("\\*Calendar\\*" |
|
|
(display-buffer-in-side-window) |
|
|
(side . bottom ) |
|
|
(slot . 0) |
|
|
(window-height . 10))) |
|
|
#+end_src |
|
|
** Scrolling |
|
|
Scrolling setup |
|
|
#+begin_src emacs-lisp |
|
|
(setq redisplay-dont-pause t |
|
|
scroll-margin 3 |
|
|
scroll-step 1 |
|
|
scroll-conservatively 10000 |
|
|
scroll-preserve-screen-position 1) |
|
|
#+end_src |
|
|
|
|
|
*** TODO find out what this was actually meant to do |
|
|
#+begin_src emacs-lisp |
|
|
(setq-default display-buffer-reuse-frames t) |
|
|
#+end_src |
|
|
|
|
|
** Mode-line |
|
|
#+begin_src emacs-lisp |
|
|
(setq-default mode-line-modified '(:eval (if (buffer-modified-p) "●" "·")) |
|
|
;Still needs some improvements, does not report Readonly state |
|
|
mode-line-remote '(:eval (let ((s (format-mode-line "%@"))) |
|
|
(cond |
|
|
((equal s "-") " ") ((equal s "@") "@") (t s))))) |
|
|
|
|
|
(defface mode-line-indicator |
|
|
'((t nil)) |
|
|
"mode-line active indicator" |
|
|
:group 'mode-line-faces) |
|
|
|
|
|
(defface mode-line-inactive-indicator |
|
|
'((t nil)) |
|
|
"Inactive variant of indicator" |
|
|
:group 'mode-line-faces) |
|
|
|
|
|
|
|
|
;; This has some issues when two buffers are shown in different windows |
|
|
;; There are some solutions but let us see if this works first |
|
|
(defun mode-line-update-face (window) |
|
|
"Update the `mode-line' face in WINDOW to indicate whether the window is selected." |
|
|
(with-current-buffer (window-buffer window) |
|
|
(if (eq (current-buffer) (window-buffer (selected-window))) |
|
|
(face-remap-reset-base 'mode-line-indicator) |
|
|
(face-remap-set-base 'mode-line-indicator (face-all-attributes 'mode-line-inactive-indicator))))) |
|
|
|
|
|
(add-hook 'buffer-list-update-hook (lambda () (walk-windows #'mode-line-update-face nil t))) |
|
|
|
|
|
(defvar wilder-buffer-vc-mode-line |
|
|
'("%b" (vc-mode (:propertize |
|
|
;; Strip the backend name from the VC status information |
|
|
(:eval (let* ((backend (downcase (symbol-name (vc-backend (buffer-file-name))))) |
|
|
(branch (substring vc-mode (+ (length backend) 2))) |
|
|
(s (substring vc-mode (+ (length backend) 1) (+ (length backend) 2))) |
|
|
(status (cond ((equal s "-") "") ((equal s ":") "!") (t s)))) |
|
|
(concat "·" branch status))) |
|
|
face magit-branch-local)))) |
|
|
|
|
|
(defvar wilder-position |
|
|
'("%p · %I" )) |
|
|
|
|
|
(put 'wilder-buffer-vc-mode-line 'risky-local-variable t) |
|
|
(put 'wilder-position 'risky-local-variable t) |
|
|
|
|
|
(defvar wilder/mode-line-modes |
|
|
(let ((recursive-edit-help-echo "Recursive edit, type C-M-c to get out")) |
|
|
(list (propertize "%[" 'help-echo recursive-edit-help-echo) |
|
|
'("" mode-name) |
|
|
'("" mode-line-process) |
|
|
" " |
|
|
'("" minor-mode-alist) |
|
|
(propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer" |
|
|
'mouse-face 'mode-line-highlight |
|
|
'local-map (make-mode-line-mouse-map |
|
|
'mouse-2 #'mode-line-widen)) |
|
|
(propertize "%]" 'help-echo recursive-edit-help-echo) |
|
|
" ")) |
|
|
"Mode line construct for displaying major and minor modes.") |
|
|
|
|
|
(put 'wilder/mode-line-modes 'risky-local-variable t) |
|
|
|
|
|
(setq-default header-line-format nil);'("%e" ));wilder-buffer-vc-mode-line)) |
|
|
|
|
|
(setq mode-line-separator |
|
|
'(:eval (let* ((len-left (length (format-mode-line mode-line-format-left))) |
|
|
(len-right (length (format-mode-line mode-line-format-right))) |
|
|
(len-separator (- (+ (window-width) 2 2 2) (+ len-left len-right)))) |
|
|
(format (format "%%%ds" len-separator) "")))) |
|
|
|
|
|
(setq mode-line-format-left |
|
|
'("%e" |
|
|
(:propertize "╺═╸" face mode-line-indicator) |
|
|
mode-line-front-space |
|
|
mode-line-modified |
|
|
mode-line-remote |
|
|
mode-line-frame-identification |
|
|
wilder-buffer-vc-mode-line |
|
|
" " |
|
|
wilder-position)) |
|
|
|
|
|
(setq mode-line-format-right |
|
|
'(" " |
|
|
wilder/mode-line-modes |
|
|
mode-line-misc-info |
|
|
" ")) |
|
|
|
|
|
(put 'mode-line-format-left 'risky-local-variable t) |
|
|
(put 'mode-line-format-right 'risky-local-variable t) |
|
|
(put 'mode-line-separator 'risky-local-variable t) |
|
|
|
|
|
(setq-default mode-line-format |
|
|
'("%e" mode-line-format-left |
|
|
mode-line-separator |
|
|
mode-line-format-right)) |
|
|
#+end_src |
|
|
The following has been found in [[https://www.masteringemacs.org/article/hiding-replacing-modeline-strings][here]] to clean up the modeline |
|
|
#+begin_src emacs-lisp |
|
|
(defvar mode-line-cleaner-alist |
|
|
'() |
|
|
"Alist for `clean-mode-line.' |
|
|
|
|
|
When you add a new element to the alist, keep in mind that you |
|
|
must pass the correct minor/major mode symbol and a string you |
|
|
want to use in the modeline *in lieu of* the original.") |
|
|
|
|
|
(setq mode-line-cleaner-alist |
|
|
`((auto-complete-mode . " α") |
|
|
(yas/minor-mode . " υ") |
|
|
(paredit-mode . " π") |
|
|
(eldoc-mode . "") |
|
|
(abbrev-mode . "") |
|
|
(auto-fill-function . "") |
|
|
(disable-mouse-global-mode . "") |
|
|
(auto-revert-mode . "") |
|
|
(helm-mode . " η" ) |
|
|
(smart-tab-mode . "") |
|
|
(subword-mode . "") |
|
|
(outshine-mode . " o") |
|
|
(outline-minor-mode . "") |
|
|
(reftex-mode . "") |
|
|
(flyspell-mode . "") |
|
|
(geiser-mode . " γ") |
|
|
(geiser-autodoc-mode . "·α") |
|
|
(org-src-mode . " ωσ") |
|
|
;; Major modes |
|
|
(lisp-interaction-mode . "λ") |
|
|
(hi-lock-mode . "") |
|
|
(python-mode . "Py") |
|
|
(emacs-lisp-mode . "ελ") |
|
|
(help-mode . "+") |
|
|
(scheme-mode . "λ") |
|
|
(tex-mode . "χ") |
|
|
(latex-mode . "χ") |
|
|
(TeX-latex-mode . "χ") |
|
|
(org-mode . "Ω") |
|
|
(org-agenda-mode . "Ω:Agenda"))) |
|
|
|
|
|
(defun clean-mode-line () |
|
|
(interactive) |
|
|
(loop for cleaner in mode-line-cleaner-alist |
|
|
do (let* ((mode (car cleaner)) |
|
|
(mode-str (cdr cleaner)) |
|
|
(old-mode-str (cdr (assq mode minor-mode-alist)))) |
|
|
(when old-mode-str |
|
|
(setcar old-mode-str mode-str)) |
|
|
;; major mode |
|
|
(when (eq mode major-mode) |
|
|
(setq mode-name mode-str))))) |
|
|
|
|
|
|
|
|
(add-hook 'after-change-major-mode-hook 'clean-mode-line) |
|
|
|
|
|
#+end_src |
|
|
** Coding system |
|
|
Prefer the utf-8 coding system globally. This helps with the issue of |
|
|
magit not correctly staging hunks containing utf-8 characters. |
|
|
See [https://github.com/magit/magit/issues/32] |
|
|
#+begin_src emacs-lisp |
|
|
(prefer-coding-system 'utf-8) |
|
|
#+end_src |
|
|
** Mark handling |
|
|
No transient mark is more flexible |
|
|
#+begin_src emacs-lisp |
|
|
(transient-mark-mode 0) |
|
|
#+end_src |
|
|
But of course we need to /see/ the mark |
|
|
#+begin_src emacs-lisp |
|
|
(require 'visible-mark) |
|
|
(visible-mark-mode t) |
|
|
(global-visible-mark-mode t) |
|
|
#+end_src |
|
|
|
|
|
The following are some convenient bindings; notice that on my layout F17 and |
|
|
F18 are obtained by tapping the left and right shift respectively |
|
|
#+begin_src emacs-lisp |
|
|
(defun jump-to-mark () |
|
|
"Jumps to the local mark, respecting the `mark-ring' order. |
|
|
This is the same as using \\[set-mark-command] with the prefix argument." |
|
|
(interactive) |
|
|
(set-mark-command 1)) |
|
|
|
|
|
(defun just-activate-mark () |
|
|
(interactive) |
|
|
(activate-mark)) |
|
|
|
|
|
(global-set-key-alist |
|
|
'(("<f17>" . set-mark-command) |
|
|
("<f18>" . set-mark-command) |
|
|
("<M-f17>" . jump-to-mark) |
|
|
("<M-f18>" . jump-to-mark) |
|
|
("<S-f17>" . just-activate-mark) |
|
|
("<S-f18>" . just-activate-mark))) |
|
|
|
|
|
(advice-add 'menu-bar-enable-clipboard :override (lambda ())) ; prevent the menu-bar from stealing my binding |
|
|
#+end_src |
|
|
** Global bindings |
|
|
Remove some bindings that I find supremely annoying. |
|
|
Additionally, unbind the ~C-x o~ binding to force me using ~ace-window~ |
|
|
Finally, unbind the ~C-SPC~ to mark the point, as I am using LR-shift |
|
|
Note that I will use ~C-SPC~ as a hydra below to deal with parenthesis |
|
|
#+begin_src emacs-lisp |
|
|
(dolist (ch '("C-h C-n" "C-c C-x" "C-x C-r" "C-z" "C-x f" "C-x C-f" "<M-f4>" "C-M-u" "C-M-d" "C-x o" "C-SPC")) |
|
|
(global-unset-key (kbd ch))) |
|
|
#+end_src |
|
|
|
|
|
Change {up,down}-list |
|
|
#+begin_src emacs-lisp |
|
|
(global-set-key (kbd "C-M-i") 'down-list) ;; -i stands for /in/ |
|
|
(global-set-key (kbd "C-M-o") 'up-list) ;; -o stands for /out/ |
|
|
#+end_src |
|
|
Add some opinionated bindings |
|
|
#+begin_src emacs-lisp |
|
|
(global-set-key-alist |
|
|
'(("C-M-d" . kill-sexp) |
|
|
("C-M-<backspace>" . backward-kill-sexp) |
|
|
("C-x k" . kill-this-buffer) |
|
|
("C-S-v" . scroll-down-command) |
|
|
("C-M-u" . universal-argument))) |
|
|
#+end_src |
|
|
** Disable commands |
|
|
Enable narrow commands |
|
|
#+begin_src emacs-lisp |
|
|
(put 'narrow-to-region 'disabled nil) |
|
|
(put 'LaTeX-narrow-to-environment 'disabled nil) |
|
|
(put 'narrow-to-page 'disabled nil) |
|
|
#+end_src |
|
|
** Smart beginning-of-line |
|
|
This is a relatively smart way to go back to the beginning of the line |
|
|
#+begin_src emacs-lisp |
|
|
(defun smart-line-beginning () |
|
|
"Move point to the beginning of text on the current line; if that |
|
|
is already the current position of point, then move it to the |
|
|
beginning of the line." |
|
|
(interactive) |
|
|
(let ((pt (point))) |
|
|
(beginning-of-line-text) |
|
|
(when (eq pt (point)) |
|
|
(beginning-of-line)))) |
|
|
(global-set-key (kbd "C-a") 'smart-line-beginning) |
|
|
#+end_src |
|
|
** Smart open-line |
|
|
#+begin_src emacs-lisp |
|
|
(defun open-line-and-indent (arg) |
|
|
(interactive "*p") |
|
|
(save-excursion |
|
|
(newline-and-indent) |
|
|
(skip-chars-forward "[:blank:]") |
|
|
(when (eolp) |
|
|
(let ((e (point)) |
|
|
(b (progn (beginning-of-line) (point)))) |
|
|
(delete-region b e))))) |
|
|
|
|
|
(global-set-key (kbd "C-o") #'open-line-and-indent) |
|
|
#+end_src |
|
|
** Whitespace |
|
|
Trailing whitespace is evil; get rid of it unless we are in special |
|
|
modes, namely calendar (and later mu4e) |
|
|
#+begin_src emacs-lisp |
|
|
(setq-default show-trailing-whitespace t) |
|
|
(add-hook 'calendar-mode-hook |
|
|
#'(lambda () (setq-local show-trailing-whitespace nil))) |
|
|
(add-hook 'mu4e-main-mode-hook |
|
|
#'(lambda () (setq-local show-trailing-whitespace nil))) |
|
|
(add-hook 'helm-major-mode-hook |
|
|
#'(lambda () (setq-local show-trailing-whitespace nil))) |
|
|
(add-hook 'before-save-hook 'delete-trailing-whitespace) |
|
|
#+end_src |
|
|
Define what is whitespace during a search |
|
|
#+begin_src emacs-lisp |
|
|
(setq search-whitespace-regexp "[ \t\r\n]+") |
|
|
#+end_src |
|
|
Tabs are evil; get rid of them |
|
|
#+begin_src emacs-lisp |
|
|
(setq c-basic-offset 4) |
|
|
(setq-default tab-width 4 |
|
|
indent-tabs-mode nil) |
|
|
#+end_src |
|
|
*** TODO Have a look at ws-trim.el |
|
|
It is conf'ble to avoid removing whitespace from lines that were not |
|
|
modified; sounds like a good idea for git |
|
|
[ftp://ftp.lysator.liu.se/pub/emacs/ws-trim.el] |
|
|
|
|
|
** Show matching parens |
|
|
Use ~show-paren~ for paren-highlighting |
|
|
#+begin_src emacs-lisp |
|
|
(show-paren-mode 1) |
|
|
#+end_src |
|
|
*** TODO find out if there are better options out there |
|
|
** Load hydra |
|
|
#+begin_src emacs-lisp |
|
|
(require 'hydra) |
|
|
#+end_src |
|
|
|
|
|
** Abbrevs |
|
|
Set up abbrevs |
|
|
#+begin_src emacs-lisp |
|
|
(setq-default abbrev-mode t) |
|
|
(read-abbrev-file "~/.abbrev_defs") |
|
|
(setq save-abbrevs t) |
|
|
#+end_src |
|
|
** Spellcheck |
|
|
Use Canadian spelling |
|
|
#+begin_src emacs-lisp |
|
|
(setq ispell-dictionary "canadian") |
|
|
#+end_src |
|
|
Use flyspell |
|
|
|
|
|
#+begin_src emacs-lisp |
|
|
(require 'flyspell-lazy) |
|
|
(flyspell-lazy-mode 1) |
|
|
(add-hook 'text-mode-hook 'flyspell-mode) |
|
|
(setq flyspell-use-meta-tab nil) |
|
|
(defface flyspell-margin-incorrect |
|
|
'((t nil)) |
|
|
"flyspell-margin-incorrect" |
|
|
:group 'flyspell) |
|
|
(setq flyspell-before-incorrect-word-string |
|
|
(propertize "." 'display `((margin left-margin) |
|
|
,(propertize "×" 'face 'flyspell-margin-incorrect)))) |
|
|
#+end_src |
|
|
** Fringe treatment |
|
|
*** Add margin marker for current line |
|
|
Add marker for current line in the margin (see |
|
|
[https://github.com/kyanagi/fringe-current-line] and |
|
|
[https://github.com/nschum/fringe-helper.el] for inspiration) |
|
|
#+begin_src emacs-lisp |
|
|
(require 'margin-current-line) |
|
|
(global-margin-current-line-mode 1) |
|
|
#+end_src |
|
|
|
|
|
** Turn on echo mode |
|
|
Display unfinished commands in the echo area after the specified delay. |
|
|
#+begin_src emacs-lisp |
|
|
(setq echo-keystrokes 0.10) |
|
|
#+end_src |
|
|
** frame helpers |
|
|
#+begin_src emacs-lisp |
|
|
(defmacro with-new-frame (&rest body) |
|
|
`(with-selected-frame (make-frame) ,@body)) |
|
|
(defmacro eval-with-new-frame (&rest body) |
|
|
`(lambda () (interactive) (with-new-frame ,@body))) |
|
|
#+end_src |
|
|
** Disable completion buffer |
|
|
Remove the completion buffer as soon as we get out of the minibuffer |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'minibuffer-exit-hook |
|
|
'(lambda () |
|
|
(let ((buffer "*Completions*")) |
|
|
(and (get-buffer buffer) |
|
|
(kill-buffer buffer))) )) |
|
|
#+end_src |
|
|
** Auto save on compile |
|
|
#+begin_src emacs-lisp |
|
|
(setq compilation-ask-about-save nil) |
|
|
#+end_src |
|
|
** Idle-kill |
|
|
#+begin_src emacs-lisp |
|
|
(defun kill-buffers-on-idle () |
|
|
"Kill buffers that have the appropriate property set." |
|
|
(save-excursion |
|
|
(save-window-excursion |
|
|
(dolist (b (buffer-list)) |
|
|
(when (and (with-current-buffer b (boundp 'buffer-close-on-idle)) |
|
|
(with-current-buffer b buffer-close-on-idle)) |
|
|
(with-current-buffer b (message "Killed %s" buffer-file-name)) |
|
|
(pop-to-buffer-same-window b) |
|
|
(kill-buffer)))))) |
|
|
(run-with-idle-timer 180 t 'kill-buffers-on-idle) |
|
|
#+end_src |
|
|
** Calendar |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'calendar-load-hook |
|
|
(lambda () |
|
|
(calendar-set-date-style 'european))) |
|
|
;; first day of the week is monday instead of sunday: |
|
|
(setq calendar-week-start-day 1 |
|
|
calendar-location-name "TO" |
|
|
calendar-latitude 43.7 |
|
|
calendar-longitude -79.4 |
|
|
calendar-today-marker "·") |
|
|
(add-hook 'today-visible-calendar-hook 'calendar-mark-today) |
|
|
#+end_src |
|
|
** Use local proxy |
|
|
I need this to route bibretrieve requests through my local proxy, |
|
|
that in turn routes requests to ~mathscinet~ through the University network |
|
|
#+begin_src emacs-lisp |
|
|
;; Use system proxy |
|
|
(setq url-proxy-services '(("http" . "127.0.0.1:8118") |
|
|
("https" . "127.0.0.1:8118"))) |
|
|
#+end_src |
|
|
** Indicator |
|
|
Change cursor color to reflect different layers on the keyboard |
|
|
This is the handler |
|
|
#+begin_src emacs-lisp |
|
|
(defun xkbvleds-indicator-signal-handler (indicator on) |
|
|
(if on |
|
|
(cond ((string-equal indicator "Greek") |
|
|
(set-face-background 'cursor "#2aa198")) |
|
|
((string-equal indicator "Math") |
|
|
(set-face-background 'cursor "#cb4b16"))) |
|
|
(set-face-background 'cursor "#919191"))) |
|
|
#+end_src |
|
|
Connect the signal from xkbvleds to the handler |
|
|
#+begin_src emacs-lisp |
|
|
(require 'dbus) |
|
|
;; Register for the signal indicatorChanged coming from our service. It is |
|
|
;; important that the =service= argument is =nil=, otherwise the registration |
|
|
;; would stop once the process is respawned. |
|
|
(ignore-errors (dbus-register-signal |
|
|
:session nil "/org/xkbvleds" "org.xkbvleds" "indicatorChanged" |
|
|
'xkbvleds-indicator-signal-handler)) |
|
|
#+end_src |
|
|
** COMMENT Tarmak-ready |
|
|
Set up all possible combinations of modifiers |
|
|
#+begin_src emacs-lisp |
|
|
(defun concat-recursive (b &optional a) |
|
|
(if b |
|
|
(append (concat-recursive (cdr b) (concat a (car b))) |
|
|
(concat-recursive (cdr b) a)) |
|
|
(when a (list a)))) |
|
|
|
|
|
(setq modifier-combo (concat-recursive '("C-" "M-" "s-" "H-"))) |
|
|
#+end_src |
|
|
Then list the swapped keys |
|
|
#+begin_src emacs-lisp |
|
|
(setq tarmak1-swaps '(("j" . "e") |
|
|
("e" . "k") |
|
|
("k" . "n") |
|
|
("n" . "j"))) |
|
|
#+end_src |
|
|
** Transmission |
|
|
#+begin_src emacs-lisp |
|
|
(require 'transmission) |
|
|
(setq transmission-host "192.168.0.40") |
|
|
#+end_src |
|
|
|
|
|
** Tree-sitter grammars |
|
|
#+begin_src emacs-lisp |
|
|
(use-package treesit-auto |
|
|
:config |
|
|
(global-treesit-auto-mode)) |
|
|
(setq treesit-auto-install 'prompt) |
|
|
#+end_src |
|
|
|
|
|
** TODO KDE integration |
|
|
*** TODO cleanup and split |
|
|
These functions connects to dbus to find out the current activity |
|
|
id and name |
|
|
#+begin_src emacs-lisp |
|
|
(defun kde-current-activity () |
|
|
"Returns the current KDE activity" |
|
|
(substring (let ((default-directory "~")) (shell-command-to-string "qdbus org.kde.ActivityManager /ActivityManager/Activities org.kde.ActivityManager.Activities.CurrentActivity")) 0 -1)) |
|
|
(defun kde-current-activity-name () |
|
|
"Returns the name of the current KDE activity" |
|
|
(substring (let ((default-directory "~")) (shell-command-to-string (concat "qdbus org.kde.ActivityManager /ActivityManager/Activities org.kde.ActivityManager.Activities.ActivityName " (kde-current-activity)))) 0 -1)) |
|
|
#+end_src |
|
|
|
|
|
#+begin_src emacs-lisp |
|
|
(defun X-window-id-belongs-to-activity (window-id activity) |
|
|
(= (shell-command (concat "xprop -id " window-id " | grep _KDE_NET_WM_ACTIVITIES | grep " activity ">/dev/null")) 0) |
|
|
) |
|
|
|
|
|
(defun select-frame-on-activity (activity) |
|
|
(setq framelist (frame-list)) |
|
|
(setq done nil) |
|
|
(while (and framelist (not done)) |
|
|
(setq cur (car framelist)) |
|
|
(setq cur-id (cdr (assq 'window-id (frame-parameters cur)))) |
|
|
(if cur-id (if (X-window-id-belongs-to-activity cur-id activity) (progn; |
|
|
(setq done 't) (select-frame cur)) )) |
|
|
(setq framelist (cdr framelist))) |
|
|
done |
|
|
) |
|
|
|
|
|
(defun select-X-frame () |
|
|
(setq framelist (frame-list)) |
|
|
(setq done nil) |
|
|
(while (and framelist (not done)) |
|
|
(setq cur (car framelist)) |
|
|
(setq cur-id (cdr (assq 'window-id (frame-parameters cur)))) |
|
|
(if cur-id (progn; |
|
|
(setq done 't) (raise-frame cur))) |
|
|
(setq framelist (cdr framelist))) |
|
|
done |
|
|
) |
|
|
#+end_src |
|
|
This function is used to raise the frame associated to the current |
|
|
activity |
|
|
*** TODO These entries should be added to the subtree once it is split |
|
|
#+begin_src emacs-lisp |
|
|
(defun select-frame-on-current-activity () |
|
|
(select-frame-on-activity (kde-current-activity))) |
|
|
#+end_src |
|
|
* Main major modes |
|
|
** mu4e |
|
|
These are some standard config items |
|
|
#+begin_src emacs-lisp |
|
|
(require 'smtpmail) |
|
|
(require 'mu4e) |
|
|
(require 'helm-mu) |
|
|
(global-set-key (kbd "C-x μ") 'mu4e) |
|
|
(global-set-key (kbd "C-x 5 μ") (eval-with-new-frame (mu4e))) |
|
|
|
|
|
(defalias 'μ 'mu4e) |
|
|
|
|
|
(setq mu4e-maildir "~/.mail" |
|
|
mu4e-attachment-dir "~/attachments" |
|
|
mu4e-html2text-command "w3m -dump -T text/html -cols 72 -o display_link_number=true -o auto_image=false -o display_image=false -o ignore_null_img_alt=true" |
|
|
mu4e-headers-show-threads nil |
|
|
mu4e-headers-sort-direction "descending" |
|
|
mail-user-agent 'mu4e-user-agent |
|
|
read-mail-command 'mu4e |
|
|
mu4e-update-interval 120 |
|
|
mu4e-view-use-gnus t |
|
|
message-kill-buffer-on-exit t |
|
|
mu4e-compose-format-flowed t |
|
|
fill-flowed-encode-column 998 |
|
|
mm-text-html-renderer 'gnus-w3m |
|
|
mu4e-read-option-use-builtin nil |
|
|
mu4e-completing-read-function 'completing-read |
|
|
mu4e-modeline-support nil) |
|
|
|
|
|
(let (p1 p2 myLine) |
|
|
(setq p1 (line-beginning-position) ) |
|
|
(setq p2 (line-end-position) ) |
|
|
(setq myLine (buffer-substring-no-properties p1 p2))) |
|
|
|
|
|
(defun get-buffer-current-line () |
|
|
(let ((p (line-beginning-position)) |
|
|
(q (line-end-position))) |
|
|
(buffer-substring-no-properties p q))) |
|
|
|
|
|
(defun get-buffer-to-eol () |
|
|
(let ((p (point)) |
|
|
(q (line-end-position))) |
|
|
(buffer-substring-no-properties p q))) |
|
|
|
|
|
(defun format-zoom-meeting () |
|
|
(if (boundp 'zoom-link) |
|
|
(if (boundp 'zoom-passcode) |
|
|
(concat zoom-summary |
|
|
" [[" zoom-link "][link]] (" zoom-passcode ")\n SCHEDULED: " zoom-scheduled-time) |
|
|
(concat zoom-summary |
|
|
" [[" zoom-link "][link]]\n SCHEDULED: " zoom-scheduled-time)) |
|
|
(concat zoom-summary "\n SCHEDULED: " zoom-scheduled-time))) |
|
|
|
|
|
(defun create-item-for-meeting (msg) |
|
|
"Search for messages sent by the sender of the message at point." |
|
|
(makunbound 'zoom-link) |
|
|
(makunbound 'zoom-passcode) |
|
|
(save-excursion |
|
|
(beginning-of-buffer) |
|
|
(cond ((search-forward "Join Zoom Meeting" nil t) |
|
|
(message "found zoom meeting") |
|
|
(right-char) |
|
|
(setq zoom-link (get-buffer-current-line)) |
|
|
(when (search-forward "Passcode:" nil t) |
|
|
(setq zoom-passcode (get-buffer-current-line))) |
|
|
(beginning-of-buffer) |
|
|
(search-forward "Time:") |
|
|
(search-forward "<") |
|
|
(left-char) |
|
|
(setq zoom-scheduled-time (get-buffer-to-eol)) |
|
|
(search-backward "Summary:") |
|
|
(right-word) |
|
|
(right-word) |
|
|
(left-word) |
|
|
(setq zoom-summary (get-buffer-to-eol)) |
|
|
(org-capture nil "z")) |
|
|
((search-forward "Accept ]") |
|
|
(message "found generic meeting") |
|
|
(search-forward "Summary:") |
|
|
(right-word) |
|
|
(right-word) |
|
|
(left-word) |
|
|
(setq zoom-summary (get-buffer-to-eol)) |
|
|
(search-forward "Location:") |
|
|
(right-word) |
|
|
(left-word) |
|
|
(setq zoom-summary (concat zoom-summary " @" (get-buffer-to-eol))) |
|
|
(search-forward "Time:") |
|
|
(search-forward "<") |
|
|
(left-char) |
|
|
(setq zoom-scheduled-time (get-buffer-to-eol)) |
|
|
(org-capture nil "z"))))) |
|
|
|
|
|
|
|
|
;; define 'z' as the shortcut |
|
|
(add-to-list 'mu4e-view-actions |
|
|
'("zCreate agenda item for meeting" . create-item-for-meeting) t) |
|
|
(advice-add 'mu4e-update-mail-and-index :around |
|
|
(lambda (orig-fun &rest args) |
|
|
"Only trigger an update if the server is the one that should |
|
|
currently be active; otherwise each mu4e instance will trigger an |
|
|
update" |
|
|
(when (string-equal server-name (kde-current-activity-name)) |
|
|
(apply orig-fun args)))) |
|
|
|
|
|
(advice-add 'mu4e~header-line-format :around |
|
|
(lambda (orig-fun &rest args) |
|
|
(let ((mu4e-use-fancy-chars t) |
|
|
(mu4e--mark-fringe-len 5)) |
|
|
(apply orig-fun args)))) |
|
|
|
|
|
#+end_src |
|
|
*** Recipient handling |
|
|
Add aliases (i.e. mailing groups) to [[file://~/.mailrc][the .mailrc file]] |
|
|
|
|
|
This function inserts recipients name at point; might be useful |
|
|
with abbrev mode shortcuts |
|
|
|
|
|
#+begin_src emacs-lisp |
|
|
(load-file "~/.emacs.d/confidential.el") |
|
|
|
|
|
(defun wilder/mu4e-process-recipient-name (name) |
|
|
(interactive) |
|
|
(let* ((clean-name (replace-regexp-in-string "\"" "" name)) |
|
|
(tentative-nickname (assoc clean-name nicknames-alist #'string-match-p))) |
|
|
(cond |
|
|
(tentative-nickname (cdr tentative-nickname)) |
|
|
((string-match-p (regexp-quote ",") clean-name) (string-trim (replace-regexp-in-string ".*," "" clean-name))) |
|
|
(t (replace-regexp-in-string " .*" "" clean-name))))) |
|
|
|
|
|
(defun recipients-ng () |
|
|
(interactive) |
|
|
(save-excursion |
|
|
(goto-char (point-min)) |
|
|
(search-forward-regexp "To: \\(\\(.\\|\n\\)*\\)\\(Subject\\|C(C\\|BCC\\)") |
|
|
(mapconcat #'wilder/mu4e-process-recipient-name |
|
|
(mapcar (lambda (s) (substring-no-properties |
|
|
(string-trim (car (split-string s "<"))))) |
|
|
(mapcar #'string-trim (split-string (match-string 1) ">,"))) |
|
|
", "))) |
|
|
#+end_src |
|
|
|
|
|
This seems to be a very crude attempt, that is indeed what I am |
|
|
using right now. |
|
|
#+begin_src emacs-lisp |
|
|
(defun mu4e-insert-recipients-name () |
|
|
"Inserts the name of the recipients at point" |
|
|
;; needs several improvements: at the moment it only handles very |
|
|
;; basic situations (one recipient, single-word first name) |
|
|
(insert (concat (recipients-ng) ", "))) |
|
|
#+end_src |
|
|
|
|
|
Customize ~mu4e~ bookmarks |
|
|
#+begin_src emacs-lisp |
|
|
(add-to-list 'mu4e-bookmarks |
|
|
'( :name "Starred" |
|
|
:query "flag:flagged" |
|
|
:key ?s)) |
|
|
(add-to-list 'mu4e-bookmarks |
|
|
'( :name "Archived, last two weeks" |
|
|
:query "date:14d..now maildir:/math/Archive" |
|
|
:key ?a)) |
|
|
(add-to-list 'mu4e-bookmarks |
|
|
'( :name "to capture" |
|
|
:query "maildir:/math/to-capture or maildir:/proton/Labels.capture" |
|
|
:key ?c)) |
|
|
(add-to-list 'mu4e-bookmarks |
|
|
'( :name "action-needed" |
|
|
:query "maildir:/math/action-needed" |
|
|
:key ?n)) |
|
|
(add-to-list 'mu4e-bookmarks |
|
|
'( :name "inbox" |
|
|
:query "maildir:/math/INBOX" |
|
|
:key ?i)) |
|
|
#+end_src |
|
|
|
|
|
Customize ~mu4e~ contexts |
|
|
#+begin_src emacs-lisp |
|
|
(setq mu4e-contexts |
|
|
`( ,(make-mu4e-context |
|
|
:name "Work" |
|
|
:enter-func (lambda () (mu4e-message "Switch to the Work context")) |
|
|
;; no leave-func |
|
|
;; we match based on the maildir of the message |
|
|
:match-func (lambda (msg) |
|
|
(when msg |
|
|
(string-match-p "^/math/INBOX" (mu4e-message-field |
|
|
msg :maildir)))) |
|
|
:vars '( ( user-mail-address . "jacopods@math.utoronto.ca" ) |
|
|
( user-full-name . "Jacopo De Simoi" ) |
|
|
( mu4e-sent-folder . "/math/Sent") |
|
|
( mu4e-drafts-folder . "/math/Drafts") |
|
|
( mu4e-trash-folder . "/math/Trash") |
|
|
( mu4e-refile-folder . "/math/Archive") |
|
|
( smtpmail-stream-type . starttls ) |
|
|
( smtpmail-smtp-service . 587 ) |
|
|
( mu4e-compose-signature . (concat |
|
|
"Jacopo De Simoi (he · him)\n" |
|
|
"Professor · U Toronto\n")))) |
|
|
|
|
|
,(make-mu4e-context |
|
|
:name "CMP Editor" |
|
|
:enter-func (lambda () (mu4e-message "Switch to the CMP Editor context")) |
|
|
:leave-func (lambda () (mu4e-message "Leave the CMP Editor context")) |
|
|
;; we match based on the maildir of the message |
|
|
:match-func (lambda (msg) |
|
|
(when msg |
|
|
(string-match-p "^/math/editorial.CMP" (mu4e-message-field |
|
|
msg :maildir)))) |
|
|
:vars '( ( user-mail-address . "jacopods@math.utoronto.ca" ) |
|
|
( user-full-name . "Jacopo De Simoi" ) |
|
|
( mu4e-sent-folder . "/math/Sent") |
|
|
( mu4e-drafts-folder . "/math/Drafts") |
|
|
( mu4e-trash-folder . "/math/Trash") |
|
|
( mu4e-refile-folder . "/math/editorial.CMP.Archive") |
|
|
( smtpmail-stream-type . starttls ) |
|
|
( smtpmail-smtp-service . 587 ) |
|
|
( mu4e-compose-signature . (concat |
|
|
"Jacopo De Simoi (he · him)\n" |
|
|
"Associate Editor for CMP\n" |
|
|
"Professor · U Toronto\n")))))) |
|
|
|
|
|
;; set `mu4e-context-policy` and `mu4e-compose-policy` to tweak when mu4e should |
|
|
;; guess or ask the correct context, e.g. |
|
|
|
|
|
;; start with the first (default) context; |
|
|
;; default is to ask-if-none (ask when there's no context yet, and none match) |
|
|
(setq mu4e-context-policy 'pick-first) |
|
|
|
|
|
;; compose with the current context is no context matches; |
|
|
;; default is to ask |
|
|
(setq mu4e-compose-context-policy 'pick-first) |
|
|
|
|
|
|
|
|
(add-hook 'mu4e-headers-mode-hook |
|
|
#'(lambda () (setq-local show-trailing-whitespace nil))) |
|
|
(add-hook 'mu4e-view-mode-hook |
|
|
#'(lambda () (setq-local show-trailing-whitespace nil))) |
|
|
(add-hook 'mu4e-compose-mode-hook |
|
|
#'(lambda () |
|
|
(auto-fill-mode -1) |
|
|
(visual-line-mode) |
|
|
;; flyspell does not want to work with buffers that |
|
|
;; start with a "*", but this includes compose buffers |
|
|
;; (that are called *draft*) |
|
|
(let ((flyspell-lazy-disallow-buffers nil)) |
|
|
(flyspell-mode)))) |
|
|
|
|
|
(define-key-alist message-mode-map |
|
|
'(("M-\"" . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter "“" "”" r-begin r-end))))) |
|
|
|
|
|
#+end_src |
|
|
|
|
|
Use ~mbsync~ te fetch emails from server |
|
|
#+begin_src emacs-lisp |
|
|
|
|
|
|
|
|
;; Get mail |
|
|
(setq mu4e-get-mail-command " mbsync math-fast; mbsync proton-capture" |
|
|
mu4e-change-filenames-when-moving t ; needed for mbsync |
|
|
mu4e-headers-include-related nil ;do not include threaded |
|
|
;sent messages in inbox queries |
|
|
) |
|
|
|
|
|
(define-key mu4e-headers-mode-map (kbd "h") 'mu4e-headers-mark-for-refile) |
|
|
(define-key mu4e-headers-mode-map (kbd "h") 'mu4e-headers-mark-for-refile) |
|
|
|
|
|
(define-key mu4e-headers-mode-map (kbd "h") 'mu4e-headers-mark-for-refile) |
|
|
(define-key mu4e-view-mode-map (kbd "h") 'mu4e-view-mark-for-refile) |
|
|
|
|
|
(define-key mu4e-headers-mode-map (kbd "r") 'mu4e-compose-reply) |
|
|
(define-key mu4e-view-mode-map (kbd "r") 'mu4e-compose-reply) |
|
|
|
|
|
(define-key mu4e-headers-mode-map (kbd "f") 'mu4e-compose-forward) |
|
|
(define-key mu4e-headers-mode-map (kbd "C-k") 'mu4e-headers-mark-for-trash) |
|
|
|
|
|
|
|
|
;; Send mail |
|
|
(setq message-send-mail-function 'smtpmail-send-it |
|
|
smtpmail-servers-requiring-authorization ".*" |
|
|
smtpmail-smtp-server "smtp.math.toronto.edu") |
|
|
|
|
|
;(require 'org-mu4e) |
|
|
;;store link to message if in header view, not to header query |
|
|
(setq mu4e-org-link-query-in-headers-mode nil) |
|
|
#+end_src |
|
|
|
|
|
This is supposed to make the calendar invitation buttons work. |
|
|
#+begin_src emacs-lisp |
|
|
(require 'mu4e-icalendar) |
|
|
(mu4e-icalendar-setup) |
|
|
#+end_src |
|
|
|
|
|
This snippet filters stale addresses from address completion; I |
|
|
found this method [[https://emacs.stackexchange.com/questions/47789/how-to-remove-email-address-from-local-database-in-mu4e][here]]. The code loads the definition of ~filter-addresses~ from an external file and checks if ~addr~ is |
|
|
one of the addresses in the list. |
|
|
|
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/save-filter-addresses () |
|
|
"Saves my projects in my home folder." |
|
|
(interactive) |
|
|
(with-temp-buffer |
|
|
(insert (prin1-to-string filter-addresses)) |
|
|
(write-region (point-min) |
|
|
(point-max) |
|
|
"~/.emacs.d/filter-addresses.eld"))) |
|
|
|
|
|
(setq filter-addresses (car (read-from-string (with-temp-buffer |
|
|
(insert-file-contents "~/.emacs.d/filter-addresses.eld") |
|
|
(buffer-string))))) |
|
|
|
|
|
(defun wilder/update-fiter-addresses-regex () |
|
|
(setq filter-addresses-regex |
|
|
(mapconcat #'regexp-quote filter-addresses "\\\|") |
|
|
helm-mu-contacts-ignore-candidates-regexp filter-addresses-regex)) |
|
|
|
|
|
(wilder/update-fiter-addresses-regex) |
|
|
|
|
|
(defun my-mu4e-contact-filter-function (addr) |
|
|
(unless (string-match-p filter-addresses-regex addr) |
|
|
addr)) |
|
|
|
|
|
(setq mu4e-contact-process-function 'my-mu4e-contact-filter-function) |
|
|
|
|
|
(defun wilder/add-address-to-filter (address) |
|
|
(add-to-list 'filter-addresses address) |
|
|
(wilder/save-filter-addresses) |
|
|
(wilder/update-fiter-addresses-regex)) |
|
|
|
|
|
(defun wilder/add-candidate-to-filter (candidate) |
|
|
(wilder/add-address-to-filter (car (split-string candidate "\t")))) |
|
|
|
|
|
(helm-add-action-to-source "Filter away" #'wilder/add-candidate-to-filter helm-source-mu-contacts) |
|
|
|
|
|
|
|
|
#+end_src |
|
|
This snippet improves handling of links that span several lines. |
|
|
It piggy-backs on ~gnus~ doing all the heavy lifting |
|
|
#+begin_src emacs-lisp |
|
|
(defun gnus-next-url () |
|
|
(gnus-text-property-search 'action 'browse-url t t)) |
|
|
|
|
|
(defun gnus-bounds-of-url-at-point () |
|
|
(let ((start (point)) |
|
|
(current-url (get-text-property (point) 'button-data))) |
|
|
(gnus-text-property-search 'action 'browse-url t t t) |
|
|
(while (and (= 1 (- (or (gnus-text-property-search 'action 'browse-url t) 0) (point))) |
|
|
(string-equal current-url (get-text-property (1+ (point)) 'button-data)) ) |
|
|
(forward-char) |
|
|
(gnus-text-property-search 'action 'browse-url t t t)) |
|
|
(cons start (point)))) |
|
|
|
|
|
(defun mu4e--view-activate-urls () |
|
|
"Turn things that look like URLs into clickable things. |
|
|
Also number them so they can be opened using `mu4e-view-go-to-url'." |
|
|
(let ((num 0) |
|
|
(use-gnus-properties mu4e-view-use-gnus)) |
|
|
(save-excursion |
|
|
(setq mu4e--view-link-map ;; buffer local |
|
|
(make-hash-table :size 32 :weakness nil)) |
|
|
(goto-char (point-min)) |
|
|
(while (if use-gnus-properties (gnus-next-url) |
|
|
(re-search-forward mu4e--view-beginning-of-url-regexp nil t)) |
|
|
(let ((bounds (if use-gnus-properties (gnus-bounds-of-url-at-point) |
|
|
(thing-at-point-bounds-of-url-at-point)))) |
|
|
(when bounds |
|
|
(let* ((url (or (get-text-property (car bounds) 'button-data) |
|
|
(thing-at-point-url-at-point))) |
|
|
(ov (make-overlay (car bounds) (cdr bounds)))) |
|
|
(when (string-match-p "https?" url) |
|
|
(puthash (cl-incf num) url mu4e--view-link-map) |
|
|
(add-text-properties |
|
|
(car bounds) |
|
|
(cdr bounds) |
|
|
`(face mu4e-link-face |
|
|
mouse-face highlight |
|
|
mu4e-url ,url |
|
|
keymap ,mu4e-view-active-urls-keymap |
|
|
help-echo |
|
|
"[mouse-1] or [M-RET] to open the link")) |
|
|
(overlay-put ov 'mu4e-overlay t) |
|
|
(overlay-put ov 'after-string |
|
|
(propertize (format "\u200B[%d]" num) |
|
|
'face 'mu4e-url-number-face)))))))))) |
|
|
#+end_src |
|
|
This one kills servers attached to mu4e instances in other |
|
|
activities |
|
|
#+begin_src emacs-lisp |
|
|
(defun mu4e--server-sentinel (proc _msg) |
|
|
"Function called when the server process PROC terminates with MSG." |
|
|
(let ((status (process-status proc)) (code (process-exit-status proc))) |
|
|
(setq mu4e--server-process nil) |
|
|
(setq mu4e--server-buf "") ;; clear any half-received sexps |
|
|
(cond |
|
|
((eq status 'signal) |
|
|
(cond |
|
|
((or(eq code 9) (eq code 2)) (message nil)) |
|
|
;;(message "the mu server process has been stopped")) |
|
|
(t (error (format "mu server process received signal %d" code))))) |
|
|
((eq status 'exit) |
|
|
(cond |
|
|
((eq code 0) |
|
|
(message nil)) ;; don't do anything |
|
|
((eq code 19) |
|
|
(when (string-equal (kde-current-activity-name) server-name) |
|
|
(start-process "killer" nil "killall" "mu")) |
|
|
(error "Database is locked by another process; try again")) |
|
|
(t (error "Mu server process ended with exit code %d" code)))) |
|
|
(t |
|
|
(error "Something bad happened to the mu server process"))))) |
|
|
#+end_src |
|
|
** org-mode |
|
|
*** Require |
|
|
Require the ~org~ package; |
|
|
#+begin_src emacs-lisp |
|
|
(with-eval-after-load "org" |
|
|
(define-key org-src-mode-map (kbd "C-c C-c") 'org-edit-src-exit) |
|
|
(define-key org-src-mode-map (kbd "C-c C-k") nil) ;this conflicts with my LaTeX bindings |
|
|
(define-key org-src-mode-map (kbd "C-c C-`") 'org-edit-src-abort)) |
|
|
|
|
|
(require 'org) |
|
|
(require 'org-habit) |
|
|
#+end_src |
|
|
*** Hooks |
|
|
Enable ~org-inline-pdf~ |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'org-mode-hook #'org-inline-pdf-mode) |
|
|
#+end_src |
|
|
Enable ~auto-fill-mode~ (see [[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Auto-Fill.html][the manual]]]: honestly I do not see |
|
|
where I would not want to use this feature). |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'org-mode-hook 'turn-on-auto-fill) |
|
|
#+end_src |
|
|
It sometimes happen (e.g. when opening files from magit) that |
|
|
the point is in an invisible region; then the usual ~smart-line-beginning~ would get upset. This variation takes care |
|
|
of the problem. |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-smart-line-beginning () |
|
|
(interactive) |
|
|
(when (or (invisible-p (point)) |
|
|
(invisible-p (- (point) 1))) |
|
|
;; assume that we are in a folded headline; move back to heading |
|
|
;; otherwise we will be trapped in the invisible region |
|
|
(org-back-to-heading)) |
|
|
(smart-line-beginning)) |
|
|
(define-key org-mode-map (kbd "C-a") 'org-smart-line-beginning) |
|
|
#+end_src |
|
|
Disable ~truncate-lines~ |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-starting-truncated nil |
|
|
org-startup-folded t) |
|
|
#+end_src |
|
|
*** Helper for quotes |
|
|
#+begin_src emacs-lisp |
|
|
(define-key-alist org-mode-map |
|
|
'(("M-\"" . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter "“" "”" r-begin r-end))))) |
|
|
#+end_src |
|
|
*** Cosmetics |
|
|
Change the default ellipsis ~...~ to the unicode ellipsis ~…~ and |
|
|
set my preferred indentation |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-ellipsis "…" |
|
|
org-adapt-indentation t |
|
|
org-tags-column -75) |
|
|
#+end_src |
|
|
Set the image width to fill the window (this may not be OK for |
|
|
everything, but it works for zettels) |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-image-actual-width '(1.0)) |
|
|
#+end_src |
|
|
*** Ligatures [not in working order] |
|
|
These require a patched ~hackminus~ font. Too bad it does not work |
|
|
for some reason |
|
|
#+begin_src emacs-lisp |
|
|
;; (defun delayed-org-prettify () |
|
|
;; (prettify-symbols-mode 0) |
|
|
;; (run-with-idle-timer 0 nil |
|
|
;; (lambda () |
|
|
;; (prettify-symbols-mode 1) |
|
|
;; (add-to-list 'prettify-symbols-alist '("**" . (? (Br . Bl) ?))) |
|
|
;; (add-to-list 'prettify-symbols-alist '("***" . (? (Br . Bl) ? (Br . Bl) ?))) |
|
|
;; (add-to-list 'prettify-symbols-alist '("****" . (? (Br . Bl) ? (Br . Bl) ? (Br . Bl) ?)))))) |
|
|
;;(add-hook 'org-mode-hook 'delayed-org-prettify) |
|
|
|
|
|
#+end_src |
|
|
*** Links |
|
|
Open links with external stuff |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-file-apps |
|
|
'((auto-mode . emacs) |
|
|
("\\.x?html?\\'" . "xdg-open %s") |
|
|
("\\.djvu\\'" . "xdg-open \"%s\"") |
|
|
("\\.pdf\\'" . "xdg-open \"%s\""))) |
|
|
#+end_src |
|
|
*** Agenda |
|
|
Set the canonical binding for the agenda |
|
|
#+begin_src emacs-lisp |
|
|
(global-set-key (kbd "C-c a") 'org-agenda) |
|
|
#+end_src |
|
|
Define agenda files: the main one is ~master.org~, but then I have |
|
|
a bunch of them on orgzly |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-files |
|
|
'("~/org/master.org" |
|
|
"~/org/orgzly/work.org" |
|
|
"~/org/orgzly/hack.org" |
|
|
"~/org/orgzly/library.org" |
|
|
"~/org/kaizen.org" |
|
|
"~/org/editorial.org" |
|
|
"~/clones/orgzly/open-loops.org" |
|
|
"~/clones/orgzly/refile-git.org" |
|
|
"~/clones/orgzly/calendar.org" |
|
|
"~/clones/orgzly/not-now.org" |
|
|
"~/clones/orgzly/reference.org")) |
|
|
#+end_src |
|
|
Use less verbose reminders: |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-deadline-leaders |
|
|
'("⇒" "@ %3d d. " "! %2d d. ago ") |
|
|
org-agenda-scheduled-leaders |
|
|
'("→" "↻ %2d×") |
|
|
org-agenda-habit-leader "·") |
|
|
#+end_src |
|
|
Default to daily agenda |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-span 1) |
|
|
#+end_src |
|
|
Prevent headlines to be marked as DONE if some sub-headings are |
|
|
still TODO. |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-enforce-todo-dependencies t) |
|
|
#+end_src |
|
|
Do not show scheduled or deadline'd stuff in the Global TODO list |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-todo-ignore-deadlines 'all |
|
|
org-agenda-todo-ignore-scheduled 'all) |
|
|
#+end_src |
|
|
Do not show stuff marked with DONE even if they have a deadline |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-skip-deadline-if-done t) |
|
|
#+end_src |
|
|
Custom separator |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-block-separator ?─) |
|
|
#+end_src |
|
|
Turn on speed keys |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-use-speed-commands t) |
|
|
#+end_src |
|
|
Customize the agenda interface a bit |
|
|
- add a newline before habits |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/insert-before-first (pred el list &optional base) |
|
|
"Returns a list in which EL is inserted before the first occurrence in LIST satisfying PRED" |
|
|
(if (not list) |
|
|
base |
|
|
(if (funcall pred (car list)) |
|
|
(append base (list el) list) |
|
|
(wilder/insert-before-first pred el (cdr list) (nconc base (list (car list))))))) |
|
|
|
|
|
(defun org-agenda-finalize-entries (list &optional type) |
|
|
"Sort, limit and concatenate the LIST of agenda items. |
|
|
The optional argument TYPE tells the agenda type." |
|
|
(setq org-planner list) |
|
|
(let ((max-effort (cond ((listp org-agenda-max-effort) |
|
|
(cdr (assoc type org-agenda-max-effort))) |
|
|
(t org-agenda-max-effort))) |
|
|
(max-todo (cond ((listp org-agenda-max-todos) |
|
|
(cdr (assoc type org-agenda-max-todos))) |
|
|
(t org-agenda-max-todos))) |
|
|
(max-tags (cond ((listp org-agenda-max-tags) |
|
|
(cdr (assoc type org-agenda-max-tags))) |
|
|
(t org-agenda-max-tags))) |
|
|
(max-entries (cond ((listp org-agenda-max-entries) |
|
|
(cdr (assoc type org-agenda-max-entries))) |
|
|
(t org-agenda-max-entries)))) |
|
|
(when org-agenda-before-sorting-filter-function |
|
|
(setq list |
|
|
(delq nil |
|
|
(mapcar |
|
|
org-agenda-before-sorting-filter-function list)))) |
|
|
(setq list (mapcar #'org-agenda-highlight-todo list) |
|
|
list (mapcar #'identity (sort list #'org-entries-lessp))) |
|
|
(when max-effort |
|
|
(setq list (org-agenda-limit-entries |
|
|
list 'effort-minutes max-effort |
|
|
(lambda (e) (or e (if org-agenda-sort-noeffort-is-high |
|
|
32767 -1)))))) |
|
|
(when max-todo |
|
|
(setq list (org-agenda-limit-entries list 'todo-state max-todo))) |
|
|
(when max-tags |
|
|
(setq list (org-agenda-limit-entries list 'tags max-tags))) |
|
|
(when max-entries |
|
|
(setq list (org-agenda-limit-entries list 'org-hd-marker max-entries))) |
|
|
(when (and org-agenda-dim-blocked-tasks org-blocker-hook) |
|
|
(setq list (mapcar #'org-agenda--mark-blocked-entry list))) |
|
|
; this bit adds a blank line before the first habit |
|
|
(setq list |
|
|
(wilder/insert-before-first #'(lambda (string) |
|
|
(get-text-property 0 'org-habit-p string)) |
|
|
"" list)) |
|
|
|
|
|
(mapconcat #'identity list "\n"))) |
|
|
|
|
|
#+end_src |
|
|
- Sleeker time-grid |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-time-grid '((daily today require-timed remove-match) |
|
|
(800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000) |
|
|
" ······· " "───────────────") |
|
|
org-agenda-current-time-string "····· now ·····" |
|
|
org-agenda-time-leading-zero t) |
|
|
#+end_src |
|
|
- Use figlet-type fonts to display the date |
|
|
#+begin_src emacs-lisp |
|
|
(setq figlet |
|
|
[["███" "██ " "███" "███" "█ █" "███" "███" "███" "███" "███"] |
|
|
["█ █" " █ " " █" " █" "█ █" "█ " "█ " " █" "█ █" "█ █"] |
|
|
["█ █" " █ " "███" "███" "███" "███" "███" " █" "███" "███"] |
|
|
["█ █" " █ " "█ " " █" " █" " █" "█ █" " █" "█ █" " █"] |
|
|
["███" "███" "███" "███" " █" "███" "███" " █" "███" "███"]]) |
|
|
|
|
|
(setq figlet-lean |
|
|
[["┌──┐" "╶┐ " "╶──┐" "╶──┐" "╷ ╷" "┌──╴" "┌──╴" "╶──┐" "┌──┐" "┌──┐"] |
|
|
["│ │" " │ " " │" " │" "│ │" "│ " "│ " " │" "│ │" "│ │"] |
|
|
["│ │" " │ " "┌──┘" "╶──┤" "└──┤" "└──┐" "├──┐" " │" "├──┤" "└──┤"] |
|
|
["│ │" " │ " "│ " " │" " │" " │" "│ │" " │" "│ │" " │"] |
|
|
["└──┘" "╶┴╴" "└──╴" "╶──┘" " ╵" "╶──┘" "└──┘" " ╵" "└──┘" "╶──┘"]]) |
|
|
|
|
|
(defun figlet-digit (digit row) |
|
|
(aref (aref figlet-lean row) digit)) |
|
|
|
|
|
(defun figlet-num (number row) |
|
|
(let ((n number) |
|
|
(d (list))) |
|
|
(while (> n 0) |
|
|
(setq d (append (list (mod n 10)) d)) |
|
|
(setq n (/ n 10))) |
|
|
(string-join (mapcar |
|
|
(lambda (digit) (concat (figlet-digit digit row) " ")) |
|
|
d)))) |
|
|
(defun org-agenda-format-date-figlet (date) |
|
|
"Format a DATE string for display in the daily/weekly agenda. |
|
|
This function makes sure that dates are aligned for easy reading." |
|
|
;(require 'cal-iso) |
|
|
(if (not (eq 'day org-agenda-current-span)) |
|
|
(concat "\n" (org-agenda-format-date-aligned date) |
|
|
" ────────────────────────────────────────────────────\n") |
|
|
(let* ((dayname (calendar-day-name date)) |
|
|
(day (cadr date)) |
|
|
(day-of-week (calendar-day-of-week date)) |
|
|
(month (car date)) |
|
|
(monthname (calendar-month-name month)) |
|
|
(year (nth 2 date))nf |
|
|
(iso-week (org-days-to-iso-week |
|
|
(calendar-absolute-from-gregorian date))) |
|
|
(weekyear (cond ((and (= month 1) (>= iso-week 52)) |
|
|
(1- year)) |
|
|
((and (= month 12) (<= iso-week 1)) |
|
|
(1+ year)) |
|
|
(t year))) |
|
|
(weekstring (if (= day-of-week 1) |
|
|
(format " W%02d" iso-week) |
|
|
""))) |
|
|
|
|
|
(format (concat "\n" |
|
|
"%9s %s %4d%s\n" |
|
|
"%9s \n" |
|
|
"%9s %-10s\n" |
|
|
"%9s \n" |
|
|
"%9s %s\n") |
|
|
(figlet-num day 0) monthname year weekstring |
|
|
(figlet-num day 1) |
|
|
(figlet-num day 2) dayname |
|
|
(figlet-num day 3) |
|
|
(figlet-num day 4) (sunrise-sunset))))) |
|
|
|
|
|
(setq org-agenda-format-date #'org-agenda-format-date-figlet) |
|
|
(setq org-agenda-prefix-format |
|
|
'((agenda . " %i %6c %s %?-12t") |
|
|
(todo . " %i %6c · ") |
|
|
(tags . " %i %6c · ") |
|
|
(search . " %i %6c · "))) |
|
|
|
|
|
(advice-add 'org-agenda-get-scheduled :around |
|
|
(lambda (orig-fun &rest args) |
|
|
"Only show habits on day wiew, not on week view" |
|
|
(let ((org-habit-show-habits |
|
|
(with-current-buffer org-agenda-buffer (eq org-agenda-current-span 'day)))) |
|
|
(apply orig-fun args)))) |
|
|
#+end_src |
|
|
- Add a whiteline after the header Now, this is a horrible hack: |
|
|
the string that forms the header is obtained by constructing |
|
|
some pretty-text in a temporary buffer and then retrieving the |
|
|
string with #'buffer-string Hence we add (and remove) an advice |
|
|
to the function buffer-string that concat's a newline around |
|
|
each function that produces the headers |
|
|
#+begin_src emacs-lisp |
|
|
(defun concat-newline (s) |
|
|
(concat s "\n")) |
|
|
|
|
|
(dolist (fun '(org-tags-view org-todo-list)) |
|
|
(advice-add fun :around |
|
|
(lambda (orig-fun &rest args) |
|
|
(advice-add #'buffer-string :filter-return #'concat-newline) |
|
|
(apply orig-fun args) |
|
|
(advice-remove #'buffer-string #'concat-newline)))) |
|
|
#+end_src |
|
|
#+begin_src emacs-lisp |
|
|
(advice-add #'org-agenda--insert-overriding-header :filter-args |
|
|
(lambda (params) |
|
|
(list `(concat ,(car params) "\n")))) |
|
|
#+end_src |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/delta-list (l op) |
|
|
(unless (< (length l) 2) |
|
|
(cons (funcall op (car l) (cadr l)) (wilder/delta-list (cdr l) op)))) |
|
|
|
|
|
(defun wilder/truncate-list-when (l predicate) |
|
|
(unless (funcall predicate (car l)) |
|
|
(cons (car l) (wilder/truncate-list-when (cdr l) predicate)))) |
|
|
|
|
|
(defun wilder/habit-streak (habit) |
|
|
(let* ((done-dates (sort (org-habit-done-dates habit) #'>)) |
|
|
(dead-rep (org-habit-deadline-repeat habit)) |
|
|
(sched-rep (org-habit-scheduled-repeat habit)) |
|
|
(today-date (time-to-days nil)) |
|
|
(done-list (append (list today-date) done-dates '(0))) |
|
|
(delta-list (wilder/delta-list done-list #'-)) |
|
|
(truncated-list (wilder/truncate-list-when delta-list (lambda (x) (> x dead-rep))))) |
|
|
(list (length truncated-list) |
|
|
(list (car delta-list) sched-rep dead-rep)))) |
|
|
|
|
|
#+end_src |
|
|
Improve the org-habit graph display |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-habit-regular-glyph ?□ |
|
|
org-habit-today-glyph ? |
|
|
org-habit-completed-glyph ?▣ |
|
|
org-habit-completed-today-glyph ? |
|
|
org-habit-preceding-days 6 |
|
|
org-habit-graph-column 62) |
|
|
|
|
|
(defun wilder/org-habit-build-graph (habit starting current ending) |
|
|
"Build a graph for the given HABIT, from STARTING to ENDING. |
|
|
CURRENT gives the current time between STARTING and ENDING, for |
|
|
the purpose of drawing the graph. It need not be the actual |
|
|
current time." |
|
|
(let* ((all-done-dates (sort (org-habit-done-dates habit) #'<)) |
|
|
(done-dates all-done-dates) |
|
|
(scheduled (org-habit-scheduled habit)) |
|
|
(s-repeat (org-habit-scheduled-repeat habit)) |
|
|
(start (time-to-days starting)) |
|
|
(now (time-to-days current)) |
|
|
(end (time-to-days ending)) |
|
|
(graph (make-string (- end start) org-habit-regular-glyph)) |
|
|
(index 0) |
|
|
last-done-date) |
|
|
(while (and done-dates (< (car done-dates) start)) |
|
|
(setq last-done-date (car done-dates) |
|
|
done-dates (cdr done-dates))) |
|
|
(while (< start end) |
|
|
(let* ((in-the-past-p (< start now)) |
|
|
(todayp (= start now)) |
|
|
(donep (and done-dates (= start (car done-dates)))) |
|
|
(faces |
|
|
(if (and in-the-past-p |
|
|
(not last-done-date) |
|
|
(not (< scheduled now))) |
|
|
(if (and all-done-dates (= (car all-done-dates) start)) |
|
|
;; This is the very first done of this habit. |
|
|
'(org-habit-ready-face . org-habit-ready-future-face) |
|
|
'(org-habit-clear-face . org-habit-clear-future-face)) |
|
|
(org-habit-get-faces |
|
|
habit start |
|
|
(and in-the-past-p |
|
|
last-done-date |
|
|
;; Compute scheduled time for habit at the time |
|
|
;; START was current. |
|
|
(let ((type (org-habit-repeat-type habit))) |
|
|
(cond |
|
|
;; At the last done date, use current |
|
|
;; scheduling in all cases. |
|
|
((null done-dates) scheduled) |
|
|
((equal type ".+") (+ last-done-date s-repeat)) |
|
|
((equal type "+") |
|
|
;; Since LAST-DONE-DATE, each done mark |
|
|
;; shifted scheduled date by S-REPEAT. |
|
|
(- scheduled (* (length done-dates) s-repeat))) |
|
|
(t |
|
|
;; Compute the scheduled time after the |
|
|
;; first repeat. This is the closest time |
|
|
;; past FIRST-DONE which can reach SCHEDULED |
|
|
;; by a number of S-REPEAT hops. |
|
|
;; |
|
|
;; Then, play TODO state change history from |
|
|
;; the beginning in order to find current |
|
|
;; scheduled time. |
|
|
(let* ((first-done (car all-done-dates)) |
|
|
(s (let ((shift (mod (- scheduled first-done) |
|
|
s-repeat))) |
|
|
(+ (if (= shift 0) s-repeat shift) |
|
|
first-done)))) |
|
|
(if (= first-done last-done-date) s |
|
|
(catch :exit |
|
|
(dolist (done (cdr all-done-dates) s) |
|
|
;; Each repeat shifts S by any |
|
|
;; number of S-REPEAT hops it takes |
|
|
;; to get past DONE, with a minimum |
|
|
;; of one hop. |
|
|
(cl-incf s (* (1+ (/ (max (- done s) 0) |
|
|
s-repeat)) |
|
|
s-repeat)) |
|
|
(when (= done last-done-date) |
|
|
(throw :exit s)))))))))) |
|
|
donep))) |
|
|
markedp face) |
|
|
(cond |
|
|
(donep |
|
|
(aset graph index (if todayp org-habit-completed-today-glyph org-habit-completed-glyph)) |
|
|
(setq markedp t) |
|
|
(while (and done-dates (= start (car done-dates))) |
|
|
(setq last-done-date (car done-dates)) |
|
|
(setq done-dates (cdr done-dates)))) |
|
|
(todayp |
|
|
(aset graph index org-habit-today-glyph))) |
|
|
(setq face (if (or in-the-past-p todayp) |
|
|
(car faces) |
|
|
(cdr faces))) |
|
|
(when (and in-the-past-p |
|
|
(not (eq face 'org-habit-overdue-face)) |
|
|
(not markedp)) |
|
|
(setq face (cdr faces))) |
|
|
(put-text-property index (1+ index) 'face face graph) |
|
|
) |
|
|
(setq start (1+ start) |
|
|
index (1+ index))) |
|
|
|
|
|
(let* ((streak (wilder/habit-streak habit)) |
|
|
(streak-current (car streak)) |
|
|
(streak-current-stats (nth 1 streak)) |
|
|
(current-ok (< (car streak-current-stats) (nth 1 streak-current-stats))) |
|
|
(current-on-deadline (= (car streak-current-stats) (nth 2 streak-current-stats))) |
|
|
(streak-face (if (eq 0 streak-current) 'font-lock-comment-face |
|
|
(if current-on-deadline 'org-habit-overdue-face |
|
|
(if current-ok 'org-habit-ready-face |
|
|
'org-habit-alert-face)))) |
|
|
(streak-string (format "%4d" streak-current))) |
|
|
(put-text-property 0 4 'face streak-face streak-string) |
|
|
(concat streak-string "·" graph)))) |
|
|
|
|
|
(advice-add 'org-habit-build-graph :override #'wilder/org-habit-build-graph) |
|
|
#+end_src |
|
|
Ideally I should tag some tasks as “break” tasks, which are |
|
|
suitable to be taken care of during a pomodoro break. Such tasks |
|
|
should be marked with tags ~:5m:~ and ~:20m:~ according to the |
|
|
estimate on the time it would take to take care of them |
|
|
|
|
|
Is it possible to edit the default agenda views? |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-agenda-custom-commands |
|
|
'( |
|
|
("a" "Main agenda" |
|
|
((agenda "") |
|
|
(todo "NEXT|ONGOING") |
|
|
(tags-todo "hack") |
|
|
(tags-todo "5m") |
|
|
(todo "TODO"))) |
|
|
("h" "Just the agenda" |
|
|
((agenda ""))) |
|
|
("5" "Agenda and Break tasks" |
|
|
((agenda "") |
|
|
(tags-todo "5m") |
|
|
(tags-todo "20m"))))) |
|
|
#+end_src |
|
|
|
|
|
Custom agenda view |
|
|
- cleanup stuff (TODO, what is this doing, exactly? ) |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-agenda-prepare (&optional name) |
|
|
(let ((filter-alist (if org-agenda-persistent-filter |
|
|
(with-current-buffer |
|
|
(get-buffer-create org-agenda-buffer-name) |
|
|
(list `(tag . ,org-agenda-tag-filter) |
|
|
`(re . ,org-agenda-regexp-filter) |
|
|
`(effort . ,org-agenda-effort-filter) |
|
|
`(cat . ,org-agenda-category-filter)))))) |
|
|
(if (org-agenda-use-sticky-p) |
|
|
(progn |
|
|
(put 'org-agenda-tag-filter :preset-filter nil) |
|
|
(put 'org-agenda-category-filter :preset-filter nil) |
|
|
(put 'org-agenda-regexp-filter :preset-filter nil) |
|
|
;; Popup existing buffer |
|
|
(org-agenda-prepare-window (get-buffer org-agenda-buffer-name) |
|
|
filter-alist) |
|
|
(message "Sticky Agenda buffer, use `r' to refresh") |
|
|
(or org-agenda-multi (org-agenda-fit-window-to-buffer)) |
|
|
(throw 'exit "Sticky Agenda buffer, use `r' to refresh")) |
|
|
(setq org-todo-keywords-for-agenda nil) |
|
|
(put 'org-agenda-tag-filter :preset-filter |
|
|
org-agenda-tag-filter-preset) |
|
|
(put 'org-agenda-category-filter :preset-filter |
|
|
org-agenda-category-filter-preset) |
|
|
(put 'org-agenda-regexp-filter :preset-filter |
|
|
org-agenda-regexp-filter-preset) |
|
|
(put 'org-agenda-effort-filter :preset-filter |
|
|
org-agenda-effort-filter-preset) |
|
|
(if org-agenda-multi |
|
|
(progn |
|
|
(setq buffer-read-only nil) |
|
|
(goto-char (point-max)) |
|
|
(unless (or (bobp) org-agenda-compact-blocks |
|
|
(not org-agenda-block-separator)) |
|
|
(insert "\n" |
|
|
(if (stringp org-agenda-block-separator) |
|
|
org-agenda-block-separator |
|
|
(make-string (window-width) org-agenda-block-separator)) |
|
|
"\n\n")) |
|
|
(narrow-to-region (point) (point-max))) |
|
|
(setq org-done-keywords-for-agenda nil) |
|
|
;; Setting any variables that are in org-agenda-local-vars |
|
|
;; list need to be done after the prepare call |
|
|
(org-agenda-prepare-window |
|
|
(get-buffer-create org-agenda-buffer-name) filter-alist) |
|
|
(setq buffer-read-only nil) |
|
|
(org-agenda-reset-markers) |
|
|
(let ((inhibit-read-only t)) (erase-buffer)) |
|
|
(org-agenda-mode) |
|
|
(setq org-refile-targets '((nil . (:maxlevel . 5)) |
|
|
(org-agenda-files . (:maxlevel . 1)))) |
|
|
(setq org-refile-use-outline-path 'file |
|
|
org-outline-path-complete-in-steps nil) |
|
|
(setq org-agenda-buffer (current-buffer)) |
|
|
(setq org-agenda-contributing-files nil) |
|
|
(setq org-agenda-columns-active nil) |
|
|
(org-agenda-prepare-buffers (org-agenda-files nil 'ifmode)) |
|
|
(setq org-todo-keywords-for-agenda |
|
|
(org-uniquify org-todo-keywords-for-agenda)) |
|
|
(setq org-done-keywords-for-agenda |
|
|
(org-uniquify org-done-keywords-for-agenda)) |
|
|
(setq org-agenda-last-prefix-arg current-prefix-arg) |
|
|
(setq org-agenda-this-buffer-name org-agenda-buffer-name) |
|
|
(and name (not org-agenda-name) |
|
|
(setq-local org-agenda-name name))) |
|
|
(setq buffer-read-only nil)))) |
|
|
|
|
|
(add-hook 'org-agenda-mode-hook |
|
|
(lambda () (setq truncate-lines t |
|
|
show-trailing-whitespace nil))) |
|
|
|
|
|
#+end_src |
|
|
*** Automate saving |
|
|
Save all org buffers with the auto-save-hook. Avoid saving the |
|
|
buffer that is currently being edited; doing so removes trailing |
|
|
whitespace, which is undesirable while writing. |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-save-all-org-buffers-except-current () |
|
|
"Save all Org buffers without user confirmation." |
|
|
(interactive) |
|
|
(message "Saving all Org buffers...") |
|
|
(let ((buffer (current-buffer))) |
|
|
(save-some-buffers t (lambda () (and (not (eq (current-buffer) buffer)) (derived-mode-p 'org-mode))))) |
|
|
(when (featurep 'org-id) (org-id-locations-save)) |
|
|
(message "Saving all Org buffers... done")) |
|
|
|
|
|
(add-hook 'auto-save-hook 'org-save-all-org-buffers-except-current) |
|
|
#+end_src |
|
|
Also, we need to trigger saving once the state changes, so that we |
|
|
are always able to sync the right state; saving runs with a timer |
|
|
so that it is not triggered e.g. when cycling through states. The |
|
|
problem with the current implementation is that we do not know |
|
|
which buffer has been modified if the state has been changed using |
|
|
the agenda, which is kind of the point… |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'org-after-todo-state-change-hook |
|
|
(lambda () |
|
|
(when buffer-file-name (save-buffer-with-timer 5)))) |
|
|
#+end_src |
|
|
*** cycling-headlines |
|
|
This bit is useful to cycle headings in files; the interesting |
|
|
variable should be buffer-local |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-heading-cycle-list nil) |
|
|
|
|
|
(defun next-heading-in-cycle-list (heading cycle-list) |
|
|
(when cycle-list |
|
|
(or (next-heading-in-cycle heading (car cycle-list)) |
|
|
(next-heading-in-cycle-list heading (cdr cycle-list))))) |
|
|
|
|
|
(defun next-heading-in-cycle (heading cycle) |
|
|
"Returns the next heading in the cycle or nil if none" |
|
|
(let ((pos (cl-position heading cycle :test 'equal)) |
|
|
(len (length cycle))) |
|
|
(when pos |
|
|
(let ((next (mod (+ 1 pos) len))) |
|
|
(nth next cycle))))) |
|
|
|
|
|
(defun wilder/org-cycle-headlines () |
|
|
(when (equal org-state "DONE") |
|
|
(let* ((current-heading (org-entry-get nil "ITEM")) |
|
|
(next-heading (next-heading-in-cycle-list current-heading org-heading-cycle-list))) |
|
|
(when next-heading |
|
|
(org-edit-headline next-heading))))) |
|
|
#+end_src |
|
|
|
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'org-after-todo-state-change-hook |
|
|
#'wilder/org-cycle-headlines) |
|
|
#+end_src |
|
|
|
|
|
*** Reverting stuff from orgzly |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-revert-all-orgzly-buffers (&optional ALL) |
|
|
"Revert all Org buffers that are sitting in orgzly |
|
|
Prompt for confirmation when there are unsaved changes. Be sure |
|
|
you know what you are doing before letting this function |
|
|
overwrite your changes." |
|
|
(interactive) |
|
|
(message "Reverting Orgzly buffers…") |
|
|
; pull from remote |
|
|
(let ((default-directory "~/clones/orgzly")) |
|
|
(or (eq 0 (shell-command "git pull origin master")) (call-interactively #'magit-status))) |
|
|
(save-excursion |
|
|
(save-window-excursion |
|
|
(dolist (b (buffer-list)) |
|
|
(when (and (with-current-buffer b (derived-mode-p 'org-mode)) |
|
|
(with-current-buffer b buffer-file-name) |
|
|
(with-current-buffer b (string-match-p "orgzly" default-directory))) |
|
|
(pop-to-buffer-same-window b) |
|
|
(revert-buffer t 'no-confirm))))) |
|
|
|
|
|
(org-agenda-redo-all)) |
|
|
(define-key org-agenda-mode-map (kbd "Z") 'org-revert-all-orgzly-buffers) |
|
|
#+end_src |
|
|
Use gac |
|
|
#+begin_src emacs-lisp |
|
|
(require 'git-auto-commit-mode) |
|
|
(setq gac-automatically-push-p t) |
|
|
#+end_src |
|
|
*** Hydras for timestamps |
|
|
#+begin_src emacs-lisp |
|
|
(defun hydra-timestamp/hl-paren-force-fix () |
|
|
;;; This is needed b/c hl-parent caches the point position and |
|
|
;;; refuses to update if the point did not move |
|
|
(let ((hl-paren-last-point -1)) |
|
|
(hl-paren-highlight))) |
|
|
(defun hydra-timestamp/pre () |
|
|
(unless (boundp 'hydra-timestamp/hpm) |
|
|
(highlight-parentheses-mode 1)) |
|
|
(setq hydra-timestamp/hpm t)) |
|
|
(defun hydra-timestamp/post () |
|
|
(highlight-parentheses-mode -1) |
|
|
(makunbound 'hydra-timestamp/hpm)) |
|
|
(global-set-key |
|
|
(kbd "C-α") |
|
|
(defhydra hydra-timestamp |
|
|
(:pre hydra-timestamp/pre :post hydra-timestamp/post) |
|
|
"paren slurp and barf" |
|
|
("i" (progn |
|
|
(org-timestamp-up-day) |
|
|
(hydra-timestamp/hl-paren-force-fix)) "+1D") |
|
|
("n" (progn |
|
|
(org-timestamp-down-day) |
|
|
(hydra-timestamp/hl-paren-force-fix)) "-1D") |
|
|
("t" (progn |
|
|
(org-timestamp-up-day 7) |
|
|
(hydra-timestamp/hl-paren-force-fix)) "+1W") |
|
|
("r" (progn |
|
|
(org-timestamp-down-day 7) |
|
|
(hydra-timestamp/hl-paren-force-fix)) "-1W"))) |
|
|
#+end_src |
|
|
*** Hydras for refiling |
|
|
This bit evolved from [[https://www.mollermara.com/blog/Fast-refiling-in-org-mode-with-hydras/][this post]]; see more at [[https://gist.github.com/mm--/60e0790bcbf8447160cc87a66dc949ab ][this gist]]. |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/refile (file headline &optional arg) |
|
|
(let ((pos (save-excursion |
|
|
(find-file file) |
|
|
(org-find-exact-headline-in-buffer headline)))) |
|
|
(org-refile arg nil (list headline file nil pos))) |
|
|
(switch-to-buffer (current-buffer))) |
|
|
#+end_src |
|
|
#+begin_src emacs-lisp |
|
|
(defhydra wilder/hydra-org-refile (:foreign-keys run) |
|
|
"Refile" |
|
|
("g" (wilder/refile "hack.org" "emacs") "Refile to Hack-emacs") |
|
|
("q" nil "cancel")) |
|
|
(define-key org-mode-map (kbd "C-x \\") 'wilder/hydra-org-refile/body) |
|
|
#+end_src |
|
|
*** Hydras for moving headlines around |
|
|
#+begin_src emacs-lisp |
|
|
(defhydra wilder/hydra-org-move () |
|
|
"Move subtree" |
|
|
("p" outline-previous-visible-heading "move up" :column "Movement") |
|
|
("n" outline-next-visible-heading "move down") |
|
|
("r" outline-up-heading "level up") |
|
|
("f" org-promote-subtree "promote" :column "Bubble") |
|
|
("s" org-demote-subtree "demote") |
|
|
("i" org-move-subtree-up "bubble up") |
|
|
("e" org-move-subtree-down "bubble down") |
|
|
("h" org-archive-subtree "archive" :column "Edit") |
|
|
("d" kill-line "kill") |
|
|
("q" nil "cancel")) |
|
|
(define-key org-mode-map (kbd "C-x SPC") 'wilder/hydra-org-move/body) |
|
|
#+end_src |
|
|
|
|
|
*** tags-to-filename |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-tags-to-filenames () |
|
|
(message "parsing") |
|
|
(sleep-for 1) |
|
|
(let ((tags (car (last (org-heading-components))))) |
|
|
(when (stringp tags) |
|
|
(remove nil (mapcar |
|
|
(lambda (tag) |
|
|
(cdr (assoc tag tag-to-filenames-alist))) |
|
|
(org-split-string tags ":")))))) |
|
|
#+end_src |
|
|
|
|
|
*** Capture |
|
|
Set default keybinding |
|
|
#+begin_src emacs-lisp |
|
|
(global-set-key (kbd "C-c c") 'org-capture) |
|
|
#+end_src |
|
|
This is my capture template: it needs to be revised as I really do not use |
|
|
The Idea, journal and break entry |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-default-notes-file "~/org/master.org") |
|
|
(setq wilder/org-calendar-file "~/clones/orgzly/calendar.org") |
|
|
(setq org-capture-templates |
|
|
`(("t" "TODO today" entry (file+headline ,wilder/org-calendar-file "Tasks") |
|
|
"* TODO %?\n SCHEDULED: %t\n %a" :clock-in t :clock-resume t) |
|
|
("N" "DOING right now" entry (file+headline ,wilder/org-calendar-file "Tasks") |
|
|
"* ONGOING %?\n SCHEDULED: %T\n %a" :clock-in t :clock-keep t) |
|
|
("n" "TODO next" entry (file+headline ,wilder/org-calendar-file "Tasks") |
|
|
"* NEXT %?\n " :clock-in t :clock-resume t) |
|
|
("T" "TODO" entry (file+headline ,wilder/org-calendar-file "Tasks") |
|
|
"* TODO %?\n %a" :clock-in t :clock-resume t) |
|
|
("M" "TENTATIVE" entry (file+headline ,wilder/org-calendar-file "Tasks") |
|
|
"* TENTATIVE %?\n %a" :clock-in t :clock-resume t) |
|
|
|
|
|
("i" "Idea" entry (file+headline "~/org/master.org" "Ideas") |
|
|
"* IDEA %?\n %u" :clock-in t :clock-resume t ) |
|
|
("j" "Journal" entry (file+datetree "~/org/master.org") |
|
|
"* %?\n%U\n" :clock-in t :clock-resume t) |
|
|
("b" "Break" entry (file+datetree "~/org/master.org") |
|
|
"* break %?\n" :clock-in t :clock-resume t) |
|
|
("e" "Mail To" entry (file+headline ,wilder/org-calendar-file "E-mails") |
|
|
"* DONE mailto:%?") |
|
|
("R" "Reply" entry (file+headline ,wilder/org-calendar-file "E-Mails") |
|
|
"* TODO Reply to %:from \nSCHEDULED: %(org-insert-time-stamp |
|
|
(org-read-date nil t \"+0d\"))\n%a\n%?\n") |
|
|
("z" "Zoom meeting" entry (file+headline ,wilder/org-calendar-file "Tasks") |
|
|
"* TODO Attend %(format-zoom-meeting)\n%a" :immediate-finish t))) |
|
|
#+end_src |
|
|
*** LaTeX export |
|
|
#+begin_src emacs-lisp |
|
|
; (add-to-list 'org-latex-classes |
|
|
; '("amsart" "\\documentclass[11pt]{amsart}" |
|
|
; ("\\section{%s}" . "\\section*{%s}") |
|
|
; ("\\subsection{%s}" . "\\subsection*{%s}") |
|
|
; ("\\subsubsection{%s}" . "\\subsubsection*{%s}") |
|
|
; ("\\paragraph{%s}" . "\\paragraph*{%s}") |
|
|
; ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))) |
|
|
#+end_src |
|
|
*** Source blocks |
|
|
Add template for a source block in a few selected languages; for |
|
|
now I have |
|
|
- emacs-lisp |
|
|
- shell |
|
|
. This is useful for writing the emacs init file in literate form, |
|
|
or dotfiles for other code. Since Org 9.2, the behavior that I |
|
|
grew accustomed to (e.g. ~<el TAB~) )is implemented by ~org-tempo~, so I need to require that as well. |
|
|
#+begin_src emacs-lisp |
|
|
(dolist (structure-template '(("el" . "src emacs-lisp") |
|
|
("sh" . "src sh") |
|
|
("ss" . "src scheme") |
|
|
("py" . "src python") |
|
|
("χ" . "src LaTeX"))) |
|
|
(add-to-list 'org-structure-template-alist structure-template)) |
|
|
|
|
|
(require 'org-tempo) |
|
|
#+end_src |
|
|
Fontify src blocks |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-src-fontify-natively t) |
|
|
#+end_src |
|
|
This makes scheme src blocks work |
|
|
#+begin_src emacs-lisp |
|
|
(org-babel-do-load-languages |
|
|
'org-babel-load-languages |
|
|
'((python . t) |
|
|
(scheme . t) |
|
|
(emacs-lisp . t))) |
|
|
#+end_src |
|
|
Patch ~ob-scheme.el~ while waiting for upstream |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-babel-scheme-execute-with-geiser (code output impl repl) |
|
|
"Execute code in specified REPL. |
|
|
If the REPL doesn't exist, create it using the given scheme |
|
|
implementation. |
|
|
|
|
|
Returns the output of executing the code if the OUTPUT parameter |
|
|
is true; otherwise returns the last value." |
|
|
|
|
|
(let ((result nil)) |
|
|
(with-temp-buffer |
|
|
(insert (format ";; -*- geiser-scheme-implementation: %s -*-" impl)) |
|
|
(newline) |
|
|
(insert code) |
|
|
(geiser-mode) |
|
|
(let ((geiser-repl-window-allow-split nil) |
|
|
(geiser-repl-use-other-window nil)) |
|
|
(let ((repl-buffer (save-current-buffer |
|
|
(org-babel-scheme-get-repl impl repl)))) |
|
|
(when (not (eq impl (org-babel-scheme-get-buffer-impl |
|
|
(current-buffer)))) |
|
|
(message "Implementation mismatch: %s (%s) %s (%s)" impl (symbolp impl) |
|
|
(org-babel-scheme-get-buffer-impl (current-buffer)) |
|
|
(symbolp (org-babel-scheme-get-buffer-impl |
|
|
(current-buffer))))) |
|
|
(setq geiser-repl--repl repl-buffer) |
|
|
(setq geiser-impl--implementation nil) |
|
|
(let ((geiser-debug-jump-to-debug-p nil) |
|
|
(geiser-debug-show-debug-p nil)) |
|
|
(let ((ret (funcall |
|
|
;; use `geiser-eval-region/wait' only when available |
|
|
;; in newer versions of `geiser' |
|
|
(if (fboundp 'geiser-eval-region/wait) |
|
|
'geiser-eval-region/wait |
|
|
'geiser-eval-region) |
|
|
(point-min) |
|
|
(point-max)))) |
|
|
(setq result (if output |
|
|
(or (geiser-eval--retort-output ret) |
|
|
"Geiser Interpreter produced no output") |
|
|
(geiser-eval--retort-result-str ret ""))))) |
|
|
(when (not repl) |
|
|
(save-current-buffer (set-buffer repl-buffer) |
|
|
(geiser-repl-exit)) |
|
|
(set-process-query-on-exit-flag (get-buffer-process repl-buffer) nil) |
|
|
(kill-buffer repl-buffer))))) |
|
|
result)) |
|
|
#+end_src |
|
|
*** Lowercase org blocks |
|
|
There is a new trend to use lowercase in the org block |
|
|
definitions; this gist (taken from the [[https://scripter.co/org-keywords-lower-case/][Scripter blog]]) lowercases |
|
|
all instances in the current buffer |
|
|
#+begin_src emacs-lisp |
|
|
(defun modi/lower-case-org-keywords () |
|
|
"Lower case Org keywords and block identifiers. |
|
|
|
|
|
Example: \"#+TITLE\" → \"#+title\" |
|
|
\"#+BEGIN_EXAMPLE\" → \"#+begin_example\" |
|
|
|
|
|
Inspiration: |
|
|
https://code.orgmode.org/bzg/org-mode/commit/13424336a6f30c50952d291e7a82906c1210daf0." |
|
|
(interactive) |
|
|
(save-excursion |
|
|
(goto-char (point-min)) |
|
|
(let ((case-fold-search nil) |
|
|
(count 0)) |
|
|
;; Match examples: "#+foo bar", "#+foo:", "=#+foo=", "~#+foo~", |
|
|
;; "‘#+foo’", "“#+foo”", ",#+foo bar", |
|
|
;; "#+FOO_bar<eol>", "#+FOO<eol>". |
|
|
(while (re-search-forward "\\(?1:#\\+[A-Z_]+\\(?:_[[:alpha:]]+\\)*\\)\\(?:[ :=~’”]\\|$\\)" nil :noerror) |
|
|
(setq count (1+ count)) |
|
|
(replace-match (downcase (match-string-no-properties 1)) :fixedcase nil nil 1)) |
|
|
(message "Lower-cased %d matches" count)))) |
|
|
#+end_src |
|
|
*** Clocking |
|
|
Look into [[https://www.reddit.com/r/emacs/comments/rqoxl8/remove_orgmode_clock_drawer_if_the_time_is_less/][a possible enhancement]] of remove-zero-time-clocks |
|
|
#+begin_src emacs-lisp |
|
|
;; Separate drawers for clocking and logs |
|
|
(setq org-drawers (quote ("PROPERTIES" "CLOCKBOOK"))) |
|
|
;; Save clock data and state changes and notes in the CLOCK drawer |
|
|
(setq org-clock-into-drawer "CLOCKBOOK") |
|
|
;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration |
|
|
(setq org-clock-out-remove-zero-time-clocks t) |
|
|
;; Clock out when moving task to a done state |
|
|
(setq org-clock-out-when-done t) |
|
|
|
|
|
(setq org-clock-persist 'history) |
|
|
|
|
|
(setq org-duration-format 'h:mm) |
|
|
(setq org-clock-clocktable-default-properties '(:maxlevel 2 :scope subtree)) |
|
|
(org-clock-persistence-insinuate) |
|
|
#+end_src |
|
|
*** COMMENT Tangle to different files |
|
|
This has been broken by commit 3ebee033103ccd3c3e8c354bad01c15332b9d901 |
|
|
and a2cb9b853d30fc301f4553d1556dba4ee6bc1ead. |
|
|
Try some workarounds to pinpoint the issue: |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-babel-tangle-use-relative-file-links nil) |
|
|
#+end_src |
|
|
---- |
|
|
|
|
|
This is some some super-clever stuff. See |
|
|
[https://emacs.stackexchange.com/questions/39032/tangle-the-same-src-block-to-different-files] |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-babel-tangle-collect-blocks-handle-tangle-list (&optional language tangle-file) |
|
|
"Can be used as :override advice for `org-babel-tangle-collect-blocks'. |
|
|
Handles lists of :tangle files." |
|
|
(let ((counter 0) last-heading-pos blocks) |
|
|
(org-babel-map-src-blocks (buffer-file-name) |
|
|
(let ((current-heading-pos |
|
|
(org-with-wide-buffer |
|
|
(org-with-limited-levels (outline-previous-heading))))) |
|
|
(if (eq last-heading-pos current-heading-pos) (cl-incf counter) |
|
|
(setq counter 1) |
|
|
(setq last-heading-pos current-heading-pos))) |
|
|
(unless (org-in-commented-heading-p) |
|
|
(let* ((info (org-babel-get-src-block-info)) |
|
|
(src-lang (nth 0 info)) |
|
|
(src-tfiles (cdr (assq :tangle (nth 2 info))))) ; Tobias: accept list for :tangle |
|
|
(unless (consp src-tfiles) ; Tobias: unify handling of strings and lists for :tangle |
|
|
(setq src-tfiles (list src-tfiles))) ; Tobias: unify handling |
|
|
(dolist (src-tfile src-tfiles) ; Tobias: iterate over list |
|
|
(unless (or (string= src-tfile "no") |
|
|
(and tangle-file (not (equal tangle-file src-tfile))) |
|
|
(and language (not (string= language src-lang)))) |
|
|
;; Add the spec for this block to blocks under its |
|
|
;; language. |
|
|
(let ((by-lang (assoc src-lang blocks)) |
|
|
(block (org-babel-tangle-single-block counter))) |
|
|
(setcdr (assoc :tangle (nth 4 block)) src-tfile) ; Tobias: |
|
|
(if by-lang (setcdr by-lang (cons block (cdr by-lang))) |
|
|
(push (cons src-lang (list block)) blocks)))))))) ; Tobias: just () |
|
|
;; Ensure blocks are in the correct order. |
|
|
(mapcar (lambda (b) (cons (car b) (nreverse (cdr b)))) blocks))) |
|
|
|
|
|
(defun org-babel-tangle-single-block-handle-tangle-list (oldfun block-counter &optional only-this-block) |
|
|
"Can be used as :around advice for `org-babel-tangle-single-block'. |
|
|
If the :tangle header arg is a list of files. Handle all files" |
|
|
(let* ((info (org-babel-get-src-block-info)) |
|
|
(params (nth 2 info)) |
|
|
(tfiles (cdr (assoc :tangle params)))) |
|
|
(message nil) |
|
|
(if (null (and only-this-block (consp tfiles))) |
|
|
(funcall oldfun block-counter only-this-block) |
|
|
(cl-assert (listp tfiles) nil |
|
|
":tangle only allows a tangle file name or a list of tangle file names") |
|
|
(let ((ret (mapcar |
|
|
(lambda (tfile) |
|
|
(message tfile) |
|
|
(sleep-for 1) |
|
|
(let (old-get-info) |
|
|
(cl-letf* (((symbol-function 'old-get-info) (symbol-function 'org-babel-get-src-block-info)) |
|
|
((symbol-function 'org-babel-get-src-block-info) |
|
|
`(lambda (&rest get-info-args) |
|
|
(let* ((info (apply 'old-get-info get-info-args)) |
|
|
(params (nth 2 info)) |
|
|
(tfile-cons (assoc :tangle params))) |
|
|
(setcdr tfile-cons ,tfile) |
|
|
info)))) |
|
|
(funcall oldfun block-counter only-this-block)))) |
|
|
tfiles))) |
|
|
(if only-this-block |
|
|
(list (cons (cl-caaar ret) (mapcar #'cadar ret))) |
|
|
ret))))) |
|
|
|
|
|
|
|
|
(advice-add 'org-babel-tangle-collect-blocks :override |
|
|
#'org-babel-tangle-collect-blocks-handle-tangle-list) |
|
|
(advice-add 'org-babel-tangle-single-block :around |
|
|
#'org-babel-tangle-single-block-handle-tangle-list) |
|
|
#+end_src |
|
|
*** Append tangle |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-babel-tangle-append (filename) |
|
|
"Append source code block at point to its tangle file. |
|
|
The command works like `org-babel-tangle' with prefix arg |
|
|
but `delete-file' is ignored." |
|
|
(interactive) |
|
|
(cl-letf (((symbol-function 'delete-file) #'ignore)) |
|
|
(org-babel-tangle '(4) filename))) |
|
|
#+end_src |
|
|
*** Tangle file |
|
|
This can be used to tangle one or more files to their output files [[https://gitlab.com/to1ne/literate-dotfiles/blob/master/elisp/tangle.el][ |
|
|
Source on gitlab]] |
|
|
#+begin_src emacs-lisp |
|
|
(defun tangle-file(&rest files nokill) |
|
|
"Tangle FILES or all files in the project." |
|
|
(when (null files) |
|
|
(setq files command-line-args-left)) |
|
|
(dolist (file files) |
|
|
(with-current-buffer (find-file-noselect file) |
|
|
(org-babel-tangle) |
|
|
; (unless nokill (kill-buffer)) |
|
|
))) |
|
|
#+end_src |
|
|
*** Export file |
|
|
This can be used to tangle one or more files to their output files [[https://gitlab.com/to1ne/literate-dotfiles/blob/master/elisp/tangle.el][ |
|
|
Source on gitlab]] |
|
|
#+begin_src emacs-lisp |
|
|
(defun export-org-file-pdf(&rest files nokill) |
|
|
"Export FILES or all files in the project." |
|
|
(when (null files) |
|
|
(setq files command-line-args-left)) |
|
|
(dolist (file files) |
|
|
(with-current-buffer (find-file-noselect file) |
|
|
(org-latex-export-to-pdf) |
|
|
(kill-buffer)))) |
|
|
#+end_src |
|
|
*** Crypto stuff |
|
|
#+begin_src emacs-lisp |
|
|
(require 'org-crypt) |
|
|
(org-crypt-use-before-save-magic) |
|
|
(setq org-tags-exclude-from-inheritance (quote ("crypt"))) |
|
|
|
|
|
(setq org-crypt-key "C86A9E7675295C62") |
|
|
#+end_src |
|
|
|
|
|
*** ox-export |
|
|
Load ox-hugo |
|
|
#+begin_src emacs-lisp |
|
|
(with-eval-after-load 'ox |
|
|
(require 'ox-hugo)) |
|
|
#+end_src |
|
|
*** ~org-roam~ setup |
|
|
#+begin_src emacs-lisp |
|
|
(setq org-roam-subdir-alist '(("hack" . "hack") |
|
|
("research-dsr" . "math") |
|
|
("service" . "service") |
|
|
("editorial" . "editorial"))) |
|
|
(setq org-roam-directory (file-truename (concat "~/org-roam/" (assoc-default (kde-current-activity-name) org-roam-subdir-alist)))) |
|
|
(setq org-roam-db-location (file-truename (concat org-roam-directory "/org-roam.db"))) |
|
|
(setq org-roam-capture-templates |
|
|
'(("d" "default" plain "%?" :target |
|
|
(file+head "%<%Y%m%d%H%M%S>-${slug}.org" |
|
|
"#+title: ${title} |
|
|
,#+startup: inlineimages |
|
|
") |
|
|
:unnarrowed t :kill-buffer tp))) |
|
|
|
|
|
(org-roam-db-autosync-mode) |
|
|
(setq org-roam-node-display-template |
|
|
(concat "${title} " |
|
|
(propertize "${tags:10}" 'face 'org-tag))) |
|
|
|
|
|
(global-set-key (kbd "C-c n f") 'org-roam-node-find) |
|
|
(global-set-key (kbd "C-c n c") 'org-roam-node-capture) |
|
|
(global-set-key (kbd "C-c n l") 'org-roam-node-insert) |
|
|
(global-set-key (kbd "C-c n n") 'org-roam-buffer-toggle) |
|
|
(add-to-list 'display-buffer-alist |
|
|
'("\\*org-roam\\*" |
|
|
(display-buffer-in-direction) |
|
|
(direction . bottom ) |
|
|
(window-width . fit-window-to-buffer) |
|
|
(window-height . 0.40 ))) |
|
|
#+end_src |
|
|
This snippet was taken from [[https://org-roam.discourse.group/t/creating-an-org-roam-note-from-an-existing-headline/978][this thread]], it allows to refile an |
|
|
org-headline to a org-roam node. |
|
|
#+begin_src emacs-lisp |
|
|
(defun org-roam-create-note-from-headline () |
|
|
"Create an Org-roam note from the current headline if it doesn't |
|
|
exist without jumping to it" |
|
|
(let* ((title (nth 4 (org-heading-components))) |
|
|
;; Read in the name of the node, with the title filled in |
|
|
;; TODO: How can I just use the title without user input? |
|
|
(node (org-roam-node-read title))) |
|
|
;; Skip the node if it already exists |
|
|
(if (org-roam-node-file node) |
|
|
(message "Skipping %s, node already exists" title) |
|
|
;; Without this the subsequent kills seem to be grouped together, not |
|
|
;; sure why |
|
|
(kill-new "") |
|
|
;; Cut the subtree from the original file |
|
|
(org-cut-subtree) |
|
|
;; Create the new capture file |
|
|
(org-roam-capture- :node node) |
|
|
;; Paste in the subtree |
|
|
(org-paste-subtree) |
|
|
;; Removing the heading from new node |
|
|
(kill-whole-line) |
|
|
;; Finalizing the capture will save and close the capture buffer |
|
|
(org-capture-finalize nil) |
|
|
;; Because we've deleted a subtree, we need the following line to make the |
|
|
;; `org-map-entries' call continue from the right place |
|
|
(setq org-map-continue-from |
|
|
(org-element-property :begin (org-element-at-point)))))) |
|
|
|
|
|
(defun org-roam-create-note-from-headlines () |
|
|
(interactive) |
|
|
(if (region-active-p) |
|
|
;; `region-start-level' means we'll map over only headlines that are at |
|
|
;; the same level as the first headline in the region. This may or may not |
|
|
;; be what you want |
|
|
(org-map-entries |
|
|
'org-roam-create-note-from-headline t 'region-start-level) |
|
|
;; If no region was selected, just create the note from the current headline |
|
|
(org-roam-create-note-from-headline))) |
|
|
#+end_src |
|
|
|
|
|
*** export to ics |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/push-subtree-to-nextcloud () |
|
|
(interactive) |
|
|
(org-back-to-heading) |
|
|
(org-map-entries #'wilder/push-heading-to-nextcloud nil 'tree)) |
|
|
|
|
|
(defun wilder/push-heading-to-nextcloud () |
|
|
(interactive) |
|
|
(org-narrow-to-subtree) |
|
|
(let ((org-icalendar-scheduled-summary-prefix "") |
|
|
(org-icalendar-use-scheduled '(event-if-todo)) |
|
|
(org-icalendar-include-todo nil)) |
|
|
(shell-command (concat "~/scripts/push-to-nextcloud-cal.sh " |
|
|
(org-icalendar-export-to-ics)))) |
|
|
(widen)) |
|
|
#+end_src |
|
|
*** org-babel |
|
|
I thought that i had no reason to use it, but let's see |
|
|
#+begin_src emacs-lisp |
|
|
(org-babel-lob-ingest "~/work/include/latexmkrc-base.org") |
|
|
#+end_src |
|
|
*** Make reftex work in org-edit |
|
|
RefTeχ refuses to work when the buffer is not visiting a file. |
|
|
Here is a tentative workaround: |
|
|
- extract the file we are tangling to |
|
|
- use it as TeX-master |
|
|
- hijack ~buffer-file-name~ to return something non-nil; I would |
|
|
rather hijack it locally (using cl-letf rather than advising the |
|
|
function since other things would be confused by the advice) |
|
|
#+begin_src emacs-lisp |
|
|
(defun hijack-buffer-file-name (orig-fun &rest args) |
|
|
(if (not (boundp 'reftex-workaround-enabled)) |
|
|
(apply orig-fun args) |
|
|
(cl-letf (((symbol-function 'buffer-file-name) (lambda (&optional buffer) "fragment.tex"))) |
|
|
(apply orig-fun args)))) |
|
|
|
|
|
(advice-add 'reftex-label :around |
|
|
#'hijack-buffer-file-name) |
|
|
(advice-add 'reftex-reference :around |
|
|
#'hijack-buffer-file-name) |
|
|
|
|
|
|
|
|
#+end_src |
|
|
|
|
|
** elisp |
|
|
*** Paredit |
|
|
#+begin_src emacs-lisp |
|
|
(autoload 'enable-paredit-mode "paredit" "Turn on |
|
|
pseudo-structural editing of Lisp code." t) |
|
|
(add-hook 'emacs-lisp-mode-hook #'enable-paredit-mode) |
|
|
(add-hook 'scheme-mode-hook #'enable-paredit-mode) |
|
|
(require 'highlight-parentheses) |
|
|
|
|
|
(with-eval-after-load 'paredit |
|
|
(define-key paredit-mode-map (kbd "<return>") #'paredit-open-round) |
|
|
(define-key paredit-mode-map (kbd "C-<return>") #'paredit-wrap-round)) |
|
|
|
|
|
(defun hydra-paren/hl-paren-force-fix () |
|
|
;;; This is needed b/c hl-parent caches the point position and |
|
|
;;; refuses to update if the point did not move |
|
|
(let ((hl-paren-last-point -1)) |
|
|
(hl-paren-highlight))) |
|
|
(defun hydra-paren/pre () |
|
|
(unless (boundp 'hydra-paren/hpm) |
|
|
(highlight-parentheses-mode 1)) |
|
|
(setq hydra-paren/hpm t)) |
|
|
(defun hydra-paren/post () |
|
|
(highlight-parentheses-mode -1) |
|
|
(makunbound 'hydra-paren/hpm)) |
|
|
|
|
|
(defhydra hydra-paren |
|
|
(:pre hydra-paren/pre :post hydra-paren/post) |
|
|
"paren slurp and barf" |
|
|
("i" (progn |
|
|
(paredit-forward-slurp-sexp) |
|
|
(hydra-paren/hl-paren-force-fix)) "slurp forward") |
|
|
("n" (progn |
|
|
(paredit-forward-barf-sexp) |
|
|
(hydra-paren/hl-paren-force-fix)) "barf forward") |
|
|
("r" (progn |
|
|
(paredit-backward-slurp-sexp) |
|
|
(hydra-paren/hl-paren-force-fix)) "slurp backward") |
|
|
("t" (progn |
|
|
(paredit-backward-barf-sexp) |
|
|
(hydra-paren/hl-paren-force-fix)) "barf backward")) |
|
|
(global-set-key (kbd "C-c C-SPC") #'hydra-paren/body) |
|
|
#+end_src |
|
|
*** Replace last sexp |
|
|
I use this a lot to evaluate (e.g.) quick computations in files |
|
|
#+begin_src emacs-lisp |
|
|
(defun replace-last-sexp () |
|
|
(interactive) |
|
|
(let ((value (eval (preceding-sexp)))) |
|
|
(kill-sexp -1) |
|
|
(insert (format "%S" value)))) |
|
|
|
|
|
(global-set-key (kbd "C-c C-x C-e") #'replace-last-sexp) |
|
|
#+end_src |
|
|
** qml |
|
|
Load ~qml-mode~ |
|
|
#+begin_src emacs-lisp |
|
|
(autoload 'qml-mode "qml-mode.el" t) |
|
|
(add-to-list 'auto-mode-alist '("\\.qml\\'" . qml-mode)) |
|
|
#+end_src |
|
|
** Python |
|
|
Silent offset warning |
|
|
#+begin_src emacs-lisp |
|
|
(setq python-indent-guess-indent-offset-verbose nil) |
|
|
#+end_src |
|
|
** Dired |
|
|
Do not show hidden files in dired |
|
|
#+begin_src emacs-lisp |
|
|
(setq dired-listing-switches "-l") |
|
|
#+end_src |
|
|
** pdf-tools |
|
|
Let us give pdf-tools a try |
|
|
#+begin_src emacs-lisp |
|
|
(pdf-tools-install) |
|
|
(define-key pdf-view-mode-map (kbd "<mouse-12>") |
|
|
'pdf-sync-backward-search-mouse) |
|
|
(add-hook 'pdf-view-mode-hook #'auto-revert-mode) |
|
|
(add-hook 'pdf-view-mode-hook (lambda () (margin-current-line-mode 0))) |
|
|
#+end_src |
|
|
** C and C++ |
|
|
*** Hooks |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'c-mode-hook |
|
|
(lambda () |
|
|
(subword-mode) |
|
|
(define-key c-mode-map (kbd "C-c C-c") 'make))) |
|
|
(add-hook 'c++-mode-hook |
|
|
(lambda () |
|
|
(subword-mode) |
|
|
(define-key c++-mode-map (kbd "C-c C-c") 'make))) |
|
|
#+end_src |
|
|
** TODO split --- LaTeX |
|
|
*** TODO Setup ~reftex~ |
|
|
#+begin_src emacs-lisp |
|
|
(with-eval-after-load "reftex" |
|
|
(setq reftex-cite-format "~\\cite{%l}" |
|
|
reftex-plug-into-AUCTeX '(t t nil t t) |
|
|
reftex-insert-label-flags '(t t) |
|
|
reftex-label-alist |
|
|
'(("thm" ?t "thm:" "~\\ref{%s}" thm (regexp "theorems?")) |
|
|
("lem" ?l "lem:" "~\\ref{%s}" lem (regexp "lemma{ta}?")) |
|
|
("prop" ?p "prp:" "~\\ref{%s}" prp (regexp "propositions?")) |
|
|
("cor" ?c "cor:" "~\\ref{%s}" cor (regexp "corollary")) |
|
|
("def" ?d "def:" "~\\ref{%s}" cor (regexp "defintions?"))))) |
|
|
;;; advice the auctex function to replace the |
|
|
;;; lighter with somthing softer |
|
|
(with-eval-after-load "auctex" |
|
|
(defun cleanup-TeX-mode (&optional mode local reset) |
|
|
"Advice for the base auctex MODE naming" |
|
|
(setq mode-name (replace-regexp-in-string "LaTeX" "χ" mode-name t)) |
|
|
(setq mode-name (replace-regexp-in-string "/" "·" mode-name))) |
|
|
(advice-add 'TeX-set-mode-name :after 'cleanup-TeX-mode)) |
|
|
|
|
|
#+end_src |
|
|
*** Setup ~latex-mode~ |
|
|
#+begin_src emacs-lisp |
|
|
(eval-after-load 'latex |
|
|
'(progn |
|
|
(font-lock-add-keywords 'latex-mode |
|
|
`((,(rx "$") 0 'font-latex-sedate-face t)) t) |
|
|
(defface font-latex-special-comment-face '((t (:foreground "#2aa198"))) "Cyan") |
|
|
|
|
|
|
|
|
(font-lock-add-keywords 'latex-mode '(("^% \\([^*].*\\)" 1 'font-latex-special-comment-face t))) |
|
|
|
|
|
(define-key-alist LaTeX-mode-map |
|
|
'(("M-S-SPC" . TeX-insert-braces) |
|
|
("C-c C-v" . wilder/TeX-insert-reference) |
|
|
("C-M-<return>" . wilder/TeX-insert-align-dwim) |
|
|
;; unbind return - NOTE it is important to unbind |
|
|
;; <return> and not RET. If we unbind RET then C-m won't work |
|
|
;; either. |
|
|
("<return>" . (lambda() (interactive) (insert "\\"))) |
|
|
("S-<return>" . (lambda() (interactive) (insert "|"))) |
|
|
("C-c C-." . LaTeX-mark-environment) |
|
|
("C-c <return>" . LaTeX-environment) |
|
|
("C-c C-e" . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter "_{" "}" r-begin r-end))) |
|
|
("C-c C-u" . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter "^{" "}" r-begin r-end))) |
|
|
("M-|" . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter "|" "|" r-begin r-end))) |
|
|
("M-," . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter ", " ", " r-begin r-end))) |
|
|
("M-\"" . (lambda (r-begin r-end) |
|
|
(interactive "r") |
|
|
(add-delimiter "“" "”" r-begin r-end))) |
|
|
|
|
|
;; This is the rationale: C-M-SPC starts inline math C-M-RET starts display math |
|
|
|
|
|
("C-M-SPC" . (lambda (r-begin r-end) (interactive "r") (add-delimiter "$" "$" r-begin r-end))) |
|
|
("C-c C-d" . wilder/TeX-insert-todonote) |
|
|
("C-M-d" . kill-sexp) |
|
|
("C-M-i" . down-list) ;; -i stands for /in/ |
|
|
("C-M-o" . up-list) ;; -o stands for /out/ |
|
|
("<f4>" . ( lambda() (interactive) (message "Use C-c C-c"))) |
|
|
("C-c C-c" . ( lambda() (interactive) (compile "/home/jacopods/scripts/latex-mk")) ) |
|
|
("~" . wilder/TeX-replace-tilde) |
|
|
("C-o" . wilder/open-line-and-indent) |
|
|
;; Force moves around to be more “semantic” |
|
|
("C-v" . goto-next-comment-line) |
|
|
("M-v" . goto-previous-comment-line) |
|
|
("C-x n c" . narrow-between-comments) |
|
|
("C-S-v" . backward-paragraph))) |
|
|
|
|
|
(dolist (ch '("=" "≠" ">" "<" "≥" "≤" "⇒" "∩" "∪" "∨" "∧" "×" "⊂" "⊃")) |
|
|
(define-key LaTeX-mode-map (kbd ch) 'insert-char-with-padding)) |
|
|
|
|
|
;; Move around commands in the Right Way™ |
|
|
(modify-syntax-entry ?\\ "w" LaTeX-mode-syntax-table) |
|
|
|
|
|
;; add fancy quotes to the syntax table |
|
|
(modify-syntax-entry ?“ "(”" LaTeX-mode-syntax-table) |
|
|
(modify-syntax-entry ?” ")“" LaTeX-mode-syntax-table) |
|
|
|
|
|
(setq subword-forward-regexp "\\W*\\(\\([\\\\[:upper:]]*\\W?\\)[[:lower:][:digit:]]*\\)") |
|
|
(setq subword-backward-regexp "\\(\\(\\W\\|[[:lower:][:digit:]]\\)\\([\\\\[:upper:]]+\\W*\\)\\|\\W\\w+\\)"))) |
|
|
#+end_src |
|
|
|
|
|
*** Require |
|
|
Load auctex, reftex and set up related hooks |
|
|
#+begin_src emacs-lisp |
|
|
(require 'tex-site) |
|
|
(require 'reftex) |
|
|
#+end_src |
|
|
*** Set default TeX options |
|
|
#+begin_src emacs-lisp |
|
|
(setq-default TeX-master nil) |
|
|
(setq TeX-auto-save t |
|
|
TeX-parse-self t |
|
|
TeX-insert-braces nil |
|
|
TeX-view-program-selection '((output-pdf "PDF Tools")) |
|
|
TeX-source-correlate-mode t |
|
|
TeX-source-correlate-start-server t) |
|
|
#+end_src |
|
|
*** Appearance |
|
|
No fontification for sub and superscripts |
|
|
#+begin_src emacs-lisp |
|
|
(setq font-latex-fontify-script nil) |
|
|
#+end_src |
|
|
Add unicode quotes to the quote-list |
|
|
#+begin_src emacs-lisp |
|
|
; (defvar font-latex-quote-list '(("``" "''") ("“" "”") ("<<" ">>" french) ("«" "»" french)) ; |
|
|
#+end_src |
|
|
Redefine fold ellipsis |
|
|
#+begin_src emacs-lisp |
|
|
(setq TeX-fold-ellipsis " …") |
|
|
#+end_src |
|
|
*** Default labels |
|
|
#+begin_src emacs-lisp |
|
|
(setq LaTeX-equation-label "e_" |
|
|
LaTeX-section-label '( ("part" . "pp_") |
|
|
("chapter" . "c_") |
|
|
("section" . "s_") |
|
|
("subsection" . "s_") |
|
|
("subsubsection" . "s_")) |
|
|
LaTeX-figure-label "f_") |
|
|
#+end_src |
|
|
*** Spell checking help |
|
|
Do not spell-check inside the following commands. See |
|
|
[https://tex.stackexchange.com/questions/117204/skip-spelling-in-emacs-for-the-content-of-a-user-macro] |
|
|
#+begin_src emacs-lisp |
|
|
(setq ispell-tex-skip-alists |
|
|
(list |
|
|
(append |
|
|
(car ispell-tex-skip-alists) ;tell ispell to ignore content of this: |
|
|
'(("\\\\eqref" ispell-tex-arg-end) |
|
|
("\\\\cite" ispell-tex-arg-end) |
|
|
("\\\\author" ispell-tex-arg-end) |
|
|
("\\\\address" ispell-tex-arg-end) |
|
|
("\\\\include" ispell-tex-arg-end) |
|
|
)) |
|
|
(cadr ispell-tex-skip-alists))) |
|
|
#+end_src |
|
|
*** Specialty functions |
|
|
This function adds a pair of delimiters or surrounds the active region |
|
|
with the given delimiters. |
|
|
TODO it breaks when there is no mark |
|
|
#+begin_src emacs-lisp |
|
|
(defun add-delimiter (delim-begin delim-end r-begin r-end) |
|
|
"Add the pair of delimiters given in delim at the ends of the |
|
|
region if it is activated" |
|
|
(interactive "cBegin delimiter: \ncEnd delimiter: \nr") |
|
|
(if (region-active-p) |
|
|
(progn |
|
|
(save-excursion |
|
|
(goto-char r-end) |
|
|
(insert delim-end) |
|
|
(goto-char r-begin) |
|
|
(insert delim-begin))) |
|
|
(progn |
|
|
(save-excursion |
|
|
(insert delim-end)) |
|
|
(insert delim-begin)))) |
|
|
#+end_src |
|
|
The following re-implements =TeX-insert-braces= to work with |
|
|
negative argument |
|
|
#+begin_src emacs-lisp |
|
|
(defun TeX-insert-braces (arg) |
|
|
"Make a pair of braces around next ARG sexps and leave point inside. |
|
|
No argument is equivalent to zero: just insert braces and leave point |
|
|
between. |
|
|
|
|
|
If there is an active region, ARG will be ignored, braces will be |
|
|
inserted around the region, and point will be left after the |
|
|
closing brace." |
|
|
(interactive "P") |
|
|
(if (TeX-active-mark) |
|
|
(progn |
|
|
(if (< (point) (mark)) |
|
|
(exchange-point-and-mark)) |
|
|
(insert TeX-grcl) |
|
|
(save-excursion |
|
|
(goto-char (mark)) |
|
|
(insert TeX-grop))) |
|
|
(if (and arg (< arg 0)) |
|
|
(progn |
|
|
(save-excursion |
|
|
(backward-sexp (prefix-numeric-value (- 0 arg))) |
|
|
(insert TeX-grop)) |
|
|
(insert TeX-grcl)) |
|
|
(insert TeX-grop) |
|
|
(save-excursion |
|
|
(if arg (forward-sexp (prefix-numeric-value arg))) |
|
|
(insert TeX-grcl))))) |
|
|
|
|
|
(defun TeX-back-insert-braces (arg) |
|
|
(interactive "P") |
|
|
(if arg (TeX-insert-braces (- 0 arg)) |
|
|
(insert TeX-grcl))) |
|
|
#+end_src |
|
|
This is a helper for [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Dynamic-Abbrevs.html][Dynamic abbrev expansion]] |
|
|
#+begin_src emacs-lisp |
|
|
(defun dabbrev-expand-helper () |
|
|
(interactive) |
|
|
(call-interactively 'dabbrev-expand)) |
|
|
#+end_src |
|
|
This inserts a char adding some whitespace padding whenever necessary |
|
|
#+begin_src emacs-lisp |
|
|
(defun insert-char-with-padding (arg) |
|
|
(interactive "*P") |
|
|
(unless (string-match (string (preceding-char)) " \&") |
|
|
(insert " ")) |
|
|
(self-insert-command (prefix-numeric-value arg)) |
|
|
(unless (char-equal (following-char) ?\s) |
|
|
(insert " "))) ;; decide what to do with the point |
|
|
#+end_src |
|
|
This function opens a line and indents it |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/open-line-and-indent (arg) |
|
|
"This function opens a line using (open-line) and indents it" |
|
|
(interactive "p") |
|
|
(open-line arg) |
|
|
(save-excursion (forward-char arg) (unless (eolp) (indent-according-to-mode)))) |
|
|
#+end_src |
|
|
This inserts a plain reference (or a reference to an equation with |
|
|
prefix arg) |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/TeX-insert-reference (arg) |
|
|
(interactive "P") |
|
|
(insert "~") |
|
|
(if arg (TeX-insert-macro "eqref") |
|
|
(TeX-insert-macro "ref"))) |
|
|
#+end_src |
|
|
This inserts a citation |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/TeX-insert-cite (arg) |
|
|
(interactive "P") |
|
|
(insert "~") |
|
|
(TeX-insert-macro "cite")) |
|
|
#+end_src |
|
|
This inserts an ~align~ environment (or an ~align*~ with prefix arg) |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/TeX-insert-align (arg) |
|
|
(interactive "P") |
|
|
(if arg |
|
|
(LaTeX-insert-environment "align") |
|
|
(LaTeX-insert-environment "align*")) |
|
|
(indent-according-to-mode)) |
|
|
|
|
|
(defun wilder/TeX-insert-align-dwim (arg) |
|
|
(interactive "P") |
|
|
(if (texmathp) |
|
|
(let ((why (car texmathp-why))) |
|
|
(cond ((string-prefix-p "$" why) (wilder/TeX-promote-inline-math arg)) |
|
|
((string-equal "equation*" why) (LaTeX-modify-environment "align*")) |
|
|
((string-equal "equation" why) (LaTeX-modify-environment "align")) |
|
|
((string-equal "align*" why) (when arg (LaTeX-modify-environment "align"))))) |
|
|
(wilder/TeX-insert-align arg))) |
|
|
#+end_src |
|
|
This replaces horizontal space with a tilde |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/TeX-replace-tilde (arg) |
|
|
(interactive "P") |
|
|
(delete-horizontal-space) |
|
|
(insert "~")) |
|
|
#+end_src |
|
|
Insert a todonote at point or wrap the region in a todonote. |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/TeX-insert-todonote (arg r-begin r-end) |
|
|
(interactive "P\nr") |
|
|
(if arg |
|
|
(add-delimiter "{\\todo[inline]{" "}}" r-begin r-end) |
|
|
(add-delimiter "{\\todo{" "}}" r-begin r-end))) |
|
|
#+end_src |
|
|
Promote inline math to an align |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/char-punctuation-p (ch) |
|
|
"returns t if the char is a punctuation" |
|
|
(interactive) |
|
|
(string-match (string ch) ".,:;")) |
|
|
(defun wilder/TeX-promote-inline-math (arg) |
|
|
(interactive "P") |
|
|
(when (and (texmathp) (string-prefix-p "$" (car texmathp-why))) |
|
|
(let* ((delim (car texmathp-why)) |
|
|
(pos (cdr texmathp-why)) |
|
|
(n (length delim))) |
|
|
(goto-char pos) |
|
|
(push-mark) |
|
|
(delete-char n) |
|
|
(search-forward delim) |
|
|
(delete-backward-char n) |
|
|
;; include punctuations in the align |
|
|
(when (wilder/char-punctuation-p (char-after)) (forward-char)) |
|
|
(activate-mark) |
|
|
(wilder/TeX-insert-align arg) |
|
|
(pop-mark) |
|
|
(pop-mark)))) |
|
|
#+end_src |
|
|
Shadow the AucTeX version of this function |
|
|
#+begin_src emacs-lisp |
|
|
(defun LaTeX-insert-environment (environment &optional extra) |
|
|
"Insert LaTeX ENVIRONMENT with optional argument EXTRA." |
|
|
(let ((active-mark (and (TeX-active-mark) (not (eq (mark) (point))))) |
|
|
prefix content-start env-start env-end) |
|
|
(when (and active-mark (< (mark) (point))) (exchange-point-and-mark)) |
|
|
;; Compute the prefix. |
|
|
(when (and LaTeX-insert-into-comments (TeX-in-commented-line)) |
|
|
(save-excursion |
|
|
(beginning-of-line) |
|
|
(looking-at |
|
|
(concat "^\\([ \t]*" TeX-comment-start-regexp "+\\)+[ \t]*")) |
|
|
(setq prefix (match-string 0)))) |
|
|
;; What to do with the line containing point. |
|
|
(cond (;; if the line contains only whitespace, delete them |
|
|
(save-excursion (beginning-of-line) |
|
|
(looking-at (concat prefix "[ \t]*$"))) |
|
|
(delete-region (match-beginning 0) (match-end 0))) |
|
|
;; otherwise, something is written on the line. We can be |
|
|
;; at the beginning of the text |
|
|
((TeX-looking-at-backward (concat "^" prefix "[ \t]*") |
|
|
(line-beginning-position)) |
|
|
;;this morally opens a new line |
|
|
(beginning-of-line) |
|
|
(newline) |
|
|
(beginning-of-line 0)) |
|
|
((bolp) |
|
|
(delete-horizontal-space) |
|
|
(newline) |
|
|
(beginning-of-line 0)) |
|
|
((looking-at "[ \t]*$") |
|
|
(delete-horizontal-space) |
|
|
(newline) |
|
|
(when prefix (insert prefix)) |
|
|
(beginning-of-line)) |
|
|
(t |
|
|
(delete-horizontal-space) |
|
|
(newline 2) |
|
|
(when prefix (insert prefix)) |
|
|
(beginning-of-line 0))) |
|
|
;; What to do with the line containing mark. |
|
|
(when active-mark |
|
|
(save-excursion |
|
|
(goto-char (mark)) |
|
|
(cond ((save-excursion (beginning-of-line) |
|
|
(or (looking-at (concat prefix "[ \t]*$")) |
|
|
(looking-at "[ \t]*$"))) |
|
|
(delete-region (match-beginning 0) (match-end 0))) |
|
|
((TeX-looking-at-backward (concat "^" prefix "[ \t]*") |
|
|
(line-beginning-position)) |
|
|
(beginning-of-line) |
|
|
(newline) |
|
|
(beginning-of-line 0)) |
|
|
((looking-at "[ \t]*$") |
|
|
(delete-horizontal-space) |
|
|
(insert-before-markers "\n") |
|
|
;(newline) |
|
|
(when prefix (insert prefix))) |
|
|
(t |
|
|
(delete-horizontal-space) |
|
|
(insert-before-markers "\n") |
|
|
(newline) |
|
|
(when prefix (insert prefix)))))) |
|
|
;; Now insert the environment. |
|
|
(when prefix (insert prefix)) |
|
|
(setq env-start (point)) |
|
|
(insert TeX-esc "begin" TeX-grop environment TeX-grcl) |
|
|
(indent-according-to-mode) |
|
|
(when extra (insert extra)) |
|
|
(setq content-start (line-beginning-position 2)) |
|
|
(unless active-mark |
|
|
(newline) |
|
|
(when prefix (insert prefix)) |
|
|
(newline)) |
|
|
(when active-mark (goto-char (mark))) |
|
|
(when prefix (insert prefix)) |
|
|
(insert TeX-esc "end" TeX-grop environment TeX-grcl) |
|
|
(end-of-line 0) |
|
|
(if active-mark |
|
|
(progn |
|
|
(or (assoc environment LaTeX-indent-environment-list) |
|
|
(if auto-fill-function |
|
|
;; Fill the region only when `auto-fill-mode' is active. |
|
|
(LaTeX-fill-region content-start (line-beginning-position 2)))) |
|
|
(set-mark content-start)) |
|
|
(indent-according-to-mode)) |
|
|
(save-excursion (beginning-of-line 2) (indent-according-to-mode)) |
|
|
(TeX-math-input-method-off) |
|
|
(setq env-end (save-excursion |
|
|
(search-forward |
|
|
(concat TeX-esc "end" TeX-grop |
|
|
environment TeX-grcl)) |
|
|
(match-beginning 0))) |
|
|
(run-hook-with-args 'LaTeX-after-insert-env-hooks |
|
|
environment env-start env-end))) |
|
|
#+end_src |
|
|
*** Patch auctex |
|
|
I don't particularly like the way that the prefix arguments works |
|
|
with font selection. The function below patches the annoying |
|
|
behavior, by defaulting to the default behavior if the prefix |
|
|
argument is 0 |
|
|
#+begin_src emacs-lisp |
|
|
(defun wilder/TeX-font (replace what) |
|
|
"Insert template for font change command. |
|
|
If REPLACE is not nil, replace current font. WHAT determines the font |
|
|
to use, as specified by `TeX-font-list'." |
|
|
(interactive "*P\nc") |
|
|
(TeX-update-style) |
|
|
(let* ((entry (assoc what TeX-font-list)) |
|
|
(in-math (texmathp)) |
|
|
(before (nth 1 entry)) |
|
|
(after (nth 2 entry))) |
|
|
(setq replace (or replace (eq t (nth 3 entry)) (eq t (nth 5 entry)))) |
|
|
(if (and in-math (stringp (nth 3 entry))) |
|
|
(setq before (nth 3 entry) |
|
|
after (nth 4 entry))) |
|
|
(setq arg (prefix-numeric-value replace)) |
|
|
(cond |
|
|
((null entry) |
|
|
(let ((help (concat |
|
|
"Font list: " |
|
|
"KEY TEXTFONT MATHFONT\n\n" |
|
|
(mapconcat 'TeX-describe-font-entry |
|
|
TeX-font-list "\n")))) |
|
|
(with-output-to-temp-buffer "*Help*" |
|
|
(set-buffer "*Help*") |
|
|
(insert help)))) |
|
|
((and replace (eq 0 arg)) |
|
|
(funcall TeX-font-replace-function before after)) |
|
|
((and replace (< arg 0) ) |
|
|
(progn |
|
|
(save-excursion |
|
|
(backward-sexp (- 0 arg)) |
|
|
(insert before)) |
|
|
(insert after))) |
|
|
((and replace (> arg 0)) |
|
|
(insert before) |
|
|
(save-excursion |
|
|
(forward-sexp arg) |
|
|
(insert after))) |
|
|
((TeX-active-mark) |
|
|
(save-excursion |
|
|
(cond ((> (mark) (point)) |
|
|
(insert before) |
|
|
(goto-char (mark)) |
|
|
(insert after)) |
|
|
(t |
|
|
(insert after) |
|
|
(goto-char (mark)) |
|
|
(insert before))))) |
|
|
(t |
|
|
(insert before) |
|
|
(save-excursion |
|
|
(insert after)))))) |
|
|
(advice-add 'TeX-font :override #'wilder/TeX-font) |
|
|
#+end_src |
|
|
*** The hook |
|
|
#+begin_src emacs-lisp |
|
|
(add-hook 'LaTeX-mode-hook |
|
|
(lambda () |
|
|
(turn-on-reftex) |
|
|
|
|
|
(setq comment-column 0) |
|
|
(setq prettify-symbols-alist nil) |
|
|
(add-to-list 'prettify-symbols-alist '(" ⊂ " . (? (Br . Bl) ? (Br . Bl) ?))) |
|
|
|
|
|
(turn-on-auto-fill) |
|
|
(subword-mode) |
|
|
(TeX-fold-mode 1) |
|
|
(outshine-mode))) |
|
|
#+end_src |
|
|
*** Compilation fixes |
|
|
Load appropriate compliation filters for warnings |
|
|
#+begin_src emacs-lisp |
|
|
(load "latex-compile-filters.el") |
|
|
#+end_src |
|
|
Fix erroneous parsing of the LuaLaTeχ output "avail lists" as an |
|
|
error (works on ≥ emacs-27.1) |
|
|
#+begin_src emacs-lisp |
|
|
(add-to-list 'compilation-transform-file-match-alist '(" avail lists" nil)) |
|
|
#+end_src |
|
|
*** Load ~bibretrieve~ |
|
|
#+begin_src emacs-lisp |
|
|
(byte-recompile-directory "~/.emacs.d/bibretrieve" 0) |
|
|
(load "bibretrieve") |
|
|
(setq bibretrieve-backends '(("msn" . 10))) |
|
|
(require 'biblio) |
|
|
#+end_src |
|
|
|
|
|
** Journal |
|
|
Setup ~org-journal~ |
|
|
#+begin_src emacs-lisp |
|
|
(require 'org-journal) |
|
|
(setq org-journal-dir "~/org/journal" |
|
|
org-journal-file-type 'yearly |
|
|
org-journal-file-format "%Y" |
|
|
org-journal-encrypt-journal t) |
|
|
|
|
|
|
|
|
#+end_src |
|
|
* Specialties |
|
|
** repeat-mode and smerge |
|
|
Try to enable repeat-mode in smerge (see [[https://karthinks.com/software/it-bears-repeating/][here]] |
|
|
#+begin_src emacs-lisp |
|
|
(defun repeatize (keymap) |
|
|
"Add `repeat-mode' support to a KEYMAP." |
|
|
(map-keymap |
|
|
(lambda (_key cmd) |
|
|
(when (symbolp cmd) |
|
|
(put cmd 'repeat-map keymap))) |
|
|
(symbol-value keymap))) |
|
|
#+end_src |
|
|
And enable! |
|
|
#+begin_src emacs-lisp |
|
|
(require 'smerge-mode) |
|
|
(repeatize 'smerge-basic-map) |
|
|
(repeat-mode) |
|
|
#+end_src |
|
|
** try and fix issue with static-if |
|
|
#+begin_src emacs-lisp |
|
|
(require 'compat) |
|
|
#+end_src |
|
|
** powerthesaurus |
|
|
This is throwing an error (static-if) |
|
|
#+begin_src emacs-lisp |
|
|
(require 'powerthesaurus) |
|
|
(global-set-key (kbd "C-<f18>") #'powerthesaurus-hydra/body) |
|
|
#+end_src |
|
|
|
|
|
** beacon =beacon= is a package that helps finding the point when switching |
|
|
buffers. I like the idea but the aesthetics is a bit baroque. I |
|
|
should do the same with =flash-hline= |
|
|
** flash-hline |
|
|
This function is defined to help in training with new keybindings. It |
|
|
acts as a visual bell which flashes the current line. It is (arbitrarily) |
|
|
bound to F15 which is supposed to be triggered by some “illegal” key hit |
|
|
#+begin_src emacs-lisp |
|
|
(defun flash-hline () |
|
|
"Flash the current line to emph some mistake" |
|
|
(interactive) |
|
|
(let ((fg (face-foreground 'default)) |
|
|
(bg (face-background 'hl-line))) |
|
|
(set-face-background 'hl-line fg) |
|
|
(run-with-timer |
|
|
0.1 nil (lambda () |
|
|
(set-face-background 'hl-line "#303030") )))) |
|
|
|
|
|
(global-set-key (kbd "<f15>") 'flash-hline) |
|
|
#+end_src |
|
|
** unfill-paragraph |
|
|
This is authored by Stefan Monnier <foo at acm.org>. It is the opposite of |
|
|
fill-paragraph |
|
|
#+begin_src emacs-lisp |
|
|
(defun unfill-paragraph (&optional region) |
|
|
"Takes a multi-line paragraph and makes it into a single line of text." |
|
|
(interactive (progn (barf-if-buffer-read-only) '(t))) |
|
|
(let ((fill-column (point-max)) |
|
|
;; This would override `fill-column' if it's an integer. |
|
|
(emacs-lisp-docstring-fill-column t)) |
|
|
(fill-paragraph nil region))) |
|
|
#+end_src |
|
|
** kill-word |
|
|
dwim manage space after kill-word. This has been morally pasted from |
|
|
[[https://www.reddit.com/r/emacs/comments/3nlws0/automanage_spaces_post_word_kills/][this reddit post]]. The original version does not allow for double |
|
|
spaces after a period. |
|
|
#+begin_src emacs-lisp |
|
|
(defun modi/just-one-space-post-kill-word (&rest _) |
|
|
"Function to manage white space after `kill-word' operations. |
|
|
|
|
|
1. If point is at the beginning of the line after possibly some white space, |
|
|
remove that white space and re-indent that line. |
|
|
2. If there is space before or after the point, ensure that there is only |
|
|
one white space around the point. |
|
|
3. Otherwise, do nothing. |
|
|
|
|
|
During the whole operation do not change the point position with respect to the |
|
|
surrounding white space. |
|
|
|
|
|
abc| def ghi <-- point on the left of white space after 'abc' |
|
|
abc| ghi <-- point still before white space after calling this function |
|
|
abc |def ghi <-- point on the right of white space before 'def' |
|
|
abc |ghi <-- point still after white space after calling this function." |
|
|
(save-excursion ; maintain the initial position of the pt with respect to space |
|
|
(cond ((looking-back "^ *") ; remove extra space at beginning of line |
|
|
(just-one-space 0) |
|
|
(indent-according-to-mode)) |
|
|
((looking-back "\\. *") |
|
|
(just-one-space 2)) |
|
|
((or (looking-at " ") |
|
|
(looking-back " ")) ; adjust space only if it exists |
|
|
(just-one-space 1)) |
|
|
(t ; do nothing otherwise, includes case where the point is at EOL |
|
|
)))) |
|
|
;; Delete extra horizontal white space after `kill-word' and `backward-kill-word' |
|
|
(advice-add 'kill-word :after #'modi/just-one-space-post-kill-word) |
|
|
#+end_src |
|
|
|
|
|
#+results: |
|
|
|
|
|
** word-count |
|
|
Add word count in modeline; useful to prepare those pesky grant |
|
|
applications |
|
|
#+begin_src emacs-lisp |
|
|
(require 'word-count) |
|
|
#+end_src |
|
|
** try fixing autorevert |
|
|
This patches the inotify watch to follow symlinks. It is |
|
|
particulary useful for my setup where stuff is in symlinked |
|
|
directories (e.g. to tmpfs or to cloud providers) |
|
|
#+begin_src emacs-lisp |
|
|
(defun file-notify--add-watch-inotify (_file dir flags) |
|
|
"Add a watch for FILE in DIR with FLAGS, using inotify." |
|
|
(inotify-add-watch dir |
|
|
(append |
|
|
(and (memq 'change flags) |
|
|
'(create delete delete-self modify move-self move)) |
|
|
(and (memq 'attribute-change flags) |
|
|
'(attrib))) |
|
|
#'file-notify--callback-inotify)) |
|
|
#+end_src |
|
|
** COMMENT gist |
|
|
Use [[https://github.com/defunkt/gist.el][gist.el]] |
|
|
#+begin_src emacs-lisp |
|
|
(require 'gist) |
|
|
#+end_src |
|
|
* Main packages |
|
|
** Magit |
|
|
Load ~magit~ and introduce the keybinding |
|
|
#+begin_src emacs-lisp |
|
|
(require 'magit) |
|
|
(global-set-key (kbd "C-x g") 'magit-status) |
|
|
(global-set-key (kbd "C-x 5 g") (eval-with-new-frame (magit-status) (delete-other-windows))) |
|
|
#+end_src |
|
|
Customize the sections in ~magit-status~ by adding modules overview |
|
|
and untracked files. |
|
|
#+begin_src emacs-lisp |
|
|
(magit-add-section-hook 'magit-status-sections-hook |
|
|
#'magit-insert-modules-overview |
|
|
#'magit-insert-unpushed-to-pushremote |
|
|
:append) |
|
|
(magit-add-section-hook 'magit-status-sections-hook |
|
|
#'magit-insert-untracked-files |
|
|
#'magit-insert-modules-overview |
|
|
:append) |
|
|
#+end_src |
|
|
Enable [[https://github.com/alphapapa/magit-todos ][magit-todos]] in magit status buffer |
|
|
#+begin_src emacs-lisp |
|
|
(require 'magit-todos) |
|
|
(magit-todos-mode) |
|
|
#+end_src |
|
|
Enable [[https://github.com/magit/orgit][orgit]] so that we can link to magit status buffer |
|
|
#+begin_src emacs-lisp |
|
|
(require 'orgit) |
|
|
#+end_src |
|
|
** smart-tab |
|
|
This package is a gem: it allows to make tab work dwim |
|
|
#+begin_src emacs-lisp |
|
|
(require 'smart-tab) |
|
|
(global-smart-tab-mode 1) |
|
|
#+end_src |
|
|
** Outshine |
|
|
#+begin_src emacs-lisp |
|
|
(defvar outline-minor-mode-prefix "\M-#") |
|
|
(setq outshine-use-speed-commands t) |
|
|
(with-eval-after-load 'outshine |
|
|
(define-key outshine-mode-map (kbd "C-M-i") nil)) |
|
|
(add-hook 'sh-mode-hook 'outshine-mode) |
|
|
(require 'outshine) |
|
|
(require 'outorg) |
|
|
#+end_src |
|
|
Cook up some extra narrowing function |
|
|
#+begin_src emacs-lisp |
|
|
(defun goto-next-comment-line () |
|
|
(interactive) |
|
|
(re-search-forward "^\\S<" nil 1) |
|
|
(re-search-forward "^\\s<" nil 1) |
|
|
(beginning-of-line) |
|
|
) |
|
|
|
|
|
(defun goto-previous-comment-line () |
|
|
(interactive) |
|
|
(re-search-backward "^\\s<" nil 1 ) |
|
|
(re-search-backward "^\\S<" nil 1 ) |
|
|
(beginning-of-line) |
|
|
(unless (bobp) (next-line)) |
|
|
) |
|
|
|
|
|
(defun narrow-between-comments () |
|
|
(interactive) |
|
|
(save-excursion |
|
|
(next-line) |
|
|
(goto-previous-comment-line) |
|
|
(setq beginning (point))) |
|
|
(save-excursion |
|
|
(goto-next-comment-line) |
|
|
(unless (bobp) (previous-line) (end-of-line)) |
|
|
(setq ending (point))) |
|
|
(narrow-to-region beginning ending)) |
|
|
|
|
|
#+end_src |
|
|
|
|
|
** helm |
|
|
#+begin_src emacs-lisp |
|
|
(require 'helm) |
|
|
(helm-mode 1) |
|
|
|
|
|
(defun fix-helm-margins () |
|
|
(setq helm-left-margin-width left-margin-width) |
|
|
(overlay-put helm-selection-overlay 'before-string |
|
|
(propertize "." 'display |
|
|
`((margin left-margin) ,(propertize "▶ " 'face 'default)))) |
|
|
) |
|
|
|
|
|
(global-set-key-alist |
|
|
'(("M-x" . helm-M-x) |
|
|
("C-x f" . helm-find-files) |
|
|
("C-x b" . helm-mini) |
|
|
("M-y" . helm-show-kill-ring) |
|
|
("C-c s" . helm-occur))) |
|
|
|
|
|
(define-key helm-map (kbd "C-h") nil) |
|
|
(define-key helm-find-files-map (kbd "C-h") nil) |
|
|
(define-key helm-find-files-map (kbd "C-<backspace>") nil) |
|
|
(define-key helm-read-file-map (kbd "C-<backspace>") nil) |
|
|
|
|
|
(helm-define-key-with-subkeys helm-find-files-map (kbd "DEL") ?\d 'helm-ff-delete-char-backward |
|
|
'(([C-c DEL] . helm-ff-run-toggle-auto-update)) |
|
|
nil 'helm-ff-delete-char-backward--exit-fn) |
|
|
(add-hook 'helm-after-initialize-hook 'fix-helm-margins) |
|
|
|
|
|
#+end_src |
|
|
Enable “better” buffer handling on two-column layouts |
|
|
#+begin_src emacs-lisp |
|
|
(setq helm-always-two-windows nil |
|
|
helm-split-window-inside-p t |
|
|
helm-display-header-line nil) |
|
|
#+end_src |
|
|
|
|
|
Customize ~helm-mini~ adding bookmarks for files and dirs. This |
|
|
is particularly useful to quickly open commonly accessed files |
|
|
that I keep in the bookmarks. As an exercise, I wonder if we |
|
|
could have per-server bookmarks. |
|
|
#+begin_src emacs-lisp |
|
|
(setq helm-mini-default-sources |
|
|
'(helm-source-buffers-list |
|
|
helm-source-recentf |
|
|
helm-source-bookmark-files&dirs |
|
|
helm-source-buffer-not-found)) |
|
|
#+end_src |
|
|
Fix helm margins the bad way |
|
|
#+begin_src emacs-lisp |
|
|
(defun helm-display-mode-line (source &optional force) |
|
|
"Set up mode-line and header-line for `helm-buffer'. |
|
|
|
|
|
SOURCE is a Helm source object. |
|
|
|
|
|
Optional argument FORCE forces redisplay of the Helm buffer's |
|
|
mode and header lines." |
|
|
(set (make-local-variable 'helm-mode-line-string) |
|
|
(helm-interpret-value (or (and (listp source) ; Check if source is empty. |
|
|
(assoc-default 'mode-line source)) |
|
|
(default-value 'helm-mode-line-string)) |
|
|
source)) |
|
|
(let ((follow (and (or (helm-follow-mode-p source) |
|
|
(and helm-follow-mode-persistent |
|
|
(member (assoc-default 'name source) |
|
|
helm-source-names-using-follow))) |
|
|
" (HF)")) |
|
|
(marked (and helm-marked-candidates |
|
|
(cl-loop with cur-name = (assoc-default 'name source) |
|
|
for c in helm-marked-candidates |
|
|
for name = (assoc-default 'name (car c)) |
|
|
when (string= name cur-name) |
|
|
collect c)))) |
|
|
;; Setup mode-line. |
|
|
(if helm-mode-line-string |
|
|
(setq mode-line-format |
|
|
`(:propertize |
|
|
("╺═╸ " mode-line-buffer-identification " " |
|
|
(:eval (format "L%-3d" (helm-candidate-number-at-point))) |
|
|
,follow |
|
|
" " |
|
|
(:eval ,(and marked |
|
|
(propertize |
|
|
(format "M%d" (length marked)) |
|
|
'face 'helm-visible-mark))) |
|
|
(:eval (when ,helm--mode-line-display-prefarg |
|
|
(let ((arg (prefix-numeric-value |
|
|
(or prefix-arg current-prefix-arg)))) |
|
|
(unless (= arg 1) |
|
|
(propertize (format " [prefarg:%s]" arg) |
|
|
'face 'helm-prefarg))))) |
|
|
" " |
|
|
(:eval (with-helm-buffer |
|
|
(helm-show-candidate-number |
|
|
(car-safe helm-mode-line-string)))) |
|
|
" " helm--mode-line-string-real " " |
|
|
(:eval (make-string (window-width) ? ))) |
|
|
keymap (keymap (mode-line keymap |
|
|
(mouse-1 . ignore) |
|
|
(down-mouse-1 . ignore) |
|
|
(drag-mouse-1 . ignore) |
|
|
(mouse-2 . ignore) |
|
|
(down-mouse-2 . ignore) |
|
|
(drag-mouse-2 . ignore) |
|
|
(mouse-3 . ignore) |
|
|
(down-mouse-3 . ignore) |
|
|
(drag-mouse-3 . ignore)))) |
|
|
helm--mode-line-string-real |
|
|
(substitute-command-keys (if (listp helm-mode-line-string) |
|
|
(cadr helm-mode-line-string) |
|
|
helm-mode-line-string))) |
|
|
(setq mode-line-format (default-value 'mode-line-format))) |
|
|
;; Setup header-line. |
|
|
(cond (helm-echo-input-in-header-line |
|
|
(setq force t) |
|
|
(helm--set-header-line)) |
|
|
(helm-display-header-line |
|
|
(let ((hlstr (helm-interpret-value |
|
|
(and (listp source) |
|
|
(assoc-default 'header-line source)) |
|
|
source)) |
|
|
(endstr (make-string (window-width) ? ))) |
|
|
(setq header-line-format |
|
|
(propertize (concat "··· " hlstr endstr) |
|
|
'face 'helm-header)))))) |
|
|
(when force (force-mode-line-update))) |
|
|
#+end_src |
|
|
** multiple-cursors |
|
|
#+begin_src emacs-lisp |
|
|
(require 'multiple-cursors) |
|
|
(define-key mc/keymap (kbd "<return>") nil) |
|
|
(global-set-key-alist |
|
|
'(("C->" . mc/mark-next-like-this) |
|
|
("C-<" . mc/mark-previous-like-this) |
|
|
("C-c C-<" . mc/mark-all-like-this) |
|
|
("C-c C-#" . mc/insert-numbers) |
|
|
("C-c C-\\" . mc/mark-all-dwim) |
|
|
("C-c <f17>" . mc/mark-pop) |
|
|
("C-c <f18>" . mc/mark-pop))) |
|
|
#+end_src |
|
|
** =avy= |
|
|
This packages allows fast navigation and action-ing |
|
|
It is probably a good idea to explore some smarter key-bindings |
|
|
#+begin_src emacs-lisp |
|
|
(require 'avy) |
|
|
(avy-setup-default) |
|
|
(global-set-key-alist |
|
|
'(("C-c C-j" . avy-resume) |
|
|
("C-SPC" . avy-goto-word-or-subword-1) |
|
|
("M-s" . avy-goto-word-or-subword-1) |
|
|
("M-g M-g" . avy-goto-line) |
|
|
("M-g g" . avy-goto-line))) |
|
|
(require 'ace-window) |
|
|
(global-set-key (kbd "M-o") 'ace-window) |
|
|
(setq aw-keys '(?f ?j ?d ?k ?s ?l ?a ?g ?h) |
|
|
aw-scope 'global |
|
|
aw-ignore-current t) |
|
|
(require 'ace-link) |
|
|
(ace-link-setup-default) |
|
|
(setq avy-styles-alist nil |
|
|
avy-background t) |
|
|
(define-key org-mode-map (kbd "C-c M-o") 'ace-link-org) |
|
|
(define-key org-agenda-mode-map (kbd "C-c M-o") 'ace-link-org) |
|
|
#+end_src |
|
|
** expand-region |
|
|
This is an excellent package, although I do not use it that much |
|
|
I should find a better binding |
|
|
#+begin_src emacs-lisp |
|
|
(require 'expand-region) |
|
|
(global-set-key (kbd "C-=") 'er/expand-region) |
|
|
#+end_src |
|
|
|
|
|
** ERC |
|
|
Enable ~erc dcc~ files transfer |
|
|
#+begin_src emacs-lisp |
|
|
(require 'erc-dcc) |
|
|
#+end_src |
|
|
** nov.el |
|
|
#+begin_src emacs-lisp |
|
|
(add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) |
|
|
(setq nov-text-width 70 |
|
|
nov-variable-pitch nil) |
|
|
(add-hook 'nov-mode-hook |
|
|
#'(lambda () (setq-local show-trailing-whitespace nil))) |
|
|
|
|
|
#+end_src |
|
|
** TODO Phase out Package.el |
|
|
Load package.el |
|
|
#+begin_src emacs-lisp |
|
|
(require 'package) |
|
|
(add-to-list 'package-archives |
|
|
'("melpa" . "http://melpa.org/packages/") t) |
|
|
(add-to-list 'package-archives |
|
|
'("melpa-stable" . "http://stable.melpa.org/packages/") t) |
|
|
#+end_src |
|
|
|
|
|
* Tidy-up |
|
|
** Save emacs-session files in appropriate directory |
|
|
Save session files to the ~sessions~ directory so that they do not litter |
|
|
the ~.emacs.d~ base directory. |
|
|
#+begin_src emacs-lisp |
|
|
(defun emacs-session-filename (session-id) |
|
|
"Construct a filename to save the session in based on SESSION-ID. |
|
|
If the directory ~/.emacs.d exists, we make a filename in there, otherwise |
|
|
a file in the home directory." |
|
|
(let ((basename (concat "sessions/session." session-id)) |
|
|
(emacs-dir user-emacs-directory)) |
|
|
(expand-file-name (if (file-directory-p emacs-dir) |
|
|
(concat emacs-dir basename) |
|
|
(concat "~/.emacs-" basename))))) |
|
|
#+end_src |
|
|
|
|
|
* Moving to colemak |
|
|
Set up all possible combinations of modifiers |
|
|
#+begin_src emacs-lisp |
|
|
(defun concat-recursive (b &optional a) |
|
|
(if b |
|
|
(append (concat-recursive (cdr b) (concat a (car b))) |
|
|
(concat-recursive (cdr b) a)) |
|
|
(when a (list a)))) |
|
|
|
|
|
(setq modifier-combo (concat-recursive '("C-" "M-" "s-" "H-"))) |
|
|
#+end_src |
|
|
Then list the swapped keys |
|
|
#+begin_src emacs-lisp |
|
|
(setq tarmak1-swap-alist '((?j . ?e) |
|
|
(?e . ?k) |
|
|
(?k . ?n) |
|
|
(?n . ?j))) |
|
|
(setq tarmak2-swap-alist '((?j . ?g) |
|
|
(?g . ?t) |
|
|
(?t . ?f) |
|
|
(?f . ?e) |
|
|
(?e . ?k) |
|
|
(?k . ?n) |
|
|
(?n . ?j))) |
|
|
(setq tarmak3-swap-alist '((?j . ?r) |
|
|
(?r . ?s) |
|
|
(?s . ?d) |
|
|
(?d . ?g) |
|
|
(?g . ?t) |
|
|
(?t . ?f) |
|
|
(?f . ?e) |
|
|
(?e . ?k) |
|
|
(?k . ?n) |
|
|
(?n . ?j))) |
|
|
|
|
|
(setq tarmak-current-swap-alist tarmak3-swap-alist) |
|
|
#+end_src |
|
|
|
|
|
#+begin_src emacs-lisp |
|
|
(defun set-key-translation-map (pairs) |
|
|
(dolist (p pairs) |
|
|
(define-key key-translation-map (kbd (downcase (car p))) (kbd (downcase (cdr p)))) |
|
|
(define-key key-translation-map (kbd (upcase (car p))) (kbd (upcase (cdr p)))))) |
|
|
|
|
|
(defun set-key-translation-map-caseless (pairs) |
|
|
(dolist (p pairs) |
|
|
(define-key key-translation-map (kbd (car p)) (kbd (cdr p))))) |
|
|
|
|
|
(defun reset-key-translation-map () |
|
|
(dolist (c (mapcar 'cdr tarmak-current-swap-alist)) |
|
|
(define-key key-translation-map (kbd (downcase c)) nil) |
|
|
(define-key key-translation-map (kbd (upcase c)) nil))) |
|
|
|
|
|
(defun untarmak-modifiers () |
|
|
(interactive) |
|
|
(set-key-translation-map-caseless |
|
|
(mapcan (lambda (swap-pair) |
|
|
(mapcar (lambda (mod-combo) |
|
|
(cons (concat mod-combo |
|
|
(string (car swap-pair))) |
|
|
(concat mod-combo |
|
|
(string (cdr swap-pair))))) |
|
|
modifier-combo)) |
|
|
tarmak-current-swap-alist))) |
|
|
|
|
|
(defun untarmak-key (char) |
|
|
(let ((pair (rassoc char tarmak-current-swap-alist))) |
|
|
(if pair (car pair) char))) |
|
|
|
|
|
(defun tarmak-avy () |
|
|
(interactive) |
|
|
(setq aw-keys '(?n ?t ?h ?d ?e ?s ?i ?r ?o ?a) |
|
|
avy-keys '(?n ?t ?h ?d ?e ?s ?i ?r ?o ?a))) |
|
|
|
|
|
(defun qwerty() |
|
|
(interactive) |
|
|
|
|
|
(reset-key-translation-map)) |
|
|
|
|
|
;; (untarmak-modifiers) |
|
|
(tarmak-avy) |
|
|
#+end_src |
|
|
* Finale |
|
|
** Fixup faces |
|
|
This is really a workaround as I do not like either bold or italic. |
|
|
It needs to be at the end of the file since it sets the face for |
|
|
packages that have loaded in the meantime; yet it does not work |
|
|
perfectly as some packages are still to be loaded (most notably ~magit~) |
|
|
#+begin_src emacs-lisp |
|
|
(set-face-bold 'bold nil) |
|
|
(wilder/fixup-faces) |
|
|
(with-eval-after-load "info" (wilder/fixup-faces) nil) |
|
|
(with-eval-after-load "mu4e" (wilder/fixup-faces) nil) |
|
|
(with-eval-after-load "mm-view" (wilder/fixup-faces) nil) |
|
|
(with-eval-after-load 'gnus-cite (wilder/fixup-faces) nil) |
|
|
(add-hook 'gnus-art-load-hook #'wilder/fixup-faces) |
|
|
#+end_src |
|
|
|
|
|
|