m-buffer-el-0.15/0000755000175000017500000000000013072003611013363 5ustar dogslegdogslegm-buffer-el-0.15/dev/0000755000175000017500000000000013072003611014141 5ustar dogslegdogslegm-buffer-el-0.15/dev/assess-discover.el0000644000175000017500000000511313072003611017600 0ustar dogslegdogsleg;;; assess-discover.el --- Test support functions -*- lexical-binding: t -*- ;;; Header: ;; This file is not part of Emacs ;; Author: Phillip Lord ;; Maintainer: Phillip Lord ;; The contents of this file are subject to the GPL License, Version 3.0. ;; Copyright (C) 2015, 2016, Phillip Lord ;; 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 . ;;; Code: (defun assess-discover-tests (directory) "Discover tests in directory. Tests must conform to one (and only one!) of several naming schemes. - End with -test.el - End with -tests.el - Start with test- - Any .el file in a directory called test - Any .el file in a directory called tests Each of these is tried until one matches. So, a top-level file called \"blah-test.el\" will prevent discovery of files in a tests directory." (or ;; files with (directory-files directory nil ".*-test.el$") (directory-files directory nil ".*-tests.el$") (directory-files directory nil "test-.*.el$") (let ((dir-test (concat directory "test/"))) (when (file-exists-p dir-test) (mapcar (lambda (file) (concat dir-test file)) (directory-files dir-test nil ".*.el")))) (let ((dir-tests (concat directory "tests/"))) (when (file-exists-p dir-tests) (mapcar (lambda (file) (concat dir-tests file)) (directory-files dir-tests nil ".*.el")))))) (defun assess-discover--load-all-tests (directory) (mapc 'load (assess-discover-tests directory))) (defun assess-discover-load-tests () (interactive) (assess-discover--load-all-tests default-directory)) ;;;###autoload (defun assess-discover-run-batch (&optional selector) (assess-discover--load-all-tests default-directory) (ert-run-tests-batch selector)) ;;;###autoload (defun assess-discover-run-and-exit-batch (&optional selector) (assess-discover--load-all-tests default-directory) (ert-run-tests-batch-and-exit selector)) (provide 'assess-discover) m-buffer-el-0.15/m-buffer-macro.el0000644000175000017500000000777013072003611016522 0ustar dogslegdogsleg;;; 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, Phillip Lord, Newcastle University ;; 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 . ;;; 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 m-buffer-el-0.15/Makefile0000644000175000017500000000247513072003611015033 0ustar dogslegdogslegEMACS ?= emacs CASK ?= cask EMACSES=there-is-no-sensible-default-here -include makefile-local ifdef EMACS EMACS_ENV=EMACS=$(EMACS) endif all: test install: $(EMACS_ENV) $(CASK) install just-test: $(EMACS_ENV) $(CASK) emacs --batch -q \ --directory=. \ --load "dev/assess-discover" \ --funcall assess-discover-run-and-exit-batch test: install just-test package: $(EMACS_ENV) $(CASK) package doc-gen: $(EMACS_ENV) $(CASK) emacs \ --directory=. \ --script dev/doc-gen.el -f doc-gen publish-doc: ../m-buffer-pages/index.html ../m-buffer-pages/m-buffer-doc.css ../m-buffer-pages/m-buffer-doc.css: m-buffer-doc.css cp $< $@ ../m-buffer-pages/index.html: m-buffer-doc.html perl -p -e 's#["]http://orgmode.org/org-info.js#"./org-info.js#' \ $< > $@ m-buffer-doc.html: m-buffer-doc.org m-buffer.el m-buffer-at.el m-buffer-macro.el $(MAKE) doc-gen clean: find . -name "m-buffer*org" -not -name "m-buffer-doc.org" \ -exec rm {} \; - rm m-buffer-doc.html multi-test: make EMACS=$(EMACSES)/master/src/emacs test make EMACS=$(EMACSES)/emacs-25/src/emacs test make EMACS=$(EMACSES)/emacs-25.1/src/emacs test make EMACS=$(EMACSES)/emacs-24.5/src/emacs test make EMACS=$(EMACSES)/emacs-24.4/src/emacs test make EMACS=$(EMACSES)/emacs-24.3/src/emacs test make EMACS=$(EMACSES)/emacs-24.2/src/emacs test .PHONY: test m-buffer-el-0.15/m-buffer-benchmark.els0000644000175000017500000000732013072003611017525 0ustar dogslegdogsleg;;; -*- emacs-lisp -*- ;; ;;; Benchmarking: ;; This file is not meant a emacs package, but for benchmarking m-buffer. ;; To hide Emacs' there are lots of places where m-buffer saves, changes and then ;; restores this global state. One obvious question is what impact does this have ;; on performance. We check this here. ;; ** Evaluation ;; The results of running these forms are "pre-evaluated", because this file ;; forms part of the lentic documentation for m-buffer. We could evaluate ;; these at export time but, by default, this form of evaluation is blocked. ;; Moreover, it can be quite slow which would be less than ideal with ;; lentic-server. ;; To evaluate on the local machine use `org-babel-execute-buffer', probably ;; after setting `org-confirm-babel-evaluate' to nil. ;; ** Support ;; Build a nice simple bench macro. ;; #+begin_src emacs-lisp (defmacro bench (&rest body) `(format "%e" (car (benchmark-run-compiled 1000000 (progn ,@body))))) ;; #+end_src ;; #+RESULTS: ;; : bench ;; ** How Long does it take to change current-buffer ;; *** Entering and Restoring ;; There are lots of places where we set the current buffer, do something, then ;; get the result again, so understanding how long this takes is important. ;; So, how long does it take to set and restore the current buffer. ;; It's quite a bit slower -- about an order of magnitude. ;; **** Implementation ;; We get the `current-buffer' and `point'. In the first case, we also do ;; this inside a `with-current-buffer'. ;; #+begin_src emacs-lisp (bench (with-current-buffer (current-buffer) (point))) ;; #+end_src ;; #+RESULTS: ;; : 3.371566e-02 ;; #+begin_src emacs-lisp (bench (current-buffer) (point)) ;; #+end_src ;; #+RESULTS: ;; : 2.120534e-03 ;; *** Does buffer context help ;; Is `with-current-buffer' quicker if we are already in the current-buffer? ;; This is interesting to know because if it is, grouping several commands ;; that operate on a single would run much faster. ;; We test this by entering having two `with-current-buffer' calls which do ;; nothing, one nested and one not. Our conclusion is, no, it makes not ;; difference, so there is little pointing in putting a grouping construct in, ;; unless we do something intelligent. ;; ;; #+BEGIN_SRC emacs-lisp (bench (with-current-buffer (current-buffer) (with-current-buffer (current-buffer)))) ;; #+END_SRC ;; #+RESULTS: ;; : 1.001554e-01 ;; #+BEGIN_SRC emacs-lisp (bench (with-current-buffer (current-buffer)) (with-current-buffer (current-buffer))) ;; #+END_SRC ;; #+RESULTS: ;; : 1.205835e-01 ;; *** How fast is point ;; m-buffer-at provides stateless functions, but how much overhead does this ;; introduce. We try this with the simplest function I can think of, which is ;; point. The various forms look different here -- because we have a ;; `current-buffer' call in, but not with `point'. But then, effectively, ;; `point' must call `current-buffer' somewhere as part of its implementation, ;; so this difference is fair. ;; We conclude that m-buffer is about 100x slower for calling `point', even ;; when the buffer does not actually need to be changed. So, a lot slower. ;; #+BEGIN_SRC emacs-lisp (bench (point)) ;; #+END_SRC ;; #+RESULTS: ;; : -1.722546e-03 ;; #+BEGIN_SRC emacs-lisp (bench (with-current-buffer (current-buffer) (point))) ;; #+END_SRC ;; #+RESULTS: ;; : 3.669245e-02 ;; #+BEGIN_SRC emacs-lisp (bench (m-buffer-at-point (current-buffer))) ;; #+END_SRC ;; #+RESULTS: ;; : 1.011448e-01 ;; # Local Variables: ;; # lentic-init: lentic-el-org-init ;; # End: m-buffer-el-0.15/Cask0000644000175000017500000000031213072003611014163 0ustar dogslegdogsleg(source gnu) (source melpa) (package-file "m-buffer.el") (files "m-buffer*el" "m-buffer*els" "m-buffer-doc.org" "m-buffer-doc.css") (development (depends-on "htmlize") (depends-on "load-relative")) m-buffer-el-0.15/.dir-locals.el0000644000175000017500000000023513072003611016014 0ustar dogslegdogsleg;;; Directory Local Variables ;;; For more information see (info "(emacs) Directory Variables") ((emacs-lisp-mode (lentic-init . lentic-orgel-org-init))) m-buffer-el-0.15/m-buffer-doc.org0000644000175000017500000000520313072003611016342 0ustar dogslegdogsleg #+TITLE: Manipulate the Contents of Emacs Buffers #+AUTHOR: Phillip Lord #+INFOJS_OPT: view:info toc:nil * Introduction m-buffer provides functions for accessing and manipulating the contents of an Emacs buffer. While Emacs already provides these features, m-buffer provides a higher-level interaction. It achieves this in several ways: many of the functions are list-orientated, so avoiding the need for iteration; it avoids the use of global emacs state whenever it can be avoided, so avoiding side-effects; and it provides a large library of functions supporting common operations. Core usage of buffer m-buffer is simple. For example, the following code returns a list of all matches to the /regexp/ "m-buffer" in the `current-buffer`. #+BEGIN_SRC elisp (m-buffer-match (current-buffer) "m-buffer") #+END_SRC m-buffer is also expanding. Other parts of m-buffer provide stateless interaction with the existing buffer; for example, we can use the following to fetch the point of any buffer: #+BEGIN_SRC elisp (m-buffer-at-point buffer) #+END_SRC These functions can help greatly when writing code which operates on two or more buffers. It is also possible to check whether the status of a location -- either a buffer and position or a marker. For example, these calls are equivalent to `eolp`. #+BEGIN_SRC elisp (m-buffer-at-eolp buffer position) (m-buffer-at-eolp marker) #+END_SRC ** Status `m-buffer' is a work in progress, but much of it is now stable and the interface should change only in forward-compatible ways for 1.0 release. The individual files have statements about their stability. * m-buffer m-buffer.el provides list-orientated search both for any regexp and standard regexps, as well as the ability to do things with these matches: replace, add overlays or text-properties or, most generically of all, call any function on matches. #+include: "m-buffer.org" :minlevel 2 * m-buffer-at m-buffer-at.el provides a set of stateless functions which for accessing data about buffers, without requiring changing the `current-buffer'. #+include: "m-buffer-at.org" :minlevel 2 * m-buffer-macro m-buffer-macro.el provides some general purpose macros for: - dealing with markers and their cleanup - running code at a specific location #+include: "m-buffer-macro.org" :minlevel 2 * m-buffer-benchmark m-buffer-benchmark.el provides no functions, but is a set of benchmarks to give some idea of how much overhead various m-buffer functions entail. #+include: "m-buffer-benchmark.org" :minlevel 2 * Roadmap ** 0.11 Full lentic documentation using lentic-server ** 0.12 Completion of m-buffer-at with all the core buffer functions. m-buffer-el-0.15/README.md0000644000175000017500000001213713072003611014646 0ustar dogslegdogslegm-buffer.el =========== [![Build Status](https://travis-ci.org/phillord/m-buffer-el.png?branch=master)](https://travis-ci.org/phillord/m-buffer-el) ## Introduction This package provides a set of list-orientated functions for operating over the contents of Emacs buffers. Functions are generally purish: i.e. they may change the state of one buffer by side-effect, but should not affect point, current buffer, match data or so forth. Generally, markers are preferred over point locations so that it is possible, for example, to search for regexp matches and then replace them all without the early replacement invalidating the location of the later ones. m-buffer is now documented at http://phillord.github.io/m-buffer-el/ or live in Emacs with [Lentic Server](https://github.com/phillord/lentic-server). ## Status The code is now in active use. APIs are open to change, but I am not intending to. Version 0.14 did not support Emacs-24, which unintentionally broke assess.el which needs to work on these platforms. Emacs-24 is now supported again. ## Contributions Contributions are welcome. However, I would like to keep the option of hosting m-buffer.el on ELPA, therefore, contributors should have [Copyright Assignment papers](https://www.gnu.org/prep/maintain/html_node/Copyright-Papers.html) with the FSF. ## Change Log ### 0.15 Support Emacs-24 again ### 0.14 New function added `m-buffer-match-multi` ### 0.13 New function added `m-buffer-at-string` #### Bug Fixes - m-buffer was actually moving point, because the state was saved before changing buffer. - The benchmark documentation file was being compiled and run on installation, when it is supposed to serve as static documentation. ### 0.12 New funtion added: `m-buffer-partition-by-marker` ### 0.11 This release mostly includes considerably improved documentation. There is one change which is half-way between a breaking change and a bug fix. Previously, in the m-buffer-match-* functions "match" arguments could take any keyword argument and these would over-ride any arguments already set. This means that a call such as: (m-buffer-match-page (current-buffer) :regexp "this") would behave the same as: (m-buffer-match (current-buffer) :regexp "this") rather than matching pages. Alternatively, this call: (m-buffer-match-line (current-buffer) :post-match (lambda () t)) never terminates. Both of these now throw an error instead. #### Breaking Changes - m-buffer-match-* functions now error on conflicting arguments ### 0.10 #### Bug Fixes - m-buffer-replace-match no longer moves point ### 0.9 #### Bug Fixes - Now byte-compiles without errors/warning #### Breaking Changes - `m-buffer-point' renamed to `m-buffer-at-point' ### 0.8 - New macros for marker usage. - m-buffer-at added. New stateless functions for information about Emacs buffers. #### Breaking Changes - File organisation has been refactored with some macros moved out of m-buffer.el ### 0.7 - `m-buffer-match-first-line' added. ### 0.6 - All match functions now take a :numeric argument which forces the return of numbers rather than markers. - Two new functions for subtracting one set of matches from another: `m-buffer-match-subtract` and `m-buffer-match-exact-subtract` - `m-buffer-with-markers` is a `let*` like macro which autonils markers after use. - `m-buffer-with-current-location` is like `with-current-buffer` but also takes a location. - `m-buffer-with-current-marker` is like `with-current-buffer` but takes a marker. ### 0.5 - Automated Testing with Cask #### Breaking Changes - m-buffer-replace-match optional arguments now expanded to match replace-match. This means the 3rd argument has changed meaning. ### 0.4 - m-buffer-match-data has become m-buffer-match - Testing is now via Cask #### New Functions - m-buffer-delete-match ### 0.3 - Various functions for colourising/adding faces - Documentation improvements. - m-buffer-nil-markers has been depluralised to m-buffer-nil-marker - m-buffer-replace-match now returns start end markers - m-buffer-clone-markers added. ### 0.2 #### New Functions - Functions for matching block things -- line start and end, sentence end, paragraph separators, words. - `m-buffer-match-string` and `m-buffer-match-substring` for extracting match-strings. #### Name changes - Functions now use singular rather than plural -- so `m-buffer-matches-data` has become `m-buffer-match-data`. - All uses of `beginning` have been changed to `begin` -- it is shorter and matches `end` #### Matchers - Regexp functions are now overloaded and take either a buffer and regexp or match-data (except for `m-buffer-match-data` for which it makes no sense to pass in match-data). This allows easy chaining of methods. - Matchers now also overloaded for windows -- searching in the visible portion of window. `m-buffer-match-data-visible-window` access this feature directly. - Match Options are now keyword rather than positional which considerably simplifies the implementation, especially with an eye to future expansion. #### Build and Test - Reworked tests and build scripts. m-buffer-el-0.15/.gitignore0000644000175000017500000000011613072003611015351 0ustar dogslegdogsleg/.cask/ m-buffer*org !m-buffer-doc.org *html *elc /dist/ /org/ m-buffer-pkg.elm-buffer-el-0.15/m-buffer-at.el0000644000175000017500000000476013072003611016021 0ustar dogslegdogsleg;;; m-buffer-at.el --- Stateless point functions -*- 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, Phillip Lord, Newcastle University ;; 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 . ;;; Commentary: ;; Provides stateless equivalents to many core Emacs functions, that provide ;; information about a buffer. Most of these functions take either a buffer as ;; a parameter or a location, which is either a marker (with a non-nil buffer ;; and location) or a buffer and integer. ;; These functions are generally competitive with the originals in terms of ;; speed. ;;; Status: ;; There are lots more to do, but the interface should be stable. ;;; Code: ;; #+begin_src emacs-lisp (require 'm-buffer-macro) (defun m-buffer-at-point (buffer) "Return the location of point in BUFFER. See also `point'." (with-current-buffer buffer (point))) (defun m-buffer-at-eolp (&rest location) "Return t if LOCATION is at the end of a line. See also `eolp'." (m-buffer-with-current-location location (eolp))) (defun m-buffer-at-bolp (&rest location) "Return t if LOCATION is at the begining of a line. See also `bolp'" (m-buffer-with-current-location location (bolp))) (defun m-buffer-at-line-beginning-position (&rest location) "Return the start of the line of LOCATION." (m-buffer-with-current-location location (line-beginning-position))) (defun m-buffer-at-line-end-position (&rest location) "Return the end of the line of LOCATION." (m-buffer-with-current-location location (line-end-position))) (defun m-buffer-at-narrowed-p (buffer) (with-current-buffer buffer (buffer-narrowed-p))) (defun m-buffer-at-string (buffer) (with-current-buffer buffer (buffer-string))) (provide 'm-buffer-at) ;;; m-buffer-at.el ends here ;; #+end_src m-buffer-el-0.15/m-buffer.el0000644000175000017500000010270213072003611015412 0ustar dogslegdogsleg;;; m-buffer.el --- List-Oriented, Functional Buffer Manipulation -*- lexical-binding: t -*- ;;; Header: ;; This file is not part of Emacs ;; Author: Phillip Lord ;; Maintainer: Phillip Lord ;; Version: 0.15 ;; Package-Requires: ((seq "2.14")) ;; The contents of this file are subject to the GPL License, Version 3.0. ;; Copyright (C) 2014, 2015, 2016, 2017 Phillip Lord ;; 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 . ;;; Commentary: ;; This file provides a set of list-oriented functions for operating over the ;; contents of buffers, mostly revolving around regexp searching, and regions. ;; They avoid the use of looping, manipulating global state with `match-data'. ;; Many high-level functions exist for matching sentences, lines and so on. ;; Functions are generally purish: i.e. that is those functions which do ;; change state, by for example replacing text or adding overlays, should only ;; change state in one way; they will not affect point, current buffer, match ;; data or so forth. ;; Likewise to protect against changes in state, markers are used rather than ;; integer positions. This means that it is possible, for example, to search ;; for regexp matches and then replace them all without the earlier ;; replacements invalidating the location of the later ones. Otherwise ;; replacements need to be made in reverse order. This can have implications ;; for performance, so m-buffer also provides functions for making markers nil; ;; there are also macros which help manage markers in `m-buffer-macro'. ;; Where possible, functions share interfaces. So most of the match functions ;; take a list of "match" arguments, either position or as a plist, which avoids ;; using lots of `nil' arguments. Functions operating on matches take a list of ;; `match-data' as returned by the match functions, making it easy to chain ;; matches. ;; This file is documented using lentic.el. Use ;; [[http://github.com/phillord/lentic-server][lentic-server]] to view. ;;; Status: ;; m-buffer.el is now stable and is expected to change only in ;; forward-compatible ways. ;;; Code: ;; #+begin_src emacs-lisp (require 'seq) (require 'm-buffer-macro) ;; #+end_src ;; ** Regexp Matching ;; We first provide a single match function, `m-bufffer-match' which converts ;; between Emacs' stateful matching and a more sequence-oriented interface. ;; This function also defines the "match" arguments which are a standard set of ;; arguments used throughout this package. ;; #+begin_src emacs-lisp (defun m-buffer-match (&rest match) "Return a list of all `match-data' for MATCH. MATCH may be of the forms: BUFFER REGEXP &optional MATCH-OPTIONS WINDOW REGEXP &optional MATCH-OPTIONS MATCH-OPTIONS If BUFFER is given, search this buffer. If WINDOW is given search the visible window. MATCH-OPTIONS is a plist with any of the following keys: :buffer -- the buffer to search :regexp -- the regexp to search with :begin -- the start of the region to search -- default point min :end -- the end of the region to search -- default point max :post-match -- function called after a match -- default nil :widen -- if true, widen buffer first -- default nil :case-fold-search value of `case-fold-search' during search. If :default accept the current buffer-local value :numeric -- if true, return integers not markers If options are expressed in two places, the plist form takes precedence over positional args. So calling with both a first position buffer and a :buffer arg will use the second. Likewise, if a window is given as first arg and :end is given, then the :end value will be used. REGEXP should advance point (i.e. not be zero-width) or the function will loop infinitely. POST-MATCH can be used to avoid this. The buffer is searched forward." (apply 'm-buffer--match-1 (m-buffer--normalize-args match))) ;; #+end_src ;; The match function is actually implemented here in the `m-buffer--match-1' ;; function, with positional arguments. ;; #+begin_src emacs-lisp (defun m-buffer--match-1 (buffer regexp begin end post-match widen cfs numeric) "Return a list of `match-data'. This is an internal function: please prefer `m-buffer-match'. BUFFER -- the buffer. REGEXP -- the regexp. BEGIN -- the start of the region to search END -- the end of the region to search POST-MATCH -- function to run after each match POST-MATCH is useful for zero-width matches which will otherwise cause infinite loop. The buffer is searched forward. POST-MATCH return can also be used to terminate the matching by returning nil. WIDEN -- call widen first. CFS -- Non-nil if searches and matches should ignore case. NUMERIC -- Non-nil if we should return integers not markers." ;; #+end_src ;; We start by saving everything to ensure that we do not pollute the global ;; state. This means match-data, point, narrowing and current buffer! Hopefully ;; this is all the global state that exists and that we are changing. ;; #+begin_src emacs-lisp (with-current-buffer buffer (save-match-data (save-excursion (save-restriction (when widen (widen)) ;; #+end_src ;; This let form is doing a number of things. It sets up a dynamic binding for ;; `case-fold-search' (which works even though we are using lexical binding), ;; ensures a non-nil value for =end-bound= and defines a sentinal value that ;; =post-match-return= can use to end early. ;; #+begin_src emacs-lisp (let ((rtn nil) (post-match-return t) (end-bound (or end (point-max))) ;; over-ride default if necessary (case-fold-search (if (eq :default cfs) case-fold-search cfs))) ;; #+end_src ;; We start at the beginning. There was no particularly good reason for this, and ;; it would have made just as much sense to go backward. ;; #+begin_src emacs-lisp (goto-char (or begin (point-min))) (while (and ;; #+end_src ;; The original purpose for =post-match-return= was for zero-width matches -- ;; these do not advance point beyond their end, so the while loop never ;; terminates. Unfortunately, avoiding this depends on the regexp being called, ;; so we provide the most general solution of all. ;; As well as this, we check the return value of =post-match-return=, so as well ;; as advancing `point' by side-effect, we can also use it to terminate the look ;; at any point that we want; for example, we can terminate after the first match ;; which feels more efficient than searching the whole buffer then taking the ;; first match. ;; #+begin_src emacs-lisp post-match-return ;; we need to check we are less than the end-bound ;; or re-search-forward will break (<= (point) end-bound) (re-search-forward regexp end-bound t)) ;; #+end_src ;; Store the `match-data' in a backward list, run post-match. Finally, reverse ;; and terminate. ;; #+begin_src emacs-lisp (setq rtn (cons (if numeric (m-buffer-marker-to-pos-nil (match-data)) (match-data)) rtn)) (when post-match (setq post-match-return (funcall post-match)))) (reverse rtn))))))) ;; #+end_src ;; This method implements the argument list processing. I find this interface ;; fairly attractive to use since it takes the two "main" arguments -- buffer and ;; regexp -- as positional args optionally, and everything else as keywords. The ;; use of keywords is pretty much essential as have eight arguments most of which ;; are not essential. ;; This is fairly close to the logic provided by `cl-defun' which I wasn't aware ;; of when I wrote this. However `cl-defun' does not allow optional arguments ;; before keyword arguments -- all the optional arguments have to be given if we ;; are to use keywords. ;; #+begin_src emacs-lisp (defun m-buffer--normalize-args (match-with) "Manipulate args into a standard form and return as a list. MATCH-WITH are these args. This is an internal function." (let* ( ;; split up into keyword and non keyword limits (args (seq-take-while (lambda (x) (not (keywordp x))) match-with)) (pargs (seq-drop-while (lambda (x) (not (keywordp x))) match-with)) ;; sort actual actual parameters (first (car args)) ;; buffer may be first (buffer (or (plist-get pargs :buffer) (and (bufferp first) first))) ;; or window may be first (window (or (plist-get pargs :window) (and (windowp first) first))) ;; regexp always comes second (regexp (or (plist-get pargs :regexp) (nth 1 args))) ;; begin depends on other arguments (begin (or (plist-get pargs :begin) (and window (window-start window)))) ;; end depends on other arguments (end (or (plist-get pargs :end) (and window (window-end window)))) ;; pm (post-match (plist-get pargs :post-match)) ;; widen (widen (plist-get pargs :widen)) ;; case-fold-search this needs to overwrite the buffer contents iff ;; set, otherwise be ignored, so we need to distinguish a missing ;; property and a nil one (cfs (if (plist-member pargs :case-fold-search) (plist-get pargs :case-fold-search) :default)) ;; numeric (numeric (plist-get pargs :numeric))) (list buffer regexp begin end post-match widen cfs numeric))) ;; #+end_src ;; Finally, this function provides a link between the match function, and the ;; match manipulation functions. We can either choose to match once against a set ;; of arguments and then apply multiple manipulations on the returned match data. ;; Or just use the match manipulation function directly. ;; The first version of `m-buffer' did not include this but it required lots of ;; nested calls which seem inconvenient. ;; #+begin_example ;; (m-buffer-match-manipulate ;; (m-buffer-match (current-buffer) "hello")) ;; #+end_example ;; I think that convienience is worth the overhead. ;; #+begin_src emacs-lisp (defun m-buffer-ensure-match (&rest match) "Ensure that we have MATCH data. If a single arg, assume it is match data and return. If multiple args, assume they are of the form accepted by `m-buffer-match'." (cond ;; we have match data ((= 1 (length match)) (car match)) ((< 1 (length match)) (apply 'm-buffer-match match)) (t (error "Invalid arguments")))) ;; #+end_src ;; ** Match Data Manipulation Functions ;; These functions manipulate lists of either match-data or match arguments in ;; some way. ;; #+begin_src emacs-lisp (defun m-buffer-buffer-for-match (match-data) "Given some MATCH-DATA return the buffer for that data." (marker-buffer (caar match-data))) (defun m-buffer-match-nth-group (n match-data) "Fetch the Nth group from MATCH-DATA." (seq-map (lambda (m) (let ((drp (seq-drop m (* 2 n)))) (list (car drp) (cadr drp)))) match-data)) (defun m-buffer-match-begin-n (n &rest match) "Return markers to the start of the Nth group in MATCH. MATCH may be of any form accepted by `m-buffer-ensure-match'. Use `m-buffer-nil-marker' after the markers have been finished with or they will slow future use of the buffer until garbage collected." (seq-map (lambda (m) (nth (* 2 n) m)) (apply 'm-buffer-ensure-match match))) (defun m-buffer-match-begin-n-pos (n &rest match) "Return positions of the start of the Nth group in MATCH. MATCH may be of any form accepted by `m-buffer-ensure-match'. If `match-data' is passed markers will be set to nil after this function. See `m-buffer-nil-marker' for details." (m-buffer-marker-to-pos-nil (apply 'm-buffer-match-begin-n n match))) (defun m-buffer-match-begin (&rest match) "Return a list of markers to the start of MATCH. MATCH may of any form accepted by `m-buffer-ensure-match'. Use `m-buffer-nil-marker' after the markers have been used or they will slow future changes to the buffer." (apply 'm-buffer-match-begin-n 0 match)) (defun m-buffer-match-begin-pos (&rest match) "Return a list of positions at the start of matcher. MATCH may be of any form accepted by `m-buffer-ensure-match'. If `match-data' is passed markers will be set to nil after this function. See `m-buffer-nil-marker' for details." (apply 'm-buffer-match-begin-n-pos 0 match)) (defun m-buffer-match-end-n (n &rest match) "Return markers to the end of the match to the Nth group. MATCH may be of any form accepted by `m-buffer-ensure-match'. If `match-data' is passed markers will be set to nil after this function. See `m-buffer-nil-marker' for details." (seq-map (lambda (m) (nth (+ 1 (* 2 n)) m)) (apply 'm-buffer-ensure-match match))) (defun m-buffer-match-end-n-pos (n &rest match) "Return positions of the end Nth group of MATCH. MATCH may be of any form accepted by `m-buffer-ensure-match'. If `match-data' is passed markers will be set to nil after this function. See `m-buffer-nil-marker' for details." (m-buffer-marker-to-pos-nil (apply 'm-buffer-match-end-n-pos n match))) (defun m-buffer-match-end (&rest match) "Return a list of markers to the end of MATCH to regexp in buffer. MATCH may be of any form accepted by `m-buffer-ensure-match'. Use `m-buffer-nil-marker' after the markers have been used or they will slow future changes to the buffer." (apply 'm-buffer-match-end-n 0 match)) (defun m-buffer-match-end-pos (&rest match) "Return a list of positions to the end of the match. MATCH may be of any form accepted by `m-buffer-ensure-match'. If `match-data' is passed markers will be set to nil after this function. See `m-buffer-nil-marker' for details." (m-buffer-marker-to-pos-nil (apply 'm-buffer-match-end match))) ;; #+end_src ;; ** Match Utility and Predicates ;; *** Subtraction ;; Some predicates and the ability to subtract to lists of matches from each ;; other. This makes up for limitations in Emacs regexp which can't do "match x ;; but not y". ;; #+begin_src emacs-lisp (defun m-buffer-match-equal (m n) "Return true if M and N are cover the same region. Matches are equal if they match the same region; subgroups are ignored." (and (equal (car m) (car n)) (equal (cadr m) (cadr n)))) ;; #+end_src ;; A nice simple implementation for the general purpose solution. ;; Unfortunately, performance sucks, running in quadratic time. ;; #+begin_src emacs-lisp (defun m-buffer-match-subtract (m n) "Remove from M any match in N. Matches are equivalent if overall they match the same area; subgroups are ignored. See also `m-buffer-match-exact-subtract' which often runs faster but has some restrictions." (seq-remove (lambda (o) (seq-some (lambda (p) (m-buffer-match-equal o p)) n)) m)) ;; #+end_src ;; The ugly and complicated and less general solution. But it runs in linear ;; time. ;; #+begin_src emacs-lisp (defun m-buffer-match-exact-subtract (m n) "Remove from M any match in N. Both M and N must be fully ordered, and any element in N must be in M." (if n ;; n-eaten contains the remaining elements of n that we haven't tested ;; for yet. We throw them away as we go (let ((n-eaten n)) (seq-remove (lambda (o) (cond ;; n-eaten has been eaten. Check here or later "<" comparison crashes. ((not n-eaten) ;; return nil because we always want things in m now. nil ) ;; we have a match so throw away the first element of n-eaten ;; which we won't need again. ((m-buffer-match-equal (car n-eaten) o) (progn (setq n-eaten (seq-drop n-eaten 1)) t)) ;; we should discard also if n-eaten 1 is less than o because, both ;; are sorted, so we will never match ((< ;; first half of the first match in n-eaten (caar n-eaten) ;; first half of match (car o)) (progn (setq n-eaten (seq-drop n-eaten 1)) t)))) m)) m)) (defun m-buffer-in-match-p (matches position) "Returns true is any of MATCHES contain POSITION." (seq-some (lambda (match) (and (<= (car match) position) (<= position (cadr match)))) matches)) ;; #+end_src ;; *** Partition ;; Partition one set of markers by another. This is useful for finding matched ;; pairs of markers. ;; #+begin_src emacs-lisp (defun m-buffer--partition-by-marker(list partition) "Given LIST, split at markers in PARTITION. This is the main implementation for `m-buffer-partition-by-marker', but assumes that partition starts with a very low value (or nil)." (let* ((p-top (car-safe partition)) (p-val (car-safe (cdr-safe partition))) (p-fn (lambda (n) (or (not p-val) (< n p-val))))) (when list (cons (cons p-top (seq-take-while p-fn list)) (m-buffer--partition-by-marker (seq-drop-while p-fn list) (cdr partition)))))) (defun m-buffer-partition-by-marker (list partition) "Given LIST of markers, split at markers in PARTITION. Returns a list of lists. The first element of each list is nil or the marker from PARTITION. The rest of the elements are those elements in LIST which are at the same position or later in the buffer than the element from PARTITION, but before the next element from PARTITION. Both LIST and PARTITION must be sorted." ;; TODO! (m-buffer--partition-by-marker list (cons nil partition))) ;; #+end_src ;; ** Marker manipulation functions ;; These functions do things to markers rather than the areas of the buffers ;; indicated by the markers. This includes transforming between markers and ;; integer positions, and niling markers explicitly, which prevents slow down ;; before garbage collection. ;; #+begin_src emacs-lisp (defun m-buffer-nil-marker (markers) "Takes a (nested) list of MARKERS and nils them all. Markers slow buffer movement while they are pointing at a specific location, until they have been garbage collected. Niling them prevents this. See Info node `(elisp) Overview of Markers'." (seq-map (lambda (marker) (if (seqp marker) (m-buffer-nil-marker marker) (set-marker marker nil))) markers)) (defun m-buffer-marker-to-pos (markers &optional postnil) "Transforms a list of MARKERS to a list of positions. If the markers are no longer needed, set POSTNIL to true, or call `m-buffer-nil-marker' manually after use to speed future buffer movement. Or use `m-buffer-marker-to-pos-nil'." (seq-map (lambda (marker) (prog1 (marker-position marker) (when postnil (set-marker marker nil)))) markers)) (defun m-buffer-marker-to-pos-nil (markers) "Transforms a list of MARKERS to a list of positions then nils. See also `m-buffer-nil-markers'" (m-buffer-marker-to-pos markers t)) (defun m-buffer-marker-tree-to-pos (marker-tree &optional postnil) "Transforms a tree of markers to equivalent positions. MARKER-TREE is the tree. POSTNIL sets markers to nil afterwards." (seq-map (lambda (marker) (if (seqp marker) (m-buffer-marker-tree-to-pos marker postnil) (prog1 (marker-position marker) (when postnil (set-marker marker nil))))) marker-tree)) (defun m-buffer-marker-tree-to-pos-nil (marker-tree) "Transforms a tree of markers to equivalent positions. MARKER-TREE is the tree. Markers are niled afterwards." (m-buffer-marker-tree-to-pos marker-tree t)) (defun m-buffer-marker-clone (marker-tree &optional type) "Return a clone of MARKER-TREE. The optional argument TYPE specifies the insertion type. See `copy-marker' for details." (seq-map (lambda (marker) (if (seqp marker) (m-buffer-marker-clone marker type) (copy-marker marker type))) marker-tree)) (defun m-buffer-pos-to-marker (buffer positions) "In BUFFER translates a list of POSITIONS to markers." (seq-map (lambda (pos) (set-marker (make-marker) pos buffer)) positions)) ;; #+end_src ;; ** Replace, Delete, Extract ;; #+begin_src emacs-lisp (defun m-buffer-replace-match (match-data replacement &optional fixedcase literal subexp) "Given a list of MATCH-DATA, replace with REPLACEMENT. If FIXEDCASE do not alter the case of the replacement text. If LITERAL insert the replacement literally. SUBEXP should be a number indicating the regexp group to replace. Returns markers to the start and end of the replacement. These markers are part of MATCH-DATA, so niling them will percolate backward. See also `replace-match'." (save-excursion (seq-map (lambda (match) (with-current-buffer (marker-buffer (car match)) (save-match-data (set-match-data match) (replace-match replacement fixedcase literal nil (or subexp 0))))) match-data)) ;; we have match-data (m-buffer-match-nth-group (or subexp 0) match-data)) (defun m-buffer-delete-match (match-data &optional subexp) "Delete all MATCH-DATA. SUBEXP should be a number indicating the regexp group to delete. Returns markers to the start and end of the replacement. These markers are part of MATCH_DATA, so niling them will percolate backward." (m-buffer-replace-match match-data "" subexp)) (defun m-buffer-match-string (match-data &optional subexp) "Return strings for MATCH-DATA optionally of group SUBEXP." (seq-map (lambda (match) (with-current-buffer (marker-buffer (car match)) (save-match-data (set-match-data match) (match-string (or subexp 0))))) match-data)) (defun m-buffer-match-string-no-properties (match-data &optional subexp) "Return strings for MATCH-DATA optionally of group SUBEXP. Remove all properties from return." (seq-map 'substring-no-properties (m-buffer-match-string match-data subexp))) ;; #+end_src ;; ** Match Things ;; Emacs comes with a set of in-built regexps most of which we use here. ;; We define `m-buffer-apply-join' first. The reason for this function is that ;; we want to take a list of match arguments and add to with, for instance, a ;; regular expression. We need to add these at the end because most of our ;; functions contain some positional arguments. ;; #+begin_src emacs-lisp (defun m-buffer-apply-join (fn match &rest more-match) (let* ((args (seq-take-while (lambda (x) (not (keywordp x))) match)) (pargs (seq-drop-while (lambda (x) (not (keywordp x))) match)) (more-keywords (seq-map 'car (seq-partition more-match 2)))) (when (seq-find (lambda (keyword) (plist-member pargs keyword)) more-keywords) (error "Match arg contradicts a defined argument.")) (apply fn (append args more-match pargs)))) ;; #+end_src ;; For the following code, we use Emacs core regexps where possible. ;; #+begin_src emacs-lisp (defun m-buffer-match-page (&rest match) "Return a list of match data to all pages in MATCH. MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match match :regexp page-delimiter)) ;; #+end_src ;; The `paragraph-separate' regexp can match an empty region, so we need to start ;; each search at the beginning of the next line. ;; #+begin_src emacs-lisp (defun m-buffer-match-paragraph-separate (&rest match) "Return a list of match data to `paragraph-separate' in MATCH. MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for futher details." (m-buffer-apply-join 'm-buffer-match match :regexp paragraph-separate :post-match 'm-buffer-post-match-forward-line)) (defvar m-buffer--line-regexp "^.*$" "Regexp to match a line.") (defun m-buffer-match-line (&rest match) "Return a list of match data to all lines. MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match for further details." (m-buffer-apply-join 'm-buffer-match match :regexp m-buffer--line-regexp :post-match 'm-buffer-post-match-forward-char)) (defun m-buffer-match-line-start (&rest match) "Return a list of match data to all line start. MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match-begin match :regexp "^" :post-match 'm-buffer-post-match-forward-char)) (defun m-buffer-match-line-end (&rest match) "Return a list of match to line end. MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match-begin match :regexp "$" :post-match 'm-buffer-post-match-forward-char)) ;; #+end_src ;; This is the first use of the =post-match= to terminate the loop, and was ;; actually the motivation for adding it. We automatically terminate after the ;; first match by simply returning nil. ;; #+begin_src emacs-lisp (defun m-buffer-match-first (&rest match) "Return the first match to MATCH. This matches more efficiently than matching all matches and taking the car. See `m-buffer-match' for further details of MATCH." (m-buffer-apply-join #'m-buffer-match match :post-match (lambda () nil))) (defun m-buffer-match-first-line (&rest match) "Return a match to the first line of MATCH. This matches more efficiently than matching all lines and taking the car. See `m-buffer-match' for further details of MATCH." (m-buffer-apply-join 'm-buffer-match-first match :regexp m-buffer--line-regexp)) (defun m-buffer-match-multi (regexps &rest match) "Incrementally find matches to REGEXPS in MATCH. Finds the first match to the first element of regexps, then starting from the end of this match, the first match to the second element of regexps and so forth. See `m-buffer-match' for futher details of MATCH." (when regexps (let ((first-match (m-buffer-apply-join #'m-buffer-match-first match :regexp (car regexps)))) (append first-match (apply #'m-buffer-match-multi (cdr regexps) (plist-put match :begin (car (m-buffer-match-end first-match)))))))) ;; #+end_src ;; Emacs has a rather inconsistent interface here -- suddenly, we have a function ;; rather than a variable for accessing a regexp. ;; #+begin_src emacs-lisp (defun m-buffer-match-sentence-end (&rest match) "Return a list of match to sentence end. MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match-begin match :regexp (sentence-end))) (defun m-buffer-match-word (&rest match) "Return a list of match to all words. MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match match :regexp "\\\w+")) (defun m-buffer-match-empty-line (&rest match) "Return a list of match to all empty lines. MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match match :regexp "^$" :post-match 'm-buffer-post-match-forward-line)) (defun m-buffer-match-non-empty-line (&rest match) "Return a list of match to all non-empty lines. MATCH is fo the form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match match :regexp "^.+$")) (defun m-buffer-match-whitespace-line (&rest match) "Return match data to all lines with only whitespace characters. Note empty lines are not included. MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (m-buffer-apply-join 'm-buffer-match match :regexp "^\\s-+$")) ;; #+end_src ;; I don't think that there is a way to do this with regexps entirely, so we use ;; substraction. ;; #+begin_src emacs-lisp (defun m-buffer-match-non-whitespace-line (&rest match) "Return match data to all lines with at least one non-whitespace character. Note empty lines do not contain any non-whitespace lines. MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for further details." (seq-difference (apply 'm-buffer-match-line match) (apply 'm-buffer-match-whitespace-line match))) ;; Useful post-match functions (defun m-buffer-post-match-forward-line () "Attempt to move forward one line, return true if success." (= 0 (forward-line))) (defun m-buffer-post-match-forward-char () "Attempts to move forward one char. Returns true if succeeds." (condition-case _e (progn (forward-char) t) (error 'end-of-buffer nil))) ;; #+end_src ;; ** Apply Function to Match ;; These functions apply another function to some match-data. This is pretty ;; useful generically, but also I use it for many of the following functions. ;; #+begin_src emacs-lisp (defun m-buffer-on-region (fn match-data) "Apply FN to MATCH-DATA. FN should take two args, the start and stop of each region. MATCH-DATA can be any list of lists with two elements (or more)." (m-buffer-on-region-nth-group fn 0 match-data)) (defun m-buffer-on-region-nth-group (fn n match-data) "Apply FN to the Nth group of MATCH-DATA. FN should take two args, the start and stop of each region. MATCH-DATA can be any list of lists with two elements (or more)." (seq-map (lambda (x) (apply fn x)) (m-buffer-match-nth-group n match-data))) ;; #+end_src ;; ** Overlay and Property Functions ;; Adding properties or overlays to match-data. The functionality here somewhat ;; overlaps with [[https://github.com/ShingoFukuyama/ov.el][ov.el]], which I didn't know about when I wrote this. It generally ;; works over overlays, or regexps, while m-buffer works over match-data. ;; #+begin_src emacs-lisp (defun m-buffer-overlay-match (match-data &optional front-advance rear-advance) "Return an overlay for all match to MATCH-DATA. FRONT-ADVANCE and REAR-ADVANCE controls the borders of the overlay as defined in `make-overlay'. Overlays do not scale that well, so use `m-buffer-propertize-match' if you intend to make and keep many of these. See Info node `(elisp) Overlays' for further information." (let ((buffer (m-buffer-buffer-for-match match-data))) (m-buffer-on-region (lambda (beginning end) (make-overlay beginning end buffer front-advance rear-advance)) match-data))) (defun m-buffer-add-text-property-match (match-data properties) "To MATCH-DATA add PROPERTIES. See `add-text-property' for details of the format of properties. Text properties are associated with the text and move with it. See Info node `(elisp) Text Properties' for further details." (m-buffer-on-region (lambda (beginning end) (add-text-properties beginning end properties)) match-data)) (defun m-buffer-put-text-property-match (match-data property value) "To MATCH-DATA add PROPERTY wth VALUE. See `put-text-property' for details of the format of properties. Text properties are associated with the text and move with it. See Info node `(elisp) Text Properties' for further details." (m-buffer-on-region (lambda (beginning end) (put-text-property beginning end property value)) match-data)) (defun m-buffer-overlay-face-match (match-data face) "To MATCH-DATA add FACE to the face property. This is for use in buffers which do not have function `font-lock-mode' enabled; otherwise use `m-buffer-overlay-font-lock-face-match'." (seq-map (lambda (ovly) (overlay-put ovly 'face face)) (m-buffer-overlay-match match-data))) (defun m-buffer-overlay-font-lock-face-match (match-data face) "To MATCH-DATA add FACE to the face property. This is for use in buffers which have variable `font-lock-mode' enabled; otherwise use `m-buffer-overlay-face-match'." (seq-map (lambda (ovly) (overlay-put ovly 'face face)) (m-buffer-overlay-match match-data))) (defun m-buffer-text-property-face (match-data face) "To MATCH-DATA apply FACE. This is for use in buffers which do not have variable `font-lock-mode' enabled; otherwise use `m-buffer-text-property-font-lock-face'." (m-buffer-put-text-property-match match-data 'face face)) (defun m-buffer-text-property-font-lock-face (match-data face) "To MATCH-DATA apply FACE. This is for use in buffers which have variable `font-lock-mode' enabled; otherwise use `m-buffer-text-property-face'." (m-buffer-put-text-property-match match-data 'font-lock-face face)) (provide 'm-buffer) ;;; m-buffer.el ends here ;; #+end_src m-buffer-el-0.15/test/0000755000175000017500000000000013072003611014342 5ustar dogslegdogslegm-buffer-el-0.15/test/m-buffer-init.el0000644000175000017500000000013513072003611017327 0ustar dogslegdogsleg(defvar m-buffer-init-path (directory-file-name (file-name-directory load-file-name))) m-buffer-el-0.15/test/match-data.txt0000644000175000017500000000003013072003611017077 0ustar dogslegdogslegone two one two one two m-buffer-el-0.15/test/one-two-three.txt0000644000175000017500000000002213072003611017572 0ustar dogslegdogslegone two one three m-buffer-el-0.15/test/m-buffer-test.el0000644000175000017500000002573413072003611017357 0ustar dogslegdogsleg;;; m-buffer-test.el --- Tests for m-buffer ;; The contents of this file are subject to the GPL License, Version 3.0. ;; ;; Copyright (C) 2014, Phillip Lord, Newcastle University ;; ;; 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 . ;;; Code: (require 'm-buffer) (defvar m-buffer-test-path (directory-file-name (file-name-directory (or load-file-name (buffer-file-name (get-buffer "m-buffer-test.el")))))) (defmacro m-buffer-wtb-of-file (file &rest body) "Run BODY in a temp buffer with the contents of FILE inserted." `(with-temp-buffer (insert-file-contents (concat m-buffer-test-path "/" ,file)) ,@body)) (ert-deftest m-with-temp-buffer-of-file () "Test my test macro." (should (equal "one\ntwo\nthree\n" (m-buffer-wtb-of-file "with-temp-buffer.txt" (buffer-string))))) (ert-deftest m-buffer-loaded () "Has m-buffer loaded at all?" (should (fboundp 'm-buffer-match))) (ert-deftest normalize-args () "Normalize Region" ;; just buffer and regexp (should (equal (list (current-buffer) "regexp" nil nil nil nil :default nil) (m-buffer--normalize-args (list (current-buffer) "regexp")))) (should (equal (list (current-buffer) "regexp" nil nil nil nil :default nil) (m-buffer--normalize-args (list (current-buffer) :regexp "regexp")))) (should (equal (list (current-buffer) "regexp" 1 2 3 4 :default nil) (m-buffer--normalize-args (list (current-buffer) "regexp" :begin 1 :end 2 :post-match 3 :widen 4)))) (should (equal (list (current-buffer) "regexp" 1 2 3 4 5 nil) (m-buffer--normalize-args (list (current-buffer) "regexp" :begin 1 :end 2 :post-match 3 :widen 4 :case-fold-search 5)))) (should (equal (list (current-buffer) "regexp" 1 2 3 4 5 6) (m-buffer--normalize-args (list (current-buffer) "regexp" :begin 1 :end 2 :post-match 3 :widen 4 :case-fold-search 5 :numeric 6))))) (defun m-buffer--flatten (l) (if (listp l) (apply 'append (seq-map 'm-buffer--flatten l)) (list l))) (ert-deftest m-buffer-matches () (should (= 3 (length (m-buffer-wtb-of-file "match-data.txt" (m-buffer-match (current-buffer) "^one$"))))) (should (seq-every-p 'markerp (m-buffer--flatten (m-buffer-wtb-of-file "match-data.txt" (m-buffer-match (current-buffer) "^one$")))))) (ert-deftest m-buffer-match-begin () (should (seq-every-p 'markerp (m-buffer-wtb-of-file "match-data.txt" (m-buffer-match-begin (current-buffer) "^one$"))))) (ert-deftest marker-to-pos () (should (equal '(1 1 1) (m-buffer-marker-to-pos-nil (list (copy-marker 1) (copy-marker 1) (copy-marker 1)))))) (ert-deftest m-buffer-match-begin-pos () (should (equal '(1 9 17) (m-buffer-wtb-of-file "match-data.txt" (m-buffer-match-begin-pos (current-buffer) "^one$"))))) (ert-deftest m-buffer-nil-marker () (should (m-buffer-wtb-of-file "match-data.txt" (seq-every-p (lambda (marker) (and (marker-position marker) (marker-buffer marker))) (m-buffer-match-begin (current-buffer) "^one$")))) (should (m-buffer-wtb-of-file "match-data.txt" (seq-every-p (lambda (marker) (and (not (marker-position marker)) (not (marker-buffer marker)))) (m-buffer-nil-marker (m-buffer-match-begin (current-buffer) "^one$")))))) (ert-deftest replace-matches () (should (equal '((1 6) (11 16) (21 26)) (m-buffer-wtb-of-file "match-data.txt" (m-buffer-marker-tree-to-pos (m-buffer-replace-match (m-buffer-match (current-buffer) "^one$") "three"))))) (should (equal "three\ntwo\nthree\ntwo\nthree\ntwo\n" (m-buffer-wtb-of-file "match-data.txt" (m-buffer-replace-match (m-buffer-match (current-buffer) "^one$") "three") (buffer-string))))) (ert-deftest page-matches () (should (not (m-buffer-wtb-of-file "match-data.txt" (m-buffer-match-page (current-buffer)))))) (ert-deftest paragraph-separate () (should (m-buffer-match-paragraph-separate (current-buffer)))) (ert-deftest line-start () (should (equal '(1 2 3 5 7 10 13) (m-buffer-wtb-of-file "line-start.txt" (m-buffer-marker-to-pos (m-buffer-match-line-start (current-buffer))))))) (ert-deftest line-end () (should (equal '(1 2 4 6 9 12 13) (m-buffer-wtb-of-file "line-start.txt" (m-buffer-marker-to-pos (m-buffer-match-line-end (current-buffer))))))) (ert-deftest first-line () (should (equal '((1 1)) (m-buffer-wtb-of-file "line-start.txt" (m-buffer-marker-tree-to-pos (m-buffer-match-first-line (current-buffer))))))) (ert-deftest multi-match () (should (equal '((1 4) (5 8) (13 18)) (m-buffer-wtb-of-file "one-two-three.txt" (m-buffer-marker-tree-to-pos (m-buffer-match-multi '("one" "two" "three") :buffer (current-buffer)))))) ) (ert-deftest sentence-end () (should (equal '(15 32 48) (m-buffer-wtb-of-file "sentence-end.txt" (m-buffer-marker-to-pos (m-buffer-match-sentence-end (current-buffer))))))) (ert-deftest buffer-for-match () (should (with-temp-buffer (progn (insert "a") (equal (current-buffer) (m-buffer-buffer-for-match (m-buffer-match (current-buffer) "a"))))))) (ert-deftest match-n () (should (equal '((1 7 1 4 4 7) (8 14 8 11 11 14) (15 21 15 18 18 21) (22 28 22 25 25 28)) (m-buffer-wtb-of-file "nth.txt" (m-buffer-marker-tree-to-pos (m-buffer-match (current-buffer) "\\(one\\)\\(two\\)"))))) (should (equal '((1 7)(8 14)(15 21)(22 28)) (m-buffer-wtb-of-file "nth.txt" (m-buffer-marker-tree-to-pos (m-buffer-match-nth-group 0 (m-buffer-match (current-buffer) "\\(one\\)\\(two\\)")))))) (should (equal '((1 4) (8 11) (15 18) (22 25)) (m-buffer-wtb-of-file "nth.txt" (m-buffer-marker-tree-to-pos (m-buffer-match-nth-group 1 (m-buffer-match (current-buffer) "\\(one\\)\\(two\\)"))))))) (ert-deftest apply-functions () (should (equal '("onetwo" "onetwo" "onetwo" "onetwo" "") (m-buffer-wtb-of-file "nth.txt" (m-buffer-on-region (lambda (from to) (buffer-substring-no-properties from to)) (m-buffer-match-line (current-buffer))))))) (ert-deftest case-fold-search () ;; match everything -- technically this is dependent on the buffer-local ;; value of case-fold-search (should (equal '((1 2) (3 4) (5 6) (7 8)) (m-buffer-wtb-of-file "case-match.txt" (m-buffer-marker-tree-to-pos (m-buffer-match (current-buffer) "A"))))) ;; match just upper case (i.e. cfs nil) (should (equal '((1 2)(5 6)) (m-buffer-wtb-of-file "case-match.txt" (m-buffer-marker-tree-to-pos (m-buffer-match (current-buffer) "A" :case-fold-search nil))))) ;; match all again (should (equal '((1 2) (3 4) (5 6) (7 8)) (m-buffer-wtb-of-file "case-match.txt" (m-buffer-marker-tree-to-pos (m-buffer-match (current-buffer) "A" :case-fold-search t)))))) (ert-deftest subtract () (should (equal '((1 6) (17 23) (34 39)) (m-buffer-wtb-of-file "sentence-end.txt" (m-buffer-marker-tree-to-pos (m-buffer-match-subtract (m-buffer-match-word (current-buffer)) (m-buffer-match (current-buffer) "sentence"))))))) (ert-deftest exact-subtract () (should (equal '((1 1)(2 2)(3 3)) (m-buffer-match-exact-subtract '((0 0) (1 1) (2 2) (3 3) (4 4)) '((0 0) (4 4))))) (should (equal '((1 1)(2 2)(3 3)) (m-buffer-match-exact-subtract '((0 0) (1 1) (2 2) (3 3) (4 4)) '((-1 -1) (4 4))))) (should (equal '((1 6) (17 23) (34 39)) (m-buffer-wtb-of-file "sentence-end.txt" (m-buffer-marker-tree-to-pos (m-buffer-match-exact-subtract (m-buffer-match-word (current-buffer)) (m-buffer-match (current-buffer) "sentence"))))))) (ert-deftest exact-subtract-with-nil () (should (equal '((1 1)) (m-buffer-match-exact-subtract '((1 1)) nil)))) (ert-deftest exact-subtract-error-simplified () (should (equal '((2 2)) (m-buffer-match-exact-subtract '((1 1) (2 2)) '((1 1)))))) (ert-deftest exact-subtract-error () "This is a test case for a bug found from linked-buffer." (should (equal '((19 31 19 19)) (m-buffer-match-exact-subtract '((1 18 1 1) (19 31 19 19)) '((1 18)))))) (ert-deftest replace-point-unmoved () "After a replace-match has happened point should not have moved." (should (equal (m-buffer-wtb-of-file "match-data.txt" (point-min)) (m-buffer-wtb-of-file "match-data.txt" (m-buffer-replace-match (m-buffer-match (current-buffer) "two") "one") (point))))) (ert-deftest match-error () "Should error because we try to override existing args." (should-error (m-buffer-match-word (current-buffer) :regexp "notword"))) (ert-deftest partition-by-markers () (should (equal '((nil 1) (2 2 3 4) (5 5 6 7) (8 8 9)) (m-buffer-partition-by-marker '(1 2 3 4 5 6 7 8 9) '(2 5 8) )))) (ert-deftest point-stationionary-with-current () "This test addresses a bug where m-buffer did not correctly protect global state when the buffer being operated on was not current -- in this case, a match could move point. The two clauses are identical, one changing the current buffer and one changing a buffer which is not current." (should (let ((out) (out-point)) (with-temp-buffer (insert "one\ntwo\nthree\n") (setq out (current-buffer)) (setq out-point (point)) (m-buffer-match-first-line out) (= (point) out-point)))) (should (let ((out) (out-point)) (with-temp-buffer (insert "one\ntwo\nthree\n") (setq out (current-buffer)) (setq out-point (point)) (with-temp-buffer (m-buffer-match-first-line out)) (= (point) out-point))))) ;;; m-buffer-test.el ends here m-buffer-el-0.15/test/m-buffer-at-test.el0000644000175000017500000000146013072003611017747 0ustar dogslegdogsleg(require 'm-buffer-at) (ert-deftest m-buffer-at-eolp-1() (should (with-temp-buffer (insert "hello") (m-buffer-at-eolp (point-marker))))) (ert-deftest m-buffer-at-eolp-2 () (should (with-temp-buffer (insert "hello") (m-buffer-at-eolp (current-buffer) (point))))) (ert-deftest m-buffer-at-eolp-3 () (should-not (with-temp-buffer (insert "hello") (goto-char (point-min)) (m-buffer-at-eolp (point-marker))))) (ert-deftest m-buffer-at-eolp-4 () (should-not (with-temp-buffer (insert "hello") (goto-char (point-min)) (m-buffer-at-eolp (current-buffer) (point))))) (ert-deftest m-buffer-string () (should (string= "hello" (with-temp-buffer (insert "hello") (m-buffer-at-string (current-buffer)))))) m-buffer-el-0.15/test/test-helper.el0000644000175000017500000000000013072003611017106 0ustar dogslegdogslegm-buffer-el-0.15/test/line-start.txt0000644000175000017500000000001413072003611017160 0ustar dogslegdogsleg 1 1 22 22 m-buffer-el-0.15/test/sentence-end.txt0000644000175000017500000000006113072003611017450 0ustar dogslegdogslegFirst sentence. Second sentence. Third sentence. m-buffer-el-0.15/test/with-temp-buffer.txt0000644000175000017500000000001613072003611020265 0ustar dogslegdogslegone two three m-buffer-el-0.15/test/nth.txt0000644000175000017500000000003413072003611015671 0ustar dogslegdogslegonetwo onetwo onetwo onetwo m-buffer-el-0.15/test/case-match.txt0000644000175000017500000000001013072003611017077 0ustar dogslegdogslegA a A a m-buffer-el-0.15/.ert-runner0000644000175000017500000000006313072003611015464 0ustar dogslegdogsleg--load m-buffer-macro.el m-buffer.el m-buffer-at.elm-buffer-el-0.15/.travis.yml0000644000175000017500000000064213072003611015476 0ustar dogslegdogsleglanguage: generic sudo: no env: - EVM_EMACS=emacs-24.1-travis - EVM_EMACS=emacs-24.2-travis - EVM_EMACS=emacs-24.3-travis - EVM_EMACS=emacs-24.4-travis - EVM_EMACS=emacs-24.5-travis - EVM_EMACS=emacs-25.1-travis install: - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > travis.sh && source ./travis.sh - evm install $EVM_EMACS --use --skip - cask script: - emacs --version - make