buffer-layers.el 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. ;;; buffer-sets.el --- Configurable sets of buffers
  2. ;; Copyright (C) 2016 Samuel Flint
  3. ;; Author: Samuel W. Flint <swflint@flintfam.org>
  4. ;; Version: 1.7
  5. ;; Package-Requires: ((cl-lib "0.5"))
  6. ;; Keywords: buffer-management
  7. ;; URL: http://github.com/swflint/buffer-layers
  8. ;;; Commentary:
  9. ;;
  10. ;; This is Buffer Layers, a simple, layer-based buffer management system.
  11. ;;
  12. ;; It works by defining buffer layers using a fairly simple macro. The following would be put in a file called `org.layer`
  13. ;;
  14. ;; ```elisp
  15. ;; ;; -*- emacs-lisp -*-
  16. ;; (define-buffer-set org
  17. ;; :files ("~/org/"
  18. ;; "~/org/main.org")
  19. ;; :buffer-to-select "main.org"
  20. ;; :run-on-apply ((my/find-current-notes-file)))
  21. ;; ```
  22. ;;
  23. ;; It can be loaded with `(load-buffer-set "/path/to/org.layer" nil)`. If the final `nil` is changed to `t`, it will load and apply the layer.
  24. ;;
  25. ;; Buffer Layer Definitions take the following arguments:
  26. ;;
  27. ;; - `:files`: A list. This is the list of files that are loaded when the buffer layer is applied.
  28. ;; - `:buffer-to-select`: The buffer to select after files are loaded, and the given forms to run on application are executed.
  29. ;; - `:run-on-apply`: This is a list of forms to be executed in between finding files and selecting the given buffer.
  30. ;; - `:run-on-remove`: This is a list of forms to be executed after killing the buffers that have been loaded.
  31. ;;
  32. ;; To manipulate buffer layers, execute `buffer-set-mode`, and then you can use the following keybindings:
  33. ;;
  34. ;; - `C-x L l`: Load a buffer layer, if defined, otherwise, load from the given file.
  35. ;; - `C-x L u`: Unload a loaded buffer layer.
  36. ;; - `C-x L U`: Unload all loaded buffer layers.
  37. ;; - `C-x L L`: List defined buffer layers, noting if they've been applied.
  38. ;;
  39. ;; The following are the user-facing functions:
  40. ;;
  41. ;; - `define-buffer-set`
  42. ;; - `buffer-set-load-buffer-set`, also known as `load-buffer-set`
  43. ;; - `buffer-sets-unload-buffer-set`
  44. ;; - `buffer-sets-list`
  45. ;; - `buffer-sets-unload-all-buffer-sets`
  46. ;; - `buffer-sets-mode`
  47. ;;
  48. ;; On enabling `buffer-set-mode`, the map is placed onto `C-x L`, and `buffer-sets-unload-all-buffer-sets` is added to the `kill-emacs-hook`, and on disabling the mode, they are removed.
  49. ;;; Code:
  50. (require 'cl-lib)
  51. (defvar *buffer-sets* nil
  52. "List of all defined buffer layers.")
  53. (defvar *buffer-sets-applied* nil
  54. "List of applied buffer-sets.")
  55. (defun buffer-sets-applied-p (layer)
  56. "Returns true if LAYER is applied."
  57. (member layer *buffer-sets-applied*))
  58. (defun buffer-sets--applier-name (name)
  59. "Generate name to apply a buffer layer based on NAME."
  60. (intern (format "apply-buffer-sets-%s" name)))
  61. (defun buffer-sets--remover-name (name)
  62. "Generate name to remove a buffer layer based on NAME."
  63. (intern (format "remove-buffer-sets-%s" name)))
  64. (defun buffer-sets--buffer-list-name (name)
  65. "Generate name to contain buffer layer buffer list based on NAME."
  66. (intern (format "*buffer-sets-%s-buffers*" name)))
  67. (cl-defmacro define-buffer-set (name &key files run-on-apply run-on-remove buffer-to-select)
  68. "Define a buffer layer named NAME, taking FILES, RUN-ON-APPLY, RUN-ON-REMOVE and BUFFER-TO-SELECT as keyword arguments."
  69. (let ((applier (buffer-sets--applier-name name))
  70. (remover (buffer-sets--remover-name name))
  71. (buffers-list (buffer-sets--buffer-list-name name))
  72. (files-list (cons 'list
  73. (when (not (null files))
  74. (mapcar #'(lambda (name)
  75. (format "%s" name)) files)))))
  76. `(progn
  77. (add-to-list '*buffer-sets* ',name)
  78. (defvar ,buffers-list nil)
  79. (defun ,applier ()
  80. ,(format "Apply buffer-set %s." name)
  81. (interactive)
  82. (mapcar #'(lambda (file)
  83. (add-to-list ',buffers-list (find-file file)))
  84. ,files-list)
  85. ,@run-on-apply
  86. (when (not (null ,buffer-to-select))
  87. (switch-to-buffer ,buffer-to-select))
  88. (add-to-list '*buffer-sets-applied* ',name)
  89. (message "Applied Buffer Layer %s" ',name))
  90. (defun ,remover ()
  91. ,(format "Remove buffer-set %s." name)
  92. (interactive)
  93. (mapc #'(lambda (buffer)
  94. (when (buffer-live-p buffer)
  95. (with-current-buffer buffer
  96. (save-buffer)
  97. (kill-buffer))))
  98. ,buffers-list)
  99. ,@run-on-remove
  100. (setq ,buffers-list nil)
  101. (setq *buffer-sets-applied* (delq ',name *buffer-sets-applied*))
  102. (message "Removed Buffer Layer %s" ',name))
  103. (setq current-buffer-applier ',applier)
  104. (list ',applier ',remover))))
  105. (defun buffer-sets-load-buffer-set (name-or-path load-it-p)
  106. "Load a buffer named NAME-OR-PATH, and if a file, apply if LOAD-IT-P is true."
  107. (interactive (list (completing-read "Buffer Layer Name or Path: " (cl-remove-if #'(lambda (layer)
  108. (member layer *buffer-sets-applied*))
  109. *buffer-sets*))
  110. nil))
  111. (if (functionp (buffer-sets--applier-name name-or-path))
  112. (funcall (buffer-sets--applier-name name-or-path))
  113. (load name-or-path)
  114. (when load-it-p
  115. (funcall current-buffer-applier))))
  116. (defalias 'load-buffer-set 'buffer-sets-load-buffer-set)
  117. (defun buffer-sets-unload-buffer-set (name)
  118. "Unload Buffer Layer named NAME."
  119. (interactive (list (completing-read "Buffer Layer Name: " *buffer-sets-applied*)))
  120. (funcall (buffer-sets--remover-name name)))
  121. (defun buffer-sets-list ()
  122. "Produce a list of defined buffer layers."
  123. (interactive)
  124. (when (buffer-live-p "*Buffer Layers*")
  125. (kill-buffer "*Buffer Layers*"))
  126. (with-help-window "*Buffer Layers*"
  127. (with-current-buffer "*Buffer Layers*"
  128. (insert "Defined Buffer Layers:\n\n")
  129. (dolist (layer *buffer-sets*)
  130. (if (not (buffer-sets-applied-p layer))
  131. (insert (format " - %s\n" layer))
  132. (insert (format " - %s (Applied)\n" layer)))
  133. (dolist (buffer (symbol-value (buffer-sets--buffer-list-name layer)))
  134. (if (null (get-buffer-window-list buffer nil t))
  135. (insert (format " - %s\n" (buffer-name buffer)))
  136. (insert (format " - %s (visible)\n" (buffer-name buffer)))))))))
  137. (defun buffer-sets-unload-all-buffer-sets ()
  138. "Unload all loaded buffer layers."
  139. (interactive)
  140. (dolist (buffer-set *buffer-sets-applied*)
  141. (buffer-sets-unload-buffer-set buffer-set)))
  142. (defvar buffer-sets-map (make-keymap)
  143. "Keymap for buffer-set commands.")
  144. (define-key buffer-sets-map (kbd "l") #'buffer-sets-load-buffer-set)
  145. (define-key buffer-sets-map (kbd "L") #'buffer-sets-list)
  146. (define-key buffer-sets-map (kbd "u") #'buffer-sets-unload-buffer-set)
  147. (define-key buffer-sets-map (kbd "U") #'buffer-sets-unload-all-buffer-sets)
  148. (define-minor-mode buffer-sets-mode
  149. "A mode for managing configurable sets of buffers."
  150. :lighter " BSS" :global t :variable buffer-sets-mode-p
  151. (if buffer-sets-mode-p
  152. (progn
  153. (define-key ctl-x-map (kbd "S") buffer-sets-map)
  154. (add-hook 'kill-emacs-hook #'buffer-sets-unload-all-buffer-sets))
  155. (progn
  156. (define-key ctl-x-map (kbd "S") nil)
  157. (remove-hook 'kill-emacs-hook #'buffer-sets-unload-all-buffer-sets))))
  158. (provide 'buffer-sets)
  159. ;;; buffer-sets.el ends here