diff options
author | Yuchen Pei <hi@ypei.me> | 2023-06-12 19:37:49 +1000 |
---|---|---|
committer | Yuchen Pei <id@ypei.org> | 2023-06-17 16:26:14 +1000 |
commit | a9627518a51f5dc536fa22629a2da680dbc052d1 (patch) | |
tree | aec3610cc0f19c2f9bfc44d80a410bdb66d013f4 /.emacs.d/lisp/my/my-buffer.el |
first commit
Diffstat (limited to '.emacs.d/lisp/my/my-buffer.el')
-rw-r--r-- | .emacs.d/lisp/my/my-buffer.el | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/.emacs.d/lisp/my/my-buffer.el b/.emacs.d/lisp/my/my-buffer.el new file mode 100644 index 0000000..5ff09a7 --- /dev/null +++ b/.emacs.d/lisp/my/my-buffer.el @@ -0,0 +1,448 @@ +;;; my-buffer.el -- Buffers and windows related extensions for emacs core -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation. + +;; Author: Yuchen Pei <id@ypei.org> +;; Protesilaos Stavrou <info@protesilaos.com> +;; Maintainer: Yuchen Pei <id@ypei.org> +;; Package-Requires: ((emacs "28.2")) + +;; This file is part of dotfiles. + +;; dotfiles is free software: you can redistribute it and/or modify it under +;; the terms of the GNU Affero General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; dotfiles 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 Affero General +;; Public License for more details. + +;; You should have received a copy of the GNU Affero General Public +;; License along with dotfiles. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Extensions on buffers and windows. + +;;; Code: + +;; Much of the following is from prot-emacs +(defun my-get-major-mode-for-buffer (buffer) + (buffer-local-value 'major-mode (get-buffer buffer))) + +;;; Copied from mastering emacs +;;; https://www.masteringemacs.org/article/demystifying-emacs-window-manager +(defun my-buffer-make-display-matcher (major-modes) + (lambda (buffer-name action) + (with-current-buffer buffer-name (apply #'derived-mode-p major-modes)))) + +(defun my-get-buffer-modes () + (let ((results)) + (dolist (buffer (buffer-list) results) + (add-to-list 'results (my-get-major-mode-for-buffer buffer))))) + +(defun my-switch-to-buffer-matching-major-mode (mode) + (interactive + (list (intern (completing-read "Major mode: " + (mapcar 'prin1-to-string (my-get-buffer-modes)))))) + (switch-to-buffer + (read-buffer "Switch to buffer: " nil t + (lambda (pair) + (with-current-buffer (cdr pair) + (derived-mode-p mode)))))) + +(defun my--buffer-major-mode-prompt () + "Prompt of `my-buffers-major-mode'." + (let ((major major-mode) + (read-buffer-function nil)) + (read-buffer + (format "Buffer for %s: " major) + nil t + (lambda (pair) ; pair is (name-string . buffer-object) + (with-current-buffer (cdr pair) (derived-mode-p major)))))) + +;;;###autoload +(defun my-buffers-major-mode (&optional arg) + "Select BUFFER matching the current one's major mode. + +With a prefix-arg, prompt for major mode." + (interactive "P") + (if arg + (call-interactively 'my-switch-to-buffer-matching-major-mode) + (switch-to-buffer (my--buffer-major-mode-prompt)))) + +(defun my-buffer-quick-major-mode (mode) + "Switch to the first buffer of a given mode." + (let ((buffers (buffer-list))) + (while (and buffers + (with-current-buffer (car buffers) + (not (derived-mode-p mode)))) + (setq buffers (cdr buffers))) + (if buffers + (pop-to-buffer (car buffers)) + (message "No buffers in %S" mode)))) + +(defun my-buffer-switch-or-create-major-mode (mode) + "Switch to or create a buffer with a chosen major mode. + +Prompt for a major mode, then: +With no prefix: switch to the first buffer of the chosen major mode. +With one prefix: prompt for a buffer of the chosen major mode. +With two prefixes: create a buffer of the chosen major mode." + (interactive (list (my-read-major-mode))) + (pcase (prefix-numeric-value current-prefix-arg) + (16 (my-buffer-create-major-mode mode)) + (4 (my-switch-to-buffer-matching-major-mode (print mode))) + (_ (my-buffer-quick-major-mode mode)))) + +(defvar my-buffer-create-functions nil + "List indicating ways to create new buffer for a function, each + element in the form of (major-mode-name + . buffer-create-function). without specifying, the default + buffer-create-function is `my-buffer-create-scratch'.") + +(defun my-read-major-mode () + (intern + (completing-read + "Major mode: " + (cl-loop for sym symbols of obarray + when (and (functionp sym) + ;; we would like to include all modes + (provided-mode-derived-p + sym + 'text-mode 'prog-mode + 'comint-mode 'special-mode)) + collect sym)))) + +(defun my-buffer-create-major-mode (mode) + (if (alist-get mode my-buffer-create-functions) + (call-interactively (alist-get mode my-buffer-create-functions)) + (my-buffer-scratch-setup "" mode))) + +(defun my-buffer-create-same-mode (&optional arg) + (interactive "P") + (let ((mode (if arg + (my-read-major-mode) + major-mode))) + (my-buffer-create-major-mode mode))) + +(defvar my-buffers-same-mode nil + "Buffers of the same mode for cycling") + +(defun my-buffer-with-same-major-mode-p (other-buffer) + (let ((mode major-mode)) + (with-current-buffer other-buffer + (derived-mode-p mode)))) + +(defun my-buffer-cycle-same-mode () + (interactive) + (unless (and (eq last-command 'my-buffer-create-or-cycle-same-mode) + (= 1 (prefix-numeric-value last-prefix-arg))) + (setq my-buffers-same-mode + (seq-filter 'my-buffer-with-same-major-mode-p (buffer-list)))) + (setq my-buffers-same-mode + (my-list-cycle my-buffers-same-mode)) + (switch-to-buffer (car my-buffers-same-mode))) + +(defun my-buffer-create-or-cycle-same-mode (&optional arg) + "Create or switch to a buffer of the same major mode + +No prefix: cycle +One prefix: switch to buffer with prompt by calling `my-buffers-major-mode' +Two prefixes: create a buffer by calling `my-buffer-create-same-mode' +" + (interactive "P") + (pcase (prefix-numeric-value arg) + (16 (my-buffer-create-same-mode)) + (4 (my-buffers-major-mode)) + (_ (my-buffer-cycle-same-mode)))) + +(defun my-copy-buffer-file-name () + (interactive) + (when buffer-file-name) + (kill-new (abbreviate-file-name buffer-file-name)) + (message "Copied %s" (abbreviate-file-name buffer-file-name))) + +;;;###autoload +(defun my-kill-buffer (&optional arg) + "Kill current buffer. +With optional prefix ARG (\\[universal-argument]) choose which +buffer to kill." + (interactive "P") + (let ((kill-buffer-query-functions nil)) + (if arg + (call-interactively 'kill-buffer) + (kill-buffer)))) + +;;;###autoload +(defun my-rename-file-and-buffer (name) + "Apply NAME to current file and rename its buffer. +Do not try to make a new directory or anything fancy." + (interactive + (list (read-file-name "Rename current file: " (buffer-file-name)))) + (let ((file (buffer-file-name))) + (if (vc-registered file) + (vc-rename-file file name) + (rename-file file name)) + (set-visited-file-name name t t))) + +(defun my--buffer-vc-root-prompt () + "Prompt of `my-buffers-vc-root'." + (let ((root (expand-file-name + (or (vc-root-dir) + (locate-dominating-file "." ".git")))) + (read-buffer-function nil)) + (read-buffer + (format "Buffers in %s: " root) + nil t + (lambda (pair) ; pair is (name-string . buffer-object) + (with-current-buffer (cdr pair) + (string-match-p root default-directory)))))) + +;;; from prot-emacs +;;;###autoload +(defun my-buffers-vc-root () + "Select buffer matching the current one's VC root." + (interactive) + (switch-to-buffer (my--buffer-vc-root-prompt))) + +(defun my-bookmark-save-no-prompt (&rest _) + "Run `bookmark-save' without prompts. + +The intent of this function is to be added as an :after advice to +`bookmark-set-internal'. Concretely, this means that when +`bookmark-set-internal' is called, this function is called right +afterwards. We set this up because there is no hook after +setting a bookmark and we want to automatically save bookmarks at +that point." + (funcall 'bookmark-save)) + +(defun my-cycle-windows () + "Cycle all windows." + (interactive) + (let* ((windows (window-list nil 0)) + (first-window (pop windows)) + (buffer (window-buffer first-window)) + (temp-buffer) + (window)) + (when windows (select-window (car windows))) + (dolist (window windows) + (setq temp-buffer (window-buffer window)) + (set-window-buffer window buffer) + (setq buffer temp-buffer)) + (set-window-buffer first-window buffer))) + +(defun my-focus-write () + "Make the current window the only one centered with width 80." + (interactive) + (delete-other-windows) + (let ((margin (/ (- (window-width) 80) 2))) + (set-window-margins nil margin margin))) + +(defun my-select-new-window-matching-mode (mode) + "Select a new window." + (setq available-windows + (delete (selected-window) (window-list))) + (setq new-window + (or (cl-find-if (lambda (window) + (equal (my-get-major-mode-for-buffer + (window-buffer window)) + mode)) + available-windows) + (car available-windows) + (split-window-sensibly) + (split-window-right))) + (select-window new-window)) + +(defun my-toggle-lock-current-window-to-buffer () + (interactive) + (my-toggle-lock-window-to-buffer (selected-window))) + +(defun my-toggle-lock-window-to-buffer (window) + (if (window-dedicated-p window) + (progn (set-window-dedicated-p window nil) + (message "Window unlocked.")) + (set-window-dedicated-p window t) + (message "Window locked."))) + +;; https://lists.gnu.org/archive/html/help-gnu-emacs/2010-01/msg00058.html +(defun my-increase-default-face-height (&optional steps) + "Increase the height of the default face by STEPS steps. + Each step multiplies the height by 1.2; a negative number of steps + decreases the height by the same amount." + (interactive + (list + (cond ((eq current-prefix-arg '-) -1) + ((numberp current-prefix-arg) current-prefix-arg) + ((consp current-prefix-arg) -1) + (t 1)))) + (let ((frame (selected-frame))) + (set-face-attribute 'default frame + :height (floor + (* (face-attribute 'default :height frame) + (expt 1.05 steps)))))) + +(defun my-decrease-default-face-height (&optional steps) + "Decrease the height of the default face by STEPS steps. + Each step divides the height by 1.2; a negative number of steps + increases the height by the same amount." + (interactive + (list + (cond ((eq current-prefix-arg '-) -1) + ((numberp current-prefix-arg) current-prefix-arg) + ((consp current-prefix-arg) -1) + (t 1)))) + (my-increase-default-face-height (- steps))) + +;; if file link points to the same file, do not open in other window +(defun my-find-file-maybe-other-window (filename) + (if (equal buffer-file-name (expand-file-name filename)) + (find-file filename) + (find-file-other-window filename))) + +(defun my-buffer-empty-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)))) + +;;;; Scratch buffers +;; The idea is based on the `scratch.el' package by Ian Eure: +;; <https://github.com/ieure/scratch-el>. + +(defun my-buffer-scratch-list-modes () + "List known major modes." + (cl-loop for sym symbols of obarray + when (and (functionp sym) + (or (provided-mode-derived-p sym 'text-mode) + (provided-mode-derived-p sym 'prog-mode))) + collect sym)) + +(defun my-buffer-scratch-setup (region &optional mode) + "Add contents to `scratch' buffer and name it accordingly. + +REGION is added to the contents to the new buffer. + +Use the current buffer's major mode by default. With optional +MODE use that major mode instead." + (unless (provided-mode-derived-p mode 'text-mode 'prog-mode) + (error "Cannot create a scratch with %s which is not derived from +text- or prog-mode." mode)) + (let* ((major (or mode major-mode)) + (string (format "Scratch buffer for: %s\n\n" major)) + (text (concat string region)) + (buf (format "*%s scratch*" major))) + (with-current-buffer (pop-to-buffer buf) + (funcall major) + (if (my-buffer-empty-p) + ;; We could use `save-restriction' for narrowed buffers, but + ;; it is overkill. + (progn + (insert text) + (goto-char (point-min)) + (comment-region (line-beginning-position) (line-end-position)) + (goto-char (point-max))) + (goto-char (point-max)) + (when (my-line-regexp-p 'non-empty) + (insert "\n\n")) + (insert region))))) + +;;;###autoload +(defun my-buffer-create-scratch (&optional arg) + "Produce a scratch buffer matching the current major mode. + +With optional ARG as a prefix argument (\\[universal-argument]), +use `my-scratch-buffer-default-mode'. + +With ARG as a double prefix argument, prompt for a major mode +with completion. Candidates are derivatives of `text-mode' or +`prog-mode'. + +If region is active, copy its contents to the new scratch +buffer. + +Buffers are named as *MAJOR-MODE scratch*. If one already exists +for the given MAJOR-MODE, any text is appended to it." + (interactive "P") + (let* ((default-mode my-scratch-buffer-default-mode) + (modes (my-buffer-scratch-list-modes)) + (region (with-current-buffer (current-buffer) + (if (region-active-p) + (buffer-substring-no-properties + (region-beginning) + (region-end)) + ""))) + mode) + (pcase (prefix-numeric-value arg) + (16 (progn + (setq mode (intern (completing-read "Select major mode: " modes nil t))) + (my-buffer-scratch-setup region mode))) + (4 (my-buffer-scratch-setup region default-mode)) + (_ (my-buffer-scratch-setup region))))) + +(defcustom my-scratch-buffer-default-mode 'org-mode + "Default major mode for `my-buffer-create-scratch'." + :type 'symbol + :group 'my) + +(defun my-base-buffer (&optional buffer) + "Get the base buffer of BUFFER." + (setq buffer (or buffer (current-buffer))) + (unless (bufferp buffer) (error "Not a buffer.")) + (or (buffer-base-buffer buffer) buffer)) + +(defun my-buffer-with-same-base-p (other-buffer &optional buffer) + "Test that buffer has the same base buffer as the current buffer." + (equal (my-base-buffer other-buffer) + (my-base-buffer buffer))) + +(defun my-switch-indirect-buffer () + (interactive) + (let* ((current (current-buffer)) + (buffer + (read-buffer "Switch to indirect buffer: " nil t + (lambda (buffer) + (and + (my-buffer-with-same-base-p + (cdr buffer) current) + (not (equal (cdr buffer) current))))))) + (switch-to-buffer buffer))) + +(defun my-list-cycle (xs) + "Cycle a list." + (cdr (append xs (list (car xs))))) + +(defvar my-indirect-buffer-list nil) + +(defun my-cycle-indirect-buffer () + (interactive) + (unless (and (eq last-command 'my-create-or-switch-indirect-buffers) + (= 1 (prefix-numeric-value last-prefix-arg))) + (setq my-indirect-buffer-list + (seq-filter 'my-buffer-with-same-base-p (buffer-list)))) + (setq my-indirect-buffer-list + (my-list-cycle my-indirect-buffer-list)) + (switch-to-buffer (car my-indirect-buffer-list))) + +(defun my-create-or-switch-indirect-buffers (arg) + "Create or switch to an indirect buffer of the current buffer. + +With no prefix, cycle through indirect buffers. + +With optional ARG as a prefix argument (\\[universal-argument]), +prompt for indirect buffer to choose from. + +With double prefix arguments, create a new indirect buffer." + (interactive "P") + (pcase (prefix-numeric-value arg) + (16 (clone-indirect-buffer nil t)) + (4 (my-switch-indirect-buffer)) + (_ (my-cycle-indirect-buffer)))) + +(provide 'my-buffer) +;;; my-buffer.el ends here |