m-buffer.el 0.10
I have just released m-buffer version 0.10. A pretty minor point release, but I thought I would post about it, as I have not done so far.
Interacting with the text in an Emacs buffer can be a little bit painful
at times. The main issue is that Emacs makes heavy use of global state
its functions, something which has just rather gone out of fashion since
Emacs was designed, and probably for good reason. I know that I am not
the only to find this painful
(n.d.a)
Perhaps, most obvious, is that many functions work on the
current-buffer
buffer, or at point
. As a result, Emacs code tends to
have lots of save-excursion
and with-current-buffer
calls sprinkled
throughout.
We see the same thing in the regexp matching code. So for instance, the Emacs Lisp manual suggests the following:
(while (re-search-forward "foo[ \t]+bar" nil t)
(replace-match "foobar"))
to replace all matches in a buffer. As you should be able to see, the
call to replace-match
contains no reference to what match is to be
replaced. This is passed through the use of the global match data.
Again, this made managable through the use of macros to save and restore
the global state, in this case save-match-data
. So, you end up with:
(save-match-data
(save-excursion
(while (re-search-forward "foo[ \t]+bar" nil t)
(replace-match "foobar"))))
If you want to see this taken to extreme, have a look at the definition
of m-buffer-match
which is a good demonstration of the problem to
which it is a solution.
The use of while
is also less than ideal. It may not be obvious, but
the code above contains a potential non-termination. For instance, the
following should print out the position of every end-of-line in the
buffer. Try it in an Emacs you don’t want.
(while (re-search-forward "$" nil t)
(message "point at: %s" (point)))
It fails (and hangs) because the regexp $
is zero-width in the length.
So, the re-search-forward
moves point to just in front of the first
new line, reports point, and then never moves again. This problem is a
real one, and happens in real
code.
m-buffer.el is my solution. Inspired partially by dash.el it provides (nearly) stateless, list orientated operations of buffer contents. So:
(m-buffer-match (current-buffer) "buffer")
returns a list of match-data
to every occurrence of “buffer” in the
current-buffer
. There are also many, many convienience functions. So
to match the end of the line, you could do:
(m-buffer-match
(current-buffer)
"$" :post-match 'm-buffer-post-match-forward-char)
which avoids the zero-width regexp problem, but it’s even easier to do.
(m-buffer-match-line-end (current-buffer))
Or to print to mini-buffer as before, you can instead combine with dash.el.
(--map
(message "point at: %s" it)
(m-buffer-match-line-end (current-buffer)))
m-buffer has been written for ease of use and not performance. So, by default, it uses markers everywhere which can potentially cause slowdowns in Emacs.I have started to add some functions for dealing with markers which need explicitly clearing explicitly; a traditional use for macros in lisp of closing existing resources. So
(m-buffer-with-markers
((end (m-buffer-match-line-end (current-buffer))))
(--map
(message "point at: %s" it)
end))
leaves the buffer free of stray markers. The m-buffer-with-markers
macro behaves like let*
with cleanup.
I am now working on stateless functions for point — so
m-buffer-at-point
does the same as point
but needs a buffer as an
argument. Most of the code is trivial in this case, it just needs
writing.
I am rather biased, but I like m-buffer very much. It massively
simplified the development of lentic
(n.d.b) Writing that
with embedded while
loops would have been painful in the extreme. I
think I saved more time in using m-buffer than it took to write it. I
think it could simplify many parts of Emacs development significantly. I
hope others think so to.