/text.editing

Motion, editing, search, etc. operations for Cluffer buffers

Primary LanguageCommon LispGNU Lesser General Public License v3.0LGPL-3.0

text.editing README

Introduction

This library provides protocols and implementations of those protocols for text editing operations which are required by, for example, text editors or commandline processors (like REPLs). This library relies on the Cluffer library for the fundamental concepts of buffers, lines and cursors. The functionality provided by this library includes:

  • Cursor movement (by various “units”)
  • Insertion and deletion (by various “units”)
  • Transformations (for example changing case or transposing)
  • Killing, copying and inserting
  • Multiple cursors
  • “Mark” cursors and regions
  • Undo (work in progress)
  • Operations on expression (like paredit for Emacs)
  • Search and incremental search
  • Abbreviations (work in progress)

This library does not provide

  • Data structures for editor buffers (the cluffer library does that)
  • Input handling or a command processor
  • Advanced parsing (see incrementalist for that)
  • Syntax highlighting (see TODO for that)
  • Presentation/rendering/display functions for the contents of text buffers

*This library is under active development. Its ASDF system structure, package structure, exported symbols and protocols may change at any time. Consult the NEWS file or the “Changelog” section of the manual for lists of changes in specific versions.*

This document only gives a very brief overview and highlights some features. Proper documentation can be found in the file:documentation directory.

Usage Overview

Using this library typically requires a buffer class defined by the client which includes the desired mixin classes. For example

(defclass my-buffer (text.editing:multiple-site-mixin
                     text.editing:site-mixin
                     cluffer-standard-buffer:buffer)
  ())
#<STANDARD-CLASS COMMON-LISP-USER::MY-BUFFER>

This buffer class is like a Cluffer buffer but also has a primary and zero or more additional sites. Creating an instance of the buffer class automatically creates the primary site an attaches its point cursor to the initial line of the buffer:

(defvar *my-buffer*
  (make-instance 'my-buffer :initial-line (make-instance 'cluffer-standard-line:closed-line)))

(print *my-buffer*)
(print (text.editing:site *my-buffer*))
(print (text.editing:sites *my-buffer*))
(print (text.editing:point (text.editing:site *my-buffer*)))
#<MY-BUFFER 1 line 0 items {1006791483}>
#<SITE 0:0 {1006B03D03}>
(#<SITE 0:0 {1006B03D03}>)
#<CLUFFER-STANDARD-LINE:RIGHT-STICKY-CURSOR 0:0 {1006D4EDC3}>

This buffer instance is usable with the operations provided by this library:

(defvar *point* (text.editing:point *my-buffer*))
(setf (text.editing:items *point* text.editing:buffer :forward)
      (format nil "one~%two!~%three~%"))
(defun summary ()
  (values (coerce (text.editing:items *point* text.editing:buffer :forward) 'string)
          (text.editing:sites *my-buffer*)))
(summary)
"one
two!
three
"
(#<SITE 3:0 {1006B03D03}>)
(text.editing:change-case *point* text.editing:word :backward :up)
(summary)
"one
two!
THREE
"
(#<SITE 2:0 {1006B03D03}>)

While many operations can be applied directly to a cursor, it is preferable use to the “operation protocol” and let the library figure out how and to which object to apply the operation in question:

(text.editing:perform *my-buffer* 'text.editing:move text.editing:word :backward)
(text.editing:perform *my-buffer* 'text.editing:delete text.editing:line :forward)
(summary)
"one

THREE
"
(#<SITE 1:0 {1006B03D03}>)

The deleted word is now in the insertion stack (which is like the “kill ring” in Emacs) and can be inserted:

(loop :repeat 3 :do (text.editing:perform *my-buffer* 'text.editing:yank :forward))
(summary)
"one
two!two!two!
THREE
"
(#<SITE 1:12 {1006B03D03}>)

It is easy to see how the operation protocol comes into play when multiple sites are used:

(text.editing:push-site-relative *my-buffer* text.editing:line :forward)
(summary)
"one
two!two!two!
THREE
"
(#<SITE 1:12 {1006B03D03}> #<SITE 2:5 {100AE016D3}>)
(text.editing:perform *my-buffer* 'text.editing:move text.editing:line-boundary :backward)
(text.editing:perform *my-buffer* 'text.editing:change-case text.editing:word :forward :capital)
(summary)
"one
Two!two!two!
Three
"
(#<SITE 1:3 {1006B03D03}> #<SITE 2:5 {100AE016D3}>)