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 /// Intervals // An interval is just some range within a buffer. Note that the start BP // is inclusive, but the end BP is _exclusive_. define protocol <> () getter interval-start-bp (interval :: ) => (bp :: false-or()); setter interval-start-bp-setter (bp :: false-or(), interval :: ) => (bp :: false-or()); getter interval-end-bp (interval :: ) => (bp :: false-or()); setter interval-end-bp-setter (bp :: false-or(), interval :: ) => (bp :: false-or()); getter interval-buffer (interval :: ) => (buffer :: ); function copy-interval (interval :: , #key skip-test) => (new-interval :: ); getter interval-read-only? (interval :: ) => (read-only? :: ); setter interval-read-only?-setter (read-only? :: , interval :: ) => (read-only? :: ); // Applies the function to all the lines in the buffer/node/section/interval function do-lines (function :: , interval :: type-union(,
), #key from-end?, skip-test) => (); // Applies the function to all the characters in the line/interval function do-characters (function :: , interval :: type-union(, ,
), #key start: _start, end: _end, from-end?, skip-test) => (); function count-lines (interval :: type-union(, ,
), #key skip-test, cache-result?) => (nlines :: ); function count-characters (interval :: type-union(, ,
), #key skip-test, cache-result?) => (nchars :: ); end protocol <>; // An interval ranges from its start BP to its end BP, inclusive define open abstract class () sealed slot interval-start-bp :: , required-init-keyword: start-bp:; sealed slot interval-end-bp :: , required-init-keyword: end-bp:; end class ; define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define sealed method make (class == , #rest initargs, #key start-bp, end-bp, in-order? = #f) => (bp :: ) if (in-order?) // If he said the BPs are already in order, believe him make(, start-bp: copy-bp(start-bp), end-bp: copy-bp(end-bp)) else let (sbp, ebp) = order-bps(start-bp, end-bp); make(, start-bp: copy-bp(sbp), end-bp: copy-bp(ebp)) end end method make; // Simple interface to create a define inline function make-interval (start-bp :: , end-bp :: , #key in-order? = #f) => (interval :: ) assert(bp-buffer(start-bp) == bp-buffer(end-bp), "The BPs %= and %= passed to 'make-interval' are from different buffers", start-bp, end-bp); make(, start-bp: start-bp, end-bp: end-bp, in-order?: in-order?) end function make-interval; define sealed inline method interval-buffer (interval :: ) => (buffer :: ) bp-buffer(interval-start-bp(interval)) end method interval-buffer; // Copies the interval and all of the lines in the interval //--- This presently "flattens" the interval as it copies it, but we //--- also need a version that preserves node and section structure when //--- the interval exactly covers a (possibly nested) set of nodes define method copy-interval (interval :: , #key skip-test = structural-diagram-line?) => (new-interval :: ) let first-line :: false-or() = #f; let last-line :: false-or() = #f; local method copy (line :: , si :: , ei :: , last?); ignore(last?); let line :: = copy-line(line, start: si, end: ei); unless (first-line) first-line := line end; line-previous(line) := last-line; when (last-line) line-next(last-line) := line end; last-line := line; line end method; do-lines(copy, interval, skip-test: skip-test); make-interval(line-start(first-line), line-end(last-line), in-order?: #t) end method copy-interval; // Calls the function with four arguments: the line, the initial index // and final index in the line, and a boolean value that is #t iff it's // the last line in the interval define method do-lines (function :: , interval :: , #key from-end? = #f, skip-test = line-for-display-only?) => () let start-bp :: = interval-start-bp(interval); let end-bp :: = interval-end-bp(interval); let (start-line, start-index, end-line, end-index, step :: ) = if (from-end?) values(bp-line(end-bp), bp-index(end-bp), bp-line(start-bp), bp-index(start-bp), line-previous-in-buffer) else values(bp-line(start-bp), bp-index(start-bp), bp-line(end-bp), bp-index(end-bp), line-next-in-buffer) end; let buffer = bp-buffer(start-bp); assert(buffer == bp-buffer(end-bp), "The interval from %= to %= does not start and end in the same buffer", start-bp, end-bp); block (break) for (line = start-line then step(line, buffer, skip-test: #f)) when (line & (~skip-test | ~skip-test(line))) let (si, ei) = if (from-end?) values(if (line == end-line) end-index else 0 end, if (line == start-line) start-index else line-length(line) end) else values(if (line == start-line) start-index else 0 end, if (line == end-line) end-index else line-length(line) end) end; function(line, si, ei, line == end-line) end; when (~line | line == end-line) break() end end end end method do-lines; define method count-lines (interval :: , #key skip-test = line-for-display-only?, cache-result? = #f) => (nlines :: ) ignore(cache-result?); let n :: = 0; do-lines(method (line :: , si, ei, last?) ignore(line, si, ei, last?); inc!(n) end method, interval, skip-test: skip-test); n end method count-lines; define method count-lines (string :: , #key skip-test = line-for-display-only?, cache-result? = #f) => (nlines :: ) ignore(skip-test, cache-result?); count(string, '\n') + 1 end method count-lines; define method do-characters (function :: , interval :: , #key start: _start, end: _end, from-end? = #f, skip-test = line-for-display-only?) => () ignore(_start, _end); let start-line = bp-line(interval-start-bp(interval)); let end-line = bp-line(interval-end-bp(interval)); local method do-chars (line :: , si :: , ei :: , last?) => () ignore(last?); local method maybe-do-newline () => () // Call function on '\n' if really in interval let length = line-length(line); when (select (line) start-line => (si <= length) & (line ~== end-line | length < ei); end-line => (length < ei); otherwise => #t; end) function('\n', line, length) end end method; when (from-end?) maybe-do-newline() end; do-characters(function, line, start: si, end: ei, from-end?: from-end?); unless (from-end?) maybe-do-newline() end; end method; do-lines(do-chars, interval, from-end?: from-end?, skip-test: skip-test) end method do-characters; define method count-characters (interval :: , #key skip-test = line-for-display-only?, cache-result? = #f) => (nchars :: ) ignore(cache-result?); let last-index = bp-index(interval-end-bp(interval)); let n :: = 0; do-lines(method (line :: , si :: , ei :: , last?) if (~last? | last-index > line-length(line)) // '+ 1' accounts for the newline character... inc!(n, ei - si + 1) else inc!(n, ei - si) end end method, interval, skip-test: skip-test); n end method count-characters; define method count-characters (string :: , #key skip-test = line-for-display-only?, cache-result? = #f) => (nchars :: ) ignore(skip-test, cache-result?); size(string) end method count-characters; // Note that this _does_ include a '\n' character at the end of each line define method as (class :: subclass(), interval :: ) => (string :: ) let string = make(, size: count-characters(interval)); let i :: = 0; do-lines(method (line, si, ei, last?) let n :: = ei - si; // Use the fastest method available to copy the line contents copy-bytes(line-contents(line), si, string, i, n); inc!(i, n); if (~last? | ei > line-length(line)) string[i] := '\n'; inc!(i) end end method, interval, skip-test: diagram-line?); string end method as; // If there are any read-only lines in the interval, just claim that // the whole interval is read-only. Subclasses of are, of // course, free to change this rather blunt approximation define method interval-read-only? (interval :: ) => (read-only? :: ) let buffer = interval-buffer(interval); buffer-read-only?(buffer) | block (return) local method read-only? (line :: , si, ei, last?) ignore(si, ei, last?); when (line-read-only?(line)) return(#t) end end method; do-lines(read-only?, interval); #f end end method interval-read-only?; define method interval-read-only?-setter (read-only? :: , interval :: ) => (read-only? :: ) local method set-read-only (line :: , si, ei, last?) ignore(si, ei, last?); line-read-only?(line) := read-only? end method; do-lines(set-read-only, interval); read-only? end method interval-read-only?-setter; define method bp-within-interval? (bp :: , interval :: ) => (within? :: ) block (return) let start-bp :: = interval-start-bp(interval); let end-bp :: = interval-end-bp(interval); let _line = bp-line(bp); let _index = bp-index(bp); let first-line = bp-line(start-bp); let last-line = bp-line(end-bp); if (first-line == last-line) // Speed up the case of a single-line interval, since // if crops up a lot during drag-selection when (first-line == _line) let first-index = bp-index(start-bp); let last-index = bp-index(end-bp); first-index <= _index & _index <= last-index end else local method within-interval? (line :: , si, ei, last?) ignore(last?); when (_line == line) return(case line == first-line => _index >= si; line == last-line => _index <= ei; otherwise => #t end) end end method; do-lines(within-interval?, interval, skip-test: #f); #f end end end method bp-within-interval?; /// Nodes define protocol <> (<>) getter node-next (node :: ) => (next :: false-or()); setter node-next-setter (next :: false-or(), node :: ) => (next :: false-or()); getter node-previous (node :: ) => (previous :: false-or()); setter node-previous-setter (previous :: false-or(), node :: ) => (previous :: false-or()); getter node-parent (node :: ) => (parent :: false-or()); setter node-parent-setter (parent :: false-or(), node :: ) => (parent :: false-or()); getter node-children (node :: ) => (children :: ); setter node-children-setter (children :: , node :: ) => (children :: ); getter node-buffer (node :: ) => (buffer :: false-or()); setter node-buffer-setter (buffer :: false-or(), node :: ) => (buffer :: false-or()); getter node-lock (node :: ) => (lock :: false-or()); function note-node-changed (node :: ) => (); // For section nodes getter node-section (node :: ) => (section :: false-or(
)); setter node-section-setter (section ::
, node :: ) => (section ::
); getter node-definition-signature (node :: ) => (signature); getter node-definition-name (node :: ) => (name :: false-or()); getter node-definition-type (node :: ) => (type :: false-or()); end protocol <>; // Buffers are composed of a linked list of nodes. Some of the nodes // are likely to be which contain
data. define open abstract primary class (, ) sealed slot node-next :: false-or() = #f, init-keyword: next:; sealed slot node-previous :: false-or() = #f, init-keyword: previous:; // Some buffers have their nodes in a tree structure //--- Finish implementing the the parent/children stuff (e.g., 'do-lines') sealed slot node-parent :: false-or() = #f, init-keyword: parent:; sealed slot node-children :: = #(), init-keyword: children:; sealed slot node-buffer :: false-or() = #f, init-keyword: buffer:; sealed constant slot node-lock :: false-or() = #f, init-keyword: lock:; end class ; define method note-node-changed (node :: ) => () let buffer = node-buffer(node); when (buffer) note-buffer-changed(buffer) end end method note-node-changed; define method do-lines (function :: , node :: , #key from-end? = #f, skip-test) => () ignore(from-end?, skip-test); error("There is no 'do-lines' method for the node %=", node) end method do-lines; define method count-lines (node :: , #key skip-test, cache-result?) => (nlines :: ) ignore(skip-test, cache-result?); error("There is no 'count-lines' method for the node %=", node) end method count-lines; /// Section nodes // A section node is a node that contains a single section. For example, // file buffers consist of a sequence of section nodes, and each section // node contains one section of the file. define open abstract class () sealed slot node-section :: , required-init-keyword: section:; end class ; define sealed inline method make (class == , #rest initargs, #key, #all-keys) => (node :: ) apply(make, , initargs) end method make; // Non-section nodes just return #f for this... define method node-section (node :: ) => (section :: singleton(#f)) #f end method node-section; define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize (); define method make-section-node (buffer :: , section ::
, #key node-class = ) => (node :: ) let start-bp = make(, line: section-start-line(section), index: 0, buffer: buffer); let end-bp = make(, line: section-end-line(section), index: line-length(section-end-line(section)), buffer: buffer, moving?: #t); // Note that we don't associate the node with the buffer yet, // since it will probably get added with 'add-node!' later let node = make(node-class, start-bp: start-bp, end-bp: end-bp, section: section); push!(section-nodes(section), node); node end method make-section-node; define method make-empty-section-node (buffer :: , #key section-class =
, node-class = ) => (node :: ) let section = make-empty-section(section-class: section-class); make-section-node(buffer, section, node-class: node-class) end method make-empty-section-node; // A header node is a special node reserved for the header of a source // file in some language. Language-specific major modes are intended // to provide a concrete class for this. define open abstract class () end class ; // Section nodes use the lock for the section itself define sealed inline method node-lock (node :: ) => (lock :: false-or()) section-lock(node-section(node)) end method node-lock; define method do-lines (function :: , node :: , #key from-end? = #f, skip-test = line-for-display-only?) => () do-lines(function, node-section(node), from-end?: from-end?, skip-test: skip-test) end method do-lines; define method count-lines (node :: , #key skip-test = line-for-display-only?, cache-result? = #f) => (nlines :: ) count-lines(node-section(node), skip-test: skip-test, cache-result?: cache-result?) end method count-lines; /// Definition nodes // A definition node is a special kind of section node that has some // language-specific information associated with it. Language-specific // major modes are intended to provide a concrete class for this. define open abstract class () end class ; define method node-definition-signature (node :: ) => (signature) #f end method node-definition-signature; // The default method asks the section what its signature is define method node-definition-signature (node :: ) => (signature) section-definition-signature(node-section(node)) end method node-definition-signature; define method node-definition-name (node :: ) => (name :: false-or()) #f end method node-definition-name; // The default method asks the section what its name is define method node-definition-name (node :: ) => (name :: false-or()) section-definition-name(node-section(node)) end method node-definition-name; define method node-definition-type (node :: ) => (type :: false-or()) #f end method node-definition-type; define method node-definition-type (node :: ) => (type :: false-or()) section-definition-type(node-section(node)) end method node-definition-type; define sealed inline method make (class == , #rest initargs, #key, #all-keys) => (node :: ) apply(make, , initargs) end method make; define sealed class () end class ; define sealed domain make (singleton()); define sealed domain initialize ();