├── .dir-locals.el ├── .ert-runner ├── .gitignore ├── .travis.yml ├── Cask ├── Makefile ├── README.md ├── dev ├── .nosearch ├── Makefile ├── README.txt ├── doc-gen.el └── fudge-discover.el ├── m-buffer-at.el ├── m-buffer-benchmark.els ├── m-buffer-doc.org ├── m-buffer-macro.el ├── m-buffer.el └── test ├── Makefile ├── case-match.txt ├── line-start.txt ├── m-buffer-at-test.el ├── m-buffer-init.el ├── m-buffer-test.el ├── match-data.txt ├── nth.txt ├── one-two-three.txt ├── sentence-end.txt └── with-temp-buffer.txt /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((emacs-lisp-mode 5 | (lentic-init . lentic-orgel-org-init))) 6 | -------------------------------------------------------------------------------- /.ert-runner: -------------------------------------------------------------------------------- 1 | --load m-buffer-macro.el m-buffer.el m-buffer-at.el -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.cask/ 2 | m-buffer*org 3 | !m-buffer-doc.org 4 | m-buffer-doc.texi 5 | *html 6 | *elc 7 | /dist/ 8 | /org/ 9 | 10 | # ELPA-generated files 11 | /m-buffer-pkg.el 12 | /m-buffer-autoloads.el 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: no 3 | env: 4 | - EVM_EMACS=emacs-24.1-travis 5 | - EVM_EMACS=emacs-24.2-travis 6 | - EVM_EMACS=emacs-24.3-travis 7 | - EVM_EMACS=emacs-24.4-travis 8 | - EVM_EMACS=emacs-24.5-travis 9 | - EVM_EMACS=emacs-25.1-travis 10 | install: 11 | - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > travis.sh && source ./travis.sh 12 | - evm install $EVM_EMACS --use --skip 13 | - cask 14 | script: 15 | - emacs --version 16 | - make -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa-stable) 3 | 4 | (package-file "m-buffer.el") 5 | (files "m-buffer*el" "m-buffer*els" "m-buffer-doc.org" "m-buffer-doc.css") 6 | 7 | (development 8 | (depends-on "htmlize") 9 | (depends-on "lentic") 10 | (depends-on "load-relative")) 11 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EMACS ?= emacs 2 | CASK ?= cask 3 | 4 | EMACSES=there-is-no-sensible-default-here 5 | 6 | -include makefile-local 7 | 8 | ifdef EMACS 9 | EMACS_ENV=EMACS="$(EMACS)" 10 | endif 11 | 12 | 13 | all: test 14 | 15 | install: 16 | $(EMACS_ENV) $(CASK) install 17 | 18 | just-test: 19 | $(EMACS_ENV) $(CASK) $(EMACS) --batch \ 20 | --directory="$(PWD)" \ 21 | --load "dev/fudge-discover" \ 22 | --funcall fudge-discover-run-and-exit-batch 23 | 24 | test: install just-test 25 | 26 | package: 27 | $(EMACS_ENV) $(CASK) package 28 | 29 | doc-gen: 30 | $(EMACS_ENV) $(CASK) $(EMACS) --batch \ 31 | --directory="$(PWD)" \ 32 | -l dev/doc-gen.el -f doc-gen 33 | 34 | publish-doc: ../m-buffer-pages/index.html ../m-buffer-pages/m-buffer-doc.css 35 | 36 | ../m-buffer-pages/m-buffer-doc.css: m-buffer-doc.css 37 | cp $< $@ 38 | 39 | ../m-buffer-pages/index.html: m-buffer-doc.html 40 | perl -p -e 's#["]http://orgmode.org/org-info.js#"./org-info.js#' \ 41 | $< > $@ 42 | 43 | m-buffer-doc.html: m-buffer-doc.org m-buffer.el m-buffer-at.el m-buffer-macro.el 44 | $(MAKE) doc-gen 45 | 46 | clean: 47 | find . -name "m-buffer*org" -not -name "m-buffer-doc.org" \ 48 | -exec rm {} \; 49 | - rm m-buffer-doc.html 50 | 51 | multi-test: 52 | make EMACS=$(EMACSES)/master/src/emacs test 53 | make EMACS=$(EMACSES)/emacs-25/src/emacs test 54 | make EMACS=$(EMACSES)/emacs-25.1/src/emacs test 55 | make EMACS=$(EMACSES)/emacs-24.5/src/emacs test 56 | make EMACS=$(EMACSES)/emacs-24.4/src/emacs test 57 | make EMACS=$(EMACSES)/emacs-24.3/src/emacs test 58 | make EMACS=$(EMACSES)/emacs-24.2/src/emacs test 59 | make EMACS=$(EMACSES)/emacs-24.1/src/emacs test 60 | 61 | .PHONY: test 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | m-buffer.el 2 | =========== 3 | [![Build Status](https://travis-ci.org/phillord/m-buffer-el.png?branch=master)](https://travis-ci.org/phillord/m-buffer-el) 4 | 5 | ## Introduction 6 | 7 | This package provides a set of list-orientated functions for operating over 8 | the contents of Emacs buffers. Functions are generally purish: i.e. they may 9 | change the state of one buffer by side-effect, but should not affect point, 10 | current buffer, match data or so forth. Generally, markers are preferred over 11 | point locations so that it is possible, for example, to search for regexp 12 | matches and then replace them all without the early replacement invalidating 13 | the location of the later ones. 14 | 15 | m-buffer is now documented at http://phillord.github.io/m-buffer-el/ or live 16 | in Emacs with [Lentic Server](https://github.com/phillord/lentic-server). 17 | 18 | 19 | ## Status 20 | 21 | The code is now in active use. APIs are open to change, but I am not intending 22 | to. 23 | 24 | Version 0.14 did not support Emacs-24, which unintentionally broke assess.el 25 | which needs to work on these platforms. Emacs-24 is now supported again. 26 | 27 | ## Contributions 28 | 29 | Contributions are welcome. However, I would like to keep the option of hosting 30 | m-buffer.el on ELPA, therefore, contributors should have 31 | [Copyright Assignment papers](https://www.gnu.org/prep/maintain/html_node/Copyright-Papers.html) 32 | with the FSF. 33 | 34 | 35 | ## Change Log 36 | 37 | ### 0.15 38 | 39 | Support Emacs-24 again 40 | 41 | ### 0.14 42 | 43 | New function added `m-buffer-match-multi` 44 | 45 | ### 0.13 46 | 47 | New function added `m-buffer-at-string` 48 | 49 | #### Bug Fixes 50 | 51 | - m-buffer was actually moving point, because the state was saved before 52 | changing buffer. 53 | - The benchmark documentation file was being compiled and run on installation, 54 | when it is supposed to serve as static documentation. 55 | 56 | ### 0.12 57 | 58 | New funtion added: `m-buffer-partition-by-marker` 59 | 60 | ### 0.11 61 | 62 | This release mostly includes considerably improved documentation. 63 | 64 | There is one change which is half-way between a breaking change and a bug fix. 65 | Previously, in the m-buffer-match-* functions "match" arguments could take any 66 | keyword argument and these would over-ride any arguments already set. This 67 | means that a call such as: 68 | 69 | (m-buffer-match-page (current-buffer) :regexp "this") 70 | 71 | would behave the same as: 72 | 73 | (m-buffer-match (current-buffer) :regexp "this") 74 | 75 | rather than matching pages. Alternatively, this call: 76 | 77 | (m-buffer-match-line 78 | (current-buffer) 79 | :post-match (lambda () t)) 80 | 81 | never terminates. Both of these now throw an error instead. 82 | 83 | #### Breaking Changes 84 | 85 | - m-buffer-match-* functions now error on conflicting arguments 86 | 87 | ### 0.10 88 | 89 | #### Bug Fixes 90 | 91 | - m-buffer-replace-match no longer moves point 92 | 93 | ### 0.9 94 | 95 | #### Bug Fixes 96 | 97 | - Now byte-compiles without errors/warning 98 | 99 | #### Breaking Changes 100 | 101 | - `m-buffer-point' renamed to `m-buffer-at-point' 102 | 103 | ### 0.8 104 | 105 | - New macros for marker usage. 106 | - m-buffer-at added. New stateless functions for information about Emacs buffers. 107 | 108 | #### Breaking Changes 109 | 110 | - File organisation has been refactored with some macros moved out of m-buffer.el 111 | 112 | ### 0.7 113 | - `m-buffer-match-first-line' added. 114 | 115 | ### 0.6 116 | 117 | - All match functions now take a :numeric argument which forces the 118 | return of numbers rather than markers. 119 | - Two new functions for subtracting one set of matches from another: 120 | `m-buffer-match-subtract` and `m-buffer-match-exact-subtract` 121 | - `m-buffer-with-markers` is a `let*` like macro which autonils markers after 122 | use. 123 | - `m-buffer-with-current-location` is like `with-current-buffer` but also 124 | takes a location. 125 | - `m-buffer-with-current-marker` is like `with-current-buffer` but takes a 126 | marker. 127 | 128 | ### 0.5 129 | - Automated Testing with Cask 130 | 131 | #### Breaking Changes 132 | - m-buffer-replace-match optional arguments now expanded to match 133 | replace-match. This means the 3rd argument has changed meaning. 134 | 135 | ### 0.4 136 | 137 | - m-buffer-match-data has become m-buffer-match 138 | - Testing is now via Cask 139 | 140 | 141 | #### New Functions 142 | 143 | - m-buffer-delete-match 144 | 145 | ### 0.3 146 | 147 | - Various functions for colourising/adding faces 148 | - Documentation improvements. 149 | - m-buffer-nil-markers has been depluralised to m-buffer-nil-marker 150 | - m-buffer-replace-match now returns start end markers 151 | - m-buffer-clone-markers added. 152 | 153 | 154 | ### 0.2 155 | 156 | #### New Functions 157 | - Functions for matching block things -- line start and end, sentence end, 158 | paragraph separators, words. 159 | - `m-buffer-match-string` and `m-buffer-match-substring` for extracting 160 | match-strings. 161 | 162 | 163 | #### Name changes 164 | - Functions now use singular rather than plural -- so `m-buffer-matches-data` 165 | has become `m-buffer-match-data`. 166 | - All uses of `beginning` have been changed to `begin` -- it is shorter and 167 | matches `end` 168 | 169 | #### Matchers 170 | - Regexp functions are now overloaded and take either a buffer and regexp or 171 | match-data (except for `m-buffer-match-data` for which it makes no sense to 172 | pass in match-data). This allows easy chaining of methods. 173 | - Matchers now also overloaded for windows -- searching in the visible 174 | portion of window. `m-buffer-match-data-visible-window` access this feature 175 | directly. 176 | - Match Options are now keyword rather than positional which considerably 177 | simplifies the implementation, especially with an eye to future expansion. 178 | 179 | #### Build and Test 180 | - Reworked tests and build scripts. 181 | -------------------------------------------------------------------------------- /dev/.nosearch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phillord/m-buffer-el/5e7714835b2289f61dad24c0b5cf98d28fc313b0/dev/.nosearch -------------------------------------------------------------------------------- /dev/Makefile: -------------------------------------------------------------------------------- 1 | 2 | gen-discover: 3 | ## This obviously depends on assess being available and in the 4 | ## right location 5 | cp ../../assess/assess-discover.el . 6 | perl -i -pe 's/assess-discover/fudge-discover/g' assess-discover.el 7 | mv assess-discover.el fudge-discover.el 8 | -------------------------------------------------------------------------------- /dev/README.txt: -------------------------------------------------------------------------------- 1 | fudge-discover.el is a subset of my http://github.com/phillord/assess 2 | package, with the namespace, erm, fudged to something else. 3 | 4 | If you wish to use my assess package, you do not need to go to such 5 | lengths. I need to do such hackery here because (the rest of) assess 6 | uses m-buffer, and introducing a circular dependency at this point 7 | makes life really complicated. 8 | 9 | https://github.com/phillord/m-buffer-el/pull/6 10 | -------------------------------------------------------------------------------- /dev/doc-gen.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2022-2024 Free Software Foundation, Inc. 4 | 5 | (require 'm-buffer-macro) 6 | (require 'm-buffer) 7 | (require 'm-buffer-at) 8 | 9 | (require 'lentic-doc nil t) 10 | 11 | (defun doc-gen () 12 | (lentic-doc-htmlify-package 'm-buffer)) 13 | -------------------------------------------------------------------------------- /dev/fudge-discover.el: -------------------------------------------------------------------------------- 1 | ;;; fudge-discover.el --- Test support functions -*- lexical-binding: t -*- 2 | 3 | ;;; Header: 4 | 5 | ;; This file is not part of Emacs 6 | 7 | ;; Author: Phillip Lord 8 | ;; Maintainer: Phillip Lord 9 | 10 | ;; The contents of this file are subject to the GPL License, Version 3.0. 11 | 12 | ;; Copyright (C) 2015-2022 Free Software Foundation, Inc. 13 | 14 | ;; This program is free software: you can redistribute it and/or modify 15 | ;; it under the terms of the GNU General Public License as published by 16 | ;; the Free Software Foundation, either version 3 of the License, or 17 | ;; (at your option) any later version. 18 | 19 | ;; This program is distributed in the hope that it will be useful, 20 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | ;; GNU General Public License for more details. 23 | 24 | ;; You should have received a copy of the GNU General Public License 25 | ;; along with this program. If not, see . 26 | 27 | ;;; Code: 28 | 29 | 30 | ;; #+begin_src emacs-lisp 31 | (defun fudge-discover-tests (directory) 32 | "Discover tests in directory. 33 | 34 | Tests must conform to one (and only one!) of several naming 35 | schemes. 36 | 37 | - End with -test.el 38 | - End with -tests.el 39 | - Start with test- 40 | - Any .el file in a directory called test 41 | - Any .el file in a directory called tests 42 | 43 | Each of these is tried until one matches. So, a top-level file 44 | called \"blah-test.el\" will prevent discovery of files in a 45 | tests directory." 46 | (or 47 | ;; files with 48 | (directory-files directory nil ".*-test.el$") 49 | (directory-files directory nil ".*-tests.el$") 50 | (directory-files directory nil "test-.*.el$") 51 | (let ((dir-test 52 | (concat directory "test/"))) 53 | (when (file-exists-p dir-test) 54 | (mapcar 55 | (lambda (file) 56 | (concat dir-test file)) 57 | (directory-files dir-test nil ".*.el")))) 58 | (let ((dir-tests 59 | (concat directory "tests/"))) 60 | (when (file-exists-p dir-tests) 61 | (mapcar 62 | (lambda (file) 63 | (concat dir-tests file)) 64 | (directory-files dir-tests nil ".*.el")))))) 65 | 66 | (defun fudge-discover--load-all-tests (directory) 67 | (mapc 68 | #'load 69 | (fudge-discover-tests directory))) 70 | 71 | (defun fudge-discover-load-tests () 72 | (interactive) 73 | (fudge-discover--load-all-tests default-directory)) 74 | 75 | ;;;###autoload 76 | (defun fudge-discover-run-batch (&optional selector) 77 | (fudge-discover--load-all-tests default-directory) 78 | (ert-run-tests-batch selector)) 79 | 80 | ;;;###autoload 81 | (defun fudge-discover-run-and-exit-batch (&optional selector) 82 | (fudge-discover--load-all-tests default-directory) 83 | (ert-run-tests-batch-and-exit selector)) 84 | 85 | (provide 'fudge-discover) 86 | ;;; fudge-discover.el ends here 87 | ;; #+end_src 88 | -------------------------------------------------------------------------------- /m-buffer-at.el: -------------------------------------------------------------------------------- 1 | ;;; m-buffer-at.el --- Stateless point functions -*- lexical-binding: t -*- 2 | 3 | ;;; Header: 4 | 5 | ;; This file is not part of Emacs 6 | 7 | ;; The contents of this file are subject to the GPL License, Version 3.0. 8 | 9 | ;; Copyright (C) 2014-2022 Free Software Foundation, Inc. 10 | 11 | ;; This program is free software: you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; Provides stateless equivalents to many core Emacs functions, that provide 27 | ;; information about a buffer. Most of these functions take either a buffer as 28 | ;; a parameter or a location, which is either a marker (with a non-nil buffer 29 | ;; and location) or a buffer and integer. 30 | 31 | ;; These functions are generally competitive with the originals in terms of 32 | ;; speed. 33 | 34 | ;;; Status: 35 | 36 | ;; There are lots more to do, but the interface should be stable. 37 | 38 | ;;; Code: 39 | 40 | ;; #+begin_src emacs-lisp 41 | 42 | (require 'm-buffer-macro) 43 | 44 | (defun m-buffer-at-point (buffer) 45 | "Return the location of point in BUFFER. 46 | See also `point'." 47 | (with-current-buffer 48 | buffer 49 | (point))) 50 | 51 | (defun m-buffer-at-eolp (&rest location) 52 | "Return t if LOCATION is at the end of a line. 53 | See also `eolp'." 54 | (m-buffer-with-current-location 55 | location 56 | (eolp))) 57 | 58 | (defun m-buffer-at-bolp (&rest location) 59 | "Return t if LOCATION is at the begining of a line. 60 | See also `bolp'" 61 | (m-buffer-with-current-location 62 | location 63 | (bolp))) 64 | 65 | (defun m-buffer-at-line-beginning-position (&rest location) 66 | "Return the start of the line of LOCATION." 67 | (m-buffer-with-current-location 68 | location 69 | (line-beginning-position))) 70 | 71 | (defun m-buffer-at-line-end-position (&rest location) 72 | "Return the end of the line of LOCATION." 73 | (m-buffer-with-current-location 74 | location 75 | (line-end-position))) 76 | 77 | (defun m-buffer-at-narrowed-p (buffer) 78 | (with-current-buffer 79 | buffer 80 | (buffer-narrowed-p))) 81 | 82 | (defun m-buffer-at-string (buffer) 83 | (with-current-buffer 84 | buffer 85 | (buffer-string))) 86 | 87 | (provide 'm-buffer-at) 88 | ;;; m-buffer-at.el ends here 89 | ;; #+end_src 90 | -------------------------------------------------------------------------------- /m-buffer-benchmark.els: -------------------------------------------------------------------------------- 1 | ;;; -*- mode: emacs-lisp; lexical-binding:t -*- 2 | 3 | ;; 4 | 5 | ;;; Benchmarking: 6 | 7 | ;; This file is not meant a emacs package, but for benchmarking m-buffer. 8 | ;; To hide Emacs' there are lots of places where m-buffer saves, changes and then 9 | ;; restores this global state. One obvious question is what impact does this have 10 | ;; on performance. We check this here. 11 | 12 | ;; ** Evaluation 13 | 14 | ;; The results of running these forms are "pre-evaluated", because this file 15 | ;; forms part of the lentic documentation for m-buffer. We could evaluate 16 | ;; these at export time but, by default, this form of evaluation is blocked. 17 | ;; Moreover, it can be quite slow which would be less than ideal with 18 | ;; lentic-server. 19 | 20 | ;; To evaluate on the local machine use `org-babel-execute-buffer', probably 21 | ;; after setting `org-confirm-babel-evaluate' to nil. 22 | 23 | ;; ** Support 24 | 25 | ;; Build a nice simple bench macro. 26 | 27 | ;; #+begin_src emacs-lisp 28 | (defmacro bench 29 | (&rest body) 30 | `(format "%e" 31 | (car 32 | (benchmark-run-compiled 33 | 1000000 34 | (progn 35 | ,@body))))) 36 | ;; #+end_src 37 | 38 | ;; #+RESULTS: 39 | ;; : bench 40 | 41 | ;; ** How Long does it take to change current-buffer 42 | 43 | ;; *** Entering and Restoring 44 | 45 | ;; There are lots of places where we set the current buffer, do something, then 46 | ;; get the result again, so understanding how long this takes is important. 47 | ;; So, how long does it take to set and restore the current buffer. 48 | 49 | ;; It's quite a bit slower -- about an order of magnitude. 50 | 51 | ;; **** Implementation 52 | 53 | ;; We get the `current-buffer' and `point'. In the first case, we also do 54 | ;; this inside a `with-current-buffer'. 55 | 56 | ;; #+begin_src emacs-lisp 57 | (bench 58 | (with-current-buffer 59 | (current-buffer) 60 | (point))) 61 | ;; #+end_src 62 | 63 | ;; #+RESULTS: 64 | ;; : 3.371566e-02 65 | 66 | 67 | ;; #+begin_src emacs-lisp 68 | (bench 69 | (current-buffer) 70 | (point)) 71 | ;; #+end_src 72 | 73 | ;; #+RESULTS: 74 | ;; : 2.120534e-03 75 | 76 | 77 | ;; *** Does buffer context help 78 | 79 | ;; Is `with-current-buffer' quicker if we are already in the current-buffer? 80 | ;; This is interesting to know because if it is, grouping several commands 81 | ;; that operate on a single would run much faster. 82 | 83 | ;; We test this by entering having two `with-current-buffer' calls which do 84 | ;; nothing, one nested and one not. Our conclusion is, no, it makes not 85 | ;; difference, so there is little pointing in putting a grouping construct in, 86 | ;; unless we do something intelligent. 87 | ;; 88 | 89 | ;; #+BEGIN_SRC emacs-lisp 90 | (bench 91 | (with-current-buffer 92 | (current-buffer) 93 | (with-current-buffer 94 | (current-buffer)))) 95 | ;; #+END_SRC 96 | 97 | ;; #+RESULTS: 98 | ;; : 1.001554e-01 99 | 100 | 101 | ;; #+BEGIN_SRC emacs-lisp 102 | (bench 103 | (with-current-buffer 104 | (current-buffer)) 105 | (with-current-buffer 106 | (current-buffer))) 107 | ;; #+END_SRC 108 | 109 | ;; #+RESULTS: 110 | ;; : 1.205835e-01 111 | 112 | ;; *** How fast is point 113 | 114 | ;; m-buffer-at provides stateless functions, but how much overhead does this 115 | ;; introduce. We try this with the simplest function I can think of, which is 116 | ;; point. The various forms look different here -- because we have a 117 | ;; `current-buffer' call in, but not with `point'. But then, effectively, 118 | ;; `point' must call `current-buffer' somewhere as part of its implementation, 119 | ;; so this difference is fair. 120 | 121 | ;; We conclude that m-buffer is about 100x slower for calling `point', even 122 | ;; when the buffer does not actually need to be changed. So, a lot slower. 123 | 124 | ;; #+BEGIN_SRC emacs-lisp 125 | (bench 126 | (point)) 127 | ;; #+END_SRC 128 | 129 | ;; #+RESULTS: 130 | ;; : -1.722546e-03 131 | 132 | ;; #+BEGIN_SRC emacs-lisp 133 | (bench 134 | (with-current-buffer 135 | (current-buffer) 136 | (point))) 137 | ;; #+END_SRC 138 | 139 | ;; #+RESULTS: 140 | ;; : 3.669245e-02 141 | 142 | ;; #+BEGIN_SRC emacs-lisp 143 | (bench 144 | (m-buffer-at-point 145 | (current-buffer))) 146 | ;; #+END_SRC 147 | 148 | ;; #+RESULTS: 149 | ;; : 1.011448e-01 150 | 151 | 152 | ;; # Local Variables: 153 | ;; # lentic-init: lentic-el-org-init 154 | ;; # End: 155 | -------------------------------------------------------------------------------- /m-buffer-doc.org: -------------------------------------------------------------------------------- 1 | 2 | #+TITLE: Manipulate the Contents of Emacs Buffers 3 | #+AUTHOR: Phillip Lord 4 | 5 | # FIXME: In Emacs≥30, we could drop most of this. 6 | #+TEXINFO_DIR_CATEGORY: Emacs 7 | #+TEXINFO_DIR_TITLE: * m-buffer: (m-buffer-doc). 8 | #+TEXINFO_DIR_DESC: Manipulate the Contents of Emacs Buffers 9 | 10 | #+INFOJS_OPT: view:info toc:nil 11 | 12 | 13 | * Introduction 14 | 15 | m-buffer provides functions for accessing and manipulating the contents of an 16 | Emacs buffer. While Emacs already provides these features, m-buffer provides a 17 | higher-level interaction. It achieves this in several ways: many of the 18 | functions are list-orientated, so avoiding the need for iteration; it avoids 19 | the use of global emacs state whenever it can be avoided, so avoiding 20 | side-effects; and it provides a large library of functions supporting common 21 | operations. 22 | 23 | Core usage of buffer m-buffer is simple. For example, the following code 24 | returns a list of all matches to the /regexp/ "m-buffer" in the 25 | `current-buffer`. 26 | 27 | #+BEGIN_SRC elisp 28 | (m-buffer-match 29 | (current-buffer) 30 | "m-buffer") 31 | #+END_SRC 32 | 33 | m-buffer is also expanding. Other parts of m-buffer provide stateless 34 | interaction with the existing buffer; for example, we can use the following to 35 | fetch the point of any buffer: 36 | 37 | #+BEGIN_SRC elisp 38 | (m-buffer-at-point buffer) 39 | #+END_SRC 40 | 41 | These functions can help greatly when writing code which operates on two or 42 | more buffers. It is also possible to check whether the status of a location -- 43 | either a buffer and position or a marker. For example, these calls are 44 | equivalent to `eolp`. 45 | 46 | #+BEGIN_SRC elisp 47 | (m-buffer-at-eolp buffer position) 48 | (m-buffer-at-eolp marker) 49 | #+END_SRC 50 | 51 | 52 | 53 | ** Status 54 | 55 | `m-buffer' is a work in progress, but much of it is now stable and the 56 | interface should change only in forward-compatible ways for 1.0 release. 57 | The individual files have statements about their stability. 58 | 59 | 60 | 61 | * m-buffer 62 | 63 | m-buffer.el provides list-orientated search both for any regexp and standard 64 | regexps, as well as the ability to do things with these matches: replace, add 65 | overlays or text-properties or, most generically of all, call any function on 66 | matches. 67 | 68 | #+include: "m-buffer.org" :minlevel 2 69 | 70 | 71 | * m-buffer-at 72 | 73 | m-buffer-at.el provides a set of stateless functions which for accessing data 74 | about buffers, without requiring changing the `current-buffer'. 75 | 76 | #+include: "m-buffer-at.org" :minlevel 2 77 | 78 | 79 | * m-buffer-macro 80 | 81 | m-buffer-macro.el provides some general purpose macros for: 82 | 83 | - dealing with markers and their cleanup 84 | - running code at a specific location 85 | 86 | #+include: "m-buffer-macro.org" :minlevel 2 87 | 88 | 89 | * m-buffer-benchmark 90 | 91 | m-buffer-benchmark.el provides no functions, but is a set of benchmarks to 92 | give some idea of how much overhead various m-buffer functions entail. 93 | 94 | #+include: "m-buffer-benchmark.org" :minlevel 2 95 | 96 | 97 | * Roadmap 98 | 99 | ** 0.11 100 | 101 | Full lentic documentation using lentic-server 102 | 103 | 104 | ** 0.12 105 | 106 | Completion of m-buffer-at with all the core buffer functions. 107 | -------------------------------------------------------------------------------- /m-buffer-macro.el: -------------------------------------------------------------------------------- 1 | ;;; m-buffer-macro.el --- Create and dispose of markers -*- lexical-binding: t -*- 2 | 3 | ;;; Header: 4 | 5 | ;; This file is not part of Emacs 6 | 7 | ;; The contents of this file are subject to the GPL License, Version 3.0. 8 | 9 | ;; Copyright (C) 2014-2022 Free Software Foundation, Inc. 10 | 11 | ;; This program is free software: you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; This file provides some utility macros which help to support stateless 27 | ;; operation on buffers, by restoring global state after to what it was before 28 | ;; the macro starts. 29 | 30 | ;; These macros are quite useful, but with the exception of 31 | ;; `m-buffer-with-markers', they are mostly meant to underpin `m-buffer-at'. The 32 | ;; aim is that all the cases where one of these macros is used with a single form 33 | ;; from core Emacs should be provided by m-buffer-at (although this is not the 34 | ;; case yet). These macros might be more efficient if there are a lot of calls to 35 | ;; group together. 36 | 37 | ;;; Code: 38 | 39 | ;; ** Markers 40 | 41 | ;; Markers are generally much nicer than integers, but needs cleaning up 42 | ;; afterwards if a lot are created. It's possible to do this using 43 | ;; `m-buffer-nil-marker', but it can be a bit painful. This form looks like a 44 | ;; `let' form, but removes markers at the end. 45 | 46 | ;; #+begin_src emacs-lisp 47 | (defmacro m-buffer-with-markers (varlist &rest body) 48 | "Bind variables after VARLIST then eval BODY. 49 | VARLIST is of the same form as `let'. All variables should 50 | contain markers or collections of markers. All markers are niled 51 | after BODY." 52 | ;; indent let part specially, and debug like let 53 | (declare (indent 1)(debug let)) 54 | ;; so, create a rtn var with make-symbol (for hygene) 55 | (let* ((rtn-var (make-symbol "rtn-var")) 56 | (marker-vars 57 | (mapcar #'car varlist)) 58 | (full-varlist 59 | (append 60 | varlist 61 | `((,rtn-var 62 | (progn 63 | ,@body)))))) 64 | `(let* ,full-varlist 65 | (m-buffer-nil-marker 66 | (list ,@marker-vars)) 67 | ,rtn-var))) 68 | ;; #+end_src 69 | 70 | ;; ** Point and Buffer 71 | 72 | ;; These macros are extensions of `with-current-buffer', and `save-excursion', 73 | ;; which set the current buffer and location. 74 | 75 | ;; #+begin_src emacs-lisp 76 | (defmacro m-buffer-with-current-marker 77 | (marker &rest body) 78 | "At MARKER location run BODY." 79 | (declare (indent 1) (debug t)) 80 | `(with-current-buffer 81 | (marker-buffer ,marker) 82 | (save-excursion 83 | (goto-char ,marker) 84 | ,@body))) 85 | 86 | (defmacro m-buffer-with-current-position 87 | (buffer location &rest body) 88 | "In BUFFER at LOCATION, run BODY." 89 | (declare (indent 2) 90 | (debug t)) 91 | `(with-current-buffer 92 | ,buffer 93 | (save-excursion 94 | (goto-char ,location) 95 | ,@body))) 96 | ;; #+end_src 97 | 98 | ;; Combines the last two! 99 | 100 | ;; #+begin_src emacs-lisp 101 | (defmacro m-buffer-with-current-location 102 | (location &rest body) 103 | "At LOCATION, run BODY. 104 | LOCATION should be a list. If a one element list, it is a marker. 105 | If a two element, it is a buffer and position." 106 | (declare (indent 1) (debug t)) 107 | ;; multiple eval of location! 108 | (let ((loc (make-symbol "loc"))) 109 | `(let ((,loc ,location)) 110 | (if (= 1 (length ,loc)) 111 | (m-buffer-with-current-marker 112 | (nth 0 ,loc) 113 | ,@body) 114 | (if (= 2 (length ,loc)) 115 | (m-buffer-with-current-position 116 | (nth 0 ,loc) 117 | (nth 1 ,loc) 118 | ,@body) 119 | (error "m-buffer-with-current-location requires a list of one or two elements")))))) 120 | 121 | (provide 'm-buffer-macro) 122 | ;;; m-buffer-macro.el ends here 123 | ;; #+end_src 124 | -------------------------------------------------------------------------------- /m-buffer.el: -------------------------------------------------------------------------------- 1 | ;;; m-buffer.el --- List-Oriented, Functional Buffer Manipulation -*- lexical-binding: t -*- 2 | 3 | ;;; Header: 4 | 5 | ;; This file is not part of Emacs 6 | 7 | ;; Author: Phillip Lord 8 | ;; Maintainer: Phillip Lord 9 | ;; Version: 0.16.1 10 | ;; Package-Requires: ((seq "2.14")) 11 | 12 | ;; The contents of this file are subject to the GPL License, Version 3.0. 13 | 14 | ;; Copyright (C) 2014-2024 Free Software Foundation, Inc. 15 | 16 | ;; This program is free software: you can redistribute it and/or modify 17 | ;; it under the terms of the GNU General Public License as published by 18 | ;; the Free Software Foundation, either version 3 of the License, or 19 | ;; (at your option) any later version 20 | 21 | ;; This program is distributed in the hope that it will be useful, 22 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | ;; GNU General Public License for more details. 25 | 26 | ;; You should have received a copy of the GNU General Public License 27 | ;; along with this program. If not, see . 28 | 29 | ;;; Commentary: 30 | 31 | ;; This file provides a set of list-oriented functions for operating over the 32 | ;; contents of buffers, mostly revolving around regexp searching, and regions. 33 | ;; They avoid the use of looping, manipulating global state with `match-data'. 34 | ;; Many high-level functions exist for matching sentences, lines and so on. 35 | 36 | ;; Functions are generally purish: i.e. that is those functions which do 37 | ;; change state, by for example replacing text or adding overlays, should only 38 | ;; change state in one way; they will not affect point, current buffer, match 39 | ;; data or so forth. 40 | 41 | ;; Likewise to protect against changes in state, markers are used rather than 42 | ;; integer positions. This means that it is possible, for example, to search 43 | ;; for regexp matches and then replace them all without the earlier 44 | ;; replacements invalidating the location of the later ones. Otherwise 45 | ;; replacements need to be made in reverse order. This can have implications 46 | ;; for performance, so m-buffer also provides functions for making markers nil; 47 | ;; there are also macros which help manage markers in `m-buffer-macro'. 48 | 49 | ;; Where possible, functions share interfaces. So most of the match functions 50 | ;; take a list of "match" arguments, either position or as a plist, which avoids 51 | ;; using lots of `nil' arguments. Functions operating on matches take a list of 52 | ;; `match-data' as returned by the match functions, making it easy to chain 53 | ;; matches. 54 | 55 | ;; This file is documented using lentic.el. Use 56 | ;; [[http://github.com/phillord/lentic-server][lentic-server]] to view. 57 | 58 | ;;; Status: 59 | 60 | ;; m-buffer.el is now stable and is expected to change only in 61 | ;; forward-compatible ways. 62 | 63 | ;;; Code: 64 | 65 | ;; #+begin_src emacs-lisp 66 | (require 'seq) 67 | (require 'm-buffer-macro) 68 | 69 | ;; #+end_src 70 | 71 | ;; ** Regexp Matching 72 | 73 | ;; We first provide a single match function, `m-bufffer-match' which converts 74 | ;; between Emacs' stateful matching and a more sequence-oriented interface. 75 | 76 | ;; This function also defines the "match" arguments which are a standard set of 77 | ;; arguments used throughout this package. 78 | 79 | ;; #+begin_src emacs-lisp 80 | (defun m-buffer-match (&rest match) 81 | "Return a list of all `match-data' for MATCH. 82 | MATCH may be of the forms: 83 | BUFFER REGEXP &optional MATCH-OPTIONS 84 | WINDOW REGEXP &optional MATCH-OPTIONS 85 | MATCH-OPTIONS 86 | 87 | If BUFFER is given, search this buffer. If WINDOW is given search 88 | the visible window. MATCH-OPTIONS is a plist with any of the 89 | following keys: 90 | :buffer -- the buffer to search 91 | :regexp -- the regexp to search with 92 | :begin -- the start of the region to search -- default point min 93 | :end -- the end of the region to search -- default point max 94 | :post-match -- function called after a match -- default nil 95 | :widen -- if true, widen buffer first -- default nil 96 | :case-fold-search value of `case-fold-search' during search. 97 | If :default accept the current buffer-local value 98 | :numeric -- if true, return integers not markers 99 | 100 | If options are expressed in two places, the plist form takes 101 | precedence over positional args. So calling with both a first 102 | position buffer and a :buffer arg will use the second. Likewise, 103 | if a window is given as first arg and :end is given, then 104 | the :end value will be used. 105 | 106 | REGEXP should advance point (i.e. not be zero-width) or the 107 | function will loop infinitely. POST-MATCH can be used to avoid 108 | this. The buffer is searched forward." 109 | (apply #'m-buffer--match-1 110 | (m-buffer--normalize-args match))) 111 | ;; #+end_src 112 | 113 | ;; The match function is actually implemented here in the `m-buffer--match-1' 114 | ;; function, with positional arguments. 115 | 116 | ;; #+begin_src emacs-lisp 117 | (defun m-buffer--match-1 (buffer regexp begin end 118 | post-match widen cfs 119 | numeric) 120 | "Return a list of `match-data'. 121 | 122 | This is an internal function: please prefer `m-buffer-match'. 123 | 124 | BUFFER -- the buffer. 125 | REGEXP -- the regexp. 126 | BEGIN -- the start of the region to search 127 | END -- the end of the region to search 128 | POST-MATCH -- function to run after each match 129 | POST-MATCH is useful for zero-width matches which will otherwise 130 | cause infinite loop. The buffer is searched forward. POST-MATCH 131 | return can also be used to terminate the matching by returning nil. 132 | WIDEN -- call widen first. 133 | CFS -- Non-nil if searches and matches should ignore case. 134 | NUMERIC -- Non-nil if we should return integers not markers." 135 | ;; #+end_src 136 | 137 | ;; We start by saving everything to ensure that we do not pollute the global 138 | ;; state. This means match-data, point, narrowing and current buffer! Hopefully 139 | ;; this is all the global state that exists and that we are changing. 140 | 141 | ;; #+begin_src emacs-lisp 142 | (with-current-buffer 143 | buffer 144 | (save-match-data 145 | (save-excursion 146 | (save-restriction 147 | (when widen (widen)) 148 | ;; #+end_src 149 | 150 | ;; This let form is doing a number of things. It sets up a dynamic binding for 151 | ;; `case-fold-search' (which works even though we are using lexical binding), 152 | ;; ensures a non-nil value for =end-bound= and defines a sentinal value that 153 | ;; =post-match-return= can use to end early. 154 | 155 | ;; #+begin_src emacs-lisp 156 | (let ((rtn nil) 157 | (post-match-return t) 158 | (end-bound (or end (point-max))) 159 | ;; over-ride default if necessary 160 | (case-fold-search 161 | (if (eq :default cfs) 162 | case-fold-search 163 | cfs))) 164 | ;; #+end_src 165 | 166 | ;; We start at the beginning. There was no particularly good reason for this, and 167 | ;; it would have made just as much sense to go backward. 168 | 169 | ;; #+begin_src emacs-lisp 170 | (goto-char 171 | (or begin 172 | (point-min))) 173 | (while 174 | (and 175 | ;; #+end_src 176 | 177 | ;; The original purpose for =post-match-return= was for zero-width matches -- 178 | ;; these do not advance point beyond their end, so the while loop never 179 | ;; terminates. Unfortunately, avoiding this depends on the regexp being called, 180 | ;; so we provide the most general solution of all. 181 | 182 | ;; As well as this, we check the return value of =post-match-return=, so as well 183 | ;; as advancing `point' by side-effect, we can also use it to terminate the look 184 | ;; at any point that we want; for example, we can terminate after the first match 185 | ;; which feels more efficient than searching the whole buffer then taking the 186 | ;; first match. 187 | 188 | ;; #+begin_src emacs-lisp 189 | post-match-return 190 | ;; we need to check we are less than the end-bound 191 | ;; or re-search-forward will break 192 | (<= (point) end-bound) 193 | (re-search-forward 194 | regexp end-bound 195 | t)) 196 | ;; #+end_src 197 | 198 | ;; Store the `match-data' in a backward list, run post-match. Finally, reverse 199 | ;; and terminate. 200 | 201 | ;; #+begin_src emacs-lisp 202 | (setq rtn 203 | (cons 204 | (if numeric 205 | (m-buffer-marker-to-pos-nil 206 | (match-data)) 207 | (match-data)) 208 | rtn)) 209 | (when post-match 210 | (setq post-match-return (funcall post-match)))) 211 | (reverse rtn))))))) 212 | ;; #+end_src 213 | 214 | ;; This method implements the argument list processing. I find this interface 215 | ;; fairly attractive to use since it takes the two "main" arguments -- buffer and 216 | ;; regexp -- as positional args optionally, and everything else as keywords. The 217 | ;; use of keywords is pretty much essential as have eight arguments most of which 218 | ;; are not essential. 219 | 220 | ;; This is fairly close to the logic provided by `cl-defun' which I wasn't aware 221 | ;; of when I wrote this. However `cl-defun' does not allow optional arguments 222 | ;; before keyword arguments -- all the optional arguments have to be given if we 223 | ;; are to use keywords. 224 | 225 | ;; #+begin_src emacs-lisp 226 | (defun m-buffer--normalize-args (match-with) 227 | "Manipulate args into a standard form and return as a list. 228 | MATCH-WITH are these args. This is an internal function." 229 | (let* ( 230 | ;; split up into keyword and non keyword limits 231 | (args 232 | (seq-take-while 233 | (lambda (x) (not (keywordp x))) 234 | match-with)) 235 | (pargs 236 | (seq-drop-while 237 | (lambda (x) (not (keywordp x))) 238 | match-with)) 239 | ;; sort actual actual parameters 240 | (first (car args)) 241 | ;; buffer may be first 242 | (buffer 243 | (or (plist-get pargs :buffer) 244 | (and (bufferp first) first))) 245 | ;; or window may be first 246 | (window 247 | (or (plist-get pargs :window) 248 | (and (windowp first) first))) 249 | ;; regexp always comes second 250 | (regexp 251 | (or (plist-get pargs :regexp) 252 | (nth 1 args))) 253 | ;; begin depends on other arguments 254 | (begin 255 | (or (plist-get pargs :begin) 256 | (and window (window-start window)))) 257 | ;; end depends on other arguments 258 | (end 259 | (or (plist-get pargs :end) 260 | (and window (window-end window)))) 261 | ;; pm 262 | (post-match 263 | (plist-get pargs :post-match)) 264 | 265 | ;; widen 266 | (widen 267 | (plist-get pargs :widen)) 268 | 269 | ;; case-fold-search this needs to overwrite the buffer contents iff 270 | ;; set, otherwise be ignored, so we need to distinguish a missing 271 | ;; property and a nil one 272 | (cfs 273 | (if (plist-member pargs :case-fold-search) 274 | (plist-get pargs :case-fold-search) 275 | :default)) 276 | 277 | ;; numeric 278 | (numeric 279 | (plist-get pargs :numeric))) 280 | 281 | (list buffer regexp begin end post-match widen cfs numeric))) 282 | ;; #+end_src 283 | 284 | ;; Finally, this function provides a link between the match function, and the 285 | ;; match manipulation functions. We can either choose to match once against a set 286 | ;; of arguments and then apply multiple manipulations on the returned match data. 287 | ;; Or just use the match manipulation function directly. 288 | 289 | ;; The first version of `m-buffer' did not include this but it required lots of 290 | ;; nested calls which seem inconvenient. 291 | 292 | ;; #+begin_example 293 | ;; (m-buffer-match-manipulate 294 | ;; (m-buffer-match (current-buffer) "hello")) 295 | ;; #+end_example 296 | 297 | ;; I think that convienience is worth the overhead. 298 | 299 | ;; #+begin_src emacs-lisp 300 | (defun m-buffer-ensure-match (&rest match) 301 | "Ensure that we have MATCH data. 302 | If a single arg, assume it is match data and return. If multiple 303 | args, assume they are of the form accepted by 304 | `m-buffer-match'." 305 | (cond 306 | ;; we have match data 307 | ((= 1 (length match)) 308 | (car match)) 309 | ((< 1 (length match)) 310 | (apply #'m-buffer-match match)) 311 | (t 312 | (error "Invalid arguments")))) 313 | ;; #+end_src 314 | 315 | ;; ** Match Data Manipulation Functions 316 | 317 | ;; These functions manipulate lists of either match-data or match arguments in 318 | ;; some way. 319 | 320 | ;; #+begin_src emacs-lisp 321 | (defun m-buffer-buffer-for-match (match-data) 322 | "Given some MATCH-DATA return the buffer for that data." 323 | (marker-buffer (caar match-data))) 324 | 325 | (defun m-buffer-match-nth-group (n match-data) 326 | "Fetch the Nth group from MATCH-DATA." 327 | (seq-map 328 | (lambda (m) 329 | (let ((drp 330 | (seq-drop m (* 2 n)))) 331 | (list 332 | (car drp) (cadr drp)))) 333 | match-data)) 334 | 335 | (defun m-buffer-match-begin-n (n &rest match) 336 | "Return markers to the start of the Nth group in MATCH. 337 | MATCH may be of any form accepted by `m-buffer-ensure-match'. Use 338 | `m-buffer-nil-marker' after the markers have been finished with 339 | or they will slow future use of the buffer until garbage collected." 340 | (seq-map 341 | (lambda (m) 342 | (nth 343 | (* 2 n) m)) 344 | (apply #'m-buffer-ensure-match match))) 345 | 346 | (defun m-buffer-match-begin-n-pos (n &rest match) 347 | "Return positions of the start of the Nth group in MATCH. 348 | MATCH may be of any form accepted by `m-buffer-ensure-match'. If 349 | `match-data' is passed markers will be set to nil after this 350 | function. See `m-buffer-nil-marker' for details." 351 | (m-buffer-marker-to-pos-nil 352 | (apply #'m-buffer-match-begin-n 353 | n match))) 354 | 355 | (defun m-buffer-match-begin (&rest match) 356 | "Return a list of markers to the start of MATCH. 357 | MATCH may of any form accepted by `m-buffer-ensure-match'. Use 358 | `m-buffer-nil-marker' after the markers have been used or they 359 | will slow future changes to the buffer." 360 | (apply #'m-buffer-match-begin-n 0 match)) 361 | 362 | (defun m-buffer-match-begin-pos (&rest match) 363 | "Return a list of positions at the start of matcher. 364 | MATCH may be of any form accepted by `m-buffer-ensure-match'. 365 | If `match-data' is passed markers will be set to nil after this 366 | function. See `m-buffer-nil-marker' for details." 367 | (apply #'m-buffer-match-begin-n-pos 0 match)) 368 | 369 | (defun m-buffer-match-end-n (n &rest match) 370 | "Return markers to the end of the match to the Nth group. 371 | MATCH may be of any form accepted by `m-buffer-ensure-match'. 372 | If `match-data' is passed markers will be set to nil after this 373 | function. See `m-buffer-nil-marker' for details." 374 | (seq-map 375 | (lambda (m) 376 | (nth 377 | (+ 1 (* 2 n)) 378 | m)) 379 | (apply #'m-buffer-ensure-match match))) 380 | 381 | (defun m-buffer-match-end-n-pos (n &rest match) 382 | "Return positions of the end Nth group of MATCH. 383 | MATCH may be of any form accepted by `m-buffer-ensure-match'. 384 | If `match-data' is passed markers will be set to nil after this 385 | function. See `m-buffer-nil-marker' for details." 386 | (m-buffer-marker-to-pos-nil 387 | (apply #'m-buffer-match-end-n-pos 388 | n match))) 389 | 390 | (defun m-buffer-match-end (&rest match) 391 | "Return a list of markers to the end of MATCH to regexp in buffer. 392 | MATCH may be of any form accepted by `m-buffer-ensure-match'. Use 393 | `m-buffer-nil-marker' after the markers have been used or they 394 | will slow future changes to the buffer." 395 | (apply #'m-buffer-match-end-n 0 match)) 396 | 397 | (defun m-buffer-match-end-pos (&rest match) 398 | "Return a list of positions to the end of the match. 399 | MATCH may be of any form accepted by `m-buffer-ensure-match'. 400 | If `match-data' is passed markers will be set to nil after this 401 | function. See `m-buffer-nil-marker' for details." 402 | (m-buffer-marker-to-pos-nil 403 | (apply #'m-buffer-match-end match))) 404 | ;; #+end_src 405 | 406 | ;; ** Match Utility and Predicates 407 | 408 | ;; *** Subtraction 409 | 410 | ;; Some predicates and the ability to subtract to lists of matches from each 411 | ;; other. This makes up for limitations in Emacs regexp which can't do "match x 412 | ;; but not y". 413 | 414 | ;; #+begin_src emacs-lisp 415 | (defun m-buffer-match-equal (m n) 416 | "Return true if M and N are cover the same region. 417 | Matches are equal if they match the same region; subgroups are 418 | ignored." 419 | (and 420 | (equal 421 | (car m) 422 | (car n)) 423 | (equal 424 | (cadr m) 425 | (cadr n)))) 426 | ;; #+end_src 427 | 428 | ;; A nice simple implementation for the general purpose solution. 429 | ;; Unfortunately, performance sucks, running in quadratic time. 430 | 431 | ;; #+begin_src emacs-lisp 432 | (defun m-buffer-match-subtract (m n) 433 | "Remove from M any match in N. 434 | Matches are equivalent if overall they match the same 435 | area; subgroups are ignored. 436 | See also `m-buffer-match-exact-subtract' which often 437 | runs faster but has some restrictions." 438 | (seq-remove 439 | (lambda (o) 440 | (seq-some 441 | (lambda (p) 442 | (m-buffer-match-equal o p)) 443 | n)) 444 | m)) 445 | ;; #+end_src 446 | 447 | ;; The ugly and complicated and less general solution. But it runs in linear 448 | ;; time. 449 | 450 | ;; #+begin_src emacs-lisp 451 | (defun m-buffer-match-exact-subtract (m n) 452 | "Remove from M any match in N. 453 | Both M and N must be fully ordered, and any element in N must be 454 | in M." 455 | (if n 456 | ;; n-eaten contains the remaining elements of n that we haven't tested 457 | ;; for yet. We throw them away as we go 458 | (let ((n-eaten n)) 459 | (seq-remove 460 | (lambda (o) 461 | (cond 462 | ;; n-eaten has been eaten. Check here or later "<" comparison crashes. 463 | ((not n-eaten) 464 | ;; return nil because we always want things in m now. 465 | nil 466 | ) 467 | ;; we have a match so throw away the first element of n-eaten 468 | ;; which we won't need again. 469 | ((m-buffer-match-equal 470 | (car n-eaten) o) 471 | (progn 472 | (setq n-eaten (seq-drop n-eaten 1)) 473 | t)) 474 | ;; we should discard also if n-eaten 1 is less than o because, both 475 | ;; are sorted, so we will never match 476 | ((< 477 | ;; first half of the first match in n-eaten 478 | (caar n-eaten) 479 | ;; first half of match 480 | (car o)) 481 | (progn 482 | (setq n-eaten (seq-drop n-eaten 1)) 483 | t)))) 484 | m)) 485 | m)) 486 | 487 | (defun m-buffer-in-match-p (matches position) 488 | "Returns true is any of MATCHES contain POSITION." 489 | (seq-some 490 | (lambda (match) 491 | (and 492 | (<= (car match) position) 493 | (<= position (cadr match)))) 494 | matches)) 495 | ;; #+end_src 496 | 497 | 498 | ;; *** Partition 499 | 500 | ;; Partition one set of markers by another. This is useful for finding matched 501 | ;; pairs of markers. 502 | 503 | ;; #+begin_src emacs-lisp 504 | (defun m-buffer--partition-by-marker(list partition) 505 | "Given LIST, split at markers in PARTITION. 506 | 507 | This is the main implementation for `m-buffer-partition-by-marker', 508 | but assumes that partition starts with a very low value (or nil)." 509 | (let* ((p-top (car-safe partition)) 510 | (p-val (car-safe (cdr-safe partition))) 511 | (p-fn (lambda (n) 512 | (or (not p-val) 513 | (< n p-val))))) 514 | (when list 515 | (cons 516 | (cons 517 | p-top 518 | (seq-take-while p-fn list)) 519 | (m-buffer--partition-by-marker 520 | (seq-drop-while p-fn list) 521 | (cdr partition)))))) 522 | 523 | (defun m-buffer-partition-by-marker (list partition) 524 | "Given LIST of markers, split at markers in PARTITION. 525 | Returns a list of lists. The first element of each list is nil or 526 | the marker from PARTITION. The rest of the elements are those 527 | elements in LIST which are at the same position or later in the 528 | buffer than the element from PARTITION, but before the next 529 | element from PARTITION. 530 | 531 | Both LIST and PARTITION must be sorted." 532 | ;; TODO! 533 | (m-buffer--partition-by-marker list (cons nil partition))) 534 | ;; #+end_src 535 | 536 | 537 | ;; ** Marker manipulation functions 538 | 539 | ;; These functions do things to markers rather than the areas of the buffers 540 | ;; indicated by the markers. This includes transforming between markers and 541 | ;; integer positions, and niling markers explicitly, which prevents slow down 542 | ;; before garbage collection. 543 | 544 | ;; #+begin_src emacs-lisp 545 | (defun m-buffer-nil-marker (markers) 546 | "Takes a (nested) list of MARKERS and nils them all. 547 | Markers slow buffer movement while they are pointing at a 548 | specific location, until they have been garbage collected. Niling 549 | them prevents this. See Info node `(elisp) Overview of Markers'." 550 | (seq-map 551 | (lambda (marker) 552 | (if (seqp marker) 553 | (m-buffer-nil-marker marker) 554 | (set-marker marker nil))) 555 | markers)) 556 | 557 | (defun m-buffer-marker-to-pos (markers &optional postnil) 558 | "Transforms a list of MARKERS to a list of positions. 559 | If the markers are no longer needed, set POSTNIL to true, or call 560 | `m-buffer-nil-marker' manually after use to speed future buffer 561 | movement. Or use `m-buffer-marker-to-pos-nil'." 562 | (seq-map 563 | (lambda (marker) 564 | (prog1 565 | (marker-position marker) 566 | (when postnil 567 | (set-marker marker nil)))) 568 | markers)) 569 | 570 | (defun m-buffer-marker-to-pos-nil (markers) 571 | "Transforms a list of MARKERS to a list of positions then nils. 572 | See also `m-buffer-nil-markers'" 573 | (m-buffer-marker-to-pos markers t)) 574 | 575 | (defun m-buffer-marker-tree-to-pos (marker-tree &optional postnil) 576 | "Transforms a tree of markers to equivalent positions. 577 | MARKER-TREE is the tree. 578 | POSTNIL sets markers to nil afterwards." 579 | (seq-map 580 | (lambda (marker) 581 | (if (seqp marker) 582 | (m-buffer-marker-tree-to-pos marker postnil) 583 | (prog1 584 | (marker-position marker) 585 | (when postnil 586 | (set-marker marker nil))))) 587 | marker-tree)) 588 | 589 | (defun m-buffer-marker-tree-to-pos-nil (marker-tree) 590 | "Transforms a tree of markers to equivalent positions. 591 | MARKER-TREE is the tree. Markers are niled afterwards." 592 | (m-buffer-marker-tree-to-pos marker-tree t)) 593 | 594 | (defun m-buffer-marker-clone (marker-tree &optional type) 595 | "Return a clone of MARKER-TREE. 596 | The optional argument TYPE specifies the insertion type. See 597 | `copy-marker' for details." 598 | (seq-map 599 | (lambda (marker) 600 | (if (seqp marker) 601 | (m-buffer-marker-clone marker type) 602 | (copy-marker marker type))) 603 | marker-tree)) 604 | 605 | (defun m-buffer-pos-to-marker (buffer positions) 606 | "In BUFFER translates a list of POSITIONS to markers." 607 | (seq-map 608 | (lambda (pos) 609 | (set-marker 610 | (make-marker) pos buffer)) 611 | positions)) 612 | ;; #+end_src 613 | 614 | ;; ** Replace, Delete, Extract 615 | 616 | ;; #+begin_src emacs-lisp 617 | (defun m-buffer-replace-match (match-data replacement 618 | &optional fixedcase literal subexp) 619 | "Given a list of MATCH-DATA, replace with REPLACEMENT. 620 | If FIXEDCASE do not alter the case of the replacement text. 621 | If LITERAL insert the replacement literally. 622 | SUBEXP should be a number indicating the regexp group to replace. 623 | Returns markers to the start and end of the replacement. These 624 | markers are part of MATCH-DATA, so niling them will percolate backward. 625 | 626 | See also `replace-match'." 627 | (save-excursion 628 | (seq-map 629 | (lambda (match) 630 | (with-current-buffer 631 | (marker-buffer (car match)) 632 | (save-match-data 633 | (set-match-data match) 634 | (replace-match 635 | replacement fixedcase literal nil 636 | (or subexp 0))))) 637 | match-data)) 638 | ;; we have match-data 639 | (m-buffer-match-nth-group (or subexp 0) match-data)) 640 | 641 | (defun m-buffer-delete-match (match-data &optional subexp) 642 | "Delete all MATCH-DATA. 643 | SUBEXP should be a number indicating the regexp group to delete. 644 | Returns markers to the start and end of the replacement. These 645 | markers are part of MATCH_DATA, so niling them will percolate backward." 646 | (m-buffer-replace-match match-data "" subexp)) 647 | 648 | (defun m-buffer-match-string (match-data &optional subexp) 649 | "Return strings for MATCH-DATA optionally of group SUBEXP." 650 | (seq-map 651 | (lambda (match) 652 | (with-current-buffer 653 | (marker-buffer (car match)) 654 | (save-match-data 655 | (set-match-data match) 656 | (match-string 657 | (or subexp 0))))) 658 | match-data)) 659 | 660 | (defun m-buffer-match-string-no-properties (match-data &optional subexp) 661 | "Return strings for MATCH-DATA optionally of group SUBEXP. 662 | Remove all properties from return." 663 | (seq-map 664 | #'substring-no-properties 665 | (m-buffer-match-string 666 | match-data subexp))) 667 | ;; #+end_src 668 | 669 | ;; ** Match Things 670 | 671 | ;; Emacs comes with a set of in-built regexps most of which we use here. 672 | 673 | ;; We define `m-buffer-apply-join' first. The reason for this function is that 674 | ;; we want to take a list of match arguments and add to with, for instance, a 675 | ;; regular expression. We need to add these at the end because most of our 676 | ;; functions contain some positional arguments. 677 | 678 | 679 | ;; #+begin_src emacs-lisp 680 | (defun m-buffer-apply-join (fn match &rest more-match) 681 | (let* 682 | ((args 683 | (seq-take-while 684 | (lambda (x) (not (keywordp x))) 685 | match)) 686 | (pargs 687 | (seq-drop-while 688 | (lambda (x) (not (keywordp x))) 689 | match)) 690 | (more-keywords 691 | (seq-map 692 | #'car 693 | (seq-partition more-match 2)))) 694 | (when 695 | (seq-find 696 | (lambda (keyword) 697 | (plist-member pargs keyword)) 698 | more-keywords) 699 | (error 700 | "Match arg contradicts a defined argument.")) 701 | (apply fn (append args more-match pargs)))) 702 | ;; #+end_src 703 | 704 | ;; For the following code, we use Emacs core regexps where possible. 705 | 706 | ;; #+begin_src emacs-lisp 707 | (defun m-buffer-match-page (&rest match) 708 | "Return a list of match data to all pages in MATCH. 709 | MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See 710 | `m-buffer-match' for further details." 711 | (m-buffer-apply-join 'm-buffer-match 712 | match :regexp page-delimiter)) 713 | ;; #+end_src 714 | 715 | ;; The `paragraph-separate' regexp can match an empty region, so we need to start 716 | ;; each search at the beginning of the next line. 717 | 718 | ;; #+begin_src emacs-lisp 719 | (defun m-buffer-match-paragraph-separate (&rest match) 720 | "Return a list of match data to `paragraph-separate' in MATCH. 721 | MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See 722 | `m-buffer-match' for futher details." 723 | (m-buffer-apply-join 724 | 'm-buffer-match match :regexp paragraph-separate 725 | :post-match 'm-buffer-post-match-forward-line)) 726 | 727 | (defvar m-buffer--line-regexp 728 | "^.*$" 729 | "Regexp to match a line.") 730 | 731 | (defun m-buffer-match-line (&rest match) 732 | "Return a list of match data to all lines. 733 | MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. 734 | See `m-buffer-match for further details." 735 | (m-buffer-apply-join 736 | 'm-buffer-match 737 | match :regexp m-buffer--line-regexp 738 | :post-match 'm-buffer-post-match-forward-char)) 739 | 740 | (defun m-buffer-match-line-start (&rest match) 741 | "Return a list of match data to all line start. 742 | MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See 743 | `m-buffer-match' for further details." 744 | (m-buffer-apply-join 745 | 'm-buffer-match-begin 746 | match :regexp "^" 747 | :post-match 'm-buffer-post-match-forward-char)) 748 | 749 | (defun m-buffer-match-line-end (&rest match) 750 | "Return a list of match to line end. 751 | MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See 752 | `m-buffer-match' for further details." 753 | (m-buffer-apply-join 754 | 'm-buffer-match-begin 755 | match :regexp "$" 756 | :post-match 'm-buffer-post-match-forward-char)) 757 | ;; #+end_src 758 | 759 | ;; This is the first use of the =post-match= to terminate the loop, and was 760 | ;; actually the motivation for adding it. We automatically terminate after the 761 | ;; first match by simply returning nil. 762 | 763 | ;; #+begin_src emacs-lisp 764 | (defun m-buffer-match-first (&rest match) 765 | "Return the first match to MATCH. 766 | This matches more efficiently than matching all matches and 767 | taking the car. See `m-buffer-match' for further details of 768 | MATCH." 769 | (m-buffer-apply-join 770 | #'m-buffer-match match 771 | :post-match (lambda () nil))) 772 | 773 | (defun m-buffer-match-first-line (&rest match) 774 | "Return a match to the first line of MATCH. 775 | This matches more efficiently than matching all lines and taking 776 | the car. See `m-buffer-match' for further details of MATCH." 777 | (m-buffer-apply-join 778 | 'm-buffer-match-first match 779 | :regexp m-buffer--line-regexp)) 780 | 781 | (defun m-buffer-match-multi (regexps &rest match) 782 | "Incrementally find matches to REGEXPS in MATCH. 783 | Finds the first match to the first element of regexps, then 784 | starting from the end of this match, the first match to the 785 | second element of regexps and so forth. See `m-buffer-match' for 786 | futher details of MATCH." 787 | (when regexps 788 | (let ((first-match 789 | (m-buffer-apply-join 790 | #'m-buffer-match-first 791 | match 792 | :regexp (car regexps)))) 793 | (append 794 | first-match 795 | (apply 796 | #'m-buffer-match-multi 797 | (cdr regexps) 798 | (plist-put 799 | match 800 | :begin (car (m-buffer-match-end first-match)))))))) 801 | ;; #+end_src 802 | 803 | ;; Emacs has a rather inconsistent interface here -- suddenly, we have a function 804 | ;; rather than a variable for accessing a regexp. 805 | 806 | ;; #+begin_src emacs-lisp 807 | (defun m-buffer-match-sentence-end (&rest match) 808 | "Return a list of match to sentence end. 809 | MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See 810 | `m-buffer-match' for further details." 811 | (m-buffer-apply-join 812 | 'm-buffer-match-begin 813 | match :regexp (sentence-end))) 814 | 815 | (defun m-buffer-match-word (&rest match) 816 | "Return a list of match to all words. 817 | MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See 818 | `m-buffer-match' for further details." 819 | (m-buffer-apply-join 820 | 'm-buffer-match 821 | match :regexp "\\\w+")) 822 | 823 | (defun m-buffer-match-empty-line (&rest match) 824 | "Return a list of match to all empty lines. 825 | MATCH is of the form BUFFER-OR-WINDOW MATCH-OPTIONS. See 826 | `m-buffer-match' for further details." 827 | (m-buffer-apply-join 828 | 'm-buffer-match 829 | match :regexp "^$" 830 | :post-match 'm-buffer-post-match-forward-line)) 831 | 832 | (defun m-buffer-match-non-empty-line (&rest match) 833 | "Return a list of match to all non-empty lines. 834 | MATCH is fo the form BUFFER-OR-WINDOW MATCH-OPTIONS. See 835 | `m-buffer-match' for further details." 836 | (m-buffer-apply-join 837 | 'm-buffer-match 838 | match :regexp "^.+$")) 839 | 840 | (defun m-buffer-match-whitespace-line (&rest match) 841 | "Return match data to all lines with only whitespace characters. 842 | Note empty lines are not included. MATCH is of form 843 | BUFFER-OR-WINDOW MATCH-OPTIONS. See `m-buffer-match' for 844 | further details." 845 | (m-buffer-apply-join 846 | 'm-buffer-match 847 | match :regexp "^\\s-+$")) 848 | 849 | ;; #+end_src 850 | 851 | ;; I don't think that there is a way to do this with regexps entirely, so we use 852 | ;; substraction. 853 | 854 | ;; #+begin_src emacs-lisp 855 | (defun m-buffer-match-non-whitespace-line (&rest match) 856 | "Return match data to all lines with at least one non-whitespace character. 857 | Note empty lines do not contain any non-whitespace lines. 858 | MATCH is of form BUFFER-OR-WINDOW MATCH-OPTIONS. See 859 | `m-buffer-match' for further details." 860 | (seq-difference 861 | (apply #'m-buffer-match-line match) 862 | (apply #'m-buffer-match-whitespace-line match))) 863 | 864 | ;; Useful post-match functions 865 | (defun m-buffer-post-match-forward-line () 866 | "Attempt to move forward one line, return true if success." 867 | (= 0 (forward-line))) 868 | 869 | (defun m-buffer-post-match-forward-char () 870 | "Attempts to move forward one char. 871 | Returns true if succeeds." 872 | (condition-case _e 873 | (progn 874 | (forward-char) 875 | t) 876 | (error 'end-of-buffer 877 | nil))) 878 | ;; #+end_src 879 | 880 | 881 | ;; ** Apply Function to Match 882 | 883 | ;; These functions apply another function to some match-data. This is pretty 884 | ;; useful generically, but also I use it for many of the following functions. 885 | 886 | ;; #+begin_src emacs-lisp 887 | (defun m-buffer-on-region (fn match-data) 888 | "Apply FN to MATCH-DATA. 889 | FN should take two args, the start and stop of each region. 890 | MATCH-DATA can be any list of lists with two elements (or more)." 891 | (m-buffer-on-region-nth-group fn 0 match-data)) 892 | 893 | (defun m-buffer-on-region-nth-group (fn n match-data) 894 | "Apply FN to the Nth group of MATCH-DATA. 895 | FN should take two args, the start and stop of each region. 896 | MATCH-DATA can be any list of lists with two elements (or more)." 897 | (seq-map 898 | (lambda (x) 899 | (apply fn x)) 900 | (m-buffer-match-nth-group n match-data))) 901 | ;; #+end_src 902 | 903 | ;; ** Overlay and Property Functions 904 | 905 | ;; Adding properties or overlays to match-data. The functionality here somewhat 906 | ;; overlaps with [[https://github.com/ShingoFukuyama/ov.el][ov.el]], which I didn't know about when I wrote this. It generally 907 | ;; works over overlays, or regexps, while m-buffer works over match-data. 908 | 909 | ;; #+begin_src emacs-lisp 910 | (defun m-buffer-overlay-match (match-data &optional front-advance rear-advance) 911 | "Return an overlay for all match to MATCH-DATA. 912 | FRONT-ADVANCE and REAR-ADVANCE controls the borders of the 913 | overlay as defined in `make-overlay'. Overlays do not scale that 914 | well, so use `m-buffer-propertize-match' if you intend to make 915 | and keep many of these. 916 | 917 | See Info node `(elisp) Overlays' for further information." 918 | (let ((buffer (m-buffer-buffer-for-match match-data))) 919 | (m-buffer-on-region 920 | (lambda (beginning end) 921 | (make-overlay 922 | beginning end buffer 923 | front-advance rear-advance)) 924 | match-data))) 925 | 926 | (defun m-buffer-add-text-property-match 927 | (match-data properties) 928 | "To MATCH-DATA add PROPERTIES. 929 | See `add-text-property' for details of the format of properties. 930 | Text properties are associated with the text and move with it. See 931 | Info node `(elisp) Text Properties' for further details." 932 | (m-buffer-on-region 933 | (lambda (beginning end) 934 | (add-text-properties beginning end properties)) 935 | match-data)) 936 | 937 | (defun m-buffer-put-text-property-match (match-data property value) 938 | "To MATCH-DATA add PROPERTY wth VALUE. 939 | See `put-text-property' for details of the format of properties. 940 | Text properties are associated with the text and move with it. See 941 | Info node `(elisp) Text Properties' for further details." 942 | (m-buffer-on-region 943 | (lambda (beginning end) 944 | (put-text-property beginning end property value)) 945 | match-data)) 946 | 947 | (defun m-buffer-overlay-face-match (match-data face) 948 | "To MATCH-DATA add FACE to the face property. 949 | This is for use in buffers which do not have function `font-lock-mode' 950 | enabled; otherwise use `m-buffer-overlay-font-lock-face-match'." 951 | (seq-map 952 | (lambda (ovly) 953 | (overlay-put ovly 'face face)) 954 | (m-buffer-overlay-match match-data))) 955 | 956 | (defun m-buffer-overlay-font-lock-face-match (match-data face) 957 | "To MATCH-DATA add FACE to the face property. 958 | This is for use in buffers which have variable `font-lock-mode' enabled; 959 | otherwise use `m-buffer-overlay-face-match'." 960 | (seq-map 961 | (lambda (ovly) 962 | (overlay-put ovly 'face face)) 963 | (m-buffer-overlay-match match-data))) 964 | 965 | (defun m-buffer-text-property-face (match-data face) 966 | "To MATCH-DATA apply FACE. 967 | This is for use in buffers which do 968 | not have variable `font-lock-mode' enabled; otherwise use 969 | `m-buffer-text-property-font-lock-face'." 970 | (m-buffer-put-text-property-match match-data 971 | 'face face)) 972 | 973 | (defun m-buffer-text-property-font-lock-face (match-data face) 974 | "To MATCH-DATA apply FACE. 975 | This is for use in buffers which have variable `font-lock-mode' 976 | enabled; otherwise use `m-buffer-text-property-face'." 977 | (m-buffer-put-text-property-match match-data 978 | 'font-lock-face face)) 979 | 980 | (provide 'm-buffer) 981 | 982 | 983 | ;;; m-buffer.el ends here 984 | ;; #+end_src 985 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | ## what ever we called, don't do it here 2 | $(MAKECMDGOALS): 3 | $(MAKE) -C .. $(MAKECMDGOALS) 4 | -------------------------------------------------------------------------------- /test/case-match.txt: -------------------------------------------------------------------------------- 1 | A 2 | a 3 | A 4 | a 5 | -------------------------------------------------------------------------------- /test/line-start.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1 4 | 1 5 | 22 6 | 22 7 | -------------------------------------------------------------------------------- /test/m-buffer-at-test.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2014-2022 Free Software Foundation, Inc. 4 | 5 | (require 'm-buffer-at) 6 | 7 | (ert-deftest m-buffer-at-eolp-1() 8 | (should 9 | (with-temp-buffer 10 | (insert "hello") 11 | (m-buffer-at-eolp (point-marker))))) 12 | 13 | (ert-deftest m-buffer-at-eolp-2 () 14 | (should 15 | (with-temp-buffer 16 | (insert "hello") 17 | (m-buffer-at-eolp 18 | (current-buffer) 19 | (point))))) 20 | 21 | (ert-deftest m-buffer-at-eolp-3 () 22 | (should-not 23 | (with-temp-buffer 24 | (insert "hello") 25 | (goto-char (point-min)) 26 | (m-buffer-at-eolp (point-marker))))) 27 | 28 | (ert-deftest m-buffer-at-eolp-4 () 29 | (should-not 30 | (with-temp-buffer 31 | (insert "hello") 32 | (goto-char (point-min)) 33 | (m-buffer-at-eolp 34 | (current-buffer) 35 | (point))))) 36 | 37 | (ert-deftest m-buffer-string () 38 | (should 39 | (string= 40 | "hello" 41 | (with-temp-buffer 42 | (insert "hello") 43 | (m-buffer-at-string (current-buffer)))))) 44 | -------------------------------------------------------------------------------- /test/m-buffer-init.el: -------------------------------------------------------------------------------- 1 | ;; -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2014-2022 Free Software Foundation, Inc. 4 | 5 | (defvar m-buffer-init-path 6 | (directory-file-name 7 | (file-name-directory load-file-name))) 8 | 9 | -------------------------------------------------------------------------------- /test/m-buffer-test.el: -------------------------------------------------------------------------------- 1 | ;;; m-buffer-test.el --- Tests for m-buffer -*- lexical-binding: t; -*- 2 | 3 | ;; The contents of this file are subject to the GPL License, Version 3.0. 4 | ;; 5 | ;; Copyright (C) 2014-2022 Free Software Foundation, Inc. 6 | ;; 7 | ;; This program is free software: you can redistribute it and/or modify 8 | ;; it under the terms of the GNU General Public License as published by 9 | ;; the Free Software Foundation, either version 3 of the License, or 10 | ;; (at your option) any later version. 11 | ;; 12 | ;; This program is distributed in the hope that it will be useful, 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | ;; GNU General Public License for more details. 16 | ;; 17 | ;; You should have received a copy of the GNU General Public License 18 | ;; along with this program. If not, see . 19 | 20 | ;;; Code: 21 | (require 'm-buffer) 22 | 23 | (defvar m-buffer-test-path 24 | (directory-file-name 25 | (file-name-directory 26 | (or load-file-name 27 | (buffer-file-name 28 | (get-buffer "m-buffer-test.el")))))) 29 | 30 | (defmacro m-buffer-wtb-of-file (file &rest body) 31 | "Run BODY in a temp buffer with the contents of FILE inserted." 32 | `(with-temp-buffer 33 | (insert-file-contents 34 | (concat m-buffer-test-path "/" 35 | ,file)) 36 | ,@body)) 37 | 38 | 39 | (ert-deftest m-with-temp-buffer-of-file () 40 | "Test my test macro." 41 | (should 42 | (equal 43 | "one\ntwo\nthree\n" 44 | (m-buffer-wtb-of-file 45 | "with-temp-buffer.txt" 46 | (buffer-string))))) 47 | 48 | (ert-deftest m-buffer-loaded () 49 | "Has m-buffer loaded at all?" 50 | (should 51 | (fboundp 'm-buffer-match))) 52 | 53 | 54 | (ert-deftest normalize-args () 55 | "Normalize Region" 56 | ;; just buffer and regexp 57 | (should 58 | (equal 59 | (list (current-buffer) "regexp" nil nil nil nil :default nil) 60 | (m-buffer--normalize-args 61 | (list (current-buffer) "regexp")))) 62 | 63 | (should 64 | (equal 65 | (list (current-buffer) "regexp" nil nil nil nil :default nil) 66 | (m-buffer--normalize-args 67 | (list (current-buffer) :regexp "regexp")))) 68 | 69 | (should 70 | (equal 71 | (list (current-buffer) "regexp" 1 2 3 4 :default nil) 72 | (m-buffer--normalize-args 73 | (list (current-buffer) "regexp" :begin 1 :end 2 :post-match 3 :widen 4)))) 74 | 75 | (should 76 | (equal 77 | (list (current-buffer) "regexp" 1 2 3 4 5 nil) 78 | (m-buffer--normalize-args 79 | (list (current-buffer) "regexp" :begin 1 :end 2 :post-match 3 80 | :widen 4 :case-fold-search 5)))) 81 | 82 | (should 83 | (equal 84 | (list (current-buffer) "regexp" 1 2 3 4 5 6) 85 | (m-buffer--normalize-args 86 | (list (current-buffer) "regexp" :begin 1 :end 2 :post-match 3 87 | :widen 4 :case-fold-search 5 :numeric 6))))) 88 | 89 | 90 | (defun m-buffer--flatten (l) 91 | (if (listp l) 92 | (apply #'append 93 | (seq-map 94 | #'m-buffer--flatten l)) 95 | (list l))) 96 | 97 | (ert-deftest m-buffer-matches () 98 | (should 99 | (= 3 100 | (length 101 | (m-buffer-wtb-of-file 102 | "match-data.txt" 103 | (m-buffer-match 104 | (current-buffer) 105 | "^one$"))))) 106 | (should 107 | (seq-every-p 108 | #'markerp 109 | (m-buffer--flatten 110 | (m-buffer-wtb-of-file 111 | "match-data.txt" 112 | (m-buffer-match 113 | (current-buffer) 114 | "^one$")))))) 115 | 116 | (ert-deftest m-buffer-match-begin () 117 | (should 118 | (seq-every-p 119 | #'markerp 120 | (m-buffer-wtb-of-file 121 | "match-data.txt" 122 | (m-buffer-match-begin 123 | (current-buffer) 124 | "^one$"))))) 125 | 126 | 127 | (ert-deftest marker-to-pos () 128 | (should 129 | (equal '(1 1 1) 130 | (m-buffer-marker-to-pos-nil 131 | (list 132 | (copy-marker 1) 133 | (copy-marker 1) 134 | (copy-marker 1)))))) 135 | 136 | (ert-deftest m-buffer-match-begin-pos () 137 | (should 138 | (equal 139 | '(1 9 17) 140 | (m-buffer-wtb-of-file 141 | "match-data.txt" 142 | (m-buffer-match-begin-pos 143 | (current-buffer) 144 | "^one$"))))) 145 | 146 | (ert-deftest m-buffer-nil-marker () 147 | (should 148 | (m-buffer-wtb-of-file 149 | "match-data.txt" 150 | (seq-every-p 151 | (lambda (marker) 152 | (and 153 | (marker-position marker) 154 | (marker-buffer marker))) 155 | (m-buffer-match-begin (current-buffer) "^one$")))) 156 | (should 157 | (m-buffer-wtb-of-file 158 | "match-data.txt" 159 | (seq-every-p 160 | (lambda (marker) 161 | (and 162 | (not (marker-position marker)) 163 | (not (marker-buffer marker)))) 164 | (m-buffer-nil-marker 165 | (m-buffer-match-begin (current-buffer) "^one$")))))) 166 | 167 | (ert-deftest replace-matches () 168 | (should 169 | (equal 170 | '((1 6) (11 16) (21 26)) 171 | (m-buffer-wtb-of-file 172 | "match-data.txt" 173 | (m-buffer-marker-tree-to-pos 174 | (m-buffer-replace-match 175 | (m-buffer-match 176 | (current-buffer) "^one$") "three"))))) 177 | 178 | (should 179 | (equal 180 | "three\ntwo\nthree\ntwo\nthree\ntwo\n" 181 | (m-buffer-wtb-of-file 182 | "match-data.txt" 183 | (m-buffer-replace-match 184 | (m-buffer-match 185 | (current-buffer) "^one$") "three") 186 | (buffer-string))))) 187 | 188 | (ert-deftest page-matches () 189 | (should 190 | (not 191 | (m-buffer-wtb-of-file 192 | "match-data.txt" 193 | (m-buffer-match-page (current-buffer)))))) 194 | 195 | (ert-deftest paragraph-separate () 196 | (should 197 | (m-buffer-match-paragraph-separate (current-buffer)))) 198 | 199 | (ert-deftest line-start () 200 | (should 201 | (equal 202 | '(1 2 3 5 7 10 13) 203 | (m-buffer-wtb-of-file 204 | "line-start.txt" 205 | (m-buffer-marker-to-pos 206 | (m-buffer-match-line-start (current-buffer))))))) 207 | 208 | (ert-deftest line-end () 209 | (should 210 | (equal 211 | '(1 2 4 6 9 12 13) 212 | (m-buffer-wtb-of-file 213 | "line-start.txt" 214 | (m-buffer-marker-to-pos 215 | (m-buffer-match-line-end (current-buffer))))))) 216 | 217 | (ert-deftest first-line () 218 | (should 219 | (equal 220 | '((1 1)) 221 | (m-buffer-wtb-of-file 222 | "line-start.txt" 223 | (m-buffer-marker-tree-to-pos 224 | (m-buffer-match-first-line 225 | (current-buffer))))))) 226 | 227 | (ert-deftest multi-match () 228 | (should 229 | (equal 230 | '((1 4) (5 8) (13 18)) 231 | (m-buffer-wtb-of-file 232 | "one-two-three.txt" 233 | (m-buffer-marker-tree-to-pos 234 | (m-buffer-match-multi 235 | '("one" "two" "three") 236 | :buffer (current-buffer)))))) 237 | ) 238 | 239 | (ert-deftest sentence-end () 240 | (should 241 | (equal 242 | '(15 32 48) 243 | (m-buffer-wtb-of-file 244 | "sentence-end.txt" 245 | (m-buffer-marker-to-pos 246 | (m-buffer-match-sentence-end (current-buffer))))))) 247 | 248 | (ert-deftest buffer-for-match () 249 | (should 250 | (with-temp-buffer 251 | (progn 252 | (insert "a") 253 | (equal 254 | (current-buffer) 255 | (m-buffer-buffer-for-match 256 | (m-buffer-match (current-buffer) "a"))))))) 257 | 258 | (ert-deftest match-n () 259 | (should 260 | (equal 261 | '((1 7 1 4 4 7) (8 14 8 11 11 14) (15 21 15 18 18 21) (22 28 22 25 25 28)) 262 | (m-buffer-wtb-of-file 263 | "nth.txt" 264 | (m-buffer-marker-tree-to-pos 265 | (m-buffer-match 266 | (current-buffer) 267 | "\\(one\\)\\(two\\)"))))) 268 | 269 | (should 270 | (equal 271 | '((1 7)(8 14)(15 21)(22 28)) 272 | (m-buffer-wtb-of-file 273 | "nth.txt" 274 | (m-buffer-marker-tree-to-pos 275 | (m-buffer-match-nth-group 276 | 0 (m-buffer-match 277 | (current-buffer) 278 | "\\(one\\)\\(two\\)")))))) 279 | 280 | (should 281 | (equal 282 | '((1 4) (8 11) (15 18) (22 25)) 283 | (m-buffer-wtb-of-file 284 | "nth.txt" 285 | (m-buffer-marker-tree-to-pos 286 | (m-buffer-match-nth-group 287 | 1 (m-buffer-match 288 | (current-buffer) 289 | "\\(one\\)\\(two\\)"))))))) 290 | 291 | 292 | (ert-deftest apply-functions () 293 | (should 294 | (equal 295 | '("onetwo" "onetwo" "onetwo" "onetwo" "") 296 | (m-buffer-wtb-of-file 297 | "nth.txt" 298 | (m-buffer-on-region 299 | (lambda (from to) 300 | (buffer-substring-no-properties from to)) 301 | (m-buffer-match-line 302 | (current-buffer))))))) 303 | 304 | (ert-deftest case-fold-search () 305 | ;; match everything -- technically this is dependent on the buffer-local 306 | ;; value of case-fold-search 307 | (should 308 | (equal 309 | '((1 2) (3 4) (5 6) (7 8)) 310 | (m-buffer-wtb-of-file 311 | "case-match.txt" 312 | (m-buffer-marker-tree-to-pos 313 | (m-buffer-match 314 | (current-buffer) 315 | "A"))))) 316 | ;; match just upper case (i.e. cfs nil) 317 | (should 318 | (equal 319 | '((1 2)(5 6)) 320 | (m-buffer-wtb-of-file 321 | "case-match.txt" 322 | (m-buffer-marker-tree-to-pos 323 | (m-buffer-match 324 | (current-buffer) 325 | "A" 326 | :case-fold-search nil))))) 327 | ;; match all again 328 | (should 329 | (equal 330 | '((1 2) (3 4) (5 6) (7 8)) 331 | (m-buffer-wtb-of-file 332 | "case-match.txt" 333 | (m-buffer-marker-tree-to-pos 334 | (m-buffer-match 335 | (current-buffer) 336 | "A" 337 | :case-fold-search t)))))) 338 | 339 | 340 | (ert-deftest subtract () 341 | (should 342 | (equal 343 | '((1 6) (17 23) (34 39)) 344 | (m-buffer-wtb-of-file 345 | "sentence-end.txt" 346 | (m-buffer-marker-tree-to-pos 347 | (m-buffer-match-subtract 348 | (m-buffer-match-word 349 | (current-buffer)) 350 | (m-buffer-match 351 | (current-buffer) "sentence"))))))) 352 | 353 | (ert-deftest exact-subtract () 354 | (should 355 | (equal 356 | '((1 1)(2 2)(3 3)) 357 | (m-buffer-match-exact-subtract 358 | '((0 0) (1 1) (2 2) (3 3) (4 4)) 359 | '((0 0) (4 4))))) 360 | 361 | (should 362 | (equal 363 | '((1 1)(2 2)(3 3)) 364 | (m-buffer-match-exact-subtract 365 | '((0 0) (1 1) (2 2) (3 3) (4 4)) 366 | '((-1 -1) (4 4))))) 367 | 368 | (should 369 | (equal 370 | '((1 6) (17 23) (34 39)) 371 | (m-buffer-wtb-of-file 372 | "sentence-end.txt" 373 | (m-buffer-marker-tree-to-pos 374 | (m-buffer-match-exact-subtract 375 | (m-buffer-match-word 376 | (current-buffer)) 377 | (m-buffer-match 378 | (current-buffer) "sentence"))))))) 379 | 380 | (ert-deftest exact-subtract-with-nil () 381 | (should 382 | (equal 383 | '((1 1)) 384 | (m-buffer-match-exact-subtract 385 | '((1 1)) 386 | nil)))) 387 | 388 | (ert-deftest exact-subtract-error-simplified () 389 | (should 390 | (equal 391 | '((2 2)) 392 | (m-buffer-match-exact-subtract 393 | '((1 1) (2 2)) 394 | '((1 1)))))) 395 | 396 | (ert-deftest exact-subtract-error () 397 | "This is a test case for a bug found from linked-buffer." 398 | (should 399 | (equal 400 | '((19 31 19 19)) 401 | (m-buffer-match-exact-subtract 402 | '((1 18 1 1) 403 | (19 31 19 19)) 404 | '((1 18)))))) 405 | 406 | 407 | (ert-deftest replace-point-unmoved () 408 | "After a replace-match has happened point 409 | should not have moved." 410 | (should 411 | (equal 412 | (m-buffer-wtb-of-file 413 | "match-data.txt" 414 | (point-min)) 415 | (m-buffer-wtb-of-file 416 | "match-data.txt" 417 | (m-buffer-replace-match 418 | (m-buffer-match 419 | (current-buffer) "two") 420 | "one") 421 | (point))))) 422 | 423 | (ert-deftest match-error () 424 | "Should error because we try to override existing args." 425 | (should-error 426 | (m-buffer-match-word 427 | (current-buffer) :regexp "notword"))) 428 | 429 | 430 | (ert-deftest partition-by-markers () 431 | (should 432 | (equal 433 | '((nil 1) 434 | (2 2 3 4) 435 | (5 5 6 7) 436 | (8 8 9)) 437 | (m-buffer-partition-by-marker 438 | '(1 2 3 4 5 6 7 8 9) 439 | '(2 5 8) 440 | )))) 441 | 442 | 443 | (ert-deftest point-stationionary-with-current () 444 | "This test addresses a bug where m-buffer did not correctly 445 | protect global state when the buffer being operated on was not 446 | current -- in this case, a match could move point. 447 | 448 | The two clauses are identical, one changing the current buffer 449 | and one changing a buffer which is not current." 450 | (should 451 | (let ((out) (out-point)) 452 | (with-temp-buffer 453 | (insert "one\ntwo\nthree\n") 454 | (setq out (current-buffer)) 455 | (setq out-point (point)) 456 | (m-buffer-match-first-line out) 457 | (= (point) out-point)))) 458 | 459 | (should 460 | (let ((out) (out-point)) 461 | (with-temp-buffer 462 | (insert "one\ntwo\nthree\n") 463 | (setq out (current-buffer)) 464 | (setq out-point (point)) 465 | (with-temp-buffer 466 | (m-buffer-match-first-line out)) 467 | (= (point) out-point))))) 468 | 469 | ;;; m-buffer-test.el ends here 470 | -------------------------------------------------------------------------------- /test/match-data.txt: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | one 4 | two 5 | one 6 | two 7 | -------------------------------------------------------------------------------- /test/nth.txt: -------------------------------------------------------------------------------- 1 | onetwo 2 | onetwo 3 | onetwo 4 | onetwo 5 | -------------------------------------------------------------------------------- /test/one-two-three.txt: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | one 4 | three 5 | -------------------------------------------------------------------------------- /test/sentence-end.txt: -------------------------------------------------------------------------------- 1 | First sentence. 2 | Second sentence. 3 | Third sentence. 4 | -------------------------------------------------------------------------------- /test/with-temp-buffer.txt: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | three 4 | --------------------------------------------------------------------------------