Module: deuce-internals Synopsis: The Deuce editor Author: Scott McKay Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// BPs, that is, buffer pointers define protocol <> () getter bp-line (bp :: ) => (line :: ); setter bp-line-setter (line :: , bp :: ) => (line :: ); getter bp-index (bp :: ) => (index :: ); setter bp-index-setter (index :: , bp :: ) => (index :: ); getter bp-buffer (bp :: ) => (buffer :: ); setter bp-buffer-setter (buffer :: , bp :: ) => (buffer :: ); getter bp-character (bp :: ) => (char :: ); setter bp-character-setter (char :: , bp :: ) => (char :: ); function simple-bp? (bp :: ) => (simple? :: ); function moving-bp? (bp :: ) => (moving? :: ); function bp-less? (bp1 :: , bp2 :: ) => (less? :: ); function bp-greater? (bp1 :: , bp2 :: ) => (greater? :: ); function copy-bp (bp :: ) => (new-bp :: ); function move-bp! (bp :: , line :: , index :: ) => (bp :: ); function kill-bp! (bp :: ) => (); end protocol <>; // A BP ("buffer pointer") points to a line and an index in the line. // "Moving" BPs will get this index updated as insertions and deletions // take place within the same line. define abstract primary class () // _not_ open! sealed slot bp-line :: , required-init-keyword: line:; sealed slot bp-index :: , required-init-keyword: index:; end class ; define sealed inline method make (class == , #rest initargs, #key buffer = #f, moving? = #f, #all-keys) => (bp :: ) let class = if (buffer) if (moving?) else end else if (moving?) else end end; apply(make, class, initargs) end method make; // Simple, error-checking interface to create a define inline function make-bp (line :: , index :: ) => (bp :: ) assert(index >= 0 & index <= line-length(line), "Index %d is out of range for line %= in 'make-bp'", index, line); make(, line: line, index: index) end function make-bp; define sealed method \= (bp1 :: , bp2 :: ) => (equal? :: ) bp-line(bp1) == bp-line(bp2) & bp-index(bp1) = bp-index(bp2) & bp-buffer(bp1) == bp-buffer(bp2) end method \=; define sealed method bp-less? (bp1 :: , bp2 :: ) => (less? :: ) assert(bp-buffer(bp1) == bp-buffer(bp2), "'bp-less?' can't compare BPs from two different buffers: %= and %=", bp1, bp2); let line1 = bp-line(bp1); let line2 = bp-line(bp2); if (line1 == line2) bp-index(bp1) < bp-index(bp2) else line-less?(bp-buffer(bp1), line1, line2) end end method bp-less?; define sealed method bp-greater? (bp1 :: , bp2 :: ) => (greater? :: ) assert(bp-buffer(bp1) == bp-buffer(bp2), "'bp-greater?' can't compare BPs from two different buffers: %= and %=", bp1, bp2); let line1 = bp-line(bp1); let line2 = bp-line(bp2); if (line1 == line2) bp-index(bp1) > bp-index(bp2) else ~line-less?(bp-buffer(bp1), line1, line2) end end method bp-greater?; define inline function order-bps (bp1 :: , bp2 :: ) => (sbp :: , ebp :: ) if (bp1 = bp2 | bp-less?(bp1, bp2)) values(bp1, bp2) else values(bp2, bp1) end end function order-bps; // Moving BPs get their line and index updated as insertions and // deletions occur define open abstract class () end class ; define method initialize (bp :: , #key) => () next-method(); // This line needs to keep track of the new BP push!(line-bps(bp-line(bp)), bp) end method initialize; define sealed inline method moving-bp? (bp :: ) => (moving? :: singleton(#f)) #f end method moving-bp?; define sealed inline method moving-bp? (bp :: ) => (moving? :: singleton(#t)) #t end method moving-bp?; // A simple BP is associated with the "current" buffer, that is, when // you call 'bp-buffer' on a simple BP, you get the value of *buffer* define sealed class () end class ; define sealed class (, ) end class ; define sealed inline method bp-buffer (bp :: ) => (buffer :: ) *buffer* end method bp-buffer; define sealed method copy-bp (bp :: ) => (new-bp :: ) make(, line: bp-line(bp), index: bp-index(bp), moving?: moving-bp?(bp)) end method copy-bp; define sealed inline method simple-bp? (bp :: ) => (simple? :: singleton(#t)) #t end method simple-bp?; // A permanent BP is associated with a given buffer define sealed class () sealed slot bp-buffer :: , required-init-keyword: buffer:; end class ; define sealed class (, ) end class ; define sealed method copy-bp (bp :: ) => (new-bp :: ) make(, line: bp-line(bp), index: bp-index(bp), buffer: bp-buffer(bp), moving?: moving-bp?(bp)) end method copy-bp; define sealed inline method simple-bp? (bp :: ) => (simple? :: singleton(#f)) #f end method simple-bp?; define sealed method as (class :: subclass(), bp :: ) => (bp :: ) make(, line: bp-line(bp), index: bp-index(bp), buffer: bp-buffer(bp), moving?: moving-bp?(bp)) end method as; define sealed method as (class :: subclass(), bp :: ) => (bp :: ) bp end method as; /// Seal the domains on BPs define sealed domain make (singleton()); define sealed domain initialize (); define sealed domain make (singleton()); define sealed domain initialize (); define sealed domain make (singleton()); define sealed domain initialize (); define sealed domain make (singleton()); define sealed domain initialize (); /// BP motion define sealed method move-bp! (bp :: , line :: , index :: ) => (bp :: ) let old-line = bp-line(bp); assert(index >= 0 & index <= line-length(line), "Index %d is out of range for line %= in 'move-bp!'", index, line); if (line == old-line) bp-index(bp) := index else when (moving-bp?(bp)) // It's a moving BP, need to update BP relocation list line-bps(old-line) := remove!(line-bps(old-line), bp); push!(line-bps(line), bp) end; bp-line(bp) := line; bp-index(bp) := index end; bp end method move-bp!; // Ensure no line is hanging onto this BP define sealed method kill-bp! (bp :: ) => () when (moving-bp?(bp)) let line = bp-line(bp); line-bps(line) := remove!(line-bps(line), bp) end end method kill-bp!; // This "works" on diagram lines due to the default method for 'line-contents' define sealed method bp-character (bp :: ) => (char :: ) let line = bp-line(bp); let index = bp-index(bp); if (index = line-length(line)) '\n' else line-contents(line)[index] end end method bp-character; // NB: You can't insert '\n' this way! // Also note that the caller is responsible for calling 'note-line-changed' define sealed method bp-character-setter (char :: , bp :: ) => (char :: ) assert(char ~== '\n', "'bp-character-setter' can't insert newline characters"); let line = bp-line(bp); let index = bp-index(bp); line-contents(line)[index] := char end method bp-character-setter; define sealed method bp-character-before (bp :: ) => (char :: ) let line = bp-line(bp); let index = bp-index(bp); if (index = 0) '\n' else line-contents(line)[index - 1] end end method bp-character-before; define sealed method bp-character-after (bp :: ) => (char :: ) let line = bp-line(bp); let index = bp-index(bp); if (index = line-length(line) - 1) '\n' else line-contents(line)[index + 1] end end method bp-character-after; // Intended only to compare against strings within a single line. // So don't bother comparing against strings with '\n' in them... define sealed method bp-looking-at? (bp :: , string :: ) => (match? :: ) let line = bp-line(bp); let index = bp-index(bp); text-line?(line) & string-equal?(line-contents(line), string, start1: index, end1: min(index + size(string), line-length(line))) end method bp-looking-at?; define sealed method bp-looking-at-word? (bp :: , string :: ) => (match? :: ) let line = bp-line(bp); let index = bp-index(bp); when (text-line?(line)) let contents = line-contents(line); let _end = min(index + size(string), line-length(line)); string-equal?(contents, string, start1: index, end1: _end) & (_end >= line-length(line) | word-syntax(contents[_end]) ~= $word-alphabetic) end end method bp-looking-at-word?; define sealed method bp-looking-at-atom? (bp :: , string :: ) => (match? :: ) let line = bp-line(bp); let index = bp-index(bp); when (text-line?(line)) let contents = line-contents(line); let _end = min(index + size(string), line-length(line)); string-equal?(contents, string, start1: index, end1: _end) & (_end >= line-length(line) | atom-syntax(contents[_end]) ~= $atom-alphabetic) end end method bp-looking-at-atom?;