Copy over the search, window and dired modules from Prot
Make some changes based on my preferences, but not many.
This commit is contained in:
parent
166d46c4cb
commit
03024fa9d4
6 changed files with 2602 additions and 0 deletions
422
custom-lisp/prot-common.el
Normal file
422
custom-lisp/prot-common.el
Normal file
|
@ -0,0 +1,422 @@
|
|||
;;; prot-common.el --- Common functions for my dotemacs -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2020-2024 Protesilaos Stavrou
|
||||
|
||||
;; Author: Protesilaos Stavrou <info@protesilaos.com>
|
||||
;; URL: https://protesilaos.com/emacs/dotemacs
|
||||
;; Version: 0.1.0
|
||||
;; Package-Requires: ((emacs "30.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or (at
|
||||
;; your option) any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; Common functions for my Emacs: <https://protesilaos.com/emacs/dotemacs/>.
|
||||
;;
|
||||
;; Remember that every piece of Elisp that I write is for my own
|
||||
;; educational and recreational purposes. I am not a programmer and I
|
||||
;; do not recommend that you copy any of this if you are not certain of
|
||||
;; what it does.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile
|
||||
(require 'subr-x)
|
||||
(require 'cl-lib))
|
||||
|
||||
(defgroup prot-common ()
|
||||
"Auxiliary functions for my dotemacs."
|
||||
:group 'editing)
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-number-even-p (n)
|
||||
"Test if N is an even number."
|
||||
(if (numberp n)
|
||||
(= (% n 2) 0)
|
||||
(error "%s is not a number" n)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-number-integer-p (n)
|
||||
"Test if N is an integer."
|
||||
(if (integerp n)
|
||||
n
|
||||
(error "%s is not an integer" n)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-number-integer-positive-p (n)
|
||||
"Test if N is a positive integer."
|
||||
(if (prot-common-number-integer-p n)
|
||||
(> n 0)
|
||||
(error "%s is not a positive integer" n)))
|
||||
|
||||
;; Thanks to Gabriel for providing a cleaner version of
|
||||
;; `prot-common-number-negative': <https://github.com/gabriel376>.
|
||||
;;;###autoload
|
||||
(defun prot-common-number-negative (n)
|
||||
"Make N negative."
|
||||
(if (and (numberp n) (> n 0))
|
||||
(* -1 n)
|
||||
(error "%s is not a valid positive number" n)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-reverse-percentage (number percent change-p)
|
||||
"Determine the original value of NUMBER given PERCENT.
|
||||
|
||||
CHANGE-P should specify the increase or decrease. For simplicity,
|
||||
nil means decrease while non-nil stands for an increase.
|
||||
|
||||
NUMBER must satisfy `numberp', while PERCENT must be `natnump'."
|
||||
(unless (numberp number)
|
||||
(user-error "NUMBER must satisfy numberp"))
|
||||
(unless (natnump percent)
|
||||
(user-error "PERCENT must satisfy natnump"))
|
||||
(let* ((pc (/ (float percent) 100))
|
||||
(pc-change (if change-p (+ 1 pc) pc))
|
||||
(n (if change-p pc-change (float (- 1 pc-change)))))
|
||||
;; FIXME 2021-12-21: If float, round to 4 decimal points.
|
||||
(/ number n)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-percentage-change (n-original n-final)
|
||||
"Find percentage change between N-ORIGINAL and N-FINAL numbers.
|
||||
|
||||
When the percentage is not an integer, it is rounded to 4
|
||||
floating points: 16.666666666666664 => 16.667."
|
||||
(unless (numberp n-original)
|
||||
(user-error "N-ORIGINAL must satisfy numberp"))
|
||||
(unless (numberp n-final)
|
||||
(user-error "N-FINAL must satisfy numberp"))
|
||||
(let* ((difference (float (abs (- n-original n-final))))
|
||||
(n (* (/ difference n-original) 100))
|
||||
(round (floor n)))
|
||||
;; FIXME 2021-12-21: Any way to avoid the `string-to-number'?
|
||||
(if (> n round) (string-to-number (format "%0.4f" n)) round)))
|
||||
|
||||
;; REVIEW 2023-04-07 07:43 +0300: I just wrote the conversions from
|
||||
;; seconds. Hopefully they are correct, but I need to double check.
|
||||
(defun prot-common-seconds-to-minutes (seconds)
|
||||
"Convert a number representing SECONDS to MM:SS notation."
|
||||
(let ((minutes (/ seconds 60))
|
||||
(seconds (% seconds 60)))
|
||||
(format "%.2d:%.2d" minutes seconds)))
|
||||
|
||||
(defun prot-common-seconds-to-hours (seconds)
|
||||
"Convert a number representing SECONDS to HH:MM:SS notation."
|
||||
(let* ((hours (/ seconds 3600))
|
||||
(minutes (/ (% seconds 3600) 60))
|
||||
(seconds (% seconds 60)))
|
||||
(format "%.2d:%.2d:%.2d" hours minutes seconds)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-seconds-to-minutes-or-hours (seconds)
|
||||
"Convert SECONDS to either minutes or hours, depending on the value."
|
||||
(if (> seconds 3599)
|
||||
(prot-common-seconds-to-hours seconds)
|
||||
(prot-common-seconds-to-minutes seconds)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-rotate-list-of-symbol (symbol)
|
||||
"Rotate list value of SYMBOL by moving its car to the end.
|
||||
Return the first element before performing the rotation.
|
||||
|
||||
This means that if `sample-list' has an initial value of `(one
|
||||
two three)', this function will first return `one' and update the
|
||||
value of `sample-list' to `(two three one)'. Subsequent calls
|
||||
will continue rotating accordingly."
|
||||
(unless (symbolp symbol)
|
||||
(user-error "%s is not a symbol" symbol))
|
||||
(when-let* ((value (symbol-value symbol))
|
||||
(list (and (listp value) value))
|
||||
(first (car list)))
|
||||
(set symbol (append (cdr list) (list first)))
|
||||
first))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-empty-buffer-p ()
|
||||
"Test whether the buffer is empty."
|
||||
(or (= (point-min) (point-max))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (and (looking-at "^\\([a-zA-Z]+: ?\\)?$")
|
||||
(zerop (forward-line 1))))
|
||||
(eobp))))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-minor-modes-active ()
|
||||
"Return list of active minor modes for the current buffer."
|
||||
(let ((active-modes))
|
||||
(mapc (lambda (m)
|
||||
(when (and (boundp m) (symbol-value m))
|
||||
(push m active-modes)))
|
||||
minor-mode-list)
|
||||
active-modes))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-truncate-lines-silently ()
|
||||
"Toggle line truncation without printing messages."
|
||||
(let ((inhibit-message t))
|
||||
(toggle-truncate-lines t)))
|
||||
|
||||
;; NOTE 2023-08-12: I tried the `clear-message-function', but it did
|
||||
;; not work. What I need is very simple and this gets the job done.
|
||||
;;;###autoload
|
||||
(defun prot-common-clear-minibuffer-message (&rest _)
|
||||
"Print an empty message to clear the echo area.
|
||||
Use this as advice :after a noisy function."
|
||||
(message ""))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-disable-hl-line ()
|
||||
"Disable Hl-Line-Mode (for hooks)."
|
||||
(hl-line-mode -1))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-window-bounds ()
|
||||
"Return start and end points in the window as a cons cell."
|
||||
(cons (window-start) (window-end)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-page-p ()
|
||||
"Return non-nil if there is a `page-delimiter' in the buffer."
|
||||
(or (save-excursion (re-search-forward page-delimiter nil t))
|
||||
(save-excursion (re-search-backward page-delimiter nil t))))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-window-small-p ()
|
||||
"Return non-nil if window is small.
|
||||
Check if the `window-width' or `window-height' is less than
|
||||
`split-width-threshold' and `split-height-threshold',
|
||||
respectively."
|
||||
(or (and (numberp split-width-threshold)
|
||||
(< (window-total-width) split-width-threshold))
|
||||
(and (numberp split-height-threshold)
|
||||
(> (window-total-height) split-height-threshold))))
|
||||
|
||||
(defun prot-common-window-narrow-p ()
|
||||
"Return non-nil if window is narrow.
|
||||
Check if the `window-width' is less than `split-width-threshold'."
|
||||
(and (numberp split-width-threshold)
|
||||
(< (window-total-width) split-width-threshold)))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-three-or-more-windows-p (&optional frame)
|
||||
"Return non-nil if three or more windows occupy FRAME.
|
||||
If FRAME is non-nil, inspect the current frame."
|
||||
(>= (length (window-list frame :no-minibuffer)) 3))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-read-data (file)
|
||||
"Read Elisp data from FILE."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(read (current-buffer))))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-completion-category ()
|
||||
"Return completion category."
|
||||
(when-let* ((window (active-minibuffer-window)))
|
||||
(with-current-buffer (window-buffer window)
|
||||
(completion-metadata-get
|
||||
(completion-metadata (buffer-substring-no-properties
|
||||
(minibuffer-prompt-end)
|
||||
(max (minibuffer-prompt-end) (point)))
|
||||
minibuffer-completion-table
|
||||
minibuffer-completion-predicate)
|
||||
'category))))
|
||||
|
||||
;; Thanks to Omar Antolín Camarena for providing this snippet!
|
||||
;;;###autoload
|
||||
(defun prot-common-completion-table (category candidates)
|
||||
"Pass appropriate metadata CATEGORY to completion CANDIDATES.
|
||||
|
||||
This is intended for bespoke functions that need to pass
|
||||
completion metadata that can then be parsed by other
|
||||
tools (e.g. `embark')."
|
||||
(lambda (string pred action)
|
||||
(if (eq action 'metadata)
|
||||
`(metadata (category . ,category))
|
||||
(complete-with-action action candidates string pred))))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-completion-table-no-sort (category candidates)
|
||||
"Pass appropriate metadata CATEGORY to completion CANDIDATES.
|
||||
Like `prot-common-completion-table' but also disable sorting."
|
||||
(lambda (string pred action)
|
||||
(if (eq action 'metadata)
|
||||
`(metadata (category . ,category)
|
||||
(display-sort-function . ,#'identity))
|
||||
(complete-with-action action candidates string pred))))
|
||||
|
||||
;; Thanks to Igor Lima for the `prot-common-crm-exclude-selected-p':
|
||||
;; <https://github.com/0x462e41>.
|
||||
;; This is used as a filter predicate in the relevant prompts.
|
||||
(defvar crm-separator)
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-crm-exclude-selected-p (input)
|
||||
"Filter out INPUT from `completing-read-multiple'.
|
||||
Hide non-destructively the selected entries from the completion
|
||||
table, thus avoiding the risk of inputting the same match twice.
|
||||
|
||||
To be used as the PREDICATE of `completing-read-multiple'."
|
||||
(if-let* ((pos (string-match-p crm-separator input))
|
||||
(rev-input (reverse input))
|
||||
(element (reverse
|
||||
(substring rev-input 0
|
||||
(string-match-p crm-separator rev-input))))
|
||||
(flag t))
|
||||
(progn
|
||||
(while pos
|
||||
(if (string= (substring input 0 pos) element)
|
||||
(setq pos nil)
|
||||
(setq input (substring input (1+ pos))
|
||||
pos (string-match-p crm-separator input)
|
||||
flag (when pos t))))
|
||||
(not flag))
|
||||
t))
|
||||
|
||||
;; The `prot-common-line-regexp-p' and `prot-common--line-regexp-alist'
|
||||
;; are contributed by Gabriel: <https://github.com/gabriel376>. They
|
||||
;; provide a more elegant approach to using a macro, as shown further
|
||||
;; below.
|
||||
(defvar prot-common--line-regexp-alist
|
||||
'((empty . "[\s\t]*$")
|
||||
(indent . "^[\s\t]+")
|
||||
(non-empty . "^.+$")
|
||||
(list . "^\\([\s\t#*+]+\\|[0-9]+[^\s]?[).]+\\)")
|
||||
(heading . "^[=-]+"))
|
||||
"Alist of regexp types used by `prot-common-line-regexp-p'.")
|
||||
|
||||
(defun prot-common-line-regexp-p (type &optional n)
|
||||
"Test for TYPE on line.
|
||||
TYPE is the car of a cons cell in
|
||||
`prot-common--line-regexp-alist'. It matches a regular
|
||||
expression.
|
||||
|
||||
With optional N, search in the Nth line from point."
|
||||
(save-excursion
|
||||
(goto-char (line-beginning-position))
|
||||
(and (not (bobp))
|
||||
(or (beginning-of-line n) t)
|
||||
(save-match-data
|
||||
(looking-at
|
||||
(alist-get type prot-common--line-regexp-alist))))))
|
||||
|
||||
;; The `prot-common-shell-command-with-exit-code-and-output' function is
|
||||
;; courtesy of Harold Carr, who also sent a patch that improved
|
||||
;; `prot-eww-download-html' (from the `prot-eww.el' library).
|
||||
;;
|
||||
;; More about Harold: <http://haroldcarr.com/about/>.
|
||||
(defun prot-common-shell-command-with-exit-code-and-output (command &rest args)
|
||||
"Run COMMAND with ARGS.
|
||||
Return the exit code and output in a list."
|
||||
(with-temp-buffer
|
||||
(list (apply 'call-process command nil (current-buffer) nil args)
|
||||
(buffer-string))))
|
||||
|
||||
(defvar prot-common-url-regexp
|
||||
(concat
|
||||
"~?\\<\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]*\\)"
|
||||
"[.@]"
|
||||
"\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]+\\)\\>/?")
|
||||
"Regular expression to match (most?) URLs or email addresses.")
|
||||
|
||||
(autoload 'auth-source-search "auth-source")
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-auth-get-field (host prop)
|
||||
"Find PROP in `auth-sources' for HOST entry."
|
||||
(when-let* ((source (auth-source-search :host host)))
|
||||
(if (eq prop :secret)
|
||||
(funcall (plist-get (car source) prop))
|
||||
(plist-get (flatten-list source) prop))))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-parse-file-as-list (file)
|
||||
"Return the contents of FILE as a list of strings.
|
||||
Strings are split at newline characters and are then trimmed for
|
||||
negative space.
|
||||
|
||||
Use this function to provide a list of candidates for
|
||||
completion (per `completing-read')."
|
||||
(split-string
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(buffer-substring-no-properties (point-min) (point-max)))
|
||||
"\n" :omit-nulls "[\s\f\t\n\r\v]+"))
|
||||
|
||||
(defun prot-common-ignore (&rest _)
|
||||
"Use this as override advice to make a function do nothing."
|
||||
nil)
|
||||
|
||||
;; NOTE 2023-06-02: The `prot-common-wcag-formula' and
|
||||
;; `prot-common-contrast' are taken verbatim from my `modus-themes'
|
||||
;; and renamed to have the prefix `prot-common-' instead of
|
||||
;; `modus-themes-'. This is all my code, of course, but I do it this
|
||||
;; way to ensure that this file is self-contained in case someone
|
||||
;; copies it.
|
||||
|
||||
;; This is the WCAG formula: <https://www.w3.org/TR/WCAG20-TECHS/G18.html>.
|
||||
(defun prot-common-wcag-formula (hex)
|
||||
"Get WCAG value of color value HEX.
|
||||
The value is defined in hexadecimal RGB notation, such #123456."
|
||||
(cl-loop for k in '(0.2126 0.7152 0.0722)
|
||||
for x in (color-name-to-rgb hex)
|
||||
sum (* k (if (<= x 0.03928)
|
||||
(/ x 12.92)
|
||||
(expt (/ (+ x 0.055) 1.055) 2.4)))))
|
||||
|
||||
;;;###autoload
|
||||
(defun prot-common-contrast (c1 c2)
|
||||
"Measure WCAG contrast ratio between C1 and C2.
|
||||
C1 and C2 are color values written in hexadecimal RGB."
|
||||
(let ((ct (/ (+ (prot-common-wcag-formula c1) 0.05)
|
||||
(+ (prot-common-wcag-formula c2) 0.05))))
|
||||
(max ct (/ ct))))
|
||||
|
||||
;;;; EXPERIMENTAL macros (not meant to be used anywhere)
|
||||
|
||||
;; TODO 2023-09-30: Try the same with `cl-defmacro' and &key
|
||||
(defmacro prot-common-if (condition &rest consequences)
|
||||
"Separate the CONSEQUENCES of CONDITION semantically.
|
||||
Like `if', `when', `unless' but done by using `:then' and `:else'
|
||||
keywords. The forms under each keyword of `:then' and `:else'
|
||||
belong to the given subset of CONSEQUENCES.
|
||||
|
||||
- The absence of `:else' means: (if CONDITION (progn CONSEQUENCES)).
|
||||
- The absence of `:then' means: (if CONDITION nil CONSEQUENCES).
|
||||
- Otherwise: (if CONDITION (progn then-CONSEQUENCES) else-CONSEQUENCES)."
|
||||
(declare (indent 1))
|
||||
(let (then-consequences else-consequences last-kw)
|
||||
(dolist (elt consequences)
|
||||
(let ((is-keyword (keywordp elt)))
|
||||
(cond
|
||||
((and (not is-keyword) (eq last-kw :then))
|
||||
(push elt then-consequences))
|
||||
((and (not is-keyword) (eq last-kw :else))
|
||||
(push elt else-consequences))
|
||||
((and is-keyword (eq elt :then))
|
||||
(setq last-kw :then))
|
||||
((and is-keyword (eq elt :else))
|
||||
(setq last-kw :else)))))
|
||||
`(if ,condition
|
||||
,(if then-consequences
|
||||
`(progn ,@(nreverse then-consequences))
|
||||
nil)
|
||||
,@(nreverse else-consequences))))
|
||||
|
||||
(provide 'prot-common)
|
||||
;;; prot-common.el ends here
|
236
custom-lisp/prot-window.el
Normal file
236
custom-lisp/prot-window.el
Normal file
|
@ -0,0 +1,236 @@
|
|||
;;; prot-window.el --- Display-buffer and window-related extensions for my dotemacs -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2023-2024 Protesilaos Stavrou
|
||||
|
||||
;; Author: Protesilaos Stavrou <info@protesilaos.com>
|
||||
;; URL: https://protesilaos.com/emacs/dotemacs
|
||||
;; Version: 0.1.0
|
||||
;; Package-Requires: ((emacs "30.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This covers my window and display-buffer extensions, for use in my
|
||||
;; Emacs setup: https://protesilaos.com/emacs/dotemacs.
|
||||
;;
|
||||
;; Remember that every piece of Elisp that I write is for my own
|
||||
;; educational and recreational purposes. I am not a programmer and I
|
||||
;; do not recommend that you copy any of this if you are not certain of
|
||||
;; what it does.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'prot-common)
|
||||
|
||||
(defvar prot-window-window-sizes
|
||||
'( :max-height (lambda () (floor (frame-height) 3))
|
||||
:min-height 10
|
||||
:max-width (lambda () (floor (frame-width) 4))
|
||||
:min-width 20)
|
||||
"Property list of maximum and minimum window sizes.
|
||||
The property keys are `:max-height', `:min-height', `:max-width',
|
||||
and `:min-width'. They all accept a value of either a
|
||||
number (integer or floating point) or a function.")
|
||||
|
||||
(defun prot-window--get-window-size (key)
|
||||
"Extract the value of KEY from `prot-window-window-sizes'."
|
||||
(when-let* ((value (plist-get prot-window-window-sizes key)))
|
||||
(cond
|
||||
((functionp value)
|
||||
(funcall value))
|
||||
((numberp value)
|
||||
value)
|
||||
(t
|
||||
(error "The value of `%s' is neither a number nor a function" key)))))
|
||||
|
||||
(defun prot-window-select-fit-size (window)
|
||||
"Select WINDOW and resize it.
|
||||
The resize pertains to the maximum and minimum values for height
|
||||
and width, per `prot-window-window-sizes'.
|
||||
|
||||
Use this as the `body-function' in a `display-buffer-alist' entry."
|
||||
(select-window window)
|
||||
(fit-window-to-buffer
|
||||
window
|
||||
(prot-window--get-window-size :max-height)
|
||||
(prot-window--get-window-size :min-height)
|
||||
(prot-window--get-window-size :max-width)
|
||||
(prot-window--get-window-size :min-width))
|
||||
;; If we did not use `display-buffer-below-selected', then we must
|
||||
;; be in a lateral window, which has more space. Then we do not
|
||||
;; want to dedicate the window to this buffer, because we will be
|
||||
;; running out of space.
|
||||
(when (or (window-in-direction 'above) (window-in-direction 'below))
|
||||
(set-window-dedicated-p window t)))
|
||||
|
||||
(defun prot-window--get-display-buffer-below-or-pop ()
|
||||
"Return list of functions for `prot-window-display-buffer-below-or-pop'."
|
||||
(list
|
||||
#'display-buffer-reuse-mode-window
|
||||
(if (or (prot-common-window-small-p)
|
||||
(prot-common-three-or-more-windows-p))
|
||||
#'display-buffer-below-selected
|
||||
#'display-buffer-pop-up-window)))
|
||||
|
||||
(defun prot-window-display-buffer-below-or-pop (&rest args)
|
||||
"Display buffer below current window or pop a new window.
|
||||
The criterion for choosing to display the buffer below the
|
||||
current one is a non-nil return value for
|
||||
`prot-common-window-small-p'.
|
||||
|
||||
Apply ARGS expected by the underlying `display-buffer' functions.
|
||||
|
||||
This as the action function in a `display-buffer-alist' entry."
|
||||
(let ((functions (prot-window--get-display-buffer-below-or-pop)))
|
||||
(catch 'success
|
||||
(dolist (fn functions)
|
||||
(when (apply fn args)
|
||||
(throw 'success fn))))))
|
||||
|
||||
(defun prot-window-shell-or-term-p (buffer &rest _)
|
||||
"Check if BUFFER is a shell or terminal.
|
||||
This is a predicate function for `buffer-match-p', intended for
|
||||
use in `display-buffer-alist'."
|
||||
(when (string-match-p "\\*.*\\(e?shell\\|v?term\\).*" (buffer-name (get-buffer buffer)))
|
||||
(with-current-buffer buffer
|
||||
;; REVIEW 2022-07-14: Is this robust?
|
||||
(and (not (derived-mode-p 'message-mode 'text-mode))
|
||||
(derived-mode-p 'eshell-mode 'shell-mode 'comint-mode 'fundamental-mode)))))
|
||||
|
||||
(defun prot-window-remove-dedicated (&rest _)
|
||||
"Remove dedicated window parameter.
|
||||
Use this as :after advice to `delete-other-windows' and
|
||||
`delete-window'."
|
||||
(when (one-window-p :no-mini)
|
||||
(set-window-dedicated-p nil nil)))
|
||||
|
||||
(mapc
|
||||
(lambda (fn)
|
||||
(advice-add fn :after #'prot-window-remove-dedicated))
|
||||
'(delete-other-windows delete-window))
|
||||
|
||||
(defmacro prot-window-define-full-frame (name &rest args)
|
||||
"Define command to call ARGS in new frame with `display-buffer-full-frame' bound.
|
||||
Name the function prot-window- followed by NAME. If ARGS is nil,
|
||||
call NAME as a function."
|
||||
(declare (indent 1))
|
||||
`(defun ,(intern (format "prot-window-%s" name)) ()
|
||||
,(format "Call `prot-window-%s' in accordance with `prot-window-define-full-frame'." name)
|
||||
(interactive)
|
||||
(let ((display-buffer-alist '((".*" (display-buffer-full-frame)))))
|
||||
(with-selected-frame (make-frame)
|
||||
,(if args
|
||||
`(progn ,@args)
|
||||
`(funcall ',name))
|
||||
(modify-frame-parameters nil '((buffer-list . nil)))))))
|
||||
|
||||
(defun prot-window--get-shell-buffers ()
|
||||
"Return list of `shell' buffers."
|
||||
(seq-filter
|
||||
(lambda (buffer)
|
||||
(with-current-buffer buffer
|
||||
(derived-mode-p 'shell-mode)))
|
||||
(buffer-list)))
|
||||
|
||||
(defun prot-window--get-new-shell-buffer ()
|
||||
"Return buffer name for `shell' buffers."
|
||||
(if-let* ((buffers (prot-window--get-shell-buffers))
|
||||
(buffers-length (length buffers))
|
||||
((>= buffers-length 1)))
|
||||
(format "*shell*<%s>" (1+ buffers-length))
|
||||
"*shell*"))
|
||||
|
||||
;;;###autoload (autoload 'prot-window-shell "prot-window")
|
||||
(prot-window-define-full-frame shell
|
||||
(let ((name (prot-window--get-new-shell-buffer)))
|
||||
(shell name)
|
||||
(set-frame-name name)
|
||||
(when-let* ((buffer (get-buffer name)))
|
||||
(with-current-buffer buffer
|
||||
(add-hook
|
||||
'delete-frame-functions
|
||||
(lambda (_)
|
||||
;; FIXME 2023-09-09: Works for multiple frames (per
|
||||
;; `make-frame-command'), but not if the buffer is in two
|
||||
;; windows in the same frame.
|
||||
(unless (> (safe-length (get-buffer-window-list buffer nil t)) 1)
|
||||
(let ((kill-buffer-query-functions nil))
|
||||
(kill-buffer buffer))))
|
||||
nil
|
||||
:local)))))
|
||||
|
||||
;;;###autoload (autoload 'prot-window-coach "prot-window")
|
||||
(prot-window-define-full-frame coach
|
||||
(let ((buffer (get-buffer-create "*scratch for coach*")))
|
||||
(with-current-buffer buffer
|
||||
(funcall initial-major-mode))
|
||||
(display-buffer buffer)
|
||||
(set-frame-name "Coach")))
|
||||
|
||||
;; REVIEW 2023-06-25: Does this merit a user option? I don't think I
|
||||
;; will ever set it to the left. It feels awkward there.
|
||||
(defun prot-window-scroll-bar-placement ()
|
||||
"Control the placement of scroll bars."
|
||||
(when scroll-bar-mode
|
||||
(setq default-frame-scroll-bars 'right)
|
||||
(set-scroll-bar-mode 'right)))
|
||||
|
||||
(add-hook 'scroll-bar-mode-hook #'prot-window-scroll-bar-placement)
|
||||
|
||||
(defun prot-window-no-minibuffer-scroll-bar (frame)
|
||||
"Remove the minibuffer scroll bars from FRAME."
|
||||
(set-window-scroll-bars (minibuffer-window frame) nil nil nil nil :persistent))
|
||||
|
||||
(add-hook 'after-make-frame-functions 'prot-window-no-minibuffer-scroll-bar)
|
||||
|
||||
;;;; Run commands in a popup frame (via emacsclient)
|
||||
|
||||
(defun prot-window-delete-popup-frame (&rest _)
|
||||
"Kill selected selected frame if it has parameter `prot-window-popup-frame'.
|
||||
Use this function via a hook."
|
||||
(when (frame-parameter nil 'prot-window-popup-frame)
|
||||
(delete-frame)))
|
||||
|
||||
(defmacro prot-window-define-with-popup-frame (command)
|
||||
"Define function which calls COMMAND in a new frame.
|
||||
Make the new frame have the `prot-window-popup-frame' parameter."
|
||||
`(defun ,(intern (format "prot-window-popup-%s" command)) ()
|
||||
,(format "Run `%s' in a popup frame with `prot-window-popup-frame' parameter.
|
||||
Also see `prot-window-delete-popup-frame'." command)
|
||||
(interactive)
|
||||
(let ((frame (make-frame '((prot-window-popup-frame . t)))))
|
||||
(select-frame frame)
|
||||
(switch-to-buffer " prot-window-hidden-buffer-for-popup-frame")
|
||||
(condition-case nil
|
||||
(call-interactively ',command)
|
||||
((quit error user-error)
|
||||
(delete-frame frame))))))
|
||||
|
||||
(declare-function org-capture "org-capture" (&optional goto keys))
|
||||
(defvar org-capture-after-finalize-hook)
|
||||
|
||||
;;;###autoload (autoload 'prot-window-popup-org-capture "prot-window")
|
||||
(prot-window-define-with-popup-frame org-capture)
|
||||
|
||||
(declare-function tmr "tmr" (time &optional description acknowledgep))
|
||||
(defvar tmr-timer-created-functions)
|
||||
|
||||
;;;###autoload (autoload 'prot-window-popup-tmr "prot-window")
|
||||
(prot-window-define-with-popup-frame tmr)
|
||||
|
||||
(provide 'prot-window)
|
||||
;;; prot-window.el ends here
|
1607
unravel-emacs.org
1607
unravel-emacs.org
File diff suppressed because it is too large
Load diff
98
unravel-modules/unravel-dired.el
Normal file
98
unravel-modules/unravel-dired.el
Normal file
|
@ -0,0 +1,98 @@
|
|||
;;; Dired file manager and prot-dired.el extras
|
||||
(use-package dired
|
||||
:ensure nil
|
||||
:commands (dired)
|
||||
:config
|
||||
(setq dired-recursive-copies 'always)
|
||||
(setq dired-recursive-deletes 'always)
|
||||
(setq delete-by-moving-to-trash t))
|
||||
|
||||
(use-package dired
|
||||
:ensure nil
|
||||
:commands (dired)
|
||||
:config
|
||||
(setq dired-listing-switches
|
||||
"-AGFhlv --group-directories-first --time-style=long-iso"))
|
||||
|
||||
(use-package dired
|
||||
:ensure nil
|
||||
:commands (dired)
|
||||
:config
|
||||
(setq dired-dwim-target t))
|
||||
|
||||
(use-package dired
|
||||
:ensure nil
|
||||
:commands (dired)
|
||||
:config
|
||||
(setq dired-auto-revert-buffer #'dired-directory-changed-p) ; also see `dired-do-revert-buffer'
|
||||
(setq dired-make-directory-clickable t) ; Emacs 29.1
|
||||
(setq dired-free-space nil) ; Emacs 29.1
|
||||
(setq dired-mouse-drag-files t) ; Emacs 29.1
|
||||
|
||||
(add-hook 'dired-mode-hook #'dired-hide-details-mode)
|
||||
(add-hook 'dired-mode-hook #'hl-line-mode)
|
||||
|
||||
;; In Emacs 29 there is a binding for `repeat-mode' which lets you
|
||||
;; repeat C-x C-j just by following it up with j. For me, this is a
|
||||
;; problem as j calls `dired-goto-file', which I often use.
|
||||
(define-key dired-jump-map (kbd "j") nil))
|
||||
|
||||
(use-package dired-aux
|
||||
:ensure nil
|
||||
:after dired
|
||||
:bind
|
||||
( :map dired-mode-map
|
||||
("C-+" . dired-create-empty-file)
|
||||
("M-s f" . nil)
|
||||
("C-<return>" . dired-do-open) ; Emacs 30
|
||||
("C-x v v" . dired-vc-next-action)) ; Emacs 28
|
||||
:config
|
||||
(setq dired-isearch-filenames 'dwim)
|
||||
(setq dired-create-destination-dirs 'ask) ; Emacs 27
|
||||
(setq dired-vc-rename-file t) ; Emacs 27
|
||||
(setq dired-do-revert-buffer (lambda (dir) (not (file-remote-p dir)))) ; Emacs 28
|
||||
(setq dired-create-destination-dirs-on-trailing-dirsep t)) ; Emacs 29
|
||||
|
||||
(use-package dired-x
|
||||
:ensure nil
|
||||
:after dired
|
||||
:bind
|
||||
( :map dired-mode-map
|
||||
("I" . dired-info))
|
||||
:config
|
||||
(setq dired-clean-up-buffers-too t)
|
||||
(setq dired-clean-confirm-killing-deleted-buffers t)
|
||||
(setq dired-x-hands-off-my-keys t) ; easier to show the keys I use
|
||||
(setq dired-bind-man nil)
|
||||
(setq dired-bind-info nil))
|
||||
|
||||
(use-package dired-subtree
|
||||
:ensure t
|
||||
:after dired
|
||||
:bind
|
||||
( :map dired-mode-map
|
||||
("<tab>" . dired-subtree-toggle)
|
||||
("TAB" . dired-subtree-toggle)
|
||||
("<backtab>" . dired-subtree-remove)
|
||||
("S-TAB" . dired-subtree-remove))
|
||||
:config
|
||||
(setq dired-subtree-use-backgrounds nil))
|
||||
|
||||
(use-package wdired
|
||||
:ensure nil
|
||||
:commands (wdired-change-to-wdired-mode)
|
||||
:config
|
||||
(setq wdired-allow-to-change-permissions t)
|
||||
(setq wdired-create-parent-directories t))
|
||||
|
||||
;;; dired-like mode for the trash (trashed.el)
|
||||
(use-package trashed
|
||||
:ensure t
|
||||
:commands (trashed)
|
||||
:config
|
||||
(setq trashed-action-confirmer 'y-or-n-p)
|
||||
(setq trashed-use-header-line t)
|
||||
(setq trashed-sort-key '("Date deleted" . t))
|
||||
(setq trashed-date-format "%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
(provide 'unravel-dired)
|
93
unravel-modules/unravel-search.el
Normal file
93
unravel-modules/unravel-search.el
Normal file
|
@ -0,0 +1,93 @@
|
|||
;;; Isearch, occur, grep
|
||||
(use-package isearch
|
||||
:ensure nil
|
||||
:demand t
|
||||
:config
|
||||
(setq search-whitespace-regexp ".*?" ; one `setq' here to make it obvious they are a bundle
|
||||
isearch-lax-whitespace t
|
||||
isearch-regexp-lax-whitespace nil))
|
||||
|
||||
(use-package isearch
|
||||
:ensure nil
|
||||
:demand t
|
||||
:config
|
||||
(setq search-highlight t)
|
||||
(setq isearch-lazy-highlight t)
|
||||
(setq lazy-highlight-initial-delay 0.5)
|
||||
(setq lazy-highlight-no-delay-length 4))
|
||||
|
||||
(use-package isearch
|
||||
:ensure nil
|
||||
:demand t
|
||||
:config
|
||||
(setq isearch-lazy-count t)
|
||||
(setq lazy-count-prefix-format "(%s/%s) ")
|
||||
(setq lazy-count-suffix-format nil))
|
||||
|
||||
(use-package isearch
|
||||
:ensure nil
|
||||
:demand t
|
||||
:config
|
||||
(setq list-matching-lines-jump-to-current-line nil) ; do not jump to current line in `*occur*' buffers
|
||||
(add-hook 'occur-mode-hook #'hl-line-mode))
|
||||
|
||||
(use-package isearch
|
||||
:ensure nil
|
||||
:demand t
|
||||
:bind
|
||||
( :map minibuffer-local-isearch-map
|
||||
("M-/" . isearch-complete-edit)
|
||||
:map occur-mode-map
|
||||
("t" . toggle-truncate-lines)
|
||||
:map isearch-mode-map
|
||||
("C-g" . isearch-cancel) ; instead of `isearch-abort'
|
||||
("M-/" . isearch-complete)))
|
||||
|
||||
;;; grep and xref
|
||||
(use-package re-builder
|
||||
:ensure nil
|
||||
:commands (re-builder regexp-builder)
|
||||
:config
|
||||
(setq reb-re-syntax 'read))
|
||||
|
||||
(use-package xref
|
||||
:ensure nil
|
||||
:commands (xref-find-definitions xref-go-back)
|
||||
:config
|
||||
;; All those have been changed for Emacs 28
|
||||
(setq xref-show-definitions-function #'xref-show-definitions-completing-read) ; for M-.
|
||||
(setq xref-show-xrefs-function #'xref-show-definitions-buffer) ; for grep and the like
|
||||
(setq xref-file-name-display 'project-relative))
|
||||
|
||||
(use-package grep
|
||||
:ensure nil
|
||||
:commands (grep lgrep rgrep)
|
||||
:config
|
||||
(setq grep-save-buffers nil)
|
||||
(setq grep-use-headings t) ; Emacs 30
|
||||
|
||||
(let ((executable (or (executable-find "rg") "grep"))
|
||||
(rgp (string-match-p "rg" grep-program)))
|
||||
(setq grep-program executable)
|
||||
(setq grep-template
|
||||
(if rgp
|
||||
"/usr/bin/rg -nH --null -e <R> <F>"
|
||||
"/usr/bin/grep <X> <C> -nH --null -e <R> <F>"))
|
||||
(setq xref-search-program (if rgp 'ripgrep 'grep))))
|
||||
|
||||
;;; wgrep (writable grep)
|
||||
;; See the `grep-edit-mode' for the new built-in feature.
|
||||
(unless (>= emacs-major-version 31)
|
||||
(use-package wgrep
|
||||
:ensure t
|
||||
:after grep
|
||||
:bind
|
||||
( :map grep-mode-map
|
||||
("e" . wgrep-change-to-wgrep-mode)
|
||||
("C-x C-q" . wgrep-change-to-wgrep-mode)
|
||||
("C-c C-c" . wgrep-finish-edit))
|
||||
:config
|
||||
(setq wgrep-auto-save-buffer t)
|
||||
(setq wgrep-change-readonly-file t)))
|
||||
|
||||
(provide 'unravel-search)
|
146
unravel-modules/unravel-window.el
Normal file
146
unravel-modules/unravel-window.el
Normal file
|
@ -0,0 +1,146 @@
|
|||
;;; General window and buffer configurations
|
||||
(use-package uniquify
|
||||
:ensure nil
|
||||
:config
|
||||
;;;; `uniquify' (unique names for buffers)
|
||||
(setq uniquify-buffer-name-style 'forward)
|
||||
(setq uniquify-strip-common-suffix t)
|
||||
(setq uniquify-after-kill-buffer-p t))
|
||||
|
||||
;;;; `window', `display-buffer-alist', and related
|
||||
(use-package prot-window
|
||||
:ensure nil
|
||||
:demand t
|
||||
:config
|
||||
;; NOTE 2023-03-17: Remember that I am using development versions of
|
||||
;; Emacs. Some of my `display-buffer-alist' contents are for Emacs
|
||||
;; 29+.
|
||||
(setq display-buffer-alist
|
||||
`(;; no window
|
||||
("\\`\\*Async Shell Command\\*\\'"
|
||||
(display-buffer-no-window))
|
||||
("\\`\\*\\(Warnings\\|Compile-Log\\|Org Links\\)\\*\\'"
|
||||
(display-buffer-no-window)
|
||||
(allow-no-window . t))
|
||||
;; bottom side window
|
||||
("\\*Org \\(Select\\|Note\\)\\*" ; the `org-capture' key selection and `org-add-log-note'
|
||||
(display-buffer-in-side-window)
|
||||
(dedicated . t)
|
||||
(side . bottom)
|
||||
(slot . 0)
|
||||
(window-parameters . ((mode-line-format . none))))
|
||||
;; bottom buffer (NOT side window)
|
||||
((or . ((derived-mode . flymake-diagnostics-buffer-mode)
|
||||
(derived-mode . flymake-project-diagnostics-mode)
|
||||
(derived-mode . messages-buffer-mode)
|
||||
(derived-mode . backtrace-mode)))
|
||||
(display-buffer-reuse-mode-window display-buffer-at-bottom)
|
||||
(window-height . 0.3)
|
||||
(dedicated . t)
|
||||
(preserve-size . (t . t)))
|
||||
("\\*Embark Actions\\*"
|
||||
(display-buffer-reuse-mode-window display-buffer-below-selected)
|
||||
(window-height . fit-window-to-buffer)
|
||||
(window-parameters . ((no-other-window . t)
|
||||
(mode-line-format . none))))
|
||||
("\\*\\(Output\\|Register Preview\\).*"
|
||||
(display-buffer-reuse-mode-window display-buffer-at-bottom))
|
||||
;; below current window
|
||||
("\\(\\*Capture\\*\\|CAPTURE-.*\\)"
|
||||
(display-buffer-reuse-mode-window display-buffer-below-selected))
|
||||
("\\*\\vc-\\(incoming\\|outgoing\\|git : \\).*"
|
||||
(display-buffer-reuse-mode-window display-buffer-below-selected)
|
||||
(window-height . 0.1)
|
||||
(dedicated . t)
|
||||
(preserve-size . (t . t)))
|
||||
((derived-mode . reb-mode) ; M-x re-builder
|
||||
(display-buffer-reuse-mode-window display-buffer-below-selected)
|
||||
(window-height . 4) ; note this is literal lines, not relative
|
||||
(dedicated . t)
|
||||
(preserve-size . (t . t)))
|
||||
((or . ((derived-mode . occur-mode)
|
||||
(derived-mode . grep-mode)
|
||||
(derived-mode . Buffer-menu-mode)
|
||||
(derived-mode . log-view-mode)
|
||||
(derived-mode . help-mode) ; See the hooks for `visual-line-mode'
|
||||
"\\*\\(|Buffer List\\|Occur\\|vc-change-log\\|eldoc.*\\).*"
|
||||
prot-window-shell-or-term-p
|
||||
;; ,world-clock-buffer-name
|
||||
))
|
||||
(prot-window-display-buffer-below-or-pop)
|
||||
(body-function . prot-window-select-fit-size))
|
||||
("\\*\\(Calendar\\|Bookmark Annotation\\|ert\\).*"
|
||||
(display-buffer-reuse-mode-window display-buffer-below-selected)
|
||||
(dedicated . t)
|
||||
(window-height . fit-window-to-buffer))
|
||||
;; same window
|
||||
;; NOTE 2023-02-17: `man' does not fully obey the
|
||||
;; `display-buffer-alist'. It works for new frames and for
|
||||
;; `display-buffer-below-selected', but otherwise is
|
||||
;; unpredictable. See `Man-notify-method'.
|
||||
((or . ((derived-mode . Man-mode)
|
||||
(derived-mode . woman-mode)
|
||||
"\\*\\(Man\\|woman\\).*"))
|
||||
(display-buffer-same-window)))))
|
||||
|
||||
(use-package prot-window
|
||||
:ensure nil
|
||||
:demand t
|
||||
:config
|
||||
(setq window-combination-resize t)
|
||||
(setq even-window-sizes 'height-only)
|
||||
(setq window-sides-vertical nil)
|
||||
(setq switch-to-buffer-in-dedicated-window 'pop)
|
||||
(setq split-height-threshold 80)
|
||||
(setq split-width-threshold 125)
|
||||
(setq window-min-height 3)
|
||||
(setq window-min-width 30))
|
||||
|
||||
;;; Frame-isolated buffers
|
||||
;; Another package of mine. Read the manual:
|
||||
;; <https://protesilaos.com/emacs/beframe>.
|
||||
(use-package beframe
|
||||
:ensure t
|
||||
:hook (after-init . beframe-mode)
|
||||
:bind
|
||||
("C-x f" . other-frame-prefix)
|
||||
("C-c b" . beframe-prefix-map)
|
||||
;; Replace the generic `buffer-menu'. With a prefix argument, this
|
||||
;; commands prompts for a frame. Call the `buffer-menu' via M-x if
|
||||
;; you absolutely need the global list of buffers.
|
||||
("C-x C-b" . beframe-buffer-menu)
|
||||
("C-x B" . select-frame-by-name)
|
||||
:config
|
||||
(setq beframe-functions-in-frames '(project-prompt-project-dir)))
|
||||
|
||||
;;; Frame history (undelete-frame-mode)
|
||||
(use-package frame
|
||||
:ensure nil
|
||||
:bind ("C-x u" . undelete-frame) ; I use only C-/ for `undo'
|
||||
:hook (after-init . undelete-frame-mode))
|
||||
|
||||
;;; Window history (winner-mode)
|
||||
(use-package winner
|
||||
:ensure nil
|
||||
:hook (after-init . winner-mode)
|
||||
:bind
|
||||
(("C-x <right>" . winner-redo)
|
||||
("C-x <left>" . winner-undo)))
|
||||
|
||||
;;; Header line context of symbol/heading (breadcrumb.el)
|
||||
(use-package breadcrumb
|
||||
:ensure t
|
||||
:functions (prot/breadcrumb-local-mode)
|
||||
:hook ((text-mode prog-mode) . prot/breadcrumb-local-mode)
|
||||
:config
|
||||
(setq breadcrumb-project-max-length 0.5)
|
||||
(setq breadcrumb-project-crumb-separator "/")
|
||||
(setq breadcrumb-imenu-max-length 1.0)
|
||||
(setq breadcrumb-imenu-crumb-separator " > ")
|
||||
|
||||
(defun prot/breadcrumb-local-mode ()
|
||||
"Enable `breadcrumb-local-mode' if the buffer is visiting a file."
|
||||
(when buffer-file-name
|
||||
(breadcrumb-local-mode 1))))
|
||||
|
||||
(provide 'unravel-window)
|
Loading…
Reference in a new issue