diff --git a/README.org b/README.org index e2523ef..dec097c 100644 --- a/README.org +++ b/README.org @@ -322,6 +322,8 @@ of [[info:emacs#Saving Customizations][saving customizations (info)]]. ;; Enable `package-install-upgrade-built-in' to upgrade Org and transient. ;; Caveat: works with `list-packages' but not with `package-upgrade-all'. ;; Disable upgrading other packages by pinning them to "nongnu". +(defvar package-pinned-packages) + (setopt package-install-upgrade-built-in t package-pinned-packages (cl-union '((bind-key . "nongnu") @@ -392,6 +394,8 @@ version. #+caption: Install the selected packages. #+name: lst:install-selected-packages #+begin_src emacs-lisp -n :results silent +(defvar package-selected-packages) + (unless noninteractive (unless (require 'no-littering nil 'noerror) (unless (bound-and-true-p package-archive-contents) @@ -679,8 +683,8 @@ and [[lst:3rd-window-management]] implement a selection of his recommendations. (defun make-display-buffer-matcher-function (major-modes) "Return a lambda function to match a list of MAJOR-MODES." - (lambda (buffer-name action) - (with-current-buffer buffer-name (apply #'derived-mode-p major-modes)))) + (lambda (buf-name _action) + (with-current-buffer buf-name (apply #'derived-mode-p major-modes)))) (keymap-global-set "M-o" #'other-window)) #+end_src @@ -968,7 +972,7 @@ minibuffer completion]]. #+caption: Set =minibuffer= options. #+name: lst:set-minibuffer-options #+begin_src emacs-lisp -n :results silent -(when (require 'minibuffer nil 'noerror) +(when (require 'minibuffer nil 'noerror) ;; https://www.masteringemacs.org/article/understanding-minibuffer-completion (setopt completion-category-overrides '((file (styles basic substring))) @@ -1434,12 +1438,16 @@ histories. (setopt savehist-additional-variables '(kill-ring search-ring)) (savehist-mode +1)) (when (and (ensure-package-installation 'vertico) - (fboundp 'vertico-mode)) - (vertico-mode +1)) -(with-eval-after-load 'vertico - (keymap-set vertico-map "RET" #'vertico-directory-enter) - (keymap-set vertico-map "DEL" #'vertico-directory-delete-char) - (keymap-set vertico-map "M-DEL" #'vertico-directory-delete-word)) + (fboundp 'vertico-directory-delete-char) + (fboundp 'vertico-directory-delete-word) + (fboundp 'vertico-mode) + (fboundp 'vertico-directory-enter)) + (vertico-mode +1) + (with-eval-after-load 'vertico + (defvar vertico-map) + (keymap-set vertico-map "DEL" #'vertico-directory-delete-char) + (keymap-set vertico-map "M-DEL" #'vertico-directory-delete-word) + (keymap-set vertico-map "RET" #'vertico-directory-enter))) #+end_src #+attr_latex: :booktabs yes :float table @@ -1649,13 +1657,17 @@ Consult usage tips are: #+caption: Implement =find-file= with previewing before selection. #+name: lst:find-file-preview #+begin_src emacs-lisp -n :results silent -(defun consult-find-file-preview(cue &optional dir default mustmatch init pred) +(defun consult-find-file-preview + (prompt &optional dir _default mustmatch initial predicate) + "Helper to implement previewing when calling `find-file'. +See `read-file-name' for the meaning of PROMPT, DIR, MUSTMATCH, INITIAL, +and PREDICATE." (interactive) (let ((default-directory (or dir default-directory)) (minibuffer-completing-file-name t)) (consult--read #'read-file-name-internal :state (consult--file-preview) - :prompt cue :initial init - :require-match mustmatch :predicate pred))) + :prompt prompt :initial initial + :require-match mustmatch :predicate predicate))) (defun toggle-find-file-preview () "Toggle previewing when calling `find-file'." @@ -1680,7 +1692,8 @@ configures =company= after ensuring the =company= installation. #+caption: Setup =company=. #+name: lst:setup-company #+begin_src emacs-lisp -n :results silent -(when (ensure-package-installation 'company) +(when (and (ensure-package-installation 'company) + (fboundp 'company-mode)) ;; https://github.com/purcell/emacs.d/issues/778 (setopt company-transformers '(company-sort-by-occurrence)) (dolist (symbol '(LaTeX-mode-hook @@ -1864,7 +1877,7 @@ regexp strings. #+caption: Ensure =xr= installation. #+name: lst:ensure-xr-installation #+begin_src emacs-lisp -n :results silent -(when (ensure-package-installation 'xr)) +(when (ensure-package-installation 'xr) t) #+end_src * [[info:emacs#Version Control][Version Control (info)]] @@ -1887,17 +1900,17 @@ single frame and to make all text visible prior to ediffing Org buffers. #+caption: Setup =ediff=. #+name: lst:setup-ediff #+begin_src emacs-lisp -n :results silent -(with-eval-after-load 'emacs - (setopt ediff-merge-split-window-function #'split-window-horizontally - ediff-split-window-function #'split-window-horizontally - ediff-window-setup-function #'ediff-setup-windows-plain)) +(with-eval-after-load 'ediff-wind + (when (fboundp 'ediff-setup-windows-plain) + (setopt ediff-merge-split-window-function #'split-window-horizontally + ediff-split-window-function #'split-window-horizontally + ediff-window-setup-function #'ediff-setup-windows-plain))) (with-eval-after-load 'org ;; https://github.com/oantolin/emacs-config#readme - (defun ediff-with-org-show-all () - "Expand all headings prior to ediffing org buffers." - (add-hook 'ediff-prepare-buffer-hook #'org-fold-show-all nil t)) - - (add-hook 'org-mode-hook #'ediff-with-org-show-all)) + (add-hook 'org-mode-hook + (defun ediff-with-org-show-all () + "Expand all headings prior to ediffing org buffers." + (add-hook 'ediff-prepare-buffer-hook #'org-fold-show-all nil t)))) #+end_src ** [[https://git-scm.com/book/en/v2][Git]] @@ -2105,7 +2118,6 @@ configuration objectives: TeX-engine 'luatex TeX-indent-close-delimiters "]" TeX-indent-open-delimiters "[" - TeX-install-font-lock #'font-latex-setup TeX-parse-self t ;; Disable `TeX-electric-math' to prevent collisions with `smartparens'. TeX-electric-math nil))) @@ -2165,7 +2177,7 @@ configuration objectives: #+caption: Configure =markdown-mode=. #+name: lst:configure-markdown-mode #+begin_src emacs-lisp -n :results silent -(when (ensure-package-installation 'markdown-mode)) +(when (ensure-package-installation 'markdown-mode) t) #+end_src ** Writing [[info:org#Top][Org (info)]] files and [[info:org#Activation][Org activation (info)]] @@ -2333,7 +2345,8 @@ Watch out for completion `visit-tags-table' prompts." (with-eval-after-load 'ob-lisp ;; Default to `sly-eval' whenever feasible: - (when (package-installed-p 'sly) + (when (and (package-installed-p 'sly) + (fboundp 'sly-eval)) (setopt org-babel-lisp-eval-fn #'sly-eval))) #+end_src @@ -2341,10 +2354,10 @@ Watch out for completion `visit-tags-table' prompts." #+caption: Unevaluable source blocks. #+name: lst:no-org-babel-eval #+begin_src emacs-lisp -n :results silent -(setq org-babel-default-header-args:conf '((:eval . "no"))) -(setq org-babel-default-header-args:diff '((:eval . "no"))) -(setq org-babel-default-header-args:text '((:eval . "no"))) -(setq org-babel-default-header-args:toml '((:eval . "no"))) +(defvar org-babel-default-header-args:conf '((:eval . "no"))) +(defvar org-babel-default-header-args:diff '((:eval . "no"))) +(defvar org-babel-default-header-args:text '((:eval . "no"))) +(defvar org-babel-default-header-args:toml '((:eval . "no"))) ;;; Kludges to silence `org-lint' warnings: (defun org-babel-execute:conf (_body _params) @@ -2390,94 +2403,93 @@ Undo this by means of `org-delete-property-globally'." #+caption: Setup =org-mode-map= 1. #+name: lst:setup-org-mode-map-1 #+begin_src emacs-lisp -n :results silent -(with-eval-after-load 'emacs - ;; From: "Nicolas Richard" - ;; Date: Fri, 08 Mar 2013 16:23:02 +0100 [thread overview] - ;; Message-ID: <87vc913oh5.fsf@yahoo.fr> (raw) - (defun org-electric-dollar () - "When called once, insert \\(\\) and leave point in between. +;; From: "Nicolas Richard" +;; Date: Fri, 08 Mar 2013 16:23:02 +0100 [thread overview] +;; Message-ID: <87vc913oh5.fsf@yahoo.fr> (raw) +(defun org-electric-dollar () + "When called once, insert \\(\\) and leave point in between. When called twice, replace the previously inserted \\(\\) by one $." - (interactive) - (if (and (looking-at "\\\\)") (looking-back "\\\\(" (- (point) 2))) - (progn (delete-char 2) - (delete-char -2) - (insert "$")) - (insert "\\(\\)") - (backward-char 2))) + (interactive) + (if (and (looking-at "\\\\)") (looking-back "\\\\(" (- (point) 2))) + (progn (delete-char 2) + (delete-char -2) + (insert "$")) + (insert "\\(\\)") + (backward-char 2))) - (defun org-active-current-time-stamp () - "Insert an active date-time stamp of the current time." - (interactive) - (if (fboundp 'org-insert-time-stamp) - (org-insert-time-stamp (current-time) 'with-hm) - (let ((stamp (format-time-string "<%Y-%m-%d %a %H:%M>" (current-time)))) - (insert stamp) - stamp))) +(defun org-active-current-time-stamp () + "Insert an active date-time stamp of the current time." + (interactive) + (if (fboundp 'org-insert-time-stamp) + (org-insert-time-stamp (current-time) 'with-hm) + (let ((stamp (format-time-string "<%Y-%m-%d %a %H:%M>" (current-time)))) + (insert stamp) + stamp))) - (defun org-inactive-current-time-stamp () - "Insert an inactive date-time stamp of the current time." - (interactive) - (if (fboundp 'org-insert-time-stamp) - (org-insert-time-stamp (current-time) 'with-hm 'inactive) - (let ((stamp (format-time-string "[%Y-%m-%d %a %H:%M]" (current-time)))) - (insert stamp) - stamp))) +(defun org-inactive-current-time-stamp () + "Insert an inactive date-time stamp of the current time." + (interactive) + (if (fboundp 'org-insert-time-stamp) + (org-insert-time-stamp (current-time) 'with-hm 'inactive) + (let ((stamp (format-time-string "[%Y-%m-%d %a %H:%M]" (current-time)))) + (insert stamp) + stamp))) - (with-eval-after-load 'org - (setopt org-return-follows-link t) - (keymap-set org-mode-map "C-c " #'org-inactive-current-time-stamp) - (keymap-set org-mode-map "C-c C-" #'org-active-current-time-stamp) - (keymap-set org-mode-map "$" #'org-electric-dollar) - (keymap-set org-mode-map "M-q" #'org-fill-paragraph))) +(with-eval-after-load 'org + (setopt org-return-follows-link t) + (keymap-set org-mode-map "C-c " #'org-inactive-current-time-stamp) + (keymap-set org-mode-map "C-c C-" #'org-active-current-time-stamp) + (keymap-set org-mode-map "$" #'org-electric-dollar) + (keymap-set org-mode-map "M-q" #'org-fill-paragraph)) #+end_src #+caption[Setup =org-mode-map= 2]: #+caption: Setup =org-mode-map= 2. #+name: lst:setup-org-mode-map-2 #+begin_src emacs-lisp -n :results silent -(with-eval-after-load 'emacs - ;; Stolen from `org-insert-structure-template'. - ;; Note: `org-tempo' does not require `tempo' at all. - (defcustom org-insert-source-block-defaults - '("calc -n :results silent" - "emacs-lisp -n :results silent" - "julia -n :results silent" - "latex -n" - "lisp -n :results silent :package :cs325-user" - "org -n" - "python -i -n :results silent") - "Default values for `org-insert-source-block'." - :group 'org) +;; Stolen from `org-insert-structure-template'. +;; Note: `org-tempo' does not require `tempo' at all. +(defcustom org-insert-source-block-defaults + '("calc -n :results silent" + "emacs-lisp -n :results silent" + "julia -n :results silent" + "latex -n" + "lisp -n :results silent :package :cs325-user" + "org -n" + "python -i -n :results silent") + "Default values for `org-insert-source-block'." + :group 'org + :type '(repeat string)) - (defun org-insert-source-block () - "Insert a source block #+begin_src/SPEC/BODY/#+end_src. +(defun org-insert-source-block () + "Insert a source block #+begin_src/SPEC/BODY/#+end_src. Prompt for the source block SPEC. With an active region, the region becomes the block BODY. Otherwise, insert an empty block." - (interactive) - (let* ((spec (completing-read - "Block spec: " org-insert-source-block-defaults nil 'confirm)) - (region? (use-region-p)) - (region-start (and region? (region-beginning))) - (region-end (and region? (copy-marker (region-end))))) - (when region? (goto-char region-start)) - (let ((column (current-indentation))) - (if (save-excursion (skip-chars-backward " \t") (bolp)) - (forward-line 0) - (insert "\n")) - (save-excursion - (indent-to column) - (insert (format "#+begin_src %s\n" spec)) - (when region? - (org-escape-code-in-region (point) region-end) - (goto-char region-end) - (skip-chars-backward " \n\t\r") - (end-of-line)) - (unless (bolp) (insert "\n")) - (indent-to column) - (insert "#+end_src") - (if (looking-at "[ \t]*$") (replace-match "") (insert "\n")) - (when (and (eobp) (not (bolp))) (insert "\n"))) - (end-of-line))))) + (interactive) + (let* ((spec (completing-read + "Block spec: " org-insert-source-block-defaults nil 'confirm)) + (region? (use-region-p)) + (region-start (and region? (region-beginning))) + (region-end (and region? (copy-marker (region-end))))) + (when region? (goto-char region-start)) + (let ((column (current-indentation))) + (if (save-excursion (skip-chars-backward " \t") (bolp)) + (forward-line 0) + (insert "\n")) + (save-excursion + (indent-to column) + (insert (format "#+begin_src %s\n" spec)) + (when region? + (org-escape-code-in-region (point) region-end) + (goto-char region-end) + (skip-chars-backward " \n\t\r") + (end-of-line)) + (unless (bolp) (insert "\n")) + (indent-to column) + (insert "#+end_src") + (if (looking-at "[ \t]*$") (replace-match "") (insert "\n")) + (when (and (eobp) (not (bolp))) (insert "\n"))) + (end-of-line)))) (with-eval-after-load 'org (keymap-set org-mode-map "C-c C-;" #'org-insert-source-block)) @@ -3433,57 +3445,60 @@ The listings below implement or reimplement three groups of =org-link= types: #+caption: Define an =org-link= type for =pdf-tools=. #+name: lst:define-org-pdfview-link-type #+begin_src emacs-lisp -n :results silent +(declare-function pdf-view-current-page "pdf-macs" (&optional window)) +(declare-function pdf-view-goto-page "pdf-view" (page &optional window)) + +(defun org-pdfview-export (link description backend _) + "Export LINK as a \"pdfview\" type link using DESCRIPTION and export BACKEND. +The paths of the links export as file relative paths in order to +facilate moving single directories or whole directory trees." + (let ((path (if (string-match "\\(.+\\)::.+" link) + (match-string 1 link) + link)) + (desc (or description link))) + (when (stringp path) + (setq path (file-relative-name path)) + (pcase backend + (`html (format "%s" path desc)) + (`latex (format "\\href{%s}{%s}" path desc)) + (`ascii (format "%s (%s)" desc path)) + (_ path))))) + +(defun org-pdfview-open (link _) + "Open LINK as a \"pdfview\" type link." + (string-match "\\(.*?\\)\\(?:::\\([0-9]+\\)\\)?$" link) + (let ((path (match-string 1 link)) + (page (and (match-beginning 2) + (string-to-number (match-string 2 link))))) + ;; Let Org mode open the file to respect org-link-frame-setup. + (org-open-file path 1) + (when page (pdf-view-goto-page page)))) + +(defun org-pdfview-store-link () + "Store a \"pdfview\" type link." + (when (eq major-mode 'pdf-view-mode) + (let* ((path buffer-file-name) + (page (pdf-view-current-page)) + (link (concat "pdfview:" path "::" (number-to-string page)))) + (org-link-store-props + :type "pdfview" + :link link + :description path)))) + (with-eval-after-load 'ol - (autoload 'pdf-view-goto-page - "Go to PAGE in PDF with optional WINDOW to go to PAGE in all windows." - nil t) (org-link-set-parameters "pdfview" :follow #'org-pdfview-open :export #'org-pdfview-export - :store #'org-pdfview-store-link) - - (defun org-pdfview-export (link description backend _) - "Export a \"pdfview\" type link. -The paths of the links export as file relative paths in order to -facilate moving single directories or whole directory trees." - (let ((path (if (string-match "\\(.+\\)::.+" link) - (match-string 1 link) - link)) - (desc (or description link))) - (when (stringp path) - (setq path (file-relative-name path)) - (pcase backend - (`html (format "%s" path desc)) - (`latex (format "\\href{%s}{%s}" path desc)) - (`ascii (format "%s (%s)" desc path)) - (_ path))))) - - (defun org-pdfview-open (link _) - "Open a \"pdfview\" type link." - (string-match "\\(.*?\\)\\(?:::\\([0-9]+\\)\\)?$" link) - (let ((path (match-string 1 link)) - (page (and (match-beginning 2) - (string-to-number (match-string 2 link))))) - ;; Let Org mode open the file to respect org-link-frame-setup. - (org-open-file path 1) - (when page (pdf-view-goto-page page)))) - - (defun org-pdfview-store-link () - "Store a \"pdfview\" type link." - (when (eq major-mode 'pdf-view-mode) - (let* ((path buffer-file-name) - (page (pdf-view-current-page)) - (link (concat "pdfview:" path "::" (number-to-string page)))) - (org-link-store-props - :type "pdfview" - :link link - :description path))))) + :store #'org-pdfview-store-link)) #+end_src #+caption[Patch =ol-info=]: #+caption: Patch =ol-info=. #+name: lst:patch-org-info-link-type #+begin_src emacs-lisp -n :exports code :results silent +(declare-function org-info-map-html-url "ol-info" (filename)) +(declare-function org-info--expand-node-name "ol-info" (node)) + (with-eval-after-load 'ol-info (defun org-info-export (path desc format) "Export an info link. @@ -3553,6 +3568,11 @@ The following posts provide programming information: #+name: lst:define-org-yt-link-type #+begin_src emacs-lisp -n :results silent ;; https://bitspook.in/blog/extending-org-mode-to-handle-youtube-paths/ +(declare-function emms-add-url "emms-source-file" (url)) +(declare-function emms-playlist-last "emms" ()) +(declare-function emms-playlist-mode-play-current-track "emms-playlist-mode" ()) +(declare-function with-current-emms-playlist "emms" (&rest body)) + (defun org-yt-emms-open (path) "Open an \"YouTube\" PATH link with `emms' using \"mpv\"." (let ((url (format "https://www.youtube.com/watch?v=%s" path))) @@ -4425,16 +4445,11 @@ it for this buffer is by typing {{{kbd(C-c C-e t U)}}} to export the it to a (when (and (ensure-package-installation 'writegood-mode) (fboundp 'writegood-mode)) - (defcustom writegood-mode-for '(org-mode text-mode) - "List of modes for which to enable `writegood-mode'." - :group 'writegood - :type '(repeat symbol)) - (add-hook 'after-init-hook (defun on-after-change-mode-hook-enable-writegood-mode () (add-hook 'after-change-major-mode-hook (defun enable-writegood-mode () - (when (derived-mode-p writegood-mode-for) + (when (derived-mode-p '(org-mode text-mode)) (writegood-mode +1))))) 'depth) @@ -4462,8 +4477,8 @@ true in case document headlines contain links. The code in listing (setopt which-func-modes '(emacs-lisp-mode org-mode pdf-view-mode))) -;; It looks like `python-mode' does nothing when they it is an element -;; of `which-func-modes'. +;; It looks like `python-mode' does nothing when it is an element of +;; `which-func-modes'. ;; (setopt which-func-modes t) @@ -4475,6 +4490,8 @@ true in case document headlines contain links. The code in listing #+name: lst:define-for-which-func-functions #+begin_src emacs-lisp -n :results silent ;; https://emacs.stackexchange.com/questions/30894/ +(declare-function pdf-info-outline "pdf-info" (&optional file-or-buffer)) +(declare-function org-element-type-p "org-element-ast" (node types)) (defvar which-func-functions nil) (defun which-func-org-function () @@ -4496,7 +4513,6 @@ Return the document title when point is above the first headline." (count (length chain))) (setq text (format "%s|%s" count (nth (1- count) chain))))) text))) - (add-to-list 'which-func-functions 'which-func-org-function) (defun which-func-pdf-view-function () @@ -4519,7 +4535,6 @@ Return \"Front Matter\" when current page is above the first headline." (setq new-title (alist-get 'title (nth hl-index outline))) (cl-incf hl-index)) (if (< current-page hl-page) old-title new-title)))) - (add-to-list 'which-func-functions 'which-func-pdf-view-function) #+end_src @@ -4617,6 +4632,7 @@ julia --project=eglot-jl-20240318.1159/ eglot-jl-20240318.1159/eglot-jl.jl #+name: lst:eglot-maybe-ensure #+begin_src emacs-lisp -n :results silent (defun eglot-maybe-ensure () + "Call `eglot-ensure' under favorable conditions." (when (and (apply #'derived-mode-p '(python-mode)) (assoc 'eglot-workspace-configuration dir-local-variables-alist)) (eglot-ensure))) @@ -6501,7 +6517,7 @@ setup and requires no configuration. #+name: lst:configure-ws-butler #+begin_src emacs-lisp -n :results silent (when (and (ensure-package-installation 'ws-butler) - (require 'ws-butler nil 'noerror)) + (fboundp 'ws-butler-mode)) (setopt ws-butler-keep-whitespace-before-point nil) (add-hook 'prog-mode-hook #'ws-butler-mode) (add-hook 'text-mode-hook #'ws-butler-mode)) @@ -6568,18 +6584,20 @@ contrary to for instance [[https://github.com/Fanael/rainbow-delimiters#readme][ ("C-M-(" . sp-backward-barf-sexp) ("C-M-)" . sp-forward-barf-sexp))) - (dolist (symbol '(conf-toml-mode-hook prog-mode-hook text-mode-hook)) - (add-hook symbol #'smartparens-mode)) + (when (fboundp 'smartparens-mode) + (dolist (symbol '(conf-toml-mode-hook prog-mode-hook text-mode-hook)) + (add-hook symbol #'smartparens-mode))) - (dolist (symbol '(emacs-lisp-mode-hook - go-ts-mode-hook - ielm-mode-hook - inferior-python-mode-hook - lisp-data-mode-hook - lisp-mode-hook - python-mode-hook - sly-mrepl-mode-hook)) - (add-hook symbol #'smartparens-strict-mode)) + (when (fboundp 'smartparens-strict-mode) + (dolist (symbol '(emacs-lisp-mode-hook + go-ts-mode-hook + ielm-mode-hook + inferior-python-mode-hook + lisp-data-mode-hook + lisp-mode-hook + python-mode-hook + sly-mrepl-mode-hook)) + (add-hook symbol #'smartparens-strict-mode))) (when (fboundp 'go-ts-mode) ;; Stolen from `smartparens-go': @@ -6672,11 +6690,12 @@ formatter for Python]]. (when (ensure-package-installation 'yasnippet) ;; Set `yas-alias-to-yas/prefix-p' before loading `yasnippet'. (setopt yas-alias-to-yas/prefix-p nil) - (dolist (symbol '(LaTeX-mode-hook - org-mode-hook - python-mode-hook - python-ts-mode-hook)) - (add-hook symbol #'yas-minor-mode))) + (when (fboundp 'yas-minor-mode) + (dolist (symbol '(LaTeX-mode-hook + org-mode-hook + python-mode-hook + python-ts-mode-hook)) + (add-hook symbol #'yas-minor-mode)))) #+end_src * [[info:emacs#Display][Display (info)]] @@ -6719,8 +6738,8 @@ applies first. Narrowing to org-src-block actually calls With prefix P, don't widen, just narrow even if buffer is already narrowed." + (declare (interactive-only t)) (interactive "P") - (declare (interactive-only)) (cond ((and (buffer-narrowed-p) (not p)) (widen)) ((and (bound-and-true-p org-src-mode) (not p))