;;; m-buffer-macro.el --- Create and dispose of markers -*- lexical-binding: t -*-

;;; Header:

;; This file is not part of Emacs

;; The contents of this file are subject to the GPL License, Version 3.0.

;; Copyright (C) 2014-2022  Free Software Foundation, Inc.

;; 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 <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This file provides some utility macros which help to support stateless
;; operation on buffers, by restoring global state after to what it was before
;; the macro starts.

;; These macros are quite useful, but with the exception of
;; `m-buffer-with-markers', they are mostly meant to underpin `m-buffer-at'. The
;; aim is that all the cases where one of these macros is used with a single form
;; from core Emacs should be provided by m-buffer-at (although this is not the
;; case yet). These macros might be more efficient if there are a lot of calls to
;; group together.

;;; Code:

;; ** Markers

;; Markers are generally much nicer than integers, but needs cleaning up
;; afterwards if a lot are created. It's possible to do this using
;; `m-buffer-nil-marker', but it can be a bit painful. This form looks like a
;; `let' form, but removes markers at the end.

;; #+begin_src emacs-lisp
(defmacro m-buffer-with-markers (varlist &rest body)
  "Bind variables after VARLIST then eval BODY.
VARLIST is of the same form as `let'. All variables should
contain markers or collections of markers. All markers are niled
after BODY."
  ;; indent let part specially, and debug like let
  (declare (indent 1)(debug let))
  ;; so, create a rtn var with make-symbol (for hygene)
  (let* ((rtn-var (make-symbol "rtn-var"))
         (marker-vars
          (mapcar #'car varlist))
         (full-varlist
          (append
           varlist
           `((,rtn-var
              (progn
                ,@body))))))
    `(let* ,full-varlist
       (m-buffer-nil-marker
        (list ,@marker-vars))
       ,rtn-var)))
;; #+end_src

;; ** Point and Buffer

;; These macros are extensions of `with-current-buffer', and `save-excursion',
;; which set the current buffer and location.

;; #+begin_src emacs-lisp
(defmacro m-buffer-with-current-marker
    (marker &rest body)
  "At MARKER location run BODY."
  (declare (indent 1) (debug t))
  `(with-current-buffer
       (marker-buffer ,marker)
     (save-excursion
       (goto-char ,marker)
       ,@body)))

(defmacro m-buffer-with-current-position
    (buffer location &rest body)
  "In BUFFER at LOCATION, run BODY."
  (declare (indent 2)
           (debug t))
  `(with-current-buffer
       ,buffer
     (save-excursion
       (goto-char ,location)
      ,@body)))
;; #+end_src

;; Combines the last two!

;; #+begin_src emacs-lisp
(defmacro m-buffer-with-current-location
    (location &rest body)
  "At LOCATION, run BODY.
LOCATION should be a list. If a one element list, it is a marker.
If a two element, it is a buffer and position."
  (declare (indent 1) (debug t))
  ;; multiple eval of location!
  (let ((loc (make-symbol "loc")))
    `(let ((,loc ,location))
       (if (= 1 (length ,loc))
           (m-buffer-with-current-marker
               (nth 0 ,loc)
             ,@body)
         (if (= 2 (length ,loc))
             (m-buffer-with-current-position
                 (nth 0 ,loc)
                 (nth 1 ,loc)
               ,@body)
           (error "m-buffer-with-current-location requires a list of one or two elements"))))))

(provide 'm-buffer-macro)
;;; m-buffer-macro.el ends here
;; #+end_src
