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
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
with-current-buffer calls sprinkled
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
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
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.
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
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.
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
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
I am rather biased, but I like m-buffer very much. It massively
simplified the development of lentic [@url:www.russet.org.uk/blog/3047]
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.