text.editor-buffer User's Manual
********************************

This manual is for text.editor-buffer version 0.2.0.

   Copyright © 2015 - 2022 Robert Strandh

   Copyright © 2023 - 2025 Jan Moringen

1 Introduction
**************

text.editor-buffer is a library for representing the buffer of a text
editor.  As such, it defines a set of Common Lisp Object System
_protocols_ for client code to interact with the buffer contents in
various ways, and it supplies different _implementations_ of those
protocols for different purposes.

   The buffer protocols have been chosen so that they can fit a variety
of editors.  As a consequence, they are not particularly Emacs-centric.
For example, in Emacs, a newline character is just another character, so
that moving past it using the ‘forward-char’ command changes the line in
which _point_ is located, and using the ‘delete-char’ command when
_point_ is to the left of a newline character joins the line to the next
one.

   In contrast, the buffer protocols documented here are _line oriented_
and there is no newline character; only a sequence of lines.  At some
level, it is of course desirable to have Emacs-compatible commands, but
these commands are written separately, using this buffer protocol to
accomplish the effects.  For example, the Emacs-compatible
‘forward-item’ command (which this library does not provide; see
<https://github.com/scymtym/text.editing> for a possible implementation)
checks whether it is at the end of a line, and if so, detaches the
cursor from that line and attaches it to the next one.  Similarly, the
Emacs compatible ‘delete-item’ command calls
text.editor-buffer:join-line in the buffer protocol to obtain the
desired effect when it is at the end of a line.

   Figure 1.1 gives a simplified
overview of the architecture text.editor-buffer in terms of the most
important concepts and their composition.  For a more complete picture,
see Implementation Protocols.  As mentioned above, a buffer consists of
a sequence of lines which in turn consist of a sequence of items (not
necessarily characters).  In addition to containing items, lines can
have zero, one or multiple cursors associated with them.  Each cursor
has an index which references a spot between two items of the associated
line or the spot before the first item or the spot after the last item.

Figure 1.1: An example of important text.editor-buffer concepts and
their composition for a single buffer.

   By writing the editor commands in two levels like this, we hope it
will be easier to use the buffer protocols to write emulators for other
editors, such as VIM.

   The buffer participates in two different buffer protocols:

  1. The “edit protocol”, used by client editing and cursor-motion
     operations.

  2. The “update protocol”, used by redisplay operations to determine
     what items are contained in the buffer.

   The operations in the edit protocol were designed to be _fast_
(typically around 10 μs) so that it is practical to use these operations
in a loop, say to insert or delete a region, or to accomplish several
operations inside a keyboard macro.  The exceptions are the operations
text.editor-buffer:split and text.editor-buffer:join that take time
proportional to the number of items in the second line.  (1)

   The operations in the update protocol were designed to be called at
the frequency of the _event loop_ of an application, typically once for
each character typed, but also when a window is resized or scrolled (in
which case, these operations are very fast since no modifications to the
buffer have occurred).

   The buffer edit protocols expose two levels of abstraction to client
code:

  1. The _buffer_ level represents the _sequence of lines_ independently
     of how the individual lines are represented.

  2. The _line_ level represents individual lines.

   As mentioned above, the buffer protocols do not pretend to manage any
equivalence between line breaks and some sequence of characters.  It is
up to client code to model such an equivalence if desired.  As a
consequence, the buffer protocols do not allow for a cursor at the
beginning of a line to move backward or a cursor at the end of a line to
move forward.  An attempt at doing so will result in an error being
signaled.  If client code wants to impose a model where the line break
corresponds to (say) the _newline_ character, then it must explicitly
detach and reattach the cursor to a different line in these cases.  It
can manage that in two different ways: either by explicitly testing for
text.editor-buffer:beginning-of-line-p or
text.editor-buffer:end-of-line-p before calling the equivalent buffer
function, or by handling the error that results from the attempt.

   The buffer also does not interpret the meaning of any of items
contained in it.  For instance, whether an item is to be considered part
of a _word_ or not, is not decided at the buffer level, but at the level
of the syntax.  As a consequence, the buffer protocol does not offer any
functions that require such interpretation, such as forward-word,
end-of-paragraph, etc.

   ---------- Footnotes ----------

   (1) We may improve on this performance in the future.

2 External Protocols
********************

2.1 Packages and Use Recommendation
===================================

All symbols that are relevant to external protocols are in the package
named text.editor-buffer.  We recommend against client code _using_ this
package in the sense of the ‘:use’ option to cl:defpackage
(https://novaspec.org/cl/f_defpackage) or in the sense of calling
cl:use-package (https://novaspec.org/cl/f_use-package).  The reason for
this recommendation is that we can not guarantee that future additions
to this library will not define external symbols that conflict with
symbols in the common-lisp package or symbols used by the client for
other purposes.

   Instead, we recommend that client code use explicit package prefixes,
possibly in combination with package-local nicknames.  In addition to
avoiding future conflicts, this practice will make the origin of the
respective symbol obvious from the source code.

2.2 Thread Safety
=================

Operations in text.editor-buffer are not safe to perform concurrently
when the operations either directly involve a common object or the
involved objects are (directly or indirectly) attached to a common
object.  An example of the latter case would be concurrent operations on
two distinct cursors that are attached to lines which in turn are
attached to the same buffer (See Figure 1.1
for the concepts and their relations).  Put
differently, concurrent operations are safe on distinct detached objects
or objects with disjoint attachment relations.  In particular note that
even if the two cursors in the example are attached to different lines,
concurrent operations on the cursors are still unsafe if the lines are
ultimately attached to the same buffer.

2.3 Conditions
==============

text.editor-buffer defines a number of conditions that are signaled when
text.editor-buffer is unable to fulfill the contract stipulated by the
protocol function being used.

 -- Class: editor-buffer-error [text.editor-buffer]

     This condition type is the base of all error conditions signaled by
     text.editor-buffer.  Client code that wishes to handle all error
     conditions signaled by text.editor-buffer may use this condition in
     its condition handlers.

   The following conditions are signaled when an operation on one or
more cursors cannot be performed because the supplied cursors are not in
the correct state in some way such as not being attached to a suitable
line or (indirectly) a suitable buffer.  When any of these conditions is
signaled, the cause is a program error in text.editor-buffer or the
client program (as opposed to something the end user did).

 -- Class: cursors-not-comparable-error [text.editor-buffer]

     This condition is signaled when an attempt is made to compare two
     cursors which are each attached to a line but the lines do not
     belong to the same buffer.  The readers cursor1 and cursor2 can be
     used to obtain the offending cursor objects.

 -- Class: cursor-attached-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use a cursor
     in an operation that requires that cursor to be detached, but the
     cursor used in the operation is attached to a line.

 -- Class: cursor-detached-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use a cursor
     in an operation that requires that cursor to be attached, but the
     cursor used in the operation is not attached to any line.

 -- Class: line-detached-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use a line in
     an operation that requires the line to be attached to a buffer, but
     the line used in the operation is not attached to a buffer.  An
     example of such an operation would be to attempt to get the line
     number of the line, given that the line number of a line is
     determined by the buffer to which the line is attached.

   The following conditions are signaled when an operation cannot be
performed for the specified location within a line or buffer.  The cause
for signaling one of these conditions can be either a program error (in
text.editor-buffer or the client program) or an operation requested by
the end user.  Clients can therefore allow end users to request
operations on invalid locations and handle the resulting conditions by
displaying the condition report to the end user.

 -- Class: beginning-of-line-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use an index
     that is negative, either by moving a cursor there, or by attempting
     to access an item at such an index.

 -- Class: end-of-line-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use an index
     that is too large, either by moving a cursor there, or by
     attempting to access an item at such an index.  Notice that in some
     cases, "too large" means "strictly greater than the number of items
     in a line", and sometimes it means "greater than or equal to the
     number of items in a line".  For example, it is perfectly
     acceptable to move a cursor to an index that is equal to the number
     of items in a line, but it is not acceptable to attempt to access
     an item in a line at that index.

 -- Class: beginning-of-buffer-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use a line
     number that is negative, for example by issuing a "previous line"
     or "goto line" cursor movement command for which the target line
     number that gets passed to text.editor-buffer:find-line is
     negative.

 -- Class: end-of-buffer-error [text.editor-buffer]

     This condition is signaled when an attempt is made to use a line
     number that is too large, for example by issuing a "next line" or
     "goto line" cursor movement command for which the target line
     number that gets passed to text.editor-buffer:find-line is larger
     than the number of lines in the buffer.

 -- Class: invalid-item-index-error [text.editor-buffer]

     This condition is signaled when an attempt is made to access an
     item within a line at an index that is not valid for that line.  An
     invalid index is either negative or too large given the item count
     of the line.  Notice that in some cases, "too large" means
     "strictly greater than the number of items in the line", and
     sometimes it means "greater than or equal to the number of items in
     the line".  For example, it is perfectly acceptable to split a line
     at an index that is equal to the number of items in the line, but
     it is not acceptable to attempt to access an item at that index.

 -- Class: join-last-line-error [text.editor-buffer]

     This condition is signaled when an attempt is made to join the last
     line of a buffer with its (non-existent) successor line.

 -- Class: invalid-line-index-error [text.editor-buffer]

     This condition is signaled when an attempt is made to access a line
     within a buffer at a line number that is not valid for that buffer.
     An invalid index is either negative or too large given the line
     count of the buffer.

 -- Class: bounding-indices-error [text.editor-buffer]

     This condition is signaled when invalid bounding index designators
     are supplied.

     Bounding index designators are accepted as optional arguments by
     the generic function text.editor-buffer:items.

2.4 Protocol Classes
====================

The protocol classes described in this section serve as universal
superclasses for the classes of certain text.editor-buffer objects.  The
purpose of protocol classes is mainly to allow specializing methods on
protocol functions such that the methods are applicable to all objects
that play a certain role in the respective protocol, regardless of the
classes of those objects.

 -- Class: cursor [text.editor-buffer]

     This is the superclass for all cursors.  It should not itself be
     instantiated.  Instead, text.editor-buffer contains two different
     modules each supplying two different subclass that can be
     instantiated.  See Implementations.

     By default, it is recommended that client code instantiate the
     class text.editor-buffer.standard-line:right-sticky-cursor.  This
     is also what text.editor-buffer:make-buffer does by default.

 -- Class: line [text.editor-buffer]

     This class is the superclass for all lines.  It should not itself
     be instantiated.  Instead, text.editor-buffer contains two
     different modules each supplying a different subclass that can be
     instantiated.  See Implementations.

 -- Class: buffer [text.editor-buffer]

     This is the superclass for all buffers.  It should not itself be
     instantiated.  Instead, text.editor-buffer contains different
     modules, each providing a different subclass that can be
     instantiated.  See Implementations.

     By default, it is recommended that client code instantiate the
     class text.editor-buffer.standard-buffer:buffer.  This is also what
     text.editor-buffer:make-buffer does by default.

2.5 Location Comparison Protocol
================================

Cursors which are attached to lines which belong to the same buffer can
be lexicographically ordered based on their line numbers and within-line
indices.  The functions in this protocol allow comparing cursor objects
according to this order.

   This functions in this protocol perform checks for invalid arguments,
group their arguments into one or more binary operator applications,
flip the comparison direction where necessary and then call functions in
the Location Comparison Implementation Protocol which implement
the core behavior based on the classes of the supplied cursors.

 -- Function: location< [text.editor-buffer] cursor &rest more-cursors

     Return true if for each adjacent pair of cursors (c_1, c_2) in the
     sequence of cursors consisting of CURSOR followed by MORE-CURSORS,
     c_1 is positioned before c_2 in the buffer.  This function calls
     the generic function text.editor-buffer.implementation:location<
     for each such pair to check whether the property holds.  As a
     consequence, return true if only CURSOR is supplied.

     If any of the cursor is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.  Unless all cursor are attached to lines which belong to
     the same buffer, a condition of type
     text.editor-buffer:cursors-not-comparable-error is signaled.

 -- Function: location<= [text.editor-buffer] cursor &rest more-cursors

     Return true if for each adjacent pair of cursors (c_1, c_2) in the
     sequence of cursors consisting of CURSOR followed by MORE-CURSORS,
     c_1 is positioned before c_2 or at the same location as c_2 in the
     buffer.  This function calls the generic function
     text.editor-buffer.implementation:location<= for each such pair to
     check whether the property holds.  As a consequence, return true if
     only CURSOR is supplied.

     If any of the cursors is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.  Unless all cursors are attached to lines which belong to
     the same buffer, a condition of type
     text.editor-buffer:cursors-not-comparable-error is signaled.

 -- Function: location= [text.editor-buffer] cursor &rest more-cursors

     Return true if all cursors in the sequence of cursors consisting of
     CURSOR followed by MORE-CURSORS are positioned at the same location
     in the buffer.  This function calls the generic function
     text.editor-buffer.implementation:location= for pairs of cursors to
     check whether the property holds.  As a consequence, return true if
     only CURSOR is supplied.

     If any of the cursors is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.  Unless all cursors are attached to lines which belong to
     the same buffer, a condition of type
     text.editor-buffer:cursors-not-comparable-error is signaled.

 -- Function: location/= [text.editor-buffer] cursor &rest more-cursors

     Return true if no two cursors in the sequence of cursors consisting
     of CURSOR followed by MORE-CURSORS are positioned at the same
     location in the buffer.  This function calls the generic function
     text.editor-buffer.implementation:location= for _all_ pairs of
     cursors to check whether the property is violated.  As a
     consequence, return true if only CURSOR is supplied.

     If any of the cursors is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.  Unless all cursors are attached to lines which belong to
     the same buffer, a condition of type
     text.editor-buffer:cursors-not-comparable-error is signaled.

 -- Function: location>= [text.editor-buffer] cursor &rest more-cursors

     Return true if for each adjacent pair of cursors (c_1, c_2) in the
     sequence of cursors consisting of CURSOR followed by MORE-CURSORS,
     c_1 is positioned after or at the same location as c_2 in the
     buffer.  This function calls the generic function
     text.editor-buffer.implementation:location< for each such pair to
     check whether the property is violated.  As a consequence, return
     true if only CURSOR is supplied.

     If any of the cursors is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.  Unless all cursors are attached to lines which belong to
     the same buffer, a condition of type
     text.editor-buffer:cursors-not-comparable-error is signaled.

 -- Function: location> [text.editor-buffer] cursor &rest more-cursors

     Return true if for each adjacent pair of cursors (c_1, c_2) in the
     sequence of cursors consisting of CURSOR followed by MORE-CURSORS,
     c_1 is positioned strictly after var_2 in the buffer.  This
     function calls the generic function
     text.editor-buffer.implementation:location<= for each such pair to
     check whether the property is violated.  As a consequence, return
     true if only CURSOR is supplied.

     If any of the cursors is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.  Unless all cursors are attached to lines which belong to
     the same buffer, a condition of type
     text.editor-buffer:cursors-not-comparable-error is signaled.

2.6 Item Container Protocol
===========================

The generic functions in this protocol can be applied to
text.editor-buffer:cursor instances and text.editor-buffer:line
instances and text.editor-buffer:buffer instances.

   The functions in this protocol perform checks for invalid arguments
and invalid state then call the generic functions of the Item Container Implementation Protocol
to perform the core behavior based
on the class of the supplied container.

 -- Generic Function: item-count [text.editor-buffer] container

     If CONTAINER is an attached cursor, return the number of items in
     the line to which CONTAINER is attached.  If CONTAINER is not
     currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     If CONTAINER is a line, then return the number of items in that
     line.

     If CONTAINER is a buffer, then return the total number of items in
     that buffer.

 -- Generic Function: items [text.editor-buffer] container &optional
          start end

     Return the sequence items of CONTAINER or the specified subsequence
     as a vector.

     The optional parameters START and END have the same interpretation
     as for the cl:subseq (https://novaspec.org/cl/f_subseq) function.
     If START and END are not valid bounding index designators, a
     condition of type text.editor-buffer:bounding-indices-error is
     signaled.

     If CONTAINER is an attached cursor, operate on the sequence or a
     subsequence of the items of the line to which CONTAINER is
     attached.  If CONTAINER is not currently attached to a line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.

     If CONTAINER is a line, operate on the sequence or a subsequence of
     the items of that line.

     If CONTAINER is a buffer, then operator on the sequence or a
     subsequence of all items in that buffer.

          note: If CONTAINER is a buffer and START and END select only a
          small subsequence of all items, the function will currently
          likely require a similar amount of computation as selecting
          all items.  The reason is that this functions does not
          currently use an efficient method of mapping linear indices
          like START and END to the respective line which contains that
          linear index.

2.7 Line Number Protocol
========================

The generic functions in this protocol can be applied to
text.editor-buffer:cursor instances and text.editor-buffer:line
instances.

 -- Generic Function: line-number [text.editor-buffer] thing

     Return the line number of THING.

     If THING is a cursor and that cursor is attached to a line, then
     the generic function text.editor-buffer:line is called with THING
     as its argument and the return value is used as the argument in a
     recursive call to line-number which behaves as described below.  If
     THING is a cursor and that cursor is not attached to any line, a
     condition of type text.editor-buffer:cursor-detached-error is
     signaled.

     If THING is a line and that line is attached to a buffer, then the
     line number of LINE in that buffer is returned.  The first line of
     the buffer has the number 0.  If THING is a line that is not
     attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

2.8 Buffer Link Protocol
========================

This protocol allows obtaining the buffer, if any, to which an object is
directly or indirectly attached.  The generic functions in this protocol
can be applied to text.editor-buffer:cursor instances and
text.editor-buffer:line instances.

 -- Generic Function: buffer [text.editor-buffer] thing

     Return the buffer to which THING is directly or indirectly
     attached.

     If THING is an attached cursor, then return the buffer of the line
     to which the cursor is attached.  If THING is a cursor that is not
     currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     If THING is a an attached line, then return the buffer to which the
     line is attached.  If THING is a line that is not currently
     attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

2.9 Cursor Protocol
===================

This protocol extends the Location Comparison Protocol,
Item Container Protocol, Line Number Protocol and Buffer Link Protocol.
As a result, the functions that form those protocols
such as text.editor-buffer:location<, text.editor-buffer:item-count,
text.editor-buffer:line-number and text.editor-buffer:buffer are
applicable to cursor objects.

 -- Generic Function: line [text.editor-buffer] cursor

     Return the line to which CURSOR is attached or ‘nil’ if CURSOR is
     not attached.

     This function typically calls
     text.editor-buffer.implementation:line to actually perform the
     operation.

 -- Generic Function: stickiness [text.editor-buffer] cursor

     Return either ‘:left’ or ‘:right’ to indicate the stickiness of
     CURSOR.

     This function typically calls
     text.editor-buffer.implementation:stickiness to actually perform
     the operation.

Cursor Attachment
-----------------

 -- Function: attachedp [text.editor-buffer] cursor

     Return _true_ if CURSOR is attached to a line.

 -- Generic Function: attach [text.editor-buffer] cursor line &optional
          (index 0)

     Attach CURSOR to LINE at INDEX.

     If INDEX is supplied and it is greater than the number of items in
     LINE, the text.editor-buffer:end-of-line-error is signaled.  If
     CURSOR is already attached to a line,
     text.editor-buffer:cursor-attached-error is signaled.

     This function typically calls
     text.editor-buffer.implementation:attach to actually perform the
     operation.

 -- Generic Function: detach [text.editor-buffer] cursor

     Detach CURSOR from the line to which it is attached.

     If CURSOR is already detached, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     This function typically calls
     text.editor-buffer.implementation:detach to actually perform the
     operation.

Cursor Location
---------------

 -- Generic Function: index [text.editor-buffer] cursor

     Return the index of CURSOR in the line to which it is attached.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

 -- Generic Function: (setf index) [text.editor-buffer] new-value cursor

     Set the index of CURSOR to NEW-VALUE in the line to which CURSOR is
     attached.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     If NEW-VALUE is negative, then a condition of type
     text.editor-buffer:beginning-of-line-error is signaled.  If
     NEW-VALUE is strictly greater than the number of items in the line
     to which CURSOR is attached (See text.editor-buffer:item-count),
     then a condition of type text.editor-buffer:end-of-line-error is
     signaled.

 -- Generic Function: beginning-of-line-p [text.editor-buffer] cursor

     Return _true_ if and only if CURSOR is located at the beginning of
     the line to which CURSOR is attached.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Calling this function is equivalent to calling the function
     text.editor-buffer:index with CURSOR as argument and comparing the
     return value to 0.  However, this function might be implemented
     differently for reasons of performance.

 -- Generic Function: end-of-line-p [text.editor-buffer] cursor

     Return _true_ if and only if CURSOR is located at the end of the
     line to which CURSOR is attached.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Calling this function is equivalent to calling the function
     text.editor-buffer:index with CURSOR as argument and comparing the
     return value to the number of items in the line to which CURSOR is
     attached (See text.editor-buffer:item-count).  However, this
     function might be implemented differently for reasons of
     performance.

 -- Generic Function: beginning-of-buffer-p [text.editor-buffer] cursor

     Return _true_ if and only if CURSOR is located at the beginning of
     the buffer to which CURSOR is indirectly attached.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  If CURSOR is
     attached to a line, but that line is not attached to a buffer, a
     condition of type text.editor-buffer:line-detached-error is
     signaled.

 -- Generic Function: end-of-buffer-p [text.editor-buffer] cursor

     Return _true_ if and only if CURSOR is located at the end of the
     buffer to which CURSOR is indirectly attached.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  If CURSOR is
     attached to a line, but that line is not attached to a buffer, a
     condition of type text.editor-buffer:line-detached-error is
     signaled.

Cursor Movement
---------------

 -- Generic Function: backward-item [text.editor-buffer] cursor

     Move CURSOR backward by one index and return CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  If CURSOR is
     at the beginning of the line to which it is attached, an error of
     type text.editor-buffer:beginning-of-line-error is signaled.

     Except for checking of preconditions, calling this function is
     equivalent to decrementing the text.editor-buffer:index of CURSOR.
     However, this function might be implemented differently for reasons
     of performance.

 -- Generic Function: forward-item [text.editor-buffer] cursor

     Move CURSOR forward by one index and return CURSOR

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  If CURSOR is
     at the end of the line to which it is attached, an error of type
     text.editor-buffer:end-of-line-error is signaled.

     Except for checking of preconditions, calling this function is
     equivalent to incrementing the text.editor-buffer:index of CURSOR.
     However, this function might be implemented differently for reasons
     of performance.

 -- Generic Function: beginning-of-line [text.editor-buffer] cursor

     Position CURSOR at the very beginning of the line to which it is
     attached and return CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Except for checking of preconditions, calling this function is
     equivalent to calling the function text.editor-buffer:(setf index)
     with 0 and CURSOR as arguments.  However, this function might be
     implemented differently for reasons of performance.

 -- Generic Function: end-of-line [text.editor-buffer] cursor

     Position CURSOR at the very end of the line to which it is attached
     and return CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Except for checking of preconditions, calling this function is
     equivalent to calling the function text.editor-buffer:(setf index)
     with the number of items in the line to which CURSOR is attached
     (See text.editor-buffer:item-count) and CURSOR as arguments.
     However, this function might be implemented differently for reasons
     of performance.

 -- Generic Function: beginning-of-buffer [text.editor-buffer] cursor

     Position CURSOR at the very beginning of the buffer to which it is
     attached and return CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  Otherwise
     assume CURSOR is attached to LINE.  If LINE is not currently
     attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

     Except for checking of preconditions, calling this function is
     equivalent to calling the function text.editor-buffer:detach with
     CURSOR and then calling text.editor-buffer:attach CURSOR and the
     first line of the buffer as arguments.  However, this function
     might be implemented differently for reasons of performance.

 -- Generic Function: end-of-buffer [text.editor-buffer] cursor

     Position CURSOR at the very end of the buffer to which it is
     attached and return CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  Otherwise
     assume CURSOR is attached to LINE.  If LINE is not currently
     attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

     Except for checking of preconditions, calling this function is
     equivalent to calling the function text.editor-buffer:detach with
     CURSOR and then calling text.editor-buffer:attach CURSOR, the last
     line of the buffer and the item count of that last line as
     arguments.  However, this function might be implemented differently
     for reasons of performance.

Cursor Items
------------

 -- Generic Function: item-before [text.editor-buffer] cursor

     Return the item located immediately before CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     If CURSOR is positioned at the beginning of the line it is attached
     to, signal a condition of type
     text.editor-buffer:beginning-of-line-error.

     Calling this function is equivalent to calling
     text.editor-buffer:item with the line to which CURSOR is attached
     (See text.editor-buffer:line) and the index of CURSOR (See
     text.editor-buffer:index) minus one.  However, this function
     normally calls text.editor-buffer.implementation:item-before and
     the implementation performs the operation in an opaque way that
     allows for better performance or meets other requirements.

 -- Generic Function: item-after [text.editor-buffer] cursor

     Return the item located immediately after CURSOR.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     If CURSOR is positioned at the end of the line it is attached to,
     signal a condition of type text.editor-buffer:end-of-line-error.

     Calling this function is equivalent to calling
     text.editor-buffer:item with the line to which CURSOR is attached
     (See text.editor-buffer:line) and the index of CURSOR (See
     text.editor-buffer:index).  However, this function normally calls
     text.editor-buffer.implementation:item-after and the implementation
     performs the operation in an opaque way that allows for better
     performance or meets other requirements.

 -- Generic Function: insert-item-at [text.editor-buffer] cursor item

     Insert ITEM at the index of CURSOR and return ‘nil’.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Calling this function is equivalent to calling
     text.editor-buffer:insert-item with the line to which CURSOR is
     attached, ITEM, and the index of CURSOR.  However, this function
     normally calls text.editor-buffer.implementation:delete-item-after
     and the implementation performs the operation in an opaque way that
     allows for better performance or meets other requirements.

 -- Generic Function: delete-item-before [text.editor-buffer] cursor

     Delete the item immediately before CURSOR and return ‘nil’.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Calling this function is equivalent to calling
     text.editor-buffer:delete-item with the line to which CURSOR is
     attached and the index of CURSOR minus one.  However, this function
     normally calls text.editor-buffer.implementation:delete-item-before
     and the implementation performs the operation in an opaque way that
     allows for better performance or meets other requirements.

 -- Generic Function: delete-item-after [text.editor-buffer] cursor

     Delete the item immediately after CURSOR and return ‘nil’.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Calling this function is equivalent to calling
     text.editor-buffer:delete-item with the line to which CURSOR is
     attached and the index of CURSOR.  However, this function might be
     implemented differently for reasons of performance.

 -- Generic Function: split-line [text.editor-buffer] cursor

     Split the line to which CURSOR is attached at the index of CURSOR
     into two lines, the line CURSOR is attached to and a newly created
     one, and return the two lines as two values.

     After this operation, items and other cursors which precede the
     index of CURSOR will be contained in the first line, items and
     other cursors which follow the index of CURSOR will be contained in
     the second line.

     After this operation, CURSOR and other cursors with the same index
     will be attached to one of the two returned lines: If the cursor in
     question is left-sticky, it will be attached to the first line, if
     the cursor in question is right-sticky, it will be attached to the
     second line.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.

     Calling this function is equivalent to calling
     text.editor-buffer:split, passing it the line to which CURSOR is
     attached, and the index of CURSOR.  However, this function normally
     calls text.editor-buffer.implementation:split-using-buffer and the
     implementation performs the operation in an opaque way that allows
     for better performance or meets other requirements.

 -- Generic Function: join-line [text.editor-buffer] cursor

     Join the line to which CURSOR is attached with the line following
     it in the buffer to which the line is attached and return the
     single remaining line.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  If CURSOR is
     attached to the last line of the buffer, an error type
     text.editor-buffer:join-last-line-error is signaled.

     Calling this function is equivalent to calling
     text.editor-buffer:join, passing it the line to which CURSOR is
     attached, and the index of CURSOR.  However, this function normally
     calls text.editor-buffer.implementation:join-using-buffer and the
     implementation performs the operation in an opaque way that allows
     for better performance or meets other requirements.

2.10 Line Protocol
==================

This protocol extends the Item Container Protocol, Line Number Protocol
and Buffer Link Protocol.  As a result, the
functions that form those protocols such as
text.editor-buffer:item-count, text.editor-buffer:line-number and
text.editor-buffer:buffer are applicable to line objects.

 -- Generic Function: first-line-p [text.editor-buffer] line

     Return _true_ if LINE is the first line in the buffer it is
     attached to.

     If LINE is not currently attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

 -- Generic Function: last-line-p [text.editor-buffer] line

     Return _true_ if LINE is the last in the buffer it is attached to.

     If LINE is not currently attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

 -- Generic Function: cursors [text.editor-buffer] line

     Return a list of the cursors attached to LINE.

     The order of the cursor objects in the returned list is
     unspecified.  In particular, clients must not rely on the cursor
     objects being ordered according to their text.editor-buffer:index.

     Furthermore, the returned list may be destructively modified by a
     subsequent operation on a related cursor, line or buffer object.
     In other words, clients that retain the returned set of cursors
     must copy the cursor list or otherwise transfer the cursor objects
     into some other container before invoking a subsequent operation.

     Whether LINE is attached or not is of no consequence for this
     operation but the utility of the operation is questionable when
     LINE is detached.

     This function typically calls
     text.editor-buffer.implementation:cursors to actually perform the
     operation.

 -- Generic Function: item [text.editor-buffer] line index

     Return the item located at INDEX in LINE.

     If INDEX is less than zero, a condition of type
     text.editor-buffer:beginning-of-line-error is signaled.  If INDEX
     is greater than or equal to the number of items in LINE (See
     text.editor-buffer:item-count), a condition of type
     text.editor-buffer:end-of-line-error is signaled.

     Whether LINE is attached or not is of no consequence for this
     operation but the utility of the operation is questionable when
     LINE is detached.

     This function typically calls
     text.editor-buffer.implementation:item to actually perform the
     operation.

 -- Generic Function: insert-item [text.editor-buffer] line index item

     Insert ITEM into the items of LINE at index INDEX and return ‘nil’.

     After this operation completes, what happens to cursors located at
     INDEX before the operation depends on the class of the cursor and
     of LINE.  The standard implementation
     provides two kinds of cursors, namely
     text.editor-buffer.standard-line:left-sticky-cursor and
     text.editor-buffer.standard-line:right-sticky-cursor.  For such an
     implementation, after this operation completes, any left-sticky
     cursor located at INDEX will be located before ITEM, and any
     right-sticky cursor located INDEX will be located after ITEM.

     If INDEX is less than 0, a condition of type
     text.editor-buffer:beginning-of-line-error is signaled.  If INDEX
     is greater than the number of items in LINE (See
     text.editor-buffer:item-count), a condition of type
     text.editor-buffer:end-of-line-error is signaled.

     Whether LINE is attached or not is of no consequence for this
     operation but the utility of the operation is questionable when
     LINE is detached.

     This function typically calls
     text.editor-buffer.implementation:insert-item to actually perform
     the operation.

 -- Generic Function: delete-item [text.editor-buffer] line index

     Delete the item at INDEX in LINE and return ‘nil’.

     If INDEX is less than 0, a condition of type
     text.editor-buffer:beginning-of-line-error is signaled.  If
     POSITION is greater than or equal to the number of items in LINE
     (See text.editor-buffer:item-count), a condition of type
     text.editor-buffer:end-of-line-error is signaled.

     Whether LINE is attached or not is of no consequence for this
     operation but the utility of the operation is questionable when
     LINE is detached.

     This function typically calls
     text.editor-buffer.implementation:delete-item to actually perform
     the operation.

 -- Generic Function: split [text.editor-buffer] line index

     Split LINE into two lines at INDEX, LINE and a newly created line,
     and return the newly created line.

     After this operation, LINE will contain the items and cursors of
     LINE that precede INDEX and the new line will contain the items and
     cursors of LINE that follow INDEX.

     After this operation, any left-sticky cursor located at INDEX will
     be located at the end of LINE, and any right-sticky cursor located
     at INDEX will be located at the beginning of the new line.

     If LINE is not currently attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

     To actually perform the operation, this function normally calls
     text.editor-buffer.implementation:split-using-buffer which in turn
     calls text.editor-buffer.implementation:make-line-like with LINE
     and appropriate keyword arguments to create the new line.

 -- Generic Function: join [text.editor-buffer] line

     Join LINE with the line following it in the buffer to which LINE is
     attached which we will call NEXT-LINE and return the single
     remaining line.

     Items and cursors from NEXT-LINE are transferred to LINE.

     If LINE is not currently attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.  If LINE is the
     last line of the buffer to which it is attached, a condition of
     type text.editor-buffer:join-last-line-error is signaled.

     This function normally calls
     text.editor-buffer.implementation:join-using-buffer to actually
     perform the operation.

     note: Notice that the protocols described here do not contain any
     delete-line operation.  This design decision was made on purpose.
     By only providing text.editor-buffer:join, we guarantee that
     removing a line leaves a _trace_ in the buffer in the form of a
     modification operation on the first of the two lines that were
     joined.  This feature is essential in order for the Update Protocol
     to work correctly.

2.11 Buffer Protocol
====================

This protocol extends the Item Container Protocol.  As a result,
the functions that form that protocol such as
text.editor-buffer:item-count are applicable to line objects.

 -- Generic Function: current-time [text.editor-buffer] buffer

     Return the time stamp of the most recent operation that modified
     BUFFER.

     The main operation that client code can perform with the returned
     time stamp is to pass it as an argument to the
     text.editor-buffer:update function.  If the
     text.editor-buffer:update function is called with a time stamp as
     its TIME argument that is greater than or equal to
     text.editor-buffer:current-time of the buffer in question, then a
     single skip operation is issued.  Therefore,
     text.editor-buffer:update must return the value of
     text.editor-buffer:current-time, so that the second of two
     consecutive calls to text.editor-buffer:update with the same time
     stamp will skip the entire buffer.  See Update Protocol.

 -- Generic Function: line-count [text.editor-buffer] buffer

     Return the number of lines in BUFFER.

 -- Generic Function: find-line [text.editor-buffer] buffer line-number

     Return the line in BUFFER with the given LINE-NUMBER.

     If LINE-NUMBER is less than 0 or greater than or equal to the
     number of lines in BUFFER, then the error
     text.editor-buffer:invalid-line-index-error is signaled.

2.12 Update Protocol
====================

The purpose of the buffer update protocol is to, for example in a text
editor application, allow for a number of edit operations to the buffer
without updating the view of the buffer.  This functionality is
important because a single user-level command may result in an arbitrary
number of edit operations to the buffer, and we typically want the view
to be updated only once, when all those edit operations have been
executed.

   At the center of the update protocol is the concept of a “time
stamp”.  The nature of this time stamp is not specified, other than the
fact that its value is incremented for each operation that alters the
contents of the buffer.  The only operation that client code is allowed
to perform on a time stamp is to store it and pass it as an argument to
the protocol function text.editor-buffer:update.  Using a time stamp in
this way, the client indicates the most recent buffer state that has
been displayed in the view (or processed by the client in some other
way).  The update protocol uses the time stamp to report changes to the
client that edit operations have performed on the buffer since the
indicated state.  The initial line of a fresh buffer has a create-time
and a modify-time of 0, and the text.editor-buffer:current-time of a
fresh buffer is also 0.  It follows that a TIME argument of ‘nil’ passed
to text.editor-buffer:update must be interpreted as negative so that a
create operation of that initial line is correctly issued.

   Changes are reported via four callback functions that the client must
supply to the update protocol: create, modify, sync and skip.  The
callbacks only assume that the view (or client in general) keeps a copy
of the structure of the lines of the buffer, and that this copy has a
cursor that is affected by the callbacks.  This cursor can be before the
first line of the view, after the last line of the view, or between two
lines of the view.  When text.editor-buffer:update is called by client
code, the cursor is located before the first line of the view.

 -- Generic Function: update [text.editor-buffer] buffer time sync skip
          modify create

     The BUFFER parameter is a buffer that might have been modified
     since the last call to ‘update’.  The TIME parameter is the time
     stamp of the last time the ‘update’ was called, so that the
     function will report modifications since that time.  In addition to
     a time stamp, the TIME argument can also be ‘nil’, which is
     interpreted as the beginning of time.  Thus, when ‘nil’ is given as
     a value of this argument, the operations generated correspond to
     the creation of the buffer.

     This function returns a new time stamp to be used as the TIME
     argument in the next call to ‘update’.

     The time stamp is specific to each buffer, and more importantly, to
     each _buffer implementation_.  The consequences are unspecified if
     a time stamp returned by calling ‘update’ on one buffer is used in
     a call to ‘update’ with a different buffer.

     The parameters SYNC, SKIP, MODIFY, and CREATE, are designators for
     callback functions that are called by this function.  They are to
     be considered as _update operations_ on some representation of the
     buffer as it was after the previous call to ‘update’.  The
     operations have the following meaning:

     sync

          indicates the first unmodified line after a sequence of new or
          modified lines.  Accordingly, this function is called once,
          following one or more calls to CREATE or MODIFY.  This
          function is called with a single argument: the unmodified
          line.  Client code must compare the current line of its view
          to the argument, and delete the current line repeatedly until
          the two are cl:eq (https://novaspec.org/cl/f_eq).  Finally,
          the client must make the immediately following line the
          current one.

     skip

          indicates that a number of lines have not been subject to any
          modifications since the last ‘update’ call.  The function
          takes a single argument: the number of lines to skip.  This
          function is called _first_ to indicate that a _prefix_ of the
          buffer is unmodified, or after a SYNC operation to indicate
          that that many lines following the one given as argument to
          the SYNC operation are unmodified.  This operation is also
          called when there are unmodified lines at the end of the
          buffer so that the total line count of the buffer corresponds
          to the total number of lines mentioned in the sequence of
          operations.

     modify

          indicates a line that has been modified.  The function is
          called with that line as the argument.  Client code must
          compare the current line of its view to the argument, and
          delete the current line repeatedly until the two are cl:eq
          (https://novaspec.org/cl/f_eq).  It must then take whatever
          action is needed for the modified contents of the line, and
          finally it must make the immediately following line the
          current one.

     create

          indicates a line that has been created.  The function is
          called with that line as the argument.  Client code should
          insert the new line at the position of the cursor, and then
          leave the cursor positioned immediately after the inserted
          line.

     This function typically coerces the values of the SYNC, SKIP,
     MODIFY, and CREATE parameters from function designators to
     functions and then calls text.editor-buffer.implementation:update
     with the modified argument list to actually perform the operation.

2.13 Convenience Functions
==========================

 -- Function: make-buffer [text.editor-buffer] &key (buffer-class
          'text.editor-buffer.standard-buffer:buffer) (line-class
          'text.editor-buffer.standard-line:line) (cursor-class
          'text.editor-buffer.standard-line:right-sticky-cursor)
          initial-contents line-separator

     Create a buffer, a line and possibly a cursor and return two
     values: the buffer and the cursor or ‘nil’.

     The line is contained in the buffer and the cursor, if created, is
     attached to the line.

     BUFFER-CLASS, if supplied, specifies the class of the created
     buffer.  The default is text.editor-buffer.standard-buffer:buffer.

     LINE-CLASS, if supplied, specifies the class of the created line.
     The default is text.editor-buffer.standard-line:line.

     CURSOR-CLASS, if supplied, specifies the class of the created
     cursor.  The default is
     text.editor-buffer.standard-line:right-sticky-cursor.  If
     CURSOR-CLASS is ‘nil’, no cursor is attached to the initial line
     and the second return value of this function is ‘nil’.

     INITIAL-CONTENTS, if supplied, is a sequence of items which should
     be contained in the buffer.  If INITIAL-CONTENTS is not supplied,
     the returned buffer will be empty.  The cursor moves based on its
     stickiness when the elements of INITIAL-CONTENTS are inserted as
     items.  Unless LINE-SEPARATOR is supplied, the entirety of
     INITIAL-CONTENTS is inserted into the initial (and only) line.

     If LINE-SEPARATOR is supplied, elements of INITIAL-CONTENTS that
     are cl:eql (https://novaspec.org/cl/f_eql) to LINE-SEPARATOR act as
     line separators.  Such an element will not be inserted as an item
     but instead causes the items preceding it and the items following
     it to be inserted into different lines.  If INITIAL-CONTENTS is a
     string and LINE-SEPARATOR is ‘#\Newline’, the resulting buffer will
     be similar to how most text editors would interpret that string.

 -- Function: insert-items [text.editor-buffer] line index items &key
          (start 0) end line-separator

     Insert the elements of the sequence ITEMS into LINE at INDEX and
     return LINE.

     If supplied, START and END are designators for a subsequence of
     ITEMS that should be inserted instead of all elements.

     If LINE-SEPARATOR is supplied, elements of ITEMS that are cl:eql
     (https://novaspec.org/cl/f_eql) to LINE-SEPARATOR act as line
     separators.  Such an element will not be inserted as an item but
     instead causes the items preceding it and the items following it to
     be inserted into different lines.

     If LINE is not currently attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

 -- Function: insert-items-at [text.editor-buffer] cursor items &key
          (start 0) end line-separator

     Insert the elements of the sequence ITEMS into the buffer to which
     CURSOR is attached at the location of CURSOR and return CURSOR.

     If supplied, START and END are designators for a subsequence of
     ITEMS that should be inserted instead of all elements.

     If LINE-SEPARATOR is supplied, elements of ITEMS that are cl:eql
     (https://novaspec.org/cl/f_eql) to LINE-SEPARATOR act as line
     separators.  Such an element will not be inserted as an item but
     instead causes the items preceding it and the items following it to
     be inserted into different lines.

     If CURSOR is not currently attached to a line, a condition of type
     text.editor-buffer:cursor-detached-error is signaled.  Otherwise
     assume that CURSOR is attached is attached to LINE.  If LINE is not
     currently attached to a buffer, a condition of type
     text.editor-buffer:line-detached-error is signaled.

 -- Function: safe-line-number [text.editor-buffer] line

     Return ‘nil’ or the line number of LINE.

     The function returns ‘nil’ if line is not attached to a buffer.

     In contrast to text.editor-buffer:line-number and
     text.editor-buffer.implementation:line-number-using-buffer, this
     function does not disturb any data structures of the buffer or line
     implementation and is thus suitable for cl:print-object
     (https://novaspec.org/cl/f_print-object) methods and similar
     situations.  In line with this purpose, this function may have much
     worse performance characteristics than the functions mentioned
     above.

3 Implementation Protocols
**************************

This chapter describes the implementation protocols that
text.editor-buffer uses and provides.  Generally, the protocol functions
described in External Protocols are not specialized to
particular buffer, line or cursor classes.  Those functions check
preconditions of the respective operation and then call one of the
implementation protocol functions to perform the essence of the
operation.  In contrast, implementation protocol functions are usually
specialized to particular implementation classes, do not check
preconditions and do not delegate further.

   As an example, the default method on text.editor-buffer:item-before
when applied to a cursor checks that the cursor is attached to a line
and not positioned at the beginning of that line and then calls
text.editor-buffer.implementation:item-before.  The latter
implementation function retrieves and returns the requested item in an
implementation-specific manner without checking the preconditions again.

Figure 3.1: Important implementation concepts and their relations for a
single buffer.  Note the inclusion of docks and detached objects in
contrast to the simplified architecture diagram in Figure 1.1.

   Figure 3.1 illustrates the important
concepts of text.editor-buffer in more detail than Figure 1.1.
In particular, this version of the diagram
includes the implementation concept of a “dock” which is an indirection
between the buffer object and its attached line objects.  Docks are tied
to the buffer implementation (as opposed to the line implementation) and
allow the buffer implementation to store additional information about a
line without intruding into the line implementation.  As an example, the
buffer part of the Standard Implementation organizes lines in a
tree and stores the tree structure as well as information about subtrees
in its dock objects.

   For many of the generic functions described in this chapter, default
methods exist which are specialized to one of the protocol classes
text.editor-buffer:cursor, text.editor-buffer:line or
text.editor-buffer:buffer and perform the required behavior, possibly in
an inefficient way, by calling other generic functions from the
implementation protocols.  A new implementation does not have to define
methods on generic functions with such a default function but may do so
anyway, for example in order to provide a more efficient implementation.

3.1 Location Comparison Implementation Protocol
===============================================

The functions of the Location Comparison Protocol perform checks
for invalid arguments, group their arguments into one or more binary
operator applications, flip the comparison direction where necessary and
then call the generic functions of this protocol to perform the core
behavior.

 -- Generic Function: location< [text.editor-buffer.implementation]
          location1 location2

     Return _true_ if LOCATION1 is positioned strictly before LOCATION2
     in the buffer.

     All locations have to be attached to lines which in turn must all
     belong to a single buffer.  occur.  These preconditions are not
     checked.  If these preconditions are violated, incorrect behavior
     or errors may occur.

 -- Generic Function: location<= [text.editor-buffer.implementation]
          location1 location2

     Return _true_ if LOCATION1 is positioned before or at the same
     position as LOCATION2 in the buffer.

     All locations have to be attached to lines which in turn must all
     belong to a single buffer.  occur.  These preconditions are not
     checked.  If these preconditions are violated, incorrect behavior
     or errors may occur.

 -- Generic Function: location= [text.editor-buffer.implementation]
          location1 location2

     Return _true_ if LOCATION1 is positioned at the same position as
     LOCATION2 in the buffer.

     All locations have to be attached to lines which in turn must all
     belong to a single buffer.  occur.  These preconditions are not
     checked.  If these preconditions are violated, incorrect behavior
     or errors may occur.

3.2 Item Container Implementation Protocol
==========================================

The default methods of the Item Container Protocol perform
checks for invalid arguments and invalid state then call the generic
functions of this protocol to perform the core behavior.

   This protocol is implemented by line classes and buffer classes but
not cursor classes since those delegate to the respective line they are
attached to.

 -- Generic Function: item-count [text.editor-buffer.implementation]
          container

     Return the number of items in CONTAINER.

 -- Generic Function: items [text.editor-buffer.implementation]
          container start end

     Return the subsequence of items in CONTAINER designated by START
     and END.

     START must be valid start bound for a subsequence of the items in
     CONTAINER.  END must be either nil or valid end bound for a
     subsequence of the items in CONTAINER.  These preconditions are not
     checked.  If these preconditions are violated, incorrect behavior
     or errors may occur.

3.3 Line Link Implementation Protocol
=====================================

 -- Generic Function: line [text.editor-buffer.implementation] thing

     Return ‘nil’ or the line object to which THING is attached.

     If THING is a cursor, return the line to which THING is attached or
     ‘nil’ if THING is not attached.

     If THING is a dock, return the line associated to THING which is a
     particular line.  The association never changes and is never ‘nil’.

3.4 Buffer Link Implementation Protocol
=======================================

 -- Generic Function: buffer [text.editor-buffer.implementation] thing

     Return ‘nil’ or the buffer object to which THING is attached.

     If THING is a line, return the buffer in which THING is contained.
     The default specialized to text.editor-buffer:line first retrieves
     the dock of THING via text.editor-buffer.implementation:dock then
     calls this function on the dock object.

     If THING is a dock, return the buffer in which THING is contained.
     The association never changes and is never ‘nil’.

3.5 Cursor Implementation Protocol
==================================

This protocol, including its sub-protocols, consists of methods for
implementing the behavior of cursors.  An implementation of this
protocol must include the Location Comparison Implementation Protocol
and the Line Link Implementation Protocol.

 -- Generic Function: stickiness [text.editor-buffer.implementation]
          cursor

     Return either ‘:left’ or ‘:right’ to indicate the stickiness of
     CURSOR.

Cursor Attachment
-----------------

 -- Generic Function: attach [text.editor-buffer.implementation] cursor
          line index

     Attach CURSOR to LINE at INDEX and return ‘nil’.

     CURSOR must not already be attached to a line.  INDEX must not be
     negative and has to be less than the number of items in LINE.
     These preconditions are not checked.  If these preconditions are
     violated, incorrect behavior or errors may occur.

 -- Generic Function: detach [text.editor-buffer.implementation] cursor

     Detach CURSOR from the line to which it is attached and return that
     line.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

Cursor Location
---------------

 -- Generic Function: index [text.editor-buffer.implementation] cursor

     Return the index of CURSOR in the line to which it is attached.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

 -- Generic Function: (setf index) [text.editor-buffer.implementation]
          new-value cursor

     Set the index of CURSOR to NEW-VALUE in the line to which cursor is
     attached.

     CURSOR has to be attached to a line.  NEW-VALUE most not be
     negative or strictly greater than the number of items in the line
     to which CURSOR is attached.  This precondition is not checked.  If
     this precondition is violated, incorrect behavior or errors may
     occur.

 -- Generic Function: beginning-of-line-p
          [text.editor-buffer.implementation] cursor

     Return _true_ if and only if CURSOR is located at the beginning of
     the line to which CURSOR is attached.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     exists.  Simple implementations of the cursor protocol can
     therefore forego defining a method on this generic function.  More
     sophisticated implementations might use a more optimized way of
     determining whether CURSOR is at the beginning of the line.

 -- Generic Function: end-of-line-p [text.editor-buffer.implementation]
          cursor

     Return _true_ if and only if CURSOR is located at the end of the
     line to which CURSOR is attached.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     exists.  Simple implementations of the cursor protocol can
     therefore forego defining a method on this generic function.  More
     sophisticated implementations might use a more optimized way of
     determining whether CURSOR is at the end of the line.

 -- Generic Function: beginning-of-buffer-p
          [text.editor-buffer.implementation] cursor

     Return _true_ if and only if CURSOR is located at the end of the
     buffer to which CURSOR is indirectly attached.

     CURSOR has to be attached to a line.  That line has to be attached
     to a buffer.  These preconditions are not checked.  If these
     preconditions are violated, incorrect behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     exists.  Simple implementations of the cursor protocol can
     therefore forego defining a method on this generic function.  More
     sophisticated implementations might use a more optimized way of
     determining whether CURSOR is at the beginning of the buffer.

 -- Generic Function: end-of-buffer-p
          [text.editor-buffer.implementation] cursor

     CURSOR has to be attached to a line.  That line has to be attached
     to a buffer.  These preconditions are not checked.  If these
     preconditions are violated, incorrect behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     exists.  Simple implementations of the cursor protocol can
     therefore forego defining a method on this generic function.  More
     sophisticated implementations might use a more optimized way of
     determining whether CURSOR is at the end of the buffer.

Cursor Movement
---------------

 -- Generic Function: backward-item [text.editor-buffer.implementation]
          cursor

     Move CURSOR backward by one index and return CURSOR.

     CURSOR has to be attached to a line.  CURSOR must not be positioned
     at the beginning of the line it is attached to.  These
     preconditions are not checked.  If these preconditions are
     violated, incorrect behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply decrements the text.editor-buffer.implementation:index
     of CURSOR exists.

 -- Generic Function: forward-item [text.editor-buffer.implementation]
          cursor

     Move CURSOR forward by one index and return CURSOR.

     CURSOR has to be attached to a line.  CURSOR must not be positioned
     at the end of the line it is attached to.  These preconditions are
     not checked.  If these preconditions are violated, incorrect
     behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply increments the text.editor-buffer.implementation:index
     of CURSOR exists.

 -- Generic Function: beginning-of-line
          [text.editor-buffer.implementation] cursor

     Move CURSOR to the beginning of the line it is attached to and
     return CURSOR.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply sets the text.editor-buffer.implementation:index of
     CURSOR to 0 exists.

 -- Generic Function: end-of-line [text.editor-buffer.implementation]
          cursor

     Move CURSOR to the end of the line it is attached to and return
     CURSOR.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply sets the text.editor-buffer.implementation:index of
     CURSOR to the text.editor-buffer.implementation:item-count of the
     line exists.

Cursor Items
------------

 -- Generic Function: item-before [text.editor-buffer.implementation]
          cursor

     Return the item located immediately before CURSOR.

     CURSOR has to be attached to a line.  CURSOR must not be positioned
     at the beginning of the line it is attached to.  These
     preconditions are not checked.  If these preconditions are
     violated, incorrect behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply calls text.editor-buffer.implementation:item with the
     line to which CURSOR is attached and ‘(1- (index CURSOR))’ exists.

 -- Generic Function: item-after [text.editor-buffer.implementation]
          cursor

     Return the item located immediately after CURSOR.

     CURSOR has to be attached to a line.  CURSOR must not be positioned
     at the end of the line it is attached to.  These preconditions are
     not checked.  If these preconditions are violated, incorrect
     behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply calls text.editor-buffer.implementation:item with the
     line to which CURSOR is attached and ‘(index CURSOR)’ exists.

 -- Generic Function: insert-item-at [text.editor-buffer.implementation]
          cursor item

     Insert ITEM into the items of the line to which CURSOR is attached
     at the index of CURSOR and return ‘nil’.

     CURSOR has to be attached to a line.  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply calls text.editor-buffer.implementation:insert-item with
     the line to which CURSOR is attached, ‘(index CURSOR)’ and ITEM
     exists.

 -- Generic Function: delete-item-before
          [text.editor-buffer.implementation] cursor

     Delete the item before CURSOR from the items of the line to which
     CURSOR is attached and return ‘nil’.

     CURSOR has to be attached to a line.  CURSOR must not be located at
     the beginning of the line to which it is attached.  These
     preconditions are not checked.  If these preconditions are
     violated, incorrect behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply calls text.editor-buffer.implementation:delete-item with
     the line to which CURSOR is attached and ‘(1- (index CURSOR))’
     exists.

 -- Generic Function: delete-item-after
          [text.editor-buffer.implementation] cursor

     Delete the item after CURSOR from the items of the line to which
     CURSOR is attached and return ‘nil’.

     CURSOR has to be attached to a line.  CURSOR must not be located at
     the end of the line to which it is attached.  These preconditions
     are not checked.  If these preconditions are violated, incorrect
     behavior or errors may occur.

     A default method which is specialized to text.editor-buffer:cursor
     and simply calls text.editor-buffer.implementation:delete-item with
     the line to which CURSOR is attached and ‘(index CURSOR)’ exists.

3.6 Line Implementation Protocol
================================

This protocol, including its sub-protocols, consists of methods for
implementing the behavior of line objects.  An implementation of this
protocol must include the Item Container Implementation Protocol.

 -- Generic Function: dock [text.editor-buffer.implementation] line

     Return the dock which contains LINE.

 -- Generic Function: (setf dock) [text.editor-buffer.implementation]
          new-value line

     Set the dock of LINE to NEW-VALUE.

 -- Generic Function: first-line-p [text.editor-buffer.implementation]
          line

     Return _true_ if LINE is the first line of the buffer that contains
     it and _false_ otherwise.

     LINE has to be attached to a buffer..  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

 -- Generic Function: last-line-p [text.editor-buffer.implementation]
          line

     Return _true_ if LINE is the last line of the buffer that contains
     it and _false_ otherwise.

     LINE has to be attached to a buffer..  This precondition is not
     checked.  If this precondition is violated, incorrect behavior or
     errors may occur.

 -- Generic Function: cursors [text.editor-buffer.implementation] line

     Return a list of the cursors attached to LINE.

     The order of cursor objects in the returned list is unspecified.
     Furthermore, the returned list may be destructively modified by a
     subsequent operation on a related cursor, line or buffer object.

     Whether LINE is attached to a buffer is of no consequence for this
     operation.

 -- Generic Function: item [text.editor-buffer.implementation] line
          index

     Return the item at index INDEX in LINE.

     INDEX must be between 0 and one less than the item count of LINE.
     This precondition is not checked.  If this precondition is
     violated, incorrect behavior or errors may occur.

     Whether LINE is attached to a buffer is of no consequence for this
     operation.

 -- Generic Function: insert-item [text.editor-buffer.implementation]
          line index item

     Insert ITEM into the items of LINE at index INDEX and return ‘nil’.

     INDEX must be between 0 and the item count of LINE.  This
     precondition is not checked.  If this precondition is violated,
     incorrect behavior or errors may occur.

     The effect of this operation on items and cursors already contained
     in LINE is the same as for text.editor-buffer:insert-item.

     If a LINE is contained in a dock,
     text.editor-buffer.implementation:note-line-changed is called with
     that dock, LINE and 1 as its arguments.

 -- Generic Function: delete-item [text.editor-buffer.implementation]
          line index

     Insert ITEM at INDEX of LINE and return ‘nil’.

     INDEX must be between 0 and one less than the item count of LINE.
     This precondition is not checked.  If this precondition is
     violated, incorrect behavior or errors may occur.

     The effect of this operation on items and cursors already contained
     in LINE is the same as for text.editor-buffer:insert-item.

     If a LINE is contained in a dock,
     text.editor-buffer.implementation:note-line-changed is called with
     that dock, LINE and -1 as its arguments.

 -- Generic Function: split [text.editor-buffer.implementation] line
          index

     Split LINE at INDEX into two lines, LINE and a newly created line
     which is returned.

     LINE contains the items and cursors preceding INDEX and the newly
     created and returned line contains the items and cursors following
     INDEX.  For cursors the index of which is INDEX, whether LINE or
     the new line will contain the cursor depends on the stickiness of
     the cursor: if the cursor is left-sticky, it will be contained by
     LINE, if the cursor is right-sticky, it will be contained by the
     new line.

     Note that this function takes care only of creating the new line as
     well as transferring items and cursors.  Any changes to the
     arrangement of lines within a buffer are handled by
     text.editor-buffer.implementation:split-using-buffer which
     typically calls this function.

     To create the new line, the method on this generic function
     specialized to text.editor-buffer.standard-line:line, calls
     text.editor-buffer.implementation:make-line-like with LINE and the
     keyword arguments ‘:contents’, ‘:first-line-p’ and ‘:last-line-p’.
     The method on this generic function specialized to
     text.editor-buffer.simple-line:line, calls
     text.editor-buffer.implementation:make-line-like with LINE and the
     keyword argument ‘:contents’.

 -- Generic Function: join [text.editor-buffer.implementation] line1
          line2

     Join LINE1 with LINE2 and return the single remaining line.

     Items and cursors from LINE2 are transferred to LINE1.  Note that
     this function takes care only of items and cursors.  Any changes to
     the arrangement of lines within a buffer are handled by
     text.editor-buffer.implementation:join-using-buffer which typically
     calls this function.

 -- Generic Function: make-line-like [text.editor-buffer.implementation]
          line &key contents first-line-p last-line-p &allow-other-keys

     Make and return a new line object similar to LINE.

     The purpose of this function is to allow clients to use custom line
     classes which may or may not be subclasses of the line classes
     provided by text.editor-buffer.  To this end, the function
     text.editor-buffer.implementation:split calls this function when
     splitting a given line by creating a similar line object, also
     retaining the original line object and then distributing items and
     cursors between the two lines.  Since the client can supply an
     initial line of any class when a buffer is created and control the
     class of lines added by splitting, the client has control over the
     creation of all line objects without having to interfere with the
     respective text.editor-buffer.implementation:split method.

     The keyword arguments accepted by this function depend on the class
     of LINE.  When LINE is a text.editor-buffer.standard-line:line, the
     default method on text.editor-buffer.implementation:split calls
     this function with the keyword arguments ‘:contents’,
     ‘:first-line-p’ and ‘:last-line-p’.  When LINE is a
     text.editor-buffer.simple-line:line, the default method on
     text.editor-buffer.implementation:split calls this function with
     the keyword argument ‘:contents’.  For a custom line class with an
     associated custom method on
     text.editor-buffer.implementation:split, a different set of keyword
     arguments may be supplied and accepted.  In general, methods on
     this generic function should accept the same set of initargs as is
     accepted by the respective line class and call cl:make-instance
     (https://novaspec.org/cl/f_make-instance) with the provided
     initargs.

3.7 Dock Protocol
=================

This protocol, including its sub-protocols, consists of methods for
implementing the behavior of dock objects.  An implementation of this
protocol must include the Line Link Implementation Protocol and
the Buffer Link Implementation Protocol.  A dock object is
associated to a particular line object and a particular buffer object
for its whole existence.

 -- Generic Function: note-line-changed
          [text.editor-buffer.implementation] node line item-count-diff

     Adjust information stored in NODE according to ITEM-COUNT-DIFF and
     return unspecified values.

     NODE is the dock that contains LINE.  ITEM-COUNT-DIFF indicates the
     amount by which the item count has changed and is typically either
     1 or -1.

     This function is called when the item count of LINE changes so that
     any summary information such as item counters stored in NODE can be
     adjusted.

3.8 Buffer Implementation Protocol
==================================

This protocol, including its sub-protocols, consists of methods for
implementing the behavior of buffer objects.  An implementation of this
protocol must include the Item Container Implementation Protocol.

 -- Generic Function: current-time [text.editor-buffer.implementation]
          buffer

     Return the time stamp of the most recent operation that modified
     BUFFER.

 -- Generic Function: line-count [text.editor-buffer.implementation]
          buffer

     Return the number of lines in BUFFER.

 -- Generic Function: find-line [text.editor-buffer.implementation]
          buffer line-number

     Return the line with line number LINE-NUMBER in BUFFER.

     LINE-NUMBER must not be negative and has to be smaller than the
     number of lines in BUFFER.  This precondition is not checked.  If
     this precondition is violated, incorrect behavior or errors may
     occur.

 -- Generic Function: line-number-using-buffer
          [text.editor-buffer.implementation] buffer dock line

     Return the line number of LINE within BUFFER.

     The following relations have to be true: ‘(eq DOCK
     (text.editor-buffer.implementation:dock LINE))’ and ‘(eq BUFFER
     (text.editor-buffer.implementation:buffer DOCK))’.  These
     preconditions are not checked.  If these preconditions are
     violated, incorrect behavior or errors may occur.

 -- Generic Function: split-using-buffer
          [text.editor-buffer.implementation] buffer dock line index

     Split LINE at INDEX into two lines, a modified version of LINE and
     a newly created line, and return the newly created line.

     The first line is cl:eq (https://novaspec.org/cl/f_eq) to LINE and
     contains the items and cursors preceding INDEX and the second line
     contains the items and cursors following INDEX.

     The following relations have to be true: ‘(eq DOCK
     (text.editor-buffer.implementation:dock LINE))’ and ‘(eq BUFFER
     (text.editor-buffer.implementation:buffer DOCK))’.  These
     preconditions are not checked.  If these preconditions are
     violated, incorrect behavior or errors may occur.

 -- Generic Function: join-using-buffer
          [text.editor-buffer.implementation] buffer dock line

     Join LINE with the line following it in BUFFER which we will call
     NEXT-LINE and return the single remaining line.

     This function normally calls text.editor-buffer.implementation:join
     with LINE and NEXT-LINE to transfer items and cursors from
     NEXT-LINE to LINE.  NEXT-LINE becomes detached from BUFFER.

     The following relations have to be true: ‘(eq DOCK
     (text.editor-buffer.implementation:dock LINE))’ and ‘(eq BUFFER
     (text.editor-buffer.implementation:buffer DOCK))’.  LINE must not
     be the last line of BUFFER.  These preconditions are not checked.
     If these preconditions are violated, incorrect behavior or errors
     may occur.

3.9 Update Implementation protocol
==================================

 -- Generic Function: update [text.editor-buffer.implementation] buffer
          time sync skip modify create

     This function is typically called by text.editor-buffer:update and
     behaves mostly like that function except
       1. This function dos not allow the value ‘nil’ for the TIME
          parameter.  Instead the caller should indicate the initial
          time, by passing ‘-1’ for TIME.
       2. This function expects the SYNC, SKIP, MODIFY and CREATE
          arguments to be functions instead of function designators.

4 Implementations
*****************

text.editor-buffer includes two modules each of which contains an
implementation of all relevant protocols.  However, the two modules are
optimized for different purposes:

  1. The "simple" module contains an
     implementation that is, as the name suggests, optimized for
     simplicity of its data structures, algorithms and code without any
     performance considerations.  This module serves mainly as a
     reference in the sense that the behavior of other implementations
     can be compared to this baseline, for example in randomized tests.

  2. The "standard" module contains an
     implementation that is optimized for high performance in most usage
     scenarios.  To this end, the module employs more complicated data
     structures, caches and optimized code.

   Clients of text.editor-buffer should generally use the standard
implementation since it provides consistent performance across most
usage scenarios without exposing clients to the internal complexity.
This is why the convenience function text.editor-buffer:make-buffer by
default creates and returns a text.editor-buffer.standard-buffer:buffer
which is set up to contain instances of
text.editor-buffer.standard-line:line.

4.1 Simple Implementation
=========================

The "simple" implementation is distributed across two packages:
text.editor-buffer.simple-line and text.editor-buffer.simple-buffer.
While the two packages are intended to be used together, it is possible
to use only one of those and use a different implementation for the
remaining aspects.

 -- Class: line [text.editor-buffer.simple-line]

     A simple line that can store items and cursors but does not lend
     itself to efficient implementation of the relevant protocol
     operations.

 -- Class: left-sticky-cursor [text.editor-buffer.simple-line]

     A left-sticky cursor that works with instances of
     text.editor-buffer.simple-line:line.

 -- Class: right-sticky-cursor [text.editor-buffer.simple-line]

     A right-sticky cursor that works with instances of
     text.editor-buffer.simple-line:line.

 -- Class: buffer [text.editor-buffer.simple-buffer]

     A simple buffer that can contain line instances and supports the
     buffer protocols but is not efficient at looking up or manipulating
     its lines.

4.2 Standard Implementation
===========================

The "standard" implementation is distributed across two packages:
text.editor-buffer.standard-line and text.editor-buffer.standard-buffer.
While the two packages are intended to be used together, it is possible
to use only one of those and use a different implementation for the
remaining aspects.

 -- Class: line [text.editor-buffer.standard-line]

     A line class that supports an efficient implementation of the edit
     protocol operations by representing the contained items either as a
     simple vector or as a gap buffer.

 -- Class: left-sticky-cursor [text.editor-buffer.standard-line]

     A left-sticky cursor that works with instances of
     text.editor-buffer.standard-line:line.

 -- Class: right-sticky-cursor [text.editor-buffer.standard-line]

     A right-sticky cursor that works with instances of
     text.editor-buffer.standard-line:line.

 -- Class: buffer [text.editor-buffer.standard-buffer]

     A buffer class that supports an efficient implementation of the
     edit protocol operations by representing the contained lines in a
     binary tree and by caching various pieces of information at
     different levels.

     The simplest way to create a correctly configured instance of this
     class is the convenience function text.editor-buffer:make-buffer.

Changelog
*********

Release 0.2 (not yet released)

Release 0.1.0 (2026-02-06)

        •
          Initial import based on Cluffer predominantly written by
          Robert Strandh.

          Major differences compared to Cluffer are listed below.

        •
          All protocols have been split into an external part and an
          implementation part.  The external part is intended to be safe
          and convenient while the implementation part is intended to be
          efficient and minimal.  On the safe vs.  efficient axis,
          functions in external protocols check preconditions while
          functions in implementation protocols assume correct
          arguments.  On the minimal vs.  convenient axis, a good
          example are convenient external functions
          location{<=,<,=,/=,>,>=} each of which accept any number of
          arguments.  On the implementation side, there are only the
          (almost) minimal functions location{<=,<,=} each of which
          accepts exactly two arguments.

        •
          Compared to Cluffer, lines in the standard-line module are
          less frequently converted between the gap-buffer
          representation and the plain vector representation.  This
          change makes many operations more efficient, in particular
          sequences of operations that used to trigger many back and
          forth conversions.

        •
          Compared to Cluffer, it is easier for clients to supply their
          own line subclasses due to two changes: Firstly, when the
          split operation has to make a new line object, it uses the new
          generic function make-line-like on which clients can define
          their own methods.  Secondly, the standard-line module no
          longer uses change-class to implement the transition between
          open and closed lines.  The latter changes also has a positive
          impact on performance in most all tested implementations.

        •
          The standard-buffer module now caches information that is
          needed for mapping line numbers to line objects and vice
          versa.  Compared to Cluffer, these caches allow many common
          operations to be performed without splaying the tree at the
          core of standard-buffer data structure.

        •
          This library offers the convenience functions make-buffer,
          insert-items and insert-items-at for the common tasks of
          creating a buffer and adding items in bulk.