From a99afd40fa4aac615d277cfb065f722712880563 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Mon, 19 Jun 2023 09:28:04 -0500 Subject: Update emacs.el --- emacs.el | 1947 +++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 1039 insertions(+), 908 deletions(-) diff --git a/emacs.el b/emacs.el index f6cec48..73e0e8b 100644 --- a/emacs.el +++ b/emacs.el @@ -1,73 +1,92 @@ -;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*- +;;; ~/.emacs -*- mode: emacs-lisp; lexical-binding: t; -*- ;; by Case Duckworth -;; Bankruptcy 10: "Annoyance" -;; ;; License: GPLv3 -;;; Commentary: +(setopt custom-file (locate-user-emacs-file "custom.el")) +(load custom-file :noerror) -;; This is my Emacs configuration. There are many like it but this -;; one is mine. -;; -;; For the tenth time! +(add-hook 'after-init-hook + (lambda () (load (locate-user-emacs-file "private") :noerror))) -;;; Code: +(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) +(package-initialize) -(defun sync/ (&optional file-name) - (expand-file-name (or file-name "") "~/sync")) +(defun package-ensure (pkg &optional local require) + "Ensure PACKAGE is installed. +If LOCAL is t, add ~/src/PKG.el to `load-path' and generate autoloads. +If REQUIRE is non-nil, require it as well." + (cond + ((stringp local) + (unless (file-directory-p local) + (user-error "Package directory does not exist: %s" local)) + (let ((autoload-file (expand-file-name (format "%s-autoloads.el" pkg) local)) + (backup-inhibited t)) + (add-to-list 'load-path local) + (loaddefs-generate local autoload-file) + (load autoload-file nil t))) + (local + (package-ensure pkg (expand-file-name (format "~/src/%s.el" pkg)) require)) + (:else + (unless (package-installed-p pkg) + (unless (ignore-errors (package-install pkg)) + (package-refresh-contents) + (package-install pkg))))) + (when require + (require pkg)) + pkg) -(add-hook 'after-init-hook - (lambda () - (load (sync/ "private")))) +(defmacro setf/assoc (alist key val &optional keep) + "Set KEY to VAL in ALIST using `assoc'/`equal' semantics. +Written as a convenience to writing out this long `alist-get' +call every time. If VAL is nil, the entry with KEY is removed from ALIST unless +KEEP is also non-nil." + `(setf (alist-get ,key ,alist nil ,(not keep) #'equal) + ,val)) -;;; Definitions: +(defmacro comment (&rest _) (declare (indent defun)) nil) -(defmacro inhibit-messages (&rest body) - "Inhibit all messages in BODY." - (declare (indent defun)) - `(cl-letf (((symbol-function 'message) #'ignore)) - ,@body)) +(package-ensure 'crux) +(crux-reopen-as-root-mode) -(defmacro comment (message &rest _ignore) - "Comment out lisp forms. -MESSAGE is for documentation purposes." - (declare (indent defun)) - t) +(crux-with-region-or-buffer indent-region) +(crux-with-region-or-buffer tabify) +(crux-with-region-or-buffer untabify) -(defmacro uncomment (message &rest body) - "Uncomment a commented form." - (declare (indent defun)) - `(progn ,@body)) - -(comment - (defun autoload-keymap (keymap library &optional parent-keymap) - "Require LIBRARY to load KEYMAP-SYMBOL, then press the buttons again. -If PARENT-KEYMAP is given, map KEYMAP within it; otherwise, use -`current-global-map'. This rips off `use-package-autoload-keymap' -basically." - (lambda () (interactive) - (unless (featurep library) - (require (cond ((symbolp library) library) - ((stringp library) (intern library)) - (t (user-error "LIBRARY should be a symbol or string: %s" - library))))) - (let ((kv (this-command-keys-vector))) - (define-key (or parent-keymap (current-global-map)) - kv - (symbol-value keymap)) - (setq unread-command-events - (mapcar (lambda (ev) (cons t ev)) - (listify-key-sequence kv))))))) - -(defun autoload-keymap (keymap-symbol package) - (require package) - (let ((kv (this-command-keys-vector))) - (global-set-key kv (symbol-value keymap-symbol)) - (setq unread-command-events - (mapcar (lambda (ev) (cons t ev)) - (listify-key-sequence kv))))) - -(defun reset-faces () +(keymap-global-set "C-c i" #'crux-find-user-init-file) + + +;;; Theme + +(setopt modus-themes-bold-constructs nil) +(setopt modus-themes-italic-constructs t) +(setopt modus-themes-variable-pitch-ui t) +(setopt modus-themes-disable-other-themes t) + +(tool-bar-mode -1) +(menu-bar-mode -1) +(scroll-bar-mode -1) +(tooltip-mode -1) + +(setopt read-answer-short t) +(setopt use-dialog-box nil) +(setopt use-file-dialog nil) +(setopt use-short-answers t) + +(setopt inhibit-startup-screen t) +(setopt initial-buffer-choice t) +(setopt initial-scratch-message ";; Emacs!\n\n") + +(setopt x-underline-at-descent-line t) +(setopt blink-cursor-delay 0.25) +(setopt blink-cursor-interval 0.25) +(setopt blink-cursor-blinks 4) + +(define-advice startup-echo-area-message (:override ()) + (if (get-buffer "*Warnings*") + ";_;" + "^_^")) + +(defun reset-faces (&rest _) (dolist (face '(font-lock-regexp-face font-lock-builtin-face font-lock-variable-name-face @@ -95,86 +114,28 @@ basically." (face-spec-set face '((t :foreground unspecified :background unspecified :slant italic)))) - (face-spec-set 'font-lock-comment-face - `((t :foreground ; :inherit doesn't work for some reason?? - ,(face-foreground font-lock-doc-markup-face))))) - -(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))))) + (when-let ((current (cl-loop for modus in '(modus-vivendi modus-operandi) + if (memq modus custom-enabled-themes) + return modus + finally return nil))) + (face-spec-set 'font-lock-comment-face + `((t :foreground ; :inherit doesn't work for some reason?? + ,(if (eq current 'modus-operandi) + "#7c318f" + "#caa6df")))))) +(advice-add 'load-theme :after #'reset-faces) -(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 delete-window-or-bury-buffer () - "Delete the current window or bury its buffer. -If the current window is the only window in the frame, bury its -buffer instead." - (interactive) - (unless (ignore-errors (delete-window) t) - (bury-buffer))) +(load-theme 'modus-vivendi :no-confirm :no-enable) +(load-theme 'modus-operandi :no-confirm :no-enable) +(if (and (executable-find "darkman") + (let ((stat (shell-command "darkman get"))) + (and (= stat 0) + (equal (with-current-buffer shell-command-buffer-name + (buffer-substring (point-min) (point-max))) + "dark\n")))) + (load-theme 'modus-vivendi :no-confirm) + (load-theme 'modus-operandi :no-confirm)) -(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 @@ -223,288 +184,44 @@ spaces." (setopt tab-bar-show t) (tab-bar-mode)) -(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." - ;; This `interactive' form is needed to override the advised function's form, - ;; to avoid errors when the region isn't active. This means that FN must take - ;; 2 arguments, the beginning and the end of the region to act on. - (interactive) - (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 fn (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 run-after-init-or-first-frame (func) + "Run FUNC after init or after the first frame." + (if (daemonp) + (add-hook 'server-after-make-frame-hook func) + (add-hook 'after-init-hook func))) -(defun ensure-package (pkg &optional local require) - "Esnure PKG is installed from repositories. -If LOCAL is t, add ~/src/PKG.el to `load-path'. -If LOCAL is a string, add that directory to the `load-path'. -If REQUIRE is a non-nil value, require the package after adding it." - (cond - ((stringp local) - (cond - ((file-exists-p local) - (add-to-list 'load-path local)) - (t (user-error "File does not exist: %s" local)))) - (local - (ensure-package pkg - (expand-file-name - (format "~/src/%s.el" - (symbol-name pkg))) - require)) - (:else - (unless (package-installed-p pkg) - (unless (ignore-errors (package-install pkg)) - (package-refresh-contents) - (package-install pkg))))) - (when require - (require pkg)) - pkg) - -;; Install packages here. Acutal configuration is done in the Configuration -;; section. -(ensure-package 'consult nil t) -(ensure-package 'marginalia nil t) -(ensure-package 'visual-fill-column) -(ensure-package 'adaptive-wrap) -(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 'mode-line-bell t) -(ensure-package 'titlecase t) - -;;; Jabber -(ensure-package 'jabber t t) - -(setopt jabber-chat-buffer-format "*%n*") -(setopt jabber-browse-buffer-format "*%n*") -(setopt jabber-groupchat-buffer-format "*%n*") -(setopt jabber-muc-private-buffer-format "*%n*") - -(face-spec-set 'jabber-activity-face - '((t :inherit jabber-chat-prompt-foreign - :foreground unspecified - :weight normal))) -(face-spec-set 'jabber-activity-personal-face - '((t :inherit jabber-chat-prompt-local - :foreground unspecified - :weight bold))) -(face-spec-set 'jabber-chat-prompt-local - '((t :inherit minibuffer-prompt - :foreground unspecified - :weight normal - :slant italic))) -(face-spec-set 'jabber-chat-prompt-foreign - '((t :inherit warning - :foreground unspecified - :weight normal))) -(face-spec-set 'jabber-chat-prompt-system - '((t :inherit font-lock-doc-face - :foreground unspecified))) -(face-spec-set 'jabber-rare-time-face - '((t :inherit font-lock-comment-face - :foreground unspecified - :underline nil))) - -(setopt jabber-auto-reconnect t) -(setopt jabber-last-read-marker " ") -(setopt jabber-muc-decorate-presence-patterns - '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil) - ("Mode #.*" . jabber-muc-presence-dim) - ("." . jabber-muc-presence-dim))) -(setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten) -(setopt jabber-rare-time-format - (format " - - - - - %%H:%d %%F" - (let ((min (string-to-number (format-time-string "%M")))) - (* 5 (floor min 5))))) -(setopt jabber-muc-header-line-format '(" " jabber-muc-topic)) - -(setopt jabber-groupchat-prompt-format "%n. ") -(setopt jabber-chat-local-prompt-format "%n. ") -(setopt jabber-chat-foreign-prompt-format "%n. ") -(setopt jabber-muc-private-foreign-prompt-format "%g/%n. ") - -(defun jabber-connect-all* (&optional arg) - "Connect to all defined jabber accounts. -If called with ARG non-nil, or with \\[universal-argument], -disconnect first." - (interactive "P") - (when arg (jabber-disconnect)) - (jabber-connect-all)) - -(keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to) -(with-eval-after-load 'jabber - (require 'jabber-httpupload nil t) - (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)) -(keymap-global-set "C-x C-j" #'dired-jump) -(keymap-set jabber-global-keymap "c" #'jabber-connect-all*) +(run-after-init-or-first-frame #'first-frame@set-fonts) -(with-eval-after-load 'dired - (keymap-set dired-mode-map "C-j" #'dired-up-directory)) - -(keymap-global-set "C-c j" jabber-global-keymap) - -(remove-hook 'jabber-alert-muc-hooks 'jabber-muc-echo) -(remove-hook 'jabber-alert-presence-hooks 'jabber-presence-echo) -(add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) -(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-activity-mode-hook - (defun jabber-activity-no-global-mode () - (setq global-mode-string - (delete '(t jabber-activity-mode-string) - global-mode-string)))) -(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 "C-x 0" #'delete-window-or-bury-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) +(defun tab-bar-end-space () + `((end menu-item " " ignore))) -;;; Theme +(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) -(if (daemonp) - (add-hook 'server-after-make-frame-hook #'first-frame@set-fonts) - (run-with-idle-timer 1 nil #'first-frame@set-fonts)) +(add-hook 'dired-mode-hook #'hl-line-mode) +(with-eval-after-load 'org-agenda + (add-hook 'org-agenda-mode-hook #'hl-line-mode)) -(tool-bar-mode -1) +(with-eval-after-load 'tabulated-list + (add-hook 'tabulated-list-mode-hook #'hl-line-mode)) -(setopt modus-themes-bold-constructs nil) -(setopt modus-themes-italic-constructs t) -(setopt modus-themes-variable-pitch-ui t) -(setopt modus-themes-disable-other-themes t) +(setopt echo-keystrokes 0.01) -(add-hook 'modus-themes-after-load-theme-hook #'reset-faces) -(add-hook 'after-init-hook #'reset-faces) +(setopt switch-to-buffer-in-dedicated-window 'pop) +(setopt switch-to-buffer-obey-display-actions t) -(load-theme 'modus-vivendi :no-confirm :no-enable) -(load-theme 'modus-operandi :no-confirm) +(when (package-ensure 'adaptive-wrap) + (add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode)) -(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 -;;; Mode line +(defun hide-minor-mode (mode &optional hook) + "Hide MODE from the mode-line. +HOOK is used to trigger the action, and defaults to MODE-hook." + (setf (alist-get mode minor-mode-alist) (list "")) + (add-hook (intern (or hook (format "%s-hook" mode))) + (lambda () (hide-minor-mode mode)))) (setq mode-line-modes (let ((recursive-edit-help-echo @@ -547,140 +264,251 @@ mouse-3: Toggle minor modes" (" (" (:eval (string-trim vc-mode)) ")")) " " (mode-line-position - (" - " mode-line-position)) - " - " + (" ∙ " mode-line-position)) + " ∙ " mode-line-modes ; the one above mode-line-misc-info mode-line-end-spaces))) -;; Remove modes from mode-line -(defun hide-minor (mode &optional hook) - "Hide MODE from the mode line. -HOOK defaults to MODE-hook, and is used to trigger the hiding." - (setf (alist-get mode minor-mode-alist) (list "")) - (add-hook (intern (or hook (format "%s-hook" mode))) - (lambda () - (setf (alist-get mode minor-mode-alist) (list ""))))) + +;;; Completion and minibuffer -(hide-minor 'frowny-mode) -(hide-minor 'whitespace-mode) -(hide-minor 'hungry-delete-mode) +(setopt tab-always-indent 'complete) +;; (setopt completion-styles '(basic partial-completion substring flex)) -(scroll-bar-mode -1) -(menu-bar-mode -1) +(setopt completion-ignore-case t) +(setopt read-buffer-completion-ignore-case t) +(setopt read-file-name-completion-ignore-case t) +(setopt completion-flex-nospace t) -(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe) +;; These aren't /that/ useful if you're not using *Completions*. +(setopt completions-detailed t) +(setopt completion-auto-help 'visible) +(setopt completion-auto-select 'second-tab) +(setopt completions-header-format nil) +(setopt completions-format 'one-column) + +(progn + (keymap-set minibuffer-local-map "C-p" + #'minibuffer-previous-completion) + (keymap-set minibuffer-local-map "C-n" + #'minibuffer-next-completion) + (keymap-set completion-in-region-mode-map "C-p" + #'minibuffer-previous-completion) + (keymap-set completion-in-region-mode-map "C-n" + #'minibuffer-next-completion)) + +(setf/assoc display-buffer-alist + "\\*Completions\\*" + '((display-buffer-reuse-mode-window))) -(setopt electric-pair-skip-whitespace 'chomp) -(electric-pair-mode) +(setopt enable-recursive-minibuffers t) +(minibuffer-depth-indicate-mode) +(minibuffer-electric-default-mode) -(setopt sh-basic-offset tab-width) +(setopt file-name-shadow-properties '(invisible t intangible t)) +(file-name-shadow-mode) -(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) - -(keymap-global-set "M-=" #'count-words) - -;;; Geiser & Scheme - -(ensure-package 'geiser) -(when (executable-find "csi") - (ensure-package 'geiser-chicken)) -(setopt scheme-program-name (or (executable-find "csi") - "scheme")) -(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)) +(define-minor-mode truncate-lines-local-mode + "Toggle `truncate-lines' in the current buffer." + :lighter "" + (setq-local truncate-lines truncate-lines-local-mode)) + +(add-hook 'completion-list-mode-hook #'truncate-lines-local-mode) +(add-hook 'minibuffer-setup-hook #'truncate-lines-local-mode) + +(when (package-ensure 'orderless) + (setopt completion-styles '(substring orderless basic)) + (setopt completion-category-overrides + '((file (styles basic partial-completion))))) + +(when (package-ensure 'consult nil t) + (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) + + (setopt xref-show-xrefs-function #'consult-xref) + (setopt xref-show-definitions-function #'xref-show-definitions-completing-read) + + (setopt consult-preview-key "M-.") + + (consult-customize + consult-ripgrep consult-git-grep consult-grep + consult-xref + :preview-key '(:debounce 0.4 any))) + +(when (package-ensure 'marginalia) + (marginalia-mode)) -(setopt gieser-autodoc-delay 0.1) +(setopt history-length t) +(setopt history-delete-duplicates t) +(setopt savehist-save-minibuffer-history t) +(setopt savehist-autosave-interval 5) +(savehist-mode) + +;;; Text editing -(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) - (add-hook 'geiser-repl-startup-hook - (defun geiser-add-default-directory-to-load-path () - (geiser-add-to-load-path default-directory)))) +(setopt fill-column 80) +(global-so-long-mode) -;;; Visual fill column +(defun cycle-spacing* (&optional n) + "Negate N argument on `cycle-spacing'." + (interactive "*p") + (cycle-spacing (- n))) +(keymap-global-set "M-SPC" #'cycle-spacing*) + +(when (package-ensure 'hungry-delete) + (setopt hungry-delete-chars-to-skip " \t") + (setopt hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip)) + (setopt hungry-delete-join-reluctantly nil) + (with-eval-after-load 'hungry-delete + (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) + (hide-minor-mode 'hungry-delete-mode)) + (global-hungry-delete-mode)) + +(setopt isearch-lazy-count t) +(setopt isearch-regexp-lax-whitespace t) +(setopt isearch-wrap-pause 'no) +(setopt search-default-mode t) +(setopt search-whitespace-regexp ".*?") ; swiper-style +(setopt search-ring-max 256) +(setopt regexp-search-ring-max 256) + +(define-advice isearch-cancel (:before () add-to-history) + "Add search string to history when canceling isearch." + (unless (string-equal "" isearch-string) + (isearch-update-ring isearch-string isearch-regexp))) + +(define-advice perform-replace (:around (fn &rest args) dont-exit-on-anykey) + "Don't exit replace for anykey that's not in `query-replace-map'." + (save-window-excursion + (cl-letf* ((lookup-key-orig + (symbol-function 'lookup-key)) + ((symbol-function 'lookup-key) + (lambda (map key &optional accept-default) + (or (apply lookup-key-orig map key accept-default) + (when (eq map query-replace-map) 'help))))) + (apply fn args)))) + +(when (package-ensure 'isearch-mb) + (with-eval-after-load 'isearch-mb + (with-eval-after-load 'consult + (add-to-list 'isearch-mb--with-buffer #'consult-isearch-history) + (keymap-set isearch-mb-minibuffer-map "M-r" #'consult-isearch-history) + (add-to-list 'isearch-mb--after-exit #'consult-line) + (keymap-set isearch-mb-minibuffer-map "M-s l" #'consult-line)) + (with-eval-after-load 'anzu + (add-to-list 'isearch-mb--after-exit #'anzu-isearch-query-replace) + (keymap-set isearch-mb-minibuffer-map "M-%" + #'anzu-isearch-query-replace))) + (isearch-mb-mode)) + +(when (package-ensure 'anzu) + (setopt anzu-mode-lighter "") + (setopt anzu-deactivate-region t) + (keymap-global-set "M-%" #'anzu-query-replace-regexp) + (keymap-global-set "C-M-%" #'anzu-query-replace) + (keymap-set isearch-mode-map "M-%" #'anzu-isearch-query-replace-regexp) + (keymap-set isearch-mode-map "C-M-%" #'anzu-isearch-query-replace) + (global-anzu-mode)) -(with-eval-after-load 'visual-fill-column - (setopt visual-fill-column-center-text t) - (setopt 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) +(keymap-global-set "M-/" #'hippie-expand) +(keymap-global-set "C-x C-b" #'ibuffer) -;;; Set major mode for non-file buffers +(defun call-with-region-or-buffer (fn &rest _r) + "Call function FN with current region or buffer. +Good to use for :around advice." + ;; This `interactive' form is needed to override the advised function's form, + ;; to avoid errors when the region isn't active. This means that FN must take + ;; 2 arguments, the beginning and the end of the region to act on. + (interactive) + (if (region-active-p) + (funcall fn (region-beginning) (region-end)) + (funcall fn (point-min) (point-max)))) -(setopt major-mode - (lambda () ; guess major mode from buffer name - (unless buffer-file-name - (let ((buffer-file-name (buffer-name))) - (set-auto-mode))))) +(delete-selection-mode) -;; Dialogs -(unless (boundp 'use-short-answers) - (fset 'yes-or-no-p 'y-or-n-p)) +(when (package-ensure 'avy) + (setopt avy-background t) + (setopt avy-keys (string-to-list "asdfghjklqwertyuiopzxcvbnm")) + (keymap-global-set "M-j" #'avy-goto-char-timer) + (keymap-set isearch-mode-map "M-j" #'avy-isearch)) -(setopt read-answer-short t) -(setopt use-dialog-box nil) -(setopt use-file-dialog nil) -(setopt use-short-answers t) -(require 'savehist) -(setopt history-length t) -(setopt history-delete-duplicates t) -(setopt savehist-save-minibuffer-history t) -(setopt savehist-autosave-interval 30) -(savehist-mode) +(when (package-ensure 'zzz-to-char) + (keymap-global-set "M-z" + (defun zzz-to-char* (arg) + (interactive "P") + (call-interactively + (if arg #'zzz-to-char #'zzz-to-char-up-to-char))))) -;; Killing and yanking -(setopt kill-do-not-save-duplicates t) -(setopt 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. -(setopt save-interprogram-paste-before-kill t) -(setopt yank-pop-change-selection t) -(delete-selection-mode) +;;; Prose -;; Notifying the user -(setopt echo-keystrokes 0.01) -(setopt ring-bell-function #'ignore) +(add-hook 'text-mode-hook #'visual-line-mode) -;; Point and mark -(setopt set-mark-command-repeat-pop t) +(when (package-ensure 'olivetti) + (add-hook 'text-mode-hook #'olivetti-mode)) + +(when (package-ensure 'jinx) + (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))) + +(defun org-fk-region (start end) + "Get the Flesch-Kincaid score of an `org-mode' region." + (interactive "r") + (let ((buf (get-buffer-create "*fk*" t))) + (shell-command-on-region start end + "pandoc -t plain -f org | ~/src/fk/fk.perl" + buf) + (with-current-buffer buf + (buffer-substring-no-properties (point-min) (- (point-max) 1))) + (kill-buffer buf))) -;; The system -(setopt read-process-output-max (* 10 1024 1024)) +(crux-with-region-or-buffer org-fk-region) -;; Startup -(setopt inhibit-startup-screen t) -(setopt initial-buffer-choice t) -(setopt initial-scratch-message nil) +(when (package-ensure 'scule t t) + (keymap-global-set "M-c" scule-map)) -(define-advice startup-echo-area-message (:override ()) - (if (get-buffer "*Warnings*") - ";_;" - "^_^")) +(when (package-ensure 'titlecase t) + (keymap-set scule-map "M-t" #'titlecase-dwim)) -;; Text editing -(setopt fill-column 80) -(setopt sentence-end-double-space nil) -(setopt tab-width 8) -(setopt tab-always-indent 'complete) -(global-so-long-mode) +(setopt dictionary-default-popup-strategy "lev") ; Levenshtein distance 1 +(setopt dictionary-server "dict.org") +(setopt dictionary-use-single-buffer t) +(keymap-global-set "M-#" + (defun dictionary-lookup-dwim () + (interactive) + (unless (ignore-errors (dictionary-lookup-definition)) + (call-interactively #'dictionary-search)))) + +;;; Programming + +(setopt electric-pair-skip-whitespace 'chomp) +(electric-pair-mode) (setopt show-paren-delay 0.01) (setopt show-paren-style 'parenthesis) @@ -688,6 +516,103 @@ HOOK defaults to MODE-hook, and is used to trigger the hiding." (setopt show-paren-when-point-inside-paren t) (show-paren-mode) +(add-hook 'prog-mode-hook #'auto-fill-mode) +(add-hook 'prog-mode-hook #'display-fill-column-indicator-mode) +(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p) + +(when (package-ensure 'dumb-jump) + (add-hook 'xref-backend-functions #'dumb-jump-xref-activate)) + +(add-hook 'prog-mode-hook #'prettify-symbols-mode) + +(keymap-global-set "M-:" #'pp-eval-expression) + +;; Tabs + +(setopt tab-width 8) + +(defvar space-indent-modes '(emacs-lisp-mode + lisp-mode + scheme-mode + python-mode + haskell-mode + text-mode) + "Modes to indent with spaces, not tabs.") + +(defun indent-tabs-mode-maybe () + (setq indent-tabs-mode + (if (apply #'derived-mode-p space-indent-modes) nil t))) +(add-hook 'prog-mode-hook #'indent-tabs-mode-maybe) + +(setopt sh-basic-offset tab-width) +(setopt perl-indent-level tab-width) + +;; Scheme + +(when (package-ensure 'geiser) + (when (executable-find "csi") + (package-ensure 'geiser-chicken)) + (setopt scheme-program-name (or (executable-find "csi") + "scheme")) + (add-hook 'scheme-mode-hook #'geiser-mode)) + +(require 'autoinsert) +(setf/assoc auto-insert-alist + "\\.scm" + '(nil "#!/bin/sh" \n + "#| -*- scheme -*-" \n + "exec csi -ss \"$0\" \"$@\"" \n + _ \n + "|#" \n \n)) + + +;;; Files + +(setopt auto-revert-verbose nil) +(setopt global-auto-revert-non-file-buffers t) +(global-auto-revert-mode) + +(setopt create-lockfiles nil) +(setopt mode-require-final-newline t) +(setopt view-read-only t) +(setopt save-silently t) + +(setopt auto-save-default nil) +(setopt auto-save-no-message t) +(setopt auto-save-interval 2) +(setopt auto-save-timeout 2) +(setopt auto-save-visited-interval 2) +(setopt remote-file-name-inhibit-auto-save t) +(setopt 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) +(setopt version-control t) +(setopt kept-new-versions 3) +(setopt kept-old-versions 3) +(setopt delete-old-versions t) +(add-to-list 'backup-directory-alist '("^/dev/shm/" . nil)) +(add-to-list 'backup-directory-alist '("^/tmp/" . nil)) +(when-let ((xrd (getenv "XDG_RUNTIME_DIR"))) + (add-to-list 'backup-directory-alist (cons xrd nil))) +(add-to-list 'backup-directory-alist + (cons "." (locate-user-emacs-file "backup/")) + :append) + +(setopt recentf-max-menu-items 100) +(setopt recentf-max-saved-items nil) +(setopt recentf-case-fold-search t) +(with-eval-after-load 'recentf + (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) + +(setopt save-place-forget-unreadable-files (eq system-type 'gnu/linux)) +(save-place-mode) + ;; Encodings (set-language-environment "UTF-8") (setopt buffer-file-coding-system 'utf-8-unix) @@ -704,130 +629,178 @@ HOOK defaults to MODE-hook, and is used to trigger the hiding." ((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))) - -(setopt x-underline-at-descent-line t) -(setopt blink-cursor-delay 0.25) -(setopt blink-cursor-interval 0.25) -(setopt blink-cursor-blinks 1) - -;; Files -(setopt auto-revert-verbose nil) -(setopt global-auto-revert-non-file-buffers t) -(setopt create-lockfiles nil) -(setopt find-file-visit-truename t) -(setopt mode-require-final-newline t) -(setopt view-read-only t) -(setopt save-silently t) -(global-auto-revert-mode) + (_ + (set-selection-coding-system 'utf-8) + (set-clipboard-coding-system 'utf-8))) -(setopt auto-save-default nil) -(setopt auto-save-interval 1) -(setopt auto-save-no-message t) -(setopt auto-save-timeout 1) -(setopt auto-save-visited-interval 1) -(setopt 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) +;; Undo +(when (package-ensure 'undohist) + (undohist-initialize)) -(setopt backup-by-copying t) -(setopt version-control t) -(setopt kept-new-versions 8) -(setopt kept-old-versions 8) -(setopt 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) -(setopt recentf-max-saved-items nil) -(setopt recentf-auto-cleanup 'mode) -(setopt 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) +(when (package-ensure 'embark nil t) + (when (package-installed-p 'consult) + (package-ensure 'embark-consult nil t)) + (keymap-global-set "C-." #'embark-act) + (keymap-global-set "M-." #'embark-dwim) + (keymap-global-set "C-h B" #'embark-bindings) + (setf/assoc display-buffer-alist + "\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + '(nil (window-parameters (mode-line-format . none)))) + (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode)) + +(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) +(setopt eldoc-idle-delay 0.01) + +(setopt recenter-positions '(top middle bottom)) + +(defmacro inhibit-messages (&rest body) + "Inhibit all messages in BODY." + (declare (indent defun)) + `(cl-letf (((symbol-function 'message) #'ignore)) + ,@body)) -(require 'uniquify) -(setopt uniquify-after-kill-buffer-p t) -(setopt uniquify-buffer-name-style 'forward) -(setopt uniquify-ignore-buffers-re "^\\*") -(setopt uniquify-separator path-separator) +(add-hook 'find-file-not-found-functions + (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))))) (setopt vc-follow-symlinks t) (setopt vc-make-backup-files t) +(add-hook 'find-file-hook + (defun vc-remote-off () + "Turn VC off when remote." + (when (file-remote-p (buffer-file-name)) + (setq-local vc-handled-backends nil)))) +(with-eval-after-load 'vc-dir + (add-hook 'vc-dir-mode-hook #'hl-line-mode)) -;; 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) -(setopt native-comp-deferred-compilation t) -(setopt 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)) +(hide-minor-mode 'whitespace-mode) +(add-hook 'before-save-hook + (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))))) -(global-goto-address-mode) +(defcustom browse-url-safe-browser-functions nil + "\"Safe\" browser functions." + :type '(repeat-function)) -;; Winner -(winner-mode) +(defun browse-url-browser-function-safe-p (fn) + "Return t if FN is a \"safe\" browser function." + (memq fn (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))))))) -;;; Hooks +(put 'browse-url-browser-function 'safe-local-variable + 'browse-url-browser-function-safe-p) -(defcustom persist-settings-hook nil - "Functions to run in order to persist settings." - :type 'hook) +(defun list-of-strings-p (x) + "Is X a list of strings?" + (and x + (listp x) + (cl-every #'stringp x))) -(defun persist-settings () - (inhibit-messages - (run-with-idle-timer 5 nil #'run-hooks 'persist-settings-hook))) +(put 'ispell-local-words 'safe-local-variable + 'list-of-strings-p) -(defvar persist-timer - (run-with-timer nil 60 #'persist-settings) - "Timer running `persist-settings-hook'.") +(package-ensure '0x0) ; TODO: write my own package for rsync + +(when (package-ensure 'electric-cursor t) + (hide-minor-mode 'electric-cursor-mode) + (setopt electric-cursor-alist '((overwrite-mode . (hbar . 8)) + (t . box))) + (electric-cursor-mode)) + +(defun fill-double-space-sentences-region (start end) + "Fill from START to END, double-spacing sentences." + (let ((sentence-end-double-space t)) + (repunctuate-sentences :no-query start end) + (fill-region start end))) + +(defun fill-or-unfill-region (start end) + "Fill or unfill from START to END." + (let ((filled-p (cl-every (lambda (ln) (<= (length ln) fill-column)) + (string-split (buffer-substring start end) + "[\n\r]+")))) + (if filled-p + (let ((fill-column most-positive-fixnum) + (fill-paragraph-function nil)) + (message "Unfilling region") + (fill-double-space-sentences-region start end)) + (message "Filling region") + (fill-double-space-sentences-region start end)))) + +(defun fill-or-unfill-dwim () + (interactive) + (save-mark-and-excursion + (if (region-active-p) + (fill-or-unfill-region (region-beginning) + (region-end)) + (fill-or-unfill-region (progn (backward-paragraph) + (point)) + (progn (forward-paragraph) + (point)))))) -(add-hook 'persist-settings-hook #'save-place-kill-emacs-hook) -(add-hook 'persist-settings-hook #'bookmark-exit-hook-internal) -(with-eval-after-load 'em-hist - (add-hook 'persist-settings-hook #'eshell-save-some-history)) -(with-eval-after-load 'prescient - (add-hook 'persist-settings-hook #'prescient--save)) +(keymap-global-set "M-q" #'fill-or-unfill-dwim) -(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) +;; Fix annoying error messages when I type the key +(keymap-global-set "<0x100811d0>" #'ignore) ; Keychron +(keymap-global-set "" #'ignore) ; Laptop -;;; Tab bar +(keymap-global-set "M-u" #'universal-argument) +(keymap-set universal-argument-map "M-u" #'universal-argument-more) -(defun tab-bar-end-space () - `((end menu-item " " ignore))) +(defun kill-buffer-dwim (&optional buffer-or-name) + "Kill BUFFER-OR-NAME or the current buffer." + (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))))) +(keymap-global-set "C-x C-k" #'kill-buffer-dwim) + +(defun other-window-dwim (&optional arg) + "Switch to another window/buffer. +Calls `other-window', which see, unless +- the current window is alone on its frame +- `other-window-dwim' is called with \\[universal-argument] +In these cases, switch to the last-used buffer." + (interactive "P") + (if (or arg (one-window-p)) + (switch-to-buffer (other-buffer) nil t) + (other-window 1))) +(keymap-global-set "M-o" #'other-window-dwim) -(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) +(defun delete-window-dwim () + "Delete the current window or bury its buffer. +If the current window is alone in its frame, bury the buffer +instead." + (interactive) + (unless (ignore-errors (delete-window) t) + (bury-buffer))) +(keymap-global-set "C-x 0" #'delete-window-dwim) + ;;; Org mode (keymap-global-set "C-c a" #'org-agenda) (keymap-global-set "C-c c" #'org-capture) +(keymap-global-set "C-c l" #'org-store-link) (setopt org-clock-clocked-in-display 'mode-line) (setopt org-clock-out-remove-zero-time-clocks t) @@ -845,69 +818,21 @@ HOOK defaults to MODE-hook, and is used to trigger the hiding." (setopt org-agenda-inhibit-startup t) (setopt org-deadline-warning-days 0) (setopt org-cycle-separator-lines 0) -(setopt org-agenda-span 14) - -(add-hook 'org-agenda-after-show-hook #'org-narrow-to-subtree) - -(defmacro org-insert-or-surround (character) - (let ((c (gensym))) - `(defun ,(intern (format "org-insert-or-surround-%s" character)) (arg) - ,(format "Insert %s or surround the region with it." character) - (interactive "p") - (let ((,c ,(if (stringp character) - (string-to-char character) - character))) - (if (org-region-active-p) - (let ((begin (region-beginning)) - (end (region-end))) - (save-mark-and-excursion - (deactivate-mark) - (goto-char begin) - (self-insert-command arg ,c) - (goto-char (+ 1 end)) - (self-insert-command arg ,c))) - (self-insert-command arg ,c)))))) - -(with-eval-after-load 'org - (keymap-set org-mode-map "*" (org-insert-or-surround "*")) - (keymap-set org-mode-map "/" (org-insert-or-surround "/")) - (keymap-set org-mode-map "_" (org-insert-or-surround "_")) - (keymap-set org-mode-map "=" (org-insert-or-surround "=")) - (keymap-set org-mode-map "~" (org-insert-or-surround "~")) - (keymap-set org-mode-map "+" (org-insert-or-surround "+"))) - -;; Fix braindead behavior -(with-eval-after-load 'org-mouse - (defun org--mouse-open-at-point (orig-fun &rest args) - (let ((context (org-context))) - (cond - ;; Don't org-cycle when clicking on headline stars. The biggest problem - ;; is that this function advises `org-open-at-point', so I can't C-c C-o - ;; from a headline star. - ;; ((assq :headline-stars context) (org-cycle)) - ((assq :checkbox context) (org-toggle-checkbox)) - ((assq :item-bullet context) - (let ((org-cycle-include-plain-lists t)) (org-cycle))) - ((org-footnote-at-reference-p) nil) - (t (apply orig-fun args)))))) - -;;; Spelling - -(defun list-of-strings-p (x) - "Is X a list of strings?" - (and x - (listp x) - (cl-every #'stringp x))) +(setopt org-agenda-span 10) +(setopt org-blank-before-new-entry '((heading . t) + (plain-list-item . nil))) -(put 'ispell-local-words 'safe-local-variable - 'list-of-strings-p) +(defvar-local org-agenda/setup-done nil) -(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)) +(add-hook 'org-agenda-after-show-hook + (defun org-agenda-after-show/setup () + (org-narrow-to-subtree) + (goto-char (point-min)) + (unless org-agenda/setup-done + (run-hooks 'org-mode-hook)) + (setq org-agenda/setup-done t))) -;;; org-return-dwim +;; 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) @@ -1002,12 +927,36 @@ itself. Other values of ARG will call `newline' with that ARG." (org-table-copy-down (or n 1)) (org-return-dwim n))) +(defmacro org-insert-or-surround (character) + (let ((c (gensym))) + `(defun ,(intern (format "org-insert-or-surround-%s" character)) (arg) + ,(format "Insert %s or surround the region with it." character) + (interactive "p") + (let ((,c ,(if (stringp character) + (string-to-char character) + character))) + (if (org-region-active-p) + (let ((begin (region-beginning)) + (end (region-end))) + (save-mark-and-excursion + (deactivate-mark) + (goto-char begin) + (self-insert-command arg ,c) + (goto-char (+ 1 end)) + (self-insert-command arg ,c))) + (self-insert-command arg ,c)))))) + (with-eval-after-load 'org (keymap-set org-mode-map "RET" #'org-return-dwim) - (keymap-set org-mode-map "S-" #'org-table-copy-down|org-return-dwim)) - -;;; Copy rich text to the keyboard + (keymap-set org-mode-map "S-" #'org-table-copy-down|org-return-dwim) + (keymap-set org-mode-map "*" (org-insert-or-surround "*")) + (keymap-set org-mode-map "/" (org-insert-or-surround "/")) + (keymap-set org-mode-map "_" (org-insert-or-surround "_")) + (keymap-set org-mode-map "=" (org-insert-or-surround "=")) + (keymap-set org-mode-map "~" (org-insert-or-surround "~")) + (keymap-set org-mode-map "+" (org-insert-or-surround "+"))) +;; Copy rich text to the keyboard (defcustom clipboard-html-copy-program (if (or (equal "wayland" (getenv "XDG_SESSION_TYPE")) @@ -1019,20 +968,6 @@ 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'." @@ -1050,161 +985,344 @@ ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." (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") -(setopt hungry-delete-skip-regexp (format "[%s]" hungry-delete-chars-to-skip)) -(setopt 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) -(setopt 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) - -(setopt xref-show-xrefs-function #'consult-xref) -(setopt xref-show-definitions-function #'consult-xref) - -(setopt consult-preview-key "M-.") - -(consult-customize - consult-ripgrep consult-git-grep consult-grep - consult-xref - :preview-key '(:debounce 0.4 any)) - -(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) +;; (info "(org) Breaking Down Tasks") +(defun org-summary-todo (n-done n-not-done) + "Switch entry to DONE when all subentries are done, to TODO otherwise." + (let (org-log-done org-log-states) ; turn off logging + (org-todo (if (= n-not-done 0) "DONE" "TODO")))) +(add-hook 'org-after-todo-statistics-hook #'org-summary-todo) -(keymap-global-set "M-/" #'hippie-expand) +;; Fix braindead behavior +(with-eval-after-load 'org-mouse + (defun org--mouse-open-at-point (orig-fun &rest args) + (let ((context (org-context))) + (cond + ;; Don't org-cycle when clicking on headline stars. The biggest problem + ;; is that this function advises `org-open-at-point', so I can't C-c C-o + ;; from a headline star. + ;; ((assq :headline-stars context) (org-cycle)) + ((assq :checkbox context) (org-toggle-checkbox)) + ((assq :item-bullet context) + (let ((org-cycle-include-plain-lists t)) (org-cycle))) + ((org-footnote-at-reference-p) nil) + (t (apply orig-fun args)))))) -(setopt mode-line-bell-flash-time 0.25) -(autoload 'mode-line-bell-mode "mode-line-bell" nil t) -(mode-line-bell-mode) +(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)) -(keymap-global-set "C-x C-k" #'kill-this-buffer) +(add-hook 'org-mode-hook + (defun org-mode-line-position () + (setq-local mode-line-position + '((:propertize + ("" mode-line-percent-position) + local-map mode-line-column-line-number-mode-map + display (min-width (5.0))) + (org-word-count-mode org-word-count-string)))) + (setq mode-line-misc-info + (delete '(org-word-count-mode org-word-count-string) + mode-line-misc-info))) + +;;; Org word count +;; also does Flesch-Kincaid reading level. +;; TODO: customization ... stuff. + +(defun fk-region (start end) + (interactive "r") + ;; (let* ((fk-buf (get-buffer-create " *fk*")) + ;; (fk-proc + ;; (start-process "fk" fk-buf "/home/acdw/src/fk/fk.perl"))) + ;; (set-process-sentinel fk-proc #'ignore) + ;; (process-send-region fk-proc start end) + ;; (process-send-eof fk-proc) + ;; (with-current-buffer fk-buf + ;; (goto-char (point-max)) + ;; (forward-line -1) + ;; (string-chop-newline (buffer-substring-no-properties + ;; (line-beginning-position) (point-max))))) + + (let ((shell-command-buffer-name (format "*fk/%s*" (buffer-name)))) + (shell-command-on-region start end "~/src/fk/fk.perl") + (with-current-buffer shell-command-buffer-name + (buffer-substring-no-properties (point-min) (- (point-max) 1)))) + ) + +(defun org-word-count-region (start end &optional interactive) + (interactive "r\np") + (when (derived-mode-p 'org-mode) + (save-window-excursion + (inhibit-messages + (let ((shell-command-buffer-name (format " *wc/%s*" (buffer-name))) + wc fk) + (shell-command-on-region start end + "pandoc -t plain -f org") + (with-current-buffer shell-command-buffer-name + (setq wc (count-words (point-min) (point-max))) + (setq fk (string-to-number (fk-region (point-min) (point-max))))) + (when interactive (message "%s" wc)) + (list wc fk)))))) + +(defvar-local org-word-count-string "" + "Number of words in buffer.") + +(defun update-org-word-count-string () + (when (derived-mode-p 'org-mode) + (setq org-word-count-string + (apply #'format " %dw/%.2ffk" + (org-word-count-region (point-min) (point-max)))))) + +(defvar org-word-count-timer nil + "Timer for `org-word-count'.") + +(define-minor-mode org-word-count-mode + "Count words and update the org-word-count-string." + :lighter " owc" + (cond + ((and (derived-mode-p 'org-mode) + org-word-count-mode) + (unless (timerp org-word-count-timer) + (setq org-word-count-timer + (run-with-idle-timer 1 t #'update-org-word-count-string)))) + (:else + (when (timerp org-word-count-timer) + (cancel-timer org-word-count-timer)) + (setq org-word-count-timer nil) + (setq org-word-count-mode nil)))) +(hide-minor-mode 'org-word-count-mode) -(require 'anzu) -(global-anzu-mode) -(setopt search-default-mode t) -(setopt anzu-mode-lighter "") -(setopt anzu-deactivate-region t) +(add-hook 'org-mode-hook #'org-word-count-mode) -(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) +;;; Org recentering -;;; Completion & minibuffer +(defun org-recenter (&optional arg) + (interactive "P") + (if (or arg + (eq last-command 'org-recenter)) + (recenter-top-bottom arg) + (save-excursion + (unless (org-at-heading-p) + (ignore-errors (org-previous-visible-heading 1))) + (recenter-top-bottom 0)))) +(with-eval-after-load 'org + (keymap-set org-mode-map "C-l" #'org-recenter)) -(minibuffer-depth-indicate-mode) -(setopt tab-always-indent 'complete) -(file-name-shadow-mode) -(minibuffer-electric-default-mode) +;;; Org links -- extra types -(setopt completion-ignore-case t) -(setopt read-buffer-completion-ignore-case t) -(setopt read-file-name-completion-ignore-case t) -(setopt completions-detailed t) -(setopt enable-recursive-minibuffers t) -(setopt file-name-shadow-properties '(invisible t intangible t)) -(setopt minibuffer-eldef-shorten-default t) -(setopt minibuffer-prompt-properties - '(read-only t cursor-intangible t face minibuffer-prompt)) -(setopt window-resize-pixelwise t) -(setopt frame-resize-pixelwise t) +(with-eval-after-load 'ol + (org-link-set-parameters "tel" :follow #'ignore) + (org-link-set-parameters "sms" :follow #'ignore)) -(add-hook 'completion-list-mode-hook #'truncate-lines-mode) -(add-hook 'minibuffer-setup-hook #'truncate-lines-mode) + +;;; Jabber -(setopt completion-auto-help 'visible) -(setopt completion-auto-select 'second-tab) -(setopt completions-header-formato nil) -;; Up/down when completing in the minibuffer -(keymap-set minibuffer-local-map "C-p" #'minibuffer-previous-completion) -(keymap-set minibuffer-local-map "C-n" #'minibuffer-next-completion) -;; Up/down when competing in a normal buffer -(keymap-set completion-in-region-mode-map "C-p" - #'minibuffer-previous-completion) -(keymap-set completion-in-region-mode-map "C-n" #'minibuffer-next-completion) -(keymap-set completion-in-region-mode-map "RET" #'minibuffer-choose-completion) +(when (package-ensure 'jabber t t) + (setopt jabber-chat-buffer-format "*%n*") + (setopt jabber-browse-buffer-format "*%n*") + (setopt jabber-groupchat-buffer-format "*%n*") + (setopt jabber-muc-private-buffer-format "*%n*") + + (face-spec-set 'jabber-activity-face + '((t :inherit jabber-chat-prompt-foreign + :foreground unspecified + :weight normal))) + (face-spec-set 'jabber-activity-personal-face + '((t :inherit jabber-chat-prompt-local + :foreground unspecified + :weight bold))) + (face-spec-set 'jabber-chat-prompt-local + '((t :inherit minibuffer-prompt + :foreground unspecified + :weight normal + :slant italic))) + (face-spec-set 'jabber-chat-prompt-foreign + '((t :inherit warning + :foreground unspecified + :weight normal))) + (face-spec-set 'jabber-chat-prompt-system + '((t :inherit font-lock-doc-face + :foreground unspecified))) + (face-spec-set 'jabber-rare-time-face + '((t :inherit font-lock-comment-face + :foreground unspecified + :underline nil))) + + (setopt jabber-auto-reconnect t) + (setopt jabber-last-read-marker + "-------------------------------------------------------------------") + (setopt jabber-muc-decorate-presence-patterns + '(("\\( enters the room ([^)]+)\\| has left the chatroom\\)$" . nil) + ("Mode #.*" . jabber-muc-presence-dim) + ("." . jabber-muc-presence-dim))) + (setopt jabber-activity-make-strings #'jabber-activity-make-strings-shorten) + (setopt jabber-rare-time-format + (format " - - - - - %%H:%d %%F" + (let ((min (string-to-number (format-time-string "%M")))) + (* 5 (floor min 5))))) + (setopt jabber-muc-header-line-format '(" " jabber-muc-topic)) + + (setopt jabber-groupchat-prompt-format "%n. ") + (setopt jabber-chat-local-prompt-format "%n. ") + (setopt jabber-chat-foreign-prompt-format "%n. ") + (setopt jabber-muc-private-foreign-prompt-format "%g/%n. ") + + (defun jabber-connect-all* (&optional arg) + "Connect to all defined jabber accounts. +If called with ARG non-nil, or with \\[universal-argument], +disconnect first." + (interactive "P") + (when arg (jabber-disconnect)) + (jabber-connect-all)) + + (keymap-global-set "C-c C-SPC" #'jabber-activity-switch-to) + (with-eval-after-load 'jabber + (require 'jabber-httpupload nil t) + (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)) + (keymap-global-set "C-x C-j" #'dired-jump) + (keymap-set jabber-global-keymap "c" #'jabber-connect-all*) + (keymap-global-set "C-c j" jabber-global-keymap) + + (remove-hook 'jabber-alert-muc-hooks #'jabber-muc-echo) + (remove-hook 'jabber-alert-presence-hooks #'jabber-presence-echo) + (add-hook 'jabber-post-connect-hooks #'jabber-enable-carbons) + (add-hook 'jabber-chat-mode-hook #'olivetti-mode) + (add-hook 'jabber-chat-mode-hook + (defun jabber-chat-mode-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)))))) + + (add-hook 'jabber-chat-mode-hook + (defun electric-pair-local-disable () + (electric-pair-local-mode -1))) + + (when (fboundp 'jabber-chat-update-focus) + (add-hook 'window-configuration-change-hook #'jabber-chat-update-focus))) -;;; Other stuff + +;;; Dired -(comment "Unnecessary after being defined." - (ensure-package 'wiki-abbrev t t) - (wiki-abbrev-insinuate)) -(add-hook 'text-mode-hook #'abbrev-mode) +(with-eval-after-load 'dired + (keymap-set dired-mode-map "C-j" #'dired-up-directory)) -;;; Browser + +;;; Browsing the web (setopt browse-url-browser-function #'eww-browse-url) +;;; EWW + +(setopt eww-use-browse-url ".") +(setopt eww-auto-rename-buffer 'title) +(setopt eww-default-download-directory + (or (xdg-user-dir "DOWNLOAD") + "~/Downloads")) +(setopt eww-history-limit nil) + +;; Use Emacs bookmarks for EWW +(defun bookmark-eww--make () + "Make eww bookmark record." + `((filename . ,(plist-get eww-data :url)) + (title . ,(plist-get eww-data :title)) + (time . ,(current-time-string)) + (handler . ,#'bookmark-eww-handler) + (defaults . (,(concat + ;; url without the https and path + (replace-regexp-in-string + "/.*" "" + (replace-regexp-in-string + "\\`https?://" "" + (plist-get eww-data :url))) + " - " + ;; page title + (replace-regexp-in-string + "\\` +\\| +\\'" "" + (replace-regexp-in-string + "[\n\t\r ]+" " " + (plist-get eww-data :title)))))))) + +(defun bookmark-eww-handler (bm) + "Handler for eww bookmarks." + (eww-browse-url (alist-get 'filename bm))) + +(defun bookmark-eww--setup () + "Setup eww bookmark integration." + (setq-local bookmark-make-record-function #'bookmark-eww--make)) +(add-hook 'eww-mode-hook #'bookmark-eww--setup) + +(with-eval-after-load 'eww + (define-key eww-mode-map "b" #'bookmark-set) + (define-key eww-mode-map "B" #'bookmark-jump)) + +;; Transforming URLs +;; `eww-transform-url' exists, but I like my package better. + +(when (package-ensure 'browse-url-transform t) + (setopt browse-url-transform-alist + `(;; Privacy-respecting alternatives + ("twitter\\.com" . "nitter.snopyta.org") + ("\\(?:\\(?:old\\.\\)?reddit\\.com\\)" . "libreddit.de") + ("medium\\.com" . "scribe.rip") + ;; Text-mode of non-text-mode sites + ("www\\.npr\\.org" . "text.npr.org") + ;; Ask for raw versions of paste sites + ("^.*dpaste\\.com.*$" . "\\&.txt") + ("bpa\\.st/\\(.*\\)" . "bpa.st/raw/\\1") + ("\\(paste\\.debian\\.net\\)/\\(.*\\)" . "\\1/plain/\\2") + ("\\(pastebin\\.com\\)/\\\(.*\\)" . "\\1/raw/\\2") + ("\\(paste\\.centos\\.org/view\\)/\\(.*\\)" . "\\1/raw/\\2"))) + (browse-url-transform-mode) + (hide-minor-mode 'browse-url-transform-mode)) + +(with-eval-after-load 'browse-url-transform + (setopt eww-url-transformers + '(eww-remove-tracking + browse-url-transform-url))) + ;; External browsers: firefox > chromium > chrome (setq browse-url-firefox-program (or (executable-find "firefox") @@ -1226,87 +1344,100 @@ ORG-EXPORT-ARGS are passed to `org-export-to-buffer'." (browse-url-chrome-program #'browse-url-chrome) (t #'browse-url-default-browser))) -(ensure-package 'link-hint) -(setopt link-hint-avy-style 'at-full) -(setopt link-hint-avy-all-windows t) - -(defvar link-hint-map - (define-keymap - :name "Open a link" - :prefix 'link-hint-map - "M-l" #'link-hint-open-link - "M-w" #'link-hint-copy-link)) +;; Hinting at links +(when (package-ensure 'link-hint) + (setopt link-hint-avy-style 'at-full) + (setopt link-hint-avy-all-windows t) + (defvar link-hint-map + (define-keymap + :name "Open a link" + :prefix 'link-hint-map + "M-l" #'link-hint-open-link + "M-w" #'link-hint-copy-link)) + (keymap-global-set "M-l" 'link-hint-map)) -(keymap-global-set "M-l" 'link-hint-map) - -(setq global-mode-string - '((jabber-activity-mode jabber-activity-mode-string) - " ")) - -(add-hook 'prog-mode-hook #'prettify-symbols-mode) - -(require 'autoinsert) -(setf (alist-get "\\.scm" auto-insert-alist nil nil #'equal) - '(nil - "#!/bin/sh" \n - "#| -*- scheme -*-" \n - "exec csi -R r7rs -ss \"$0\" \"$@\"" \n - _ \n - "|#" \n \n)) - -(ensure-package 'embark nil t) -(when (package-installed-p 'consult) - (ensure-package 'embark-consult nil t)) - -(keymap-global-set "C-." #'embark-act) -(keymap-global-set "M-." #'embark-dwim) -(keymap-global-set "C-h B" #'embark-bindings) - -(setopt eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) -(setopt eldoc-idle-delay 0.01) - -(setf (alist-get "\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - display-buffer-alist - nil nil #'equal) - '(nil (window-parameters (mode-line-format . none)))) - -(add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode) - -(global-goto-address-mode) - -(ensure-package 'pulse-location t t) -(pulse-location-mode) -(hide-minor 'pulse-location-mode) -(setopt pulse-flag t) - -(define-advice eval-region (:around (orig start end &rest args) pulse) - (apply orig start end args) - (pulse-momentary-highlight-region start end)) + +;;; Eshell + +(setopt eshell-modules-list + '(eshell-alias + eshell-banner + eshell-basic + eshell-cmpl + eshell-dirs + eshell-elecslash + eshell-extpipe + eshell-glob + eshell-hist + eshell-ls + eshell-pred + eshell-prompt + eshell-script + eshell-unix)) + +(setopt eshell-banner-message "") +(setopt eshell-destroy-buffer-when-process-dies t) +(setopt eshell-error-if-no-glob t) +(setopt eshell-hist-ignoredups 'erase) +(setopt eshell-kill-on-exit t) +(setopt eshell-prefer-lisp-functions t) +(setopt eshell-prefer-lisp-variables t) +(setopt eshell-scroll-to-bottom-on-input 'this) +(setopt eshell-history-size 1024) +(setopt eshell-input-filter (lambda (input) + (or (eshell-input-filter-default input) + (eshell-input-filter-initial-space input)))) +(setopt eshell-prompt-function + (lambda () + (concat (if (= 0 eshell-last-command-status) + "^_^" + ";_;") + " " + (abbreviate-file-name (eshell/pwd)) + (if (= (user-uid) 0) + " # " + " $ ")))) + +(add-hook 'eshell-mode-hook + (defun eshell-setup () + (setq-local outline-regexp eshell-prompt-regexp) + (setq-local page-delimiter eshell-prompt-regexp) + (setq-local imenu-generic-expression + '(("Prompt" " \\($\\|#\\) \\(.*\\)" 2))) + (setq-local truncate-lines t))) + +(setenv "PAGER" (executable-find "cat")) + +(defun eshellp (buffer-or-name) + (with-current-buffer buffer-or-name + (derived-mode-p 'eshell-mode))) + +(defun eshell-pop-up (&optional arg) + "Pop up an eshell in the `default-directory'. +NEW is passed to `eshell'." + (interactive "P") + (require 'eshell) + (let ((dir default-directory) + (display-comint-buffer-action 'pop-to-buffer)) + (if-let ((buf (and (not arg) + (or (get-buffer eshell-buffer-name) + (seq-find #'eshellp (reverse (buffer-list))))))) + (pop-to-buffer buf) + (eshell arg)) + ;; In the eshell buffer + (unless (equal default-directory dir) + (eshell/cd dir) + (eshell-send-input) + (goto-char (point-max))))) + +(keymap-global-set "C-z" #'eshell-pop-up) +(with-eval-after-load 'esh-mode + (keymap-set eshell-mode-map "C-z" #'quit-window)) + +(when (package-ensure 'eat) + (add-hook 'eshell-first-time-mode-hook #'eat-eshell-mode)) + +(when (package-ensure 'wiki-abbrev t) + (wiki-abbrev-insinuate) + (add-hook 'text-mode-hook #'abbrev-mode)) (put 'list-timers 'disabled nil) - -(defun string-intersperse (strings delim) - "Intersperse STRINGS with DELIM and return the concatenated result." - (cl-loop for string in strings - concat (concat delim string) into acc - finally return (substring acc (length delim)))) - -(add-to-list 'inhibit-message-regexps - (string-intersperse (map 'list #'expand-file-name - (list recentf-save-file - save-place-file)) - "\\|")) - -(add-to-list 'set-message-functions 'inhibit-message) - -(ensure-package 'web-mode) -(add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode)) -(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode)) - -(add-hook 'help-fns-describe-function-functions - #'shortdoc-help-fns-examples-function) -- cgit 1.4.1-21-gabe81