;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*- ;; by Case Duckworth ;; Bankruptcy 10: "Annoyance" ;;; Commentary: ;; This is my Emacs configuration. There are many like it but this ;; one is mine. ;; ;; For the tenth time! ;;; Code: (add-hook 'after-init-hook (lambda () (load (locate-user-emacs-file "private")))) ;;; Definitions: (defun reset-faces () (dolist (face '(font-lock-regexp-face font-lock-builtin-face font-lock-variable-name-face font-lock-preprocessor-face font-lock-remove-face font-lock-delimiter-face font-lock-label-face font-lock-operator-face font-lock-property-face font-lock-builtin-face font-lock-number-face font-lock-keyword-face font-lock-set-face font-lock-warning-face font-lock-punctuation-face font-lock-constant-face font-lock-type-face font-lock-function-name-face font-lock-reference-face font-lock-misc-punctuation-face font-lock-bracket-face)) (face-spec-set face '((t :foreground unspecified :background unspecified))))) (defun electric-pair-local-mode-disable () "Disable `electric-pair-mode', locally." (electric-pair-local-mode -1)) (defun kill-this-buffer (&optional buffer-or-name) "Kill this buffer, or BUFFER-OR-NAME. When called interactvely, the user will be prompted when passing \\[universal-argument]." (interactive "P") (cond ((bufferp buffer-or-name) (kill-buffer buffer-or-name)) ((null buffer-or-name) (kill-current-buffer)) (:else (kill-buffer (read-buffer "Kill: " nil :require-match))))) (defun define-org-capture-template (description &rest args) "Define an template for `org-capture-templates'. Will not replace an existing template unless `:force' in ARGS is non-nil. ARGS is a plist, which in addition to the additional options `org-capture-templates' accepts (which see), also accepts the following: `:keys', `:description', `:type', `:target', and `:template'." (declare (indent 1)) (let* ((keys (plist-get args :keys)) (type (plist-get args :type)) (target (plist-get args :target)) (template (plist-get args :template)) (force (plist-get args :force)) (template-value (append (list description) (when (or type target template) (list (or type 'entry) target template)) (cl-loop for i from 0 below (length args) by 2 unless (member (nth i args) '( :keys :description :type :target :template)) append (list (nth i args) (plist-get args (nth i args))))))) (if (seq-find (lambda (el) (equal (car el) keys)) org-capture-templates) (and force (setf (alist-get keys org-capture-templates nil nil #'equal) template-value)) (setf org-capture-templates (append org-capture-templates (list (cons keys template-value))))) org-capture-templates)) (defun other-window-or-switch-buffer (&optional arg) "Switch to the other window. If a window is the only buffer on a frame, switch buffer. When run with \\[universal-argument], unconditionally switch buffer." (interactive "P") (if (or arg (one-window-p)) (switch-to-buffer (other-buffer) nil t) (other-window 1))) (defun cycle-spacing@ (&optional n) ;; `cycle-spacing' is wildly different in 29.1 over 28. "Negate N argument on `cycle-spacing'. That is, with a positive N, deletes newlines as well, leaving -N spaces. If N is negative, it will not delete newlines and leave N spaces." (interactive "*p") (cycle-spacing (- n))) (defun first-frame@set-fonts () (remove-hook 'server-after-make-frame-hook #'first-frame@set-fonts) (face-spec-set 'default `((t :family "Recursive Mono Casual Static" :height 110))) (face-spec-set 'variable-pitch `((t :family "Recursive Sans Casual Static" :height 1.0))) ;; Emojis (cl-loop with ffl = (font-family-list) for font in '("Noto Emoji" "Noto Color Emoji" "Segoe UI Emoji" "Apple Color Emoji" "FreeSans" "FreeMono" "FreeSerif" "Unifont" "Symbola") if (member font ffl) do (set-fontset-font t 'symbol font)) ;; International fonts (cl-loop with ffl = (font-family-list) for (charset . font) in '((latin . "Noto Sans") (han . "Noto Sans CJK SC Regular") (kana . "Noto Sans CJK JP Regular") (hangul . "Noto Sans CJK KR Regular") (cjk-misc . "Noto Sans CJK KR Regular") (khmer . "Noto Sans Khmer") (lao . "Noto Sans Lao") (burmese . "Noto Sans Myanmar") (thai . "Noto Sans Thai") (ethiopic . "Noto Sans Ethiopic") (hebrew . "Noto Sans Hebrew") (arabic . "Noto Sans Arabic") (gujarati . "Noto Sans Gujarati") (devanagari . "Noto Sans Devanagari") (kannada . "Noto Sans Kannada") (malayalam . "Noto Sans Malayalam") (oriya . "Noto Sans Oriya") (sinhala . "Noto Sans Sinhala") (tamil . "Noto Sans Tamil") (telugu . "Noto Sans Telugu") (tibetan . "Noto Sans Tibetan")) if (member font ffl) do (set-fontset-font t charset font)) ;; XXX: tab-bar does a weird thing, so i set it up here.... (setopt tab-bar-show t) (tab-bar-mode)) (defun renz/sort-by-alpha-length (elems) "Sort ELEMS first alphabetically, then by length." (sort elems (lambda (c1 c2) (or (string-version-lessp c1 c2) (< (length c1) (length c2)))))) (defun renz/sort-by-history (elems) "Sort ELEMS by minibuffer history. Use `mct-sort-sort-by-alpha-length' if no history is available." (if-let ((hist (and (not (eq minibuffer-history-variable t)) (symbol-value minibuffer-history-variable)))) (minibuffer--sort-by-position hist elems) (renz/sort-by-alpha-length elems))) (defun renz/completion-category () "Return completion category." (when-let ((window (active-minibuffer-window))) (with-current-buffer (window-buffer window) (completion-metadata-get (completion-metadata (buffer-substring-no-properties (minibuffer-prompt-end) (max (minibuffer-prompt-end) (point))) minibuffer-completion-table minibuffer-completion-predicate) 'category)))) (defun renz/sort-multi-category (elems) "Sort ELEMS per completion category." (pcase (renz/completion-category) ('nil elems) ; no sorting ('kill-ring elems) ('project-file (renz/sort-by-alpha-length elems)) (_ (renz/sort-by-history elems)))) (defvar no-tabs-modes '(emacs-lisp-mode lisp-mode scheme-mode python-mode haskell-mode) "Modes /not/ to indent with tabs.") (defun indent-tabs-mode-maybe () (if (apply #'derived-mode-p no-tabs-modes) (indent-tabs-mode -1) (indent-tabs-mode 1))) (define-minor-mode truncate-lines-mode "Buffer-local mode to toggle `truncate-lines'." :lighter "" (setq-local truncate-lines truncate-lines-mode)) ;;; Region or buffer stuff (defun call-with-region-or-buffer (fn &rest _r) "Call function FN with current region or buffer. Good to use for :around advice." (if (region-active-p) (funcall fn (region-beginning) (region-end)) (funcall fn (point-min) (point-max)))) (defun delete-trailing-whitespace-except-current-line () (save-excursion (delete-trailing-whitespace (point-min) (line-beginning-position)) (delete-trailing-whitespace (line-end-position) (point-max)))) (defun create-missing-directories () "Automatically create missing directories." (let ((target-dir (file-name-directory buffer-file-name))) (unless (file-exists-p target-dir) (make-directory target-dir :parents)))) (defun vc-remote-off () "Turn VC off when remote." (when (file-remote-p (buffer-file-name)) (setq-local vc-handled-backends nil))) (defun +titlecase-sentence-style-dwim (&optional arg) "Titlecase a sentence. With prefix ARG, toggle the value of `titlecase-downcase-sentences' before sentence-casing." (interactive "P") (let ((titlecase-downcase-sentences (if arg (not titlecase-downcase-sentences) titlecase-downcase-sentences))) (titlecase-dwim 'sentence))) (defun +titlecase-org-headings () (interactive) (require 'org) (save-excursion (goto-char (point-min)) ;; See also `org-map-tree'. I'm not using that function because I want to ;; skip the first headline. A better solution would be to patch ;; `titlecase-line' to ignore org-mode metadata (TODO cookies, tags, etc). (let ((level (funcall outline-level)) (org-special-ctrl-a/e t)) (while (and (progn (outline-next-heading) (> (funcall outline-level) level)) (not (eobp))) (titlecase-region (progn (org-beginning-of-line) (point)) (progn (org-end-of-line) (point))))))) (defcustom browse-url-safe-browser-functions nil "\"Safe\" browser functions." :type '(repeat-function)) (defun browse-url-browser-function-safe-p (fn) "Return t if FN is a \"safe\" browser function." (memq f (append browse-url-safe-browser-functions (mapcar (lambda (i) (plist-get (cdr i) :value)) (seq-filter (lambda (i) (eq (car i) 'function-item)) (cdr (get 'browse-url-browser-function 'custom-type))))))) (put 'browse-url-browser-function 'safe-local-variable 'browse-url-browser-function-safe-p) ;;; Packages: (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (defun ensure-package (pkg &optional localp) "Esnure PKG is installed from repositories. If LOCALP is t, add ~/src/PKG.el to `load-path'. If LOCALP is a string, add that directory to the `load-path'." (cond ((stringp localp) (and (file-exists-p localp) (add-to-list 'load-path localp))) (localp (ensure-package pkg (expand-file-name (format "~/src/%s.el" (symbol-name pkg))))) (:else (unless (package-installed-p pkg) (unless (ignore-errors (package-install pkg)) (package-refresh-contents) (package-install pkg)))))) ;; Install packages here. Acutal configuration is done in the Configuration ;; section. (ensure-package 'consult) (ensure-package 'marginalia) (ensure-package 'visual-fill-column) (ensure-package 'adaptive-wrap) (ensure-package 'geiser) (when (executable-find "csi") (ensure-package 'geiser-chicken)) (ensure-package 'avy) (ensure-package 'zzz-to-char) (ensure-package 'hungry-delete) (ensure-package 'undohist) (ensure-package 'jinx) (ensure-package 'markdown-mode) (ensure-package 'anzu) ;; Local packages (ensure-package 'scule t) (ensure-package 'frowny t) (ensure-package 'hippie-completing-read t) (ensure-package 'mode-line-bell t) (ensure-package 'titlecase t) (ensure-package 'jabber t) ;;; Jabber (use-package jabber :load-path "~/src/jabber.el" :defer t :bind-keymap (("C-c j" . jabber-global-keymap)) :preface nil (setq-default jabber-chat-buffer-format "*%n*" jabber-browse-buffer-format "*%n*" jabber-groupchat-buffer-format "*%n*" jabber-muc-private-buffer-format "*%n*") :custom-face (jabber-activity-face ((t :inherit jabber-chat-prompt-foreign :foreground unspecified :weight normal))) (jabber-activity-personal-face ((t :inherit jabber-chat-prompt-local :foreground unspecified :weight bold))) (jabber-chat-prompt-local ((t :inherit minibuffer-prompt :foreground unspecified :weight normal :slant italic))) (jabber-chat-prompt-foreign ((t :inherit warning :foreground unspecified :weight normal))) (jabber-chat-prompt-system ((t :inherit font-lock-doc-face :foreground unspecified))) (jabber-rare-time-face ((t :inherit font-lock-comment-face :foreground unspecified :underline nil))) :config (require 'jabber-httpupload nil t) (setopt jabber-auto-reconnect t jabber-last-read-marker "~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~" jabber-muc-decorate-presence-patterns '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil) ("Mode #.*" . jabber-muc-presence-dim) ("." . jabber-muc-presence-dim)) jabber-activity-make-strings #'jabber-activity-make-strings-shorten jabber-rare-time-format (format " - - - - - %%H:%d %%F" (let ((min (string-to-number (format-time-string "%M")))) (* 5 (floor min 5)))) jabber-muc-header-line-format '(" " jabber-muc-topic)) (setopt jabber-groupchat-prompt-format "%n. " jabber-chat-local-prompt-format "%n. " jabber-chat-foreign-prompt-format "%n. " jabber-muc-private-foreign-prompt-format "%g/%n. ") (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to) (map-keymap (lambda (key command) (define-key jabber-global-keymap (vector (+ key #x60)) command)) jabber-global-keymap) (keymap-global-set "C-x C-j" #'dired-jump) (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) (remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo) (remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo) (add-hook 'jabber-chat-mode-hook 'visual-line-mode) (add-hook 'jabber-chat-mode-hook (defun jabber-no-position () (setq-local mode-line-position nil))) (add-hook 'jabber-alert-muc-hooks (defun jabber@highlight-acdw (&optional _ _ buf _ _) (when buf (with-current-buffer buf (let ((regexp (rx word-boundary "acdw" ; maybe get from the config? word-boundary))) (hi-lock-unface-buffer regexp) (highlight-regexp regexp 'jabber-chat-prompt-local)))))) (when (fboundp 'jabber-chat-update-focus) (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus))) ;;; Configuration: (setopt custom-file (locate-user-emacs-file "custom.el")) (load custom-file :noerror) ;;; General keybinding changes (keymap-global-set "M-o" #'other-window-or-switch-buffer) (keymap-global-set "M-SPC" #'cycle-spacing@) (keymap-global-set "M-u" #'universal-argument) (keymap-set universal-argument-map "M-u" #'universal-argument-more) ;;; Theme (if (daemonp) (add-hook 'server-after-make-frame-hook #'first-frame@set-fonts) (run-with-idle-timer 1 nil #'first-frame@set-fonts)) (tool-bar-mode -1) (setopt modus-themes2-bold-constructs nil modus-themes-italic-constructs t modus-themes-variable-pitch-ui t) (add-hook 'modus-themes-after-load-theme-hook #'reset-faces) (load-theme 'modus-vivendi :no-confirm :no-enable) (load-theme 'modus-operandi :no-confirm) (add-hook 'text-mode-hook #'visual-line-mode) (add-hook 'prog-mode-hook #'auto-fill-mode) (add-hook 'prog-mode-hook #'display-fill-column-indicator-mode) ;;; Mode line (defvar mode-line-position '("" (:propertize ("" (:eval (if line-number-mode "%3l" "")) (:eval (if column-number-mode (if column-number-indicator-zero-based "/%2c" "/%2C") ""))) display (min-width (3.0))) (:propertize (" [" (-3 "%p") "] ") display (min-width (6.0))))) (setopt mode-line-format '(("%e" mode-line-front-space (:propertize ("" mode-line-client mode-line-modified mode-line-remote) display (min-width (3.0))) " " mode-line-buffer-identification (vc-mode (" (" (:eval (string-trim vc-mode)) ")")) " " (mode-line-position mode-line-position) mode-line-modes mode-line-misc-info mode-line-end-spaces))) ;; Remove modes from mode-line (dolist (minor-mode '(frowny-mode whitespace-mode hungry-delete-mode)) (setf (alist-get minor-mode minor-mode-alist) (list "")) (add-hook (intern (format "%s-hook" minor-mode)) (lambda () (setf (alist-get minor-mode minor-mode-alist) (list ""))))) ;;; Completion & minibuffer (fido-vertical-mode) (minibuffer-depth-indicate-mode) (setopt completion-auto-help (not icomplete-mode) completion-auto-select 'second-tab completions-header-format nil completions-max-height 12 completions-format 'one-column completion-styles '(basic partial-completion emacs22 flex) completion-ignore-case t read-buffer-completion-ignore-case t read-file-name-completion-ignore-case t completions-detailed t enable-recursive-minibuffers t file-name-shadow-properties '(invisible t intangible t) minibuffer-eldef-shorten-default t minibuffer-prompt-properties '( read-only t cursor-intangible t face minibuffer-prompt) window-resize-pixelwise t frame-resize-pixelwise t) (add-hook 'completion-list-mode-hook #'truncate-lines-mode) (add-hook 'minibuffer-setup-hook #'truncate-lines-mode) ;; Up/down when completing in the minibuffer ;; (define-key minibuffer-local-map (kbd "C-p") #'minibuffer-previous-completion) ;; (define-key minibuffer-local-map (kbd "C-n") #'minibuffer-next-completion) ;; Up/down when competing in a normal buffer ;; (define-key completion-in-region-mode-map (kbd "C-p") #'minibuffer-previous-completion) ;; (define-key completion-in-region-mode-map (kbd "C-n") #'minibuffer-next-completion) (setopt completions-sort #'renz/sort-multi-category) (setopt tab-always-indent 'complete) (file-name-shadow-mode) (minibuffer-electric-default-mode) (scroll-bar-mode -1) (menu-bar-mode -1) (add-hook 'prog-mode-hook #'indent-tabs-mode-maybe) (setopt electric-pair-skip-whitespace 'chomp) (electric-pair-mode) (setopt sh-basic-offset tab-width) (keymap-set emacs-lisp-mode-map "C-c C-c" #'eval-defun) (keymap-set emacs-lisp-mode-map "C-c C-k" #'eval-buffer) (keymap-set lisp-interaction-mode-map "C-c C-c" #'eval-defun) (keymap-set lisp-interaction-mode-map "C-c C-k" #'eval-buffer) (advice-add 'indent-region :around #'call-with-region-or-buffer) (advice-add 'tabify :around #'call-with-region-or-buffer) (advice-add 'untabify :around #'call-with-region-or-buffer) (with-eval-after-load 'scheme (keymap-unset scheme-mode-map "M-o" t) ;; Comparse "keywords" --- CHICKEN (http://wiki.call-cc.org/eggref/5/comparse) (put 'sequence* 'scheme-indent-function 1) (put 'satisfies 'scheme-indent-function 1) (add-hook 'scheme-mode-hook #'geiser-mode)) (with-eval-after-load 'geiser-mode (keymap-set geiser-mode-map "C-c C-k" #'geiser-eval-buffer-and-go) (keymap-unset geiser-mode-map "C-." t)) (with-eval-after-load 'visual-fill-column (setopt visual-fill-column-center-text t visual-fill-column-width (+ fill-column 2)) (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust)) (add-hook 'visual-line-mode-hook #'visual-fill-column-mode) (add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode) (setopt major-mode (lambda () ; guess major mode from buffer name (unless buffer-file-name (let ((buffer-file-name (buffer-name))) (set-auto-mode))))) ;; Dialogs (unless (boundp 'use-short-answers) (fset 'yes-or-no-p 'y-or-n-p)) (setopt read-answer-short t use-dialog-box nil use-file-dialog nil use-short-answers t) (require 'savehist) (setopt history-length 1024 history-delete-duplicates t ;; savehist-file (etc/ "savehist.el") savehist-save-minibuffer-history t savehist-autosave-interval 30) (savehist-mode) ;; Killing and yanking (setopt kill-do-not-save-duplicates t kill-read-only-ok t ;; XXX: This setting causes an error message the first time it's ;; called: "Selection owner couldn't convert: TIMESTAMP". I have ;; absolutely no idea why I get this error, but it's generated in ;; `x_get_foreign_selection'. I also can't inhibit the message or ;; do anything else with it, so for now, I'll just live with the ;; message. save-interprogram-paste-before-kill t yank-pop-change-selection t) (delete-selection-mode) ;; Notifying the user (setopt echo-keystrokes 0.01 ring-bell-function #'ignore) ;; Point and mark (setopt set-mark-command-repeat-pop t) ;; The system (setopt read-process-output-max (* 10 1024 1024)) ;; Startup (setopt inhibit-startup-screen t initial-buffer-choice t initial-scratch-message nil) (define-advice startup-echo-area-message (:override ()) (if (get-buffer "*Warnings*") ";_;" "^_^")) ;; Text editing (setopt fill-column 80 sentence-end-double-space nil tab-width 8 tab-always-indent 'complete) (global-so-long-mode) (setopt show-paren-delay 0.01 show-paren-style 'parenthesis show-paren-when-point-in-periphery t show-paren-when-point-inside-paren t) (show-paren-mode) ;; Encodings (set-language-environment "UTF-8") (setopt buffer-file-coding-system 'utf-8-unix coding-system-for-read 'utf-8-unix coding-system-for-write 'utf-8-unix default-process-coding-system '(utf-8-unix . utf-8-unix) locale-coding-system 'utf-8-unix) (set-charset-priority 'unicode) (prefer-coding-system 'utf-8-unix) (set-default-coding-systems 'utf-8-unix) (set-terminal-coding-system 'utf-8-unix) (set-keyboard-coding-system 'utf-8-unix) (pcase system-type ((or 'ms-dos 'windows-nt) (set-clipboard-coding-system 'utf-16-le) (set-selection-coding-system 'utf-16-le)) (_ (set-selection-coding-system 'utf-8) (set-clipboard-coding-system 'utf-8))) ;; Files (setopt auto-revert-verbose nil global-auto-revert-non-file-buffers t create-lockfiles nil find-file-visit-truename t mode-require-final-newline t view-read-only t save-silently t) (global-auto-revert-mode) (setopt auto-save-default nil auto-save-interval 1 auto-save-no-message t auto-save-timeout 1 auto-save-visited-interval 1 remote-file-name-inhibit-auto-save-visited t) (add-to-list 'auto-save-file-name-transforms `(".*" ,(locate-user-emacs-file "auto-save/") t)) (auto-save-visited-mode) (setopt backup-by-copying t version-control t kept-new-versions 8 kept-old-versions 8 delete-old-versions t) (setq-default backup-directory-alist `(("^/dev/shm" . nil) ("^/tmp" . nil) (,(getenv "XDG_RUNTIME_DIR") . nil) ("." . ,(locate-user-emacs-file "backup")))) (require 'recentf) (setopt recentf-max-menu-items 500 recentf-max-saved-items nil ; Save the whole list recentf-auto-cleanup 'mode recentf-case-fold-search t) ;; (add-to-list 'recentf-exclude etc/) (add-to-list 'recentf-exclude "-autoloads.el\\'") (add-hook 'buffer-list-update-hook #'recentf-track-opened-file) (add-hook 'after-save-hook #'recentf-save-list) (recentf-mode) (require 'saveplace) (setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux)) (save-place-mode) (require 'uniquify) (setq uniquify-after-kill-buffer-p t uniquify-buffer-name-style 'forward uniquify-ignore-buffers-re "^\\*" uniquify-separator path-separator) (setq-local vc-follow-symlinks t vc-make-backup-files t) ;; Whitespace (require 'whitespace) (setopt whitespace-style '(face trailing tabs tab-mark)) (global-whitespace-mode) (add-hook 'before-save-hook #'delete-trailing-whitespace-except-current-line) ;; Native compilation (setopt native-comp-async-report-warnings-errors 'silent native-comp-deferred-compilation t native-compile-target-directory (locate-user-emacs-file "eln")) (when (boundp 'native-comp-eln-load-path) (add-to-list 'native-comp-eln-load-path native-compile-target-directory)) (when (fboundp 'startup-redirect-eln-cache) (startup-redirect-eln-cache native-compile-target-directory)) (global-goto-address-mode) ;; Winner (winner-mode) ;;; Hooks (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) (add-hook 'find-file-not-found-functions #'create-missing-directories) (add-hook 'find-file-hook #'vc-remote-off) (add-hook 'dired-mode-hook #'hl-line-mode) (add-hook 'org-agenda-mode-hook #'hl-line-mode) ;;; Tab bar (defun tab-bar-end-space () `((end menu-item " " ignore))) (add-to-list 'tab-bar-format 'tab-bar-format-align-right :append) (add-to-list 'tab-bar-format 'tab-bar-format-global :append) (add-to-list 'tab-bar-format 'tab-bar-end-space :append) ;;(setopt tab-bar-show t) ;;(tab-bar-mode) ; done after setting fonts ;;; Org mode (keymap-global-set "C-c a" #'org-agenda) (keymap-global-set "C-c c" #'org-capture) (setopt org-clock-clocked-in-display 'frame-title org-clock-frame-title-format '("%b" " - " (t org-mode-line-string)) org-tags-column (- (- fill-column 3)) org-log-into-drawer t org-clock-into-drawer t) ;;; Spelling (defun list-of-strings-p (x) "Is X a list of strings?" (and x (listp x) (cl-every #'stringp x))) (put 'ispell-local-words 'safe-local-variable 'list-of-strings-p) (add-hook 'text-mode-hook #'jinx-mode) (with-eval-after-load 'jinx (keymap-set jinx-mode-map "M-$" #'jinx-correct) (keymap-set jinx-mode-map "C-M-$" #'jinx-languages)) ;;; org-return-dwim ;; https://github.com/alphapapa/unpackaged.el, ;; http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/ (defun org-return-dwim (&optional arg) "A helpful replacement for `org-return'. When called interactively with \\[universal-argument], call `org-return' itself. Other values of ARG will call `newline' with that ARG." (interactive "P") ;; Auto-fill if enabled (when auto-fill-function (dolist (func (ensure-list auto-fill-function)) (funcall func))) (cl-letf* ((el (org-element-at-point)) ((symbol-function 'el-child-of) (lambda (&rest types) (org-element-lineage el types t)))) (cond ; Figure out what we're going to do (arg ; Handle prefix ARG (pcase arg ('(4) (org-return t nil t)) (_ (newline arg t)))) ((and org-return-follows-link ; Open a link (el-child-of 'link)) (org-open-at-point-global)) ((org-at-heading-p) ; Open a paragraph after a heading (let ((heading-start (org-entry-beginning-position))) (goto-char (org-entry-end-position)) (cond ((and (org-at-heading-p) ; Entry is only a heading (= heading-start (org-entry-beginning-position))) (end-of-line) (newline 2)) (:else ; Entry is more than a heading (forward-line -1) (end-of-line) (when (org-at-heading-p) ;; Open a paragraph (forward-line) (newline) (forward-line -1)) (while (not (looking-back "\\(?:[[:blank:]]?\n\\)\\{3\\}" nil)) (newline)) (forward-line -1))))) ((org-at-item-checkbox-p) ; Insert a new checkbox item (end-of-line) (org-insert-todo-heading nil)) ((org-in-item-p) ; Insert a new list item (let* ((context (org-element-context el)) (first-item-p (eq 'plain-list (car context))) (itemp (eq 'item (car context))) (emptyp (or ;; This (regular) list item is empty (eq (org-element-property :contents-begin context) (org-element-property :contents-end context)) ;; This (definition) list item is empty (looking-at " *::"))) (item-child-p (el-child-of 'item))) (cond ((and itemp emptyp) ;; This test has to be here even though it's the same as the ;; :else clause, because an item that's empty will also satisfy ;; the next clause. (delete-region (line-beginning-position) (line-end-position)) (newline)) ((or first-item-p (and itemp (not emptyp)) item-child-p) (org-end-of-item) (org-insert-item)) (:else (delete-region (line-beginning-position) (line-end-position)) (newline))))) ((and (fboundp 'org-inlinetask-in-task-p) ; Just return for inline tasks (org-inlinetask-in-task-p)) (org-return)) ((org-at-table-p) ; Insert a new table row (cond ((save-excursion ; Empty row: end the table (beginning-of-line) (cl-loop with end = (line-end-position) for cell = (org-element-table-cell-parser) always (eq (org-element-property :contents-begin cell) (org-element-property :contents-end cell)) while (re-search-forward "|" end t))) (delete-region (line-beginning-position) (line-end-position)) (org-return)) (:else ; Non-empty row (org-return)))) (:else ; Something else (org-return))))) (defun org-table-copy-down|org-return-dwim (&optional n) "Call `org-table-copy-down' or `+org-return' depending on context." (interactive "P") (if (org-table-check-inside-data-field 'noerror) (org-table-copy-down (or n 1)) (org-return-dwim n))) (with-eval-after-load 'org (keymap-set org-mode-map "RET" #'org-return-dwim) (keymap-set org-mode-map "S-RET" #'org-table-copy-down|org-return-dwim)) ;;; Copy rich text to the keyboard (defcustom clipboard-html-copy-program (if (or (equal "wayland" (getenv "XDG_SESSION_TYPE")) (getenv "WAYLAND_DISPLAY")) '("wl-copy" "-t" "text/html") '("xclip" "-t" "text/html" "-selection" "clipboard")) "Program to use to copy HTML to the clipboard. Should be a list of strings---the command line. Defaults to 'wl-copy' on wayland and 'xclip' on Xorg." :type '(repeat string)) ;; Thanks to Oleh Krehel: ;; https://emacs.stackexchange.com/questions/54292/copy-results-of-org-export-directly-to-clipboard ;; So. Emacs can't do this itself because it doesn't support sending clipboard ;; or selection contents as text/html. We have to use xclip instead. ;; (defun org-to-html-to-clipboard (&rest org-export-args) ;; "Export current org buffer to HTML, then copy it to the clipboard. ;; ORG-EXPORT-ARGS are passed to `org-export-to-file'." ;; (let ((f (make-temp-file "org-html-export"))) ;; (apply #'org-export-to-file 'html f org-export-args) ;; (start-process "xclip" " *xclip*" ;; "xclip" "-verbose" "-i" f ;; "-t" "text/html" "-selection" "clipboard") ;; (message "HTML pasted to clipboard."))) (defun org-export-html-copy (&rest org-export-args) "Export current org buffer to HTML and copy to clipboard as rich text. ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." (let ((buf (generate-new-buffer "*org-html-clipboard*" t))) (apply #'org-export-to-buffer 'html buf org-export-args) (with-current-buffer buf (apply #'call-process-region (point-min) (point-max) (car clipboard-html-copy-program) nil ; don't delete text nil ; discard the output nil ; don't redisplay (cdr clipboard-html-copy-program)) (kill-buffer-and-window)) (message "HTML copied to clipboard."))) ;; Wayland version.. TODO: make it work for both ;; (defun org-to-html-to-clipboard (&rest org-export-args) ;; "Export current org buffer to HTML, then copy it to the clipboard. ;; ORG-EXPORT-ARGS are passed to `org-export-to-file'." ;; (let ((buf (generate-new-buffer "*org-html-clipboard*" t))) ;; (apply #'org-export-to-buffer 'html buf org-export-args) ;; (with-current-buffer buf ;; (call-process-region (point-min) (point-max) ;; "wl-copy" nil nil nil ;; "-t" "text/html") ;; (kill-buffer-and-window)) ;; (message "HTML copied to clipboard."))) (defun org-subtree-to-html-to-clipboard () "Export current subtree to HTML." (interactive) (org-export-html-copy nil :subtree)) (undohist-initialize) (require 'hungry-delete) (setopt hungry-delete-chars-to-skip " \t" hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip) hungry-delete-join-reluctantly nil) (add-to-list 'hungry-delete-except-modes 'eshell-mode) (add-to-list 'hungry-delete-except-modes 'nim-mode) (add-to-list 'hungry-delete-except-modes 'python-mode) (global-hungry-delete-mode) (setopt avy-background t avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm")) (keymap-global-set "M-j" #'avy-goto-char-timer) (keymap-set isearch-mode-map "M-j" #'avy-isearch) (keymap-global-set "M-z" #'zzz-to-char) (marginalia-mode) (keymap-global-set "C-x b" #'consult-buffer) (keymap-global-set "C-x 4 b" #'consult-buffer-other-window) (keymap-global-set "C-x 5 b" #'consult-buffer-other-frame) (keymap-global-set "C-x r b" #'consult-bookmark) (keymap-global-set "M-y" #'consult-yank-pop) (keymap-global-set "M-g g" #'consult-goto-line) (keymap-global-set "M-g M-g" #'consult-goto-line) (keymap-global-set "M-g o" #'consult-outline) (keymap-global-set "M-g m" #'consult-mark) (keymap-global-set "M-g i" #'consult-imenu) (keymap-global-set "M-s d" #'consult-find) (keymap-global-set "M-s D" #'consult-locate) (keymap-global-set "M-s g" #'consult-grep) (keymap-global-set "M-s G" #'consult-git-grep) (keymap-global-set "M-s r" #'consult-ripgrep) (keymap-global-set "M-s l" #'consult-line) (keymap-global-set "M-s k" #'consult-keep-lines) (keymap-global-set "M-s u" #'consult-focus-lines) (keymap-global-set "M-s e" #'consult-isearch-history) (keymap-set isearch-mode-map "M-e" #'consult-isearch-history) (keymap-set isearch-mode-map "M-s e" #'consult-isearch-history) (keymap-set isearch-mode-map "M-s l" #'consult-line) (keymap-set minibuffer-local-map "M-n" #'consult-history) (keymap-set minibuffer-local-map "M-p" #'consult-history) (setopt completion-in-region-function #'consult-completion-in-region xref-show-xrefs-function #'consult-xref xref-show-definitions-function #'consult-xref) (setopt initial-scratch-message ";;; Emacs!\n\n") (keymap-global-set "C-x C-b" #'ibuffer) (add-hook 'ibuffer-hook #'hl-line-mode) (require 'scule) (keymap-global-set "M-c" scule-map) (autoload 'titlecase-dwim "titlecase" nil t) (keymap-set scule-map "M-t" #'titlecase-dwim) ;; Use M-u for prefix keys (keymap-global-set "M-u" #'universal-argument) (keymap-set universal-argument-map "M-u" #'universal-argument-more) (autoload 'frowny-mode "frowny" nil t) (add-hook 'jabber-chat-mode-hook #'frowny-mode) (add-hook 'jabber-chat-mode-hook #'electric-pair-local-mode-disable) (autoload 'hippie-completing-read "hippie-completing-read" nil t) (keymap-global-set "M-/" #'hippie-completing-read) (setopt mode-line-bell-flash-time 0.25) (autoload 'mode-line-bell-mode "mode-line-bell" nil t) (mode-line-bell-mode) (keymap-global-set "C-x C-k" #'kill-this-buffer) (require 'anzu) (global-anzu-mode) (setopt search-default-mode t anzu-mode-lighter "" anzu-deactivate-region t) (global-set-key [remap query-replace] #'anzu-query-replace-regexp) (global-set-key [remap query-replace-regexp] #'anzu-query-replace) (define-key isearch-mode-map [remap isearch-query-replace] #'anzu-isearch-query-replace-regexp) (define-key isearch-mode-map [remap isearch-query-replace-regexp] #'anzu-isearch-query-replace)